From 29581f2fc3132f460b3e5f84f311d49aed1c3718 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 30 Oct 2024 00:10:31 -0700 Subject: [PATCH] implicitly cast to bool in bindings Only the node binding's `StatusFlags` and `WriteConfig` interfaces must use explicit boolean values. To implicitly cast those interfaces' values to boolean would - incur much more overhead - lose precise typing information See napi-rs docs about Env and Context. --- README.md | 3 +- bindings/node/src/radio.rs | 61 ++++++++++++++++-------- bindings/node/src/types.rs | 10 ++++ bindings/python/src/radio.rs | 48 +++++++++---------- bindings/python/src/types.rs | 13 +++-- crates/rf24-rs/README.md | 2 +- crates/rf24-rs/src/radio/rf24/details.rs | 9 ++-- crates/rf24-rs/src/radio/rf24/mod.rs | 4 +- crates/rf24-rs/src/radio/rf24/power.rs | 10 ++-- rf24_py.pyi | 28 ++++++----- 10 files changed, 115 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 61c995f..9d57a73 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,13 @@ This includes but is not limited to Linux on RPi. Other points of interest: Here is the intended roadmap: -- [x] implement driver for the nRF24L01 (OTA compatible with other RF24 library) +- [x] implement driver for the nRF24L01 (OTA compatible with RF24 library) This should be HAL-agnostic in terms of MCU. It would also be nice to reimplement the same API (using [rust's `trait` feature][rust-traits]) for use on nRF5x radios. +- [ ] implement a fake BLE API for the nRF24L01 (see [#4](https://github.com/nRF24/rf24-rs/issues/4)) - [ ] implement network layers (OTA compatible with RF24Network and RF24Mesh libraries) - [ ] implement ESB support for nRF5x MCUs. This might be guarded under [cargo features][cargo-feat]. diff --git a/bindings/node/src/radio.rs b/bindings/node/src/radio.rs index 8a9676c..31c2594 100644 --- a/bindings/node/src/radio.rs +++ b/bindings/node/src/radio.rs @@ -1,15 +1,15 @@ #![cfg(target_os = "linux")] use crate::types::{ - AvailablePipe, CrcLength, DataRate, FifoState, HardwareConfig, PaLevel, StatusFlags, - WriteConfig, + coerce_to_bool, AvailablePipe, CrcLength, DataRate, FifoState, HardwareConfig, PaLevel, + StatusFlags, WriteConfig, }; use linux_embedded_hal::{ gpio_cdev::{chips, LineRequestFlags}, spidev::{SpiModeFlags, SpidevOptions}, CdevPin, Delay, SpidevDevice, }; -use napi::{bindgen_prelude::Buffer, Error, Result, Status}; +use napi::{bindgen_prelude::Buffer, Error, JsNumber, Result, Status}; use rf24::radio::prelude::*; @@ -168,10 +168,15 @@ impl RF24 { /// /// @group Basic #[napi] - pub fn send(&mut self, buf: Buffer, ask_no_ack: Option) -> Result { + pub fn send( + &mut self, + buf: Buffer, + #[napi(ts_arg_type = "boolean | number")] ask_no_ack: Option, + ) -> Result { let buf = buf.to_vec(); + let ask_no_ack = coerce_to_bool(ask_no_ack, false)?; self.inner - .send(&buf, ask_no_ack.unwrap_or_default()) + .send(&buf, ask_no_ack) .map_err(|e| Error::new(Status::GenericFailure, format!("{e:?}"))) } @@ -330,9 +335,12 @@ impl RF24 { /// /// @group Configuration #[napi] - pub fn set_lna(&mut self, enable: bool) -> Result<()> { + pub fn set_lna( + &mut self, + #[napi(ts_arg_type = "boolean | number")] enable: JsNumber, + ) -> Result<()> { self.inner - .set_lna(enable) + .set_lna(coerce_to_bool(Some(enable), true)?) .map_err(|e| Error::new(Status::GenericFailure, format!("{e:?}"))) } @@ -345,9 +353,12 @@ impl RF24 { /// /// @group Configuration #[napi] - pub fn allow_ack_payloads(&mut self, enable: bool) -> Result<()> { + pub fn allow_ack_payloads( + &mut self, + #[napi(ts_arg_type = "boolean | number")] enable: JsNumber, + ) -> Result<()> { self.inner - .allow_ack_payloads(enable) + .allow_ack_payloads(coerce_to_bool(Some(enable), false)?) .map_err(|e| Error::new(Status::GenericFailure, format!("{e:?}"))) } @@ -359,9 +370,12 @@ impl RF24 { /// /// @group Configuration #[napi] - pub fn set_auto_ack(&mut self, enable: bool) -> Result<()> { + pub fn set_auto_ack( + &mut self, + #[napi(ts_arg_type = "boolean | number")] enable: JsNumber, + ) -> Result<()> { self.inner - .set_auto_ack(enable) + .set_auto_ack(coerce_to_bool(Some(enable), false)?) .map_err(|e| Error::new(Status::GenericFailure, format!("{e:?}"))) } @@ -369,9 +383,9 @@ impl RF24 { /// /// @group Configuration #[napi] - pub fn set_auto_ack_pipe(&mut self, enable: bool, pipe: u8) -> Result<()> { + pub fn set_auto_ack_pipe(&mut self, enable: JsNumber, pipe: u8) -> Result<()> { self.inner - .set_auto_ack_pipe(enable, pipe) + .set_auto_ack_pipe(coerce_to_bool(Some(enable), false)?, pipe) .map_err(|e| Error::new(Status::GenericFailure, format!("{e:?}"))) } @@ -383,9 +397,12 @@ impl RF24 { /// /// @group Configuration #[napi] - pub fn allow_ask_no_ack(&mut self, enable: bool) -> Result<()> { + pub fn allow_ask_no_ack( + &mut self, + #[napi(ts_arg_type = "boolean | number")] enable: JsNumber, + ) -> Result<()> { self.inner - .allow_ask_no_ack(enable) + .allow_ask_no_ack(coerce_to_bool(Some(enable), false)?) .map_err(|e| Error::new(Status::GenericFailure, format!("{e:?}"))) } @@ -567,9 +584,12 @@ impl RF24 { /// /// @group Advanced #[napi] - pub fn get_fifo_state(&mut self, about_tx: bool) -> Result { + pub fn get_fifo_state( + &mut self, + #[napi(ts_arg_type = "boolean | number")] about_tx: JsNumber, + ) -> Result { self.inner - .get_fifo_state(about_tx) + .get_fifo_state(coerce_to_bool(Some(about_tx), false)?) .map_err(|e| Error::new(Status::GenericFailure, format!("{e:?}"))) .map(|e| FifoState::from_inner(e)) } @@ -631,9 +651,12 @@ impl RF24 { /// /// @group Configuration #[napi] - pub fn set_dynamic_payloads(&mut self, enable: bool) -> Result<()> { + pub fn set_dynamic_payloads( + &mut self, + #[napi(ts_arg_type = "boolean | number")] enable: JsNumber, + ) -> Result<()> { self.inner - .set_dynamic_payloads(enable) + .set_dynamic_payloads(coerce_to_bool(Some(enable), false)?) .map_err(|e| Error::new(Status::GenericFailure, format!("{e:?}"))) } diff --git a/bindings/node/src/types.rs b/bindings/node/src/types.rs index 222a0f9..2204258 100644 --- a/bindings/node/src/types.rs +++ b/bindings/node/src/types.rs @@ -1,5 +1,15 @@ //! This module defines thin wrappers around rust native types to be exposed in node.js +use napi::{JsNumber, Result}; + +/// A private helper to implicitly convert JS numbers to boolean values (falling back to a `default` value) +pub fn coerce_to_bool(napi_instance: Option, default: bool) -> Result { + if let Some(napi_value) = napi_instance { + return napi_value.coerce_to_bool()?.get_value(); + } + return Ok(default); +} + /// Optional configuration parameters to fine tune instantiating the {@link RF24} object. /// Pass this object as third parameter to {@link RF24} constructor. #[napi(object)] diff --git a/bindings/python/src/radio.rs b/bindings/python/src/radio.rs index df29793..5f4c491 100644 --- a/bindings/python/src/radio.rs +++ b/bindings/python/src/radio.rs @@ -150,12 +150,12 @@ impl RF24 { /// This has no effect if auto-ack is disabled or /// [RF24.allow_ask_no_ack] is not enabled. #[pyo3( - signature = (buf, ask_no_ack = false), - text_signature = "(buf: bytes | bytearray, ask_no_ack = False) -> bool", + signature = (buf, ask_no_ack = 0i32), + text_signature = "(buf: bytes | bytearray, ask_no_ack: bool | int = False) -> bool", )] - pub fn send(&mut self, buf: &[u8], ask_no_ack: bool) -> PyResult { + pub fn send(&mut self, buf: &[u8], ask_no_ack: i32) -> PyResult { self.inner - .send(buf, ask_no_ack) + .send(buf, ask_no_ack != 0) .map_err(|e| PyRuntimeError::new_err(format!("{e:?}"))) } @@ -180,12 +180,12 @@ impl RF24 { /// Returns: /// A Boolean that describes if the given `buf` was successfully loaded into the TX FIFO. #[pyo3( - signature = (buf, ask_no_ack = false, start_tx = true), - text_signature = "(buf: bytes | bytearray, ask_no_ack = False, start_tx = True) -> bool", + signature = (buf, ask_no_ack = 0i32, start_tx = 1i32), + text_signature = "(buf: bytes | bytearray, ask_no_ack: bool | int = False, start_tx: bool | int = True) -> bool", )] - pub fn write(&mut self, buf: &[u8], ask_no_ack: bool, start_tx: bool) -> PyResult { + pub fn write(&mut self, buf: &[u8], ask_no_ack: i32, start_tx: i32) -> PyResult { self.inner - .write(buf, ask_no_ack, start_tx) + .write(buf, ask_no_ack != 0, start_tx != 0) .map_err(|e| PyRuntimeError::new_err(format!("{e:?}"))) } @@ -299,9 +299,9 @@ impl RF24 { /// For clone's and module's with a separate PA/LNA circuit (external antenna), /// this function may not behave exactly as expected. Consult the radio module's /// manufacturer. - pub fn set_lna(&mut self, enable: bool) -> PyResult<()> { + pub fn set_lna(&mut self, enable: i32) -> PyResult<()> { self.inner - .set_lna(enable) + .set_lna(enable != 0) .map_err(|e| PyRuntimeError::new_err(format!("{e:?}"))) } @@ -311,9 +311,9 @@ impl RF24 { /// > This feature requires dynamically sized payloads. /// > Use [`RF24.set_dynamic_payloads(True)`][rf24_py.RF24.set_dynamic_payloads] /// > to enable dynamically sized payloads. - pub fn allow_ack_payloads(&mut self, enable: bool) -> PyResult<()> { + pub fn allow_ack_payloads(&mut self, enable: i32) -> PyResult<()> { self.inner - .allow_ack_payloads(enable) + .allow_ack_payloads(enable != 0) .map_err(|e| PyRuntimeError::new_err(format!("{e:?}"))) } @@ -325,9 +325,9 @@ impl RF24 { /// /// Parameters: /// enable: Pass true to enable the auto-ack feature for all pipes. - pub fn set_auto_ack(&mut self, enable: bool) -> PyResult<()> { + pub fn set_auto_ack(&mut self, enable: i32) -> PyResult<()> { self.inner - .set_auto_ack(enable) + .set_auto_ack(enable != 0) .map_err(|e| PyRuntimeError::new_err(format!("{e:?}"))) } @@ -340,9 +340,9 @@ impl RF24 { /// Parameters: /// enable: Pass true to enable the auto-ack feature for the specified `pipe`. /// pipe: The pipe about which to control the auto-ack feature. - pub fn set_auto_ack_pipe(&mut self, enable: bool, pipe: u8) -> PyResult<()> { + pub fn set_auto_ack_pipe(&mut self, enable: i32, pipe: u8) -> PyResult<()> { self.inner - .set_auto_ack_pipe(enable, pipe) + .set_auto_ack_pipe(enable != 0, pipe) .map_err(|e| PyRuntimeError::new_err(format!("{e:?}"))) } @@ -352,9 +352,9 @@ impl RF24 { /// enable: Setting this to `true` will allow the `ask_no_ack` parameter to /// take effect. See [`RF24.send()`][rf24_py.RF24.send] and /// [`RF24.write()`][rf24_py.RF24.write] for more detail. - pub fn allow_ask_no_ack(&mut self, enable: bool) -> PyResult<()> { + pub fn allow_ask_no_ack(&mut self, enable: i32) -> PyResult<()> { self.inner - .allow_ask_no_ack(enable) + .allow_ask_no_ack(enable != 0) .map_err(|e| PyRuntimeError::new_err(format!("{e:?}"))) } @@ -494,9 +494,9 @@ impl RF24 { /// Parameters: /// about_tx: True returns data about the TX FIFO. /// False returns data about the RX FIFO. - pub fn get_fifo_state(&mut self, about_tx: bool) -> PyResult { + pub fn get_fifo_state(&mut self, about_tx: i32) -> PyResult { self.inner - .get_fifo_state(about_tx) + .get_fifo_state(about_tx != 0) .map_err(|e| PyRuntimeError::new_err(format!("{e:?}"))) .map(|e| FifoState::from_inner(e)) } @@ -544,9 +544,9 @@ impl RF24 { /// enable: If set to `true`, the statically sized payload length (set via /// [`RF24.payload_length`][rf24_py.RF24.payload_length]) are not /// used. - pub fn set_dynamic_payloads(&mut self, enable: bool) -> PyResult<()> { + pub fn set_dynamic_payloads(&mut self, enable: i32) -> PyResult<()> { self.inner - .set_dynamic_payloads(enable) + .set_dynamic_payloads(enable != 0) .map_err(|e| PyRuntimeError::new_err(format!("{e:?}"))) } @@ -633,8 +633,8 @@ impl RF24 { /// Setting this attribute to `True` is equivalent to calling /// [`power_up()`][rf24_py.RF24.power_up] (using default delay). #[setter] - pub fn set_power(&mut self, enable: bool) -> PyResult<()> { - if enable { + pub fn set_power(&mut self, enable: i32) -> PyResult<()> { + if enable != 0 { self.power_up(None) } else { self.power_down() diff --git a/bindings/python/src/types.rs b/bindings/python/src/types.rs index cfb8bc6..0528224 100644 --- a/bindings/python/src/types.rs +++ b/bindings/python/src/types.rs @@ -21,12 +21,15 @@ pub struct StatusFlags { #[pymethods] impl StatusFlags { #[new] - #[pyo3(signature = (rx_dr = false, tx_ds = false, tx_df = false))] - fn new(rx_dr: bool, tx_ds: bool, tx_df: bool) -> Self { + #[pyo3( + signature = (rx_dr = 0i32, tx_ds = 0i32, tx_df = 0i32), + text_signature = "(rx_dr: bool = False, tx_ds: bool = False, tx_df: bool = False) -> StatusFlags", + )] + fn new(rx_dr: i32, tx_ds: i32, tx_df: i32) -> Self { Self { - rx_dr, - tx_ds, - tx_df, + rx_dr: rx_dr != 0, + tx_ds: tx_ds != 0, + tx_df: tx_df != 0, } } diff --git a/crates/rf24-rs/README.md b/crates/rf24-rs/README.md index 12c42a3..af8cd70 100644 --- a/crates/rf24-rs/README.md +++ b/crates/rf24-rs/README.md @@ -1,6 +1,6 @@ # rf24-rs -This is a rust driver library for the nRF24L01 wireless transceivers. +This crate is a rust driver library for the nRF24L01 wireless transceivers. See the examples in the repository's [examples/rust](https://github.com/nRF24/rf24-rs/blob/main/examples/rust) folder. diff --git a/crates/rf24-rs/src/radio/rf24/details.rs b/crates/rf24-rs/src/radio/rf24/details.rs index a04c349..4e7520b 100644 --- a/crates/rf24-rs/src/radio/rf24/details.rs +++ b/crates/rf24-rs/src/radio/rf24/details.rs @@ -8,7 +8,7 @@ use super::{mnemonics, registers}; use crate::{ radio::prelude::{ EsbChannel, EsbCrcLength, EsbDataRate, EsbFifo, EsbPaLevel, EsbPayloadLength, EsbPipe, - EsbStatus, + EsbPower, EsbStatus, }, StatusFlags, }; @@ -124,10 +124,7 @@ where "Primary Mode______________{=istr}X", if self._config_reg & 1 > 0 { rx } else { tx } ); - defmt::println!( - "Powered Up________________{=bool}", - self._config_reg & 2 > 0 - ); + defmt::println!("Powered Up________________{=bool}", self.is_powered()); // print pipe addresses self.spi_read(5, registers::TX_ADDR)?; @@ -259,7 +256,7 @@ where "Primary Mode______________{}X", if self._config_reg & 1 > 0 { "R" } else { "T" } ); - std::println!("Powered Up________________{}", self._config_reg & 2 > 0); + std::println!("Powered Up________________{}", self.is_powered()); // print pipe addresses self.spi_read(5, registers::TX_ADDR)?; diff --git a/crates/rf24-rs/src/radio/rf24/mod.rs b/crates/rf24-rs/src/radio/rf24/mod.rs index 56d1b39..1a493ac 100644 --- a/crates/rf24-rs/src/radio/rf24/mod.rs +++ b/crates/rf24-rs/src/radio/rf24/mod.rs @@ -65,8 +65,8 @@ where /// `spi` bus with the given `ce_pin`. /// /// The radio's CSN pin (aka Chip Select pin) shall be defined - /// when instantiating the [`SpiDevice`] object (passed to the - /// `spi` parameter). + /// when instantiating the [`SpiDevice`](trait@embedded-hal::spi::SpiDevice) + /// object (passed to the `spi` parameter). pub fn new(ce_pin: DO, spi: SPI, delay_impl: DELAY) -> RF24 { RF24 { _status: 0, diff --git a/crates/rf24-rs/src/radio/rf24/power.rs b/crates/rf24-rs/src/radio/rf24/power.rs index 6168fc1..01cf1cf 100644 --- a/crates/rf24-rs/src/radio/rf24/power.rs +++ b/crates/rf24-rs/src/radio/rf24/power.rs @@ -37,8 +37,8 @@ where self.spi_write_byte(registers::CONFIG, self._config_reg)?; // For nRF24L01+ to go from power down mode to TX or RX mode it must first pass through stand-by mode. - // There must be a delay of Tpd2stby (see Table 16.) after the nRF24L01+ leaves power down mode before - // the CEis set high. - Tpd2stby can be up to 5ms per the 1.0 datasheet + // There must be a delay of Tpd2standby (see Table 16.) after the nRF24L01+ leaves power down mode before + // the CE is set high. Tpd2standby can be up to 5ms per the 1.0 datasheet if delay.is_some_and(|val| val > 0) || delay.is_none() { self._delay_impl.delay_ns(delay.unwrap_or(5000000)); } @@ -47,7 +47,7 @@ where /// Is the radio powered up? fn is_powered(&self) -> bool { - (self._config_reg & 2) != 2 + (self._config_reg & 2) > 0 } } @@ -110,6 +110,7 @@ mod test { let mut spi_mock = SpiMock::new(&spi_expectations); let mut radio = RF24::new(pin_mock.clone(), spi_mock.clone(), delay_mock); radio.power_up(Some(0)).unwrap(); + assert!(radio.is_powered()); spi_mock.done(); pin_mock.done(); } @@ -126,7 +127,8 @@ mod test { let spi_expectations = vec![]; let mut spi_mock = SpiMock::new(&spi_expectations); let radio = RF24::new(pin_mock.clone(), spi_mock.clone(), delay_mock); - assert!(radio.is_powered()); + // without calling `RF24::init()`, the lib _assumes_ the radio is powered down. + assert!(!radio.is_powered()); spi_mock.done(); pin_mock.done(); } diff --git a/rf24_py.pyi b/rf24_py.pyi index 207c99d..6d044a7 100644 --- a/rf24_py.pyi +++ b/rf24_py.pyi @@ -23,7 +23,10 @@ class DataRate(Enum): class StatusFlags: def __init__( - self, rx_dr: bool = False, tx_ds: bool = False, tx_df: bool = False + self, + rx_dr: bool | int = False, + tx_ds: bool | int = False, + tx_df: bool | int = False, ): ... @property def rx_dr(self) -> bool: ... @@ -46,9 +49,12 @@ class RF24: def is_rx(self) -> bool: ... def as_rx(self) -> None: ... def as_tx(self) -> None: ... - def send(self, buf: bytes | bytearray, ask_no_ack: bool = False) -> bool: ... + def send(self, buf: bytes | bytearray, ask_no_ack: bool | int = False) -> bool: ... def write( - self, buf: bytes | bytearray, ask_no_ack: bool = False, start_tx: bool = True + self, + buf: bytes | bytearray, + ask_no_ack: bool | int = False, + start_tx: bool | int = True, ) -> bool: ... def read(self, len: int | None = None) -> bytes: ... def resend(self) -> bool: ... @@ -60,11 +66,11 @@ class RF24: def rpd(self) -> bool: ... def start_carrier_wave(self, level: PaLevel, channel: int) -> None: ... def stop_carrier_wave(self) -> None: ... - def set_lna(self, enable: bool) -> None: ... - def allow_ack_payloads(self, enable: bool) -> None: ... - def set_auto_ack(self, enable: bool) -> None: ... - def set_auto_ack_pipe(self, enable: bool, pipe: int) -> None: ... - def allow_ask_no_ack(self, enable: bool) -> None: ... + def set_lna(self, enable: bool | int) -> None: ... + def allow_ack_payloads(self, enable: bool | int) -> None: ... + def set_auto_ack(self, enable: bool | int) -> None: ... + def set_auto_ack_pipe(self, enable: bool | int, pipe: int) -> None: ... + def allow_ask_no_ack(self, enable: bool | int) -> None: ... def write_ack_payload(self, pipe: int, buf: bytes | bytearray) -> bool: ... def set_auto_retries(self, count: int, delay: int) -> None: ... @property @@ -83,7 +89,7 @@ class RF24: def available_pipe(self) -> tuple[bool, int]: ... def flush_rx(self) -> None: ... def flush_tx(self) -> None: ... - def get_fifo_state(self, about_tx: bool) -> FifoState: ... + def get_fifo_state(self, about_tx: bool | int) -> FifoState: ... @property def pa_level(self) -> PaLevel: ... @pa_level.setter @@ -92,7 +98,7 @@ class RF24: def payload_length(self) -> int: ... @payload_length.setter def payload_length(self, length: int) -> None: ... - def set_dynamic_payloads(self, enable: bool) -> None: ... + def set_dynamic_payloads(self, enable: bool | int) -> None: ... def get_dynamic_payload_length(self) -> int: ... def open_rx_pipe(self, pipe: int, address: bytes | bytearray) -> None: ... def open_tx_pipe(self, address: bytes | bytearray) -> None: ... @@ -104,7 +110,7 @@ class RF24: @property def power(self) -> bool: ... @power.setter - def power(self, enable: bool) -> None: ... + def power(self, enable: bool | int) -> None: ... def power_down(self) -> None: ... def power_up(self, delay: int | None = None) -> None: ... def set_status_flags(self, flags: StatusFlags | None = None) -> None: ...