From 495e9b4767780280261c2efd2cb1436040c80a41 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 23 Nov 2019 00:41:47 -0800 Subject: [PATCH 001/127] Delete .gitattributes --- .gitattributes | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index dfe0770..0000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# Auto detect text files and perform LF normalization -* text=auto From 9b6d3b97d1ee61f79a544ba47d987833c84535a4 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Mon, 17 Feb 2020 16:02:48 -0800 Subject: [PATCH 002/127] found pin conflict in irq example -> uses d12 now --- README.rst | 2 +- examples/nrf24l01_interrupt_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index fc94404..5ac633e 100644 --- a/README.rst +++ b/README.rst @@ -114,7 +114,7 @@ The nRF24L01 is controlled through SPI so there are 3 pins (SCK, MOSI, & MISO) t +------------+----------------+----------------+ | MISO | GPIO9 (MISO) | MISO | +------------+----------------+----------------+ -| IRQ | GPIO4 | D4 | +| IRQ | GPIO4 | D12 | +------------+----------------+----------------+ .. tip:: User reports and personal experiences have improved results if there is a capacitor of 100 mirofarads [+ another optional 0.1 microfarads capacitor for added stability] connected in parrallel to the VCC and GND pins. diff --git a/examples/nrf24l01_interrupt_test.py b/examples/nrf24l01_interrupt_test.py index d595895..428d08f 100644 --- a/examples/nrf24l01_interrupt_test.py +++ b/examples/nrf24l01_interrupt_test.py @@ -11,7 +11,7 @@ address = b'1Node' # select your digital input pin that's connected to the IRQ pin on the nRF4L01 -irq = dio.DigitalInOut(board.D4) +irq = dio.DigitalInOut(board.D12) irq.switch_to_input() # make sure its an input object # change these (digital output) pins accordingly ce = dio.DigitalInOut(board.D4) From 3ad18e5368dc0c9f2b74c7442b5a017020b3c0d8 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 20 Mar 2020 19:06:07 -0700 Subject: [PATCH 003/127] update from logitech-mouse branch --- circuitpython_nrf24l01/fake_ble.py | 16 +- circuitpython_nrf24l01/registers.py | 17 - circuitpython_nrf24l01/rf24.py | 859 +++++++++++++++------------- docs/api.rst | 290 ++++++++-- examples/nrf24l01_stream_test.py | 19 +- 5 files changed, 702 insertions(+), 499 deletions(-) delete mode 100644 circuitpython_nrf24l01/registers.py diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index 282cf8d..98ff0c1 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -129,7 +129,7 @@ class FakeBLE(RF24): Options are ``0`` (dBm), ``-6`` (dBm), ``-12`` (dBm), or ``-18`` (dBm). This can be changed at any time by using the `pa_level` attribute. """ - def __init__(self, spi, csn, ce, name=None, pa_level=False, irq_DR=False, irq_DS=True): + def __init__(self, spi, csn, ce, name=None, pa_level=False, irq_dr=False, irq_ds=True): super(FakeBLE, self).__init__(spi, csn, ce, pa_level=pa_level, crc=0, @@ -137,9 +137,9 @@ def __init__(self, spi, csn, ce, name=None, pa_level=False, irq_DR=False, irq_DS arc=0, address_length=4, ask_no_ack=False, - irq_DF=False, - irq_DR=irq_DR, - irq_DS=irq_DS) + irq_df=False, + irq_dr=irq_dr, + irq_ds=irq_ds) self._chan = 0 self._ble_name = None self.name = name @@ -204,7 +204,7 @@ def send(self, buf): .. note:: If the name of the emulated BLE device is also to be braodcast, then the 'name' attribute should be set prior to calling `send()`. """ - self.ce.value = 0 + self.ce_pin.value = 0 self.flush_tx() self.clear_status_flags(False) # clears TX related flags only # max payload_length = 32 - 14(header, MAC, & CRC) - 2(container header) - 3(BLE flags) @@ -249,14 +249,14 @@ def send(self, buf): self.write(_reverse_bits(_ble_whitening(payload, whiten_coef))) time.sleep(0.00001) # ensure CE pulse is >= 10 µs # pulse is stopped here; the nRF24L01 only handles the top level payload in the FIFO. - self.ce.value = 0 # go to Standby-I power mode (power attribute still == True) + self.ce_pin.value = 0 # go to Standby-I power mode (power attribute still == True) # T_upload is done before timeout begins (after payload write action AKA upload) timeout = (((8 * (5 + len(payload))) + 9) / 125000) + 0.0002682 start = time.monotonic() - while not self.irq_DS and (time.monotonic() - start) < timeout: + while not self.irq_ds and (time.monotonic() - start) < timeout: self.update() # perform Non-operation command to get status byte (should be faster) - # print('status: DR={} DS={} DF={}'.format(self.irq_DR, self.irq_DS, self.irq_DF)) + # print('status: DR={} DS={} DF={}'.format(self.irq_dr, self.irq_ds, self.irq_df)) self.clear_status_flags(False) # only TX related IRQ flags # Altering all the following settings is disabled diff --git a/circuitpython_nrf24l01/registers.py b/circuitpython_nrf24l01/registers.py deleted file mode 100644 index 33ef67f..0000000 --- a/circuitpython_nrf24l01/registers.py +++ /dev/null @@ -1,17 +0,0 @@ -"""nRF24L01(+) registers""" - -REGISTERS = { - 'CONFIG' : 0x00,# register for configuring IRQ, CRC, PWR & RX/TX roles - 'EN_AA' : 0x01,# register for auto-ACK feature. Each bit represents this feature per pipe - 'EN_RX' : 0x02,# register to open/close pipes. Each bit represents this feature per pipe - 'SETUP_AW' : 0x03,# address width register - 'SETUP_RETR' : 0x04,# auto-retry count and delay register - 'RF_CH' : 0x05,# channel register - 'RF_SETUP' : 0x06,# RF Power Amplifier & Data Rate - 'RX_ADDR' : 0x0a,# RX pipe addresses rangeing [0,5]:[0xA:0xF] - 'RX_PW' : 0x11,# RX payload widths on pipes ranging [0,5]:[0x11,0x16] - 'FIFO' : 0x17,# register containing info on both RX/TX FIFOs + re-use payload flag - 'DYNPD' : 0x1c,# dynamic payloads feature. Each bit represents this feature per pipe - 'FEATURE' : 0x1d,# global toggles for dynamic payloads, auto-ACK, and custom ACK features - 'TX_ADDR' : 0x10 # Address that is used for TX transmissions -} diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index c09a1ae..36b27fe 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -20,7 +20,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -# pylint: disable=too-many-lines,invalid-name +# pylint: disable=too-many-lines """ rf24 module containing the base class RF24 """ @@ -28,7 +28,23 @@ __repo__ = "https://github.com/2bndy5/CircuitPython_nRF24L01.git" import time from adafruit_bus_device.spi_device import SPIDevice -from .registers import REGISTERS as REG + +# nRF24L01 registers +# pylint: disable=bad-whitespace +CONFIG = 0x00 #: register for configuring IRQ, CRC, PWR & RX/TX roles +EN_AA = 0x01 #: register for auto-ACK feature. Each bit represents this feature per pipe +EN_RX = 0x02 #: register to open/close pipes. Each bit represents this feature per pipe +SETUP_AW = 0x03 #: address width register +SETUP_RETR = 0x04 #: auto-retry count and delay register +RF_CH = 0x05 #: channel register +RF_SETUP = 0x06 #: RF Power Amplifier & Data Rate +RX_ADDR = 0x0a #: RX pipe addresses == [0,5]:[0x0a:0x0f] +RX_PW = 0x11 #: RX payload widths on pipes == [0,5]:[0x11,0x16] +FIFO = 0x17 #: register containing info on both RX/TX FIFOs + re-use payload flag +DYNPD = 0x1c #: dynamic payloads feature. Each bit represents this feature per pipe +FEATURE = 0x1d #: global flags for dynamic payloads, custom ACK payloads, & Ask no ACK +TX_ADDR = 0x10 #: Address that is used for TX transmissions +# pylint: enable=bad-whitespace class RF24: """A driver class for the nRF24L01(+) transceiver radios. This class aims to be compatible with @@ -84,13 +100,13 @@ class RF24: allowing custom response payloads appended to the ACK packets. Enabling this also requires the `auto_ack` attribute enabled. This can be changed at any time by using the `ack` attribute. - :param bool irq_DR: When "Data is Ready", this configures the interrupt (IRQ) trigger of the + :param bool irq_dr: When "Data is Ready", this configures the interrupt (IRQ) trigger of the nRF24L01's IRQ pin (active low). Defaults to enabled. This can be changed at any time by using the `interrupt_config()` function. - :param bool irq_DS: When "Data is Sent", this configures the interrupt (IRQ) trigger of the + :param bool irq_ds: When "Data is Sent", this configures the interrupt (IRQ) trigger of the nRF24L01's IRQ pin (active low). Defaults to enabled. This can be changed at any time by using the `interrupt_config()` function. - :param bool irq_DF: When "max retry attempts are reached" (specified by the `arc` attribute), + :param bool irq_df: When "max retry attempts are reached" (specified by the `arc` attribute), this configures the interrupt (IRQ) trigger of the nRF24L01's IRQ pin (active low) and represents transmission failure. Defaults to enabled. This can be changed at any time by using the `interrupt_config()` function. @@ -108,22 +124,28 @@ def __init__(self, spi, csn, ce, auto_ack=True, ask_no_ack=True, ack=False, - irq_DR=True, - irq_DS=True, - irq_DF=True): - # init the SPI bus and pins - self.spi = SPIDevice(spi, chip_select=csn, baudrate=1250000) + irq_dr=True, + irq_ds=True, + irq_df=True): self._payload_length = payload_length # inits internal attribute self.payload_length = payload_length # last address assigned to pipe0 for reading. init to None - self.pipe0_read_addr = None self._fifo = 0 self._status = 0 + # init shadow copy of RX addresses for all pipes + self._pipes = [b'', b'', 0, 0, 0, 0] + self._payload_widths = [0, 0, 0, 0, 0, 0] # payload_length specific to each pipe + # shadow copy of last RX_ADDR written to pipe 0 + self._pipe0_read_addr = None # needed as open_tx_pipe() appropriates pipe 0 for ACK + # init the _open_pipes attribute (reflects only RX state on each pipe) + self._open_pipes = 0 # <- means all pipes closed + # init the SPI bus and pins + self._spi = SPIDevice(spi, chip_select=csn, baudrate=1250000) # store the ce pin - self.ce = ce + self.ce_pin = ce # reset ce.value & disable the chip comms - self.ce.switch_to_output(value=False) + self.ce_pin.switch_to_output(value=False) # if radio is powered up and CE is LOW: standby-I mode # if radio is powered up and CE is HIGH: standby-II mode @@ -132,25 +154,36 @@ def __init__(self, spi, csn, ce, # configure the CONFIG register:IRQ(s) config, setup CRC feature, and trigger standby-I & # TX mode (the "| 2") if 0 <= crc <= 2: - self._config = ((not irq_DR) << 6) | ((not irq_DS) << 5) | ((not irq_DF) << 4) | \ + self._config = ((not irq_dr) << 6) | ((not irq_ds) << 5) | ((not irq_df) << 4) | \ ((crc + 1) << 2 if crc else 0) | 2 - self._reg_write(REG['CONFIG'], self._config) # dump to register + self._reg_write(CONFIG, self._config) # dump to register else: raise ValueError( "CRC byte length must be an int equal to 0 (off), 1, or 2") + # check for device presence by verifying nRF24L01 is in TX + standby-I mode - if self._reg_read(REG['CONFIG']) & 3 == 2: # if in TX + standby-I mode + if self._reg_read(CONFIG) & 3 == 2: # if in TX + standby-I mode self.power = False # power down - else: # hardware presence check NOT passed - print(bin(self._reg_read(REG['CONFIG']))) + else: # hardware presence check NOT passed + print(bin(self._reg_read(CONFIG))) raise RuntimeError("nRF24L01 Hardware not responding") + # capture all pipe's RX addresses & the TX address from last usage + for i in range(6): + if i < 2: + self._pipes[i] = self._reg_read_bytes(RX_ADDR + i) + else: + self._pipes[i] = self._reg_read(RX_ADDR + i) + + # shadow copy of the TX_ADDR + self._tx_address = self._reg_read_bytes(TX_ADDR) + # configure the SETUP_RETR register if 250 <= ard <= 4000 and ard % 250 == 0 and 0 <= arc <= 15: self._setup_retr = (int((ard - 250) / 250) << 4) | arc else: raise ValueError("automatic re-transmit delay can only be a multiple of 250 in range " - " [250,4000]\nautomatic re-transmit count(/attempts) must in range " + "[250,4000]\nautomatic re-transmit count(/attempts) must range " "[0,15]") # configure the RF_SETUP register @@ -174,44 +207,50 @@ def __init__(self, spi, csn, ce, self._channel = channel self._addr_len = address_length - # init the _open_pipes attribute (reflects only RX state on each pipe) - self._open_pipes = 0 # <- means all pipes closed - with self: # write to registers & power up # using __enter__() configures all virtual features and settings to the hardware # registers - self.ce.value = 0 # ensure standby-I mode to write to CONFIG register - self._reg_write(REG['CONFIG'], self._config | 1) # enable RX mode + self.ce_pin.value = 0 # ensure standby-I mode to write to CONFIG register + self._reg_write(CONFIG, self._config | 1) # enable RX mode time.sleep(0.000015) # wait time for transitioning modes RX/TX self.flush_rx() # spec sheet say "used in RX mode" - self._reg_write(REG['CONFIG'], self._config & 0xC) # power down + TX mode + self._reg_write(CONFIG, self._config & 0xC) # power down + TX mode time.sleep(0.000015) # wait time for transitioning modes RX/TX self.flush_tx() # spec sheet say "used in TX mode" self.clear_status_flags() # writes directly to STATUS register def __enter__(self): - self._reg_write(REG['CONFIG'], self._config & - 0x7C) # dump IRQ and CRC data to CONFIG register - self._reg_write(REG['RF_SETUP'], self._rf_setup) # dump to RF_SETUP register - # update open pipe info from current state of - self._open_pipes = self._reg_read(REG['EN_RX']) - # EN_RXADDR register - self._reg_write(REG['DYNPD'], self._dyn_pl) # dump to DYNPD register - self._reg_write(REG['EN_AA'], self._aa) # dump to EN_AA register - self._reg_write(REG['FEATURE'], self._features) # dump to FEATURE register + # dump IRQ and CRC data to CONFIG register + self._reg_write(CONFIG, self._config & 0x7C) + self._reg_write(RF_SETUP, self._rf_setup) # dump to RF_SETUP register + # dump open/close pipe status to EN_RXADDR register (for all pipes) + self._reg_write(EN_RX, self._open_pipes) + self._reg_write(DYNPD, self._dyn_pl) # dump to DYNPD register + self._reg_write(EN_AA, self._aa) # dump to EN_AA register + self._reg_write(FEATURE, self._features) # dump to FEATURE register # dump to SETUP_RETR register - self._reg_write(REG['SETUP_RETR'], self._setup_retr) + self._reg_write(SETUP_RETR, self._setup_retr) + # dump pipes' RX addresses and static payload lengths + for i, address in enumerate(self._pipes): + if i < 2: + self._reg_write_bytes(RX_ADDR + i, address) + else: + self._reg_write(RX_ADDR + i, address) + self._reg_write(RX_PW + i, self._payload_widths[i]) + # dump last used TX address + self._reg_write_bytes(TX_ADDR, self._tx_address) self.address_length = self._addr_len # writes directly to SETUP_AW register self.channel = self._channel # writes directly to RF_CH register return self def __exit__(self, *exc): + self.power = 0 return False # pylint: disable=no-member def _reg_read(self, reg): buf = bytearray(2) # 2 = 1 status byte + 1 byte of returned content - with self.spi as spi: + with self._spi as spi: time.sleep(0.005) # time for CSN to settle spi.readinto(buf, write_value=reg) self._status = buf[0] # save status byte @@ -220,7 +259,7 @@ def _reg_read(self, reg): def _reg_read_bytes(self, reg, buf_len=5): # allow an extra byte for status data buf = bytearray(buf_len + 1) - with self.spi as spi: + with self._spi as spi: time.sleep(0.005) # time for CSN to settle spi.readinto(buf, write_value=reg) self._status = buf[0] # save status byte @@ -229,7 +268,7 @@ def _reg_read_bytes(self, reg, buf_len=5): def _reg_write_bytes(self, reg, out_buf): out_buf = bytes([0x20 | reg]) + out_buf in_buf = bytearray(len(out_buf)) - with self.spi as spi: + with self._spi as spi: time.sleep(0.005) # time for CSN to settle spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] # save status byte @@ -240,7 +279,7 @@ def _reg_write(self, reg, value=None): else: out_buf = bytes([0x20 | reg, value]) in_buf = bytearray(len(out_buf)) - with self.spi as spi: + with self._spi as spi: time.sleep(0.005) # time for CSN to settle spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] # save status byte @@ -255,7 +294,7 @@ def address_length(self): A valid input value must be an `int` in range [3,5]. Otherwise a `ValueError` exception is thrown. Default is set to the nRF24L01's maximum of 5. """ - return self._reg_read(REG['SETUP_AW']) + 2 + return self._reg_read(SETUP_AW) + 2 @address_length.setter def address_length(self, length): @@ -264,7 +303,7 @@ def address_length(self, length): if 3 <= length <= 5: # address width is saved in 2 bits making range = [3,5] self._addr_len = int(length) - self._reg_write(REG['SETUP_AW'], length - 2) + self._reg_write(SETUP_AW, length - 2) else: raise ValueError( "address length can only be set in range [3,5] bytes") @@ -287,11 +326,15 @@ def open_tx_pipe(self, address): # if auto_ack == True, then use this TX address as the RX address for ACK if self.auto_ack: # settings need to match on both transceivers: dynamic_payloads and payload_length - self._reg_write_bytes(REG['RX_ADDR'], address) # using pipe 0 + self._pipes[0] = address + self._reg_write_bytes(RX_ADDR, address) # using pipe 0 self._open_pipes = self._open_pipes | 1 # open pipe 0 for RX-ing ACK - self._reg_write(REG['EN_RX'], self._open_pipes) - self._reg_write(REG['RX_PW'], self.payload_length) # set expected payload_length - self._reg_write_bytes(REG['TX_ADDR'], address) + self._reg_write(EN_RX, self._open_pipes) + self._payload_widths[0] = self.payload_length + self._reg_write(RX_PW, self.payload_length) # set expected payload_length + self._pipes[0] = address # update the context as well + self._tx_address = address + self._reg_write_bytes(TX_ADDR, address) else: raise ValueError("address must be a buffer protocol object with a byte length\nequal " "to the address_length attribute (currently set to" @@ -309,19 +352,22 @@ def close_rx_pipe(self, pipe_number, reset=True): """ if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0,5]") - self._open_pipes = self._reg_read(REG['EN_RX']) # refresh data + self._open_pipes = self._reg_read(EN_RX) # refresh data if reset:# reset pipe address accordingly if not pipe_number: # NOTE this does not clear the shadow copy (pipe0_read_addr) of address for pipe 0 - self._reg_write_bytes(pipe_number + REG['RX_ADDR'], b'\xe7' * 5) - elif pipe_number < 2: # write the full address for pipe 1 - self._reg_write_bytes(pipe_number + REG['RX_ADDR'], b'\xc2' * 5) - else: # write just LSB for 2 <= pipes >= 5 - self._reg_write(pipe_number + REG['RX_ADDR'], pipe_number + 0xc1) + self._reg_write_bytes(pipe_number + RX_ADDR, b'\xe7' * 5) + self._pipes[pipe_number] = b'\xe7' * 5 + elif pipe_number == 1: # write the full address for pipe 1 + self._reg_write_bytes(pipe_number + RX_ADDR, b'\xc2' * 5) + self._pipes[pipe_number] = b'\xc2' * 5 + else: # write just MSB for 2 <= pipes <= 5 + self._reg_write(pipe_number + RX_ADDR, pipe_number + 0xc1) + self._pipes[pipe_number] = pipe_number + 0xc1 # disable the specified data pipe if not already if self._open_pipes & (1 << pipe_number): self._open_pipes = self._open_pipes & ~(1 << pipe_number) - self._reg_write(REG['EN_RX'], self._open_pipes) + self._reg_write(EN_RX, self._open_pipes) def open_rx_pipe(self, pipe_number, address): """This function is used to open a specific data pipe for OTA (over the air) RX @@ -354,21 +400,24 @@ def open_rx_pipe(self, pipe_number, address): # ensure the proper address is set to pipe 0 via _start_listening() as # open_tx_pipe() will appropriate the address on pipe 0 if auto_ack is enabled for # TX mode - self.pipe0_read_addr = address - self._reg_write_bytes(REG['RX_ADDR'] + pipe_number, address) + self._pipe0_read_addr = address + self._pipes[pipe_number] = address + self._reg_write_bytes(RX_ADDR + pipe_number, address) else: # only write MSByte if pipe_number is not 0 or 1 - self._reg_write(REG['RX_ADDR'] + pipe_number, address[0]) + self._pipes[pipe_number] = address[0] + self._reg_write(RX_ADDR + pipe_number, address[0]) # now manage the pipe - self._open_pipes = self._reg_read(REG['EN_RX']) # refresh data + self._open_pipes = self._reg_read(EN_RX) # refresh data # enable the specified data pipe self._open_pipes = self._open_pipes | (1 << pipe_number) - self._reg_write(REG['EN_RX'], self._open_pipes) + self._reg_write(EN_RX, self._open_pipes) # now adjust payload_length accordingly despite dynamic_payload setting # radio only uses this info in RX mode when dynamic_payloads == True - self._reg_write(REG['RX_PW'] + pipe_number, self.payload_length) + self._reg_write(RX_PW + pipe_number, self.payload_length) + self._payload_widths[pipe_number] = self.payload_length @property def listen(self): @@ -385,7 +434,7 @@ def listen(self): Sheet `_, this attribute - flushes the RX FIFO, clears the `irq_DR` status flag, and puts nRF24L01 in power up + flushes the RX FIFO, clears the `irq_dr` status flag, and puts nRF24L01 in power up mode. Notice the CE pin is be held HIGH during RX mode. `False` disables RX mode. As mentioned in above link, this puts nRF24L01's power in @@ -406,78 +455,71 @@ def listen(self, is_rx): def _start_listening(self): # ensure radio is in power down or standby-I mode - if self.ce.value: - self.ce.value = 0 + if self.ce_pin.value: + self.ce_pin.value = 0 - if self.pipe0_read_addr is not None: + if self._pipe0_read_addr is not None: # make sure the last call to open_rx_pipe(0) sticks if initialized - self._reg_write_bytes(REG['RX_ADDR'], self.pipe0_read_addr) + self._reg_write_bytes(RX_ADDR, self._pipe0_read_addr) + self._pipes[0] = self._pipe0_read_addr # update the context as well # power up radio & set radio in RX mode self._config = self._config & 0xFC | 3 - self._reg_write(REG['CONFIG'], self._config) + self._reg_write(CONFIG, self._config) time.sleep(0.00015) # mandatory wait time to power up radio or switch modes (RX/TX) - self.flush_rx() # spec sheet says "used in RX mode" - self.clear_status_flags(True, False, False) # only Data Ready flag + self.flush_rx() # spec sheet says "used in RX mode" + self.clear_status_flags(True, False, False) # only Data Ready flag # enable radio comms - self.ce.value = 1 # radio begins listening after CE pulse is > 130 µs - time.sleep(0.00013) # ensure pulse is > 130 µs + self.ce_pin.value = 1 # radio begins listening after CE pulse is > 130 µs + time.sleep(0.00013) # ensure pulse is > 130 µs # nRF24L01 has just entered active RX + standby-II mode def _stop_listening(self): # ensure radio is in standby-I mode - if self.ce.value: - self.ce.value = 0 + if self.ce_pin.value: + self.ce_pin.value = 0 # set radio in TX mode as recommended behavior per spec sheet. self._config = self._config & 0xFE # does not put radio to sleep - self._reg_write(REG['CONFIG'], self._config) + self._reg_write(CONFIG, self._config) # mandated wait for transitioning between modes RX/TX time.sleep(0.00016) # exits while still in Standby-I (low current & no transmissions) def any(self): - """This function checks if the nRF24L01 has received any data at all. Internally, this - function uses `pipe()` then reports the next available payload's length (in bytes) -- if - there is any. + """This function checks if the nRF24L01 has received any data at all, and then reports the + next available payload's length (in bytes) -- if there is any. :returns: - `int` of the size (in bytes) of an available RX payload (if any). - ``0`` if there is no payload in the RX FIFO buffer. """ - if self.pipe() is not None: - # 0x60 == R_RX_PL_WID command - return self._reg_read(0x60) # top-level payload length - return 0 # RX FIFO empty + # 0x60 == R_RX_PL_WID command + return self._reg_read(0x60) # top-level payload length def recv(self): """This function is used to retrieve the next available payload in the RX FIFO buffer, then - clears the `irq_DR` status flag. This function also serves as a helper function to - `read_ack()` in TX mode to aquire any custom payload in the automatic acknowledgement (ACK) - packet -- only when the `ack` attribute is enabled. + clears the `irq_dr` status flag. This function synonomous to `read_ack()`. - :returns: A `bytearray` of the RX payload data + :returns: A `bytearray` of the RX payload data or `None` if there is no payload - If the `dynamic_payloads` attribute is disabled, then the returned bytearray's length is equal to the user defined `payload_length` attribute (which defaults to 32). - If the `dynamic_payloads` attribute is enabled, then the returned bytearray's length is equal to the payload's length - - .. tip:: Call the `any()` function before calling `recv()` to verify that there is data to - fetch. If there's no data to fetch, then the nRF24L01 returns bogus data and should not - regarded as a valid payload. """ - # buffer size = current payload size (0x60 = R_RX_PL_WID) + status byte - curr_pl_size = self.payload_length if not self.dynamic_payloads else self._reg_read( - 0x60) + if not self.irq_dr: + return None + # buffer size = current payload size + curr_pl_size = self.payload_length if not self.dynamic_payloads else self.any() # get the data (0x61 = R_RX_PAYLOAD) - result = self._reg_read_bytes(0x61, curr_pl_size) - # clear only Data Ready IRQ flag for continued RX operations + result = None if not curr_pl_size else self._reg_read_bytes(0x61, curr_pl_size) + # clear only Data Ready IRQ flag for accurate RX FIFO read operations self.clear_status_flags(True, False, False) # return all available bytes from payload return result - def send(self, buf, ask_no_ack=False): + def send(self, buf, ask_no_ack=False, force_retry=0): """This blocking function is used to transmit payload(s). :returns: @@ -485,16 +527,17 @@ def send(self, buf, ask_no_ack=False): in the returned list will contain the returned status for each corresponding payload in the list/tuple that was passed. The return statuses will be in one of the following forms: - * `False` if transmission fails. + * `False` if transmission fails or reaches the timeout sentinal. The timeout condition + is very rare and could mean something/anything went wrong with either/both TX/RX + transceivers. The timeout sentinal for transmission is calculated using `table 18 in + the nRF24L01 specification sheet `_. + Transmission failure can only be returned if `arc` is greater than ``0``. * `True` if transmission succeeds. - * `bytearray` when the `ack` attribute is `True`, the payload expects a responding - custom ACK payload; the response is returned (upon successful transmission) as a - `bytearray`. Empty ACK payloads (upon successful transmission) when the `ack` - attribute is set `True` are replaced with an error message ``b'NO ACK RETURNED'``. - * `None` if transmission times out meaning nRF24L01 has malfunctioned. This condition - is very rare. The allowed time for transmission is calculated using `table 18 in the - nRF24L01 specification sheet `_ + * `bytearray` or `None` when the `ack` attribute is `True`. Because the payload expects + a responding custom ACK payload, the response is returned (upon successful + transmission) as a + `bytearray` (or `None` if ACK payload is empty) :param bytearray,list,tuple buf: The payload to transmit. This bytearray must have a length greater than 0 and less than 32, otherwise a `ValueError` exception is thrown. This can @@ -512,62 +555,57 @@ def send(self, buf, ask_no_ack=False): ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about the payload). Therefore, it takes advantage of an nRF24L01 feature specific to individual payloads, and its value is not saved anywhere. You do not need to specify - this for every payload if the `auto_ack` attribute is disabled, however this parameter - should work despite the `auto_ack` attribute's setting. + this for every payload if the `arc` attribute is disabled, however this parameter + will work despite the `arc` attribute's setting. .. note:: Each transmission is in the form of a packet. This packet contains sections of data around and including the payload. `See Chapter 7.3 in the nRF24L01 Specifications Sheet `_ for more details. - - .. tip:: It is highly recommended that `auto_ack` attribute is enabled when sending - multiple payloads. Test results with the `auto_ack` attribute disabled were very poor + :param int force_retry: The number of brute-force attempts to `resend()` a failed + transmission. Default is 0. This parameter has no affect on transmissions if `arc` is + ``0`` or ``ask_no_ack`` parameter is set to `True`. Each re-attempt still takes + advantage of `arc` & `ard` attributes. During multi-payload processing, this + parameter is meant to slow down CircuitPython devices just enough for the Raspberry + Pi to catch up (due to the Raspberry Pi's seemingly slower SPI speeds). See also + `resend()` as using this parameter carries the same implications documented there. + + .. tip:: It is highly recommended that `arc` attribute is enabled when sending + multiple payloads. Test results with the `arc` attribute disabled were very poor (much < 50% received). This same advice applies to the ``ask_no_ack`` parameter (leave it as `False` for multiple payloads). - .. warning:: The nRF24L01 will block usage of the TX FIFO buffer upon failed transmissions. Failed transmission's payloads stay in TX FIFO buffer until the MCU calls `flush_tx()` and `clear_status_flags()`. Therefore, this function will discard failed transmissions' payloads when sending a list or tuple of payloads, so it can continue to process through the list/tuple even if any payload fails to be acknowledged. - - .. note:: We've tried very hard to keep nRF24L01s driven by CircuitPython devices compliant - with nRF24L01s driven by the Raspberry Pi. But due to the Raspberry Pi's seemingly - slower SPI speeds, we've had to resort to internally deploying `resend()` twice (at - most when needed) for payloads that failed during multi-payload processing. This tactic - is meant to slow down CircuitPython devices just enough for the Raspberry Pi to catch - up. Transmission failures are less possible this way. """ # ensure power down/standby-I for proper manipulation of PWR_UP & PRIM_RX bits in # CONFIG register - self.ce.value = 0 - self.flush_tx() - self.clear_status_flags(False) # clears TX related flags only + self.ce_pin.value = 0 + self.flush_tx() # be sure there is space in the TX FIFO # using spec sheet calculations: - # timeout total = T_upload + 2 * stby2active + T_overAir + T_ack + T_irq - + # timeout total = T_upload + 2 * stby2active + T_overAir + T_ack + T_irq + T_retry # T_upload = payload length (in bits) / spi data rate (bits per second = # baudrate / bits per byte) # T_upload is finished before timeout begins - # T_download == T_upload, however RX devices spi settings must match TX's for - # accurate calc - - # let 2 * stby2active (in µs) ~= (2 + 1 if getting ack else 0) * 130 - + # accurate calc + # let 2 * stby2active (in µs) ~= (2 + (1 if getting ack else 0)) * 130 # let T_ack = T_overAir as the payload size is the only distictive variable between - # the 2 + # the 2 # T_overAir (in seconds) = ( 8 (bits/byte) * (1 byte preamble + address length + - # payload length + crc length) + 9 bit packet ID ) / RF data rate (in bits/sec) - - # spec sheet says T_irq is (0.0000082 if self.data_rate == 1 else 0.000006) seconds - pl_coef = 1 + (bool(self.auto_ack) and not ask_no_ack) - pl_len = 1 + self._addr_len + (max(0, ((self._config & 12) >> 2) - 1)) + # payload length + crc length) + 9 bit packet ID ) / RF data rate (in bits/sec) + # T_irq (in seconds) = (0.0000082 if self.data_rate == 1 else 0.000006) + # T_retry (in microseconds)= (arc * ard) + need_ack = self._setup_retr & 0x0f and not ask_no_ack + packet_data = 1 + self._addr_len + (max(0, ((self._config & 12) >> 2) - 1)) bitrate = ((2000000 if self._rf_setup & 0x28 == 8 else 250000) if self._rf_setup & 0x28 else 1000000) / 8 - stby2active = (1 + pl_coef) * 0.00013 + t_ack = (((packet_data + 32) * 8 + 9) / bitrate) if need_ack else 0 # assumes 32-byte ACK + stby2active = (1 + (need_ack)) * 0.00013 t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 t_retry = (((self._setup_retr & 0xf0) >> 4) * 250 + 380) * \ (self._setup_retr & 0x0f) / 1000000 @@ -580,69 +618,50 @@ def send(self, buf, ask_no_ack=False): raise ValueError("buf (item {} in the list/tuple) must be a" " buffer protocol object with a byte length of\nat least 1 " "and no greater than 32".format(i)) - for b in buf: - timeout = pl_coef * (((8 * (len(b) + pl_len)) + 9) / bitrate) + \ - stby2active + t_irq + t_retry + \ - (len(b) * 64 / self.spi.baudrate) - self.write(b, ask_no_ack) - # wait for the ESB protocol to finish (or at least attempt) - time.sleep(timeout) # TODO could do this better - self.update() # update status flags - if self.irq_DF: # need to clear for continuing transmissions - # retry twice at most -- this seemed adaquate during testing - for i in range(2): - if not self.resend(): # clears flags upon entering and exiting - if i: # the last try - self.flush_tx() # discard failed payloads in the name of progress - result.append(False) - else: # resend succeeded - if self.ack: # is there a custom ACK payload? - result.append(self.read_ack()) - else: - result.append(True) - break - elif self.irq_DS: - result.append(True) - # clears TX related flags only - self.clear_status_flags(False) - self.ce.value = 0 + for i, b in enumerate(buf): + timeout = (((8 * (len(b) + packet_data)) + 9) / bitrate) + \ + stby2active + t_irq + t_retry + t_ack + \ + (len(b) * 64 / self._spi.baudrate) # t_upload + self.clear_status_flags(False) # clear TX related flags + self.write(b, ask_no_ack) # clears TX flags on entering + time.sleep(0.00001) + self.ce_pin.value = 0 + self._wait_for_result(timeout) # now get result + if self._setup_retr & 0x0f and self.irq_df: + # need to clear for continuing transmissions + result.append(self._attempt2resend(force_retry)) + else: # if auto_ack is disabled + if self.ack and self.irq_dr and not ask_no_ack: + result.append(self.recv()) # save ACK payload & clears RX flag + else: + result.append(self.irq_ds) # will always be True (in this case) return result if not buf or len(buf) > 32: raise ValueError("buf must be a buffer protocol object with a byte length of" "\nat least 1 and no greater than 32") - result = None # T_upload is done before timeout begins (after payload write action AKA upload) - timeout = pl_coef * (((8 * (len(buf) + pl_len)) + 9) / - bitrate) + stby2active + t_irq + t_retry + timeout = (((8 * (len(buf) + packet_data)) + 9) / + bitrate) + stby2active + t_irq + t_retry + t_ack self.write(buf, ask_no_ack) # init using non-blocking helper time.sleep(0.00001) # ensure CE pulse is >= 10 µs # if pulse is stopped here, the nRF24L01 only handles the top level payload in the FIFO. - # hold CE HIGH to continue processing through the rest of the TX FIFO bound for the - # address passed to open_tx_pipe() - # go to Standby-I power mode (power attribute still == True) - self.ce.value = 0 - - # now wait till the nRF24L01 has determined the result or timeout (based on calcs - # from spec sheet) - start = time.monotonic() - while not self.irq_DS and not self.irq_DF and (time.monotonic() - start) < timeout: - self.update() # perform Non-operation command to get status byte (should be faster) - # print('status: DR={} DS={} DF={}'.format(self.irq_DR, self.irq_DS, self.irq_DF)) - if self.irq_DS or self.irq_DF: # transmission done - # get status flags to detect error - result = self.irq_DS if self.auto_ack else not self.irq_DF - - # read ack payload clear status flags, then power down - if self.ack and self.irq_DS and not ask_no_ack: - # get and save ACK payload to self.ack if user wants it - result = self.read_ack() # save RX'd ACK payload to result - if result is None: # can't return empty handed - result = b'NO ACK RETURNED' - self.clear_status_flags(False) # only TX related IRQ flags + # we could hold CE HIGH to continue processing through the rest of the TX FIFO bound for + # the address passed to open_tx_pipe() + self.ce_pin.value = 0 # go to Standby-I power mode (power attribute still True) + self._wait_for_result(timeout) + if self._setup_retr & 0x0f and self.irq_df: + # if auto-retransmit is on and last attempt failed + result = self._attempt2resend(force_retry) + else: # if auto_ack is disabled + if self.ack and self.irq_dr and not ask_no_ack: + result = self.recv() # save ACK payload & clears RX flag + else: + result = self.irq_ds # will always be True (in this case) + self.clear_status_flags(False) # only TX related IRQ flags return result @property - def irq_DR(self): + def irq_dr(self): """A `bool` that represents the "Data Ready" interrupted flag. (read-only) * `True` represents Data is in the RX FIFO buffer @@ -660,7 +679,7 @@ def irq_DR(self): return bool(self._status & 0x40) @property - def irq_DS(self): + def irq_ds(self): """A `bool` that represents the "Data Sent" interrupted flag. (read-only) * `True` represents a successful transmission @@ -678,7 +697,7 @@ def irq_DS(self): return bool(self._status & 0x20) @property - def irq_DF(self): + def irq_df(self): """A `bool` that represents the "Data Failed" interrupted flag. (read-only) * `True` signifies the nRF24L01 attemped all configured retries @@ -706,7 +725,7 @@ def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): :param bool data_fail: specifies wheather to clear the "Max Re-transmit reached" flag. .. note:: Clearing the ``data_fail`` flag is necessary for continued transmissions from the - nRF24L01 (locks the TX FIFO buffer when `irq_DF` is `True`) despite wheather or not the + nRF24L01 (locks the TX FIFO buffer when `irq_df` is `True`) despite wheather or not the MCU is taking advantage of the interrupt (IRQ) pin. Call this function only when there is an antiquated status flag (after you've dealt with the specific payload related to the staus flags that were set), otherwise it can cause payloads to be ignored and @@ -731,8 +750,8 @@ def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): attempts to re-transmit the packet have been reached. If `auto_ack` attribute is disabled, then this IRQ event is not used. - .. note:: To fetch the status (not configuration) of these IRQ flags, use the `irq_DF`, - `irq_DS`, `irq_DR` attributes respectively. + .. note:: To fetch the status (not configuration) of these IRQ flags, use the `irq_df`, + `irq_ds`, `irq_dr` attributes respectively. .. tip:: Paraphrased from nRF24L01+ Specification Sheet: @@ -745,11 +764,11 @@ def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): `fifo()` will get this result) 4. if there is more data in RX FIFO, repeat from step 1 """ - self._config = self._reg_read(REG['CONFIG']) # refresh data + self._config = self._reg_read(CONFIG) # refresh data # save to register and update local copy of pwr & RX/TX modes' flags self._config = (self._config & 0x0F) | (not data_fail << 4) | (not data_sent << 5) | \ (not data_recv << 6) - self._reg_write(REG['CONFIG'], self._config) + self._reg_write(CONFIG, self._config) def what_happened(self, dump_pipes=False): """This debuggung function aggregates and outputs all status/condition related information @@ -766,17 +785,20 @@ def what_happened(self, dump_pipes=False): - ``Payload lengths`` The current setting of the `payload_length` attribute - ``Auto retry delay`` The current setting of the `ard` attribute - ``Auto retry attempts`` The current setting of the `arc` attribute - - ``Packets Lost`` Total amount of packets lost (transmission failures) - - ``Retry Attempts Made`` Maximum amount of attempts to re-transmit during last + - ``Packets lost on current channel`` Total amount of packets lost (transmission + failures). This only resets when the `channel` is changed. This count will + only go up 15. + - ``Retry attempts made for last transmission`` Amount of attempts to re-transmit + during last transmission (resets per payload) - ``IRQ - Data Ready`` The current setting of the IRQ pin on "Data Ready" event - ``IRQ - Data Sent`` The current setting of the IRQ pin on "Data Sent" event - ``IRQ - Data Fail`` The current setting of the IRQ pin on "Data Fail" event - ``Data Ready`` Is there RX data ready to be read? - (state of the `irq_DR` flag) - - ``Data Sent`` Has the TX data been sent? (state of the `irq_DS` flag) + (state of the `irq_dr` flag) + - ``Data Sent`` Has the TX data been sent? (state of the `irq_ds` flag) - ``Data Failed`` Has the maximum attempts to re-transmit been reached? - (state of the `irq_DF` flag) + (state of the `irq_df` flag) - ``TX FIFO full`` Is the TX FIFO buffer full? (state of the `tx_full` flag) - ``TX FIFO empty`` Is the TX FIFO buffer empty? - ``RX FIFO full`` Is the RX FIFO buffer full? @@ -813,14 +835,15 @@ def what_happened(self, dump_pipes=False): print("Payload lengths___________{} bytes".format(self.payload_length)) print("Auto retry delay__________{} microseconds".format(self.ard)) print("Auto retry attempts_______{} maximum".format(self.arc)) - print("Packets Lost______________{} total".format((watchdog & 0xF0) >> 4)) - print("Retry Attempts Made_______{}".format(watchdog & 0x0F)) + print("Packets lost on current channel_____________________{}".format( + (watchdog & 0xF0) >> 4)) + print("Retry attempts made for last transmission___________{}".format(watchdog & 0x0F)) print("IRQ - Data Ready______{} Data Ready___________{}".format( - '_True' if not bool(self._config & 0x40) else 'False', self.irq_DR)) + '_True' if not bool(self._config & 0x40) else 'False', self.irq_dr)) print("IRQ - Data Fail_______{} Data Failed__________{}".format( - '_True' if not bool(self._config & 0x20) else 'False', self.irq_DF)) + '_True' if not bool(self._config & 0x20) else 'False', self.irq_df)) print("IRQ - Data Sent_______{} Data Sent____________{}".format( - '_True' if not bool(self._config & 0x10) else 'False', self.irq_DS)) + '_True' if not bool(self._config & 0x10) else 'False', self.irq_ds)) print("TX FIFO full__________{} TX FIFO empty________{}".format( '_True' if bool(self.tx_full) else 'False', bool(self.fifo(True, True)))) print("RX FIFO full__________{} RX FIFO empty________{}".format( @@ -833,47 +856,42 @@ def what_happened(self, dump_pipes=False): 'Enabled' if self.auto_ack else 'Disabled')) print("Primary Mode_____________{} Power Mode___________{}".format( 'RX' if self.listen else 'TX', - ('Standby-II' if self.ce.value else 'Standby-I') if self._config & 2 else 'Off')) + ('Standby-II' if self.ce_pin.value else 'Standby-I') if self._config & 2 else 'Off')) if dump_pipes: - shared_bytes = b'' - print('TX address____________', self._reg_read_bytes(REG['TX_ADDR'])) - for i in range(REG['RX_ADDR'], REG['RX_ADDR'] + 6): - j = i - REG['RX_ADDR'] - payload_width = self._reg_read(REG['RX_PW'] + j) - is_open = "( open )" if self._open_pipes & (1 << j) else "(closed)" - if j <= 1: # print full address - shared_bytes = self._reg_read_bytes(i) - print("Pipe", j, is_open, "bound:", shared_bytes) - else: # print shared bytes + unique byte = actual address used by radio - specific_address = bytearray([self._reg_read(i)]) + shared_bytes[1:] - print("Pipe", j, is_open, "bound:", specific_address) - if self._open_pipes & (1 << j): - print('\t\texpecting', payload_width, 'byte static payloads') + print('TX address____________', self._tx_address) + for i, address in enumerate(self._pipes): + is_open = "( open )" if self._open_pipes & (1 << i) else "(closed)" + if i <= 1: # print full address + print("Pipe", i, is_open, "bound:", address) + else: # print unique byte + shared bytes = actual address used by radio + print("Pipe", i, is_open, "bound:", + bytes([self._pipes[i]]) + self._pipes[1][1:]) + if self._open_pipes & (1 << i): + print('\t\texpecting', self._payload_widths[i], 'byte static payloads') @property def dynamic_payloads(self): """This `bool` attribute controls the nRF24L01's dynamic payload length feature. - - `True` enables nRF24L01's dynamic payload length feature. Enabling the `auto_ack` - attribute also enables `dynamic_payloads` as it is required. The `payload_length` + - `True` enables nRF24L01's dynamic payload length feature. The `payload_length` attribute is ignored when this feature is enabled. - - `False` disables nRF24L01's dynamic payload length feature. Disabling the - `dynamic_payloads` also disables `auto_ack` (see also the `auto_ack` attribute). + - `False` disables nRF24L01's dynamic payload length feature. Be sure to adjust + the `payload_length` attribute accordingly when `dynamic_payloads` feature is disabled. """ return bool(self._dyn_pl and (self._features & 4)) @dynamic_payloads.setter def dynamic_payloads(self, enable): assert isinstance(enable, (bool, int)) - self._features = self._reg_read(REG['FEATURE']) # refresh data + self._features = self._reg_read(FEATURE) # refresh data # save changes to registers(& their shadows) if self._features & 4 != enable: # if not already # throw a specific global flag for enabling dynamic payloads self._features = (self._features & 3) | (enable << 2) - self._reg_write(REG['FEATURE'], self._features) + self._reg_write(FEATURE, self._features) # 0x3F == all pipes have enabled dynamic payloads self._dyn_pl = 0x3F if enable else 0 - self._reg_write(REG['DYNPD'], self._dyn_pl) + self._reg_write(DYNPD, self._dyn_pl) @property def payload_length(self): @@ -903,17 +921,75 @@ def payload_length(self, length): raise ValueError( "{}: payload length can only be set in range [1,32] bytes".format(length)) + @property + def arc(self): + """"This `int` attribute specifies the nRF24L01's number of attempts to re-transmit TX + payload when acknowledgment packet is not received. The `auto_ack` must be enabled on the + receiving nRF24L01, otherwise this attribute will make `send()` seem like it failed. + + A valid input value must be in range [0,15]. Otherwise a `ValueError` exception is thrown. + Default is set to 3. A value of ``0`` disables the automatic re-transmit feature and + considers all payload transmissions a success. + """ + self._setup_retr = self._reg_read(SETUP_RETR) # refresh data + return self._setup_retr & 0x0f + + @arc.setter + def arc(self, count): + if 0 <= count <= 15: + if self.arc & 0x0F != count: # write only if needed + # save changes to register(& its shadow) + self._setup_retr = (self._setup_retr & 0xF0) | count + self._reg_write(SETUP_RETR, self._setup_retr) + else: + raise ValueError( + "automatic re-transmit count(/attempts) must in range [0,15]") + + @property + def ard(self): + """This `int` attribute specifies the nRF24L01's delay (in µs) between attempts to + automatically re-transmit the TX payload when an expected acknowledgement (ACK) packet is + not received. During this time, the nRF24L01 is listening for the ACK packet. If the + `auto_ack` attribute is disabled, this attribute is not applied. + + A valid input value must be a multiple of 250 in range [250,4000]. Otherwise a `ValueError` + exception is thrown. Default is 1500 for reliability. + + .. note:: Paraphrased from nRF24L01 specifications sheet: + + Please take care when setting this parameter. If the custom ACK payload is more than 15 + bytes in 2 Mbps data rate, the `ard` must be 500µS or more. If the custom ACK payload + is more than 5 bytes in 1 Mbps data rate, the `ard` must be 500µS or more. In 250kbps + data rate (even when there is no custom ACK payload) the `ard` must be 500µS or more. + + See `data_rate` attribute on how to set the data rate of the nRF24L01's transmissions. + """ + self._setup_retr = self._reg_read(SETUP_RETR) # refresh data + return ((self._setup_retr & 0xf0) >> 4) * 250 + 250 + + @ard.setter + def ard(self, delta_t): + if 250 <= delta_t <= 4000 and delta_t % 250 == 0: + # set new ARD data and current ARC data to register + if self.ard != delta_t: # write only if needed + # save changes to register(& its Shadow) + self._setup_retr = (int((delta_t - 250) / 250) + << 4) | (self._setup_retr & 0x0F) + self._reg_write(SETUP_RETR, self._setup_retr) + else: + raise ValueError("automatic re-transmit delay can only be a multiple of 250 in range " + "[250,4000]") + @property def auto_ack(self): - """This `bool` attribute controls the nRF24L01's automatic acknowledgment feature. - - - `True` enables automatic acknowledgment packets. Enabling the `auto_ack` attribute also - enables `dynamic_payloads` as it is required. Also the CRC (cyclic redundancy checking) - is enabled automatically by the nRF24L01 if the `auto_ack` attribute is enabled (see also - `dynamic_payloads` and `crc` attributes). - - `False` disables automatic acknowledgment packets. Disabling the `auto_ack` also disables - `dynamic_payloads` (see also the `dynamic_payloads` attribute). The `crc` attribute will - remain unaffected (remains enabled) when disabling the `auto_ack` attribute. + """This `bool` attribute controls the nRF24L01's automatic acknowledgment feature during + the process of receiving a packet. + + - `True` enables transmitting automatic acknowledgment packets. The CRC (cyclic redundancy + checking) is enabled automatically by the nRF24L01 if the `auto_ack` attribute is enabled + (see also `crc` attribute). + - `False` disables transmitting automatic acknowledgment packets. The `crc` attribute will + remain unaffected when disabling the `auto_ack` attribute. """ return self._aa @@ -922,7 +998,7 @@ def auto_ack(self, enable): assert isinstance(enable, (bool, int)) # the following 0x3F == enabled auto_ack on all pipes self._aa = 0x3F if enable else 0 - self._reg_write(REG['EN_AA'], self._aa) # 1 == EN_AA register for ACK feature + self._reg_write(EN_AA, self._aa) # 1 == EN_AA register for ACK feature # nRF24L01 automatically enables CRC if ACK packets are enabled in the FEATURE register @property @@ -949,12 +1025,12 @@ def ack(self, enable): self.auto_ack = True # ensure auto_ack feature is enabled # dynamic_payloads required for custom ACK payloads self._dyn_pl = 0x3F - self._reg_write(REG['DYNPD'], self._dyn_pl) + self._reg_write(DYNPD, self._dyn_pl) else: # setting auto_ack feature automatically updated the _features attribute, so - self._features = self._reg_read(REG['FEATURE']) # refresh data here + self._features = self._reg_read(FEATURE) # refresh data here self._features = (self._features & 5) | (6 if enable else 0) - self._reg_write(REG['FEATURE'], self._features) + self._reg_write(FEATURE, self._features) def load_ack(self, buf, pipe_number): """This allows the MCU to specify a payload to be allocated into the TX FIFO buffer for use @@ -1004,16 +1080,13 @@ def read_ack(self): """Allows user to read the automatic acknowledgement (ACK) payload (if any) when nRF24L01 is in TX mode. This function is called from a blocking `send()` call if the `ack` attribute is enabled. Alternatively, this function can be called directly in case of calling the - non-blocking `write()` function during asychronous applications. + non-blocking `write()` function during asychronous applications. This function is an alias + of `recv()` and remains for bakward compatibility with older versions of this library. - .. warning:: In the case of asychronous applications, this function will do nothing if the - status flags are cleared after calling `write()` and before calling this function. See - also the `ack`, `dynamic_payloads`, and `auto_ack` attributes as they must be enabled - to use custom ACK payloads. + .. note:: See also the `ack`, `dynamic_payloads`, and `auto_ack` attributes as they must be + enabled to use custom ACK payloads. """ - if self.any(): # check RX FIFO for ACK packet's payload - return self.recv() - return None + return self.recv() @property def data_rate(self): @@ -1034,7 +1107,7 @@ def data_rate(self): receiving nRF24L01, then try a higher data rate. CAUTION: Higher data rates mean less maximum distance between nRF24L01 transceivers (and vise versa). """ - self._rf_setup = self._reg_read(REG['RF_SETUP']) # refresh data + self._rf_setup = self._reg_read(RF_SETUP) # refresh data return (2 if self._rf_setup & 0x28 == 8 else 250) if self._rf_setup & 0x28 else 1 @data_rate.setter @@ -1046,7 +1119,7 @@ def data_rate(self, speed): speed = 0 if speed == 1 else (8 if speed == 2 else 0x20) # save changes to register(& its shadow) self._rf_setup = self._rf_setup & 0xD7 | speed - self._reg_write(REG['RF_SETUP'], self._rf_setup) + self._reg_write(RF_SETUP, self._rf_setup) else: raise ValueError( "data rate must be one of the following ([M,M,K]bps): 1, 2, 250") @@ -1058,33 +1131,34 @@ def channel(self): A valid input value must be in range [0, 125] (that means [2.4, 2.525] GHz). Otherwise a `ValueError` exception is thrown. Default is 76. """ - return self._reg_read(REG['RF_CH']) + return self._reg_read(RF_CH) @channel.setter def channel(self, channel): if 0 <= channel <= 125: self._channel = channel - self._reg_write(REG['RF_CH'], channel) # always writes to reg + self._reg_write(RF_CH, channel) # always writes to reg else: raise ValueError("channel acn only be set in range [0,125]") @property def crc(self): """This `int` attribute specifies the nRF24L01's CRC (cyclic redundancy checking) encoding - scheme in terms of byte length. + scheme in terms of byte length. CRC is a way of making sure that the transmission didn't + get corrupted over the air. A valid input value is in range [0,2]: - - ``0`` disables CRC - - ``1`` enables CRC encoding scheme using 1 byte - - ``2`` enables CRC encoding scheme using 2 bytes + - ``0`` disables CRC (no anti-corruption of data) + - ``1`` enables CRC encoding scheme using 1 byte (weak anti-corruption of data) + - ``2`` enables CRC encoding scheme using 2 bytes (better anti-corruption of data) Any invalid input throws a `ValueError` exception. Default is enabled using 2 bytes. .. note:: The nRF24L01 automatically enables CRC if automatic acknowledgment feature is enabled (see `auto_ack` attribute). """ - self._config = self._reg_read(REG['CONFIG']) # refresh data + self._config = self._reg_read(CONFIG) # refresh data return max(0, ((self._config & 12) >> 2) - 1) # this works @crc.setter @@ -1129,73 +1203,15 @@ def power(self): def power(self, is_on): assert isinstance(is_on, (bool, int)) # capture surrounding flags and set PWR_UP flag according to is_on boolean - self._config = self._reg_read(REG['CONFIG']) # refresh data + self._config = self._reg_read(CONFIG) # refresh data if self.power != is_on: # only write changes self._config = (self._config & 0x7d) | ( is_on << 1) # doesn't affect TX?RX mode - self._reg_write(REG['CONFIG'], self._config) + self._reg_write(CONFIG, self._config) # power up/down takes < 150 µs + 4 µs time.sleep(0.00016) - @property - def arc(self): - """"This `int` attribute specifies the nRF24L01's number of attempts to re-transmit TX - payload when acknowledgment packet is not received. The nRF24L01 does not attempt to - re-transmit if `auto_ack` attribute is disabled. - - A valid input value must be in range [0,15]. Otherwise a `ValueError` exception is thrown. - Default is set to 3. - """ - self._setup_retr = self._reg_read(REG['SETUP_RETR']) # refresh data - return self._setup_retr & 0x0f - - @arc.setter - def arc(self, count): - if 0 <= count <= 15: - if self.arc & 0x0F != count: # write only if needed - # save changes to register(& its shadow) - self._setup_retr = (self._setup_retr & 0xF0) | count - self._reg_write(REG['SETUP_RETR'], self._setup_retr) - else: - raise ValueError( - "automatic re-transmit count(/attempts) must in range [0,15]") - - @property - def ard(self): - """This `int` attribute specifies the nRF24L01's delay (in µs) between attempts to - automatically re-transmit the TX payload when an expected acknowledgement (ACK) packet is - not received. During this time, the nRF24L01 is listening for the ACK packet. If the - `auto_ack` attribute is disabled, this attribute is not applied. - - A valid input value must be a multiple of 250 in range [250,4000]. Otherwise a `ValueError` - exception is thrown. Default is 1500 for reliability. - - .. note:: Paraphrased from nRF24L01 specifications sheet: - - Please take care when setting this parameter. If the custom ACK payload is more than 15 - bytes in 2 Mbps data rate, the `ard` must be 500µS or more. If the custom ACK payload - is more than 5 bytes in 1 Mbps data rate, the `ard` must be 500µS or more. In 250kbps - data rate (even when there is no custom ACK payload) the `ard` must be 500µS or more. - - See `data_rate` attribute on how to set the data rate of the nRF24L01's transmissions. - """ - self._setup_retr = self._reg_read(REG['SETUP_RETR']) # refresh data - return ((self._setup_retr & 0xf0) >> 4) * 250 + 250 - - @ard.setter - def ard(self, delta_t): - if 250 <= delta_t <= 4000 and delta_t % 250 == 0: - # set new ARD data and current ARC data to register - if self.ard != delta_t: # write only if needed - # save changes to register(& its Shadow) - self._setup_retr = (int((delta_t - 250) / 250) - << 4) | (self._setup_retr & 0x0F) - self._reg_write(REG['SETUP_RETR'], self._setup_retr) - else: - raise ValueError("automatic re-transmit delay can only be a multiple of 250 in range " - "[250,4000]") - @property def pa_level(self): """This `int` attribute specifies the nRF24L01's power amplifier level (in dBm). Higher @@ -1211,8 +1227,8 @@ def pa_level(self): Any invalid input throws a `ValueError` exception. Default is 0 dBm. """ - self._rf_setup = self._reg_read(REG['RF_SETUP']) # refresh data - return (3 - ((self._rf_setup & REG['RF_SETUP']) >> 1)) * -6 + self._rf_setup = self._reg_read(RF_SETUP) # refresh data + return (3 - ((self._rf_setup & RF_SETUP) >> 1)) * -6 @pa_level.setter def pa_level(self, power): @@ -1222,11 +1238,30 @@ def pa_level(self, power): power = (3 - int(power / -6)) * 2 # this works # save changes to register (& its shadow) self._rf_setup = (self._rf_setup & 0xF9) | power - self._reg_write(REG['RF_SETUP'], self._rf_setup) + self._reg_write(RF_SETUP, self._rf_setup) else: raise ValueError( "power amplitude must be one of the following (dBm): -18, -12, -6, 0") + @property + def rpd(self): + """This read-only attribute returns `True` if RPD (Received Power Detector) is triggered + or `False` if not triggered. + + .. note:: The RPD flag is triggered in the following cases: + + 1. During RX mode (`listen` = `True`) and a RF transmission with a gain above a preset + (non-adjustable) -64 dBm threshold. + 2. When a packet is received (indicative of the nRF24L01 used to detect/"listen" for + incoming packets). + 3. When the nRF24L01's CE pin goes from HIGH to LOW (or when the `listen` attribute + changes from `True` to `False`). + 4. When the underlying ESB (Enhanced ShockBurst) protocol reaches a hardcoded + (non-adjustable) RX timeout. + + """ + return bool(self._reg_read(0x09)) + @property def tx_full(self): """An attribute to represent the nRF24L01's status flag signaling that the TX FIFO buffer @@ -1247,16 +1282,18 @@ def update(self): """This function is only used to get an updated status byte over SPI from the nRF24L01 and is exposed to the MCU for asynchronous applications. Refreshing the status byte is vital to checking status of the interrupts, RX pipe number related to current RX payload, and if the - TX FIFO buffer is full. This function returns nothing, but internally updates the `irq_DR`, - `irq_DS`, `irq_DF`, and `tx_full` attributes. Internally this is a helper function to + TX FIFO buffer is full. This function returns nothing, but internally updates the `irq_dr`, + `irq_ds`, `irq_df`, and `tx_full` attributes. Internally this is a helper function to `pipe()`, `send()`, and `resend()` functions""" # perform non-operation to get status byte # should be faster than reading the STATUS register self._reg_write(0xFF) def resend(self): - """Use this function to maunally re-send the previously failed-to-transmit payload in the - top level (first out) of the TX FIFO buffer. + """Use this function to maunally re-send the previous payload in the + top level (first out) of the TX FIFO buffer. All returned data follows the same patttern + that `send()` returns with the added condition that this function will return `False` + if the TX FIFO buffer is empty. .. note:: The nRF24L01 normally removes a payload from the TX FIFO buffer after successful transmission, but not when this function is called. The payload (successfully @@ -1264,50 +1301,38 @@ def resend(self): remove them. Alternatively, using this function also allows the failed payload to be over-written by using `send()` or `write()` to load a new payload. """ - if not self.fifo(True, True): # also updates _fifo attribute - if self.irq_DF or self.irq_DS: # check and clear flags - self.clear_status_flags(False) # clears TX related flags only - result = None - if self._features & 1 == 0: # ensure REUSE_TX_PL optional command is allowed - self._features = self._features & 0xFE | 1 # set EN_DYN_ACK flag high - self._reg_write(REG['FEATURE'], self._features) - # payload will get re-used. This command tells the radio not pop TX payload from - # FIFO on success + result = False + if not self.fifo(True, True): # is there a pre-existing payload + self.clear_status_flags(False) # clear TX related flags + # indicate existing payload will get re-used. + # This command tells the radio not pop TX payload from FIFO on success self._reg_write(0xE3) # 0xE3 == REUSE_TX_PL command - self.ce.value = 0 # this cycles the CE pin to re-enable transmission of re-used payload - self.ce.value = 1 - time.sleep(0.00001) # mandated 10 µs pulse - # now get result - self.ce.value = 0 # only send one payload - start = time.monotonic() - # timeout calc assumes 32 byte payload (no way to tell when payload has already been - # loaded into TX FIFO) - pl_coef = 1 + bool(self.auto_ack) - pl_len = 1 + self._addr_len + \ - (max(0, ((self._config & 12) >> 2) - 1)) + # timeout calc assumes 32 byte payload because there is no way to tell when payload + # has already been loaded into TX FIFO; also assemues 32-byte ACK if needed + pl_coef = 1 + bool(self._setup_retr & 0x0f) + pl_len = 1 + self._addr_len + ( + max(0, ((self._config & 12) >> 2) - 1)) bitrate = ((2000000 if self._rf_setup & 0x28 == 8 else 250000) if self._rf_setup & 0x28 else 1000000) / 8 stby2active = (1 + pl_coef) * 0.00013 t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 t_retry = (((self._setup_retr & 0xf0) >> 4) * 250 + 380) * (self._setup_retr & 0x0f) / 1000000 - timeout = pl_coef * (((8 * (32 + pl_len)) + 9) / - bitrate) + stby2active + t_irq + t_retry - while not self.irq_DS and not self.irq_DF and (time.monotonic() - start) < timeout: - self.update() # perform Non-operation command to get status byte (should be faster) - if self.irq_DS or self.irq_DF: # transmission done - # get status flags to detect error - result = bool(self.irq_DS) - # read ack payload clear status flags, then power down - if self.ack and self.irq_DS: - # get and save ACK payload to self.ack if user wants it - result = self.read_ack() # save reply in input buffer - if result is None: # can't return empty handed - result = b'NO ACK RETURNED' - self.clear_status_flags(False) # only TX related IRQ flags - return result + timeout = pl_coef * (((8 * (32 + pl_len)) + 9) / bitrate) + \ + stby2active + t_irq + t_retry + # cycle the CE pin to re-enable transmission of re-used payload + self.ce_pin.value = 0 + self.ce_pin.value = 1 + time.sleep(0.00001) + self.ce_pin.value = 0 # only send one payload + self._wait_for_result(timeout) + result = self.irq_ds + if self.ack and self.irq_dr: # check if there is an ACK payload + result = self.recv() # save ACK payload & clear RX related IRQ flag + self.clear_status_flags(False) # only clear TX related IRQ flags + return result - def write(self, buf=None, ask_no_ack=False): + def write(self, buf, ask_no_ack=False): """This non-blocking function (when used as alternative to `send()`) is meant for asynchronous applications and can only handle one payload at a time as it is a helper function to `send()`. @@ -1341,43 +1366,41 @@ def write(self, buf=None, ask_no_ack=False): .. note:: The nRF24L01 doesn't initiate sending until a mandatory minimum 10 µs pulse on the CE pin is acheived. That pulse is initiated before this function exits. However, we have left that 10 µs wait time to be managed by the MCU in cases of asychronous - application, or it is managed by using `send()` instead of this function. If the CE pin - remains HIGH for longer than 10 µs, then the nRF24L01 will continue to transmit all - payloads found in the TX FIFO buffer. + application, or it is managed by using `send()` instead of this function. According to + the Specification sheet, if the CE pin remains HIGH for longer than 10 µs, then the + nRF24L01 will continue to transmit all payloads found in the TX FIFO buffer. .. warning:: A note paraphrased from the `nRF24L01+ Specifications Sheet `_: It is important to NEVER to keep the nRF24L01+ in TX mode for more than 4 ms at a time. - If the [`auto_ack` and `dynamic_payloads`] features are enabled, nRF24L01+ is never in - TX mode longer than 4 ms. + If the [`arc` attribute is] enabled, nRF24L01+ is never in TX mode longer than 4 + ms. .. tip:: Use this function at your own risk. Because of the underlying `"Enhanced ShockBurst Protocol" `_, disobeying the 4 - ms rule is easily avoided if you enable the `dynamic_payloads` and `auto_ack` - attributes. Alternatively, you MUST use interrupt flags or IRQ pin with user defined - timer(s) to AVOID breaking the 4 ms rule. If the `nRF24L01+ Specifications Sheet - explicitly states this `_, we have to assume radio damage or misbehavior as a result of disobeying the 4 ms rule. See also `table 18 in the nRF24L01 specification sheet `_ for calculating - necessary transmission time (these calculations are used in the `send()` function). + necessary transmission time (these calculations are used in the `send()` and `resend()` + functions). """ if not buf or len(buf) > 32: raise ValueError("buf must be a buffer protocol object with a byte length of" "\nat least 1 and no greater than 32") self.clear_status_flags(False) # only TX related IRQ flags - - if not self.power or (self._config & 1): # ready radio if it isn't yet - # also ensures tx mode - self._config = (self._reg_read(REG['CONFIG']) & 0x7c) | 2 - self._reg_write(0, self._config) - # power up/down takes < 150 µs + 4 µs - time.sleep(0.00016) - + if self._config & 3 != 2: # ready radio if it isn't yet + # ensures tx mode & powered up + self._config = (self._reg_read(CONFIG) & 0x7c) | 2 + self._reg_write(CONFIG, self._config) + time.sleep(0.00016) # power up/down takes < 150 µs + 4 µs # pad out or truncate data to fill payload_length if dynamic_payloads == False if not self.dynamic_payloads: if len(buf) < self.payload_length: @@ -1385,24 +1408,20 @@ def write(self, buf=None, ask_no_ack=False): buf += b'\x00' elif len(buf) > self.payload_length: buf = buf[:self.payload_length] - - # now upload the payload accordingly - if ask_no_ack: - # payload doesn't want acknowledgment - # 0xB0 = W_TX_PAYLOAD_NO_ACK; this command works with auto_ack on or off - # write appropriate command with payload - self._reg_write_bytes(0xB0, buf) - # print("payload doesn't want acknowledgment") - else: # payload may require acknowledgment - # 0xA0 = W_TX_PAYLOAD; this command works with auto_ack on or off - # write appropriate command with payload - self._reg_write_bytes(0xA0, buf) - # print("payload does want acknowledgment") + # now upload the payload accordingly with appropriate command + if ask_no_ack: # payload doesn't want acknowledgment + # ensure this feature is allowed by setting EN_DYN_ACK flag in the FEATURE register + if self._features & 1 == 0: + self._features = self._features & 0xFE | 1 # set EN_DYN_ACK flag high + self._reg_write(FEATURE, self._features) + # write appropriate command with payload + # 0xA0 = W_TX_PAYLOAD; 0xB0 = W_TX_PAYLOAD_NO_ACK + self._reg_write_bytes(0xA0 | (ask_no_ack << 4), buf) # enable radio comms so it can send the data by starting the mandatory minimum 10 µs pulse - # on CE. Let send() measure this pulse for blocking reasons - self.ce.value = 1 # re-used payloads start with this as well - # radio will automatically go to standby-II after transmission while CE is still HIGH only - # if dynamic_payloads and auto_ack are enabled + # on CE. Let send() or resend() measure this pulse for blocking reasons + self.ce_pin.value = 1 + # while CE is still HIGH only if dynamic_payloads and auto_ack are enabled + # automatically goes to standby-II after successful TX of all payloads in the FIFO def flush_rx(self): """A helper function to flush the nRF24L01's internal RX FIFO buffer. (write-only) @@ -1426,15 +1445,15 @@ def flush_tx(self): """ self._reg_write(0xE1) - def fifo(self, tx=False, empty=None): + def fifo(self, about_tx=False, check_empty=None): """This provides some precision determining the status of the TX/RX FIFO buffers. (read-only) - :param bool tx: + :param bool about_tx: * `True` means information returned is about the TX FIFO buffer. * `False` means information returned is about the RX FIFO buffer. This parameter defaults to `False` when not specified. - :param bool empty: + :param bool check_empty: * `True` tests if the specified FIFO buffer is empty. * `False` tests if the specified FIFO buffer is full. * `None` (when not specified) returns a 2 bit number representing both empty (bit 1) & @@ -1442,20 +1461,20 @@ def fifo(self, tx=False, empty=None): :returns: * A `bool` answer to the question: "Is the [TX/RX]:[`True`/`False`] FIFO buffer [empty/full]:[`True`/`False`]? - * If the ``empty`` parameter is not specified: an `int` in range [0,2] for which: + * If the ``check_empty`` parameter is not specified: an `int` in range [0,2] for which: - ``1`` means the specified FIFO buffer is full - ``2`` means the specified FIFO buffer is empty - ``0`` means the specified FIFO buffer is neither full nor empty """ - if (empty is None and isinstance(tx, (bool, int))) or \ - (isinstance(empty, (bool, int)) and isinstance(tx, (bool, int))): - self._fifo = self._reg_read(REG['FIFO']) # refresh the data - if empty is None: - return (self._fifo & (0x30 if tx else 0x03)) >> (4 * tx) - return bool(self._fifo & ((2 - empty) << (4 * tx))) - raise ValueError("Argument 1 ('tx') must always be a bool or int. Argument 2 ('empty')" - ", if specified, must be a bool or int") + if (check_empty is None and isinstance(about_tx, (bool, int))) or \ + (isinstance(check_empty, (bool, int)) and isinstance(about_tx, (bool, int))): + self._fifo = self._reg_read(FIFO) # refresh the data + if check_empty is None: + return (self._fifo & (0x30 if about_tx else 0x03)) >> (4 * about_tx) + return bool(self._fifo & ((2 - check_empty) << (4 * about_tx))) + raise ValueError("Argument 1 ('about_tx') must always be a bool or int. Argument 2" + " ('check_empty'), if specified, must be a bool or int") def pipe(self): """This function returns information about the data pipe that received the next available @@ -1467,7 +1486,37 @@ def pipe(self): the RX FIFO buffer. """ self.update() # perform Non-operation command to get status byte (should be faster) - pipe = (self._status & 0x0E) >> 1 # 0x0E==RX_P_NO - if pipe <= 5: # is there data in RX FIFO? - return pipe + result = (self._status & 0x0E) >> 1 # 0x0E==RX_P_NO + if result <= 5: # is there data in RX FIFO? + return result return None # RX FIFO is empty + + def address(self, index=-1): + """Returns the current address set to a specified data pipe or the TX address. (read-only) + + :param int index: the number of the data pipe whose address is to be returned. Defaults to + ``-1``. A valid index ranges [0,5] for RX addresses or any negative `int` for the TX + address. Otherwise an `IndexError` is thown. + """ + if index > 5: + raise IndexError("index {} is out of bounds [0,5]".format(index)) + if index < 0: + return self._tx_address + if index <= 1: + return self._pipes[index] + return bytes(self._pipes[index]) + self._pipes[1][1:] + + def _wait_for_result(self, timeout): + start = time.monotonic() + while not self.irq_ds and not self.irq_df and (time.monotonic() - start) < timeout: + self.update() # perform Non-operation command to get status byte (should be faster) + # print('status: DR={} DS={} DF={}'.format(self.irq_dr, self.irq_ds, self.irq_df)) + + def _attempt2resend(self, attempts): + retry = False + for _ in range(attempts): + # resend() clears flags upon entering and exiting + retry = self.resend() + if retry is None or retry: + break # retry succeeded + return retry diff --git a/docs/api.rst b/docs/api.rst index e756c55..31b3fa8 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -9,7 +9,10 @@ RF24 class ============== -.. important:: The nRF24L01 has 3 key features that are very interdependent of each other. Their +Troubleshooting info +-------------------- + +.. important:: The nRF24L01 has 3 key features that can be interdependent of each other. Their priority of dependence is as follows: 1. `dynamic_payloads` feature allowing either TX/RX nRF24L01 to be able to send/receive @@ -17,100 +20,265 @@ RF24 class nRF24L01 must use matching `payload_length` attributes. 2. `auto_ack` feature provides transmission verification by using the RX nRF24L01 to automatically and imediatedly send an acknowledgment (ACK) packet in response to freshly - received payloads. `auto_ack` requires `dynamic_payloads` to be enabled. + received payloads. `auto_ack` does not require `dynamic_payloads` to be enabled. 3. `ack` feature allows the MCU to append a payload to the ACK packet, thus instant bi-directional communication. A transmitting ACK payload must be loaded into the nRF24L01's TX FIFO buffer (done using `load_ack()`) BEFORE receiving the payload that is to be acknowledged. Once transmitted, the payload is released from the TX FIFO buffer. This - feature obviously requires the `auto_ack` feature enabled. + feature requires the `auto_ack` and `dynamic_payloads` features enabled. Remeber that the nRF24L01's FIFO (first-in,first-out) buffer has 3 levels. This means that there can be up to 3 payloads waiting to be read (RX) and up to 3 payloads waiting to be transmit (TX). -With the `auto_ack` feature enabled you get: +With the `auto_ack` feature enabled, you get: - * cycle redundancy checking (`crc`) automatically enabled + * cyclic redundancy checking (`crc`) automatically enabled * to change amount of automatic re-transmit attempts and the delay time between them. See the `arc` and `ard` attributes. .. note:: A word on pipes vs addresses vs channels. - You should think of the data pipes as a vehicle that you (the payload) get into. Continuing the - analogy, the specified address is not the address of an nRF24L01 radio, rather it is more - like a route that connects the endpoints. There are only six data pipes on the nRF24L01, - thus it can simultaneously listen to a maximum of 6 other nRF24L01 radios (can only talk to - 1 at a time). When assigning addresses to a data pipe, you can use any 5 byte long address - you can think of (as long as the last byte is unique among simultaneously broadcasting - addresses), so you're not limited to communicating to the same 6 radios (more on this when - we support "Multiciever" mode). Also the radio's channel is not be confused with the - radio's pipes. Channel selection is a way of specifying a certain radio frequency - (frequency = [2400 + channel] MHz). Channel defaults to 76 (like the arduino library), but - options range from 0 to 125 -- that's 2.4 GHz to 2.525 GHz. The channel can be tweaked to - find a less occupied frequency amongst (Bluetooth & WiFi) ambient signals. + You should think of the data pipes as a "parking spot" for your payload. There are only six + data pipes on the nRF24L01, thus it can simultaneously "listen" to a maximum of 6 other nRF24L01 + radios. However, it can only "talk" to 1 other nRF24L01 at a time). + + The specified address is not the address of an nRF24L01 radio, rather it is more like a path + that connects the endpoints. When assigning addresses to a data pipe, you can use any 5 byte + long address you can think of (as long as the first byte is unique among simultaneously + broadcasting addresses), so you're not limited to communicating with only the same 6 nRF24L01 + radios (more on this when we officially support "Multiciever" mode). + + Finnaly, the radio's channel is not be confused with the radio's pipes. Channel selection is a + way of specifying a certain radio frequency (frequency = [2400 + channel] MHz). Channel + defaults to 76 (like the arduino library), but options range from 0 to 125 -- that's 2.4 + GHz to 2.525 GHz. The channel can be tweaked to find a less occupied frequency amongst + Bluetooth, WiFi, or other ambient signals that use the same spectrum of frequencies. .. warning:: For successful transmissions, most of the endpoint trasceivers' settings/features must match. These settings/features include: - * The RX pipe's address on the receiving nRF24L01 MUST match the TX pipe's address on the - transmitting nRF24L01 + * The RX pipe's address on the receiving nRF24L01 (passed to `open_rx_pipe()`) MUST match the + TX pipe's address on the transmitting nRF24L01 (passed to `open_tx_pipe()`) * `address_length` * `channel` * `data_rate` * `dynamic_payloads` * `payload_length` only when `dynamic_payloads` is disabled - * `auto_ack` + * `auto_ack` on the recieving nRF24L01 must be enabled if `arc` is greater than 0 on the + transmitting nRF24L01 * custom `ack` payloads * `crc` In fact the only attributes that aren't required to match on both endpoint transceivers would - be the identifying data pipe number (passed to `open_rx_pipe()`), `pa_level`, `arc`, & - `ard` attributes. The ``ask_no_ack`` feature can be used despite the settings/features - configuration (see :meth:`~circuitpython_nrf24l01.rf24.RF24.send` & `write()` function + be the identifying data pipe number (passed to `open_rx_pipe()` or `load_ack()`), `pa_level`, + `arc`, & `ard` attributes. The ``ask_no_ack`` feature can be used despite the settings/features + configuration (see `send()` & `write()` function parameters for more details). Basic API --------- +Contrusctor +****************** + .. autoclass:: circuitpython_nrf24l01.rf24.RF24 - :members: address_length, open_tx_pipe, close_rx_pipe, open_rx_pipe, listen, any, recv, send + :no-members: + +address_length +****************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.address_length + +open_tx_pipe() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_tx_pipe + +close_rx_pipe() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.close_rx_pipe + +open_rx_pipe() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_rx_pipe + +listen +****************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.listen + +any() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.any + +recv() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.recv + +send() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.send Advanced API ------------ -.. class:: circuitpython_nrf24l01.rf24.RF24 - - .. automethod:: what_happened - .. autoattribute:: dynamic_payloads - .. autoattribute:: payload_length - .. autoattribute:: auto_ack - .. autoattribute:: irq_DR - .. autoattribute:: irq_DF - .. autoattribute:: irq_DS - .. automethod:: clear_status_flags - .. automethod:: interrupt_config - .. autoattribute:: ack - .. automethod:: load_ack - .. automethod:: read_ack - .. autoattribute:: data_rate - .. autoattribute:: channel - .. autoattribute:: crc - .. autoattribute:: power - .. autoattribute:: arc - .. autoattribute:: ard - .. autoattribute:: pa_level - .. autoattribute:: tx_full - .. automethod:: update - .. automethod:: resend - .. automethod:: write - .. automethod:: flush_rx - .. automethod:: flush_tx - .. automethod:: fifo - .. automethod:: pipe - -Fake BLE API -============ - -.. automodule:: circuitpython_nrf24l01.fake_ble - :members: - :show-inheritance: - :exclude-members: listen, open_tx_pipe, address_length, data_rate, dynamic_payloads, auto_ack, ack, crc, arc +what_happened() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.what_happened + +dynamic_payloads +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.dynamic_payloads + +payload_length +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.payload_length + +auto_ack +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.auto_ack + +arc +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.arc + +ard +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ard + +ack +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ack + +load_ack() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.load_ack + +read_ack() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.read_ack + +irq_dr +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_dr + +irq_df +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_df + +irq_ds +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_ds + +clear_status_flags() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.clear_status_flags + +interrupt_config() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.interrupt_config + +data_rate +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.data_rate + +channel +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.channel + +crc +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.crc + +power +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.power + +pa_level +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pa_level + +tx_full +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.tx_full + +rpd +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.rpd + +update() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.update + +resend() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.resend + +write() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.write + +flush_rx() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_rx + +flush_tx() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_tx + +fifo() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.fifo + +pipe() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.pipe + +address() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.address + +Sniffer +======= + .. automodule:: circuitpython_nrf24l01.sniffer + :members: + +Logitech Mouse +============== + + .. automodule:: circuitpython_nrf24l01.logitech_mouse + :members: diff --git a/examples/nrf24l01_stream_test.py b/examples/nrf24l01_stream_test.py index 83c5157..b4a3810 100644 --- a/examples/nrf24l01_stream_test.py +++ b/examples/nrf24l01_stream_test.py @@ -23,9 +23,11 @@ # lets create a list of payloads to be streamed to the nRF24L01 running slave() buffers = [] -SIZE = 32 # we'll use SIZE for the number of payloads in the list and the payloads' length -for i in range(SIZE): - buff = b'' +SIZE = 31 +# we'll use SIZE + 1 for the number of payloads in the list and the payloads' length +for i in range(SIZE + 1): + # prefix payload with a sequential letter to indicate which payloads were lost + buff = bytes([i + (65 if 0 <= i < 26 else 71)]) for j in range(SIZE): buff += bytes([(j >= SIZE / 2 + abs(SIZE / 2 - i) or j < SIZE / 2 - abs(SIZE / 2 - i)) + 48]) @@ -39,15 +41,16 @@ def master(count=1): # count = 5 will transmit the list 5 times # ensures the nRF24L01 is in TX mode nrf.listen = False - success_percentage = 0 + successful = 0 for _ in range(count): now = time.monotonic() * 1000 # start timer - result = nrf.send(buffers) + result = nrf.send(buffers, force_retry=2) print('Transmission took', time.monotonic() * 1000 - now, 'ms') for r in result: - success_percentage += 1 if r else 0 - success_percentage /= SIZE * count - print('successfully sent', success_percentage * 100, '%') + successful += 1 if r else 0 + print('successfully sent {}% ({}/{})'.format( + successful / len(buffers) * 100 * count, + successful, len(buffers) * count)) def slave(timeout=5): """Stops listening after timeout with no response""" From 03033a6c541e9b660f7b61f5dde076efc04fb9b1 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 20 Mar 2020 19:14:26 -0700 Subject: [PATCH 004/127] (-) sniffer/logiMouse (+) fake_ble back in --- docs/api.rst | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 31b3fa8..4622af0 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -272,13 +272,8 @@ address() .. automethod:: circuitpython_nrf24l01.rf24.RF24.address -Sniffer -======= - .. automodule:: circuitpython_nrf24l01.sniffer - :members: - -Logitech Mouse +Fake BLE ============== - .. automodule:: circuitpython_nrf24l01.logitech_mouse + .. automodule:: circuitpython_nrf24l01.fake_ble :members: From 88acd6a0b0ae10e029cdbcbffca1dc20708cd05b Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 20 Mar 2020 19:19:53 -0700 Subject: [PATCH 005/127] pleasing travis & pylint --- .travis.yml | 2 +- README.rst | 2 +- examples/nrf24l01_context_test.py | 2 +- examples/nrf24l01_interrupt_test.py | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 733e470..14f476d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,7 @@ install: - pip install --force-reinstall pylint==1.9.2 script: - - pylint --disable=invalid-name,too-many-instance-attributes,too-many-public-methods,too-many-arguments,too-many-function-args,too-many-locals,too-many-branches,too-many-nested-blocks,inconsistent-return-statements circuitpython_nrf24l01/*.py + - pylint --disable=invalid-name,too-many-instance-attributes,too-many-public-methods,too-many-arguments,too-many-function-args,too-many-locals,too-many-branches,too-many-nested-blocks,inconsistent-return-statements,too-many-statements circuitpython_nrf24l01/*.py - ([[ ! -d "examples" ]] || pylint --disable=invalid-name examples/*.py) - circuitpython-build-bundles --library_location . --filename_prefix circuitpython-nrf24l01 - cd docs && sphinx-build -E -W -b html . _build/html && cd .. diff --git a/README.rst b/README.rst index 5ac633e..a147c38 100644 --- a/README.rst +++ b/README.rst @@ -40,7 +40,7 @@ Features currently supported * "re-use the same payload" feature (for manually re-transmitting failed transmissions that remain in the buffer) * multiple payload transmissions with one function call (MUST read documentation on the :py:meth:`~circuitpython_nrf24l01.RF24.send()` function) * context manager compatible for easily switching between different radio configurations using "with" statements -* configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or failed transmissions (these 3 flags control the 1 IRQ pin). There's also virtual representations of these interrupt flags available (see :py:attr:`~circuitpython_nrf24l01.RF24.irq_DR`, :py:attr:`~circuitpython_nrf24l01.RF24.irq_DS`, :py:attr:`~circuitpython_nrf24l01.RF24.irq_DF` attributes) +* configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or failed transmissions (these 3 flags control the 1 IRQ pin). There's also virtual representations of these interrupt flags available (see :py:attr:`~circuitpython_nrf24l01.RF24.irq_DR`, :py:attr:`~circuitpython_nrf24l01.RF24.irq_ds`, :py:attr:`~circuitpython_nrf24l01.RF24.irq_df` attributes) * invoke sleep mode (AKA power down mode) for ultra-low current consumption * cyclic redundancy checking (CRC) up to 2 bytes long * adjust the nRF24L01's builtin automatic re-transmit feature's parameters (:py:attr:`~circuitpython_nrf24l01.RF24.arc`: number of attempts, :py:attr:`~circuitpython_nrf24l01.RF24.ard`: delay between attempts) diff --git a/examples/nrf24l01_context_test.py b/examples/nrf24l01_context_test.py index b056107..a9fa317 100644 --- a/examples/nrf24l01_context_test.py +++ b/examples/nrf24l01_context_test.py @@ -33,7 +33,7 @@ # NOTE address length is set to 3 bytes basicRF = RF24(spi, csn, ce, dynamic_payloads=False, - irq_DR=False, irq_DS=False, + irq_DR=False, irq_ds=False, channel=2, crc=1, data_rate=250, payload_length=8, address_length=3, ard=1000, arc=15) diff --git a/examples/nrf24l01_interrupt_test.py b/examples/nrf24l01_interrupt_test.py index 428d08f..e417699 100644 --- a/examples/nrf24l01_interrupt_test.py +++ b/examples/nrf24l01_interrupt_test.py @@ -38,9 +38,9 @@ def master(timeout=5): # will only wait 5 seconds for slave to respond nrf.write(b'ping') time.sleep(0.00001) # mandatory 10 microsecond pulse starts transmission nrf.ce.value = 0 # end 10 us pulse; now in active TX - while not nrf.irq_DS and not nrf.irq_DF: + while not nrf.irq_ds and not nrf.irq_df: nrf.update() # updates the current status on IRQ flags - if nrf.irq_DS and not irq.value: + if nrf.irq_ds and not irq.value: print('interrupt on data sent successful') else: print( @@ -72,9 +72,9 @@ def master(timeout=5): # will only wait 5 seconds for slave to respond nrf.write(b'dummy') # slave isn't listening anymore time.sleep(0.00001) # mandatory 10 microsecond pulse starts transmission nrf.ce.value = 0 # end 10 us pulse; now in active TX - while not nrf.irq_DS and not nrf.irq_DF: # these attributes don't update themselves - nrf.update() # updates the current status on all IRQ flags (irq_DR, irq_DF, irq_DS) - if nrf.irq_DF and not irq.value: + while not nrf.irq_ds and not nrf.irq_df: # these attributes don't update themselves + nrf.update() # updates the current status on all IRQ flags (irq_DR, irq_df, irq_ds) + if nrf.irq_df and not irq.value: print('interrupt on data fail successful') else: print( From a5008aa8e432a7f1a577edd5e02eadc36fa68375 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 20 Mar 2020 19:23:01 -0700 Subject: [PATCH 006/127] forgot i renamed those vars --- README.rst | 2 +- examples/nrf24l01_context_test.py | 2 +- examples/nrf24l01_interrupt_test.py | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index a147c38..8e7a5ef 100644 --- a/README.rst +++ b/README.rst @@ -40,7 +40,7 @@ Features currently supported * "re-use the same payload" feature (for manually re-transmitting failed transmissions that remain in the buffer) * multiple payload transmissions with one function call (MUST read documentation on the :py:meth:`~circuitpython_nrf24l01.RF24.send()` function) * context manager compatible for easily switching between different radio configurations using "with" statements -* configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or failed transmissions (these 3 flags control the 1 IRQ pin). There's also virtual representations of these interrupt flags available (see :py:attr:`~circuitpython_nrf24l01.RF24.irq_DR`, :py:attr:`~circuitpython_nrf24l01.RF24.irq_ds`, :py:attr:`~circuitpython_nrf24l01.RF24.irq_df` attributes) +* configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or failed transmissions (these 3 flags control the 1 IRQ pin). There's also virtual representations of these interrupt flags available (see :py:attr:`~circuitpython_nrf24l01.RF24.irq_dr`, :py:attr:`~circuitpython_nrf24l01.RF24.irq_ds`, :py:attr:`~circuitpython_nrf24l01.RF24.irq_df` attributes) * invoke sleep mode (AKA power down mode) for ultra-low current consumption * cyclic redundancy checking (CRC) up to 2 bytes long * adjust the nRF24L01's builtin automatic re-transmit feature's parameters (:py:attr:`~circuitpython_nrf24l01.RF24.arc`: number of attempts, :py:attr:`~circuitpython_nrf24l01.RF24.ard`: delay between attempts) diff --git a/examples/nrf24l01_context_test.py b/examples/nrf24l01_context_test.py index a9fa317..0c732b0 100644 --- a/examples/nrf24l01_context_test.py +++ b/examples/nrf24l01_context_test.py @@ -33,7 +33,7 @@ # NOTE address length is set to 3 bytes basicRF = RF24(spi, csn, ce, dynamic_payloads=False, - irq_DR=False, irq_ds=False, + irq_dr=False, irq_ds=False, channel=2, crc=1, data_rate=250, payload_length=8, address_length=3, ard=1000, arc=15) diff --git a/examples/nrf24l01_interrupt_test.py b/examples/nrf24l01_interrupt_test.py index e417699..a3dc8b7 100644 --- a/examples/nrf24l01_interrupt_test.py +++ b/examples/nrf24l01_interrupt_test.py @@ -37,7 +37,7 @@ def master(timeout=5): # will only wait 5 seconds for slave to respond print("Pinging: enslaved nRF24L01 without auto_ack") nrf.write(b'ping') time.sleep(0.00001) # mandatory 10 microsecond pulse starts transmission - nrf.ce.value = 0 # end 10 us pulse; now in active TX + nrf.ce_pin.value = 0 # end 10 us pulse; now in active TX while not nrf.irq_ds and not nrf.irq_df: nrf.update() # updates the current status on IRQ flags if nrf.irq_ds and not irq.value: @@ -55,7 +55,7 @@ def master(timeout=5): # will only wait 5 seconds for slave to respond pass if nrf.any(): print('Pong received') - if nrf.irq_DR and not irq.value: + if nrf.irq_dr and not irq.value: print('interrupt on data ready successful') else: print( @@ -71,9 +71,9 @@ def master(timeout=5): # will only wait 5 seconds for slave to respond nrf.flush_tx() # just in case the previous "on data sent" test failed nrf.write(b'dummy') # slave isn't listening anymore time.sleep(0.00001) # mandatory 10 microsecond pulse starts transmission - nrf.ce.value = 0 # end 10 us pulse; now in active TX + nrf.ce_pin.value = 0 # end 10 us pulse; now in active TX while not nrf.irq_ds and not nrf.irq_df: # these attributes don't update themselves - nrf.update() # updates the current status on all IRQ flags (irq_DR, irq_df, irq_ds) + nrf.update() # updates the current status on all IRQ flags (irq_dr, irq_df, irq_ds) if nrf.irq_df and not irq.value: print('interrupt on data fail successful') else: From 6cab1cc20de5028d85c398c820fe63e9dd295cee Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 20 Mar 2020 19:39:40 -0700 Subject: [PATCH 007/127] (docs) this will do for now --- docs/_static/darkness.css | 611 +++++++++++++++++++------------------- docs/api.rst | 6 +- 2 files changed, 310 insertions(+), 307 deletions(-) diff --git a/docs/_static/darkness.css b/docs/_static/darkness.css index 5d37994..c14a4cf 100644 --- a/docs/_static/darkness.css +++ b/docs/_static/darkness.css @@ -1,304 +1,307 @@ -/* -----------------------------code sections------------------------------ */ -.highlight { - background: #202020; -} - -.highlight .s1 { - color: #da8c00; -} - -.highlight .mi { - color: #9ff1c6; -} - -.highlight .nb { - color: #f7ef84; -} - -.highlight .ow { - color: #1f95cb; -} - -.highlight .kc { - color: #2886e4; -} - -.highlight .s2 { - color: #da8c00; -} - -.highlight .mf { - color: #9ff1c6; -} - -.highlight .se { - color: #e2c458; -} - -.highlight .si { - color: #2886e4; -} - -.highlight .sa { - color: #2886e4; -} - -.highlight .nf { - color: #f7ef84; -} - -.highlight .k { - color: #af63ab; -} - -.highlight .c1 { - color: #4bbc3f; -} - -.highlight .o { - color: #fefefe; -} - -.highlight .sd { - color: #da8c00; -} - -.highlight .nn { - color: #fff; -} - -.highlight .kn { - color: #af63ab; -} - -.highlight .bp { - color: #1f95cb; -} - -.highlight .nd { - color: #f7ef84; -} - -.highlight .ne { - color: #13d297; -} - -.highlight .mh { - color: #9ff1c6; -} - -.highlight .go { - color: #1afd00; - } - -/* ----------------------table sections---------------------------------------- */ -.wy-table thead, -.rst-content table.docutils thead, -.rst-content table.field-list thead { - color:#fcfcfc; - text-align:center; - background-color: #1a4228; -} - -.wy-table-odd td, -.wy-table-striped tr:nth-child(2n-1) td, -.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td { - background-color:#3d3d3d; -} - -.wy-table-bordered-all td, -.rst-content table.docutils td { - border-bottom:1px solid #e1e4e5; - border-left:1px solid #e1e4e5; - text-align: center; -} - -/* ------------------------admonition sections------------------------------- */ -.wy-alert.wy-alert-success .wy-alert-title, -.rst-content .wy-alert-success.note .wy-alert-title, -.rst-content .wy-alert-success.attention .wy-alert-title, -.rst-content .wy-alert-success.caution .wy-alert-title, -.rst-content .wy-alert-success.danger .wy-alert-title, -.rst-content .wy-alert-success.error .wy-alert-title, -.rst-content .hint .wy-alert-title, -.rst-content .important .wy-alert-title, -.rst-content .tip .wy-alert-title, -.rst-content .wy-alert-success.warning .wy-alert-title, -.rst-content .wy-alert-success.seealso .wy-alert-title, -.rst-content .wy-alert-success.admonition-todo .wy-alert-title, -.rst-content .wy-alert-success.admonition .wy-alert-title, -.wy-alert.wy-alert-success .rst-content .admonition-title, -.rst-content .wy-alert.wy-alert-success .admonition-title, -.rst-content .wy-alert-success.note .admonition-title, -.rst-content .wy-alert-success.attention .admonition-title, -.rst-content .wy-alert-success.caution .admonition-title, -.rst-content .wy-alert-success.danger .admonition-title, -.rst-content .wy-alert-success.error .admonition-title, -.rst-content .hint .admonition-title, -.rst-content .important .admonition-title, -.rst-content .tip .admonition-title, -.rst-content .wy-alert-success.warning .admonition-title, -.rst-content .wy-alert-success.seealso .admonition-title, -.rst-content .wy-alert-success.admonition-todo .admonition-title, -.rst-content .wy-alert-success.admonition .admonition-title { - background:#05886e; -} - -.wy-alert.wy-alert-success, -.rst-content .wy-alert-success.note, -.rst-content .wy-alert-success.attention, -.rst-content .wy-alert-success.caution, -.rst-content .wy-alert-success.danger, -.rst-content .wy-alert-success.error, -.rst-content .hint, -.rst-content .important, -.rst-content .tip, -.rst-content .wy-alert-success.warning, -.rst-content .wy-alert-success.seealso, -.rst-content .wy-alert-success.admonition-todo, -.rst-content .wy-alert-success.admonition { - background: #28443e; -} - -.wy-alert.wy-alert-info .wy-alert-title, .rst-content .note .wy-alert-title, .rst-content .wy-alert-info.attention .wy-alert-title, .rst-content .wy-alert-info.caution .wy-alert-title, .rst-content .wy-alert-info.danger .wy-alert-title, .rst-content .wy-alert-info.error .wy-alert-title, .rst-content .wy-alert-info.hint .wy-alert-title, .rst-content .wy-alert-info.important .wy-alert-title, .rst-content .wy-alert-info.tip .wy-alert-title, .rst-content .wy-alert-info.warning .wy-alert-title, .rst-content .seealso .wy-alert-title, .rst-content .wy-alert-info.admonition-todo .wy-alert-title, .rst-content .wy-alert-info.admonition .wy-alert-title, .wy-alert.wy-alert-info .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-info .admonition-title, .rst-content .note .admonition-title, .rst-content .wy-alert-info.attention .admonition-title, .rst-content .wy-alert-info.caution .admonition-title, .rst-content .wy-alert-info.danger .admonition-title, .rst-content .wy-alert-info.error .admonition-title, .rst-content .wy-alert-info.hint .admonition-title, .rst-content .wy-alert-info.important .admonition-title, .rst-content .wy-alert-info.tip .admonition-title, .rst-content .wy-alert-info.warning .admonition-title, .rst-content .seealso .admonition-title, .rst-content .wy-alert-info.admonition-todo .admonition-title, .rst-content .wy-alert-info.admonition .admonition-title { - background: #064873; -} - -.wy-alert.wy-alert-info, .rst-content .note, .rst-content .wy-alert-info.attention, .rst-content .wy-alert-info.caution, .rst-content .wy-alert-info.danger, .rst-content .wy-alert-info.error, .rst-content .wy-alert-info.hint, .rst-content .wy-alert-info.important, .rst-content .wy-alert-info.tip, .rst-content .wy-alert-info.warning, .rst-content .seealso, .rst-content .wy-alert-info.admonition-todo, .rst-content .wy-alert-info.admonition { - background: #2d3d48; -} - -.wy-alert.wy-alert-warning .wy-alert-title, .rst-content .wy-alert-warning.note .wy-alert-title, .rst-content .attention .wy-alert-title, .rst-content .caution .wy-alert-title, .rst-content .wy-alert-warning.danger .wy-alert-title, .rst-content .wy-alert-warning.error .wy-alert-title, .rst-content .wy-alert-warning.hint .wy-alert-title, .rst-content .wy-alert-warning.important .wy-alert-title, .rst-content .wy-alert-warning.tip .wy-alert-title, .rst-content .warning .wy-alert-title, .rst-content .wy-alert-warning.seealso .wy-alert-title, .rst-content .admonition-todo .wy-alert-title, .rst-content .wy-alert-warning.admonition .wy-alert-title, .wy-alert.wy-alert-warning .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-warning .admonition-title, .rst-content .wy-alert-warning.note .admonition-title, .rst-content .attention .admonition-title, .rst-content .caution .admonition-title, .rst-content .wy-alert-warning.danger .admonition-title, .rst-content .wy-alert-warning.error .admonition-title, .rst-content .wy-alert-warning.hint .admonition-title, .rst-content .wy-alert-warning.important .admonition-title, .rst-content .wy-alert-warning.tip .admonition-title, .rst-content .warning .admonition-title, .rst-content .wy-alert-warning.seealso .admonition-title, .rst-content .admonition-todo .admonition-title, .rst-content .wy-alert-warning.admonition .admonition-title { - background: #ed2222; -} - -.wy-alert.wy-alert-warning, .rst-content .wy-alert-warning.note, .rst-content .attention, .rst-content .caution, .rst-content .wy-alert-warning.danger, .rst-content .wy-alert-warning.error, .rst-content .wy-alert-warning.hint, .rst-content .wy-alert-warning.important, .rst-content .wy-alert-warning.tip, .rst-content .warning, .rst-content .wy-alert-warning.seealso, .rst-content .admonition-todo, .rst-content .wy-alert-warning.admonition { - background: #490707; -} - -/* -------------------------------------general page styling-------------------------------------------- */ -body { - color:#fcfcfc; -} - -.wy-nav-content-wrap, .wy-nav-content { - background:#242424; -} - -/* -------------------------------------sidebar sections------------------------------------------------ */ -.wy-side-nav-search { - background-color:#006B09; - color:#fcfcfc; -} - -.wy-side-nav-search input[type=text] { - background: #3e3e3e; - color: #fcfcfc; -} - -.wy-menu-vertical li.on a, -.wy-menu-vertical li.current>a { - color:#fcfcfc; - background:#242424; -} - -.wy-nav-side { - background:#172353; -} - -.wy-menu-vertical li.on a:hover, -.wy-menu-vertical li.current>a:hover { - background:#395c62; -} - -.wy-menu-vertical li.on a:hover, -.wy-menu-vertical li.current>a:hover { - background:#395c62; -} - -.wy-side-nav-search>div.version { - color:rgba(255, 255, 255, 0.69); -} - -.wy-menu-vertical header, -.wy-menu-vertical p.caption { - color:#43df96; -} - -.wy-nav-top { - background:#006B09; -} - -.wy-menu-vertical li.on a span.toctree-expand, .wy-menu-vertical li.current > a span.toctree-expand { - color: #f2f2f2; -} - -.wy-menu-vertical li span.toctree-expand { - color: #f2f2f2; -} - -.wy-menu-vertical li.current { - background: #034e69; -} - -.wy-menu-vertical li.toctree-l2 a, -.wy-menu-vertical li.toctree-l3 a, -.wy-menu-vertical li.toctree-l4 a { - color:#f2f2f2; -} - -.wy-menu-vertical li.current a:hover { - background:#3e3e3ecf; -} - -.wy-menu-vertical li.toctree-l2.current>a { - background:#242424; -} - -/* -----------------------------------------API sections-------------------------------------- */ -.rst-content dl:not(.docutils) dl dt { - background: #343434; - color: #e5df8e; -} - -.rst-content tt, .rst-content tt, .rst-content code { - color: #f3f3f3; -} - -.rst-content dl:not(.docutils) dt { - background: #2e363c; -} - -.rst-content tt.literal, .rst-content tt.literal, .rst-content code.literal { - color: #16FF00; - border-color: #303030; -} - -code, .rst-content tt, .rst-content code { - background: #000; -} - -.rst-content tt.xref, a .rst-content tt, .rst-content tt.xref, .rst-content code.xref, a .rst-content tt, a .rst-content code { - color: #fff; -} - -.rst-content dl:not(.docutils) dl dt { - background: #343434; - color: #e5df8e; -} - -.rst-content dl:not(.docutils) dl dt .headerlink { - color: #29ae5b; -} - -.rst-content dl:not(.docutils) dt .headerlink { - color: #29ae5b4d !important; -} - -.rst-content dl dt .headerlink::after { - visibility: visible; -} - -.rst-content h1:hover .headerlink::after, .rst-content h2:hover .headerlink::after, .rst-content .toctree-wrapper p.caption:hover .headerlink::after, .rst-content h3:hover .headerlink::after, .rst-content h4:hover .headerlink::after, .rst-content h5:hover .headerlink::after, .rst-content h6:hover .headerlink::after, .rst-content dl dt:hover .headerlink::after, .rst-content p.caption:hover .headerlink::after, .rst-content table > caption:hover .headerlink::after, .rst-content .code-block-caption:hover .headerlink::after { - color: #29ae5b !important; -} +/* -----------------------------code sections------------------------------ */ +.highlight { + background: #202020; +} + +.highlight .s1 { + color: #da8c00; +} + +.highlight .mi { + color: #9ff1c6; +} + +.highlight .nb { + color: #f7ef84; +} + +.highlight .ow { + color: #1f95cb; +} + +.highlight .kc { + color: #2886e4; +} + +.highlight .s2 { + color: #da8c00; +} + +.highlight .mf { + color: #9ff1c6; +} + +.highlight .se { + color: #e2c458; +} + +.highlight .si { + color: #2886e4; +} + +.highlight .sa { + color: #2886e4; +} + +.highlight .nf { + color: #f7ef84; +} + +.highlight .k { + color: #af63ab; +} + +.highlight .c1 { + color: #4bbc3f; +} + +.highlight .o { + color: #fefefe; +} + +.highlight .sd { + color: #da8c00; +} + +.highlight .nn { + color: #fff; +} + +.highlight .kn { + color: #af63ab; +} + +.highlight .bp { + color: #1f95cb; +} + +.highlight .nd { + color: #f7ef84; +} + +.highlight .ne { + color: #13d297; +} + +.highlight .mh { + color: #9ff1c6; +} + +.highlight .go { + color: #1afd00; + } + +/* ----------------------table sections---------------------------------------- */ +.wy-table thead, +.rst-content table.docutils thead, +.rst-content table.field-list thead { + color:#fcfcfc; + text-align:center; + background-color: #1a4228; +} + +.wy-table-odd td, +.wy-table-striped tr:nth-child(2n-1) td, +.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td { + background-color:#3d3d3d; +} + +.wy-table-bordered-all td, +.rst-content table.docutils td { + border-bottom:1px solid #e1e4e5; + border-left:1px solid #e1e4e5; + text-align: center; +} + +/* ------------------------admonition sections------------------------------- */ +.wy-alert.wy-alert-success .wy-alert-title, +.rst-content .wy-alert-success.note .wy-alert-title, +.rst-content .wy-alert-success.attention .wy-alert-title, +.rst-content .wy-alert-success.caution .wy-alert-title, +.rst-content .wy-alert-success.danger .wy-alert-title, +.rst-content .wy-alert-success.error .wy-alert-title, +.rst-content .hint .wy-alert-title, +.rst-content .important .wy-alert-title, +.rst-content .tip .wy-alert-title, +.rst-content .wy-alert-success.warning .wy-alert-title, +.rst-content .wy-alert-success.seealso .wy-alert-title, +.rst-content .wy-alert-success.admonition-todo .wy-alert-title, +.rst-content .wy-alert-success.admonition .wy-alert-title, +.wy-alert.wy-alert-success .rst-content .admonition-title, +.rst-content .wy-alert.wy-alert-success .admonition-title, +.rst-content .wy-alert-success.note .admonition-title, +.rst-content .wy-alert-success.attention .admonition-title, +.rst-content .wy-alert-success.caution .admonition-title, +.rst-content .wy-alert-success.danger .admonition-title, +.rst-content .wy-alert-success.error .admonition-title, +.rst-content .hint .admonition-title, +.rst-content .important .admonition-title, +.rst-content .tip .admonition-title, +.rst-content .wy-alert-success.warning .admonition-title, +.rst-content .wy-alert-success.seealso .admonition-title, +.rst-content .wy-alert-success.admonition-todo .admonition-title, +.rst-content .wy-alert-success.admonition .admonition-title { + background:#05886e; +} + +.wy-alert.wy-alert-success, +.rst-content .wy-alert-success.note, +.rst-content .wy-alert-success.attention, +.rst-content .wy-alert-success.caution, +.rst-content .wy-alert-success.danger, +.rst-content .wy-alert-success.error, +.rst-content .hint, +.rst-content .important, +.rst-content .tip, +.rst-content .wy-alert-success.warning, +.rst-content .wy-alert-success.seealso, +.rst-content .wy-alert-success.admonition-todo, +.rst-content .wy-alert-success.admonition { + background: #28443e; +} + +.wy-alert.wy-alert-info .wy-alert-title, .rst-content .note .wy-alert-title, .rst-content .wy-alert-info.attention .wy-alert-title, .rst-content .wy-alert-info.caution .wy-alert-title, .rst-content .wy-alert-info.danger .wy-alert-title, .rst-content .wy-alert-info.error .wy-alert-title, .rst-content .wy-alert-info.hint .wy-alert-title, .rst-content .wy-alert-info.important .wy-alert-title, .rst-content .wy-alert-info.tip .wy-alert-title, .rst-content .wy-alert-info.warning .wy-alert-title, .rst-content .seealso .wy-alert-title, .rst-content .wy-alert-info.admonition-todo .wy-alert-title, .rst-content .wy-alert-info.admonition .wy-alert-title, .wy-alert.wy-alert-info .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-info .admonition-title, .rst-content .note .admonition-title, .rst-content .wy-alert-info.attention .admonition-title, .rst-content .wy-alert-info.caution .admonition-title, .rst-content .wy-alert-info.danger .admonition-title, .rst-content .wy-alert-info.error .admonition-title, .rst-content .wy-alert-info.hint .admonition-title, .rst-content .wy-alert-info.important .admonition-title, .rst-content .wy-alert-info.tip .admonition-title, .rst-content .wy-alert-info.warning .admonition-title, .rst-content .seealso .admonition-title, .rst-content .wy-alert-info.admonition-todo .admonition-title, .rst-content .wy-alert-info.admonition .admonition-title { + background: #064873; +} + +.wy-alert.wy-alert-info, .rst-content .note, .rst-content .wy-alert-info.attention, .rst-content .wy-alert-info.caution, .rst-content .wy-alert-info.danger, .rst-content .wy-alert-info.error, .rst-content .wy-alert-info.hint, .rst-content .wy-alert-info.important, .rst-content .wy-alert-info.tip, .rst-content .wy-alert-info.warning, .rst-content .seealso, .rst-content .wy-alert-info.admonition-todo, .rst-content .wy-alert-info.admonition { + background: #2d3d48; +} + +.wy-alert.wy-alert-warning .wy-alert-title, .rst-content .wy-alert-warning.note .wy-alert-title, .rst-content .attention .wy-alert-title, .rst-content .caution .wy-alert-title, .rst-content .wy-alert-warning.danger .wy-alert-title, .rst-content .wy-alert-warning.error .wy-alert-title, .rst-content .wy-alert-warning.hint .wy-alert-title, .rst-content .wy-alert-warning.important .wy-alert-title, .rst-content .wy-alert-warning.tip .wy-alert-title, .rst-content .warning .wy-alert-title, .rst-content .wy-alert-warning.seealso .wy-alert-title, .rst-content .admonition-todo .wy-alert-title, .rst-content .wy-alert-warning.admonition .wy-alert-title, .wy-alert.wy-alert-warning .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-warning .admonition-title, .rst-content .wy-alert-warning.note .admonition-title, .rst-content .attention .admonition-title, .rst-content .caution .admonition-title, .rst-content .wy-alert-warning.danger .admonition-title, .rst-content .wy-alert-warning.error .admonition-title, .rst-content .wy-alert-warning.hint .admonition-title, .rst-content .wy-alert-warning.important .admonition-title, .rst-content .wy-alert-warning.tip .admonition-title, .rst-content .warning .admonition-title, .rst-content .wy-alert-warning.seealso .admonition-title, .rst-content .admonition-todo .admonition-title, .rst-content .wy-alert-warning.admonition .admonition-title { + background: #ed2222; +} + +.wy-alert.wy-alert-warning, .rst-content .wy-alert-warning.note, .rst-content .attention, .rst-content .caution, .rst-content .wy-alert-warning.danger, .rst-content .wy-alert-warning.error, .rst-content .wy-alert-warning.hint, .rst-content .wy-alert-warning.important, .rst-content .wy-alert-warning.tip, .rst-content .warning, .rst-content .wy-alert-warning.seealso, .rst-content .admonition-todo, .rst-content .wy-alert-warning.admonition { + background: #490707; +} + +/* -------------------------------------general page styling-------------------------------------------- */ +body { + color:#fcfcfc; +} + +.wy-nav-content-wrap, .wy-nav-content { + background:#242424; +} + +/* -------------------------------------sidebar sections------------------------------------------------ */ +.wy-side-nav-search { + background-color:#006B09; + color:#fcfcfc; +} + +.wy-side-nav-search input[type=text] { + background: #3e3e3e; + color: #fcfcfc; +} + +.wy-menu-vertical li.on a, +.wy-menu-vertical li.current>a { + color:#fcfcfc; + background:#242424; +} + +.wy-nav-side { + background:#172353; +} + +.wy-menu-vertical li.on a:hover, +.wy-menu-vertical li.current>a:hover { + background:#395c62; +} + +.wy-menu-vertical li.on a:hover, +.wy-menu-vertical li.current>a:hover { + background:#395c62; +} + +.wy-side-nav-search>div.version { + color:rgba(255, 255, 255, 0.69); +} + +.wy-menu-vertical header, +.wy-menu-vertical p.caption { + color:#43df96; +} + +.wy-nav-top { + background:#006B09; +} + +.wy-menu-vertical li.on a span.toctree-expand, .wy-menu-vertical li.current > a span.toctree-expand { + color: #f2f2f2; +} + +.wy-menu-vertical li span.toctree-expand { + color: #f2f2f2; +} + +.wy-menu-vertical li.current { + background: #034e69; +} + +.wy-menu-vertical li.toctree-l2 a, +.wy-menu-vertical li.toctree-l3 a, +.wy-menu-vertical li.toctree-l4 a { + color:#f2f2f2; +} + +.wy-menu-vertical li.current a:hover { + background:#3e3e3ecf; +} + +.wy-menu-vertical li.toctree-l2.current>a { + background:#242424; +} +.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a { + background:#033969; +} + +/* -----------------------------------------API sections-------------------------------------- */ +.rst-content dl:not(.docutils) dl dt { + background: #343434; + color: #e5df8e; +} + +.rst-content tt, .rst-content tt, .rst-content code { + color: #f3f3f3; +} + +.rst-content dl:not(.docutils) dt { + background: #2e363c; +} + +.rst-content tt.literal, .rst-content tt.literal, .rst-content code.literal { + color: #16FF00; + border-color: #303030; +} + +code, .rst-content tt, .rst-content code { + background: #000; +} + +.rst-content tt.xref, a .rst-content tt, .rst-content tt.xref, .rst-content code.xref, a .rst-content tt, a .rst-content code { + color: #fff; +} + +.rst-content dl:not(.docutils) dl dt { + background: #343434; + color: #e5df8e; +} + +.rst-content dl:not(.docutils) dl dt .headerlink { + color: #29ae5b; +} + +.rst-content dl:not(.docutils) dt .headerlink { + color: #29ae5b4d !important; +} + +.rst-content dl dt .headerlink::after { + visibility: visible; +} + +.rst-content h1:hover .headerlink::after, .rst-content h2:hover .headerlink::after, .rst-content .toctree-wrapper p.caption:hover .headerlink::after, .rst-content h3:hover .headerlink::after, .rst-content h4:hover .headerlink::after, .rst-content h5:hover .headerlink::after, .rst-content h6:hover .headerlink::after, .rst-content dl dt:hover .headerlink::after, .rst-content p.caption:hover .headerlink::after, .rst-content table > caption:hover .headerlink::after, .rst-content .code-block-caption:hover .headerlink::after { + color: #29ae5b !important; +} diff --git a/docs/api.rst b/docs/api.rst index 4622af0..16d9025 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -4,7 +4,6 @@ .. If your library file(s) are nested in a directory (e.g. /adafruit_foo/foo.py) .. use this format as the module name: "adafruit_foo.foo" -.. currentmodule:: circuitpython_nrf24l01.rf24 RF24 class ============== @@ -12,6 +11,7 @@ RF24 class Troubleshooting info -------------------- +.. currentmodule:: circuitpython_nrf24l01.rf24 .. important:: The nRF24L01 has 3 key features that can be interdependent of each other. Their priority of dependence is as follows: @@ -275,5 +275,5 @@ address() Fake BLE ============== - .. automodule:: circuitpython_nrf24l01.fake_ble - :members: + .. autoclass:: circuitpython_nrf24l01.fake_ble + :members: send, name From b4a0577205fd47351d17fa2353e483143cd4d95d Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 21 Mar 2020 13:46:57 -0700 Subject: [PATCH 008/127] migrated docstring to api.rst --- circuitpython_nrf24l01/rf24.py | 681 +------------------------------- docs/api.rst | 683 ++++++++++++++++++++++++++++++++- 2 files changed, 683 insertions(+), 681 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 2352144..472fde2 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -46,71 +46,9 @@ TX_ADDR = 0x10 #: Address that is used for TX transmissions # pylint: enable=bad-whitespace +# documentation lives in api.rst to save space on M0 +# pylint: disable=missing-class-docstring,missing-function-docstring class RF24: - """A driver class for the nRF24L01(+) transceiver radios. This class aims to be compatible with - other devices in the nRF24xxx product line that implement the Nordic proprietary Enhanced - ShockBurst Protocol (and/or the legacy ShockBurst Protocol), but officially only supports - (through testing) the nRF24L01 and nRF24L01+ devices. - - :param ~busio.SPI spi: The object for the SPI bus that the nRF24L01 is connected to. - - .. tip:: This object is meant to be shared amongst other driver classes (like - adafruit_mcp3xxx.mcp3008 for example) that use the same SPI bus. Otherwise, multiple - devices on the same SPI bus with different spi objects may produce errors or - undesirable behavior. - :param ~digitalio.DigitalInOut csn: The digital output pin that is connected to the nRF24L01's - CSN (Chip Select Not) pin. This is required. - :param ~digitalio.DigitalInOut ce: The digital output pin that is connected to the nRF24L01's - CE (Chip Enable) pin. This is required. - :param int channel: This is used to specify a certain radio frequency that the nRF24L01 uses. - Defaults to 76 and can be changed at any time by using the `channel` attribute. - :param int payload_length: This is the length (in bytes) of a single payload to be transmitted - or received. This is ignored if the `dynamic_payloads` attribute is enabled. Defaults to 32 - and must be in range [1,32]. This can be changed at any time by using the `payload_length` - attribute. - :param int address_length: This is the length (in bytes) of the addresses that are assigned to - the data pipes for transmitting/receiving. Defaults to 5 and must be in range [3,5]. This - can be changed at any time by using the `address_length` attribute. - :param int ard: This specifies the delay time (in µs) between attempts to automatically - re-transmit. This can be changed at any time by using the `ard` attribute. This parameter - must be a multiple of 250 in the range [250,4000]. Defualts to 1500 µs. - :param int arc: This specifies the automatic re-transmit count (maximum number of automatically - attempts to re-transmit). This can be changed at any time by using the `arc` attribute. - This parameter must be in the range [0,15]. Defaults to 3. - :param int crc: This parameter controls the CRC setting of transmitted packets. Options are - ``0`` (off), ``1`` or ``2`` (byte long CRC enabled). This can be changed at any time by - using the `crc` attribute. Defaults to 2. - :param int data_rate: This parameter controls the RF data rate setting of transmissions. - Options are ``1`` (Mbps), ``2`` (Mbps), or ``250`` (Kbps). This can be changed at any time - by using the `data_rate` attribute. Defaults to 1. - :param int pa_level: This parameter controls the RF power amplifier setting of transmissions. - Options are ``0`` (dBm), ``-6`` (dBm), ``-12`` (dBm), or ``-18`` (dBm). This can be changed - at any time by using the `pa_level` attribute. Defaults to 0. - :param bool dynamic_payloads: This parameter enables/disables the dynamic payload length - feature of the nRF24L01. Defaults to enabled. This can be changed at any time by using the - `dynamic_payloads` attribute. - :param bool auto_ack: This parameter enables/disables the automatic acknowledgment (ACK) - feature of the nRF24L01. Defaults to enabled if `dynamic_payloads` is enabled. This can be - changed at any time by using the `auto_ack` attribute. - :param bool ask_no_ack: This represents a special flag that has to be thrown to enable a - feature specific to individual payloads. Setting this parameter only enables access to this - feature; it does not invoke it (see parameters for `send()` or `write()` functions). - Enabling/Disabling this does not affect `auto_ack` attribute. - :param bool ack: This represents a special flag that has to be thrown to enable a feature - allowing custom response payloads appended to the ACK packets. Enabling this also requires - the `auto_ack` attribute enabled. This can be changed at any time by using the `ack` - attribute. - :param bool irq_dr: When "Data is Ready", this configures the interrupt (IRQ) trigger of the - nRF24L01's IRQ pin (active low). Defaults to enabled. This can be changed at any time by - using the `interrupt_config()` function. - :param bool irq_ds: When "Data is Sent", this configures the interrupt (IRQ) trigger of the - nRF24L01's IRQ pin (active low). Defaults to enabled. This can be changed at any time by - using the `interrupt_config()` function. - :param bool irq_df: When "max retry attempts are reached" (specified by the `arc` attribute), - this configures the interrupt (IRQ) trigger of the nRF24L01's IRQ pin (active low) and - represents transmission failure. Defaults to enabled. This can be changed at any time by - using the `interrupt_config()` function. - """ def __init__(self, spi, csn, ce, channel=76, payload_length=32, @@ -287,13 +225,6 @@ def _reg_write(self, reg, value=None): @property def address_length(self): - """This `int` attribute specifies the length (in bytes) of addresses to be used for RX/TX - pipes. The addresses assigned to the data pipes must have byte length equal to the value - set for this attribute. - - A valid input value must be an `int` in range [3,5]. Otherwise a `ValueError` exception is - thrown. Default is set to the nRF24L01's maximum of 5. - """ return self._reg_read(SETUP_AW) + 2 @address_length.setter @@ -309,19 +240,6 @@ def address_length(self, length): "address length can only be set in range [3,5] bytes") def open_tx_pipe(self, address): - """This function is used to open a data pipe for OTA (over the air) TX transmissions. - - :param bytearray address: The virtual address of the receiving nRF24L01. This must have a - length equal to the `address_length` attribute (see `address_length` attribute). - Otherwise a `ValueError` exception is thrown. The address specified here must match the - address set to one of the RX data pipes of the receiving nRF24L01. - - .. note:: There is no option to specify which data pipe to use because the nRF24L01 only - uses data pipe 0 in TX mode. Additionally, the nRF24L01 uses the same data pipe (pipe - 0) for receiving acknowledgement (ACK) packets in TX mode when the `auto_ack` attribute - is enabled. Thus, RX pipe 0 is appropriated with the TX address (specified here) when - `auto_ack` is set to `True`. - """ if len(address) == self.address_length: # if auto_ack == True, then use this TX address as the RX address for ACK if self.auto_ack: @@ -341,15 +259,6 @@ def open_tx_pipe(self, address): " {})".format(self.address_length)) def close_rx_pipe(self, pipe_number, reset=True): - """This function is used to close a specific data pipe from OTA (over the air) RX - transmissions. - - :param int pipe_number: The data pipe to use for RX transactions. This must be in range - [0,5]. Otherwise a `ValueError` exception is thrown. - :param bool reset: `True` resets the address for the specified ``pipe_number`` to the - factory address (different for each pipe). `False` leaves the address on the specified - ``pipe_number`` alone. Be aware that the addresses will remain despite loss of power. - """ if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0,5]") self._open_pipes = self._reg_read(EN_RX) # refresh data @@ -370,22 +279,6 @@ def close_rx_pipe(self, pipe_number, reset=True): self._reg_write(EN_RX, self._open_pipes) def open_rx_pipe(self, pipe_number, address): - """This function is used to open a specific data pipe for OTA (over the air) RX - transmissions. If `dynamic_payloads` attribute is `False`, then the `payload_length` - attribute is used to specify the expected length of the RX payload on the specified data - pipe. - - :param int pipe_number: The data pipe to use for RX transactions. This must be in range - [0,5]. Otherwise a `ValueError` exception is thrown. - :param bytearray address: The virtual address to the receiving nRF24L01. This must have a - byte length equal to the `address_length` attribute. Otherwise a `ValueError` - exception is thrown. If using a ``pipe_number`` greater than 1, then only the MSByte - of the address is written, so make sure MSByte (first character) is unique among other - simultaneously receiving addresses). - - .. note:: The nRF24L01 shares the addresses' LSBytes (address[1:5]) on data pipes 2 through - 5. These shared LSBytes are determined by the address set to pipe 1. - """ if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0,5]") if len(address) != self.address_length: @@ -421,28 +314,6 @@ def open_rx_pipe(self, pipe_number, address): @property def listen(self): - """An attribute to represent the nRF24L01 primary role as a radio. - - Setting this attribute incorporates the proper transitioning to/from RX mode as it involves - playing with the `power` attribute and the nRF24L01's CE pin. This attribute does not power - down the nRF24L01, but will power it up when needed; use `power` attribute set to `False` - to put the nRF24L01 to sleep. - - A valid input value is a `bool` in which: - - `True` enables RX mode. Additionally, per `Appendix B of the nRF24L01+ Specifications - Sheet - `_, this attribute - flushes the RX FIFO, clears the `irq_dr` status flag, and puts nRF24L01 in power up - mode. Notice the CE pin is be held HIGH during RX mode. - - `False` disables RX mode. As mentioned in above link, this puts nRF24L01's power in - Standby-I (CE pin is LOW meaning low current & no transmissions) mode which is ideal - for post-reception work. Disabing RX mode doesn't flush the RX/TX FIFO buffers, so - remember to flush your 3-level FIFO buffers when appropriate using `flush_tx()` or - `flush_rx()` (see also the `recv()` function). - """ return self.power and bool(self._config & 1) @listen.setter @@ -487,27 +358,10 @@ def _stop_listening(self): # exits while still in Standby-I (low current & no transmissions) def any(self): - """This function checks if the nRF24L01 has received any data at all, and then reports the - next available payload's length (in bytes) -- if there is any. - - :returns: - - `int` of the size (in bytes) of an available RX payload (if any). - - ``0`` if there is no payload in the RX FIFO buffer. - """ # 0x60 == R_RX_PL_WID command return self._reg_read(0x60) # top-level payload length def recv(self): - """This function is used to retrieve the next available payload in the RX FIFO buffer, then - clears the `irq_dr` status flag. This function synonomous to `read_ack()`. - - :returns: A `bytearray` of the RX payload data or `None` if there is no payload - - - If the `dynamic_payloads` attribute is disabled, then the returned bytearray's length - is equal to the user defined `payload_length` attribute (which defaults to 32). - - If the `dynamic_payloads` attribute is enabled, then the returned bytearray's length - is equal to the payload's length - """ if not self.irq_dr: return None # buffer size = current payload size @@ -520,68 +374,6 @@ def recv(self): return result def send(self, buf, ask_no_ack=False, force_retry=0): - """This blocking function is used to transmit payload(s). - - :returns: - * `list` if a list or tuple of payloads was passed as the ``buf`` parameter. Each item - in the returned list will contain the returned status for each corresponding payload - in the list/tuple that was passed. The return statuses will be in one of the - following forms: - * `False` if transmission fails or reaches the timeout sentinal. The timeout condition - is very rare and could mean something/anything went wrong with either/both TX/RX - transceivers. The timeout sentinal for transmission is calculated using `table 18 in - the nRF24L01 specification sheet `_. - Transmission failure can only be returned if `arc` is greater than ``0``. - * `True` if transmission succeeds. - * `bytearray` or `None` when the `ack` attribute is `True`. Because the payload expects - a responding custom ACK payload, the response is returned (upon successful - transmission) as a - `bytearray` (or `None` if ACK payload is empty) - - :param bytearray,list,tuple buf: The payload to transmit. This bytearray must have a length - greater than 0 and less than 32, otherwise a `ValueError` exception is thrown. This can - also be a list or tuple of payloads (`bytearray`); in which case, all items in the - list/tuple are processed for consecutive transmissions. - - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is less - than the `payload_length` attribute, then this bytearray is padded with zeros until - its length is equal to the `payload_length` attribute. - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is - greater than `payload_length` attribute, then this bytearray's length is truncated to - equal the `payload_length` attribute. - :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for - an acknowledgment from the receiving nRF24L01. This parameter directly controls a - ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about - the payload). Therefore, it takes advantage of an nRF24L01 feature specific to - individual payloads, and its value is not saved anywhere. You do not need to specify - this for every payload if the `arc` attribute is disabled, however this parameter - will work despite the `arc` attribute's setting. - - .. note:: Each transmission is in the form of a packet. This packet contains sections - of data around and including the payload. `See Chapter 7.3 in the nRF24L01 - Specifications Sheet `_ for more - details. - :param int force_retry: The number of brute-force attempts to `resend()` a failed - transmission. Default is 0. This parameter has no affect on transmissions if `arc` is - ``0`` or ``ask_no_ack`` parameter is set to `True`. Each re-attempt still takes - advantage of `arc` & `ard` attributes. During multi-payload processing, this - parameter is meant to slow down CircuitPython devices just enough for the Raspberry - Pi to catch up (due to the Raspberry Pi's seemingly slower SPI speeds). See also - `resend()` as using this parameter carries the same implications documented there. - - .. tip:: It is highly recommended that `arc` attribute is enabled when sending - multiple payloads. Test results with the `arc` attribute disabled were very poor - (much < 50% received). This same advice applies to the ``ask_no_ack`` parameter (leave - it as `False` for multiple payloads). - .. warning:: The nRF24L01 will block usage of the TX FIFO buffer upon failed - transmissions. Failed transmission's payloads stay in TX FIFO buffer until the MCU - calls `flush_tx()` and `clear_status_flags()`. Therefore, this function will discard - failed transmissions' payloads when sending a list or tuple of payloads, so it can - continue to process through the list/tuple even if any payload fails to be - acknowledged. - """ # ensure power down/standby-I for proper manipulation of PWR_UP & PRIM_RX bits in # CONFIG register self.ce_pin.value = 0 @@ -662,108 +454,22 @@ def send(self, buf, ask_no_ack=False, force_retry=0): @property def irq_dr(self): - """A `bool` that represents the "Data Ready" interrupted flag. (read-only) - - * `True` represents Data is in the RX FIFO buffer - * `False` represents anything depending on context (state/condition of FIFO buffers) -- - usually this means the flag's been reset. - - Pass ``dataReady`` parameter as `True` to `clear_status_flags()` and reset this. As this is - a virtual representation of the interrupt event, this attribute will always be updated - despite what the actual IRQ pin is configured to do about this event. - - Calling this does not execute an SPI transaction. It only exposes that latest data - contained in the STATUS byte that's always returned from any other SPI transactions. Use - the `update()` function to manually refresh this data when needed. - """ return bool(self._status & 0x40) @property def irq_ds(self): - """A `bool` that represents the "Data Sent" interrupted flag. (read-only) - - * `True` represents a successful transmission - * `False` represents anything depending on context (state/condition of FIFO buffers) -- - usually this means the flag's been reset. - - Pass ``dataSent`` parameter as `True` to `clear_status_flags()` to reset this. As this is a - virtual representation of the interrupt event, this attribute will always be updated - despite what the actual IRQ pin is configured to do about this event. - - Calling this does not execute an SPI transaction. It only exposes that latest data - contained in the STATUS byte that's always returned from any other SPI transactions. Use - the `update()` function to manually refresh this data when needed. - """ return bool(self._status & 0x20) @property def irq_df(self): - """A `bool` that represents the "Data Failed" interrupted flag. (read-only) - - * `True` signifies the nRF24L01 attemped all configured retries - * `False` represents anything depending on context (state/condition) -- usually this means - the flag's been reset. - - Pass ``dataFail`` parameter as `True` to `clear_status_flags()` to reset this. As this is a - virtual representation of the interrupt event, this attribute will always be updated - despite what the actual IRQ pin is configured to do about this event.see also the `arc` and - `ard` attributes. - - Calling this does not execute an SPI transaction. It only exposes that latest data - contained in the STATUS byte that's always returned from any other SPI transactions. Use - the `update()` function to manually refresh this data when needed. - """ return bool(self._status & 0x10) def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): - """This clears the interrupt flags in the status register. Internally, this is - automatically called by `send()`, `write()`, `recv()`, and when `listen` changes from - `False` to `True`. - - :param bool data_recv: specifies wheather to clear the "RX Data Ready" flag. - :param bool data_sent: specifies wheather to clear the "TX Data Sent" flag. - :param bool data_fail: specifies wheather to clear the "Max Re-transmit reached" flag. - - .. note:: Clearing the ``data_fail`` flag is necessary for continued transmissions from the - nRF24L01 (locks the TX FIFO buffer when `irq_df` is `True`) despite wheather or not the - MCU is taking advantage of the interrupt (IRQ) pin. Call this function only when there - is an antiquated status flag (after you've dealt with the specific payload related to - the staus flags that were set), otherwise it can cause payloads to be ignored and - occupy the RX/TX FIFO buffers. See `Appendix A of the nRF24L01+ Specifications Sheet - `_ for an outline of - proper behavior. - """ # 0x07 = STATUS register; only bits 6 through 4 are write-able self._reg_write(0x07, (data_recv << 6) | ( data_sent << 5) | (data_fail << 4)) def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): - """Sets the configuration of the nRF24L01's IRQ (interrupt) pin. The signal from the - nRF24L01's IRQ pin is active LOW. (write-only) - - :param bool data_recv: If this is `True`, then IRQ pin goes active when there is new data - to read in the RX FIFO buffer. - :param bool data_sent: If this is `True`, then IRQ pin goes active when a payload from TX - buffer is successfully transmit. - :param bool data_fail: If this is `True`, then IRQ pin goes active when maximum number of - attempts to re-transmit the packet have been reached. If `auto_ack` attribute is - disabled, then this IRQ event is not used. - - .. note:: To fetch the status (not configuration) of these IRQ flags, use the `irq_df`, - `irq_ds`, `irq_dr` attributes respectively. - - .. tip:: Paraphrased from nRF24L01+ Specification Sheet: - - The procedure for handling ``data_recv`` IRQ should be: - - 1. read payload through `recv()` - 2. clear ``dataReady`` status flag (taken care of by using `recv()` in previous step) - 3. read FIFO_STATUS register to check if there are more payloads available in RX FIFO - buffer. (a call to `pipe()`, `any()` or even ``(False,True)`` as parameters to - `fifo()` will get this result) - 4. if there is more data in RX FIFO, repeat from step 1 - """ self._config = self._reg_read(CONFIG) # refresh data # save to register and update local copy of pwr & RX/TX modes' flags self._config = (self._config & 0x0F) | (not data_fail << 4) | (not data_sent << 5) | \ @@ -771,59 +477,6 @@ def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): self._reg_write(CONFIG, self._config) def what_happened(self, dump_pipes=False): - """This debuggung function aggregates and outputs all status/condition related information - from the nRF24L01. Some information may be irrelevant depending on nRF24L01's - state/condition. - - :prints: - - - ``Channel`` The current setting of the `channel` attribute - - ``RF Data Rate`` The current setting of the RF `data_rate` attribute. - - ``RF Power Amplifier`` The current setting of the `pa_level` attribute. - - ``CRC bytes`` The current setting of the `crc` attribute - - ``Address length`` The current setting of the `address_length` attribute - - ``Payload lengths`` The current setting of the `payload_length` attribute - - ``Auto retry delay`` The current setting of the `ard` attribute - - ``Auto retry attempts`` The current setting of the `arc` attribute - - ``Packets lost on current channel`` Total amount of packets lost (transmission - failures). This only resets when the `channel` is changed. This count will - only go up 15. - - ``Retry attempts made for last transmission`` Amount of attempts to re-transmit - during last - transmission (resets per payload) - - ``IRQ - Data Ready`` The current setting of the IRQ pin on "Data Ready" event - - ``IRQ - Data Sent`` The current setting of the IRQ pin on "Data Sent" event - - ``IRQ - Data Fail`` The current setting of the IRQ pin on "Data Fail" event - - ``Data Ready`` Is there RX data ready to be read? - (state of the `irq_dr` flag) - - ``Data Sent`` Has the TX data been sent? (state of the `irq_ds` flag) - - ``Data Failed`` Has the maximum attempts to re-transmit been reached? - (state of the `irq_df` flag) - - ``TX FIFO full`` Is the TX FIFO buffer full? (state of the `tx_full` flag) - - ``TX FIFO empty`` Is the TX FIFO buffer empty? - - ``RX FIFO full`` Is the RX FIFO buffer full? - - ``RX FIFO empty`` Is the RX FIFO buffer empty? - - ``Custom ACK payload`` Is the nRF24L01 setup to use an extra (user defined) payload - attached to the acknowledgment packet? (state of the `ack` attribute) - - ``Ask no ACK`` Is the nRF24L01 setup to transmit individual packets that don't - require acknowledgment? - - ``Automatic Acknowledgment`` Is the `auto_ack` attribute enabled? - - ``Dynamic Payloads`` Is the `dynamic_payloads` attribute enabled? - - ``Primary Mode`` The current mode (RX or TX) of communication of the nRF24L01 device. - - ``Power Mode`` The power state can be Off, Standby-I, Standby-II, or On. - - :param bool dump_pipes: `True` appends the output and prints: - - * the current address used for TX transmissions - * ``Pipe [#] ([open/closed]) bound: [address]`` where ``#`` represent the pipe number, - the ``open/closed`` status is relative to the pipe's RX status, and ``address`` is - read directly from the nRF24L01 registers. - * if the pipe is open, then the output also prints ``expecting [X] byte static - payloads`` where ``X`` is the `payload_length` (in bytes) the pipe is setup to - receive when `dynamic_payloads` is disabled. - - Default is `False` and skips this extra information. - """ watchdog = self._reg_read(8) # 8 == OBSERVE_TX register print("Channel___________________{} ~ {} GHz".format( self.channel, (self.channel + 2400) / 1000)) @@ -871,13 +524,6 @@ def what_happened(self, dump_pipes=False): @property def dynamic_payloads(self): - """This `bool` attribute controls the nRF24L01's dynamic payload length feature. - - - `True` enables nRF24L01's dynamic payload length feature. The `payload_length` - attribute is ignored when this feature is enabled. - - `False` disables nRF24L01's dynamic payload length feature. Be sure to adjust - the `payload_length` attribute accordingly when `dynamic_payloads` feature is disabled. - """ return bool(self._dyn_pl and (self._features & 4)) @dynamic_payloads.setter @@ -895,20 +541,6 @@ def dynamic_payloads(self, enable): @property def payload_length(self): - """This `int` attribute specifies the length (in bytes) of payload that is regarded, - meaning "how big of a payload should the radio care about?" If the `dynamic_payloads` - attribute is enabled, this attribute has no affect. When `dynamic_payloads` is disabled, - this attribute is used to specify the payload length when entering RX mode. - - A valid input value must be an `int` in range [1,32]. Otherwise a `ValueError` exception is - thrown. Default is set to the nRF24L01's maximum of 32. - - .. note:: When `dynamic_payloads` is disabled during transmissions: - - - Payloads' size of greater than this attribute's value will be truncated to match. - - Payloads' size of less than this attribute's value will be padded with zeros to - match. - """ return self._payload_length @payload_length.setter @@ -923,14 +555,6 @@ def payload_length(self, length): @property def arc(self): - """"This `int` attribute specifies the nRF24L01's number of attempts to re-transmit TX - payload when acknowledgment packet is not received. The `auto_ack` must be enabled on the - receiving nRF24L01, otherwise this attribute will make `send()` seem like it failed. - - A valid input value must be in range [0,15]. Otherwise a `ValueError` exception is thrown. - Default is set to 3. A value of ``0`` disables the automatic re-transmit feature and - considers all payload transmissions a success. - """ self._setup_retr = self._reg_read(SETUP_RETR) # refresh data return self._setup_retr & 0x0f @@ -947,23 +571,6 @@ def arc(self, count): @property def ard(self): - """This `int` attribute specifies the nRF24L01's delay (in µs) between attempts to - automatically re-transmit the TX payload when an expected acknowledgement (ACK) packet is - not received. During this time, the nRF24L01 is listening for the ACK packet. If the - `auto_ack` attribute is disabled, this attribute is not applied. - - A valid input value must be a multiple of 250 in range [250,4000]. Otherwise a `ValueError` - exception is thrown. Default is 1500 for reliability. - - .. note:: Paraphrased from nRF24L01 specifications sheet: - - Please take care when setting this parameter. If the custom ACK payload is more than 15 - bytes in 2 Mbps data rate, the `ard` must be 500µS or more. If the custom ACK payload - is more than 5 bytes in 1 Mbps data rate, the `ard` must be 500µS or more. In 250kbps - data rate (even when there is no custom ACK payload) the `ard` must be 500µS or more. - - See `data_rate` attribute on how to set the data rate of the nRF24L01's transmissions. - """ self._setup_retr = self._reg_read(SETUP_RETR) # refresh data return ((self._setup_retr & 0xf0) >> 4) * 250 + 250 @@ -982,15 +589,6 @@ def ard(self, delta_t): @property def auto_ack(self): - """This `bool` attribute controls the nRF24L01's automatic acknowledgment feature during - the process of receiving a packet. - - - `True` enables transmitting automatic acknowledgment packets. The CRC (cyclic redundancy - checking) is enabled automatically by the nRF24L01 if the `auto_ack` attribute is enabled - (see also `crc` attribute). - - `False` disables transmitting automatic acknowledgment packets. The `crc` attribute will - remain unaffected when disabling the `auto_ack` attribute. - """ return self._aa @auto_ack.setter @@ -1003,17 +601,6 @@ def auto_ack(self, enable): @property def ack(self): - """This `bool` attribute represents the status of the nRF24L01's capability to use custom - payloads as part of the automatic acknowledgment (ACK) packet. Use this attribute to - set/check if the custom ACK payloads feature is enabled. - - - `True` enables the use of custom ACK payloads in the ACK packet when responding to - receiving transmissions. As `dynamic_payloads` and `auto_ack` attributes are required for - this feature to work, they are automatically enabled as needed. - - `False` disables the use of custom ACK payloads. Disabling this feature does not disable - the `auto_ack` and `dynamic_payloads` attributes (they work just fine without this - feature). - """ return bool((self._features & 2) and self.auto_ack and self.dynamic_payloads) @ack.setter @@ -1033,35 +620,6 @@ def ack(self, enable): self._reg_write(FEATURE, self._features) def load_ack(self, buf, pipe_number): - """This allows the MCU to specify a payload to be allocated into the TX FIFO buffer for use - on a specific data pipe. This payload will then be appended to the automatic acknowledgment - (ACK) packet that is sent when fresh data is received on the specified pipe. See - `read_ack()` on how to fetch a received custom ACK payloads. - - :param bytearray buf: This will be the data attached to an automatic ACK packet on the - incoming transmission about the specified ``pipe_number`` parameter. This must have a - length in range [1,32] bytes, otherwise a `ValueError` exception is thrown. Any ACK - payloads will remain in the TX FIFO buffer until transmitted successfully or - `flush_tx()` is called. - :param int pipe_number: This will be the pipe number to use for deciding which - transmissions get a response with the specified ``buf`` parameter's data. This number - must be in range [0,5], otherwise a `ValueError` exception is thrown. - - :returns: `True` if payload was successfully loaded onto the TX FIFO buffer. `False` if it - wasn't because TX FIFO buffer is full. - - .. note:: this function takes advantage of a special feature on the nRF24L01 and needs to - be called for every time a customized ACK payload is to be used (not for every - automatic ACK packet -- this just appends a payload to the ACK packet). The `ack`, - `auto_ack`, and `dynamic_payloads` attributes are also automatically enabled by this - function when necessary. - - .. tip:: The ACK payload must be set prior to receiving a transmission. It is also worth - noting that the nRF24L01 can hold up to 3 ACK payloads pending transmission. Using this - function does not over-write existing ACK payloads pending; it only adds to the queue - (TX FIFO buffer) if it can. Use `flush_tx()` to discard unused ACK payloads when done - listening. - """ if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0,5]") if not buf or len(buf) > 32: @@ -1077,36 +635,10 @@ def load_ack(self, buf, pipe_number): return False # payload wasn't loaded def read_ack(self): - """Allows user to read the automatic acknowledgement (ACK) payload (if any) when nRF24L01 - is in TX mode. This function is called from a blocking `send()` call if the `ack` attribute - is enabled. Alternatively, this function can be called directly in case of calling the - non-blocking `write()` function during asychronous applications. This function is an alias - of `recv()` and remains for bakward compatibility with older versions of this library. - - .. note:: See also the `ack`, `dynamic_payloads`, and `auto_ack` attributes as they must be - enabled to use custom ACK payloads. - """ return self.recv() @property def data_rate(self): - """This `int` attribute specifies the nRF24L01's frequency data rate for OTA (over the air) - transmissions. - - A valid input value is: - - - ``1`` sets the frequency data rate to 1 Mbps - - ``2`` sets the frequency data rate to 2 Mbps - - ``250`` sets the frequency data rate to 250 Kbps - - Any invalid input throws a `ValueError` exception. Default is 1 Mbps. - - .. warning:: 250 Kbps is be buggy on the non-plus models of the nRF24L01 product line. If - you use 250 Kbps data rate, and some transmissions report failed by the transmitting - nRF24L01, even though the same packet in question actually reports received by the - receiving nRF24L01, then try a higher data rate. CAUTION: Higher data rates mean less - maximum distance between nRF24L01 transceivers (and vise versa). - """ self._rf_setup = self._reg_read(RF_SETUP) # refresh data return (2 if self._rf_setup & 0x28 == 8 else 250) if self._rf_setup & 0x28 else 1 @@ -1126,11 +658,6 @@ def data_rate(self, speed): @property def channel(self): - """This `int` attribute specifies the nRF24L01's frequency (in 2400 + `channel` MHz). - - A valid input value must be in range [0, 125] (that means [2.4, 2.525] GHz). Otherwise a - `ValueError` exception is thrown. Default is 76. - """ return self._reg_read(RF_CH) @channel.setter @@ -1143,21 +670,6 @@ def channel(self, channel): @property def crc(self): - """This `int` attribute specifies the nRF24L01's CRC (cyclic redundancy checking) encoding - scheme in terms of byte length. CRC is a way of making sure that the transmission didn't - get corrupted over the air. - - A valid input value is in range [0,2]: - - - ``0`` disables CRC (no anti-corruption of data) - - ``1`` enables CRC encoding scheme using 1 byte (weak anti-corruption of data) - - ``2`` enables CRC encoding scheme using 2 bytes (better anti-corruption of data) - - Any invalid input throws a `ValueError` exception. Default is enabled using 2 bytes. - - .. note:: The nRF24L01 automatically enables CRC if automatic acknowledgment feature is - enabled (see `auto_ack` attribute). - """ self._config = self._reg_read(CONFIG) # refresh data return max(0, ((self._config & 12) >> 2) - 1) # this works @@ -1175,28 +687,6 @@ def crc(self, length): @property def power(self): - """This `bool` attribute controls the power state of the nRF24L01. This is exposed for - asynchronous applications and user preference. - - - `False` basically puts the nRF24L01 to sleep (AKA power down mode) with ultra-low - current consumption. No transmissions are executed when sleeping, but the nRF24L01 can - still be accessed through SPI. Upon instantiation, this driver class puts the nRF24L01 - to sleep until the MCU invokes RX/TX transmissions. This driver class doesn't power down - the nRF24L01 after RX/TX transmissions are complete (avoiding the required power up/down - 130 µs wait time), that preference is left to the user. - - `True` powers up the nRF24L01. This is the first step towards entering RX/TX modes (see - also `listen` attribute). Powering up is automatically handled by the `listen` attribute - as well as the `send()` and `write()` functions. - - .. note:: This attribute needs to be `True` if you want to put radio on Standby-II (highest - current consumption) or Standby-I (moderate current consumption) modes. TX - transmissions are only executed during Standby-II by calling `send()` or `write()`. RX - transmissions are received during Standby-II by setting `listen` attribute to `True` - (see `Chapter 6.1.2-7 of the nRF24L01+ Specifications Sheet `_). After using `send()` or setting `listen` to `False`, the nRF24L01 - is left in Standby-I mode (see also notes on the `write()` function). - """ return bool(self._config & 2) @power.setter @@ -1214,19 +704,6 @@ def power(self, is_on): @property def pa_level(self): - """This `int` attribute specifies the nRF24L01's power amplifier level (in dBm). Higher - levels mean the transmission will cover a longer distance. Use this attribute to tweak the - nRF24L01 current consumption on projects that don't span large areas. - - A valid input value is: - - - ``-18`` sets the nRF24L01's power amplifier to -18 dBm (lowest) - - ``-12`` sets the nRF24L01's power amplifier to -12 dBm - - ``-6`` sets the nRF24L01's power amplifier to -6 dBm - - ``0`` sets the nRF24L01's power amplifier to 0 dBm (highest) - - Any invalid input throws a `ValueError` exception. Default is 0 dBm. - """ self._rf_setup = self._reg_read(RF_SETUP) # refresh data return (3 - ((self._rf_setup & RF_SETUP) >> 1)) * -6 @@ -1245,62 +722,18 @@ def pa_level(self, power): @property def rpd(self): - """This read-only attribute returns `True` if RPD (Received Power Detector) is triggered - or `False` if not triggered. - - .. note:: The RPD flag is triggered in the following cases: - - 1. During RX mode (`listen` = `True`) and a RF transmission with a gain above a preset - (non-adjustable) -64 dBm threshold. - 2. When a packet is received (indicative of the nRF24L01 used to detect/"listen" for - incoming packets). - 3. When the nRF24L01's CE pin goes from HIGH to LOW (or when the `listen` attribute - changes from `True` to `False`). - 4. When the underlying ESB (Enhanced ShockBurst) protocol reaches a hardcoded - (non-adjustable) RX timeout. - - """ return bool(self._reg_read(0x09)) @property def tx_full(self): - """An attribute to represent the nRF24L01's status flag signaling that the TX FIFO buffer - is full. (read-only) - - Calling this does not execute an SPI transaction. It only exposes that latest data - contained in the STATUS byte that's always returned from any SPI transactions with the - nRF24L01. Use the `update()` function to manually refresh this data when needed. - - :returns: - * `True` for TX FIFO buffer is full - * `False` for TX FIFO buffer is not full. This doesn't mean the TX FIFO buffer is - empty. - """ return bool(self._status & 1) def update(self): - """This function is only used to get an updated status byte over SPI from the nRF24L01 and - is exposed to the MCU for asynchronous applications. Refreshing the status byte is vital to - checking status of the interrupts, RX pipe number related to current RX payload, and if the - TX FIFO buffer is full. This function returns nothing, but internally updates the `irq_dr`, - `irq_ds`, `irq_df`, and `tx_full` attributes. Internally this is a helper function to - `pipe()`, `send()`, and `resend()` functions""" # perform non-operation to get status byte # should be faster than reading the STATUS register self._reg_write(0xFF) def resend(self): - """Use this function to maunally re-send the previous payload in the - top level (first out) of the TX FIFO buffer. All returned data follows the same patttern - that `send()` returns with the added condition that this function will return `False` - if the TX FIFO buffer is empty. - - .. note:: The nRF24L01 normally removes a payload from the TX FIFO buffer after successful - transmission, but not when this function is called. The payload (successfully - transmitted or not) will remain in the TX FIFO buffer until `flush_tx()` is called to - remove them. Alternatively, using this function also allows the failed payload to be - over-written by using `send()` or `write()` to load a new payload. - """ result = False if not self.fifo(True, True): # is there a pre-existing payload self.clear_status_flags(False) # clear TX related flags @@ -1333,65 +766,6 @@ def resend(self): return result def write(self, buf, ask_no_ack=False): - """This non-blocking function (when used as alternative to `send()`) is meant for - asynchronous applications and can only handle one payload at a time as it is a helper - function to `send()`. - - :param bytearray buf: The payload to transmit. This bytearray must have a length greater - than 0 and less than 32 bytes, otherwise a `ValueError` exception is thrown. - - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is less - than the `payload_length` attribute, then this bytearray is padded with zeros until - its length is equal to the `payload_length` attribute. - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is - greater than `payload_length` attribute, then this bytearray's length is truncated to - equal the `payload_length` attribute. - :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for - an acknowledgment from the receiving nRF24L01. This parameter directly controls a - ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about - the payload). Therefore, it takes advantage of an nRF24L01 feature specific to - individual payloads, and its value is not saved anywhere. You do not need to specify - this for every payload if the `auto_ack` attribute is disabled, however this parameter - should work despite the `auto_ack` attribute's setting. - - .. note:: Each transmission is in the form of a packet. This packet contains sections - of data around and including the payload. `See Chapter 7.3 in the nRF24L01 - Specifications Sheet `_ for more - details. - - This function isn't completely non-blocking as we still need to wait just under 5 ms for - the CSN pin to settle (allowing a clean SPI transaction). - - .. note:: The nRF24L01 doesn't initiate sending until a mandatory minimum 10 µs pulse on - the CE pin is acheived. That pulse is initiated before this function exits. However, we - have left that 10 µs wait time to be managed by the MCU in cases of asychronous - application, or it is managed by using `send()` instead of this function. According to - the Specification sheet, if the CE pin remains HIGH for longer than 10 µs, then the - nRF24L01 will continue to transmit all payloads found in the TX FIFO buffer. - - .. warning:: A note paraphrased from the `nRF24L01+ Specifications Sheet `_: - - It is important to NEVER to keep the nRF24L01+ in TX mode for more than 4 ms at a time. - If the [`arc` attribute is] enabled, nRF24L01+ is never in TX mode longer than 4 - ms. - - .. tip:: Use this function at your own risk. Because of the underlying `"Enhanced - ShockBurst Protocol" `_, disobeying the 4 - ms rule is easily avoided if you enable the `auto_ack` attribute. Alternatively, you - MUST use interrupt flags or IRQ pin with user defined timer(s) to AVOID breaking the - 4 ms rule. If the `nRF24L01+ Specifications Sheet explicitly states this - `_, we have to assume - radio damage or misbehavior as a result of disobeying the 4 ms rule. See also `table 18 - in the nRF24L01 specification sheet `_ for calculating - necessary transmission time (these calculations are used in the `send()` and `resend()` - functions). - """ if not buf or len(buf) > 32: raise ValueError("buf must be a buffer protocol object with a byte length of" "\nat least 1 and no greater than 32") @@ -1424,49 +798,12 @@ def write(self, buf, ask_no_ack=False): # automatically goes to standby-II after successful TX of all payloads in the FIFO def flush_rx(self): - """A helper function to flush the nRF24L01's internal RX FIFO buffer. (write-only) - - .. note:: The nRF24L01 RX FIFO is 3 level stack that holds payload data. This means that - there can be up to 3 received payloads (each of a maximum length equal to 32 bytes) - waiting to be read (and popped from the stack) by `recv()` or `read_ack()`. This - function clears all 3 levels. - """ self._reg_write(0xE2) def flush_tx(self): - """A helper function to flush the nRF24L01's internal TX FIFO buffer. (write-only) - - .. note:: The nRF24L01 TX FIFO is 3 level stack that holds payload data. This means that - there can be up to 3 payloads (each of a maximum length equal to 32 bytes) waiting to - be transmit by `send()`, `resend()` or `write()`. This function clears all 3 levels. It - is worth noting that the payload data is only popped from the TX FIFO stack upon - successful transmission (see also `resend()` as the handling of failed transmissions - can be altered). - """ self._reg_write(0xE1) def fifo(self, about_tx=False, check_empty=None): - """This provides some precision determining the status of the TX/RX FIFO buffers. - (read-only) - - :param bool about_tx: - * `True` means information returned is about the TX FIFO buffer. - * `False` means information returned is about the RX FIFO buffer. This parameter - defaults to `False` when not specified. - :param bool check_empty: - * `True` tests if the specified FIFO buffer is empty. - * `False` tests if the specified FIFO buffer is full. - * `None` (when not specified) returns a 2 bit number representing both empty (bit 1) & - full (bit 0) tests related to the FIFO buffer specified using the ``tx`` parameter. - :returns: - * A `bool` answer to the question: - "Is the [TX/RX]:[`True`/`False`] FIFO buffer [empty/full]:[`True`/`False`]? - * If the ``check_empty`` parameter is not specified: an `int` in range [0,2] for which: - - - ``1`` means the specified FIFO buffer is full - - ``2`` means the specified FIFO buffer is empty - - ``0`` means the specified FIFO buffer is neither full nor empty - """ if (check_empty is None and isinstance(about_tx, (bool, int))) or \ (isinstance(check_empty, (bool, int)) and isinstance(about_tx, (bool, int))): self._fifo = self._reg_read(FIFO) # refresh the data @@ -1477,14 +814,6 @@ def fifo(self, about_tx=False, check_empty=None): " ('check_empty'), if specified, must be a bool or int") def pipe(self): - """This function returns information about the data pipe that received the next available - payload in the RX FIFO buffer. - - :returns: - - `None` if there is no payload in RX FIFO. - - The `int` identifying pipe number [0,5] that received the next available payload in - the RX FIFO buffer. - """ self.update() # perform Non-operation command to get status byte (should be faster) result = (self._status & 0x0E) >> 1 # 0x0E==RX_P_NO if result <= 5: # is there data in RX FIFO? @@ -1492,12 +821,6 @@ def pipe(self): return None # RX FIFO is empty def address(self, index=-1): - """Returns the current address set to a specified data pipe or the TX address. (read-only) - - :param int index: the number of the data pipe whose address is to be returned. Defaults to - ``-1``. A valid index ranges [0,5] for RX addresses or any negative `int` for the TX - address. Otherwise an `IndexError` is thown. - """ if index > 5: raise IndexError("index {} is out of bounds [0,5]".format(index)) if index < 0: diff --git a/docs/api.rst b/docs/api.rst index 4829000..d3e4186 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -78,52 +78,262 @@ With the `auto_ack` feature enabled, you get: Basic API --------- -Contrusctor +Contructor ****************** .. autoclass:: circuitpython_nrf24l01.rf24.RF24 - :no-members: + :no-members: + + A driver class for the nRF24L01(+) transceiver radios. This class aims to be compatible with + other devices in the nRF24xxx product line that implement the Nordic proprietary Enhanced + ShockBurst Protocol (and/or the legacy ShockBurst Protocol), but officially only supports + (through testing) the nRF24L01 and nRF24L01+ devices. + + :param ~busio.SPI spi: The object for the SPI bus that the nRF24L01 is connected to. + + .. tip:: This object is meant to be shared amongst other driver classes (like + adafruit_mcp3xxx.mcp3008 for example) that use the same SPI bus. Otherwise, multiple + devices on the same SPI bus with different spi objects may produce errors or + undesirable behavior. + :param ~digitalio.DigitalInOut csn: The digital output pin that is connected to the nRF24L01's + CSN (Chip Select Not) pin. This is required. + :param ~digitalio.DigitalInOut ce: The digital output pin that is connected to the nRF24L01's + CE (Chip Enable) pin. This is required. + :param int channel: This is used to specify a certain radio frequency that the nRF24L01 uses. + Defaults to 76 and can be changed at any time by using the `channel` attribute. + :param int payload_length: This is the length (in bytes) of a single payload to be transmitted + or received. This is ignored if the `dynamic_payloads` attribute is enabled. Defaults to 32 + and must be in range [1,32]. This can be changed at any time by using the `payload_length` + attribute. + :param int address_length: This is the length (in bytes) of the addresses that are assigned to + the data pipes for transmitting/receiving. Defaults to 5 and must be in range [3,5]. This + can be changed at any time by using the `address_length` attribute. + :param int ard: This specifies the delay time (in µs) between attempts to automatically + re-transmit. This can be changed at any time by using the `ard` attribute. This parameter + must be a multiple of 250 in the range [250,4000]. Defualts to 1500 µs. + :param int arc: This specifies the automatic re-transmit count (maximum number of automatically + attempts to re-transmit). This can be changed at any time by using the `arc` attribute. + This parameter must be in the range [0,15]. Defaults to 3. + :param int crc: This parameter controls the CRC setting of transmitted packets. Options are + ``0`` (off), ``1`` or ``2`` (byte long CRC enabled). This can be changed at any time by + using the `crc` attribute. Defaults to 2. + :param int data_rate: This parameter controls the RF data rate setting of transmissions. + Options are ``1`` (Mbps), ``2`` (Mbps), or ``250`` (Kbps). This can be changed at any time + by using the `data_rate` attribute. Defaults to 1. + :param int pa_level: This parameter controls the RF power amplifier setting of transmissions. + Options are ``0`` (dBm), ``-6`` (dBm), ``-12`` (dBm), or ``-18`` (dBm). This can be changed + at any time by using the `pa_level` attribute. Defaults to 0. + :param bool dynamic_payloads: This parameter enables/disables the dynamic payload length + feature of the nRF24L01. Defaults to enabled. This can be changed at any time by using the + `dynamic_payloads` attribute. + :param bool auto_ack: This parameter enables/disables the automatic acknowledgment (ACK) + feature of the nRF24L01. Defaults to enabled if `dynamic_payloads` is enabled. This can be + changed at any time by using the `auto_ack` attribute. + :param bool ask_no_ack: This represents a special flag that has to be thrown to enable a + feature specific to individual payloads. Setting this parameter only enables access to this + feature; it does not invoke it (see parameters for `send()` or `write()` functions). + Enabling/Disabling this does not affect `auto_ack` attribute. + :param bool ack: This represents a special flag that has to be thrown to enable a feature + allowing custom response payloads appended to the ACK packets. Enabling this also requires + the `auto_ack` attribute enabled. This can be changed at any time by using the `ack` + attribute. + :param bool irq_dr: When "Data is Ready", this configures the interrupt (IRQ) trigger of the + nRF24L01's IRQ pin (active low). Defaults to enabled. This can be changed at any time by + using the `interrupt_config()` function. + :param bool irq_ds: When "Data is Sent", this configures the interrupt (IRQ) trigger of the + nRF24L01's IRQ pin (active low). Defaults to enabled. This can be changed at any time by + using the `interrupt_config()` function. + :param bool irq_df: When "max retry attempts are reached" (specified by the `arc` attribute), + this configures the interrupt (IRQ) trigger of the nRF24L01's IRQ pin (active low) and + represents transmission failure. Defaults to enabled. This can be changed at any time by + using the `interrupt_config()` function. address_length ****************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.address_length + This `int` attribute specifies the length (in bytes) of addresses to be used for RX/TX + pipes. The addresses assigned to the data pipes must have byte length equal to the value + set for this attribute. + + A valid input value must be an `int` in range [3,5]. Otherwise a `ValueError` exception is + thrown. Default is set to the nRF24L01's maximum of 5. + open_tx_pipe() ****************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.open_tx_pipe + This function is used to open a data pipe for OTA (over the air) TX transmissions. + + :param bytearray address: The virtual address of the receiving nRF24L01. This must have a + length equal to the `address_length` attribute (see `address_length` attribute). + Otherwise a `ValueError` exception is thrown. The address specified here must match the + address set to one of the RX data pipes of the receiving nRF24L01. + + .. note:: There is no option to specify which data pipe to use because the nRF24L01 only + uses data pipe 0 in TX mode. Additionally, the nRF24L01 uses the same data pipe (pipe + 0) for receiving acknowledgement (ACK) packets in TX mode when the `auto_ack` attribute + is enabled. Thus, RX pipe 0 is appropriated with the TX address (specified here) when + `auto_ack` is set to `True`. + close_rx_pipe() ****************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.close_rx_pipe + This function is used to close a specific data pipe from OTA (over the air) RX + transmissions. + + :param int pipe_number: The data pipe to use for RX transactions. This must be in range + [0,5]. Otherwise a `ValueError` exception is thrown. + :param bool reset: `True` resets the address for the specified ``pipe_number`` to the + factory address (different for each pipe). `False` leaves the address on the specified + ``pipe_number`` alone. Be aware that the addresses will remain despite loss of power. + open_rx_pipe() ****************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.open_rx_pipe + This function is used to open a specific data pipe for OTA (over the air) RX + transmissions. If `dynamic_payloads` attribute is `False`, then the `payload_length` + attribute is used to specify the expected length of the RX payload on the specified data + pipe. + + :param int pipe_number: The data pipe to use for RX transactions. This must be in range + [0,5]. Otherwise a `ValueError` exception is thrown. + :param bytearray address: The virtual address to the receiving nRF24L01. This must have a + byte length equal to the `address_length` attribute. Otherwise a `ValueError` + exception is thrown. If using a ``pipe_number`` greater than 1, then only the MSByte + of the address is written, so make sure MSByte (first character) is unique among other + simultaneously receiving addresses). + + .. note:: The nRF24L01 shares the addresses' LSBytes (address[1:5]) on data pipes 2 through + 5. These shared LSBytes are determined by the address set to pipe 1. + listen ****************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.listen + An attribute to represent the nRF24L01 primary role as a radio. + + Setting this attribute incorporates the proper transitioning to/from RX mode as it involves + playing with the `power` attribute and the nRF24L01's CE pin. This attribute does not power + down the nRF24L01, but will power it up when needed; use `power` attribute set to `False` + to put the nRF24L01 to sleep. + + A valid input value is a `bool` in which: + + `True` enables RX mode. Additionally, per `Appendix B of the nRF24L01+ Specifications + Sheet + `_, this attribute + flushes the RX FIFO, clears the `irq_dr` status flag, and puts nRF24L01 in power up + mode. Notice the CE pin is be held HIGH during RX mode. + + `False` disables RX mode. As mentioned in above link, this puts nRF24L01's power in + Standby-I (CE pin is LOW meaning low current & no transmissions) mode which is ideal + for post-reception work. Disabing RX mode doesn't flush the RX/TX FIFO buffers, so + remember to flush your 3-level FIFO buffers when appropriate using `flush_tx()` or + `flush_rx()` (see also the `recv()` function). + any() ****************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.any + This function checks if the nRF24L01 has received any data at all, and then reports the + next available payload's length (in bytes) -- if there is any. + + :returns: + - `int` of the size (in bytes) of an available RX payload (if any). + - ``0`` if there is no payload in the RX FIFO buffer. + recv() ****************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.recv + This function is used to retrieve the next available payload in the RX FIFO buffer, then + clears the `irq_dr` status flag. This function synonomous to `read_ack()`. + + :returns: A `bytearray` of the RX payload data or `None` if there is no payload + + - If the `dynamic_payloads` attribute is disabled, then the returned bytearray's length + is equal to the user defined `payload_length` attribute (which defaults to 32). + - If the `dynamic_payloads` attribute is enabled, then the returned bytearray's length + is equal to the payload's length + send() ****************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.send + This blocking function is used to transmit payload(s). + + :returns: + * `list` if a list or tuple of payloads was passed as the ``buf`` parameter. Each item + in the returned list will contain the returned status for each corresponding payload + in the list/tuple that was passed. The return statuses will be in one of the + following forms: + * `False` if transmission fails or reaches the timeout sentinal. The timeout condition + is very rare and could mean something/anything went wrong with either/both TX/RX + transceivers. The timeout sentinal for transmission is calculated using `table 18 in + the nRF24L01 specification sheet `_. + Transmission failure can only be returned if `arc` is greater than ``0``. + * `True` if transmission succeeds. + * `bytearray` or `None` when the `ack` attribute is `True`. Because the payload expects + a responding custom ACK payload, the response is returned (upon successful + transmission) as a + `bytearray` (or `None` if ACK payload is empty) + + :param bytearray,list,tuple buf: The payload to transmit. This bytearray must have a length + greater than 0 and less than 32, otherwise a `ValueError` exception is thrown. This can + also be a list or tuple of payloads (`bytearray`); in which case, all items in the + list/tuple are processed for consecutive transmissions. + + - If the `dynamic_payloads` attribute is disabled and this bytearray's length is less + than the `payload_length` attribute, then this bytearray is padded with zeros until + its length is equal to the `payload_length` attribute. + - If the `dynamic_payloads` attribute is disabled and this bytearray's length is + greater than `payload_length` attribute, then this bytearray's length is truncated to + equal the `payload_length` attribute. + :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for + an acknowledgment from the receiving nRF24L01. This parameter directly controls a + ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about + the payload). Therefore, it takes advantage of an nRF24L01 feature specific to + individual payloads, and its value is not saved anywhere. You do not need to specify + this for every payload if the `arc` attribute is disabled, however this parameter + will work despite the `arc` attribute's setting. + + .. note:: Each transmission is in the form of a packet. This packet contains sections + of data around and including the payload. `See Chapter 7.3 in the nRF24L01 + Specifications Sheet `_ for more + details. + :param int force_retry: The number of brute-force attempts to `resend()` a failed + transmission. Default is 0. This parameter has no affect on transmissions if `arc` is + ``0`` or ``ask_no_ack`` parameter is set to `True`. Each re-attempt still takes + advantage of `arc` & `ard` attributes. During multi-payload processing, this + parameter is meant to slow down CircuitPython devices just enough for the Raspberry + Pi to catch up (due to the Raspberry Pi's seemingly slower SPI speeds). See also + `resend()` as using this parameter carries the same implications documented there. + + .. tip:: It is highly recommended that `arc` attribute is enabled when sending + multiple payloads. Test results with the `arc` attribute disabled were very poor + (much < 50% received). This same advice applies to the ``ask_no_ack`` parameter (leave + it as `False` for multiple payloads). + .. warning:: The nRF24L01 will block usage of the TX FIFO buffer upon failed + transmissions. Failed transmission's payloads stay in TX FIFO buffer until the MCU + calls `flush_tx()` and `clear_status_flags()`. Therefore, this function will discard + failed transmissions' payloads when sending a list or tuple of payloads, so it can + continue to process through the list/tuple even if any payload fails to be + acknowledged. + Advanced API ------------ @@ -132,142 +342,611 @@ what_happened() .. automethod:: circuitpython_nrf24l01.rf24.RF24.what_happened + This debuggung function aggregates and outputs all status/condition related information + from the nRF24L01. Some information may be irrelevant depending on nRF24L01's + state/condition. + + :prints: + + - ``Channel`` The current setting of the `channel` attribute + - ``RF Data Rate`` The current setting of the RF `data_rate` attribute. + - ``RF Power Amplifier`` The current setting of the `pa_level` attribute. + - ``CRC bytes`` The current setting of the `crc` attribute + - ``Address length`` The current setting of the `address_length` attribute + - ``Payload lengths`` The current setting of the `payload_length` attribute + - ``Auto retry delay`` The current setting of the `ard` attribute + - ``Auto retry attempts`` The current setting of the `arc` attribute + - ``Packets lost on current channel`` Total amount of packets lost (transmission + failures). This only resets when the `channel` is changed. This count will + only go up 15. + - ``Retry attempts made for last transmission`` Amount of attempts to re-transmit + during last + transmission (resets per payload) + - ``IRQ - Data Ready`` The current setting of the IRQ pin on "Data Ready" event + - ``IRQ - Data Sent`` The current setting of the IRQ pin on "Data Sent" event + - ``IRQ - Data Fail`` The current setting of the IRQ pin on "Data Fail" event + - ``Data Ready`` Is there RX data ready to be read? + (state of the `irq_dr` flag) + - ``Data Sent`` Has the TX data been sent? (state of the `irq_ds` flag) + - ``Data Failed`` Has the maximum attempts to re-transmit been reached? + (state of the `irq_df` flag) + - ``TX FIFO full`` Is the TX FIFO buffer full? (state of the `tx_full` flag) + - ``TX FIFO empty`` Is the TX FIFO buffer empty? + - ``RX FIFO full`` Is the RX FIFO buffer full? + - ``RX FIFO empty`` Is the RX FIFO buffer empty? + - ``Custom ACK payload`` Is the nRF24L01 setup to use an extra (user defined) payload + attached to the acknowledgment packet? (state of the `ack` attribute) + - ``Ask no ACK`` Is the nRF24L01 setup to transmit individual packets that don't + require acknowledgment? + - ``Automatic Acknowledgment`` Is the `auto_ack` attribute enabled? + - ``Dynamic Payloads`` Is the `dynamic_payloads` attribute enabled? + - ``Primary Mode`` The current mode (RX or TX) of communication of the nRF24L01 device. + - ``Power Mode`` The power state can be Off, Standby-I, Standby-II, or On. + + :param bool dump_pipes: `True` appends the output and prints: + + * the current address used for TX transmissions + * ``Pipe [#] ([open/closed]) bound: [address]`` where ``#`` represent the pipe number, + the ``open/closed`` status is relative to the pipe's RX status, and ``address`` is + read directly from the nRF24L01 registers. + * if the pipe is open, then the output also prints ``expecting [X] byte static + payloads`` where ``X`` is the `payload_length` (in bytes) the pipe is setup to + receive when `dynamic_payloads` is disabled. + + Default is `False` and skips this extra information. + dynamic_payloads ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.dynamic_payloads + This `bool` attribute controls the nRF24L01's dynamic payload length feature. + + - `True` enables nRF24L01's dynamic payload length feature. The `payload_length` + attribute is ignored when this feature is enabled. + - `False` disables nRF24L01's dynamic payload length feature. Be sure to adjust + the `payload_length` attribute accordingly when `dynamic_payloads` feature is disabled. + payload_length ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.payload_length + This `int` attribute specifies the length (in bytes) of payload that is regarded, + meaning "how big of a payload should the radio care about?" If the `dynamic_payloads` + attribute is enabled, this attribute has no affect. When `dynamic_payloads` is disabled, + this attribute is used to specify the payload length when entering RX mode. + + A valid input value must be an `int` in range [1,32]. Otherwise a `ValueError` exception is + thrown. Default is set to the nRF24L01's maximum of 32. + + .. note:: When `dynamic_payloads` is disabled during transmissions: + + - Payloads' size of greater than this attribute's value will be truncated to match. + - Payloads' size of less than this attribute's value will be padded with zeros to + match. + auto_ack ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.auto_ack + This `bool` attribute controls the nRF24L01's automatic acknowledgment feature during + the process of receiving a packet. + + - `True` enables transmitting automatic acknowledgment packets. The CRC (cyclic redundancy + checking) is enabled automatically by the nRF24L01 if the `auto_ack` attribute is enabled + (see also `crc` attribute). + - `False` disables transmitting automatic acknowledgment packets. The `crc` attribute will + remain unaffected when disabling the `auto_ack` attribute. + arc ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.arc + This `int` attribute specifies the nRF24L01's number of attempts to re-transmit TX + payload when acknowledgment packet is not received. The `auto_ack` must be enabled on the + receiving nRF24L01, otherwise this attribute will make `send()` seem like it failed. + + A valid input value must be in range [0,15]. Otherwise a `ValueError` exception is thrown. + Default is set to 3. A value of ``0`` disables the automatic re-transmit feature and + considers all payload transmissions a success. + ard ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ard + This `int` attribute specifies the nRF24L01's delay (in µs) between attempts to + automatically re-transmit the TX payload when an expected acknowledgement (ACK) packet is + not received. During this time, the nRF24L01 is listening for the ACK packet. If the + `auto_ack` attribute is disabled, this attribute is not applied. + + A valid input value must be a multiple of 250 in range [250,4000]. Otherwise a `ValueError` + exception is thrown. Default is 1500 for reliability. + + .. note:: Paraphrased from nRF24L01 specifications sheet: + + Please take care when setting this parameter. If the custom ACK payload is more than 15 + bytes in 2 Mbps data rate, the `ard` must be 500µS or more. If the custom ACK payload + is more than 5 bytes in 1 Mbps data rate, the `ard` must be 500µS or more. In 250kbps + data rate (even when there is no custom ACK payload) the `ard` must be 500µS or more. + + See `data_rate` attribute on how to set the data rate of the nRF24L01's transmissions. + ack ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ack + This `bool` attribute represents the status of the nRF24L01's capability to use custom + payloads as part of the automatic acknowledgment (ACK) packet. Use this attribute to + set/check if the custom ACK payloads feature is enabled. + + - `True` enables the use of custom ACK payloads in the ACK packet when responding to + receiving transmissions. As `dynamic_payloads` and `auto_ack` attributes are required for + this feature to work, they are automatically enabled as needed. + - `False` disables the use of custom ACK payloads. Disabling this feature does not disable + the `auto_ack` and `dynamic_payloads` attributes (they work just fine without this + feature). + load_ack() ****************************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.load_ack + This allows the MCU to specify a payload to be allocated into the TX FIFO buffer for use + on a specific data pipe. This payload will then be appended to the automatic acknowledgment + (ACK) packet that is sent when fresh data is received on the specified pipe. See + `read_ack()` on how to fetch a received custom ACK payloads. + + :param bytearray buf: This will be the data attached to an automatic ACK packet on the + incoming transmission about the specified ``pipe_number`` parameter. This must have a + length in range [1,32] bytes, otherwise a `ValueError` exception is thrown. Any ACK + payloads will remain in the TX FIFO buffer until transmitted successfully or + `flush_tx()` is called. + :param int pipe_number: This will be the pipe number to use for deciding which + transmissions get a response with the specified ``buf`` parameter's data. This number + must be in range [0,5], otherwise a `ValueError` exception is thrown. + + :returns: `True` if payload was successfully loaded onto the TX FIFO buffer. `False` if it + wasn't because TX FIFO buffer is full. + + .. note:: this function takes advantage of a special feature on the nRF24L01 and needs to + be called for every time a customized ACK payload is to be used (not for every + automatic ACK packet -- this just appends a payload to the ACK packet). The `ack`, + `auto_ack`, and `dynamic_payloads` attributes are also automatically enabled by this + function when necessary. + + .. tip:: The ACK payload must be set prior to receiving a transmission. It is also worth + noting that the nRF24L01 can hold up to 3 ACK payloads pending transmission. Using this + function does not over-write existing ACK payloads pending; it only adds to the queue + (TX FIFO buffer) if it can. Use `flush_tx()` to discard unused ACK payloads when done + listening. + read_ack() ****************************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.read_ack + Allows user to read the automatic acknowledgement (ACK) payload (if any) when nRF24L01 + is in TX mode. This function is called from a blocking `send()` call if the `ack` attribute + is enabled. Alternatively, this function can be called directly in case of calling the + non-blocking `write()` function during asychronous applications. This function is an alias + of `recv()` and remains for bakward compatibility with older versions of this library. + + .. note:: See also the `ack`, `dynamic_payloads`, and `auto_ack` attributes as they must be + enabled to use custom ACK payloads. + irq_dr ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_dr + A `bool` that represents the "Data Ready" interrupted flag. (read-only) + + * `True` represents Data is in the RX FIFO buffer + * `False` represents anything depending on context (state/condition of FIFO buffers) -- + usually this means the flag's been reset. + + Pass ``dataReady`` parameter as `True` to `clear_status_flags()` and reset this. As this is + a virtual representation of the interrupt event, this attribute will always be updated + despite what the actual IRQ pin is configured to do about this event. + + Calling this does not execute an SPI transaction. It only exposes that latest data + contained in the STATUS byte that's always returned from any other SPI transactions. Use + the `update()` function to manually refresh this data when needed. + irq_df ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_df + A `bool` that represents the "Data Failed" interrupted flag. (read-only) + + * `True` signifies the nRF24L01 attemped all configured retries + * `False` represents anything depending on context (state/condition) -- usually this means + the flag's been reset. + + Pass ``dataFail`` parameter as `True` to `clear_status_flags()` to reset this. As this is a + virtual representation of the interrupt event, this attribute will always be updated + despite what the actual IRQ pin is configured to do about this event.see also the `arc` and + `ard` attributes. + + Calling this does not execute an SPI transaction. It only exposes that latest data + contained in the STATUS byte that's always returned from any other SPI transactions. Use + the `update()` function to manually refresh this data when needed. + irq_ds ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_ds + A `bool` that represents the "Data Sent" interrupted flag. (read-only) + + * `True` represents a successful transmission + * `False` represents anything depending on context (state/condition of FIFO buffers) -- + usually this means the flag's been reset. + + Pass ``dataSent`` parameter as `True` to `clear_status_flags()` to reset this. As this is a + virtual representation of the interrupt event, this attribute will always be updated + despite what the actual IRQ pin is configured to do about this event. + + Calling this does not execute an SPI transaction. It only exposes that latest data + contained in the STATUS byte that's always returned from any other SPI transactions. Use + the `update()` function to manually refresh this data when needed. + clear_status_flags() ****************************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.clear_status_flags + This clears the interrupt flags in the status register. Internally, this is + automatically called by `send()`, `write()`, `recv()`, and when `listen` changes from + `False` to `True`. + + :param bool data_recv: specifies wheather to clear the "RX Data Ready" flag. + :param bool data_sent: specifies wheather to clear the "TX Data Sent" flag. + :param bool data_fail: specifies wheather to clear the "Max Re-transmit reached" flag. + + .. note:: Clearing the ``data_fail`` flag is necessary for continued transmissions from the + nRF24L01 (locks the TX FIFO buffer when `irq_df` is `True`) despite wheather or not the + MCU is taking advantage of the interrupt (IRQ) pin. Call this function only when there + is an antiquated status flag (after you've dealt with the specific payload related to + the staus flags that were set), otherwise it can cause payloads to be ignored and + occupy the RX/TX FIFO buffers. See `Appendix A of the nRF24L01+ Specifications Sheet + `_ for an outline of + proper behavior. + interrupt_config() ****************************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.interrupt_config + Sets the configuration of the nRF24L01's IRQ (interrupt) pin. The signal from the + nRF24L01's IRQ pin is active LOW. (write-only) + + :param bool data_recv: If this is `True`, then IRQ pin goes active when there is new data + to read in the RX FIFO buffer. + :param bool data_sent: If this is `True`, then IRQ pin goes active when a payload from TX + buffer is successfully transmit. + :param bool data_fail: If this is `True`, then IRQ pin goes active when maximum number of + attempts to re-transmit the packet have been reached. If `auto_ack` attribute is + disabled, then this IRQ event is not used. + + .. note:: To fetch the status (not configuration) of these IRQ flags, use the `irq_df`, + `irq_ds`, `irq_dr` attributes respectively. + + .. tip:: Paraphrased from nRF24L01+ Specification Sheet: + + The procedure for handling ``data_recv`` IRQ should be: + + 1. read payload through `recv()` + 2. clear ``dataReady`` status flag (taken care of by using `recv()` in previous step) + 3. read FIFO_STATUS register to check if there are more payloads available in RX FIFO + buffer. (a call to `pipe()`, `any()` or even ``(False,True)`` as parameters to + `fifo()` will get this result) + 4. if there is more data in RX FIFO, repeat from step 1 + data_rate ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.data_rate + This `int` attribute specifies the nRF24L01's frequency data rate for OTA (over the air) + transmissions. + + A valid input value is: + + - ``1`` sets the frequency data rate to 1 Mbps + - ``2`` sets the frequency data rate to 2 Mbps + - ``250`` sets the frequency data rate to 250 Kbps + + Any invalid input throws a `ValueError` exception. Default is 1 Mbps. + + .. warning:: 250 Kbps is be buggy on the non-plus models of the nRF24L01 product line. If + you use 250 Kbps data rate, and some transmissions report failed by the transmitting + nRF24L01, even though the same packet in question actually reports received by the + receiving nRF24L01, then try a higher data rate. CAUTION: Higher data rates mean less + maximum distance between nRF24L01 transceivers (and vise versa). + channel ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.channel + This `int` attribute specifies the nRF24L01's frequency (in 2400 + `channel` MHz). + + A valid input value must be in range [0, 125] (that means [2.4, 2.525] GHz). Otherwise a + `ValueError` exception is thrown. Default is 76. + crc ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.crc + This `int` attribute specifies the nRF24L01's CRC (cyclic redundancy checking) encoding + scheme in terms of byte length. CRC is a way of making sure that the transmission didn't + get corrupted over the air. + + A valid input value is in range [0,2]: + + - ``0`` disables CRC (no anti-corruption of data) + - ``1`` enables CRC encoding scheme using 1 byte (weak anti-corruption of data) + - ``2`` enables CRC encoding scheme using 2 bytes (better anti-corruption of data) + + Any invalid input throws a `ValueError` exception. Default is enabled using 2 bytes. + + .. note:: The nRF24L01 automatically enables CRC if automatic acknowledgment feature is + enabled (see `auto_ack` attribute). + power ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.power + This `bool` attribute controls the power state of the nRF24L01. This is exposed for + asynchronous applications and user preference. + + - `False` basically puts the nRF24L01 to sleep (AKA power down mode) with ultra-low + current consumption. No transmissions are executed when sleeping, but the nRF24L01 can + still be accessed through SPI. Upon instantiation, this driver class puts the nRF24L01 + to sleep until the MCU invokes RX/TX transmissions. This driver class doesn't power down + the nRF24L01 after RX/TX transmissions are complete (avoiding the required power up/down + 130 µs wait time), that preference is left to the user. + - `True` powers up the nRF24L01. This is the first step towards entering RX/TX modes (see + also `listen` attribute). Powering up is automatically handled by the `listen` attribute + as well as the `send()` and `write()` functions. + + .. note:: This attribute needs to be `True` if you want to put radio on Standby-II (highest + current consumption) or Standby-I (moderate current consumption) modes. TX + transmissions are only executed during Standby-II by calling `send()` or `write()`. RX + transmissions are received during Standby-II by setting `listen` attribute to `True` + (see `Chapter 6.1.2-7 of the nRF24L01+ Specifications Sheet `_). After using `send()` or setting `listen` to `False`, the nRF24L01 + is left in Standby-I mode (see also notes on the `write()` function). + pa_level ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pa_level + This `int` attribute specifies the nRF24L01's power amplifier level (in dBm). Higher + levels mean the transmission will cover a longer distance. Use this attribute to tweak the + nRF24L01 current consumption on projects that don't span large areas. + + A valid input value is: + + - ``-18`` sets the nRF24L01's power amplifier to -18 dBm (lowest) + - ``-12`` sets the nRF24L01's power amplifier to -12 dBm + - ``-6`` sets the nRF24L01's power amplifier to -6 dBm + - ``0`` sets the nRF24L01's power amplifier to 0 dBm (highest) + + Any invalid input throws a `ValueError` exception. Default is 0 dBm. + tx_full ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.tx_full + An attribute to represent the nRF24L01's status flag signaling that the TX FIFO buffer + is full. (read-only) + + Calling this does not execute an SPI transaction. It only exposes that latest data + contained in the STATUS byte that's always returned from any SPI transactions with the + nRF24L01. Use the `update()` function to manually refresh this data when needed. + + :returns: + * `True` for TX FIFO buffer is full + * `False` for TX FIFO buffer is not full. This doesn't mean the TX FIFO buffer is + empty. + rpd ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.rpd + This read-only attribute returns `True` if RPD (Received Power Detector) is triggered + or `False` if not triggered. + + .. note:: The RPD flag is triggered in the following cases: + + 1. During RX mode (`listen` = `True`) and a RF transmission with a gain above a preset + (non-adjustable) -64 dBm threshold. + 2. When a packet is received (indicative of the nRF24L01 used to detect/"listen" for + incoming packets). + 3. When the nRF24L01's CE pin goes from HIGH to LOW (or when the `listen` attribute + changes from `True` to `False`). + 4. When the underlying ESB (Enhanced ShockBurst) protocol reaches a hardcoded + (non-adjustable) RX timeout. + update() ****************************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.update + This function is only used to get an updated status byte over SPI from the nRF24L01 and + is exposed to the MCU for asynchronous applications. Refreshing the status byte is vital to + checking status of the interrupts, RX pipe number related to current RX payload, and if the + TX FIFO buffer is full. This function returns nothing, but internally updates the `irq_dr`, + `irq_ds`, `irq_df`, and `tx_full` attributes. Internally this is a helper function to + `pipe()`, `send()`, and `resend()` functions + resend() ****************************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.resend + Use this function to maunally re-send the previous payload in the + top level (first out) of the TX FIFO buffer. All returned data follows the same patttern + that `send()` returns with the added condition that this function will return `False` + if the TX FIFO buffer is empty. + + .. note:: The nRF24L01 normally removes a payload from the TX FIFO buffer after successful + transmission, but not when this function is called. The payload (successfully + transmitted or not) will remain in the TX FIFO buffer until `flush_tx()` is called to + remove them. Alternatively, using this function also allows the failed payload to be + over-written by using `send()` or `write()` to load a new payload. + write() ****************************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.write + This non-blocking function (when used as alternative to `send()`) is meant for + asynchronous applications and can only handle one payload at a time as it is a helper + function to `send()`. + + :param bytearray buf: The payload to transmit. This bytearray must have a length greater + than 0 and less than 32 bytes, otherwise a `ValueError` exception is thrown. + + - If the `dynamic_payloads` attribute is disabled and this bytearray's length is less + than the `payload_length` attribute, then this bytearray is padded with zeros until + its length is equal to the `payload_length` attribute. + - If the `dynamic_payloads` attribute is disabled and this bytearray's length is + greater than `payload_length` attribute, then this bytearray's length is truncated to + equal the `payload_length` attribute. + :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for + an acknowledgment from the receiving nRF24L01. This parameter directly controls a + ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about + the payload). Therefore, it takes advantage of an nRF24L01 feature specific to + individual payloads, and its value is not saved anywhere. You do not need to specify + this for every payload if the `auto_ack` attribute is disabled, however this parameter + should work despite the `auto_ack` attribute's setting. + + .. note:: Each transmission is in the form of a packet. This packet contains sections + of data around and including the payload. `See Chapter 7.3 in the nRF24L01 + Specifications Sheet `_ for more + details. + + This function isn't completely non-blocking as we still need to wait just under 5 ms for + the CSN pin to settle (allowing a clean SPI transaction). + + .. note:: The nRF24L01 doesn't initiate sending until a mandatory minimum 10 µs pulse on + the CE pin is acheived. That pulse is initiated before this function exits. However, we + have left that 10 µs wait time to be managed by the MCU in cases of asychronous + application, or it is managed by using `send()` instead of this function. According to + the Specification sheet, if the CE pin remains HIGH for longer than 10 µs, then the + nRF24L01 will continue to transmit all payloads found in the TX FIFO buffer. + + .. warning:: A note paraphrased from the `nRF24L01+ Specifications Sheet `_: + + It is important to NEVER to keep the nRF24L01+ in TX mode for more than 4 ms at a time. + If the [`arc` attribute is] enabled, nRF24L01+ is never in TX mode longer than 4 + ms. + + .. tip:: Use this function at your own risk. Because of the underlying `"Enhanced + ShockBurst Protocol" `_, disobeying the 4 + ms rule is easily avoided if you enable the `auto_ack` attribute. Alternatively, you + MUST use interrupt flags or IRQ pin with user defined timer(s) to AVOID breaking the + 4 ms rule. If the `nRF24L01+ Specifications Sheet explicitly states this + `_, we have to assume + radio damage or misbehavior as a result of disobeying the 4 ms rule. See also `table 18 + in the nRF24L01 specification sheet `_ for calculating + necessary transmission time (these calculations are used in the `send()` and `resend()` + functions). + flush_rx() ****************************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_rx + A helper function to flush the nRF24L01's internal RX FIFO buffer. (write-only) + + .. note:: The nRF24L01 RX FIFO is 3 level stack that holds payload data. This means that + there can be up to 3 received payloads (each of a maximum length equal to 32 bytes) + waiting to be read (and popped from the stack) by `recv()` or `read_ack()`. This + function clears all 3 levels. + flush_tx() ****************************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_tx + A helper function to flush the nRF24L01's internal TX FIFO buffer. (write-only) + + .. note:: The nRF24L01 TX FIFO is 3 level stack that holds payload data. This means that + there can be up to 3 payloads (each of a maximum length equal to 32 bytes) waiting to + be transmit by `send()`, `resend()` or `write()`. This function clears all 3 levels. It + is worth noting that the payload data is only popped from the TX FIFO stack upon + successful transmission (see also `resend()` as the handling of failed transmissions + can be altered). + fifo() ****************************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.fifo + This provides some precision determining the status of the TX/RX FIFO buffers. + (read-only) + + :param bool about_tx: + * `True` means information returned is about the TX FIFO buffer. + * `False` means information returned is about the RX FIFO buffer. This parameter + defaults to `False` when not specified. + :param bool check_empty: + * `True` tests if the specified FIFO buffer is empty. + * `False` tests if the specified FIFO buffer is full. + * `None` (when not specified) returns a 2 bit number representing both empty (bit 1) & + full (bit 0) tests related to the FIFO buffer specified using the ``tx`` parameter. + :returns: + * A `bool` answer to the question: + "Is the [TX/RX]:[`True`/`False`] FIFO buffer [empty/full]:[`True`/`False`]? + * If the ``check_empty`` parameter is not specified: an `int` in range [0,2] for which: + + - ``1`` means the specified FIFO buffer is full + - ``2`` means the specified FIFO buffer is empty + - ``0`` means the specified FIFO buffer is neither full nor empty + pipe() ****************************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.pipe + This function returns information about the data pipe that received the next available + payload in the RX FIFO buffer. + + :returns: + - `None` if there is no payload in RX FIFO. + - The `int` identifying pipe number [0,5] that received the next available payload in + the RX FIFO buffer. + address() ****************************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.address + + Returns the current address set to a specified data pipe or the TX address. (read-only) + + :param int index: the number of the data pipe whose address is to be returned. Defaults to + ``-1``. A valid index ranges [0,5] for RX addresses or any negative `int` for the TX + address. Otherwise an `IndexError` is thown. From 725bd3ec387ae86aeaa2bdba9c774cadeae7d40c Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 21 Mar 2020 16:08:33 -0700 Subject: [PATCH 009/127] migrated breif docstring back to rf24.py & removed all option args from __init__() --- circuitpython_nrf24l01/rf24.py | 215 +++++++++++++++++---------------- docs/api.rst | 176 ++++++--------------------- 2 files changed, 148 insertions(+), 243 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 472fde2..f37ca65 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -46,27 +46,13 @@ TX_ADDR = 0x10 #: Address that is used for TX transmissions # pylint: enable=bad-whitespace -# documentation lives in api.rst to save space on M0 -# pylint: disable=missing-class-docstring,missing-function-docstring +# NOTE expanded documentation lives in api.rst to save space on M0 + class RF24: - def __init__(self, spi, csn, ce, - channel=76, - payload_length=32, - address_length=5, - ard=1500, - arc=3, - crc=2, - data_rate=1, - pa_level=0, - dynamic_payloads=True, - auto_ack=True, - ask_no_ack=True, - ack=False, - irq_dr=True, - irq_ds=True, - irq_df=True): - self._payload_length = payload_length # inits internal attribute - self.payload_length = payload_length + """A driver class for the nRF24L01(+) transceiver radios.""" + def __init__(self, spi, csn, ce): + self._payload_length = 32 # inits internal attribute + self.payload_length = 32 # last address assigned to pipe0 for reading. init to None self._fifo = 0 self._status = 0 @@ -85,20 +71,13 @@ def __init__(self, spi, csn, ce, # reset ce.value & disable the chip comms self.ce_pin.switch_to_output(value=False) # if radio is powered up and CE is LOW: standby-I mode - # if radio is powered up and CE is HIGH: standby-II mode - + # if radio is powered up and CE is HIGH: standby-II mode (dangerous if longer than 4ms) # NOTE per spec sheet: nRF24L01+ must be in a standby or power down mode before writing # to the configuration register - # configure the CONFIG register:IRQ(s) config, setup CRC feature, and trigger standby-I & - # TX mode (the "| 2") - if 0 <= crc <= 2: - self._config = ((not irq_dr) << 6) | ((not irq_ds) << 5) | ((not irq_df) << 4) | \ - ((crc + 1) << 2 if crc else 0) | 2 - self._reg_write(CONFIG, self._config) # dump to register - else: - raise ValueError( - "CRC byte length must be an int equal to 0 (off), 1, or 2") - + # configure the CONFIG register: + # 0x0E = IRQs are all enabled, CRC is enabled with 2 bytes, and power up in TX mode + self._config = 0x0E + self._reg_write(CONFIG, self._config) # dump to register # check for device presence by verifying nRF24L01 is in TX + standby-I mode if self._reg_read(CONFIG) & 3 == 2: # if in TX + standby-I mode self.power = False # power down @@ -115,35 +94,19 @@ def __init__(self, spi, csn, ce, # shadow copy of the TX_ADDR self._tx_address = self._reg_read_bytes(TX_ADDR) - # configure the SETUP_RETR register - if 250 <= ard <= 4000 and ard % 250 == 0 and 0 <= arc <= 15: - self._setup_retr = (int((ard - 250) / 250) << 4) | arc - else: - raise ValueError("automatic re-transmit delay can only be a multiple of 250 in range " - "[250,4000]\nautomatic re-transmit count(/attempts) must range " - "[0,15]") - + self._setup_retr = 0x53 #ard = 1500; arc = 3 # configure the RF_SETUP register - if data_rate in (1, 2, 250) and pa_level in (-18, -12, -6, 0): - data_rate = 0 if data_rate == 1 else ( - 8 if data_rate == 2 else 0x20) - pa_level = (3 - int(pa_level / -6)) * 2 - self._rf_setup = data_rate | pa_level - else: - raise ValueError("data rate must be one of the following ([M,M,K]bps): 1, 2, 250" - "\npower amplifier must be one of the following (dBm): -18, -12," - " -6, 0") - - # manage dynamic_payloads, auto_ack, and ack features - self._dyn_pl = 0x3F if dynamic_payloads else 0 # 0x3F == enabled on all pipes - self._aa = 0x3F if auto_ack else 0 # 0x3F == enabled on all pipes - self._features = (dynamic_payloads << 2) | ((ack if auto_ack and dynamic_payloads - else False) << 1) | ask_no_ack - + self._rf_setup = 0x06 # 1 Mbps data_rate, and 0 dbm pa_level + # configure dynamic_payloads & auto_ack for RX operations + self._dyn_pl = 0x3F # 0x3F = dynamic_payloads enabled on all pipes + self._aa = 0x3F # 0x3F = auto_ack enabled on all pipes + # configure features for TX operations + # 5 = enable dynamic_payloads, disable custom ack payloads, and allow ask_no_ack command + self._features = 5 # init the last few singleton attribute - self._channel = channel - self._addr_len = address_length + self._channel = 76 + self._addr_len = 5 with self: # write to registers & power up # using __enter__() configures all virtual features and settings to the hardware @@ -225,6 +188,8 @@ def _reg_write(self, reg, value=None): @property def address_length(self): + """This `int` attribute specifies the length (in bytes) of addresses + to be used for RX/TX pipes.""" return self._reg_read(SETUP_AW) + 2 @address_length.setter @@ -240,6 +205,8 @@ def address_length(self, length): "address length can only be set in range [3,5] bytes") def open_tx_pipe(self, address): + """This function is used to open a data pipe for OTA (over the air) TX + transmissions.""" if len(address) == self.address_length: # if auto_ack == True, then use this TX address as the RX address for ACK if self.auto_ack: @@ -259,6 +226,8 @@ def open_tx_pipe(self, address): " {})".format(self.address_length)) def close_rx_pipe(self, pipe_number, reset=True): + """This function is used to close a specific data pipe from OTA (over + the air) RX transmissions.""" if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0,5]") self._open_pipes = self._reg_read(EN_RX) # refresh data @@ -279,6 +248,8 @@ def close_rx_pipe(self, pipe_number, reset=True): self._reg_write(EN_RX, self._open_pipes) def open_rx_pipe(self, pipe_number, address): + """This function is used to open a specific data pipe for OTA (over + the air) RX transmissions.""" if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0,5]") if len(address) != self.address_length: @@ -314,6 +285,7 @@ def open_rx_pipe(self, pipe_number, address): @property def listen(self): + """An attribute to represent the nRF24L01 primary role as a radio.""" return self.power and bool(self._config & 1) @listen.setter @@ -358,10 +330,13 @@ def _stop_listening(self): # exits while still in Standby-I (low current & no transmissions) def any(self): + """This function checks if the nRF24L01 has received any data at all, + and then reports the next available payload's length (in bytes)""" # 0x60 == R_RX_PL_WID command return self._reg_read(0x60) # top-level payload length def recv(self): + """This function is used to retrieve the next available payload""" if not self.irq_dr: return None # buffer size = current payload size @@ -374,10 +349,27 @@ def recv(self): return result def send(self, buf, ask_no_ack=False, force_retry=0): + """This blocking function is used to transmit payload(s).""" # ensure power down/standby-I for proper manipulation of PWR_UP & PRIM_RX bits in # CONFIG register self.ce_pin.value = 0 self.flush_tx() # be sure there is space in the TX FIFO + if isinstance(buf, (list, tuple)): # writing a set of payloads + result = [] + for i, b in enumerate(buf): # check invalid payloads first + # this way when we raise a ValueError exception we don't leave the nRF24L01 in an + # unknown frozen state. + if not b or len(b) > 32: + raise ValueError("buf (item {} in the list/tuple) must be a" + " buffer protocol object with a byte length of\nat least 1 " + "and no greater than 32".format(i)) + for i, b in enumerate(buf): + # use recursion for each payload + result.append(self.send(b, ask_no_ack, force_retry)) + return result + if not buf or len(buf) > 32: + raise ValueError("buf must be a buffer protocol object with a byte length of" + "\nat least 1 and no greater than 32") # using spec sheet calculations: # timeout total = T_upload + 2 * stby2active + T_overAir + T_ack + T_irq + T_retry # T_upload = payload length (in bits) / spi data rate (bits per second = @@ -401,37 +393,6 @@ def send(self, buf, ask_no_ack=False, force_retry=0): t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 t_retry = (((self._setup_retr & 0xf0) >> 4) * 250 + 380) * \ (self._setup_retr & 0x0f) / 1000000 - if isinstance(buf, (list, tuple)): # writing a set of payloads - result = [] - for i, b in enumerate(buf): # check invalid payloads first - # this way when we raise a ValueError exception we don't leave the nRF24L01 in an - # unknown frozen state. - if not b or len(b) > 32: - raise ValueError("buf (item {} in the list/tuple) must be a" - " buffer protocol object with a byte length of\nat least 1 " - "and no greater than 32".format(i)) - for i, b in enumerate(buf): - timeout = (((8 * (len(b) + packet_data)) + 9) / bitrate) + \ - stby2active + t_irq + t_retry + t_ack + \ - (len(b) * 64 / self._spi.baudrate) # t_upload - self.clear_status_flags(False) # clear TX related flags - self.write(b, ask_no_ack) # clears TX flags on entering - time.sleep(0.00001) - self.ce_pin.value = 0 - self._wait_for_result(timeout) # now get result - if self._setup_retr & 0x0f and self.irq_df: - # need to clear for continuing transmissions - result.append(self._attempt2resend(force_retry)) - else: # if auto_ack is disabled - if self.ack and self.irq_dr and not ask_no_ack: - result.append(self.recv()) # save ACK payload & clears RX flag - else: - result.append(self.irq_ds) # will always be True (in this case) - return result - if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with a byte length of" - "\nat least 1 and no greater than 32") - # T_upload is done before timeout begins (after payload write action AKA upload) timeout = (((8 * (len(buf) + packet_data)) + 9) / bitrate) + stby2active + t_irq + t_retry + t_ack self.write(buf, ask_no_ack) # init using non-blocking helper @@ -443,33 +404,45 @@ def send(self, buf, ask_no_ack=False, force_retry=0): self._wait_for_result(timeout) if self._setup_retr & 0x0f and self.irq_df: # if auto-retransmit is on and last attempt failed - result = self._attempt2resend(force_retry) - else: # if auto_ack is disabled + retry = False + for _ in range(force_retry): + # resend() clears flags upon entering and exiting + retry = self.resend() + if retry is None or retry: + break # retry succeeded + result = retry + else: # if succeeded if self.ack and self.irq_dr and not ask_no_ack: + # if ACK payload is waiting in RX FIFO result = self.recv() # save ACK payload & clears RX flag - else: + else: # if auto-ack is disabled result = self.irq_ds # will always be True (in this case) self.clear_status_flags(False) # only TX related IRQ flags return result @property def irq_dr(self): + """A `bool` that represents the "Data Ready" interrupted flag.""" return bool(self._status & 0x40) @property def irq_ds(self): + """A `bool` that represents the "Data Sent" interrupted flag.""" return bool(self._status & 0x20) @property def irq_df(self): + """A `bool` that represents the "Data Failed" interrupted flag.""" return bool(self._status & 0x10) def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): + """This clears the interrupt flags in the status register.""" # 0x07 = STATUS register; only bits 6 through 4 are write-able self._reg_write(0x07, (data_recv << 6) | ( data_sent << 5) | (data_fail << 4)) def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): + """Sets the configuration of the nRF24L01's IRQ (interrupt) pin.""" self._config = self._reg_read(CONFIG) # refresh data # save to register and update local copy of pwr & RX/TX modes' flags self._config = (self._config & 0x0F) | (not data_fail << 4) | (not data_sent << 5) | \ @@ -477,6 +450,8 @@ def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): self._reg_write(CONFIG, self._config) def what_happened(self, dump_pipes=False): + """This debuggung function aggregates and outputs all status/condition + related information""" watchdog = self._reg_read(8) # 8 == OBSERVE_TX register print("Channel___________________{} ~ {} GHz".format( self.channel, (self.channel + 2400) / 1000)) @@ -524,6 +499,8 @@ def what_happened(self, dump_pipes=False): @property def dynamic_payloads(self): + """This `bool` attribute controls the nRF24L01's dynamic payload + length feature.""" return bool(self._dyn_pl and (self._features & 4)) @dynamic_payloads.setter @@ -541,6 +518,7 @@ def dynamic_payloads(self, enable): @property def payload_length(self): + """This `int` attribute specifies the length (in bytes) of payload""" return self._payload_length @payload_length.setter @@ -555,6 +533,8 @@ def payload_length(self, length): @property def arc(self): + """This `int` attribute specifies the nRF24L01's number of attempts + to re-transmit TX payload""" self._setup_retr = self._reg_read(SETUP_RETR) # refresh data return self._setup_retr & 0x0f @@ -571,6 +551,9 @@ def arc(self, count): @property def ard(self): + """This `int` attribute specifies the nRF24L01's delay (in + microseconds) between attempts to automatically re-transmit the + TX payload""" self._setup_retr = self._reg_read(SETUP_RETR) # refresh data return ((self._setup_retr & 0xf0) >> 4) * 250 + 250 @@ -589,6 +572,8 @@ def ard(self, delta_t): @property def auto_ack(self): + """This `bool` attribute controls the nRF24L01's automatic + acknowledgment feature during the process of receiving""" return self._aa @auto_ack.setter @@ -601,6 +586,9 @@ def auto_ack(self, enable): @property def ack(self): + """This `bool` attribute represents the status of the nRF24L01's + capability to use custom payloads as part of the automatic + acknowledgment (ACK) packet.""" return bool((self._features & 2) and self.auto_ack and self.dynamic_payloads) @ack.setter @@ -620,6 +608,8 @@ def ack(self, enable): self._reg_write(FEATURE, self._features) def load_ack(self, buf, pipe_number): + """This allows the MCU to specify a payload to be allocated into the + TX FIFO buffer for use on a specific data pipe.""" if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0,5]") if not buf or len(buf) > 32: @@ -635,10 +625,14 @@ def load_ack(self, buf, pipe_number): return False # payload wasn't loaded def read_ack(self): + """Allows user to read the automatic acknowledgement (ACK) payload (if + any) when nRF24L01 is in TX mode.""" return self.recv() @property def data_rate(self): + """This `int` attribute specifies the nRF24L01's frequency data rate + for OTA (over the air) transmissions.""" self._rf_setup = self._reg_read(RF_SETUP) # refresh data return (2 if self._rf_setup & 0x28 == 8 else 250) if self._rf_setup & 0x28 else 1 @@ -658,6 +652,7 @@ def data_rate(self, speed): @property def channel(self): + """This `int` attribute specifies the nRF24L01's frequency.""" return self._reg_read(RF_CH) @channel.setter @@ -670,6 +665,8 @@ def channel(self, channel): @property def crc(self): + """This `int` attribute specifies the nRF24L01's CRC (cyclic redundancy + checking) encoding scheme in terms of byte length.""" self._config = self._reg_read(CONFIG) # refresh data return max(0, ((self._config & 12) >> 2) - 1) # this works @@ -687,6 +684,7 @@ def crc(self, length): @property def power(self): + """This `bool` attribute controls the power state of the nRF24L01.""" return bool(self._config & 2) @power.setter @@ -704,6 +702,8 @@ def power(self, is_on): @property def pa_level(self): + """This `int` attribute specifies the nRF24L01's power amplifier level + (in dBm).""" self._rf_setup = self._reg_read(RF_SETUP) # refresh data return (3 - ((self._rf_setup & RF_SETUP) >> 1)) * -6 @@ -722,18 +722,25 @@ def pa_level(self, power): @property def rpd(self): + """This read-only attribute returns `True` if RPD (Received Power + Detector) is triggered or `False` if not triggered.""" return bool(self._reg_read(0x09)) @property def tx_full(self): + """An attribute to represent the nRF24L01's status flag signaling that + the TX FIFO buffer is full. (read-only)""" return bool(self._status & 1) def update(self): - # perform non-operation to get status byte - # should be faster than reading the STATUS register + """This function is only used to get an updated status byte over SPI + from the nRF24L01""" + # perform non-operation to get status byte (faster than reading the STATUS register) self._reg_write(0xFF) def resend(self): + """Use this function to maunally re-send the previous payload in the + top level (first out) of the TX FIFO buffer.""" result = False if not self.fifo(True, True): # is there a pre-existing payload self.clear_status_flags(False) # clear TX related flags @@ -766,6 +773,9 @@ def resend(self): return result def write(self, buf, ask_no_ack=False): + """This non-blocking function (when used as alternative to `send()`) is meant for + asynchronous applications and can only handle one payload at a time as it is a + helper function to `send()`.""" if not buf or len(buf) > 32: raise ValueError("buf must be a buffer protocol object with a byte length of" "\nat least 1 and no greater than 32") @@ -798,12 +808,16 @@ def write(self, buf, ask_no_ack=False): # automatically goes to standby-II after successful TX of all payloads in the FIFO def flush_rx(self): + """A helper function to flush the nRF24L01's internal RX FIFO buffer. (write-only)""" self._reg_write(0xE2) def flush_tx(self): + """A helper function to flush the nRF24L01's internal TX FIFO buffer. (write-only)""" self._reg_write(0xE1) def fifo(self, about_tx=False, check_empty=None): + """This provides some precision determining the status of the TX/RX FIFO buffers. + (read-only)""" if (check_empty is None and isinstance(about_tx, (bool, int))) or \ (isinstance(check_empty, (bool, int)) and isinstance(about_tx, (bool, int))): self._fifo = self._reg_read(FIFO) # refresh the data @@ -814,6 +828,8 @@ def fifo(self, about_tx=False, check_empty=None): " ('check_empty'), if specified, must be a bool or int") def pipe(self): + """This function returns information about the data pipe that received the next + available payload in the RX FIFO buffer.""" self.update() # perform Non-operation command to get status byte (should be faster) result = (self._status & 0x0E) >> 1 # 0x0E==RX_P_NO if result <= 5: # is there data in RX FIFO? @@ -821,6 +837,8 @@ def pipe(self): return None # RX FIFO is empty def address(self, index=-1): + """Returns the current address set to a specified data pipe or the TX address. + (read-only)""" if index > 5: raise IndexError("index {} is out of bounds [0,5]".format(index)) if index < 0: @@ -834,12 +852,3 @@ def _wait_for_result(self, timeout): while not self.irq_ds and not self.irq_df and (time.monotonic() - start) < timeout: self.update() # perform Non-operation command to get status byte (should be faster) # print('status: DR={} DS={} DF={}'.format(self.irq_dr, self.irq_ds, self.irq_df)) - - def _attempt2resend(self, attempts): - retry = False - for _ in range(attempts): - # resend() clears flags upon entering and exiting - retry = self.resend() - if retry is None or retry: - break # retry succeeded - return retry diff --git a/docs/api.rst b/docs/api.rst index d3e4186..c63ccbc 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -84,7 +84,7 @@ Contructor .. autoclass:: circuitpython_nrf24l01.rf24.RF24 :no-members: - A driver class for the nRF24L01(+) transceiver radios. This class aims to be compatible with + This class aims to be compatible with other devices in the nRF24xxx product line that implement the Nordic proprietary Enhanced ShockBurst Protocol (and/or the legacy ShockBurst Protocol), but officially only supports (through testing) the nRF24L01 and nRF24L01+ devices. @@ -99,62 +99,13 @@ Contructor CSN (Chip Select Not) pin. This is required. :param ~digitalio.DigitalInOut ce: The digital output pin that is connected to the nRF24L01's CE (Chip Enable) pin. This is required. - :param int channel: This is used to specify a certain radio frequency that the nRF24L01 uses. - Defaults to 76 and can be changed at any time by using the `channel` attribute. - :param int payload_length: This is the length (in bytes) of a single payload to be transmitted - or received. This is ignored if the `dynamic_payloads` attribute is enabled. Defaults to 32 - and must be in range [1,32]. This can be changed at any time by using the `payload_length` - attribute. - :param int address_length: This is the length (in bytes) of the addresses that are assigned to - the data pipes for transmitting/receiving. Defaults to 5 and must be in range [3,5]. This - can be changed at any time by using the `address_length` attribute. - :param int ard: This specifies the delay time (in µs) between attempts to automatically - re-transmit. This can be changed at any time by using the `ard` attribute. This parameter - must be a multiple of 250 in the range [250,4000]. Defualts to 1500 µs. - :param int arc: This specifies the automatic re-transmit count (maximum number of automatically - attempts to re-transmit). This can be changed at any time by using the `arc` attribute. - This parameter must be in the range [0,15]. Defaults to 3. - :param int crc: This parameter controls the CRC setting of transmitted packets. Options are - ``0`` (off), ``1`` or ``2`` (byte long CRC enabled). This can be changed at any time by - using the `crc` attribute. Defaults to 2. - :param int data_rate: This parameter controls the RF data rate setting of transmissions. - Options are ``1`` (Mbps), ``2`` (Mbps), or ``250`` (Kbps). This can be changed at any time - by using the `data_rate` attribute. Defaults to 1. - :param int pa_level: This parameter controls the RF power amplifier setting of transmissions. - Options are ``0`` (dBm), ``-6`` (dBm), ``-12`` (dBm), or ``-18`` (dBm). This can be changed - at any time by using the `pa_level` attribute. Defaults to 0. - :param bool dynamic_payloads: This parameter enables/disables the dynamic payload length - feature of the nRF24L01. Defaults to enabled. This can be changed at any time by using the - `dynamic_payloads` attribute. - :param bool auto_ack: This parameter enables/disables the automatic acknowledgment (ACK) - feature of the nRF24L01. Defaults to enabled if `dynamic_payloads` is enabled. This can be - changed at any time by using the `auto_ack` attribute. - :param bool ask_no_ack: This represents a special flag that has to be thrown to enable a - feature specific to individual payloads. Setting this parameter only enables access to this - feature; it does not invoke it (see parameters for `send()` or `write()` functions). - Enabling/Disabling this does not affect `auto_ack` attribute. - :param bool ack: This represents a special flag that has to be thrown to enable a feature - allowing custom response payloads appended to the ACK packets. Enabling this also requires - the `auto_ack` attribute enabled. This can be changed at any time by using the `ack` - attribute. - :param bool irq_dr: When "Data is Ready", this configures the interrupt (IRQ) trigger of the - nRF24L01's IRQ pin (active low). Defaults to enabled. This can be changed at any time by - using the `interrupt_config()` function. - :param bool irq_ds: When "Data is Sent", this configures the interrupt (IRQ) trigger of the - nRF24L01's IRQ pin (active low). Defaults to enabled. This can be changed at any time by - using the `interrupt_config()` function. - :param bool irq_df: When "max retry attempts are reached" (specified by the `arc` attribute), - this configures the interrupt (IRQ) trigger of the nRF24L01's IRQ pin (active low) and - represents transmission failure. Defaults to enabled. This can be changed at any time by - using the `interrupt_config()` function. address_length ****************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.address_length - This `int` attribute specifies the length (in bytes) of addresses to be used for RX/TX - pipes. The addresses assigned to the data pipes must have byte length equal to the value + The addresses assigned to the data pipes must have byte length equal to the value set for this attribute. A valid input value must be an `int` in range [3,5]. Otherwise a `ValueError` exception is @@ -165,8 +116,6 @@ open_tx_pipe() .. automethod:: circuitpython_nrf24l01.rf24.RF24.open_tx_pipe - This function is used to open a data pipe for OTA (over the air) TX transmissions. - :param bytearray address: The virtual address of the receiving nRF24L01. This must have a length equal to the `address_length` attribute (see `address_length` attribute). Otherwise a `ValueError` exception is thrown. The address specified here must match the @@ -183,9 +132,6 @@ close_rx_pipe() .. automethod:: circuitpython_nrf24l01.rf24.RF24.close_rx_pipe - This function is used to close a specific data pipe from OTA (over the air) RX - transmissions. - :param int pipe_number: The data pipe to use for RX transactions. This must be in range [0,5]. Otherwise a `ValueError` exception is thrown. :param bool reset: `True` resets the address for the specified ``pipe_number`` to the @@ -197,8 +143,7 @@ open_rx_pipe() .. automethod:: circuitpython_nrf24l01.rf24.RF24.open_rx_pipe - This function is used to open a specific data pipe for OTA (over the air) RX - transmissions. If `dynamic_payloads` attribute is `False`, then the `payload_length` + If `dynamic_payloads` attribute is `False`, then the `payload_length` attribute is used to specify the expected length of the RX payload on the specified data pipe. @@ -218,8 +163,6 @@ listen .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.listen - An attribute to represent the nRF24L01 primary role as a radio. - Setting this attribute incorporates the proper transitioning to/from RX mode as it involves playing with the `power` attribute and the nRF24L01's CE pin. This attribute does not power down the nRF24L01, but will power it up when needed; use `power` attribute set to `False` @@ -245,8 +188,7 @@ any() .. automethod:: circuitpython_nrf24l01.rf24.RF24.any - This function checks if the nRF24L01 has received any data at all, and then reports the - next available payload's length (in bytes) -- if there is any. + -- if there is any. :returns: - `int` of the size (in bytes) of an available RX payload (if any). @@ -257,8 +199,8 @@ recv() .. automethod:: circuitpython_nrf24l01.rf24.RF24.recv - This function is used to retrieve the next available payload in the RX FIFO buffer, then - clears the `irq_dr` status flag. This function synonomous to `read_ack()`. + in the RX FIFO buffer, then clears the `irq_dr` status flag. This function synonomous to + `read_ack()`. :returns: A `bytearray` of the RX payload data or `None` if there is no payload @@ -272,8 +214,6 @@ send() .. automethod:: circuitpython_nrf24l01.rf24.RF24.send - This blocking function is used to transmit payload(s). - :returns: * `list` if a list or tuple of payloads was passed as the ``buf`` parameter. Each item in the returned list will contain the returned status for each corresponding payload @@ -342,7 +282,6 @@ what_happened() .. automethod:: circuitpython_nrf24l01.rf24.RF24.what_happened - This debuggung function aggregates and outputs all status/condition related information from the nRF24L01. Some information may be irrelevant depending on nRF24L01's state/condition. @@ -400,22 +339,21 @@ dynamic_payloads .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.dynamic_payloads - This `bool` attribute controls the nRF24L01's dynamic payload length feature. + Default setting is `True`. - - `True` enables nRF24L01's dynamic payload length feature. The `payload_length` - attribute is ignored when this feature is enabled. - - `False` disables nRF24L01's dynamic payload length feature. Be sure to adjust - the `payload_length` attribute accordingly when `dynamic_payloads` feature is disabled. + - `True` enables nRF24L01's dynamic payload length feature. The `payload_length` attribute is + ignored when this feature is enabled. + - `False` disables nRF24L01's dynamic payload length feature. Be sure to adjust the + `payload_length` attribute accordingly when `dynamic_payloads` feature is disabled. payload_length ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.payload_length - This `int` attribute specifies the length (in bytes) of payload that is regarded, - meaning "how big of a payload should the radio care about?" If the `dynamic_payloads` - attribute is enabled, this attribute has no affect. When `dynamic_payloads` is disabled, - this attribute is used to specify the payload length when entering RX mode. + If the `dynamic_payloads` attribute is enabled, this attribute has no affect. When + `dynamic_payloads` is disabled, this attribute is used to specify the payload length when + entering RX mode. A valid input value must be an `int` in range [1,32]. Otherwise a `ValueError` exception is thrown. Default is set to the nRF24L01's maximum of 32. @@ -431,8 +369,7 @@ auto_ack .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.auto_ack - This `bool` attribute controls the nRF24L01's automatic acknowledgment feature during - the process of receiving a packet. + a packet. Default setting is `True`. - `True` enables transmitting automatic acknowledgment packets. The CRC (cyclic redundancy checking) is enabled automatically by the nRF24L01 if the `auto_ack` attribute is enabled @@ -445,8 +382,7 @@ arc .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.arc - This `int` attribute specifies the nRF24L01's number of attempts to re-transmit TX - payload when acknowledgment packet is not received. The `auto_ack` must be enabled on the + when acknowledgment packet is not received. The `auto_ack` must be enabled on the receiving nRF24L01, otherwise this attribute will make `send()` seem like it failed. A valid input value must be in range [0,15]. Otherwise a `ValueError` exception is thrown. @@ -458,8 +394,7 @@ ard .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ard - This `int` attribute specifies the nRF24L01's delay (in µs) between attempts to - automatically re-transmit the TX payload when an expected acknowledgement (ACK) packet is + when an expected acknowledgement (ACK) packet is not received. During this time, the nRF24L01 is listening for the ACK packet. If the `auto_ack` attribute is disabled, this attribute is not applied. @@ -480,9 +415,8 @@ ack .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ack - This `bool` attribute represents the status of the nRF24L01's capability to use custom - payloads as part of the automatic acknowledgment (ACK) packet. Use this attribute to - set/check if the custom ACK payloads feature is enabled. + Use this attribute to set/check if the custom ACK payloads feature is enabled. Default setting + is `False`. - `True` enables the use of custom ACK payloads in the ACK packet when responding to receiving transmissions. As `dynamic_payloads` and `auto_ack` attributes are required for @@ -496,8 +430,7 @@ load_ack() .. automethod:: circuitpython_nrf24l01.rf24.RF24.load_ack - This allows the MCU to specify a payload to be allocated into the TX FIFO buffer for use - on a specific data pipe. This payload will then be appended to the automatic acknowledgment + This payload will then be appended to the automatic acknowledgment (ACK) packet that is sent when fresh data is received on the specified pipe. See `read_ack()` on how to fetch a received custom ACK payloads. @@ -530,8 +463,7 @@ read_ack() .. automethod:: circuitpython_nrf24l01.rf24.RF24.read_ack - Allows user to read the automatic acknowledgement (ACK) payload (if any) when nRF24L01 - is in TX mode. This function is called from a blocking `send()` call if the `ack` attribute + This function is called from a blocking `send()` call if the `ack` attribute is enabled. Alternatively, this function can be called directly in case of calling the non-blocking `write()` function during asychronous applications. This function is an alias of `recv()` and remains for bakward compatibility with older versions of this library. @@ -544,7 +476,7 @@ irq_dr .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_dr - A `bool` that represents the "Data Ready" interrupted flag. (read-only) + (read-only) * `True` represents Data is in the RX FIFO buffer * `False` represents anything depending on context (state/condition of FIFO buffers) -- @@ -563,7 +495,7 @@ irq_df .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_df - A `bool` that represents the "Data Failed" interrupted flag. (read-only) + (read-only) * `True` signifies the nRF24L01 attemped all configured retries * `False` represents anything depending on context (state/condition) -- usually this means @@ -583,7 +515,7 @@ irq_ds .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_ds - A `bool` that represents the "Data Sent" interrupted flag. (read-only) + (read-only) * `True` represents a successful transmission * `False` represents anything depending on context (state/condition of FIFO buffers) -- @@ -602,9 +534,8 @@ clear_status_flags() .. automethod:: circuitpython_nrf24l01.rf24.RF24.clear_status_flags - This clears the interrupt flags in the status register. Internally, this is - automatically called by `send()`, `write()`, `recv()`, and when `listen` changes from - `False` to `True`. + Internally, this is automatically called by `send()`, `write()`, `recv()`, and when `listen` + changes from `False` to `True`. :param bool data_recv: specifies wheather to clear the "RX Data Ready" flag. :param bool data_sent: specifies wheather to clear the "TX Data Sent" flag. @@ -625,16 +556,15 @@ interrupt_config() .. automethod:: circuitpython_nrf24l01.rf24.RF24.interrupt_config - Sets the configuration of the nRF24L01's IRQ (interrupt) pin. The signal from the - nRF24L01's IRQ pin is active LOW. (write-only) + The digital signal from the nRF24L01's IRQ pin is active LOW. (write-only) :param bool data_recv: If this is `True`, then IRQ pin goes active when there is new data - to read in the RX FIFO buffer. + to read in the RX FIFO buffer. Default setting is `True` :param bool data_sent: If this is `True`, then IRQ pin goes active when a payload from TX - buffer is successfully transmit. + buffer is successfully transmit. Default setting is `True` :param bool data_fail: If this is `True`, then IRQ pin goes active when maximum number of attempts to re-transmit the packet have been reached. If `auto_ack` attribute is - disabled, then this IRQ event is not used. + disabled, then this IRQ event is not used. Default setting is `True` .. note:: To fetch the status (not configuration) of these IRQ flags, use the `irq_df`, `irq_ds`, `irq_dr` attributes respectively. @@ -655,9 +585,6 @@ data_rate .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.data_rate - This `int` attribute specifies the nRF24L01's frequency data rate for OTA (over the air) - transmissions. - A valid input value is: - ``1`` sets the frequency data rate to 1 Mbps @@ -677,8 +604,6 @@ channel .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.channel - This `int` attribute specifies the nRF24L01's frequency (in 2400 + `channel` MHz). - A valid input value must be in range [0, 125] (that means [2.4, 2.525] GHz). Otherwise a `ValueError` exception is thrown. Default is 76. @@ -687,9 +612,7 @@ crc .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.crc - This `int` attribute specifies the nRF24L01's CRC (cyclic redundancy checking) encoding - scheme in terms of byte length. CRC is a way of making sure that the transmission didn't - get corrupted over the air. + CRC is a way of making sure that the transmission didn't get corrupted over the air. A valid input value is in range [0,2]: @@ -707,8 +630,7 @@ power .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.power - This `bool` attribute controls the power state of the nRF24L01. This is exposed for - asynchronous applications and user preference. + This is exposed for asynchronous applications and user preference. - `False` basically puts the nRF24L01 to sleep (AKA power down mode) with ultra-low current consumption. No transmissions are executed when sleeping, but the nRF24L01 can @@ -734,8 +656,7 @@ pa_level .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pa_level - This `int` attribute specifies the nRF24L01's power amplifier level (in dBm). Higher - levels mean the transmission will cover a longer distance. Use this attribute to tweak the + Higher levels mean the transmission will cover a longer distance. Use this attribute to tweak the nRF24L01 current consumption on projects that don't span large areas. A valid input value is: @@ -752,9 +673,6 @@ tx_full .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.tx_full - An attribute to represent the nRF24L01's status flag signaling that the TX FIFO buffer - is full. (read-only) - Calling this does not execute an SPI transaction. It only exposes that latest data contained in the STATUS byte that's always returned from any SPI transactions with the nRF24L01. Use the `update()` function to manually refresh this data when needed. @@ -769,9 +687,6 @@ rpd .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.rpd - This read-only attribute returns `True` if RPD (Received Power Detector) is triggered - or `False` if not triggered. - .. note:: The RPD flag is triggered in the following cases: 1. During RX mode (`listen` = `True`) and a RF transmission with a gain above a preset @@ -788,8 +703,7 @@ update() .. automethod:: circuitpython_nrf24l01.rf24.RF24.update - This function is only used to get an updated status byte over SPI from the nRF24L01 and - is exposed to the MCU for asynchronous applications. Refreshing the status byte is vital to + and is exposed to the MCU for asynchronous applications. Refreshing the status byte is vital to checking status of the interrupts, RX pipe number related to current RX payload, and if the TX FIFO buffer is full. This function returns nothing, but internally updates the `irq_dr`, `irq_ds`, `irq_df`, and `tx_full` attributes. Internally this is a helper function to @@ -800,10 +714,8 @@ resend() .. automethod:: circuitpython_nrf24l01.rf24.RF24.resend - Use this function to maunally re-send the previous payload in the - top level (first out) of the TX FIFO buffer. All returned data follows the same patttern - that `send()` returns with the added condition that this function will return `False` - if the TX FIFO buffer is empty. + All returned data follows the same patttern that `send()` returns with the added condition that + this function will return `False` if the TX FIFO buffer is empty. .. note:: The nRF24L01 normally removes a payload from the TX FIFO buffer after successful transmission, but not when this function is called. The payload (successfully @@ -816,10 +728,6 @@ write() .. automethod:: circuitpython_nrf24l01.rf24.RF24.write - This non-blocking function (when used as alternative to `send()`) is meant for - asynchronous applications and can only handle one payload at a time as it is a helper - function to `send()`. - :param bytearray buf: The payload to transmit. This bytearray must have a length greater than 0 and less than 32 bytes, otherwise a `ValueError` exception is thrown. @@ -880,8 +788,6 @@ flush_rx() .. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_rx - A helper function to flush the nRF24L01's internal RX FIFO buffer. (write-only) - .. note:: The nRF24L01 RX FIFO is 3 level stack that holds payload data. This means that there can be up to 3 received payloads (each of a maximum length equal to 32 bytes) waiting to be read (and popped from the stack) by `recv()` or `read_ack()`. This @@ -892,8 +798,6 @@ flush_tx() .. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_tx - A helper function to flush the nRF24L01's internal TX FIFO buffer. (write-only) - .. note:: The nRF24L01 TX FIFO is 3 level stack that holds payload data. This means that there can be up to 3 payloads (each of a maximum length equal to 32 bytes) waiting to be transmit by `send()`, `resend()` or `write()`. This function clears all 3 levels. It @@ -906,9 +810,6 @@ fifo() .. automethod:: circuitpython_nrf24l01.rf24.RF24.fifo - This provides some precision determining the status of the TX/RX FIFO buffers. - (read-only) - :param bool about_tx: * `True` means information returned is about the TX FIFO buffer. * `False` means information returned is about the RX FIFO buffer. This parameter @@ -932,9 +833,6 @@ pipe() .. automethod:: circuitpython_nrf24l01.rf24.RF24.pipe - This function returns information about the data pipe that received the next available - payload in the RX FIFO buffer. - :returns: - `None` if there is no payload in RX FIFO. - The `int` identifying pipe number [0,5] that received the next available payload in @@ -945,8 +843,6 @@ address() .. automethod:: circuitpython_nrf24l01.rf24.RF24.address - Returns the current address set to a specified data pipe or the TX address. (read-only) - :param int index: the number of the data pipe whose address is to be returned. Defaults to ``-1``. A valid index ranges [0,5] for RX addresses or any negative `int` for the TX address. Otherwise an `IndexError` is thown. From 243eb3cdc653a57f0c97892cdc85d54d39a35566 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sat, 21 Mar 2020 16:19:36 -0700 Subject: [PATCH 010/127] fixed examples accordingly --- examples/nrf24l01_2arduino_handling_data.py | 2 +- examples/nrf24l01_context_test.py | 22 ++++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/examples/nrf24l01_2arduino_handling_data.py b/examples/nrf24l01_2arduino_handling_data.py index f27de3d..5def331 100644 --- a/examples/nrf24l01_2arduino_handling_data.py +++ b/examples/nrf24l01_2arduino_handling_data.py @@ -22,7 +22,7 @@ spi = board.SPI() # init spi bus object # initialize the nRF24L01 on the spi bus object -nrf = RF24(spi, csn, ce, ask_no_ack=False) +nrf = RF24(spi, csn, ce) nrf.dynamic_payloads = False # this is the default in the TMRh20 arduino library # set address of TX node into a RX pipe diff --git a/examples/nrf24l01_context_test.py b/examples/nrf24l01_context_test.py index 3f9609c..ce44c82 100644 --- a/examples/nrf24l01_context_test.py +++ b/examples/nrf24l01_context_test.py @@ -16,25 +16,33 @@ spi = board.SPI() # init spi bus object # initialize the nRF24L01 objects on the spi bus object -nrf = RF24(spi, csn, ce, ack=True) # the first object will have all the features enabled -# including the option to use custom ACK payloads +nrf = RF24(spi, csn, ce) +# enable the option to use custom ACK payloads +nrf.ack = True # the second object has most features disabled/altered +basicRF = RF24(spi, csn, ce) # disabled dynamic_payloads, but still using enabled auto_ack +basicRF.dynamic_payloads = False # the IRQ pin is configured to only go active on "data fail" +basicRF.interrupt_config(data_recv=False, data_sent=False) # using a different channel: 2 (default is 76) +basicRF.channel = 2 # CRC is set to 1 byte long +basicRF.crc = 1 # data rate is set to 2 Mbps +basicRF.data_rate = 2 # payload length is set to 8 bytes +basicRF.payload_length = 8 # NOTE address length is set to 3 bytes +basicRF.address_length = 3 # RF power amplifier is set to -12 dbm -# automatic retry attempts is set to 15 (maximum allowed) +basicRF.pa_level = -12 # automatic retry delay (between attempts) is set to 1000 microseconds -basicRF = RF24(spi, csn, ce, - dynamic_payloads=False, irq_dr=False, irq_ds=False, - channel=2, crc=1, data_rate=2, payload_length=8, - address_length=3, pa_level=-12, ard=1000, arc=15) +basicRF.ard = 1000 +# automatic retry attempts is set to 15 (maximum allowed) +basicRF.arc = 15 print("\nsettings configured by the nrf object") with nrf: From 53cd0958ffffc25ea6fb5d7abcc550bdfca825a6 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Mon, 23 Mar 2020 16:06:59 -0700 Subject: [PATCH 011/127] blank __init__.py, (-) most comments, & (-) reset param in close_tx_pipe() --- circuitpython_nrf24l01/__init__.py | 5 - circuitpython_nrf24l01/rf24.py | 309 ++++++++++------------------- docs/api.rst | 3 - 3 files changed, 101 insertions(+), 216 deletions(-) diff --git a/circuitpython_nrf24l01/__init__.py b/circuitpython_nrf24l01/__init__.py index fe0189b..e69de29 100644 --- a/circuitpython_nrf24l01/__init__.py +++ b/circuitpython_nrf24l01/__init__.py @@ -1,5 +0,0 @@ -"""module managment for the circuitpython-nrf24l01 package""" - -from .rf24 import RF24 - -__all__ = ['RF24'] diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index f37ca65..cf9ff0d 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -20,30 +20,28 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -# pylint: disable=too-many-lines -""" -rf24 module containing the base class RF24 -""" +"""rf24 module containing the base class RF24""" __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/2bndy5/CircuitPython_nRF24L01.git" import time +from micropython import const from adafruit_bus_device.spi_device import SPIDevice # nRF24L01 registers # pylint: disable=bad-whitespace -CONFIG = 0x00 #: register for configuring IRQ, CRC, PWR & RX/TX roles -EN_AA = 0x01 #: register for auto-ACK feature. Each bit represents this feature per pipe -EN_RX = 0x02 #: register to open/close pipes. Each bit represents this feature per pipe -SETUP_AW = 0x03 #: address width register -SETUP_RETR = 0x04 #: auto-retry count and delay register -RF_CH = 0x05 #: channel register -RF_SETUP = 0x06 #: RF Power Amplifier & Data Rate -RX_ADDR = 0x0a #: RX pipe addresses == [0,5]:[0x0a:0x0f] -RX_PW = 0x11 #: RX payload widths on pipes == [0,5]:[0x11,0x16] -FIFO = 0x17 #: register containing info on both RX/TX FIFOs + re-use payload flag -DYNPD = 0x1c #: dynamic payloads feature. Each bit represents this feature per pipe -FEATURE = 0x1d #: global flags for dynamic payloads, custom ACK payloads, & Ask no ACK -TX_ADDR = 0x10 #: Address that is used for TX transmissions +CONFIG = const(0x00) # IRQ, CRC, PWR & RX/TX roles +EN_AA = const(0x01) # auto-ACK +EN_RX = const(0x02) # open/close pipes +SETUP_AW = const(0x03) # address width +SETUP_RETR = const(0x04) # auto-retry count & delay +RF_CH = const(0x05) # channel +RF_SETUP = const(0x06) # RF Power Amplifier & Data Rate +RX_ADDR = const(0x0a) # RX pipe addresses == [0,5]:[0x0a:0x0f] +RX_PW = const(0x11) # RX payload widths on pipes == [0,5]:[0x11,0x16] +FIFO = const(0x17) # info on both RX/TX FIFOs + re-use payload flag +DYNPD = const(0x1c) # dynamic payloads +FEATURE = const(0x1d) # TX flags (dynamic payloads, ACK payloads, & NO_ACK) +TX_ADDR = const(0x10) # Address used for TX transmissions # pylint: enable=bad-whitespace # NOTE expanded documentation lives in api.rst to save space on M0 @@ -57,23 +55,15 @@ def __init__(self, spi, csn, ce): self._fifo = 0 self._status = 0 # init shadow copy of RX addresses for all pipes - self._pipes = [b'', b'', 0, 0, 0, 0] - self._payload_widths = [0, 0, 0, 0, 0, 0] # payload_length specific to each pipe + self._pipes = [b'\xE7' * 5, b'\xC2' * 5, 0, 0, 0, 0] + self._payload_widths = [32] * 6 # payload_length specific to each pipe # shadow copy of last RX_ADDR written to pipe 0 self._pipe0_read_addr = None # needed as open_tx_pipe() appropriates pipe 0 for ACK # init the _open_pipes attribute (reflects only RX state on each pipe) - self._open_pipes = 0 # <- means all pipes closed - # init the SPI bus and pins + self._open_pipes = 0 # 0 = all pipes closed self._spi = SPIDevice(spi, chip_select=csn, baudrate=1250000) - - # store the ce pin self.ce_pin = ce - # reset ce.value & disable the chip comms self.ce_pin.switch_to_output(value=False) - # if radio is powered up and CE is LOW: standby-I mode - # if radio is powered up and CE is HIGH: standby-II mode (dangerous if longer than 4ms) - # NOTE per spec sheet: nRF24L01+ must be in a standby or power down mode before writing - # to the configuration register # configure the CONFIG register: # 0x0E = IRQs are all enabled, CRC is enabled with 2 bytes, and power up in TX mode self._config = 0x0E @@ -84,14 +74,11 @@ def __init__(self, spi, csn, ce): else: # hardware presence check NOT passed print(bin(self._reg_read(CONFIG))) raise RuntimeError("nRF24L01 Hardware not responding") - - # capture all pipe's RX addresses & the TX address from last usage for i in range(6): if i < 2: self._pipes[i] = self._reg_read_bytes(RX_ADDR + i) else: self._pipes[i] = self._reg_read(RX_ADDR + i) - # shadow copy of the TX_ADDR self._tx_address = self._reg_read_bytes(TX_ADDR) # configure the SETUP_RETR register @@ -104,44 +91,36 @@ def __init__(self, spi, csn, ce): # configure features for TX operations # 5 = enable dynamic_payloads, disable custom ack payloads, and allow ask_no_ack command self._features = 5 - # init the last few singleton attribute self._channel = 76 self._addr_len = 5 with self: # write to registers & power up - # using __enter__() configures all virtual features and settings to the hardware - # registers - self.ce_pin.value = 0 # ensure standby-I mode to write to CONFIG register - self._reg_write(CONFIG, self._config | 1) # enable RX mode - time.sleep(0.000015) # wait time for transitioning modes RX/TX - self.flush_rx() # spec sheet say "used in RX mode" - self._reg_write(CONFIG, self._config & 0xC) # power down + TX mode - time.sleep(0.000015) # wait time for transitioning modes RX/TX - self.flush_tx() # spec sheet say "used in TX mode" - self.clear_status_flags() # writes directly to STATUS register + self.ce_pin.value = 0 + self._reg_write(CONFIG, self._config | 1) + time.sleep(0.000015) + self.flush_rx() + self._reg_write(CONFIG, self._config & 0xC) + time.sleep(0.000015) + self.flush_tx() + self.clear_status_flags() def __enter__(self): - # dump IRQ and CRC data to CONFIG register self._reg_write(CONFIG, self._config & 0x7C) - self._reg_write(RF_SETUP, self._rf_setup) # dump to RF_SETUP register - # dump open/close pipe status to EN_RXADDR register (for all pipes) + self._reg_write(RF_SETUP, self._rf_setup) self._reg_write(EN_RX, self._open_pipes) - self._reg_write(DYNPD, self._dyn_pl) # dump to DYNPD register - self._reg_write(EN_AA, self._aa) # dump to EN_AA register - self._reg_write(FEATURE, self._features) # dump to FEATURE register - # dump to SETUP_RETR register + self._reg_write(DYNPD, self._dyn_pl) + self._reg_write(EN_AA, self._aa) + self._reg_write(FEATURE, self._features) self._reg_write(SETUP_RETR, self._setup_retr) - # dump pipes' RX addresses and static payload lengths for i, address in enumerate(self._pipes): if i < 2: self._reg_write_bytes(RX_ADDR + i, address) else: self._reg_write(RX_ADDR + i, address) self._reg_write(RX_PW + i, self._payload_widths[i]) - # dump last used TX address self._reg_write_bytes(TX_ADDR, self._tx_address) - self.address_length = self._addr_len # writes directly to SETUP_AW register - self.channel = self._channel # writes directly to RF_CH register + self.address_length = self._addr_len + self.channel = self._channel return self def __exit__(self, *exc): @@ -150,29 +129,28 @@ def __exit__(self, *exc): # pylint: disable=no-member def _reg_read(self, reg): - buf = bytearray(2) # 2 = 1 status byte + 1 byte of returned content + buf = bytearray(2) with self._spi as spi: - time.sleep(0.005) # time for CSN to settle + time.sleep(0.005) spi.readinto(buf, write_value=reg) - self._status = buf[0] # save status byte - return buf[1] # drop status byte and return the rest + self._status = buf[0] + return buf[1] def _reg_read_bytes(self, reg, buf_len=5): - # allow an extra byte for status data buf = bytearray(buf_len + 1) with self._spi as spi: - time.sleep(0.005) # time for CSN to settle + time.sleep(0.005) spi.readinto(buf, write_value=reg) - self._status = buf[0] # save status byte - return buf[1:] # drop status byte and return the rest + self._status = buf[0] + return buf[1:] def _reg_write_bytes(self, reg, out_buf): out_buf = bytes([0x20 | reg]) + out_buf in_buf = bytearray(len(out_buf)) with self._spi as spi: - time.sleep(0.005) # time for CSN to settle + time.sleep(0.005) spi.write_readinto(out_buf, in_buf) - self._status = in_buf[0] # save status byte + self._status = in_buf[0] def _reg_write(self, reg, value=None): if value is None: @@ -181,9 +159,9 @@ def _reg_write(self, reg, value=None): out_buf = bytes([0x20 | reg, value]) in_buf = bytearray(len(out_buf)) with self._spi as spi: - time.sleep(0.005) # time for CSN to settle + time.sleep(0.005) spi.write_readinto(out_buf, in_buf) - self._status = in_buf[0] # save status byte + self._status = in_buf[0] # pylint: enable=no-member @property @@ -194,10 +172,7 @@ def address_length(self): @address_length.setter def address_length(self, length): - # nRF24L01+ must be in a standby or power down mode before writing to the configuration - # registers. if 3 <= length <= 5: - # address width is saved in 2 bits making range = [3,5] self._addr_len = int(length) self._reg_write(SETUP_AW, length - 2) else: @@ -208,16 +183,14 @@ def open_tx_pipe(self, address): """This function is used to open a data pipe for OTA (over the air) TX transmissions.""" if len(address) == self.address_length: - # if auto_ack == True, then use this TX address as the RX address for ACK if self.auto_ack: - # settings need to match on both transceivers: dynamic_payloads and payload_length self._pipes[0] = address - self._reg_write_bytes(RX_ADDR, address) # using pipe 0 - self._open_pipes = self._open_pipes | 1 # open pipe 0 for RX-ing ACK + self._reg_write_bytes(RX_ADDR, address) + self._open_pipes = self._open_pipes | 1 self._reg_write(EN_RX, self._open_pipes) self._payload_widths[0] = self.payload_length - self._reg_write(RX_PW, self.payload_length) # set expected payload_length - self._pipes[0] = address # update the context as well + self._reg_write(RX_PW, self.payload_length) + self._pipes[0] = address self._tx_address = address self._reg_write_bytes(TX_ADDR, address) else: @@ -225,24 +198,12 @@ def open_tx_pipe(self, address): "to the address_length attribute (currently set to" " {})".format(self.address_length)) - def close_rx_pipe(self, pipe_number, reset=True): + def close_rx_pipe(self, pipe_number): """This function is used to close a specific data pipe from OTA (over the air) RX transmissions.""" if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0,5]") - self._open_pipes = self._reg_read(EN_RX) # refresh data - if reset:# reset pipe address accordingly - if not pipe_number: - # NOTE this does not clear the shadow copy (pipe0_read_addr) of address for pipe 0 - self._reg_write_bytes(pipe_number + RX_ADDR, b'\xe7' * 5) - self._pipes[pipe_number] = b'\xe7' * 5 - elif pipe_number == 1: # write the full address for pipe 1 - self._reg_write_bytes(pipe_number + RX_ADDR, b'\xc2' * 5) - self._pipes[pipe_number] = b'\xc2' * 5 - else: # write just MSB for 2 <= pipes <= 5 - self._reg_write(pipe_number + RX_ADDR, pipe_number + 0xc1) - self._pipes[pipe_number] = pipe_number + 0xc1 - # disable the specified data pipe if not already + self._open_pipes = self._reg_read(EN_RX) if self._open_pipes & (1 << pipe_number): self._open_pipes = self._open_pipes & ~(1 << pipe_number) self._reg_write(EN_RX, self._open_pipes) @@ -256,30 +217,17 @@ def open_rx_pipe(self, pipe_number, address): raise ValueError("address must be a buffer protocol object with a byte length\nequal " "to the address_length attribute (currently set to " "{})".format(self.address_length)) - - # write the address - if pipe_number < 2: # write entire address if pipe_number is 0 or 1 + if pipe_number < 2: if not pipe_number: - # save shadow copy of address if target pipe_number is 0. This is done to help - # ensure the proper address is set to pipe 0 via _start_listening() as - # open_tx_pipe() will appropriate the address on pipe 0 if auto_ack is enabled for - # TX mode self._pipe0_read_addr = address self._pipes[pipe_number] = address self._reg_write_bytes(RX_ADDR + pipe_number, address) else: - # only write MSByte if pipe_number is not 0 or 1 self._pipes[pipe_number] = address[0] self._reg_write(RX_ADDR + pipe_number, address[0]) - - # now manage the pipe - self._open_pipes = self._reg_read(EN_RX) # refresh data - # enable the specified data pipe + self._open_pipes = self._reg_read(EN_RX) self._open_pipes = self._open_pipes | (1 << pipe_number) self._reg_write(EN_RX, self._open_pipes) - - # now adjust payload_length accordingly despite dynamic_payload setting - # radio only uses this info in RX mode when dynamic_payloads == True self._reg_write(RX_PW + pipe_number, self.payload_length) self._payload_widths[pipe_number] = self.payload_length @@ -297,55 +245,38 @@ def listen(self, is_rx): self._stop_listening() def _start_listening(self): - # ensure radio is in power down or standby-I mode if self.ce_pin.value: self.ce_pin.value = 0 - if self._pipe0_read_addr is not None: - # make sure the last call to open_rx_pipe(0) sticks if initialized self._reg_write_bytes(RX_ADDR, self._pipe0_read_addr) - self._pipes[0] = self._pipe0_read_addr # update the context as well - - # power up radio & set radio in RX mode + self._pipes[0] = self._pipe0_read_addr self._config = self._config & 0xFC | 3 self._reg_write(CONFIG, self._config) - time.sleep(0.00015) # mandatory wait time to power up radio or switch modes (RX/TX) - self.flush_rx() # spec sheet says "used in RX mode" - self.clear_status_flags(True, False, False) # only Data Ready flag - - # enable radio comms - self.ce_pin.value = 1 # radio begins listening after CE pulse is > 130 µs - time.sleep(0.00013) # ensure pulse is > 130 µs - # nRF24L01 has just entered active RX + standby-II mode + time.sleep(0.00015) # mandatory wait to power up radio + self.flush_rx() + self.clear_status_flags(True, False, False) + self.ce_pin.value = 1 # mandatory pulse is > 130 µs + time.sleep(0.00013) def _stop_listening(self): - # ensure radio is in standby-I mode if self.ce_pin.value: self.ce_pin.value = 0 - # set radio in TX mode as recommended behavior per spec sheet. - self._config = self._config & 0xFE # does not put radio to sleep + self._config = self._config & 0xFE self._reg_write(CONFIG, self._config) - # mandated wait for transitioning between modes RX/TX time.sleep(0.00016) - # exits while still in Standby-I (low current & no transmissions) def any(self): """This function checks if the nRF24L01 has received any data at all, and then reports the next available payload's length (in bytes)""" - # 0x60 == R_RX_PL_WID command - return self._reg_read(0x60) # top-level payload length + return self._reg_read(0x60) # 0x60 = R_RX_PL_WID command def recv(self): """This function is used to retrieve the next available payload""" if not self.irq_dr: return None - # buffer size = current payload size curr_pl_size = self.payload_length if not self.dynamic_payloads else self.any() - # get the data (0x61 = R_RX_PAYLOAD) - result = self._reg_read_bytes(0x61, curr_pl_size) - # clear only Data Ready IRQ flag for accurate RX FIFO read operations + result = self._reg_read_bytes(0x61, curr_pl_size) # 0x61 = R_RX_PAYLOAD self.clear_status_flags(True, False, False) - # return all available bytes from payload return result def send(self, buf, ask_no_ack=False, force_retry=0): @@ -437,14 +368,13 @@ def irq_df(self): def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): """This clears the interrupt flags in the status register.""" - # 0x07 = STATUS register; only bits 6 through 4 are write-able + # 0x07 = STATUS register self._reg_write(0x07, (data_recv << 6) | ( data_sent << 5) | (data_fail << 4)) def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): """Sets the configuration of the nRF24L01's IRQ (interrupt) pin.""" - self._config = self._reg_read(CONFIG) # refresh data - # save to register and update local copy of pwr & RX/TX modes' flags + self._config = self._reg_read(CONFIG) self._config = (self._config & 0x0F) | (not data_fail << 4) | (not data_sent << 5) | \ (not data_recv << 6) self._reg_write(CONFIG, self._config) @@ -506,14 +436,11 @@ def dynamic_payloads(self): @dynamic_payloads.setter def dynamic_payloads(self, enable): assert isinstance(enable, (bool, int)) - self._features = self._reg_read(FEATURE) # refresh data - # save changes to registers(& their shadows) - if self._features & 4 != enable: # if not already - # throw a specific global flag for enabling dynamic payloads + self._features = self._reg_read(FEATURE) + if self._features & 4 != enable: self._features = (self._features & 3) | (enable << 2) self._reg_write(FEATURE, self._features) - # 0x3F == all pipes have enabled dynamic payloads - self._dyn_pl = 0x3F if enable else 0 + self._dyn_pl = 0x3F if enable else 0 # 0x3F = enable dynamic payloads all pipes self._reg_write(DYNPD, self._dyn_pl) @property @@ -525,7 +452,6 @@ def payload_length(self): def payload_length(self, length): # max payload size is 32 bytes if not length or length <= 32: - # save for access via getter property self._payload_length = length else: raise ValueError( @@ -535,14 +461,13 @@ def payload_length(self, length): def arc(self): """This `int` attribute specifies the nRF24L01's number of attempts to re-transmit TX payload""" - self._setup_retr = self._reg_read(SETUP_RETR) # refresh data + self._setup_retr = self._reg_read(SETUP_RETR) return self._setup_retr & 0x0f @arc.setter def arc(self, count): if 0 <= count <= 15: - if self.arc & 0x0F != count: # write only if needed - # save changes to register(& its shadow) + if self.arc & 0x0F != count: self._setup_retr = (self._setup_retr & 0xF0) | count self._reg_write(SETUP_RETR, self._setup_retr) else: @@ -554,17 +479,15 @@ def ard(self): """This `int` attribute specifies the nRF24L01's delay (in microseconds) between attempts to automatically re-transmit the TX payload""" - self._setup_retr = self._reg_read(SETUP_RETR) # refresh data + self._setup_retr = self._reg_read(SETUP_RETR) return ((self._setup_retr & 0xf0) >> 4) * 250 + 250 @ard.setter def ard(self, delta_t): if 250 <= delta_t <= 4000 and delta_t % 250 == 0: - # set new ARD data and current ARC data to register - if self.ard != delta_t: # write only if needed - # save changes to register(& its Shadow) - self._setup_retr = (int((delta_t - 250) / 250) - << 4) | (self._setup_retr & 0x0F) + if self.ard != delta_t: + self._setup_retr = ( + int((delta_t - 250) / 250) << 4) | (self._setup_retr & 0x0F) self._reg_write(SETUP_RETR, self._setup_retr) else: raise ValueError("automatic re-transmit delay can only be a multiple of 250 in range " @@ -579,10 +502,8 @@ def auto_ack(self): @auto_ack.setter def auto_ack(self, enable): assert isinstance(enable, (bool, int)) - # the following 0x3F == enabled auto_ack on all pipes - self._aa = 0x3F if enable else 0 - self._reg_write(EN_AA, self._aa) # 1 == EN_AA register for ACK feature - # nRF24L01 automatically enables CRC if ACK packets are enabled in the FEATURE register + self._aa = 0x3F if enable else 0 # 0x3F = enable auto_ack on all pipes + self._reg_write(EN_AA, self._aa) @property def ack(self): @@ -594,16 +515,12 @@ def ack(self): @ack.setter def ack(self, enable): assert isinstance(enable, (bool, int)) - # we need to throw the EN_ACK_PAY flag in the FEATURES register accordingly on both - # TX & RX nRF24L01s - if self.ack != enable: # if enabling - self.auto_ack = True # ensure auto_ack feature is enabled - # dynamic_payloads required for custom ACK payloads + if self.ack != enable: + self.auto_ack = True self._dyn_pl = 0x3F self._reg_write(DYNPD, self._dyn_pl) else: - # setting auto_ack feature automatically updated the _features attribute, so - self._features = self._reg_read(FEATURE) # refresh data here + self._features = self._reg_read(FEATURE) self._features = (self._features & 5) | (6 if enable else 0) self._reg_write(FEATURE, self._features) @@ -615,14 +532,12 @@ def load_ack(self, buf, pipe_number): if not buf or len(buf) > 32: raise ValueError("buf must be a buffer protocol object with a byte length of" "\nat least 1 and no greater than 32") - # only prepare payload if the auto_ack attribute is enabled and ack[0] is not None if not self.ack: self.ack = True if not self.tx_full: - # 0xA8 = W_ACK_PAYLOAD - self._reg_write_bytes(0xA8 | pipe_number, buf) - return True # payload was loaded - return False # payload wasn't loaded + self._reg_write_bytes(0xA8 | pipe_number, buf) # 0xA8 = W_ACK_PAYLOAD + return True + return False def read_ack(self): """Allows user to read the automatic acknowledgement (ACK) payload (if @@ -633,17 +548,14 @@ def read_ack(self): def data_rate(self): """This `int` attribute specifies the nRF24L01's frequency data rate for OTA (over the air) transmissions.""" - self._rf_setup = self._reg_read(RF_SETUP) # refresh data + self._rf_setup = self._reg_read(RF_SETUP) return (2 if self._rf_setup & 0x28 == 8 else 250) if self._rf_setup & 0x28 else 1 @data_rate.setter def data_rate(self, speed): - # nRF24L01+ must be in a standby or power down mode before writing to the configuration - # registers. if speed in (1, 2, 250): if self.data_rate != speed: speed = 0 if speed == 1 else (8 if speed == 2 else 0x20) - # save changes to register(& its shadow) self._rf_setup = self._rf_setup & 0xD7 | speed self._reg_write(RF_SETUP, self._rf_setup) else: @@ -659,7 +571,7 @@ def channel(self): def channel(self, channel): if 0 <= channel <= 125: self._channel = channel - self._reg_write(RF_CH, channel) # always writes to reg + self._reg_write(RF_CH, channel) else: raise ValueError("channel acn only be set in range [0,125]") @@ -667,15 +579,14 @@ def channel(self, channel): def crc(self): """This `int` attribute specifies the nRF24L01's CRC (cyclic redundancy checking) encoding scheme in terms of byte length.""" - self._config = self._reg_read(CONFIG) # refresh data - return max(0, ((self._config & 12) >> 2) - 1) # this works + self._config = self._reg_read(CONFIG) + return max(0, ((self._config & 12) >> 2) - 1) @crc.setter def crc(self, length): if 0 <= length <= 2: if self.crc != length: - length = (length + 1) << 2 if length else 0 # this works - # save changes to register(& its Shadow) + length = (length + 1) << 2 if length else 0 self._config = self._config & 0x73 | length self._reg_write(0, self._config) else: @@ -690,12 +601,9 @@ def power(self): @power.setter def power(self, is_on): assert isinstance(is_on, (bool, int)) - # capture surrounding flags and set PWR_UP flag according to is_on boolean - self._config = self._reg_read(CONFIG) # refresh data + self._config = self._reg_read(CONFIG) if self.power != is_on: - # only write changes - self._config = (self._config & 0x7d) | ( - is_on << 1) # doesn't affect TX?RX mode + self._config = (self._config & 0x7d) | (is_on << 1) self._reg_write(CONFIG, self._config) # power up/down takes < 150 µs + 4 µs time.sleep(0.00016) @@ -709,11 +617,8 @@ def pa_level(self): @pa_level.setter def pa_level(self, power): - # nRF24L01+ must be in a standby or power down mode before writing to the - # configuration registers. if power in (-18, -12, -6, 0): - power = (3 - int(power / -6)) * 2 # this works - # save changes to register (& its shadow) + power = (3 - int(power / -6)) * 2 self._rf_setup = (self._rf_setup & 0xF9) | power self._reg_write(RF_SETUP, self._rf_setup) else: @@ -735,20 +640,16 @@ def tx_full(self): def update(self): """This function is only used to get an updated status byte over SPI from the nRF24L01""" - # perform non-operation to get status byte (faster than reading the STATUS register) - self._reg_write(0xFF) + self._reg_write(0xFF) # 0xFF = non-operation command def resend(self): """Use this function to maunally re-send the previous payload in the top level (first out) of the TX FIFO buffer.""" result = False - if not self.fifo(True, True): # is there a pre-existing payload - self.clear_status_flags(False) # clear TX related flags - # indicate existing payload will get re-used. - # This command tells the radio not pop TX payload from FIFO on success + if not self.fifo(True, True): + self.clear_status_flags(False) self._reg_write(0xE3) # 0xE3 == REUSE_TX_PL command - # timeout calc assumes 32 byte payload because there is no way to tell when payload - # has already been loaded into TX FIFO; also assemues 32-byte ACK if needed + # timeout calc assumes 32 byte payload (& 32-byte ACK if needed) pl_coef = 1 + bool(self._setup_retr & 0x0f) pl_len = 1 + self._addr_len + ( max(0, ((self._config & 12) >> 2) - 1)) @@ -760,16 +661,15 @@ def resend(self): 380) * (self._setup_retr & 0x0f) / 1000000 timeout = pl_coef * (((8 * (32 + pl_len)) + 9) / bitrate) + \ stby2active + t_irq + t_retry - # cycle the CE pin to re-enable transmission of re-used payload self.ce_pin.value = 0 self.ce_pin.value = 1 time.sleep(0.00001) - self.ce_pin.value = 0 # only send one payload + self.ce_pin.value = 0 self._wait_for_result(timeout) result = self.irq_ds - if self.ack and self.irq_dr: # check if there is an ACK payload - result = self.recv() # save ACK payload & clear RX related IRQ flag - self.clear_status_flags(False) # only clear TX related IRQ flags + if self.ack and self.irq_dr: # is there an ACK payload + result = self.recv() + self.clear_status_flags(False) return result def write(self, buf, ask_no_ack=False): @@ -779,8 +679,8 @@ def write(self, buf, ask_no_ack=False): if not buf or len(buf) > 32: raise ValueError("buf must be a buffer protocol object with a byte length of" "\nat least 1 and no greater than 32") - self.clear_status_flags(False) # only TX related IRQ flags - if self._config & 3 != 2: # ready radio if it isn't yet + self.clear_status_flags(False) + if self._config & 3 != 2: # ensures tx mode & powered up self._config = (self._reg_read(CONFIG) & 0x7c) | 2 self._reg_write(CONFIG, self._config) @@ -794,18 +694,12 @@ def write(self, buf, ask_no_ack=False): buf = buf[:self.payload_length] # now upload the payload accordingly with appropriate command if ask_no_ack: # payload doesn't want acknowledgment - # ensure this feature is allowed by setting EN_DYN_ACK flag in the FEATURE register if self._features & 1 == 0: self._features = self._features & 0xFE | 1 # set EN_DYN_ACK flag high self._reg_write(FEATURE, self._features) - # write appropriate command with payload # 0xA0 = W_TX_PAYLOAD; 0xB0 = W_TX_PAYLOAD_NO_ACK self._reg_write_bytes(0xA0 | (ask_no_ack << 4), buf) - # enable radio comms so it can send the data by starting the mandatory minimum 10 µs pulse - # on CE. Let send() or resend() measure this pulse for blocking reasons self.ce_pin.value = 1 - # while CE is still HIGH only if dynamic_payloads and auto_ack are enabled - # automatically goes to standby-II after successful TX of all payloads in the FIFO def flush_rx(self): """A helper function to flush the nRF24L01's internal RX FIFO buffer. (write-only)""" @@ -830,11 +724,11 @@ def fifo(self, about_tx=False, check_empty=None): def pipe(self): """This function returns information about the data pipe that received the next available payload in the RX FIFO buffer.""" - self.update() # perform Non-operation command to get status byte (should be faster) + self.update() result = (self._status & 0x0E) >> 1 # 0x0E==RX_P_NO - if result <= 5: # is there data in RX FIFO? + if result <= 5: return result - return None # RX FIFO is empty + return None def address(self, index=-1): """Returns the current address set to a specified data pipe or the TX address. @@ -850,5 +744,4 @@ def address(self, index=-1): def _wait_for_result(self, timeout): start = time.monotonic() while not self.irq_ds and not self.irq_df and (time.monotonic() - start) < timeout: - self.update() # perform Non-operation command to get status byte (should be faster) - # print('status: DR={} DS={} DF={}'.format(self.irq_dr, self.irq_ds, self.irq_df)) + self.update() diff --git a/docs/api.rst b/docs/api.rst index c63ccbc..062e914 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -134,9 +134,6 @@ close_rx_pipe() :param int pipe_number: The data pipe to use for RX transactions. This must be in range [0,5]. Otherwise a `ValueError` exception is thrown. - :param bool reset: `True` resets the address for the specified ``pipe_number`` to the - factory address (different for each pipe). `False` leaves the address on the specified - ``pipe_number`` alone. Be aware that the addresses will remain despite loss of power. open_rx_pipe() ****************** From e0c3d9c6bbb64f600ed5575f1ce1e9505c933644 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Mon, 23 Mar 2020 16:18:10 -0700 Subject: [PATCH 012/127] indentations use tabs (saved 5KB) --- circuitpython_nrf24l01/rf24.py | 1396 ++++++++++++++++---------------- 1 file changed, 698 insertions(+), 698 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index cf9ff0d..3adac56 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -47,701 +47,701 @@ # NOTE expanded documentation lives in api.rst to save space on M0 class RF24: - """A driver class for the nRF24L01(+) transceiver radios.""" - def __init__(self, spi, csn, ce): - self._payload_length = 32 # inits internal attribute - self.payload_length = 32 - # last address assigned to pipe0 for reading. init to None - self._fifo = 0 - self._status = 0 - # init shadow copy of RX addresses for all pipes - self._pipes = [b'\xE7' * 5, b'\xC2' * 5, 0, 0, 0, 0] - self._payload_widths = [32] * 6 # payload_length specific to each pipe - # shadow copy of last RX_ADDR written to pipe 0 - self._pipe0_read_addr = None # needed as open_tx_pipe() appropriates pipe 0 for ACK - # init the _open_pipes attribute (reflects only RX state on each pipe) - self._open_pipes = 0 # 0 = all pipes closed - self._spi = SPIDevice(spi, chip_select=csn, baudrate=1250000) - self.ce_pin = ce - self.ce_pin.switch_to_output(value=False) - # configure the CONFIG register: - # 0x0E = IRQs are all enabled, CRC is enabled with 2 bytes, and power up in TX mode - self._config = 0x0E - self._reg_write(CONFIG, self._config) # dump to register - # check for device presence by verifying nRF24L01 is in TX + standby-I mode - if self._reg_read(CONFIG) & 3 == 2: # if in TX + standby-I mode - self.power = False # power down - else: # hardware presence check NOT passed - print(bin(self._reg_read(CONFIG))) - raise RuntimeError("nRF24L01 Hardware not responding") - for i in range(6): - if i < 2: - self._pipes[i] = self._reg_read_bytes(RX_ADDR + i) - else: - self._pipes[i] = self._reg_read(RX_ADDR + i) - # shadow copy of the TX_ADDR - self._tx_address = self._reg_read_bytes(TX_ADDR) - # configure the SETUP_RETR register - self._setup_retr = 0x53 #ard = 1500; arc = 3 - # configure the RF_SETUP register - self._rf_setup = 0x06 # 1 Mbps data_rate, and 0 dbm pa_level - # configure dynamic_payloads & auto_ack for RX operations - self._dyn_pl = 0x3F # 0x3F = dynamic_payloads enabled on all pipes - self._aa = 0x3F # 0x3F = auto_ack enabled on all pipes - # configure features for TX operations - # 5 = enable dynamic_payloads, disable custom ack payloads, and allow ask_no_ack command - self._features = 5 - self._channel = 76 - self._addr_len = 5 - - with self: # write to registers & power up - self.ce_pin.value = 0 - self._reg_write(CONFIG, self._config | 1) - time.sleep(0.000015) - self.flush_rx() - self._reg_write(CONFIG, self._config & 0xC) - time.sleep(0.000015) - self.flush_tx() - self.clear_status_flags() - - def __enter__(self): - self._reg_write(CONFIG, self._config & 0x7C) - self._reg_write(RF_SETUP, self._rf_setup) - self._reg_write(EN_RX, self._open_pipes) - self._reg_write(DYNPD, self._dyn_pl) - self._reg_write(EN_AA, self._aa) - self._reg_write(FEATURE, self._features) - self._reg_write(SETUP_RETR, self._setup_retr) - for i, address in enumerate(self._pipes): - if i < 2: - self._reg_write_bytes(RX_ADDR + i, address) - else: - self._reg_write(RX_ADDR + i, address) - self._reg_write(RX_PW + i, self._payload_widths[i]) - self._reg_write_bytes(TX_ADDR, self._tx_address) - self.address_length = self._addr_len - self.channel = self._channel - return self - - def __exit__(self, *exc): - self.power = 0 - return False - - # pylint: disable=no-member - def _reg_read(self, reg): - buf = bytearray(2) - with self._spi as spi: - time.sleep(0.005) - spi.readinto(buf, write_value=reg) - self._status = buf[0] - return buf[1] - - def _reg_read_bytes(self, reg, buf_len=5): - buf = bytearray(buf_len + 1) - with self._spi as spi: - time.sleep(0.005) - spi.readinto(buf, write_value=reg) - self._status = buf[0] - return buf[1:] - - def _reg_write_bytes(self, reg, out_buf): - out_buf = bytes([0x20 | reg]) + out_buf - in_buf = bytearray(len(out_buf)) - with self._spi as spi: - time.sleep(0.005) - spi.write_readinto(out_buf, in_buf) - self._status = in_buf[0] - - def _reg_write(self, reg, value=None): - if value is None: - out_buf = bytes([reg]) - else: - out_buf = bytes([0x20 | reg, value]) - in_buf = bytearray(len(out_buf)) - with self._spi as spi: - time.sleep(0.005) - spi.write_readinto(out_buf, in_buf) - self._status = in_buf[0] - # pylint: enable=no-member - - @property - def address_length(self): - """This `int` attribute specifies the length (in bytes) of addresses - to be used for RX/TX pipes.""" - return self._reg_read(SETUP_AW) + 2 - - @address_length.setter - def address_length(self, length): - if 3 <= length <= 5: - self._addr_len = int(length) - self._reg_write(SETUP_AW, length - 2) - else: - raise ValueError( - "address length can only be set in range [3,5] bytes") - - def open_tx_pipe(self, address): - """This function is used to open a data pipe for OTA (over the air) TX - transmissions.""" - if len(address) == self.address_length: - if self.auto_ack: - self._pipes[0] = address - self._reg_write_bytes(RX_ADDR, address) - self._open_pipes = self._open_pipes | 1 - self._reg_write(EN_RX, self._open_pipes) - self._payload_widths[0] = self.payload_length - self._reg_write(RX_PW, self.payload_length) - self._pipes[0] = address - self._tx_address = address - self._reg_write_bytes(TX_ADDR, address) - else: - raise ValueError("address must be a buffer protocol object with a byte length\nequal " - "to the address_length attribute (currently set to" - " {})".format(self.address_length)) - - def close_rx_pipe(self, pipe_number): - """This function is used to close a specific data pipe from OTA (over - the air) RX transmissions.""" - if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0,5]") - self._open_pipes = self._reg_read(EN_RX) - if self._open_pipes & (1 << pipe_number): - self._open_pipes = self._open_pipes & ~(1 << pipe_number) - self._reg_write(EN_RX, self._open_pipes) - - def open_rx_pipe(self, pipe_number, address): - """This function is used to open a specific data pipe for OTA (over - the air) RX transmissions.""" - if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0,5]") - if len(address) != self.address_length: - raise ValueError("address must be a buffer protocol object with a byte length\nequal " - "to the address_length attribute (currently set to " - "{})".format(self.address_length)) - if pipe_number < 2: - if not pipe_number: - self._pipe0_read_addr = address - self._pipes[pipe_number] = address - self._reg_write_bytes(RX_ADDR + pipe_number, address) - else: - self._pipes[pipe_number] = address[0] - self._reg_write(RX_ADDR + pipe_number, address[0]) - self._open_pipes = self._reg_read(EN_RX) - self._open_pipes = self._open_pipes | (1 << pipe_number) - self._reg_write(EN_RX, self._open_pipes) - self._reg_write(RX_PW + pipe_number, self.payload_length) - self._payload_widths[pipe_number] = self.payload_length - - @property - def listen(self): - """An attribute to represent the nRF24L01 primary role as a radio.""" - return self.power and bool(self._config & 1) - - @listen.setter - def listen(self, is_rx): - assert isinstance(is_rx, (bool, int)) - if self.listen != is_rx: - self._start_listening() - else: - self._stop_listening() - - def _start_listening(self): - if self.ce_pin.value: - self.ce_pin.value = 0 - if self._pipe0_read_addr is not None: - self._reg_write_bytes(RX_ADDR, self._pipe0_read_addr) - self._pipes[0] = self._pipe0_read_addr - self._config = self._config & 0xFC | 3 - self._reg_write(CONFIG, self._config) - time.sleep(0.00015) # mandatory wait to power up radio - self.flush_rx() - self.clear_status_flags(True, False, False) - self.ce_pin.value = 1 # mandatory pulse is > 130 µs - time.sleep(0.00013) - - def _stop_listening(self): - if self.ce_pin.value: - self.ce_pin.value = 0 - self._config = self._config & 0xFE - self._reg_write(CONFIG, self._config) - time.sleep(0.00016) - - def any(self): - """This function checks if the nRF24L01 has received any data at all, - and then reports the next available payload's length (in bytes)""" - return self._reg_read(0x60) # 0x60 = R_RX_PL_WID command - - def recv(self): - """This function is used to retrieve the next available payload""" - if not self.irq_dr: - return None - curr_pl_size = self.payload_length if not self.dynamic_payloads else self.any() - result = self._reg_read_bytes(0x61, curr_pl_size) # 0x61 = R_RX_PAYLOAD - self.clear_status_flags(True, False, False) - return result - - def send(self, buf, ask_no_ack=False, force_retry=0): - """This blocking function is used to transmit payload(s).""" - # ensure power down/standby-I for proper manipulation of PWR_UP & PRIM_RX bits in - # CONFIG register - self.ce_pin.value = 0 - self.flush_tx() # be sure there is space in the TX FIFO - if isinstance(buf, (list, tuple)): # writing a set of payloads - result = [] - for i, b in enumerate(buf): # check invalid payloads first - # this way when we raise a ValueError exception we don't leave the nRF24L01 in an - # unknown frozen state. - if not b or len(b) > 32: - raise ValueError("buf (item {} in the list/tuple) must be a" - " buffer protocol object with a byte length of\nat least 1 " - "and no greater than 32".format(i)) - for i, b in enumerate(buf): - # use recursion for each payload - result.append(self.send(b, ask_no_ack, force_retry)) - return result - if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with a byte length of" - "\nat least 1 and no greater than 32") - # using spec sheet calculations: - # timeout total = T_upload + 2 * stby2active + T_overAir + T_ack + T_irq + T_retry - # T_upload = payload length (in bits) / spi data rate (bits per second = - # baudrate / bits per byte) - # T_upload is finished before timeout begins - # T_download == T_upload, however RX devices spi settings must match TX's for - # accurate calc - # let 2 * stby2active (in µs) ~= (2 + (1 if getting ack else 0)) * 130 - # let T_ack = T_overAir as the payload size is the only distictive variable between - # the 2 - # T_overAir (in seconds) = ( 8 (bits/byte) * (1 byte preamble + address length + - # payload length + crc length) + 9 bit packet ID ) / RF data rate (in bits/sec) - # T_irq (in seconds) = (0.0000082 if self.data_rate == 1 else 0.000006) - # T_retry (in microseconds)= (arc * ard) - need_ack = self._setup_retr & 0x0f and not ask_no_ack - packet_data = 1 + self._addr_len + (max(0, ((self._config & 12) >> 2) - 1)) - bitrate = ((2000000 if self._rf_setup & 0x28 == 8 else 250000) - if self._rf_setup & 0x28 else 1000000) / 8 - t_ack = (((packet_data + 32) * 8 + 9) / bitrate) if need_ack else 0 # assumes 32-byte ACK - stby2active = (1 + (need_ack)) * 0.00013 - t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 - t_retry = (((self._setup_retr & 0xf0) >> 4) * 250 + 380) * \ - (self._setup_retr & 0x0f) / 1000000 - timeout = (((8 * (len(buf) + packet_data)) + 9) / - bitrate) + stby2active + t_irq + t_retry + t_ack - self.write(buf, ask_no_ack) # init using non-blocking helper - time.sleep(0.00001) # ensure CE pulse is >= 10 µs - # if pulse is stopped here, the nRF24L01 only handles the top level payload in the FIFO. - # we could hold CE HIGH to continue processing through the rest of the TX FIFO bound for - # the address passed to open_tx_pipe() - self.ce_pin.value = 0 # go to Standby-I power mode (power attribute still True) - self._wait_for_result(timeout) - if self._setup_retr & 0x0f and self.irq_df: - # if auto-retransmit is on and last attempt failed - retry = False - for _ in range(force_retry): - # resend() clears flags upon entering and exiting - retry = self.resend() - if retry is None or retry: - break # retry succeeded - result = retry - else: # if succeeded - if self.ack and self.irq_dr and not ask_no_ack: - # if ACK payload is waiting in RX FIFO - result = self.recv() # save ACK payload & clears RX flag - else: # if auto-ack is disabled - result = self.irq_ds # will always be True (in this case) - self.clear_status_flags(False) # only TX related IRQ flags - return result - - @property - def irq_dr(self): - """A `bool` that represents the "Data Ready" interrupted flag.""" - return bool(self._status & 0x40) - - @property - def irq_ds(self): - """A `bool` that represents the "Data Sent" interrupted flag.""" - return bool(self._status & 0x20) - - @property - def irq_df(self): - """A `bool` that represents the "Data Failed" interrupted flag.""" - return bool(self._status & 0x10) - - def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): - """This clears the interrupt flags in the status register.""" - # 0x07 = STATUS register - self._reg_write(0x07, (data_recv << 6) | ( - data_sent << 5) | (data_fail << 4)) - - def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): - """Sets the configuration of the nRF24L01's IRQ (interrupt) pin.""" - self._config = self._reg_read(CONFIG) - self._config = (self._config & 0x0F) | (not data_fail << 4) | (not data_sent << 5) | \ - (not data_recv << 6) - self._reg_write(CONFIG, self._config) - - def what_happened(self, dump_pipes=False): - """This debuggung function aggregates and outputs all status/condition - related information""" - watchdog = self._reg_read(8) # 8 == OBSERVE_TX register - print("Channel___________________{} ~ {} GHz".format( - self.channel, (self.channel + 2400) / 1000)) - print("RF Data Rate______________{} {}".format( - self.data_rate, "Mbps" if self.data_rate != 250 else "Kbps")) - print("RF Power Amplifier________{} dbm".format(self.pa_level)) - print("CRC bytes_________________{}".format(self.crc)) - print("Address length____________{} bytes".format(self.address_length)) - print("Payload lengths___________{} bytes".format(self.payload_length)) - print("Auto retry delay__________{} microseconds".format(self.ard)) - print("Auto retry attempts_______{} maximum".format(self.arc)) - print("Packets lost on current channel_____________________{}".format( - (watchdog & 0xF0) >> 4)) - print("Retry attempts made for last transmission___________{}".format(watchdog & 0x0F)) - print("IRQ - Data Ready______{} Data Ready___________{}".format( - '_True' if not bool(self._config & 0x40) else 'False', self.irq_dr)) - print("IRQ - Data Fail_______{} Data Failed__________{}".format( - '_True' if not bool(self._config & 0x20) else 'False', self.irq_df)) - print("IRQ - Data Sent_______{} Data Sent____________{}".format( - '_True' if not bool(self._config & 0x10) else 'False', self.irq_ds)) - print("TX FIFO full__________{} TX FIFO empty________{}".format( - '_True' if bool(self.tx_full) else 'False', bool(self.fifo(True, True)))) - print("RX FIFO full__________{} RX FIFO empty________{}".format( - '_True' if bool(self._fifo & 2) else 'False', bool(self._fifo & 1))) - print("Ask no ACK_________{} Custom ACK Payload___{}".format( - '_Allowed' if bool(self._features & 1) else 'Disabled', - 'Enabled' if self.ack else 'Disabled')) - print("Dynamic Payloads___{} Auto Acknowledgment__{}".format( - '_Enabled' if self.dynamic_payloads else 'Disabled', - 'Enabled' if self.auto_ack else 'Disabled')) - print("Primary Mode_____________{} Power Mode___________{}".format( - 'RX' if self.listen else 'TX', - ('Standby-II' if self.ce_pin.value else 'Standby-I') if self._config & 2 else 'Off')) - if dump_pipes: - print('TX address____________', self._tx_address) - for i, address in enumerate(self._pipes): - is_open = "( open )" if self._open_pipes & (1 << i) else "(closed)" - if i <= 1: # print full address - print("Pipe", i, is_open, "bound:", address) - else: # print unique byte + shared bytes = actual address used by radio - print("Pipe", i, is_open, "bound:", - bytes([self._pipes[i]]) + self._pipes[1][1:]) - if self._open_pipes & (1 << i): - print('\t\texpecting', self._payload_widths[i], 'byte static payloads') - - @property - def dynamic_payloads(self): - """This `bool` attribute controls the nRF24L01's dynamic payload - length feature.""" - return bool(self._dyn_pl and (self._features & 4)) - - @dynamic_payloads.setter - def dynamic_payloads(self, enable): - assert isinstance(enable, (bool, int)) - self._features = self._reg_read(FEATURE) - if self._features & 4 != enable: - self._features = (self._features & 3) | (enable << 2) - self._reg_write(FEATURE, self._features) - self._dyn_pl = 0x3F if enable else 0 # 0x3F = enable dynamic payloads all pipes - self._reg_write(DYNPD, self._dyn_pl) - - @property - def payload_length(self): - """This `int` attribute specifies the length (in bytes) of payload""" - return self._payload_length - - @payload_length.setter - def payload_length(self, length): - # max payload size is 32 bytes - if not length or length <= 32: - self._payload_length = length - else: - raise ValueError( - "{}: payload length can only be set in range [1,32] bytes".format(length)) - - @property - def arc(self): - """This `int` attribute specifies the nRF24L01's number of attempts - to re-transmit TX payload""" - self._setup_retr = self._reg_read(SETUP_RETR) - return self._setup_retr & 0x0f - - @arc.setter - def arc(self, count): - if 0 <= count <= 15: - if self.arc & 0x0F != count: - self._setup_retr = (self._setup_retr & 0xF0) | count - self._reg_write(SETUP_RETR, self._setup_retr) - else: - raise ValueError( - "automatic re-transmit count(/attempts) must in range [0,15]") - - @property - def ard(self): - """This `int` attribute specifies the nRF24L01's delay (in - microseconds) between attempts to automatically re-transmit the - TX payload""" - self._setup_retr = self._reg_read(SETUP_RETR) - return ((self._setup_retr & 0xf0) >> 4) * 250 + 250 - - @ard.setter - def ard(self, delta_t): - if 250 <= delta_t <= 4000 and delta_t % 250 == 0: - if self.ard != delta_t: - self._setup_retr = ( - int((delta_t - 250) / 250) << 4) | (self._setup_retr & 0x0F) - self._reg_write(SETUP_RETR, self._setup_retr) - else: - raise ValueError("automatic re-transmit delay can only be a multiple of 250 in range " - "[250,4000]") - - @property - def auto_ack(self): - """This `bool` attribute controls the nRF24L01's automatic - acknowledgment feature during the process of receiving""" - return self._aa - - @auto_ack.setter - def auto_ack(self, enable): - assert isinstance(enable, (bool, int)) - self._aa = 0x3F if enable else 0 # 0x3F = enable auto_ack on all pipes - self._reg_write(EN_AA, self._aa) - - @property - def ack(self): - """This `bool` attribute represents the status of the nRF24L01's - capability to use custom payloads as part of the automatic - acknowledgment (ACK) packet.""" - return bool((self._features & 2) and self.auto_ack and self.dynamic_payloads) - - @ack.setter - def ack(self, enable): - assert isinstance(enable, (bool, int)) - if self.ack != enable: - self.auto_ack = True - self._dyn_pl = 0x3F - self._reg_write(DYNPD, self._dyn_pl) - else: - self._features = self._reg_read(FEATURE) - self._features = (self._features & 5) | (6 if enable else 0) - self._reg_write(FEATURE, self._features) - - def load_ack(self, buf, pipe_number): - """This allows the MCU to specify a payload to be allocated into the - TX FIFO buffer for use on a specific data pipe.""" - if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0,5]") - if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with a byte length of" - "\nat least 1 and no greater than 32") - if not self.ack: - self.ack = True - if not self.tx_full: - self._reg_write_bytes(0xA8 | pipe_number, buf) # 0xA8 = W_ACK_PAYLOAD - return True - return False - - def read_ack(self): - """Allows user to read the automatic acknowledgement (ACK) payload (if - any) when nRF24L01 is in TX mode.""" - return self.recv() - - @property - def data_rate(self): - """This `int` attribute specifies the nRF24L01's frequency data rate - for OTA (over the air) transmissions.""" - self._rf_setup = self._reg_read(RF_SETUP) - return (2 if self._rf_setup & 0x28 == 8 else 250) if self._rf_setup & 0x28 else 1 - - @data_rate.setter - def data_rate(self, speed): - if speed in (1, 2, 250): - if self.data_rate != speed: - speed = 0 if speed == 1 else (8 if speed == 2 else 0x20) - self._rf_setup = self._rf_setup & 0xD7 | speed - self._reg_write(RF_SETUP, self._rf_setup) - else: - raise ValueError( - "data rate must be one of the following ([M,M,K]bps): 1, 2, 250") - - @property - def channel(self): - """This `int` attribute specifies the nRF24L01's frequency.""" - return self._reg_read(RF_CH) - - @channel.setter - def channel(self, channel): - if 0 <= channel <= 125: - self._channel = channel - self._reg_write(RF_CH, channel) - else: - raise ValueError("channel acn only be set in range [0,125]") - - @property - def crc(self): - """This `int` attribute specifies the nRF24L01's CRC (cyclic redundancy - checking) encoding scheme in terms of byte length.""" - self._config = self._reg_read(CONFIG) - return max(0, ((self._config & 12) >> 2) - 1) - - @crc.setter - def crc(self, length): - if 0 <= length <= 2: - if self.crc != length: - length = (length + 1) << 2 if length else 0 - self._config = self._config & 0x73 | length - self._reg_write(0, self._config) - else: - raise ValueError( - "CRC byte length must be an int equal to 0 (off), 1, or 2") - - @property - def power(self): - """This `bool` attribute controls the power state of the nRF24L01.""" - return bool(self._config & 2) - - @power.setter - def power(self, is_on): - assert isinstance(is_on, (bool, int)) - self._config = self._reg_read(CONFIG) - if self.power != is_on: - self._config = (self._config & 0x7d) | (is_on << 1) - self._reg_write(CONFIG, self._config) - # power up/down takes < 150 µs + 4 µs - time.sleep(0.00016) - - @property - def pa_level(self): - """This `int` attribute specifies the nRF24L01's power amplifier level - (in dBm).""" - self._rf_setup = self._reg_read(RF_SETUP) # refresh data - return (3 - ((self._rf_setup & RF_SETUP) >> 1)) * -6 - - @pa_level.setter - def pa_level(self, power): - if power in (-18, -12, -6, 0): - power = (3 - int(power / -6)) * 2 - self._rf_setup = (self._rf_setup & 0xF9) | power - self._reg_write(RF_SETUP, self._rf_setup) - else: - raise ValueError( - "power amplitude must be one of the following (dBm): -18, -12, -6, 0") - - @property - def rpd(self): - """This read-only attribute returns `True` if RPD (Received Power - Detector) is triggered or `False` if not triggered.""" - return bool(self._reg_read(0x09)) - - @property - def tx_full(self): - """An attribute to represent the nRF24L01's status flag signaling that - the TX FIFO buffer is full. (read-only)""" - return bool(self._status & 1) - - def update(self): - """This function is only used to get an updated status byte over SPI - from the nRF24L01""" - self._reg_write(0xFF) # 0xFF = non-operation command - - def resend(self): - """Use this function to maunally re-send the previous payload in the - top level (first out) of the TX FIFO buffer.""" - result = False - if not self.fifo(True, True): - self.clear_status_flags(False) - self._reg_write(0xE3) # 0xE3 == REUSE_TX_PL command - # timeout calc assumes 32 byte payload (& 32-byte ACK if needed) - pl_coef = 1 + bool(self._setup_retr & 0x0f) - pl_len = 1 + self._addr_len + ( - max(0, ((self._config & 12) >> 2) - 1)) - bitrate = ((2000000 if self._rf_setup & 0x28 == 8 else 250000) - if self._rf_setup & 0x28 else 1000000) / 8 - stby2active = (1 + pl_coef) * 0.00013 - t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 - t_retry = (((self._setup_retr & 0xf0) >> 4) * 250 + - 380) * (self._setup_retr & 0x0f) / 1000000 - timeout = pl_coef * (((8 * (32 + pl_len)) + 9) / bitrate) + \ - stby2active + t_irq + t_retry - self.ce_pin.value = 0 - self.ce_pin.value = 1 - time.sleep(0.00001) - self.ce_pin.value = 0 - self._wait_for_result(timeout) - result = self.irq_ds - if self.ack and self.irq_dr: # is there an ACK payload - result = self.recv() - self.clear_status_flags(False) - return result - - def write(self, buf, ask_no_ack=False): - """This non-blocking function (when used as alternative to `send()`) is meant for - asynchronous applications and can only handle one payload at a time as it is a - helper function to `send()`.""" - if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with a byte length of" - "\nat least 1 and no greater than 32") - self.clear_status_flags(False) - if self._config & 3 != 2: - # ensures tx mode & powered up - self._config = (self._reg_read(CONFIG) & 0x7c) | 2 - self._reg_write(CONFIG, self._config) - time.sleep(0.00016) # power up/down takes < 150 µs + 4 µs - # pad out or truncate data to fill payload_length if dynamic_payloads == False - if not self.dynamic_payloads: - if len(buf) < self.payload_length: - for _ in range(self.payload_length - len(buf)): - buf += b'\x00' - elif len(buf) > self.payload_length: - buf = buf[:self.payload_length] - # now upload the payload accordingly with appropriate command - if ask_no_ack: # payload doesn't want acknowledgment - if self._features & 1 == 0: - self._features = self._features & 0xFE | 1 # set EN_DYN_ACK flag high - self._reg_write(FEATURE, self._features) - # 0xA0 = W_TX_PAYLOAD; 0xB0 = W_TX_PAYLOAD_NO_ACK - self._reg_write_bytes(0xA0 | (ask_no_ack << 4), buf) - self.ce_pin.value = 1 - - def flush_rx(self): - """A helper function to flush the nRF24L01's internal RX FIFO buffer. (write-only)""" - self._reg_write(0xE2) - - def flush_tx(self): - """A helper function to flush the nRF24L01's internal TX FIFO buffer. (write-only)""" - self._reg_write(0xE1) - - def fifo(self, about_tx=False, check_empty=None): - """This provides some precision determining the status of the TX/RX FIFO buffers. - (read-only)""" - if (check_empty is None and isinstance(about_tx, (bool, int))) or \ - (isinstance(check_empty, (bool, int)) and isinstance(about_tx, (bool, int))): - self._fifo = self._reg_read(FIFO) # refresh the data - if check_empty is None: - return (self._fifo & (0x30 if about_tx else 0x03)) >> (4 * about_tx) - return bool(self._fifo & ((2 - check_empty) << (4 * about_tx))) - raise ValueError("Argument 1 ('about_tx') must always be a bool or int. Argument 2" - " ('check_empty'), if specified, must be a bool or int") - - def pipe(self): - """This function returns information about the data pipe that received the next - available payload in the RX FIFO buffer.""" - self.update() - result = (self._status & 0x0E) >> 1 # 0x0E==RX_P_NO - if result <= 5: - return result - return None - - def address(self, index=-1): - """Returns the current address set to a specified data pipe or the TX address. - (read-only)""" - if index > 5: - raise IndexError("index {} is out of bounds [0,5]".format(index)) - if index < 0: - return self._tx_address - if index <= 1: - return self._pipes[index] - return bytes(self._pipes[index]) + self._pipes[1][1:] - - def _wait_for_result(self, timeout): - start = time.monotonic() - while not self.irq_ds and not self.irq_df and (time.monotonic() - start) < timeout: - self.update() + """A driver class for the nRF24L01(+) transceiver radios.""" + def __init__(self, spi, csn, ce): + self._payload_length = 32 # inits internal attribute + self.payload_length = 32 + # last address assigned to pipe0 for reading. init to None + self._fifo = 0 + self._status = 0 + # init shadow copy of RX addresses for all pipes + self._pipes = [b'\xE7' * 5, b'\xC2' * 5, 0, 0, 0, 0] + self._payload_widths = [32] * 6 # payload_length specific to each pipe + # shadow copy of last RX_ADDR written to pipe 0 + self._pipe0_read_addr = None # needed as open_tx_pipe() appropriates pipe 0 for ACK + # init the _open_pipes attribute (reflects only RX state on each pipe) + self._open_pipes = 0 # 0 = all pipes closed + self._spi = SPIDevice(spi, chip_select=csn, baudrate=1250000) + self.ce_pin = ce + self.ce_pin.switch_to_output(value=False) + # configure the CONFIG register: + # 0x0E = IRQs are all enabled, CRC is enabled with 2 bytes, and power up in TX mode + self._config = 0x0E + self._reg_write(CONFIG, self._config) # dump to register + # check for device presence by verifying nRF24L01 is in TX + standby-I mode + if self._reg_read(CONFIG) & 3 == 2: # if in TX + standby-I mode + self.power = False # power down + else: # hardware presence check NOT passed + print(bin(self._reg_read(CONFIG))) + raise RuntimeError("nRF24L01 Hardware not responding") + for i in range(6): + if i < 2: + self._pipes[i] = self._reg_read_bytes(RX_ADDR + i) + else: + self._pipes[i] = self._reg_read(RX_ADDR + i) + # shadow copy of the TX_ADDR + self._tx_address = self._reg_read_bytes(TX_ADDR) + # configure the SETUP_RETR register + self._setup_retr = 0x53 #ard = 1500; arc = 3 + # configure the RF_SETUP register + self._rf_setup = 0x06 # 1 Mbps data_rate, and 0 dbm pa_level + # configure dynamic_payloads & auto_ack for RX operations + self._dyn_pl = 0x3F # 0x3F = dynamic_payloads enabled on all pipes + self._aa = 0x3F # 0x3F = auto_ack enabled on all pipes + # configure features for TX operations + # 5 = enable dynamic_payloads, disable custom ack payloads, and allow ask_no_ack command + self._features = 5 + self._channel = 76 + self._addr_len = 5 + + with self: # write to registers & power up + self.ce_pin.value = 0 + self._reg_write(CONFIG, self._config | 1) + time.sleep(0.000015) + self.flush_rx() + self._reg_write(CONFIG, self._config & 0xC) + time.sleep(0.000015) + self.flush_tx() + self.clear_status_flags() + + def __enter__(self): + self._reg_write(CONFIG, self._config & 0x7C) + self._reg_write(RF_SETUP, self._rf_setup) + self._reg_write(EN_RX, self._open_pipes) + self._reg_write(DYNPD, self._dyn_pl) + self._reg_write(EN_AA, self._aa) + self._reg_write(FEATURE, self._features) + self._reg_write(SETUP_RETR, self._setup_retr) + for i, address in enumerate(self._pipes): + if i < 2: + self._reg_write_bytes(RX_ADDR + i, address) + else: + self._reg_write(RX_ADDR + i, address) + self._reg_write(RX_PW + i, self._payload_widths[i]) + self._reg_write_bytes(TX_ADDR, self._tx_address) + self.address_length = self._addr_len + self.channel = self._channel + return self + + def __exit__(self, *exc): + self.power = 0 + return False + + # pylint: disable=no-member + def _reg_read(self, reg): + buf = bytearray(2) + with self._spi as spi: + time.sleep(0.005) + spi.readinto(buf, write_value=reg) + self._status = buf[0] + return buf[1] + + def _reg_read_bytes(self, reg, buf_len=5): + buf = bytearray(buf_len + 1) + with self._spi as spi: + time.sleep(0.005) + spi.readinto(buf, write_value=reg) + self._status = buf[0] + return buf[1:] + + def _reg_write_bytes(self, reg, out_buf): + out_buf = bytes([0x20 | reg]) + out_buf + in_buf = bytearray(len(out_buf)) + with self._spi as spi: + time.sleep(0.005) + spi.write_readinto(out_buf, in_buf) + self._status = in_buf[0] + + def _reg_write(self, reg, value=None): + if value is None: + out_buf = bytes([reg]) + else: + out_buf = bytes([0x20 | reg, value]) + in_buf = bytearray(len(out_buf)) + with self._spi as spi: + time.sleep(0.005) + spi.write_readinto(out_buf, in_buf) + self._status = in_buf[0] + # pylint: enable=no-member + + @property + def address_length(self): + """This `int` attribute specifies the length (in bytes) of addresses + to be used for RX/TX pipes.""" + return self._reg_read(SETUP_AW) + 2 + + @address_length.setter + def address_length(self, length): + if 3 <= length <= 5: + self._addr_len = int(length) + self._reg_write(SETUP_AW, length - 2) + else: + raise ValueError( + "address length can only be set in range [3,5] bytes") + + def open_tx_pipe(self, address): + """This function is used to open a data pipe for OTA (over the air) TX + transmissions.""" + if len(address) == self.address_length: + if self.auto_ack: + self._pipes[0] = address + self._reg_write_bytes(RX_ADDR, address) + self._open_pipes = self._open_pipes | 1 + self._reg_write(EN_RX, self._open_pipes) + self._payload_widths[0] = self.payload_length + self._reg_write(RX_PW, self.payload_length) + self._pipes[0] = address + self._tx_address = address + self._reg_write_bytes(TX_ADDR, address) + else: + raise ValueError("address must be a buffer protocol object with a byte length\nequal " + "to the address_length attribute (currently set to" + " {})".format(self.address_length)) + + def close_rx_pipe(self, pipe_number): + """This function is used to close a specific data pipe from OTA (over + the air) RX transmissions.""" + if pipe_number < 0 or pipe_number > 5: + raise ValueError("pipe number must be in range [0,5]") + self._open_pipes = self._reg_read(EN_RX) + if self._open_pipes & (1 << pipe_number): + self._open_pipes = self._open_pipes & ~(1 << pipe_number) + self._reg_write(EN_RX, self._open_pipes) + + def open_rx_pipe(self, pipe_number, address): + """This function is used to open a specific data pipe for OTA (over + the air) RX transmissions.""" + if pipe_number < 0 or pipe_number > 5: + raise ValueError("pipe number must be in range [0,5]") + if len(address) != self.address_length: + raise ValueError("address must be a buffer protocol object with a byte length\nequal " + "to the address_length attribute (currently set to " + "{})".format(self.address_length)) + if pipe_number < 2: + if not pipe_number: + self._pipe0_read_addr = address + self._pipes[pipe_number] = address + self._reg_write_bytes(RX_ADDR + pipe_number, address) + else: + self._pipes[pipe_number] = address[0] + self._reg_write(RX_ADDR + pipe_number, address[0]) + self._open_pipes = self._reg_read(EN_RX) + self._open_pipes = self._open_pipes | (1 << pipe_number) + self._reg_write(EN_RX, self._open_pipes) + self._reg_write(RX_PW + pipe_number, self.payload_length) + self._payload_widths[pipe_number] = self.payload_length + + @property + def listen(self): + """An attribute to represent the nRF24L01 primary role as a radio.""" + return self.power and bool(self._config & 1) + + @listen.setter + def listen(self, is_rx): + assert isinstance(is_rx, (bool, int)) + if self.listen != is_rx: + self._start_listening() + else: + self._stop_listening() + + def _start_listening(self): + if self.ce_pin.value: + self.ce_pin.value = 0 + if self._pipe0_read_addr is not None: + self._reg_write_bytes(RX_ADDR, self._pipe0_read_addr) + self._pipes[0] = self._pipe0_read_addr + self._config = self._config & 0xFC | 3 + self._reg_write(CONFIG, self._config) + time.sleep(0.00015) # mandatory wait to power up radio + self.flush_rx() + self.clear_status_flags(True, False, False) + self.ce_pin.value = 1 # mandatory pulse is > 130 µs + time.sleep(0.00013) + + def _stop_listening(self): + if self.ce_pin.value: + self.ce_pin.value = 0 + self._config = self._config & 0xFE + self._reg_write(CONFIG, self._config) + time.sleep(0.00016) + + def any(self): + """This function checks if the nRF24L01 has received any data at all, + and then reports the next available payload's length (in bytes)""" + return self._reg_read(0x60) # 0x60 = R_RX_PL_WID command + + def recv(self): + """This function is used to retrieve the next available payload""" + if not self.irq_dr: + return None + curr_pl_size = self.payload_length if not self.dynamic_payloads else self.any() + result = self._reg_read_bytes(0x61, curr_pl_size) # 0x61 = R_RX_PAYLOAD + self.clear_status_flags(True, False, False) + return result + + def send(self, buf, ask_no_ack=False, force_retry=0): + """This blocking function is used to transmit payload(s).""" + # ensure power down/standby-I for proper manipulation of PWR_UP & PRIM_RX bits in + # CONFIG register + self.ce_pin.value = 0 + self.flush_tx() # be sure there is space in the TX FIFO + if isinstance(buf, (list, tuple)): # writing a set of payloads + result = [] + for i, b in enumerate(buf): # check invalid payloads first + # this way when we raise a ValueError exception we don't leave the nRF24L01 in an + # unknown frozen state. + if not b or len(b) > 32: + raise ValueError("buf (item {} in the list/tuple) must be a" + " buffer protocol object with a byte length of\nat least 1 " + "and no greater than 32".format(i)) + for i, b in enumerate(buf): + # use recursion for each payload + result.append(self.send(b, ask_no_ack, force_retry)) + return result + if not buf or len(buf) > 32: + raise ValueError("buf must be a buffer protocol object with a byte length of" + "\nat least 1 and no greater than 32") + # using spec sheet calculations: + # timeout total = T_upload + 2 * stby2active + T_overAir + T_ack + T_irq + T_retry + # T_upload = payload length (in bits) / spi data rate (bits per second = + # baudrate / bits per byte) + # T_upload is finished before timeout begins + # T_download == T_upload, however RX devices spi settings must match TX's for + # accurate calc + # let 2 * stby2active (in µs) ~= (2 + (1 if getting ack else 0)) * 130 + # let T_ack = T_overAir as the payload size is the only distictive variable between + # the 2 + # T_overAir (in seconds) = ( 8 (bits/byte) * (1 byte preamble + address length + + # payload length + crc length) + 9 bit packet ID ) / RF data rate (in bits/sec) + # T_irq (in seconds) = (0.0000082 if self.data_rate == 1 else 0.000006) + # T_retry (in microseconds)= (arc * ard) + need_ack = self._setup_retr & 0x0f and not ask_no_ack + packet_data = 1 + self._addr_len + (max(0, ((self._config & 12) >> 2) - 1)) + bitrate = ((2000000 if self._rf_setup & 0x28 == 8 else 250000) + if self._rf_setup & 0x28 else 1000000) / 8 + t_ack = (((packet_data + 32) * 8 + 9) / bitrate) if need_ack else 0 # assumes 32-byte ACK + stby2active = (1 + (need_ack)) * 0.00013 + t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 + t_retry = (((self._setup_retr & 0xf0) >> 4) * 250 + 380) * \ + (self._setup_retr & 0x0f) / 1000000 + timeout = (((8 * (len(buf) + packet_data)) + 9) / + bitrate) + stby2active + t_irq + t_retry + t_ack + self.write(buf, ask_no_ack) # init using non-blocking helper + time.sleep(0.00001) # ensure CE pulse is >= 10 µs + # if pulse is stopped here, the nRF24L01 only handles the top level payload in the FIFO. + # we could hold CE HIGH to continue processing through the rest of the TX FIFO bound for + # the address passed to open_tx_pipe() + self.ce_pin.value = 0 # go to Standby-I power mode (power attribute still True) + self._wait_for_result(timeout) + if self._setup_retr & 0x0f and self.irq_df: + # if auto-retransmit is on and last attempt failed + retry = False + for _ in range(force_retry): + # resend() clears flags upon entering and exiting + retry = self.resend() + if retry is None or retry: + break # retry succeeded + result = retry + else: # if succeeded + if self.ack and self.irq_dr and not ask_no_ack: + # if ACK payload is waiting in RX FIFO + result = self.recv() # save ACK payload & clears RX flag + else: # if auto-ack is disabled + result = self.irq_ds # will always be True (in this case) + self.clear_status_flags(False) # only TX related IRQ flags + return result + + @property + def irq_dr(self): + """A `bool` that represents the "Data Ready" interrupted flag.""" + return bool(self._status & 0x40) + + @property + def irq_ds(self): + """A `bool` that represents the "Data Sent" interrupted flag.""" + return bool(self._status & 0x20) + + @property + def irq_df(self): + """A `bool` that represents the "Data Failed" interrupted flag.""" + return bool(self._status & 0x10) + + def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): + """This clears the interrupt flags in the status register.""" + # 0x07 = STATUS register + self._reg_write(0x07, (data_recv << 6) | ( + data_sent << 5) | (data_fail << 4)) + + def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): + """Sets the configuration of the nRF24L01's IRQ (interrupt) pin.""" + self._config = self._reg_read(CONFIG) + self._config = (self._config & 0x0F) | (not data_fail << 4) | (not data_sent << 5) | \ + (not data_recv << 6) + self._reg_write(CONFIG, self._config) + + def what_happened(self, dump_pipes=False): + """This debuggung function aggregates and outputs all status/condition + related information""" + watchdog = self._reg_read(8) # 8 == OBSERVE_TX register + print("Channel___________________{} ~ {} GHz".format( + self.channel, (self.channel + 2400) / 1000)) + print("RF Data Rate______________{} {}".format( + self.data_rate, "Mbps" if self.data_rate != 250 else "Kbps")) + print("RF Power Amplifier________{} dbm".format(self.pa_level)) + print("CRC bytes_________________{}".format(self.crc)) + print("Address length____________{} bytes".format(self.address_length)) + print("Payload lengths___________{} bytes".format(self.payload_length)) + print("Auto retry delay__________{} microseconds".format(self.ard)) + print("Auto retry attempts_______{} maximum".format(self.arc)) + print("Packets lost on current channel_____________________{}".format( + (watchdog & 0xF0) >> 4)) + print("Retry attempts made for last transmission___________{}".format(watchdog & 0x0F)) + print("IRQ - Data Ready______{} Data Ready___________{}".format( + '_True' if not bool(self._config & 0x40) else 'False', self.irq_dr)) + print("IRQ - Data Fail_______{} Data Failed__________{}".format( + '_True' if not bool(self._config & 0x20) else 'False', self.irq_df)) + print("IRQ - Data Sent_______{} Data Sent____________{}".format( + '_True' if not bool(self._config & 0x10) else 'False', self.irq_ds)) + print("TX FIFO full__________{} TX FIFO empty________{}".format( + '_True' if bool(self.tx_full) else 'False', bool(self.fifo(True, True)))) + print("RX FIFO full__________{} RX FIFO empty________{}".format( + '_True' if bool(self._fifo & 2) else 'False', bool(self._fifo & 1))) + print("Ask no ACK_________{} Custom ACK Payload___{}".format( + '_Allowed' if bool(self._features & 1) else 'Disabled', + 'Enabled' if self.ack else 'Disabled')) + print("Dynamic Payloads___{} Auto Acknowledgment__{}".format( + '_Enabled' if self.dynamic_payloads else 'Disabled', + 'Enabled' if self.auto_ack else 'Disabled')) + print("Primary Mode_____________{} Power Mode___________{}".format( + 'RX' if self.listen else 'TX', + ('Standby-II' if self.ce_pin.value else 'Standby-I') if self._config & 2 else 'Off')) + if dump_pipes: + print('TX address____________', self._tx_address) + for i, address in enumerate(self._pipes): + is_open = "( open )" if self._open_pipes & (1 << i) else "(closed)" + if i <= 1: # print full address + print("Pipe", i, is_open, "bound:", address) + else: # print unique byte + shared bytes = actual address used by radio + print("Pipe", i, is_open, "bound:", + bytes([self._pipes[i]]) + self._pipes[1][1:]) + if self._open_pipes & (1 << i): + print('\t\texpecting', self._payload_widths[i], 'byte static payloads') + + @property + def dynamic_payloads(self): + """This `bool` attribute controls the nRF24L01's dynamic payload + length feature.""" + return bool(self._dyn_pl and (self._features & 4)) + + @dynamic_payloads.setter + def dynamic_payloads(self, enable): + assert isinstance(enable, (bool, int)) + self._features = self._reg_read(FEATURE) + if self._features & 4 != enable: + self._features = (self._features & 3) | (enable << 2) + self._reg_write(FEATURE, self._features) + self._dyn_pl = 0x3F if enable else 0 # 0x3F = enable dynamic payloads all pipes + self._reg_write(DYNPD, self._dyn_pl) + + @property + def payload_length(self): + """This `int` attribute specifies the length (in bytes) of payload""" + return self._payload_length + + @payload_length.setter + def payload_length(self, length): + # max payload size is 32 bytes + if not length or length <= 32: + self._payload_length = length + else: + raise ValueError( + "{}: payload length can only be set in range [1,32] bytes".format(length)) + + @property + def arc(self): + """This `int` attribute specifies the nRF24L01's number of attempts + to re-transmit TX payload""" + self._setup_retr = self._reg_read(SETUP_RETR) + return self._setup_retr & 0x0f + + @arc.setter + def arc(self, count): + if 0 <= count <= 15: + if self.arc & 0x0F != count: + self._setup_retr = (self._setup_retr & 0xF0) | count + self._reg_write(SETUP_RETR, self._setup_retr) + else: + raise ValueError( + "automatic re-transmit count(/attempts) must in range [0,15]") + + @property + def ard(self): + """This `int` attribute specifies the nRF24L01's delay (in + microseconds) between attempts to automatically re-transmit the + TX payload""" + self._setup_retr = self._reg_read(SETUP_RETR) + return ((self._setup_retr & 0xf0) >> 4) * 250 + 250 + + @ard.setter + def ard(self, delta_t): + if 250 <= delta_t <= 4000 and delta_t % 250 == 0: + if self.ard != delta_t: + self._setup_retr = ( + int((delta_t - 250) / 250) << 4) | (self._setup_retr & 0x0F) + self._reg_write(SETUP_RETR, self._setup_retr) + else: + raise ValueError("automatic re-transmit delay can only be a multiple of 250 in range " + "[250,4000]") + + @property + def auto_ack(self): + """This `bool` attribute controls the nRF24L01's automatic + acknowledgment feature during the process of receiving""" + return self._aa + + @auto_ack.setter + def auto_ack(self, enable): + assert isinstance(enable, (bool, int)) + self._aa = 0x3F if enable else 0 # 0x3F = enable auto_ack on all pipes + self._reg_write(EN_AA, self._aa) + + @property + def ack(self): + """This `bool` attribute represents the status of the nRF24L01's + capability to use custom payloads as part of the automatic + acknowledgment (ACK) packet.""" + return bool((self._features & 2) and self.auto_ack and self.dynamic_payloads) + + @ack.setter + def ack(self, enable): + assert isinstance(enable, (bool, int)) + if self.ack != enable: + self.auto_ack = True + self._dyn_pl = 0x3F + self._reg_write(DYNPD, self._dyn_pl) + else: + self._features = self._reg_read(FEATURE) + self._features = (self._features & 5) | (6 if enable else 0) + self._reg_write(FEATURE, self._features) + + def load_ack(self, buf, pipe_number): + """This allows the MCU to specify a payload to be allocated into the + TX FIFO buffer for use on a specific data pipe.""" + if pipe_number < 0 or pipe_number > 5: + raise ValueError("pipe number must be in range [0,5]") + if not buf or len(buf) > 32: + raise ValueError("buf must be a buffer protocol object with a byte length of" + "\nat least 1 and no greater than 32") + if not self.ack: + self.ack = True + if not self.tx_full: + self._reg_write_bytes(0xA8 | pipe_number, buf) # 0xA8 = W_ACK_PAYLOAD + return True + return False + + def read_ack(self): + """Allows user to read the automatic acknowledgement (ACK) payload (if + any) when nRF24L01 is in TX mode.""" + return self.recv() + + @property + def data_rate(self): + """This `int` attribute specifies the nRF24L01's frequency data rate + for OTA (over the air) transmissions.""" + self._rf_setup = self._reg_read(RF_SETUP) + return (2 if self._rf_setup & 0x28 == 8 else 250) if self._rf_setup & 0x28 else 1 + + @data_rate.setter + def data_rate(self, speed): + if speed in (1, 2, 250): + if self.data_rate != speed: + speed = 0 if speed == 1 else (8 if speed == 2 else 0x20) + self._rf_setup = self._rf_setup & 0xD7 | speed + self._reg_write(RF_SETUP, self._rf_setup) + else: + raise ValueError( + "data rate must be one of the following ([M,M,K]bps): 1, 2, 250") + + @property + def channel(self): + """This `int` attribute specifies the nRF24L01's frequency.""" + return self._reg_read(RF_CH) + + @channel.setter + def channel(self, channel): + if 0 <= channel <= 125: + self._channel = channel + self._reg_write(RF_CH, channel) + else: + raise ValueError("channel acn only be set in range [0,125]") + + @property + def crc(self): + """This `int` attribute specifies the nRF24L01's CRC (cyclic redundancy + checking) encoding scheme in terms of byte length.""" + self._config = self._reg_read(CONFIG) + return max(0, ((self._config & 12) >> 2) - 1) + + @crc.setter + def crc(self, length): + if 0 <= length <= 2: + if self.crc != length: + length = (length + 1) << 2 if length else 0 + self._config = self._config & 0x73 | length + self._reg_write(0, self._config) + else: + raise ValueError( + "CRC byte length must be an int equal to 0 (off), 1, or 2") + + @property + def power(self): + """This `bool` attribute controls the power state of the nRF24L01.""" + return bool(self._config & 2) + + @power.setter + def power(self, is_on): + assert isinstance(is_on, (bool, int)) + self._config = self._reg_read(CONFIG) + if self.power != is_on: + self._config = (self._config & 0x7d) | (is_on << 1) + self._reg_write(CONFIG, self._config) + # power up/down takes < 150 µs + 4 µs + time.sleep(0.00016) + + @property + def pa_level(self): + """This `int` attribute specifies the nRF24L01's power amplifier level + (in dBm).""" + self._rf_setup = self._reg_read(RF_SETUP) # refresh data + return (3 - ((self._rf_setup & RF_SETUP) >> 1)) * -6 + + @pa_level.setter + def pa_level(self, power): + if power in (-18, -12, -6, 0): + power = (3 - int(power / -6)) * 2 + self._rf_setup = (self._rf_setup & 0xF9) | power + self._reg_write(RF_SETUP, self._rf_setup) + else: + raise ValueError( + "power amplitude must be one of the following (dBm): -18, -12, -6, 0") + + @property + def rpd(self): + """This read-only attribute returns `True` if RPD (Received Power + Detector) is triggered or `False` if not triggered.""" + return bool(self._reg_read(0x09)) + + @property + def tx_full(self): + """An attribute to represent the nRF24L01's status flag signaling that + the TX FIFO buffer is full. (read-only)""" + return bool(self._status & 1) + + def update(self): + """This function is only used to get an updated status byte over SPI + from the nRF24L01""" + self._reg_write(0xFF) # 0xFF = non-operation command + + def resend(self): + """Use this function to maunally re-send the previous payload in the + top level (first out) of the TX FIFO buffer.""" + result = False + if not self.fifo(True, True): + self.clear_status_flags(False) + self._reg_write(0xE3) # 0xE3 == REUSE_TX_PL command + # timeout calc assumes 32 byte payload (& 32-byte ACK if needed) + pl_coef = 1 + bool(self._setup_retr & 0x0f) + pl_len = 1 + self._addr_len + ( + max(0, ((self._config & 12) >> 2) - 1)) + bitrate = ((2000000 if self._rf_setup & 0x28 == 8 else 250000) + if self._rf_setup & 0x28 else 1000000) / 8 + stby2active = (1 + pl_coef) * 0.00013 + t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 + t_retry = (((self._setup_retr & 0xf0) >> 4) * 250 + + 380) * (self._setup_retr & 0x0f) / 1000000 + timeout = pl_coef * (((8 * (32 + pl_len)) + 9) / bitrate) + \ + stby2active + t_irq + t_retry + self.ce_pin.value = 0 + self.ce_pin.value = 1 + time.sleep(0.00001) + self.ce_pin.value = 0 + self._wait_for_result(timeout) + result = self.irq_ds + if self.ack and self.irq_dr: # is there an ACK payload + result = self.recv() + self.clear_status_flags(False) + return result + + def write(self, buf, ask_no_ack=False): + """This non-blocking function (when used as alternative to `send()`) is meant for + asynchronous applications and can only handle one payload at a time as it is a + helper function to `send()`.""" + if not buf or len(buf) > 32: + raise ValueError("buf must be a buffer protocol object with a byte length of" + "\nat least 1 and no greater than 32") + self.clear_status_flags(False) + if self._config & 3 != 2: + # ensures tx mode & powered up + self._config = (self._reg_read(CONFIG) & 0x7c) | 2 + self._reg_write(CONFIG, self._config) + time.sleep(0.00016) # power up/down takes < 150 µs + 4 µs + # pad out or truncate data to fill payload_length if dynamic_payloads == False + if not self.dynamic_payloads: + if len(buf) < self.payload_length: + for _ in range(self.payload_length - len(buf)): + buf += b'\x00' + elif len(buf) > self.payload_length: + buf = buf[:self.payload_length] + # now upload the payload accordingly with appropriate command + if ask_no_ack: # payload doesn't want acknowledgment + if self._features & 1 == 0: + self._features = self._features & 0xFE | 1 # set EN_DYN_ACK flag high + self._reg_write(FEATURE, self._features) + # 0xA0 = W_TX_PAYLOAD; 0xB0 = W_TX_PAYLOAD_NO_ACK + self._reg_write_bytes(0xA0 | (ask_no_ack << 4), buf) + self.ce_pin.value = 1 + + def flush_rx(self): + """A helper function to flush the nRF24L01's internal RX FIFO buffer. (write-only)""" + self._reg_write(0xE2) + + def flush_tx(self): + """A helper function to flush the nRF24L01's internal TX FIFO buffer. (write-only)""" + self._reg_write(0xE1) + + def fifo(self, about_tx=False, check_empty=None): + """This provides some precision determining the status of the TX/RX FIFO buffers. + (read-only)""" + if (check_empty is None and isinstance(about_tx, (bool, int))) or \ + (isinstance(check_empty, (bool, int)) and isinstance(about_tx, (bool, int))): + self._fifo = self._reg_read(FIFO) # refresh the data + if check_empty is None: + return (self._fifo & (0x30 if about_tx else 0x03)) >> (4 * about_tx) + return bool(self._fifo & ((2 - check_empty) << (4 * about_tx))) + raise ValueError("Argument 1 ('about_tx') must always be a bool or int. Argument 2" + " ('check_empty'), if specified, must be a bool or int") + + def pipe(self): + """This function returns information about the data pipe that received the next + available payload in the RX FIFO buffer.""" + self.update() + result = (self._status & 0x0E) >> 1 # 0x0E==RX_P_NO + if result <= 5: + return result + return None + + def address(self, index=-1): + """Returns the current address set to a specified data pipe or the TX address. + (read-only)""" + if index > 5: + raise IndexError("index {} is out of bounds [0,5]".format(index)) + if index < 0: + return self._tx_address + if index <= 1: + return self._pipes[index] + return bytes(self._pipes[index]) + self._pipes[1][1:] + + def _wait_for_result(self, timeout): + start = time.monotonic() + while not self.irq_ds and not self.irq_df and (time.monotonic() - start) < timeout: + self.update() From 90356f76472c3c87b504ea77b74a9b310ee469ea Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Tue, 24 Mar 2020 01:54:29 -0700 Subject: [PATCH 013/127] rf24_m0.py works on trinket and feather express tested using a compiled mpy file (for circuitpython v5) --- circuitpython_nrf24l01/rf24.py | 1396 ++++++++++++++--------------- circuitpython_nrf24l01/rf24_m0.py | 493 ++++++++++ 2 files changed, 1191 insertions(+), 698 deletions(-) create mode 100644 circuitpython_nrf24l01/rf24_m0.py diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 3adac56..cf9ff0d 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -47,701 +47,701 @@ # NOTE expanded documentation lives in api.rst to save space on M0 class RF24: - """A driver class for the nRF24L01(+) transceiver radios.""" - def __init__(self, spi, csn, ce): - self._payload_length = 32 # inits internal attribute - self.payload_length = 32 - # last address assigned to pipe0 for reading. init to None - self._fifo = 0 - self._status = 0 - # init shadow copy of RX addresses for all pipes - self._pipes = [b'\xE7' * 5, b'\xC2' * 5, 0, 0, 0, 0] - self._payload_widths = [32] * 6 # payload_length specific to each pipe - # shadow copy of last RX_ADDR written to pipe 0 - self._pipe0_read_addr = None # needed as open_tx_pipe() appropriates pipe 0 for ACK - # init the _open_pipes attribute (reflects only RX state on each pipe) - self._open_pipes = 0 # 0 = all pipes closed - self._spi = SPIDevice(spi, chip_select=csn, baudrate=1250000) - self.ce_pin = ce - self.ce_pin.switch_to_output(value=False) - # configure the CONFIG register: - # 0x0E = IRQs are all enabled, CRC is enabled with 2 bytes, and power up in TX mode - self._config = 0x0E - self._reg_write(CONFIG, self._config) # dump to register - # check for device presence by verifying nRF24L01 is in TX + standby-I mode - if self._reg_read(CONFIG) & 3 == 2: # if in TX + standby-I mode - self.power = False # power down - else: # hardware presence check NOT passed - print(bin(self._reg_read(CONFIG))) - raise RuntimeError("nRF24L01 Hardware not responding") - for i in range(6): - if i < 2: - self._pipes[i] = self._reg_read_bytes(RX_ADDR + i) - else: - self._pipes[i] = self._reg_read(RX_ADDR + i) - # shadow copy of the TX_ADDR - self._tx_address = self._reg_read_bytes(TX_ADDR) - # configure the SETUP_RETR register - self._setup_retr = 0x53 #ard = 1500; arc = 3 - # configure the RF_SETUP register - self._rf_setup = 0x06 # 1 Mbps data_rate, and 0 dbm pa_level - # configure dynamic_payloads & auto_ack for RX operations - self._dyn_pl = 0x3F # 0x3F = dynamic_payloads enabled on all pipes - self._aa = 0x3F # 0x3F = auto_ack enabled on all pipes - # configure features for TX operations - # 5 = enable dynamic_payloads, disable custom ack payloads, and allow ask_no_ack command - self._features = 5 - self._channel = 76 - self._addr_len = 5 - - with self: # write to registers & power up - self.ce_pin.value = 0 - self._reg_write(CONFIG, self._config | 1) - time.sleep(0.000015) - self.flush_rx() - self._reg_write(CONFIG, self._config & 0xC) - time.sleep(0.000015) - self.flush_tx() - self.clear_status_flags() - - def __enter__(self): - self._reg_write(CONFIG, self._config & 0x7C) - self._reg_write(RF_SETUP, self._rf_setup) - self._reg_write(EN_RX, self._open_pipes) - self._reg_write(DYNPD, self._dyn_pl) - self._reg_write(EN_AA, self._aa) - self._reg_write(FEATURE, self._features) - self._reg_write(SETUP_RETR, self._setup_retr) - for i, address in enumerate(self._pipes): - if i < 2: - self._reg_write_bytes(RX_ADDR + i, address) - else: - self._reg_write(RX_ADDR + i, address) - self._reg_write(RX_PW + i, self._payload_widths[i]) - self._reg_write_bytes(TX_ADDR, self._tx_address) - self.address_length = self._addr_len - self.channel = self._channel - return self - - def __exit__(self, *exc): - self.power = 0 - return False - - # pylint: disable=no-member - def _reg_read(self, reg): - buf = bytearray(2) - with self._spi as spi: - time.sleep(0.005) - spi.readinto(buf, write_value=reg) - self._status = buf[0] - return buf[1] - - def _reg_read_bytes(self, reg, buf_len=5): - buf = bytearray(buf_len + 1) - with self._spi as spi: - time.sleep(0.005) - spi.readinto(buf, write_value=reg) - self._status = buf[0] - return buf[1:] - - def _reg_write_bytes(self, reg, out_buf): - out_buf = bytes([0x20 | reg]) + out_buf - in_buf = bytearray(len(out_buf)) - with self._spi as spi: - time.sleep(0.005) - spi.write_readinto(out_buf, in_buf) - self._status = in_buf[0] - - def _reg_write(self, reg, value=None): - if value is None: - out_buf = bytes([reg]) - else: - out_buf = bytes([0x20 | reg, value]) - in_buf = bytearray(len(out_buf)) - with self._spi as spi: - time.sleep(0.005) - spi.write_readinto(out_buf, in_buf) - self._status = in_buf[0] - # pylint: enable=no-member - - @property - def address_length(self): - """This `int` attribute specifies the length (in bytes) of addresses - to be used for RX/TX pipes.""" - return self._reg_read(SETUP_AW) + 2 - - @address_length.setter - def address_length(self, length): - if 3 <= length <= 5: - self._addr_len = int(length) - self._reg_write(SETUP_AW, length - 2) - else: - raise ValueError( - "address length can only be set in range [3,5] bytes") - - def open_tx_pipe(self, address): - """This function is used to open a data pipe for OTA (over the air) TX - transmissions.""" - if len(address) == self.address_length: - if self.auto_ack: - self._pipes[0] = address - self._reg_write_bytes(RX_ADDR, address) - self._open_pipes = self._open_pipes | 1 - self._reg_write(EN_RX, self._open_pipes) - self._payload_widths[0] = self.payload_length - self._reg_write(RX_PW, self.payload_length) - self._pipes[0] = address - self._tx_address = address - self._reg_write_bytes(TX_ADDR, address) - else: - raise ValueError("address must be a buffer protocol object with a byte length\nequal " - "to the address_length attribute (currently set to" - " {})".format(self.address_length)) - - def close_rx_pipe(self, pipe_number): - """This function is used to close a specific data pipe from OTA (over - the air) RX transmissions.""" - if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0,5]") - self._open_pipes = self._reg_read(EN_RX) - if self._open_pipes & (1 << pipe_number): - self._open_pipes = self._open_pipes & ~(1 << pipe_number) - self._reg_write(EN_RX, self._open_pipes) - - def open_rx_pipe(self, pipe_number, address): - """This function is used to open a specific data pipe for OTA (over - the air) RX transmissions.""" - if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0,5]") - if len(address) != self.address_length: - raise ValueError("address must be a buffer protocol object with a byte length\nequal " - "to the address_length attribute (currently set to " - "{})".format(self.address_length)) - if pipe_number < 2: - if not pipe_number: - self._pipe0_read_addr = address - self._pipes[pipe_number] = address - self._reg_write_bytes(RX_ADDR + pipe_number, address) - else: - self._pipes[pipe_number] = address[0] - self._reg_write(RX_ADDR + pipe_number, address[0]) - self._open_pipes = self._reg_read(EN_RX) - self._open_pipes = self._open_pipes | (1 << pipe_number) - self._reg_write(EN_RX, self._open_pipes) - self._reg_write(RX_PW + pipe_number, self.payload_length) - self._payload_widths[pipe_number] = self.payload_length - - @property - def listen(self): - """An attribute to represent the nRF24L01 primary role as a radio.""" - return self.power and bool(self._config & 1) - - @listen.setter - def listen(self, is_rx): - assert isinstance(is_rx, (bool, int)) - if self.listen != is_rx: - self._start_listening() - else: - self._stop_listening() - - def _start_listening(self): - if self.ce_pin.value: - self.ce_pin.value = 0 - if self._pipe0_read_addr is not None: - self._reg_write_bytes(RX_ADDR, self._pipe0_read_addr) - self._pipes[0] = self._pipe0_read_addr - self._config = self._config & 0xFC | 3 - self._reg_write(CONFIG, self._config) - time.sleep(0.00015) # mandatory wait to power up radio - self.flush_rx() - self.clear_status_flags(True, False, False) - self.ce_pin.value = 1 # mandatory pulse is > 130 µs - time.sleep(0.00013) - - def _stop_listening(self): - if self.ce_pin.value: - self.ce_pin.value = 0 - self._config = self._config & 0xFE - self._reg_write(CONFIG, self._config) - time.sleep(0.00016) - - def any(self): - """This function checks if the nRF24L01 has received any data at all, - and then reports the next available payload's length (in bytes)""" - return self._reg_read(0x60) # 0x60 = R_RX_PL_WID command - - def recv(self): - """This function is used to retrieve the next available payload""" - if not self.irq_dr: - return None - curr_pl_size = self.payload_length if not self.dynamic_payloads else self.any() - result = self._reg_read_bytes(0x61, curr_pl_size) # 0x61 = R_RX_PAYLOAD - self.clear_status_flags(True, False, False) - return result - - def send(self, buf, ask_no_ack=False, force_retry=0): - """This blocking function is used to transmit payload(s).""" - # ensure power down/standby-I for proper manipulation of PWR_UP & PRIM_RX bits in - # CONFIG register - self.ce_pin.value = 0 - self.flush_tx() # be sure there is space in the TX FIFO - if isinstance(buf, (list, tuple)): # writing a set of payloads - result = [] - for i, b in enumerate(buf): # check invalid payloads first - # this way when we raise a ValueError exception we don't leave the nRF24L01 in an - # unknown frozen state. - if not b or len(b) > 32: - raise ValueError("buf (item {} in the list/tuple) must be a" - " buffer protocol object with a byte length of\nat least 1 " - "and no greater than 32".format(i)) - for i, b in enumerate(buf): - # use recursion for each payload - result.append(self.send(b, ask_no_ack, force_retry)) - return result - if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with a byte length of" - "\nat least 1 and no greater than 32") - # using spec sheet calculations: - # timeout total = T_upload + 2 * stby2active + T_overAir + T_ack + T_irq + T_retry - # T_upload = payload length (in bits) / spi data rate (bits per second = - # baudrate / bits per byte) - # T_upload is finished before timeout begins - # T_download == T_upload, however RX devices spi settings must match TX's for - # accurate calc - # let 2 * stby2active (in µs) ~= (2 + (1 if getting ack else 0)) * 130 - # let T_ack = T_overAir as the payload size is the only distictive variable between - # the 2 - # T_overAir (in seconds) = ( 8 (bits/byte) * (1 byte preamble + address length + - # payload length + crc length) + 9 bit packet ID ) / RF data rate (in bits/sec) - # T_irq (in seconds) = (0.0000082 if self.data_rate == 1 else 0.000006) - # T_retry (in microseconds)= (arc * ard) - need_ack = self._setup_retr & 0x0f and not ask_no_ack - packet_data = 1 + self._addr_len + (max(0, ((self._config & 12) >> 2) - 1)) - bitrate = ((2000000 if self._rf_setup & 0x28 == 8 else 250000) - if self._rf_setup & 0x28 else 1000000) / 8 - t_ack = (((packet_data + 32) * 8 + 9) / bitrate) if need_ack else 0 # assumes 32-byte ACK - stby2active = (1 + (need_ack)) * 0.00013 - t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 - t_retry = (((self._setup_retr & 0xf0) >> 4) * 250 + 380) * \ - (self._setup_retr & 0x0f) / 1000000 - timeout = (((8 * (len(buf) + packet_data)) + 9) / - bitrate) + stby2active + t_irq + t_retry + t_ack - self.write(buf, ask_no_ack) # init using non-blocking helper - time.sleep(0.00001) # ensure CE pulse is >= 10 µs - # if pulse is stopped here, the nRF24L01 only handles the top level payload in the FIFO. - # we could hold CE HIGH to continue processing through the rest of the TX FIFO bound for - # the address passed to open_tx_pipe() - self.ce_pin.value = 0 # go to Standby-I power mode (power attribute still True) - self._wait_for_result(timeout) - if self._setup_retr & 0x0f and self.irq_df: - # if auto-retransmit is on and last attempt failed - retry = False - for _ in range(force_retry): - # resend() clears flags upon entering and exiting - retry = self.resend() - if retry is None or retry: - break # retry succeeded - result = retry - else: # if succeeded - if self.ack and self.irq_dr and not ask_no_ack: - # if ACK payload is waiting in RX FIFO - result = self.recv() # save ACK payload & clears RX flag - else: # if auto-ack is disabled - result = self.irq_ds # will always be True (in this case) - self.clear_status_flags(False) # only TX related IRQ flags - return result - - @property - def irq_dr(self): - """A `bool` that represents the "Data Ready" interrupted flag.""" - return bool(self._status & 0x40) - - @property - def irq_ds(self): - """A `bool` that represents the "Data Sent" interrupted flag.""" - return bool(self._status & 0x20) - - @property - def irq_df(self): - """A `bool` that represents the "Data Failed" interrupted flag.""" - return bool(self._status & 0x10) - - def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): - """This clears the interrupt flags in the status register.""" - # 0x07 = STATUS register - self._reg_write(0x07, (data_recv << 6) | ( - data_sent << 5) | (data_fail << 4)) - - def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): - """Sets the configuration of the nRF24L01's IRQ (interrupt) pin.""" - self._config = self._reg_read(CONFIG) - self._config = (self._config & 0x0F) | (not data_fail << 4) | (not data_sent << 5) | \ - (not data_recv << 6) - self._reg_write(CONFIG, self._config) - - def what_happened(self, dump_pipes=False): - """This debuggung function aggregates and outputs all status/condition - related information""" - watchdog = self._reg_read(8) # 8 == OBSERVE_TX register - print("Channel___________________{} ~ {} GHz".format( - self.channel, (self.channel + 2400) / 1000)) - print("RF Data Rate______________{} {}".format( - self.data_rate, "Mbps" if self.data_rate != 250 else "Kbps")) - print("RF Power Amplifier________{} dbm".format(self.pa_level)) - print("CRC bytes_________________{}".format(self.crc)) - print("Address length____________{} bytes".format(self.address_length)) - print("Payload lengths___________{} bytes".format(self.payload_length)) - print("Auto retry delay__________{} microseconds".format(self.ard)) - print("Auto retry attempts_______{} maximum".format(self.arc)) - print("Packets lost on current channel_____________________{}".format( - (watchdog & 0xF0) >> 4)) - print("Retry attempts made for last transmission___________{}".format(watchdog & 0x0F)) - print("IRQ - Data Ready______{} Data Ready___________{}".format( - '_True' if not bool(self._config & 0x40) else 'False', self.irq_dr)) - print("IRQ - Data Fail_______{} Data Failed__________{}".format( - '_True' if not bool(self._config & 0x20) else 'False', self.irq_df)) - print("IRQ - Data Sent_______{} Data Sent____________{}".format( - '_True' if not bool(self._config & 0x10) else 'False', self.irq_ds)) - print("TX FIFO full__________{} TX FIFO empty________{}".format( - '_True' if bool(self.tx_full) else 'False', bool(self.fifo(True, True)))) - print("RX FIFO full__________{} RX FIFO empty________{}".format( - '_True' if bool(self._fifo & 2) else 'False', bool(self._fifo & 1))) - print("Ask no ACK_________{} Custom ACK Payload___{}".format( - '_Allowed' if bool(self._features & 1) else 'Disabled', - 'Enabled' if self.ack else 'Disabled')) - print("Dynamic Payloads___{} Auto Acknowledgment__{}".format( - '_Enabled' if self.dynamic_payloads else 'Disabled', - 'Enabled' if self.auto_ack else 'Disabled')) - print("Primary Mode_____________{} Power Mode___________{}".format( - 'RX' if self.listen else 'TX', - ('Standby-II' if self.ce_pin.value else 'Standby-I') if self._config & 2 else 'Off')) - if dump_pipes: - print('TX address____________', self._tx_address) - for i, address in enumerate(self._pipes): - is_open = "( open )" if self._open_pipes & (1 << i) else "(closed)" - if i <= 1: # print full address - print("Pipe", i, is_open, "bound:", address) - else: # print unique byte + shared bytes = actual address used by radio - print("Pipe", i, is_open, "bound:", - bytes([self._pipes[i]]) + self._pipes[1][1:]) - if self._open_pipes & (1 << i): - print('\t\texpecting', self._payload_widths[i], 'byte static payloads') - - @property - def dynamic_payloads(self): - """This `bool` attribute controls the nRF24L01's dynamic payload - length feature.""" - return bool(self._dyn_pl and (self._features & 4)) - - @dynamic_payloads.setter - def dynamic_payloads(self, enable): - assert isinstance(enable, (bool, int)) - self._features = self._reg_read(FEATURE) - if self._features & 4 != enable: - self._features = (self._features & 3) | (enable << 2) - self._reg_write(FEATURE, self._features) - self._dyn_pl = 0x3F if enable else 0 # 0x3F = enable dynamic payloads all pipes - self._reg_write(DYNPD, self._dyn_pl) - - @property - def payload_length(self): - """This `int` attribute specifies the length (in bytes) of payload""" - return self._payload_length - - @payload_length.setter - def payload_length(self, length): - # max payload size is 32 bytes - if not length or length <= 32: - self._payload_length = length - else: - raise ValueError( - "{}: payload length can only be set in range [1,32] bytes".format(length)) - - @property - def arc(self): - """This `int` attribute specifies the nRF24L01's number of attempts - to re-transmit TX payload""" - self._setup_retr = self._reg_read(SETUP_RETR) - return self._setup_retr & 0x0f - - @arc.setter - def arc(self, count): - if 0 <= count <= 15: - if self.arc & 0x0F != count: - self._setup_retr = (self._setup_retr & 0xF0) | count - self._reg_write(SETUP_RETR, self._setup_retr) - else: - raise ValueError( - "automatic re-transmit count(/attempts) must in range [0,15]") - - @property - def ard(self): - """This `int` attribute specifies the nRF24L01's delay (in - microseconds) between attempts to automatically re-transmit the - TX payload""" - self._setup_retr = self._reg_read(SETUP_RETR) - return ((self._setup_retr & 0xf0) >> 4) * 250 + 250 - - @ard.setter - def ard(self, delta_t): - if 250 <= delta_t <= 4000 and delta_t % 250 == 0: - if self.ard != delta_t: - self._setup_retr = ( - int((delta_t - 250) / 250) << 4) | (self._setup_retr & 0x0F) - self._reg_write(SETUP_RETR, self._setup_retr) - else: - raise ValueError("automatic re-transmit delay can only be a multiple of 250 in range " - "[250,4000]") - - @property - def auto_ack(self): - """This `bool` attribute controls the nRF24L01's automatic - acknowledgment feature during the process of receiving""" - return self._aa - - @auto_ack.setter - def auto_ack(self, enable): - assert isinstance(enable, (bool, int)) - self._aa = 0x3F if enable else 0 # 0x3F = enable auto_ack on all pipes - self._reg_write(EN_AA, self._aa) - - @property - def ack(self): - """This `bool` attribute represents the status of the nRF24L01's - capability to use custom payloads as part of the automatic - acknowledgment (ACK) packet.""" - return bool((self._features & 2) and self.auto_ack and self.dynamic_payloads) - - @ack.setter - def ack(self, enable): - assert isinstance(enable, (bool, int)) - if self.ack != enable: - self.auto_ack = True - self._dyn_pl = 0x3F - self._reg_write(DYNPD, self._dyn_pl) - else: - self._features = self._reg_read(FEATURE) - self._features = (self._features & 5) | (6 if enable else 0) - self._reg_write(FEATURE, self._features) - - def load_ack(self, buf, pipe_number): - """This allows the MCU to specify a payload to be allocated into the - TX FIFO buffer for use on a specific data pipe.""" - if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0,5]") - if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with a byte length of" - "\nat least 1 and no greater than 32") - if not self.ack: - self.ack = True - if not self.tx_full: - self._reg_write_bytes(0xA8 | pipe_number, buf) # 0xA8 = W_ACK_PAYLOAD - return True - return False - - def read_ack(self): - """Allows user to read the automatic acknowledgement (ACK) payload (if - any) when nRF24L01 is in TX mode.""" - return self.recv() - - @property - def data_rate(self): - """This `int` attribute specifies the nRF24L01's frequency data rate - for OTA (over the air) transmissions.""" - self._rf_setup = self._reg_read(RF_SETUP) - return (2 if self._rf_setup & 0x28 == 8 else 250) if self._rf_setup & 0x28 else 1 - - @data_rate.setter - def data_rate(self, speed): - if speed in (1, 2, 250): - if self.data_rate != speed: - speed = 0 if speed == 1 else (8 if speed == 2 else 0x20) - self._rf_setup = self._rf_setup & 0xD7 | speed - self._reg_write(RF_SETUP, self._rf_setup) - else: - raise ValueError( - "data rate must be one of the following ([M,M,K]bps): 1, 2, 250") - - @property - def channel(self): - """This `int` attribute specifies the nRF24L01's frequency.""" - return self._reg_read(RF_CH) - - @channel.setter - def channel(self, channel): - if 0 <= channel <= 125: - self._channel = channel - self._reg_write(RF_CH, channel) - else: - raise ValueError("channel acn only be set in range [0,125]") - - @property - def crc(self): - """This `int` attribute specifies the nRF24L01's CRC (cyclic redundancy - checking) encoding scheme in terms of byte length.""" - self._config = self._reg_read(CONFIG) - return max(0, ((self._config & 12) >> 2) - 1) - - @crc.setter - def crc(self, length): - if 0 <= length <= 2: - if self.crc != length: - length = (length + 1) << 2 if length else 0 - self._config = self._config & 0x73 | length - self._reg_write(0, self._config) - else: - raise ValueError( - "CRC byte length must be an int equal to 0 (off), 1, or 2") - - @property - def power(self): - """This `bool` attribute controls the power state of the nRF24L01.""" - return bool(self._config & 2) - - @power.setter - def power(self, is_on): - assert isinstance(is_on, (bool, int)) - self._config = self._reg_read(CONFIG) - if self.power != is_on: - self._config = (self._config & 0x7d) | (is_on << 1) - self._reg_write(CONFIG, self._config) - # power up/down takes < 150 µs + 4 µs - time.sleep(0.00016) - - @property - def pa_level(self): - """This `int` attribute specifies the nRF24L01's power amplifier level - (in dBm).""" - self._rf_setup = self._reg_read(RF_SETUP) # refresh data - return (3 - ((self._rf_setup & RF_SETUP) >> 1)) * -6 - - @pa_level.setter - def pa_level(self, power): - if power in (-18, -12, -6, 0): - power = (3 - int(power / -6)) * 2 - self._rf_setup = (self._rf_setup & 0xF9) | power - self._reg_write(RF_SETUP, self._rf_setup) - else: - raise ValueError( - "power amplitude must be one of the following (dBm): -18, -12, -6, 0") - - @property - def rpd(self): - """This read-only attribute returns `True` if RPD (Received Power - Detector) is triggered or `False` if not triggered.""" - return bool(self._reg_read(0x09)) - - @property - def tx_full(self): - """An attribute to represent the nRF24L01's status flag signaling that - the TX FIFO buffer is full. (read-only)""" - return bool(self._status & 1) - - def update(self): - """This function is only used to get an updated status byte over SPI - from the nRF24L01""" - self._reg_write(0xFF) # 0xFF = non-operation command - - def resend(self): - """Use this function to maunally re-send the previous payload in the - top level (first out) of the TX FIFO buffer.""" - result = False - if not self.fifo(True, True): - self.clear_status_flags(False) - self._reg_write(0xE3) # 0xE3 == REUSE_TX_PL command - # timeout calc assumes 32 byte payload (& 32-byte ACK if needed) - pl_coef = 1 + bool(self._setup_retr & 0x0f) - pl_len = 1 + self._addr_len + ( - max(0, ((self._config & 12) >> 2) - 1)) - bitrate = ((2000000 if self._rf_setup & 0x28 == 8 else 250000) - if self._rf_setup & 0x28 else 1000000) / 8 - stby2active = (1 + pl_coef) * 0.00013 - t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 - t_retry = (((self._setup_retr & 0xf0) >> 4) * 250 + - 380) * (self._setup_retr & 0x0f) / 1000000 - timeout = pl_coef * (((8 * (32 + pl_len)) + 9) / bitrate) + \ - stby2active + t_irq + t_retry - self.ce_pin.value = 0 - self.ce_pin.value = 1 - time.sleep(0.00001) - self.ce_pin.value = 0 - self._wait_for_result(timeout) - result = self.irq_ds - if self.ack and self.irq_dr: # is there an ACK payload - result = self.recv() - self.clear_status_flags(False) - return result - - def write(self, buf, ask_no_ack=False): - """This non-blocking function (when used as alternative to `send()`) is meant for - asynchronous applications and can only handle one payload at a time as it is a - helper function to `send()`.""" - if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with a byte length of" - "\nat least 1 and no greater than 32") - self.clear_status_flags(False) - if self._config & 3 != 2: - # ensures tx mode & powered up - self._config = (self._reg_read(CONFIG) & 0x7c) | 2 - self._reg_write(CONFIG, self._config) - time.sleep(0.00016) # power up/down takes < 150 µs + 4 µs - # pad out or truncate data to fill payload_length if dynamic_payloads == False - if not self.dynamic_payloads: - if len(buf) < self.payload_length: - for _ in range(self.payload_length - len(buf)): - buf += b'\x00' - elif len(buf) > self.payload_length: - buf = buf[:self.payload_length] - # now upload the payload accordingly with appropriate command - if ask_no_ack: # payload doesn't want acknowledgment - if self._features & 1 == 0: - self._features = self._features & 0xFE | 1 # set EN_DYN_ACK flag high - self._reg_write(FEATURE, self._features) - # 0xA0 = W_TX_PAYLOAD; 0xB0 = W_TX_PAYLOAD_NO_ACK - self._reg_write_bytes(0xA0 | (ask_no_ack << 4), buf) - self.ce_pin.value = 1 - - def flush_rx(self): - """A helper function to flush the nRF24L01's internal RX FIFO buffer. (write-only)""" - self._reg_write(0xE2) - - def flush_tx(self): - """A helper function to flush the nRF24L01's internal TX FIFO buffer. (write-only)""" - self._reg_write(0xE1) - - def fifo(self, about_tx=False, check_empty=None): - """This provides some precision determining the status of the TX/RX FIFO buffers. - (read-only)""" - if (check_empty is None and isinstance(about_tx, (bool, int))) or \ - (isinstance(check_empty, (bool, int)) and isinstance(about_tx, (bool, int))): - self._fifo = self._reg_read(FIFO) # refresh the data - if check_empty is None: - return (self._fifo & (0x30 if about_tx else 0x03)) >> (4 * about_tx) - return bool(self._fifo & ((2 - check_empty) << (4 * about_tx))) - raise ValueError("Argument 1 ('about_tx') must always be a bool or int. Argument 2" - " ('check_empty'), if specified, must be a bool or int") - - def pipe(self): - """This function returns information about the data pipe that received the next - available payload in the RX FIFO buffer.""" - self.update() - result = (self._status & 0x0E) >> 1 # 0x0E==RX_P_NO - if result <= 5: - return result - return None - - def address(self, index=-1): - """Returns the current address set to a specified data pipe or the TX address. - (read-only)""" - if index > 5: - raise IndexError("index {} is out of bounds [0,5]".format(index)) - if index < 0: - return self._tx_address - if index <= 1: - return self._pipes[index] - return bytes(self._pipes[index]) + self._pipes[1][1:] - - def _wait_for_result(self, timeout): - start = time.monotonic() - while not self.irq_ds and not self.irq_df and (time.monotonic() - start) < timeout: - self.update() + """A driver class for the nRF24L01(+) transceiver radios.""" + def __init__(self, spi, csn, ce): + self._payload_length = 32 # inits internal attribute + self.payload_length = 32 + # last address assigned to pipe0 for reading. init to None + self._fifo = 0 + self._status = 0 + # init shadow copy of RX addresses for all pipes + self._pipes = [b'\xE7' * 5, b'\xC2' * 5, 0, 0, 0, 0] + self._payload_widths = [32] * 6 # payload_length specific to each pipe + # shadow copy of last RX_ADDR written to pipe 0 + self._pipe0_read_addr = None # needed as open_tx_pipe() appropriates pipe 0 for ACK + # init the _open_pipes attribute (reflects only RX state on each pipe) + self._open_pipes = 0 # 0 = all pipes closed + self._spi = SPIDevice(spi, chip_select=csn, baudrate=1250000) + self.ce_pin = ce + self.ce_pin.switch_to_output(value=False) + # configure the CONFIG register: + # 0x0E = IRQs are all enabled, CRC is enabled with 2 bytes, and power up in TX mode + self._config = 0x0E + self._reg_write(CONFIG, self._config) # dump to register + # check for device presence by verifying nRF24L01 is in TX + standby-I mode + if self._reg_read(CONFIG) & 3 == 2: # if in TX + standby-I mode + self.power = False # power down + else: # hardware presence check NOT passed + print(bin(self._reg_read(CONFIG))) + raise RuntimeError("nRF24L01 Hardware not responding") + for i in range(6): + if i < 2: + self._pipes[i] = self._reg_read_bytes(RX_ADDR + i) + else: + self._pipes[i] = self._reg_read(RX_ADDR + i) + # shadow copy of the TX_ADDR + self._tx_address = self._reg_read_bytes(TX_ADDR) + # configure the SETUP_RETR register + self._setup_retr = 0x53 #ard = 1500; arc = 3 + # configure the RF_SETUP register + self._rf_setup = 0x06 # 1 Mbps data_rate, and 0 dbm pa_level + # configure dynamic_payloads & auto_ack for RX operations + self._dyn_pl = 0x3F # 0x3F = dynamic_payloads enabled on all pipes + self._aa = 0x3F # 0x3F = auto_ack enabled on all pipes + # configure features for TX operations + # 5 = enable dynamic_payloads, disable custom ack payloads, and allow ask_no_ack command + self._features = 5 + self._channel = 76 + self._addr_len = 5 + + with self: # write to registers & power up + self.ce_pin.value = 0 + self._reg_write(CONFIG, self._config | 1) + time.sleep(0.000015) + self.flush_rx() + self._reg_write(CONFIG, self._config & 0xC) + time.sleep(0.000015) + self.flush_tx() + self.clear_status_flags() + + def __enter__(self): + self._reg_write(CONFIG, self._config & 0x7C) + self._reg_write(RF_SETUP, self._rf_setup) + self._reg_write(EN_RX, self._open_pipes) + self._reg_write(DYNPD, self._dyn_pl) + self._reg_write(EN_AA, self._aa) + self._reg_write(FEATURE, self._features) + self._reg_write(SETUP_RETR, self._setup_retr) + for i, address in enumerate(self._pipes): + if i < 2: + self._reg_write_bytes(RX_ADDR + i, address) + else: + self._reg_write(RX_ADDR + i, address) + self._reg_write(RX_PW + i, self._payload_widths[i]) + self._reg_write_bytes(TX_ADDR, self._tx_address) + self.address_length = self._addr_len + self.channel = self._channel + return self + + def __exit__(self, *exc): + self.power = 0 + return False + + # pylint: disable=no-member + def _reg_read(self, reg): + buf = bytearray(2) + with self._spi as spi: + time.sleep(0.005) + spi.readinto(buf, write_value=reg) + self._status = buf[0] + return buf[1] + + def _reg_read_bytes(self, reg, buf_len=5): + buf = bytearray(buf_len + 1) + with self._spi as spi: + time.sleep(0.005) + spi.readinto(buf, write_value=reg) + self._status = buf[0] + return buf[1:] + + def _reg_write_bytes(self, reg, out_buf): + out_buf = bytes([0x20 | reg]) + out_buf + in_buf = bytearray(len(out_buf)) + with self._spi as spi: + time.sleep(0.005) + spi.write_readinto(out_buf, in_buf) + self._status = in_buf[0] + + def _reg_write(self, reg, value=None): + if value is None: + out_buf = bytes([reg]) + else: + out_buf = bytes([0x20 | reg, value]) + in_buf = bytearray(len(out_buf)) + with self._spi as spi: + time.sleep(0.005) + spi.write_readinto(out_buf, in_buf) + self._status = in_buf[0] + # pylint: enable=no-member + + @property + def address_length(self): + """This `int` attribute specifies the length (in bytes) of addresses + to be used for RX/TX pipes.""" + return self._reg_read(SETUP_AW) + 2 + + @address_length.setter + def address_length(self, length): + if 3 <= length <= 5: + self._addr_len = int(length) + self._reg_write(SETUP_AW, length - 2) + else: + raise ValueError( + "address length can only be set in range [3,5] bytes") + + def open_tx_pipe(self, address): + """This function is used to open a data pipe for OTA (over the air) TX + transmissions.""" + if len(address) == self.address_length: + if self.auto_ack: + self._pipes[0] = address + self._reg_write_bytes(RX_ADDR, address) + self._open_pipes = self._open_pipes | 1 + self._reg_write(EN_RX, self._open_pipes) + self._payload_widths[0] = self.payload_length + self._reg_write(RX_PW, self.payload_length) + self._pipes[0] = address + self._tx_address = address + self._reg_write_bytes(TX_ADDR, address) + else: + raise ValueError("address must be a buffer protocol object with a byte length\nequal " + "to the address_length attribute (currently set to" + " {})".format(self.address_length)) + + def close_rx_pipe(self, pipe_number): + """This function is used to close a specific data pipe from OTA (over + the air) RX transmissions.""" + if pipe_number < 0 or pipe_number > 5: + raise ValueError("pipe number must be in range [0,5]") + self._open_pipes = self._reg_read(EN_RX) + if self._open_pipes & (1 << pipe_number): + self._open_pipes = self._open_pipes & ~(1 << pipe_number) + self._reg_write(EN_RX, self._open_pipes) + + def open_rx_pipe(self, pipe_number, address): + """This function is used to open a specific data pipe for OTA (over + the air) RX transmissions.""" + if pipe_number < 0 or pipe_number > 5: + raise ValueError("pipe number must be in range [0,5]") + if len(address) != self.address_length: + raise ValueError("address must be a buffer protocol object with a byte length\nequal " + "to the address_length attribute (currently set to " + "{})".format(self.address_length)) + if pipe_number < 2: + if not pipe_number: + self._pipe0_read_addr = address + self._pipes[pipe_number] = address + self._reg_write_bytes(RX_ADDR + pipe_number, address) + else: + self._pipes[pipe_number] = address[0] + self._reg_write(RX_ADDR + pipe_number, address[0]) + self._open_pipes = self._reg_read(EN_RX) + self._open_pipes = self._open_pipes | (1 << pipe_number) + self._reg_write(EN_RX, self._open_pipes) + self._reg_write(RX_PW + pipe_number, self.payload_length) + self._payload_widths[pipe_number] = self.payload_length + + @property + def listen(self): + """An attribute to represent the nRF24L01 primary role as a radio.""" + return self.power and bool(self._config & 1) + + @listen.setter + def listen(self, is_rx): + assert isinstance(is_rx, (bool, int)) + if self.listen != is_rx: + self._start_listening() + else: + self._stop_listening() + + def _start_listening(self): + if self.ce_pin.value: + self.ce_pin.value = 0 + if self._pipe0_read_addr is not None: + self._reg_write_bytes(RX_ADDR, self._pipe0_read_addr) + self._pipes[0] = self._pipe0_read_addr + self._config = self._config & 0xFC | 3 + self._reg_write(CONFIG, self._config) + time.sleep(0.00015) # mandatory wait to power up radio + self.flush_rx() + self.clear_status_flags(True, False, False) + self.ce_pin.value = 1 # mandatory pulse is > 130 µs + time.sleep(0.00013) + + def _stop_listening(self): + if self.ce_pin.value: + self.ce_pin.value = 0 + self._config = self._config & 0xFE + self._reg_write(CONFIG, self._config) + time.sleep(0.00016) + + def any(self): + """This function checks if the nRF24L01 has received any data at all, + and then reports the next available payload's length (in bytes)""" + return self._reg_read(0x60) # 0x60 = R_RX_PL_WID command + + def recv(self): + """This function is used to retrieve the next available payload""" + if not self.irq_dr: + return None + curr_pl_size = self.payload_length if not self.dynamic_payloads else self.any() + result = self._reg_read_bytes(0x61, curr_pl_size) # 0x61 = R_RX_PAYLOAD + self.clear_status_flags(True, False, False) + return result + + def send(self, buf, ask_no_ack=False, force_retry=0): + """This blocking function is used to transmit payload(s).""" + # ensure power down/standby-I for proper manipulation of PWR_UP & PRIM_RX bits in + # CONFIG register + self.ce_pin.value = 0 + self.flush_tx() # be sure there is space in the TX FIFO + if isinstance(buf, (list, tuple)): # writing a set of payloads + result = [] + for i, b in enumerate(buf): # check invalid payloads first + # this way when we raise a ValueError exception we don't leave the nRF24L01 in an + # unknown frozen state. + if not b or len(b) > 32: + raise ValueError("buf (item {} in the list/tuple) must be a" + " buffer protocol object with a byte length of\nat least 1 " + "and no greater than 32".format(i)) + for i, b in enumerate(buf): + # use recursion for each payload + result.append(self.send(b, ask_no_ack, force_retry)) + return result + if not buf or len(buf) > 32: + raise ValueError("buf must be a buffer protocol object with a byte length of" + "\nat least 1 and no greater than 32") + # using spec sheet calculations: + # timeout total = T_upload + 2 * stby2active + T_overAir + T_ack + T_irq + T_retry + # T_upload = payload length (in bits) / spi data rate (bits per second = + # baudrate / bits per byte) + # T_upload is finished before timeout begins + # T_download == T_upload, however RX devices spi settings must match TX's for + # accurate calc + # let 2 * stby2active (in µs) ~= (2 + (1 if getting ack else 0)) * 130 + # let T_ack = T_overAir as the payload size is the only distictive variable between + # the 2 + # T_overAir (in seconds) = ( 8 (bits/byte) * (1 byte preamble + address length + + # payload length + crc length) + 9 bit packet ID ) / RF data rate (in bits/sec) + # T_irq (in seconds) = (0.0000082 if self.data_rate == 1 else 0.000006) + # T_retry (in microseconds)= (arc * ard) + need_ack = self._setup_retr & 0x0f and not ask_no_ack + packet_data = 1 + self._addr_len + (max(0, ((self._config & 12) >> 2) - 1)) + bitrate = ((2000000 if self._rf_setup & 0x28 == 8 else 250000) + if self._rf_setup & 0x28 else 1000000) / 8 + t_ack = (((packet_data + 32) * 8 + 9) / bitrate) if need_ack else 0 # assumes 32-byte ACK + stby2active = (1 + (need_ack)) * 0.00013 + t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 + t_retry = (((self._setup_retr & 0xf0) >> 4) * 250 + 380) * \ + (self._setup_retr & 0x0f) / 1000000 + timeout = (((8 * (len(buf) + packet_data)) + 9) / + bitrate) + stby2active + t_irq + t_retry + t_ack + self.write(buf, ask_no_ack) # init using non-blocking helper + time.sleep(0.00001) # ensure CE pulse is >= 10 µs + # if pulse is stopped here, the nRF24L01 only handles the top level payload in the FIFO. + # we could hold CE HIGH to continue processing through the rest of the TX FIFO bound for + # the address passed to open_tx_pipe() + self.ce_pin.value = 0 # go to Standby-I power mode (power attribute still True) + self._wait_for_result(timeout) + if self._setup_retr & 0x0f and self.irq_df: + # if auto-retransmit is on and last attempt failed + retry = False + for _ in range(force_retry): + # resend() clears flags upon entering and exiting + retry = self.resend() + if retry is None or retry: + break # retry succeeded + result = retry + else: # if succeeded + if self.ack and self.irq_dr and not ask_no_ack: + # if ACK payload is waiting in RX FIFO + result = self.recv() # save ACK payload & clears RX flag + else: # if auto-ack is disabled + result = self.irq_ds # will always be True (in this case) + self.clear_status_flags(False) # only TX related IRQ flags + return result + + @property + def irq_dr(self): + """A `bool` that represents the "Data Ready" interrupted flag.""" + return bool(self._status & 0x40) + + @property + def irq_ds(self): + """A `bool` that represents the "Data Sent" interrupted flag.""" + return bool(self._status & 0x20) + + @property + def irq_df(self): + """A `bool` that represents the "Data Failed" interrupted flag.""" + return bool(self._status & 0x10) + + def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): + """This clears the interrupt flags in the status register.""" + # 0x07 = STATUS register + self._reg_write(0x07, (data_recv << 6) | ( + data_sent << 5) | (data_fail << 4)) + + def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): + """Sets the configuration of the nRF24L01's IRQ (interrupt) pin.""" + self._config = self._reg_read(CONFIG) + self._config = (self._config & 0x0F) | (not data_fail << 4) | (not data_sent << 5) | \ + (not data_recv << 6) + self._reg_write(CONFIG, self._config) + + def what_happened(self, dump_pipes=False): + """This debuggung function aggregates and outputs all status/condition + related information""" + watchdog = self._reg_read(8) # 8 == OBSERVE_TX register + print("Channel___________________{} ~ {} GHz".format( + self.channel, (self.channel + 2400) / 1000)) + print("RF Data Rate______________{} {}".format( + self.data_rate, "Mbps" if self.data_rate != 250 else "Kbps")) + print("RF Power Amplifier________{} dbm".format(self.pa_level)) + print("CRC bytes_________________{}".format(self.crc)) + print("Address length____________{} bytes".format(self.address_length)) + print("Payload lengths___________{} bytes".format(self.payload_length)) + print("Auto retry delay__________{} microseconds".format(self.ard)) + print("Auto retry attempts_______{} maximum".format(self.arc)) + print("Packets lost on current channel_____________________{}".format( + (watchdog & 0xF0) >> 4)) + print("Retry attempts made for last transmission___________{}".format(watchdog & 0x0F)) + print("IRQ - Data Ready______{} Data Ready___________{}".format( + '_True' if not bool(self._config & 0x40) else 'False', self.irq_dr)) + print("IRQ - Data Fail_______{} Data Failed__________{}".format( + '_True' if not bool(self._config & 0x20) else 'False', self.irq_df)) + print("IRQ - Data Sent_______{} Data Sent____________{}".format( + '_True' if not bool(self._config & 0x10) else 'False', self.irq_ds)) + print("TX FIFO full__________{} TX FIFO empty________{}".format( + '_True' if bool(self.tx_full) else 'False', bool(self.fifo(True, True)))) + print("RX FIFO full__________{} RX FIFO empty________{}".format( + '_True' if bool(self._fifo & 2) else 'False', bool(self._fifo & 1))) + print("Ask no ACK_________{} Custom ACK Payload___{}".format( + '_Allowed' if bool(self._features & 1) else 'Disabled', + 'Enabled' if self.ack else 'Disabled')) + print("Dynamic Payloads___{} Auto Acknowledgment__{}".format( + '_Enabled' if self.dynamic_payloads else 'Disabled', + 'Enabled' if self.auto_ack else 'Disabled')) + print("Primary Mode_____________{} Power Mode___________{}".format( + 'RX' if self.listen else 'TX', + ('Standby-II' if self.ce_pin.value else 'Standby-I') if self._config & 2 else 'Off')) + if dump_pipes: + print('TX address____________', self._tx_address) + for i, address in enumerate(self._pipes): + is_open = "( open )" if self._open_pipes & (1 << i) else "(closed)" + if i <= 1: # print full address + print("Pipe", i, is_open, "bound:", address) + else: # print unique byte + shared bytes = actual address used by radio + print("Pipe", i, is_open, "bound:", + bytes([self._pipes[i]]) + self._pipes[1][1:]) + if self._open_pipes & (1 << i): + print('\t\texpecting', self._payload_widths[i], 'byte static payloads') + + @property + def dynamic_payloads(self): + """This `bool` attribute controls the nRF24L01's dynamic payload + length feature.""" + return bool(self._dyn_pl and (self._features & 4)) + + @dynamic_payloads.setter + def dynamic_payloads(self, enable): + assert isinstance(enable, (bool, int)) + self._features = self._reg_read(FEATURE) + if self._features & 4 != enable: + self._features = (self._features & 3) | (enable << 2) + self._reg_write(FEATURE, self._features) + self._dyn_pl = 0x3F if enable else 0 # 0x3F = enable dynamic payloads all pipes + self._reg_write(DYNPD, self._dyn_pl) + + @property + def payload_length(self): + """This `int` attribute specifies the length (in bytes) of payload""" + return self._payload_length + + @payload_length.setter + def payload_length(self, length): + # max payload size is 32 bytes + if not length or length <= 32: + self._payload_length = length + else: + raise ValueError( + "{}: payload length can only be set in range [1,32] bytes".format(length)) + + @property + def arc(self): + """This `int` attribute specifies the nRF24L01's number of attempts + to re-transmit TX payload""" + self._setup_retr = self._reg_read(SETUP_RETR) + return self._setup_retr & 0x0f + + @arc.setter + def arc(self, count): + if 0 <= count <= 15: + if self.arc & 0x0F != count: + self._setup_retr = (self._setup_retr & 0xF0) | count + self._reg_write(SETUP_RETR, self._setup_retr) + else: + raise ValueError( + "automatic re-transmit count(/attempts) must in range [0,15]") + + @property + def ard(self): + """This `int` attribute specifies the nRF24L01's delay (in + microseconds) between attempts to automatically re-transmit the + TX payload""" + self._setup_retr = self._reg_read(SETUP_RETR) + return ((self._setup_retr & 0xf0) >> 4) * 250 + 250 + + @ard.setter + def ard(self, delta_t): + if 250 <= delta_t <= 4000 and delta_t % 250 == 0: + if self.ard != delta_t: + self._setup_retr = ( + int((delta_t - 250) / 250) << 4) | (self._setup_retr & 0x0F) + self._reg_write(SETUP_RETR, self._setup_retr) + else: + raise ValueError("automatic re-transmit delay can only be a multiple of 250 in range " + "[250,4000]") + + @property + def auto_ack(self): + """This `bool` attribute controls the nRF24L01's automatic + acknowledgment feature during the process of receiving""" + return self._aa + + @auto_ack.setter + def auto_ack(self, enable): + assert isinstance(enable, (bool, int)) + self._aa = 0x3F if enable else 0 # 0x3F = enable auto_ack on all pipes + self._reg_write(EN_AA, self._aa) + + @property + def ack(self): + """This `bool` attribute represents the status of the nRF24L01's + capability to use custom payloads as part of the automatic + acknowledgment (ACK) packet.""" + return bool((self._features & 2) and self.auto_ack and self.dynamic_payloads) + + @ack.setter + def ack(self, enable): + assert isinstance(enable, (bool, int)) + if self.ack != enable: + self.auto_ack = True + self._dyn_pl = 0x3F + self._reg_write(DYNPD, self._dyn_pl) + else: + self._features = self._reg_read(FEATURE) + self._features = (self._features & 5) | (6 if enable else 0) + self._reg_write(FEATURE, self._features) + + def load_ack(self, buf, pipe_number): + """This allows the MCU to specify a payload to be allocated into the + TX FIFO buffer for use on a specific data pipe.""" + if pipe_number < 0 or pipe_number > 5: + raise ValueError("pipe number must be in range [0,5]") + if not buf or len(buf) > 32: + raise ValueError("buf must be a buffer protocol object with a byte length of" + "\nat least 1 and no greater than 32") + if not self.ack: + self.ack = True + if not self.tx_full: + self._reg_write_bytes(0xA8 | pipe_number, buf) # 0xA8 = W_ACK_PAYLOAD + return True + return False + + def read_ack(self): + """Allows user to read the automatic acknowledgement (ACK) payload (if + any) when nRF24L01 is in TX mode.""" + return self.recv() + + @property + def data_rate(self): + """This `int` attribute specifies the nRF24L01's frequency data rate + for OTA (over the air) transmissions.""" + self._rf_setup = self._reg_read(RF_SETUP) + return (2 if self._rf_setup & 0x28 == 8 else 250) if self._rf_setup & 0x28 else 1 + + @data_rate.setter + def data_rate(self, speed): + if speed in (1, 2, 250): + if self.data_rate != speed: + speed = 0 if speed == 1 else (8 if speed == 2 else 0x20) + self._rf_setup = self._rf_setup & 0xD7 | speed + self._reg_write(RF_SETUP, self._rf_setup) + else: + raise ValueError( + "data rate must be one of the following ([M,M,K]bps): 1, 2, 250") + + @property + def channel(self): + """This `int` attribute specifies the nRF24L01's frequency.""" + return self._reg_read(RF_CH) + + @channel.setter + def channel(self, channel): + if 0 <= channel <= 125: + self._channel = channel + self._reg_write(RF_CH, channel) + else: + raise ValueError("channel acn only be set in range [0,125]") + + @property + def crc(self): + """This `int` attribute specifies the nRF24L01's CRC (cyclic redundancy + checking) encoding scheme in terms of byte length.""" + self._config = self._reg_read(CONFIG) + return max(0, ((self._config & 12) >> 2) - 1) + + @crc.setter + def crc(self, length): + if 0 <= length <= 2: + if self.crc != length: + length = (length + 1) << 2 if length else 0 + self._config = self._config & 0x73 | length + self._reg_write(0, self._config) + else: + raise ValueError( + "CRC byte length must be an int equal to 0 (off), 1, or 2") + + @property + def power(self): + """This `bool` attribute controls the power state of the nRF24L01.""" + return bool(self._config & 2) + + @power.setter + def power(self, is_on): + assert isinstance(is_on, (bool, int)) + self._config = self._reg_read(CONFIG) + if self.power != is_on: + self._config = (self._config & 0x7d) | (is_on << 1) + self._reg_write(CONFIG, self._config) + # power up/down takes < 150 µs + 4 µs + time.sleep(0.00016) + + @property + def pa_level(self): + """This `int` attribute specifies the nRF24L01's power amplifier level + (in dBm).""" + self._rf_setup = self._reg_read(RF_SETUP) # refresh data + return (3 - ((self._rf_setup & RF_SETUP) >> 1)) * -6 + + @pa_level.setter + def pa_level(self, power): + if power in (-18, -12, -6, 0): + power = (3 - int(power / -6)) * 2 + self._rf_setup = (self._rf_setup & 0xF9) | power + self._reg_write(RF_SETUP, self._rf_setup) + else: + raise ValueError( + "power amplitude must be one of the following (dBm): -18, -12, -6, 0") + + @property + def rpd(self): + """This read-only attribute returns `True` if RPD (Received Power + Detector) is triggered or `False` if not triggered.""" + return bool(self._reg_read(0x09)) + + @property + def tx_full(self): + """An attribute to represent the nRF24L01's status flag signaling that + the TX FIFO buffer is full. (read-only)""" + return bool(self._status & 1) + + def update(self): + """This function is only used to get an updated status byte over SPI + from the nRF24L01""" + self._reg_write(0xFF) # 0xFF = non-operation command + + def resend(self): + """Use this function to maunally re-send the previous payload in the + top level (first out) of the TX FIFO buffer.""" + result = False + if not self.fifo(True, True): + self.clear_status_flags(False) + self._reg_write(0xE3) # 0xE3 == REUSE_TX_PL command + # timeout calc assumes 32 byte payload (& 32-byte ACK if needed) + pl_coef = 1 + bool(self._setup_retr & 0x0f) + pl_len = 1 + self._addr_len + ( + max(0, ((self._config & 12) >> 2) - 1)) + bitrate = ((2000000 if self._rf_setup & 0x28 == 8 else 250000) + if self._rf_setup & 0x28 else 1000000) / 8 + stby2active = (1 + pl_coef) * 0.00013 + t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 + t_retry = (((self._setup_retr & 0xf0) >> 4) * 250 + + 380) * (self._setup_retr & 0x0f) / 1000000 + timeout = pl_coef * (((8 * (32 + pl_len)) + 9) / bitrate) + \ + stby2active + t_irq + t_retry + self.ce_pin.value = 0 + self.ce_pin.value = 1 + time.sleep(0.00001) + self.ce_pin.value = 0 + self._wait_for_result(timeout) + result = self.irq_ds + if self.ack and self.irq_dr: # is there an ACK payload + result = self.recv() + self.clear_status_flags(False) + return result + + def write(self, buf, ask_no_ack=False): + """This non-blocking function (when used as alternative to `send()`) is meant for + asynchronous applications and can only handle one payload at a time as it is a + helper function to `send()`.""" + if not buf or len(buf) > 32: + raise ValueError("buf must be a buffer protocol object with a byte length of" + "\nat least 1 and no greater than 32") + self.clear_status_flags(False) + if self._config & 3 != 2: + # ensures tx mode & powered up + self._config = (self._reg_read(CONFIG) & 0x7c) | 2 + self._reg_write(CONFIG, self._config) + time.sleep(0.00016) # power up/down takes < 150 µs + 4 µs + # pad out or truncate data to fill payload_length if dynamic_payloads == False + if not self.dynamic_payloads: + if len(buf) < self.payload_length: + for _ in range(self.payload_length - len(buf)): + buf += b'\x00' + elif len(buf) > self.payload_length: + buf = buf[:self.payload_length] + # now upload the payload accordingly with appropriate command + if ask_no_ack: # payload doesn't want acknowledgment + if self._features & 1 == 0: + self._features = self._features & 0xFE | 1 # set EN_DYN_ACK flag high + self._reg_write(FEATURE, self._features) + # 0xA0 = W_TX_PAYLOAD; 0xB0 = W_TX_PAYLOAD_NO_ACK + self._reg_write_bytes(0xA0 | (ask_no_ack << 4), buf) + self.ce_pin.value = 1 + + def flush_rx(self): + """A helper function to flush the nRF24L01's internal RX FIFO buffer. (write-only)""" + self._reg_write(0xE2) + + def flush_tx(self): + """A helper function to flush the nRF24L01's internal TX FIFO buffer. (write-only)""" + self._reg_write(0xE1) + + def fifo(self, about_tx=False, check_empty=None): + """This provides some precision determining the status of the TX/RX FIFO buffers. + (read-only)""" + if (check_empty is None and isinstance(about_tx, (bool, int))) or \ + (isinstance(check_empty, (bool, int)) and isinstance(about_tx, (bool, int))): + self._fifo = self._reg_read(FIFO) # refresh the data + if check_empty is None: + return (self._fifo & (0x30 if about_tx else 0x03)) >> (4 * about_tx) + return bool(self._fifo & ((2 - check_empty) << (4 * about_tx))) + raise ValueError("Argument 1 ('about_tx') must always be a bool or int. Argument 2" + " ('check_empty'), if specified, must be a bool or int") + + def pipe(self): + """This function returns information about the data pipe that received the next + available payload in the RX FIFO buffer.""" + self.update() + result = (self._status & 0x0E) >> 1 # 0x0E==RX_P_NO + if result <= 5: + return result + return None + + def address(self, index=-1): + """Returns the current address set to a specified data pipe or the TX address. + (read-only)""" + if index > 5: + raise IndexError("index {} is out of bounds [0,5]".format(index)) + if index < 0: + return self._tx_address + if index <= 1: + return self._pipes[index] + return bytes(self._pipes[index]) + self._pipes[1][1:] + + def _wait_for_result(self, timeout): + start = time.monotonic() + while not self.irq_ds and not self.irq_df and (time.monotonic() - start) < timeout: + self.update() diff --git a/circuitpython_nrf24l01/rf24_m0.py b/circuitpython_nrf24l01/rf24_m0.py new file mode 100644 index 0000000..98124e8 --- /dev/null +++ b/circuitpython_nrf24l01/rf24_m0.py @@ -0,0 +1,493 @@ +# pylint: disable=missing-class-docstring,missing-function-docstring,missing-module-docstring +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/2bndy5/CircuitPython_nRF24L01.git" +from time import sleep, monotonic +from adafruit_bus_device.spi_device import SPIDevice + +class RF24: + def __init__(self, spi, csn, ce): + self._pipe0_read_addr = None + self._spi = SPIDevice(spi, chip_select=csn, baudrate=1250000) + self.ce_pin = ce + self.ce_pin.switch_to_output(value=False) + self._reg_write(0, 0x0E) + self._status = 0 + self._payload_length = 32 + hw_check = self._reg_read(0) + if hw_check & 3 == 2: + self.power = False + else: + print(bin(hw_check)) + raise RuntimeError("nRF24L01 Hardware not responding") + self.channel = 76 + self.address_length = 5 + self._reg_write(6, 6) + self._reg_write(2, 0) + self._reg_write(0x1C, 0x3F) + self._reg_write(1, 0x3F) + self._reg_write(0x1D, 5) + self._reg_write(4, 0x53) + + self.flush_rx() + self.flush_tx() + self.clear_status_flags() + + # pylint: disable=no-member + def _reg_read(self, reg): + buf = bytearray(2) + with self._spi as spi: + sleep(0.005) + spi.readinto(buf, write_value=reg) + self._status = buf[0] + return buf[1] + + def _reg_read_bytes(self, reg, buf_len=5): + buf = bytearray(buf_len + 1) + with self._spi as spi: + sleep(0.005) + spi.readinto(buf, write_value=reg) + self._status = buf[0] + return buf[1:] + + def _reg_write_bytes(self, reg, out_buf): + out_buf = bytes([0x20 | reg]) + out_buf + in_buf = bytearray(len(out_buf)) + with self._spi as spi: + sleep(0.005) + spi.write_readinto(out_buf, in_buf) + self._status = in_buf[0] + + def _reg_write(self, reg, value=None): + if value is None: + out_buf = bytes([reg]) + else: + out_buf = bytes([0x20 | reg, value]) + in_buf = bytearray(len(out_buf)) + with self._spi as spi: + sleep(0.005) + spi.write_readinto(out_buf, in_buf) + self._status = in_buf[0] + # pylint: enable=no-member + + @property + def address_length(self): + return self._reg_read(3) + 2 + + @address_length.setter + def address_length(self, length): + if 3 <= length <= 5: + self._reg_write(3, length - 2) + else: + raise ValueError("address length can only be set in range [3,5] bytes") + + def open_tx_pipe(self, address): + if len(address) == self.address_length: + if self.auto_ack: + self._reg_write_bytes(0x0A, address) + self._reg_write(2, self._reg_read(2) | 1) + self._reg_write(0x11, self.payload_length) + self._reg_write_bytes(0x10, address) + else: + raise ValueError( + "address must be a buffer protocol object with a byte length\nequal " + "to the address_length attribute (currently set to" + " {})".format(self.address_length)) + + def close_rx_pipe(self, pipe_number): + if pipe_number < 0 or pipe_number > 5: + raise ValueError("pipe number must be in range [0,5]") + open_pipes = self._reg_read(2) + if open_pipes & (1 << pipe_number): + self._reg_write(2, open_pipes & ~(1 << pipe_number)) + + def open_rx_pipe(self, pipe_number, address): + if pipe_number < 0 or pipe_number > 5: + raise ValueError("pipe number must be in range [0,5]") + if len(address) != self.address_length: + raise ValueError( + "address must be a buffer protocol object with a byte length\nequal " + "to the address_length attribute (currently set to " + "{})".format(self.address_length)) + if pipe_number < 2: + if not pipe_number: + self._pipe0_read_addr = address + self._reg_write_bytes(0x0A + pipe_number, address) + else: + self._reg_write(0x0A + pipe_number, address[0]) + self._reg_write(2, self._reg_read(2) | (1 << pipe_number)) + self._reg_write(0x11 + pipe_number, self._payload_length) + + @property + def listen(self): + return (self._reg_read(0) & 3) == 3 + + @listen.setter + def listen(self, is_rx): + assert isinstance(is_rx, (bool, int)) + if self.listen != is_rx: + self._start_listening() + else: + self._stop_listening() + + def _start_listening(self): + if self.ce_pin.value: + self.ce_pin.value = 0 + if self._pipe0_read_addr is not None: + self._reg_write_bytes(0x0A, self._pipe0_read_addr) + self._reg_write(0, self._reg_read(0) & 0xFC | 3) + sleep(0.00015) # mandatory wait to power up radio + self.flush_rx() + self.clear_status_flags(True, False, False) + self.ce_pin.value = 1 # mandatory pulse is > 130 µs + sleep(0.00013) + + def _stop_listening(self): + if self.ce_pin.value: + self.ce_pin.value = 0 + self._reg_write(0, self._reg_read(0) & 0xFE) + sleep(0.00016) + + def any(self): + if self._reg_read(0x1D) & 4: + return self._reg_read(0x60) + if self.irq_dr: + return self._reg_read(0x11 + ((self._status & 0xE) >> 1)) + return 0 + + def recv(self): + pl_wid = self.any() + result = self._reg_read_bytes(0x61, pl_wid) # 0x61 = R_RX_PAYLOAD + self.clear_status_flags(True, False, False) + return result + + def send(self, buf, ask_no_ack=False, force_retry=0): + self.ce_pin.value = 0 + self.flush_tx() + if isinstance(buf, (list, tuple)): + result = [] + for i, b in enumerate(buf): # check invalid payloads first + if not b or len(b) > 32: + raise ValueError( + "buf (item {} in the list/tuple) must be a" + " buffer protocol object with a byte length of\nat least 1 " + "and no greater than 32".format(i)) + for i, b in enumerate(buf): + # use recursion for each payload + result.append(self.send(b, ask_no_ack, force_retry)) + return result + if not buf or len(buf) > 32: + raise ValueError( + "buf must be a buffer protocol object with a byte length of" + "\nat least 1 and no greater than 32") + # using spec sheet calculations: assuming max time for a packet + arc = self._reg_read(4) + ard = arc >> 4 + arc &= 0xF + packet_data = 1 + 5 + 2 # preamble (1), address (5), crc (2) + bitrate = 2000000 / 8 # 2Mbps + t_ack = 0 + if not ask_no_ack: # this assumes a 32-byte ACK payload + t_ack = ((packet_data + 32) * 8 + 9) / bitrate + stby2active = (1 + (not ask_no_ack)) * 0.00013 + t_irq = 0.0000082 # max per spec sheet + t_retry = (ard * 250 + 380) * arc / 1000000 # retries * delay + timeout = ( + (((8 * (len(buf) + packet_data)) + 9) / bitrate) + + stby2active + + t_irq + + t_retry + + t_ack) + self.write(buf, ask_no_ack) + sleep(0.00001) # ensure CE pulse is >= 10 µs + self.ce_pin.value = 0 + start = monotonic() + while not (self._status & 0x30) and (monotonic() - start) < timeout: + self.update() + if arc and self.irq_df: + retry = False + for _ in range(force_retry): + retry = self.resend() + if retry is None or retry: + break # retry succeeded + result = retry + else: # if succeeded + if self._reg_read(0x1D) & 2 and self.irq_dr and not ask_no_ack: + result = self.recv() + else: + result = self.irq_ds + self.clear_status_flags(False) + return result + + @property + def irq_dr(self): + return bool(self._status & 0x40) + + @property + def irq_ds(self): + return bool(self._status & 0x20) + + @property + def irq_df(self): + return bool(self._status & 0x10) + + def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): + self._reg_write(7, (data_recv << 6) | (data_sent << 5) | (data_fail << 4)) + + def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): + config = self._reg_read(0) & 0x0F + config |= (not data_fail << 4) | (not data_sent << 5) | (not data_recv << 6) + self._reg_write(0, config) + + @property + def dynamic_payloads(self): + return bool(self._reg_read(0x1D) & 4) + + @dynamic_payloads.setter + def dynamic_payloads(self, enable): + assert isinstance(enable, (bool, int)) + features = self._reg_read(0x1D) + if bool(features & 4) != enable: + features = (features & 3) | (enable << 2) + self._reg_write(0x1D, features) + self._reg_write(0x1C, 0x3F if enable else 0) + + @property + def payload_length(self): + return self._payload_length + + @payload_length.setter + def payload_length(self, length): + # max payload size is 32 bytes + if not length or length <= 32: + self._payload_length = length + else: + raise ValueError("{}: payload length can only be in range [1,32] bytes" % length) + + @property + def arc(self): + return self._reg_read(4) & 0x0F + + @arc.setter + def arc(self, count): + if 0 <= count <= 15: + setup_retr = self._reg_read(4) + if setup_retr & 0x0F != count: + setup_retr = (setup_retr & 0xF0) | count + self._reg_write(4, setup_retr) + else: + raise ValueError("automatic re-transmit count must in range [0,15]") + + @property + def ard(self): + return ((self._reg_read(4) & 0xF0) >> 4) * 250 + 250 + + @ard.setter + def ard(self, delta_t): + if 250 <= delta_t <= 4000 and delta_t % 250 == 0: + setup_ret = self._reg_read(4) + if ((self._reg_read(4) & 0xF0) >> 4) * 250 + 250 != delta_t: + setup_retr = (int((delta_t - 250) / 250) << 4) | (setup_retr & 0x0F) + self._reg_write(4, setup_retr) + else: + raise ValueError( + "automatic re-transmit delay must be a multiple of 250 in range [250,4000]") + + @property + def auto_ack(self): + return bool(self._reg_read(1)) + + @auto_ack.setter + def auto_ack(self, enable): + assert isinstance(enable, (bool, int)) + self._reg_write(1, 0x3F if enable else 0) + + @property + def ack(self): + return bool((self._reg_read(0x1D) & 6) and self.auto_ack) + + @ack.setter + def ack(self, enable): + assert isinstance(enable, (bool, int)) + features = self._reg_read(0x1D) & 5 + if enable: + self.auto_ack = True + self._reg_write(0x1C, 0x3F) + features = (features & 3) | 4 + features |= (2 if enable else 0) + self._reg_write(0x1D, features) + + def load_ack(self, buf, pipe_number): + if pipe_number < 0 or pipe_number > 5: + raise ValueError("pipe number must be in range [0,5]") + if not buf or len(buf) > 32: + raise ValueError( + "buf must be a buffer protocol object with a byte length of" + "\nat least 1 and no greater than 32") + if not self._reg_read(0x1D) & 2: + self.ack = True + if not self.tx_full: + self._reg_write_bytes(0xA8 | pipe_number, buf) + return True + return False + + @property + def data_rate(self): + rf_setup = self._reg_read(6) & 0x28 + return (2 if rf_setup == 8 else 250) if rf_setup else 1 + + @data_rate.setter + def data_rate(self, speed): + if speed in (1, 2, 250): + speed = 0 if speed == 1 else (8 if speed == 2 else 0x20) + rf_setup = self._reg_read(6) + if ((2 if rf_setup == 8 else 250) if rf_setup else 1) != speed: + rf_setup = (rf_setup & 0xD7) | speed + self._reg_write(6, rf_setup) + else: + raise ValueError( + "data rate must be one of the following ([M,M,K]bps): 1, 2, 250") + + @property + def channel(self): + return self._reg_read(5) + + @channel.setter + def channel(self, channel): + if 0 <= channel <= 125: + self._reg_write(5, channel) + else: + raise ValueError("channel acn only be set in range [0,125]") + + @property + def crc(self): + config = self._reg_read(0) + return max(0, ((config & 12) >> 2) - 1) + + @crc.setter + def crc(self, length): + if 0 <= length <= 2: + config = self._reg_read(0) + if max(0, ((config & 12) >> 2) - 1) != length: + length = (length + 1) << 2 if length else 0 + config = (config & 0x73) | length + self._reg_write(0, config) + else: + raise ValueError("CRC byte length must be an int equal to 0 (off), 1, or 2") + + @property + def power(self): + return bool(self._reg_read(0) & 2) + + @power.setter + def power(self, is_on): + assert isinstance(is_on, (bool, int)) + config = self._reg_read(0) + if bool(config & 2) != is_on: + config = (config & 0x7D) | (is_on << 1) + self._reg_write(0, config) + # power up/down takes < 150 µs + 4 µs + sleep(0.00016) + + @property + def pa_level(self): + rf_setup = self._reg_read(6) + return (3 - ((rf_setup & 6) >> 1)) * -6 + + @pa_level.setter + def pa_level(self, power): + if power in (-18, -12, -6, 0): + power = (3 - int(power / -6)) * 2 + rf_setup = (self._reg_read(6) & 0xF9) | power + self._reg_write(6, rf_setup) + else: + raise ValueError( + "power amplitude must be one of the following (dBm): -18, -12, -6, 0") + + @property + def rpd(self): + return bool(self._reg_read(0x09)) + + @property + def tx_full(self): + return bool(self._status & 1) + + def update(self): + self._reg_write(0xFF) + + def resend(self): + result = False + if not self.fifo(True, True): + self.clear_status_flags(False) + self._reg_write(0xE3) # 0xE3 == REUSE_TX_PL command + # timeout calc assumes 32 byte payload (& 32-byte ACK if needed) + arc = self._reg_read(4) + ard = arc >> 4 + arc &= 0xF + bitrate = 2000000 / 8 # 2Mbps + # this assumes a 32-byte TX & ACK payloads + t_packet = 2 * (((8 + 32) * 8 + 9) / bitrate) + stby2active = 2 * 0.00013 + t_irq = 0.0000082 # max per spec sheet + t_retry = (ard * 250 + 380) * arc / 1000000 # retries * delay + timeout = (t_packet + stby2active + t_irq + t_retry) + self.ce_pin.value = 0 + self.ce_pin.value = 1 + sleep(0.00001) + self.ce_pin.value = 0 + start = monotonic() + while not (self._status & 0x30) and (monotonic() - start) < timeout: + self.update() + result = self.irq_ds + if self._reg_read(0x1D) & 2 and self.irq_dr: + result = self.recv() + self.clear_status_flags(False) + return result + + def write(self, buf, ask_no_ack=False): + if not buf or len(buf) > 32: + raise ValueError( + "buf must be a buffer protocol object with a byte length of" + "\nat least 1 and no greater than 32" + ) + self.clear_status_flags(False) + config = self._reg_read(0) + if config & 3 != 2: + self._reg_write(0, (config & 0x7C) | 2) + sleep(0.00016) # power up/down takes < 150 µs + 4 µs + if not self.dynamic_payloads: + if len(buf) < self.payload_length: # pad out data to fill payload_length + for _ in range(self.payload_length - len(buf)): + buf += b"\x00" + elif len(buf) > self.payload_length: # truncate data to fill payload_length + buf = buf[: self.payload_length] + if ask_no_ack: + features = self._reg_read(0x1D) + if features & 1 == 0: # set EN_DYN_ACK flag high + self._reg_write(0x1D, (features & 0xFE) | 1) + # 0xA0 = W_TX_PAYLOAD; 0xB0 = W_TX_PAYLOAD_NO_ACK + self._reg_write_bytes(0xA0 | (ask_no_ack << 4), buf) + self.ce_pin.value = 1 + + def flush_rx(self): + self._reg_write(0xE2) + + def flush_tx(self): + self._reg_write(0xE1) + + def fifo(self, about_tx=False, check_empty=None): + if (check_empty is None and isinstance(about_tx, (bool, int))) or ( + isinstance(check_empty, (bool, int)) and isinstance(about_tx, (bool, int))): + fifo = self._reg_read(0x17) + if check_empty is None: + return (fifo & (0x30 if about_tx else 0x03)) >> (4 * about_tx) + return bool(fifo & ((2 - check_empty) << (4 * about_tx))) + raise ValueError( + "Argument 1 ('about_tx') must always be a bool or int. Argument 2" + " ('check_empty'), if specified, must be a bool or int") + + def pipe(self): + self.update() + result = (self._status & 0x0E) >> 1 # 0x0E = RX_P_NO flag + if result <= 5: + return result + return None From e88e140e4aa573477d03bf312e1740624826f4fb Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 25 Mar 2020 10:08:37 -0700 Subject: [PATCH 014/127] readying github actions & updated examples' import statements --- .github/workflows/release.yml | 81 +++++++++++++++++++++ .travis.yml | 2 +- circuitpython_nrf24l01/rf24.py | 43 +++-------- circuitpython_nrf24l01/rf24_m0.py | 52 +++++-------- examples/nrf24l01_2arduino_handling_data.py | 4 +- examples/nrf24l01_ack_payload_test.py | 4 +- examples/nrf24l01_context_test.py | 3 +- examples/nrf24l01_interrupt_test.py | 4 +- examples/nrf24l01_simple_test.py | 4 +- examples/nrf24l01_stream_test.py | 4 +- 10 files changed, 126 insertions(+), 75 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..18efb9c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,81 @@ +name: Release Actions + +on: + release: + types: [published] + +jobs: + upload-release-assets: + runs-on: ubuntu-latest + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - name: Translate Repo Name For Build Tools filename_prefix + id: repo-name + run: | + echo ::set-output name=repo-name::$( + echo ${{ github.repository }} | + awk -F '\/' '{ print tolower($2) }' | + tr '_' '-' + ) + - name: Set up Python 3.6 + uses: actions/setup-python@v1 + with: + python-version: 3.6 + - name: Versions + run: | + python3 --version + - name: Checkout Current Repo + uses: actions/checkout@v1 + with: + submodules: true + - name: Checkout tools repo + uses: actions/checkout@v2 + with: + repository: adafruit/actions-ci-circuitpython-libs + path: actions-ci + - name: Install deps + run: | + source actions-ci/install.sh + - name: Build assets + run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . + - name: Upload Release Assets + # the 'official' actions version does not yet support dynamically + # supplying asset names to upload. @csexton's version chosen based on + # discussion in the issue below, as its the simplest to implement and + # allows for selecting files with a pattern. + # https://github.com/actions/upload-release-asset/issues/4 + #uses: actions/upload-release-asset@v1.0.1 + uses: csexton/release-asset-action@master + with: + pattern: "bundles/*" + github-token: ${{ secrets.GITHUB_TOKEN }} + + upload-pypi: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Check For setup.py + id: need-pypi + run: | + echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) + - name: Set up Python + if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + uses: actions/setup-python@v1 + with: + python-version: '3.x' + - name: Install dependencies + if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + env: + TWINE_USERNAME: ${{ secrets.pypi_username }} + TWINE_PASSWORD: ${{ secrets.pypi_password }} + run: | + python setup.py sdist + twine upload dist/* diff --git a/.travis.yml b/.travis.yml index f529d60..392d574 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,7 @@ install: - pip install --force-reinstall pylint==1.9.2 script: - - pylint --disable=invalid-name,too-many-instance-attributes,too-many-public-methods,too-many-arguments,too-many-function-args,too-many-locals,too-many-branches,too-many-nested-blocks,inconsistent-return-statements,too-many-statements circuitpython_nrf24l01/*.py + - pylint --disable=too-many-instance-attributes,too-many-public-methods,too-many-locals circuitpython_nrf24l01/*.py - ([[ ! -d "examples" ]] || pylint --disable=invalid-name examples/*.py) - circuitpython-build-bundles --library_location . --filename_prefix circuitpython-nrf24l01 --package_folder_prefix "circuitpython_nrf24l01" - cd docs && sphinx-build -E -W -b html . _build/html && cd .. diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index cf9ff0d..d5dde07 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -301,54 +301,31 @@ def send(self, buf, ask_no_ack=False, force_retry=0): if not buf or len(buf) > 32: raise ValueError("buf must be a buffer protocol object with a byte length of" "\nat least 1 and no greater than 32") - # using spec sheet calculations: - # timeout total = T_upload + 2 * stby2active + T_overAir + T_ack + T_irq + T_retry - # T_upload = payload length (in bits) / spi data rate (bits per second = - # baudrate / bits per byte) - # T_upload is finished before timeout begins - # T_download == T_upload, however RX devices spi settings must match TX's for - # accurate calc - # let 2 * stby2active (in µs) ~= (2 + (1 if getting ack else 0)) * 130 - # let T_ack = T_overAir as the payload size is the only distictive variable between - # the 2 - # T_overAir (in seconds) = ( 8 (bits/byte) * (1 byte preamble + address length + - # payload length + crc length) + 9 bit packet ID ) / RF data rate (in bits/sec) - # T_irq (in seconds) = (0.0000082 if self.data_rate == 1 else 0.000006) - # T_retry (in microseconds)= (arc * ard) need_ack = self._setup_retr & 0x0f and not ask_no_ack packet_data = 1 + self._addr_len + (max(0, ((self._config & 12) >> 2) - 1)) bitrate = ((2000000 if self._rf_setup & 0x28 == 8 else 250000) if self._rf_setup & 0x28 else 1000000) / 8 - t_ack = (((packet_data + 32) * 8 + 9) / bitrate) if need_ack else 0 # assumes 32-byte ACK + t_ack = (((packet_data + 32) * 8 + 9) / bitrate) if need_ack else 0 stby2active = (1 + (need_ack)) * 0.00013 t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 t_retry = (((self._setup_retr & 0xf0) >> 4) * 250 + 380) * \ (self._setup_retr & 0x0f) / 1000000 timeout = (((8 * (len(buf) + packet_data)) + 9) / bitrate) + stby2active + t_irq + t_retry + t_ack - self.write(buf, ask_no_ack) # init using non-blocking helper - time.sleep(0.00001) # ensure CE pulse is >= 10 µs - # if pulse is stopped here, the nRF24L01 only handles the top level payload in the FIFO. - # we could hold CE HIGH to continue processing through the rest of the TX FIFO bound for - # the address passed to open_tx_pipe() - self.ce_pin.value = 0 # go to Standby-I power mode (power attribute still True) + self.write(buf, ask_no_ack) + time.sleep(0.00001) + self.ce_pin.value = 0 self._wait_for_result(timeout) if self._setup_retr & 0x0f and self.irq_df: - # if auto-retransmit is on and last attempt failed - retry = False for _ in range(force_retry): - # resend() clears flags upon entering and exiting - retry = self.resend() - if retry is None or retry: + result = self.resend() + if result is None or result: break # retry succeeded - result = retry - else: # if succeeded + else: + result = self.irq_ds if self.ack and self.irq_dr and not ask_no_ack: - # if ACK payload is waiting in RX FIFO - result = self.recv() # save ACK payload & clears RX flag - else: # if auto-ack is disabled - result = self.irq_ds # will always be True (in this case) - self.clear_status_flags(False) # only TX related IRQ flags + result = self.recv() + self.clear_status_flags(False) return result @property diff --git a/circuitpython_nrf24l01/rf24_m0.py b/circuitpython_nrf24l01/rf24_m0.py index 98124e8..6b4f5bd 100644 --- a/circuitpython_nrf24l01/rf24_m0.py +++ b/circuitpython_nrf24l01/rf24_m0.py @@ -1,3 +1,4 @@ +# see license and copyright information in rf24.py of this directory # pylint: disable=missing-class-docstring,missing-function-docstring,missing-module-docstring __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/2bndy5/CircuitPython_nRF24L01.git" @@ -180,41 +181,30 @@ def send(self, buf, ask_no_ack=False, force_retry=0): "buf must be a buffer protocol object with a byte length of" "\nat least 1 and no greater than 32") # using spec sheet calculations: assuming max time for a packet - arc = self._reg_read(4) - ard = arc >> 4 - arc &= 0xF - packet_data = 1 + 5 + 2 # preamble (1), address (5), crc (2) - bitrate = 2000000 / 8 # 2Mbps + arc_d = self._reg_read(4) + need_ack = bool((arc_d & 0xF) and not ask_no_ack) t_ack = 0 - if not ask_no_ack: # this assumes a 32-byte ACK payload - t_ack = ((packet_data + 32) * 8 + 9) / bitrate - stby2active = (1 + (not ask_no_ack)) * 0.00013 - t_irq = 0.0000082 # max per spec sheet - t_retry = (ard * 250 + 380) * arc / 1000000 # retries * delay + if need_ack: + t_ack = 329 / 250000 # this assumes a 32-byte ACK payload + t_retry = ((arc_d >> 4) * 250 + 380) * (arc_d & 0xF) / 1000000 timeout = ( - (((8 * (len(buf) + packet_data)) + 9) / bitrate) - + stby2active - + t_irq - + t_retry - + t_ack) + (((8 * (len(buf) + 8)) + 9) / 250000) + + (1 + need_ack) * 0.00013 + 0.0000082 + t_retry + t_ack) self.write(buf, ask_no_ack) sleep(0.00001) # ensure CE pulse is >= 10 µs self.ce_pin.value = 0 start = monotonic() while not (self._status & 0x30) and (monotonic() - start) < timeout: self.update() - if arc and self.irq_df: - retry = False + if need_ack and self.irq_df: for _ in range(force_retry): - retry = self.resend() - if retry is None or retry: + result = self.resend() + if result is None or result: break # retry succeeded - result = retry else: # if succeeded + result = self.irq_ds if self._reg_read(0x1D) & 2 and self.irq_dr and not ask_no_ack: result = self.recv() - else: - result = self.irq_ds self.clear_status_flags(False) return result @@ -284,8 +274,8 @@ def ard(self): @ard.setter def ard(self, delta_t): if 250 <= delta_t <= 4000 and delta_t % 250 == 0: - setup_ret = self._reg_read(4) - if ((self._reg_read(4) & 0xF0) >> 4) * 250 + 250 != delta_t: + setup_retr = self._reg_read(4) + if ((setup_retr & 0xF0) >> 4) * 250 + 250 != delta_t: setup_retr = (int((delta_t - 250) / 250) << 4) | (setup_retr & 0x0F) self._reg_write(4, setup_retr) else: @@ -419,17 +409,9 @@ def resend(self): if not self.fifo(True, True): self.clear_status_flags(False) self._reg_write(0xE3) # 0xE3 == REUSE_TX_PL command - # timeout calc assumes 32 byte payload (& 32-byte ACK if needed) - arc = self._reg_read(4) - ard = arc >> 4 - arc &= 0xF - bitrate = 2000000 / 8 # 2Mbps - # this assumes a 32-byte TX & ACK payloads - t_packet = 2 * (((8 + 32) * 8 + 9) / bitrate) - stby2active = 2 * 0.00013 - t_irq = 0.0000082 # max per spec sheet - t_retry = (ard * 250 + 380) * arc / 1000000 # retries * delay - timeout = (t_packet + stby2active + t_irq + t_retry) + arc_d = self._reg_read(4) + t_retry = ((arc_d >> 4) * 250 + 380) * (arc_d & 0xF) / 1000000 + timeout = (329 / 125000 + 0.0002682 + t_retry) self.ce_pin.value = 0 self.ce_pin.value = 1 sleep(0.00001) diff --git a/examples/nrf24l01_2arduino_handling_data.py b/examples/nrf24l01_2arduino_handling_data.py index 5def331..efed24e 100644 --- a/examples/nrf24l01_2arduino_handling_data.py +++ b/examples/nrf24l01_2arduino_handling_data.py @@ -8,7 +8,9 @@ import struct import board import digitalio as dio -from circuitpython_nrf24l01 import RF24 +# if running this on a ATSAMD21 M0 based board +# from circuitpython_nrf24l01.rf24_m0 import RF24 +from circuitpython_nrf24l01.rf24 import RF24 # addresses needs to be in a buffer protocol object (bytearray) address = [b'1Node', b'2Node'] diff --git a/examples/nrf24l01_ack_payload_test.py b/examples/nrf24l01_ack_payload_test.py index 04f2e3b..371a480 100644 --- a/examples/nrf24l01_ack_payload_test.py +++ b/examples/nrf24l01_ack_payload_test.py @@ -5,7 +5,9 @@ import time import board import digitalio as dio -from circuitpython_nrf24l01 import RF24 +# if running this on a ATSAMD21 M0 based board +# from circuitpython_nrf24l01.rf24_m0 import RF24 +from circuitpython_nrf24l01.rf24 import RF24 # change these (digital output) pins accordingly ce = dio.DigitalInOut(board.D4) diff --git a/examples/nrf24l01_context_test.py b/examples/nrf24l01_context_test.py index ce44c82..ca2c574 100644 --- a/examples/nrf24l01_context_test.py +++ b/examples/nrf24l01_context_test.py @@ -5,7 +5,8 @@ """ import board import digitalio as dio -from circuitpython_nrf24l01 import RF24 +# this script is not compatible with rf24_m0 for ATSAMD21 M0 based board +from circuitpython_nrf24l01.rf24 import RF24 # change these (digital output) pins accordingly ce = dio.DigitalInOut(board.D4) diff --git a/examples/nrf24l01_interrupt_test.py b/examples/nrf24l01_interrupt_test.py index aa6ebe7..8d761fb 100644 --- a/examples/nrf24l01_interrupt_test.py +++ b/examples/nrf24l01_interrupt_test.py @@ -5,7 +5,9 @@ import time import board import digitalio as dio -from circuitpython_nrf24l01 import RF24 +# if running this on a ATSAMD21 M0 based board +# from circuitpython_nrf24l01.rf24_m0 import RF24 +from circuitpython_nrf24l01.rf24 import RF24 # address needs to be in a buffer protocol object (bytearray is preferred) address = b'1Node' diff --git a/examples/nrf24l01_simple_test.py b/examples/nrf24l01_simple_test.py index 6ab6e5a..cd43349 100644 --- a/examples/nrf24l01_simple_test.py +++ b/examples/nrf24l01_simple_test.py @@ -5,7 +5,9 @@ import struct import board import digitalio as dio -from circuitpython_nrf24l01 import RF24 +# if running this on a ATSAMD21 M0 based board +# from circuitpython_nrf24l01.rf24_m0 import RF24 +from circuitpython_nrf24l01.rf24 import RF24 # addresses needs to be in a buffer protocol object (bytearray) address = b'1Node' diff --git a/examples/nrf24l01_stream_test.py b/examples/nrf24l01_stream_test.py index b4a3810..7a99832 100644 --- a/examples/nrf24l01_stream_test.py +++ b/examples/nrf24l01_stream_test.py @@ -4,7 +4,9 @@ import time import board import digitalio as dio -from circuitpython_nrf24l01 import RF24 +# if running this on a ATSAMD21 M0 based board +# from circuitpython_nrf24l01.rf24_m0 import RF24 +from circuitpython_nrf24l01.rf24 import RF24 # addresses needs to be in a buffer protocol object (bytearray) address = b'1Node' From 4f2e9e9f4d503ffe23f184890bfea5022d6db40a Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 25 Mar 2020 03:50:40 -0700 Subject: [PATCH 015/127] Create build.yml --- .github/workflows/build.yml | 53 +++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..dc31632 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,53 @@ +name: Build CI + +on: [pull_request, push] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - name: Translate Repo Name For Build Tools filename_prefix + id: repo-name + run: | + echo ::set-output name=repo-name::$( + echo ${{ github.repository }} | + awk -F '\/' '{ print tolower($2) }' | + tr '_' '-' + ) + - name: Set up Python 3.6 + uses: actions/setup-python@v1 + with: + python-version: 3.6 + - name: Versions + run: | + python3 --version + - name: Checkout Current Repo + uses: actions/checkout@v1 + with: + submodules: true + - name: Checkout tools repo + uses: actions/checkout@v2 + with: + repository: adafruit/actions-ci-circuitpython-libs + path: actions-ci + - name: Install deps + run: | + source actions-ci/install.sh + - name: Pip install pylint, black, & Sphinx + run: | + pip install --force-reinstall pylint==1.9.2 black==19.10b0 Sphinx sphinx-rtd-theme + - name: Library version + run: git describe --dirty --always --tags + - name: PyLint + run: | + pylint --disable=too-many-instance-attributes,too-many-public-methods,too-many-locals circuitpython_nrf24l01/*.py + ([[ ! -d "examples" ]] || pylint --disable=invalid-name $( find . -path "./examples/*.py" )) + - name: Build assets + run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . --package_folder_prefix circuitpython_nrf24l01 + - name: Build docs + working-directory: docs + run: sphinx-build -E -W -b html . _build/html From 55b8186b4a7374cf04eae3d47062bc9adf455bff Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 25 Mar 2020 03:56:53 -0700 Subject: [PATCH 016/127] Update build.yml --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc31632..b4ec6d8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,7 @@ jobs: source actions-ci/install.sh - name: Pip install pylint, black, & Sphinx run: | - pip install --force-reinstall pylint==1.9.2 black==19.10b0 Sphinx sphinx-rtd-theme + pip install --force-reinstall pylint==2.4.1 Sphinx sphinx-rtd-theme - name: Library version run: git describe --dirty --always --tags - name: PyLint From fb07eb468e17189bc08a2bd8f4754d053f2b497d Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 25 Mar 2020 04:21:36 -0700 Subject: [PATCH 017/127] fixed gitignore for "build" actions --- .gitignore | 136 ++++++++++++++++++++++++++++++++++++++++++++++----- .travis.yml | 44 ----------------- README.rst | 6 +-- docs/api.rst | 2 +- 4 files changed, 129 insertions(+), 59 deletions(-) delete mode 100644 .travis.yml diff --git a/.gitignore b/.gitignore index 01d8dc6..d608cec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,127 @@ -*.mpy -.idea -__pycache__ -_build -*.pyc +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments .env -build* -bundles -*.DS_Store -.eggs -dist -**/*.egg-info +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# VS Code folder .vscode diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 392d574..0000000 --- a/.travis.yml +++ /dev/null @@ -1,44 +0,0 @@ -# This is a common .travis.yml for generating library release zip files for -# CircuitPython library releases using circuitpython-build-tools. -# See https://github.com/adafruit/circuitpython-build-tools for detailed setup -# instructions. - -dist: xenial -language: python -python: - - "3.6" - -cache: - pip: true -env: - - DEPLOY_PYPI="true" - -deploy: - - provider: releases - api_key: "$GITHUB_TOKEN" - file_glob: true - file: "$TRAVIS_BUILD_DIR/bundles/*" - skip_cleanup: true - overwrite: true - on: - tags: true - # TODO: Use 'travis encrypt your-password-here' to generate - # the encrypted password for your account. Paste result below. - - provider: pypi - user: 2bndy5 - password: - secure: "Rn5kjXiWeG8Qlex6ZdS8sontrTaYZIFt7O/6xP5mfp9n1uxyumzteN5llI6wCDxlDPMtsvfhbWMvK+zeGJUQLyzK52t5IIWo0vULbIIZRdaJ74Djsnxh43i8siYSlNu+4VCpqfdR5o9wEU8lte53RtST9DLLk1NgokPBrRuC1uj5WGHnbVaq24QjWpDYWFw+/MtFSSbBGDPDf/s1qJnsH4LSUF95hnl1hwEyk7+0euy3wcpmDB1gqC+RRmwACiVP0qXpIroEVjRvKMjq9C0VvUQowZ/urt4isWVYrO/JT+WA2/BcLep1ep7zF46YUob1HxUqgsx04xx4y8EBbwc6yuI8xbukBIqk6DzEaFZAn5b/JFRzAOT9hbg3WYjkmtSQ2WIfb++ThnadfPhgQegrD0yBhb8UsQZeOXVWUEeSwQm4p1qQYeftfptLCkdGkamOzaijABYH8g4Rsj3oisqRMBkKE55lMcX/quggchSvASi/A/OhR/LcNX58m5yOu4rEWxdh6UEV+X7zpWDdqSMwYnCfAvrg+J7SjOYVmrfefCKRbu5I1MHO9tivZ33izUKxYeko5AexAqkoD+y1Na2PfMZi2tv2XtqPBdA6mzQwnWLNNp+xjSgntUc5VS97CpcJ1YDWiXdmhXVwy3bq1+cyN3npsAKeEySowDgtJWsS5yo=" - on: - tags: true - condition: $DEPLOY_PYPI = "true" - -install: - - pip install -r requirements.txt - - pip install circuitpython-build-tools Sphinx sphinx-rtd-theme - - pip install --force-reinstall pylint==1.9.2 - -script: - - pylint --disable=too-many-instance-attributes,too-many-public-methods,too-many-locals circuitpython_nrf24l01/*.py - - ([[ ! -d "examples" ]] || pylint --disable=invalid-name examples/*.py) - - circuitpython-build-bundles --library_location . --filename_prefix circuitpython-nrf24l01 --package_folder_prefix "circuitpython_nrf24l01" - - cd docs && sphinx-build -E -W -b html . _build/html && cd .. diff --git a/README.rst b/README.rst index 725ec37..caafc4e 100644 --- a/README.rst +++ b/README.rst @@ -10,8 +10,8 @@ Introduction :target: https://circuitpython-nrf24l01.readthedocs.io/en/stable/ :alt: Documentation Status -.. image:: https://travis-ci.org/2bndy5/CircuitPython_nRF24L01.svg?branch=master - :target: https://travis-ci.org/2bndy5/CircuitPython_nRF24L01 +.. image:: https://github.com/2bndy5/CircuitPython_nRF24L01/workflows/Build%20CI/badge.svg + :target: https://github.com/2bndy5/CircuitPython_nRF24L01/actions/ :alt: Build Status .. image:: https://img.shields.io/pypi/v/circuitpython-nrf24l01.svg @@ -44,7 +44,7 @@ Features currently supported * flag a single payload for no acknowledgment (ACK) from the receiving nRF24L01 * "re-use the same payload" feature (for manually re-transmitting failed transmissions that remain in the buffer) * multiple payload transmissions with one function call (MUST read documentation on the `send()` function) -* context manager compatible for easily switching between different radio configurations using "with" statements +* context manager compatible for easily switching between different radio configurations using "with" statements (not available in ``rf24_m0.py`` variant for M0 based boards) * configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or failed transmissions (these 3 flags control the 1 IRQ pin). There's also virtual representations of these interrupt flags available (see `irq_dr`, `irq_ds`, `irq_df` attributes) * invoke sleep mode (AKA power down mode) for ultra-low current consumption * cyclic redundancy checking (CRC) up to 2 bytes long diff --git a/docs/api.rst b/docs/api.rst index 062e914..f2da9b1 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -280,7 +280,7 @@ what_happened() .. automethod:: circuitpython_nrf24l01.rf24.RF24.what_happened from the nRF24L01. Some information may be irrelevant depending on nRF24L01's - state/condition. + state/condition. (not available in ``rf24_m0.py`` variant for M0 based boards) :prints: From 61fef475e719fd58d714a6e197ede1769913aebf Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 25 Mar 2020 11:54:56 -0700 Subject: [PATCH 018/127] docs tweaks --- docs/_static/darkness.css | 9 +++++++++ docs/api.rst | 4 ---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/_static/darkness.css b/docs/_static/darkness.css index c14a4cf..c78d90e 100644 --- a/docs/_static/darkness.css +++ b/docs/_static/darkness.css @@ -259,6 +259,15 @@ body { } /* -----------------------------------------API sections-------------------------------------- */ + +.wy-table-bordered-all td, .rst-content table.docutils td { + text-align: left; +} + +.rst-content table.docutils.citation, .rst-content table.docutils.footnote { + color: white; +} + .rst-content dl:not(.docutils) dl dt { background: #343434; color: #e5df8e; diff --git a/docs/api.rst b/docs/api.rst index f2da9b1..4dad911 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,8 +1,4 @@ -.. If you created a package, create one automodule per module in the package. - -.. If your library file(s) are nested in a directory (e.g. /adafruit_foo/foo.py) -.. use this format as the module name: "adafruit_foo.foo" .. currentmodule:: circuitpython_nrf24l01.rf24 From 0e9fc313eadd3c4d99dbcd33ad008305f14d5c5c Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 25 Mar 2020 13:42:39 -0700 Subject: [PATCH 019/127] using template for repo name in "build assets" job --- .github/workflows/build.yml | 4 ++-- .github/workflows/release.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b4ec6d8..0bc7473 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,7 +37,7 @@ jobs: - name: Install deps run: | source actions-ci/install.sh - - name: Pip install pylint, black, & Sphinx + - name: Pip install pylint & Sphinx run: | pip install --force-reinstall pylint==2.4.1 Sphinx sphinx-rtd-theme - name: Library version @@ -47,7 +47,7 @@ jobs: pylint --disable=too-many-instance-attributes,too-many-public-methods,too-many-locals circuitpython_nrf24l01/*.py ([[ ! -d "examples" ]] || pylint --disable=invalid-name $( find . -path "./examples/*.py" )) - name: Build assets - run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . --package_folder_prefix circuitpython_nrf24l01 + run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . --package_folder_prefix ${{ steps.repo-name.outputs.repo-name }} - name: Build docs working-directory: docs run: sphinx-build -E -W -b html . _build/html diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 18efb9c..e4dd574 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,7 +40,7 @@ jobs: run: | source actions-ci/install.sh - name: Build assets - run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . + run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . --package_folder_prefix ${{ steps.repo-name.outputs.repo-name }} - name: Upload Release Assets # the 'official' actions version does not yet support dynamically # supplying asset names to upload. @csexton's version chosen based on From b1a9c0d84a755e26bbacde7c93b775c1f76aa705 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 25 Mar 2020 14:58:56 -0700 Subject: [PATCH 020/127] reducing even more! documented changes in lite version --- circuitpython_nrf24l01/rf24_m0.py | 179 +++++++++--------------------- docs/api.rst | 18 +++ 2 files changed, 68 insertions(+), 129 deletions(-) diff --git a/circuitpython_nrf24l01/rf24_m0.py b/circuitpython_nrf24l01/rf24_m0.py index 6b4f5bd..ad2b165 100644 --- a/circuitpython_nrf24l01/rf24_m0.py +++ b/circuitpython_nrf24l01/rf24_m0.py @@ -2,26 +2,24 @@ # pylint: disable=missing-class-docstring,missing-function-docstring,missing-module-docstring __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/2bndy5/CircuitPython_nRF24L01.git" -from time import sleep, monotonic +import time from adafruit_bus_device.spi_device import SPIDevice class RF24: def __init__(self, spi, csn, ce): self._pipe0_read_addr = None - self._spi = SPIDevice(spi, chip_select=csn, baudrate=1250000) + self._status = 0 + self._payload_length = 32 self.ce_pin = ce self.ce_pin.switch_to_output(value=False) + self._spi = SPIDevice(spi, chip_select=csn, baudrate=1250000) self._reg_write(0, 0x0E) - self._status = 0 - self._payload_length = 32 - hw_check = self._reg_read(0) - if hw_check & 3 == 2: + if self._reg_read(0) & 3 == 2: self.power = False else: - print(bin(hw_check)) raise RuntimeError("nRF24L01 Hardware not responding") self.channel = 76 - self.address_length = 5 + self._reg_write(3, 3) self._reg_write(6, 6) self._reg_write(2, 0) self._reg_write(0x1C, 0x3F) @@ -37,7 +35,7 @@ def __init__(self, spi, csn, ce): def _reg_read(self, reg): buf = bytearray(2) with self._spi as spi: - sleep(0.005) + time.sleep(0.005) spi.readinto(buf, write_value=reg) self._status = buf[0] return buf[1] @@ -45,7 +43,7 @@ def _reg_read(self, reg): def _reg_read_bytes(self, reg, buf_len=5): buf = bytearray(buf_len + 1) with self._spi as spi: - sleep(0.005) + time.sleep(0.005) spi.readinto(buf, write_value=reg) self._status = buf[0] return buf[1:] @@ -54,7 +52,7 @@ def _reg_write_bytes(self, reg, out_buf): out_buf = bytes([0x20 | reg]) + out_buf in_buf = bytearray(len(out_buf)) with self._spi as spi: - sleep(0.005) + time.sleep(0.005) spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] @@ -65,50 +63,36 @@ def _reg_write(self, reg, value=None): out_buf = bytes([0x20 | reg, value]) in_buf = bytearray(len(out_buf)) with self._spi as spi: - sleep(0.005) + time.sleep(0.005) spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] # pylint: enable=no-member - @property - def address_length(self): - return self._reg_read(3) + 2 - - @address_length.setter - def address_length(self, length): - if 3 <= length <= 5: - self._reg_write(3, length - 2) - else: - raise ValueError("address length can only be set in range [3,5] bytes") - def open_tx_pipe(self, address): - if len(address) == self.address_length: - if self.auto_ack: - self._reg_write_bytes(0x0A, address) - self._reg_write(2, self._reg_read(2) | 1) - self._reg_write(0x11, self.payload_length) + if len(address) == 5: + self._reg_write_bytes(0x0A, address) + self._reg_write(2, self._reg_read(2) | 1) + self._reg_write(0x11, self.payload_length) self._reg_write_bytes(0x10, address) else: raise ValueError( "address must be a buffer protocol object with a byte length\nequal " - "to the address_length attribute (currently set to" - " {})".format(self.address_length)) + "to the address_length attribute (currently set to 5)") def close_rx_pipe(self, pipe_number): if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0,5]") + raise ValueError("pipe number must be in range [0, 5]") open_pipes = self._reg_read(2) if open_pipes & (1 << pipe_number): self._reg_write(2, open_pipes & ~(1 << pipe_number)) def open_rx_pipe(self, pipe_number, address): if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0,5]") - if len(address) != self.address_length: + raise ValueError("pipe number must be in range [0, 5]") + if len(address) != 5: raise ValueError( "address must be a buffer protocol object with a byte length\nequal " - "to the address_length attribute (currently set to " - "{})".format(self.address_length)) + "to the address_length attribute (currently set to 5)") if pipe_number < 2: if not pipe_number: self._pipe0_read_addr = address @@ -136,17 +120,17 @@ def _start_listening(self): if self._pipe0_read_addr is not None: self._reg_write_bytes(0x0A, self._pipe0_read_addr) self._reg_write(0, self._reg_read(0) & 0xFC | 3) - sleep(0.00015) # mandatory wait to power up radio + time.sleep(0.00015) self.flush_rx() self.clear_status_flags(True, False, False) - self.ce_pin.value = 1 # mandatory pulse is > 130 µs - sleep(0.00013) + self.ce_pin.value = 1 + time.sleep(0.00013) def _stop_listening(self): if self.ce_pin.value: self.ce_pin.value = 0 self._reg_write(0, self._reg_read(0) & 0xFE) - sleep(0.00016) + time.sleep(0.00016) def any(self): if self._reg_read(0x1D) & 4: @@ -157,7 +141,7 @@ def any(self): def recv(self): pl_wid = self.any() - result = self._reg_read_bytes(0x61, pl_wid) # 0x61 = R_RX_PAYLOAD + result = self._reg_read_bytes(0x61, pl_wid) self.clear_status_flags(True, False, False) return result @@ -166,42 +150,38 @@ def send(self, buf, ask_no_ack=False, force_retry=0): self.flush_tx() if isinstance(buf, (list, tuple)): result = [] - for i, b in enumerate(buf): # check invalid payloads first + for i, b in enumerate(buf): if not b or len(b) > 32: raise ValueError( "buf (item {} in the list/tuple) must be a" - " buffer protocol object with a byte length of\nat least 1 " - "and no greater than 32".format(i)) + " buffer protocol object with length in range [1, 32]" % i) for i, b in enumerate(buf): - # use recursion for each payload result.append(self.send(b, ask_no_ack, force_retry)) return result if not buf or len(buf) > 32: raise ValueError( - "buf must be a buffer protocol object with a byte length of" - "\nat least 1 and no greater than 32") - # using spec sheet calculations: assuming max time for a packet + "buf must be a buffer protocol object with length in range [1, 32]") arc_d = self._reg_read(4) need_ack = bool((arc_d & 0xF) and not ask_no_ack) t_ack = 0 if need_ack: - t_ack = 329 / 250000 # this assumes a 32-byte ACK payload + t_ack = 329 / 250000 t_retry = ((arc_d >> 4) * 250 + 380) * (arc_d & 0xF) / 1000000 timeout = ( (((8 * (len(buf) + 8)) + 9) / 250000) + (1 + need_ack) * 0.00013 + 0.0000082 + t_retry + t_ack) self.write(buf, ask_no_ack) - sleep(0.00001) # ensure CE pulse is >= 10 µs + time.sleep(0.00001) self.ce_pin.value = 0 - start = monotonic() - while not (self._status & 0x30) and (monotonic() - start) < timeout: + start = time.monotonic() + while not (self._status & 0x30) and (time.monotonic() - start) < timeout: self.update() if need_ack and self.irq_df: for _ in range(force_retry): result = self.resend() if result is None or result: - break # retry succeeded - else: # if succeeded + break + else: result = self.irq_ds if self._reg_read(0x1D) & 2 and self.irq_dr and not ask_no_ack: result = self.recv() @@ -251,7 +231,7 @@ def payload_length(self, length): if not length or length <= 32: self._payload_length = length else: - raise ValueError("{}: payload length can only be in range [1,32] bytes" % length) + raise ValueError("{}: payload length can only be in range [1, 32] bytes" % length) @property def arc(self): @@ -265,7 +245,7 @@ def arc(self, count): setup_retr = (setup_retr & 0xF0) | count self._reg_write(4, setup_retr) else: - raise ValueError("automatic re-transmit count must in range [0,15]") + raise ValueError("automatic re-transmit count must in range [0, 15]") @property def ard(self): @@ -282,25 +262,16 @@ def ard(self, delta_t): raise ValueError( "automatic re-transmit delay must be a multiple of 250 in range [250,4000]") - @property - def auto_ack(self): - return bool(self._reg_read(1)) - - @auto_ack.setter - def auto_ack(self, enable): - assert isinstance(enable, (bool, int)) - self._reg_write(1, 0x3F if enable else 0) - @property def ack(self): - return bool((self._reg_read(0x1D) & 6) and self.auto_ack) + return bool((self._reg_read(0x1D) & 6) and self._reg_read(1)) @ack.setter def ack(self, enable): assert isinstance(enable, (bool, int)) features = self._reg_read(0x1D) & 5 if enable: - self.auto_ack = True + self._reg_write(1, 0x3F) self._reg_write(0x1C, 0x3F) features = (features & 3) | 4 features |= (2 if enable else 0) @@ -308,14 +279,13 @@ def ack(self, enable): def load_ack(self, buf, pipe_number): if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0,5]") + raise ValueError("pipe number must be in range [0, 5]") if not buf or len(buf) > 32: raise ValueError( - "buf must be a buffer protocol object with a byte length of" - "\nat least 1 and no greater than 32") + "buf must be a buffer protocol object with length in range [1, 32]") if not self._reg_read(0x1D) & 2: self.ack = True - if not self.tx_full: + if not self._status & 1: self._reg_write_bytes(0xA8 | pipe_number, buf) return True return False @@ -348,22 +318,6 @@ def channel(self, channel): else: raise ValueError("channel acn only be set in range [0,125]") - @property - def crc(self): - config = self._reg_read(0) - return max(0, ((config & 12) >> 2) - 1) - - @crc.setter - def crc(self, length): - if 0 <= length <= 2: - config = self._reg_read(0) - if max(0, ((config & 12) >> 2) - 1) != length: - length = (length + 1) << 2 if length else 0 - config = (config & 0x73) | length - self._reg_write(0, config) - else: - raise ValueError("CRC byte length must be an int equal to 0 (off), 1, or 2") - @property def power(self): return bool(self._reg_read(0) & 2) @@ -376,7 +330,7 @@ def power(self, is_on): config = (config & 0x7D) | (is_on << 1) self._reg_write(0, config) # power up/down takes < 150 µs + 4 µs - sleep(0.00016) + time.sleep(0.00016) @property def pa_level(self): @@ -393,31 +347,23 @@ def pa_level(self, power): raise ValueError( "power amplitude must be one of the following (dBm): -18, -12, -6, 0") - @property - def rpd(self): - return bool(self._reg_read(0x09)) - - @property - def tx_full(self): - return bool(self._status & 1) - def update(self): self._reg_write(0xFF) def resend(self): result = False - if not self.fifo(True, True): + if not self._reg_read(0x17) & 0x10: self.clear_status_flags(False) - self._reg_write(0xE3) # 0xE3 == REUSE_TX_PL command + self._reg_write(0xE3) arc_d = self._reg_read(4) t_retry = ((arc_d >> 4) * 250 + 380) * (arc_d & 0xF) / 1000000 timeout = (329 / 125000 + 0.0002682 + t_retry) self.ce_pin.value = 0 self.ce_pin.value = 1 - sleep(0.00001) + time.sleep(0.00001) self.ce_pin.value = 0 - start = monotonic() - while not (self._status & 0x30) and (monotonic() - start) < timeout: + start = time.monotonic() + while not (self._status & 0x30) and (time.monotonic() - start) < timeout: self.update() result = self.irq_ds if self._reg_read(0x1D) & 2 and self.irq_dr: @@ -428,25 +374,18 @@ def resend(self): def write(self, buf, ask_no_ack=False): if not buf or len(buf) > 32: raise ValueError( - "buf must be a buffer protocol object with a byte length of" - "\nat least 1 and no greater than 32" - ) + "buf must be a buffer protocol object with length in range [1, 32]") self.clear_status_flags(False) config = self._reg_read(0) if config & 3 != 2: self._reg_write(0, (config & 0x7C) | 2) - sleep(0.00016) # power up/down takes < 150 µs + 4 µs + time.sleep(0.00016) if not self.dynamic_payloads: - if len(buf) < self.payload_length: # pad out data to fill payload_length + if len(buf) < self.payload_length: for _ in range(self.payload_length - len(buf)): buf += b"\x00" - elif len(buf) > self.payload_length: # truncate data to fill payload_length + elif len(buf) > self.payload_length: buf = buf[: self.payload_length] - if ask_no_ack: - features = self._reg_read(0x1D) - if features & 1 == 0: # set EN_DYN_ACK flag high - self._reg_write(0x1D, (features & 0xFE) | 1) - # 0xA0 = W_TX_PAYLOAD; 0xB0 = W_TX_PAYLOAD_NO_ACK self._reg_write_bytes(0xA0 | (ask_no_ack << 4), buf) self.ce_pin.value = 1 @@ -455,21 +394,3 @@ def flush_rx(self): def flush_tx(self): self._reg_write(0xE1) - - def fifo(self, about_tx=False, check_empty=None): - if (check_empty is None and isinstance(about_tx, (bool, int))) or ( - isinstance(check_empty, (bool, int)) and isinstance(about_tx, (bool, int))): - fifo = self._reg_read(0x17) - if check_empty is None: - return (fifo & (0x30 if about_tx else 0x03)) >> (4 * about_tx) - return bool(fifo & ((2 - check_empty) << (4 * about_tx))) - raise ValueError( - "Argument 1 ('about_tx') must always be a bool or int. Argument 2" - " ('check_empty'), if specified, must be a bool or int") - - def pipe(self): - self.update() - result = (self._status & 0x0E) >> 1 # 0x0E = RX_P_NO flag - if result <= 5: - return result - return None diff --git a/docs/api.rst b/docs/api.rst index 4dad911..5842580 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -71,6 +71,24 @@ With the `auto_ack` feature enabled, you get: configuration (see `send()` & `write()` function parameters for more details). +About the lite version +---------------------- + +This library contains a "lite" version of ``rf24.py`` titled ``rf24_lite.py``. It has been +developed to save space on microcontrollers with limited amount of RAM and/or storage (like boards +using the ATSAMD21 M0). The following functionality has been removed from the lite version: + + * `address` + * `rpd` + * `pipe()` + * `fifo()` + * `tx_full` + * `address_length` (this is always set to 5 bytes) + * `read_ack()` (depricated anyway; use `recv()` instead) + * `crc` (always using 2 bytes encoding scheme) + * `auto_ack` (always on) + * all comments and docstrings (meaning ``help()`` will not provide any specific information) + Basic API --------- From 65d3b63becb2c30040ce10f89599185acd2e83c0 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Wed, 25 Mar 2020 15:00:37 -0700 Subject: [PATCH 021/127] renaming file for implicit meaning --- circuitpython_nrf24l01/{rf24_m0.py => rf24_lite.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename circuitpython_nrf24l01/{rf24_m0.py => rf24_lite.py} (100%) diff --git a/circuitpython_nrf24l01/rf24_m0.py b/circuitpython_nrf24l01/rf24_lite.py similarity index 100% rename from circuitpython_nrf24l01/rf24_m0.py rename to circuitpython_nrf24l01/rf24_lite.py From 73ede457af5f794ee297d6568fa6c64f016c07df Mon Sep 17 00:00:00 2001 From: 2bndy5 <2bndy5@gmail.com> Date: Wed, 25 Mar 2020 16:23:29 -0700 Subject: [PATCH 022/127] update rf24.py w/ some optimizations from lite ver --- .github/workflows/build.yml | 2 +- circuitpython_nrf24l01/rf24.py | 108 ++++++++++++++------------------- 2 files changed, 46 insertions(+), 64 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0bc7473..bbc1ea1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,7 +44,7 @@ jobs: run: git describe --dirty --always --tags - name: PyLint run: | - pylint --disable=too-many-instance-attributes,too-many-public-methods,too-many-locals circuitpython_nrf24l01/*.py + pylint --disable=too-many-instance-attributes,too-many-public-methods circuitpython_nrf24l01/*.py ([[ ! -d "examples" ]] || pylint --disable=invalid-name $( find . -path "./examples/*.py" )) - name: Build assets run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . --package_folder_prefix ${{ steps.repo-name.outputs.repo-name }} diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index d5dde07..e9df89b 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -49,9 +49,8 @@ class RF24: """A driver class for the nRF24L01(+) transceiver radios.""" def __init__(self, spi, csn, ce): - self._payload_length = 32 # inits internal attribute + self._payload_length = 32 self.payload_length = 32 - # last address assigned to pipe0 for reading. init to None self._fifo = 0 self._status = 0 # init shadow copy of RX addresses for all pipes @@ -82,19 +81,19 @@ def __init__(self, spi, csn, ce): # shadow copy of the TX_ADDR self._tx_address = self._reg_read_bytes(TX_ADDR) # configure the SETUP_RETR register - self._setup_retr = 0x53 #ard = 1500; arc = 3 + self._setup_retr = 0x53 # ard = 1500; arc = 3 # configure the RF_SETUP register self._rf_setup = 0x06 # 1 Mbps data_rate, and 0 dbm pa_level # configure dynamic_payloads & auto_ack for RX operations self._dyn_pl = 0x3F # 0x3F = dynamic_payloads enabled on all pipes self._aa = 0x3F # 0x3F = auto_ack enabled on all pipes # configure features for TX operations - # 5 = enable dynamic_payloads, disable custom ack payloads, and allow ask_no_ack command + # 5 = enable dynamic_payloads, disable custom ack payloads, & allow ask_no_ack command self._features = 5 self._channel = 76 self._addr_len = 5 - with self: # write to registers & power up + with self: self.ce_pin.value = 0 self._reg_write(CONFIG, self._config | 1) time.sleep(0.000015) @@ -177,7 +176,7 @@ def address_length(self, length): self._reg_write(SETUP_AW, length - 2) else: raise ValueError( - "address length can only be set in range [3,5] bytes") + "address length can only be set in range [3, 5] bytes") def open_tx_pipe(self, address): """This function is used to open a data pipe for OTA (over the air) TX @@ -268,39 +267,37 @@ def _stop_listening(self): def any(self): """This function checks if the nRF24L01 has received any data at all, and then reports the next available payload's length (in bytes)""" - return self._reg_read(0x60) # 0x60 = R_RX_PL_WID command + self._features = self._reg_read(FEATURE) + if self._features & 4: + return self._reg_read(0x60) + if self.irq_dr: + return self._reg_read(RX_PW + ((self._status & 0xE) >> 1)) + return 0 def recv(self): """This function is used to retrieve the next available payload""" if not self.irq_dr: return None - curr_pl_size = self.payload_length if not self.dynamic_payloads else self.any() - result = self._reg_read_bytes(0x61, curr_pl_size) # 0x61 = R_RX_PAYLOAD + curr_pl_size = self.any() + result = self._reg_read_bytes(0x61, curr_pl_size) self.clear_status_flags(True, False, False) return result def send(self, buf, ask_no_ack=False, force_retry=0): """This blocking function is used to transmit payload(s).""" - # ensure power down/standby-I for proper manipulation of PWR_UP & PRIM_RX bits in - # CONFIG register self.ce_pin.value = 0 - self.flush_tx() # be sure there is space in the TX FIFO - if isinstance(buf, (list, tuple)): # writing a set of payloads + self.flush_tx() + if isinstance(buf, (list, tuple)): result = [] - for i, b in enumerate(buf): # check invalid payloads first - # this way when we raise a ValueError exception we don't leave the nRF24L01 in an - # unknown frozen state. + for i, b in enumerate(buf): if not b or len(b) > 32: raise ValueError("buf (item {} in the list/tuple) must be a" - " buffer protocol object with a byte length of\nat least 1 " - "and no greater than 32".format(i)) + " buffer protocol object with length in range [1, 32]" % i) for i, b in enumerate(buf): - # use recursion for each payload result.append(self.send(b, ask_no_ack, force_retry)) return result if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with a byte length of" - "\nat least 1 and no greater than 32") + raise ValueError("buf must be a buffer protocol object with length in range [1, 32]") need_ack = self._setup_retr & 0x0f and not ask_no_ack packet_data = 1 + self._addr_len + (max(0, ((self._config & 12) >> 2) - 1)) bitrate = ((2000000 if self._rf_setup & 0x28 == 8 else 250000) @@ -310,17 +307,16 @@ def send(self, buf, ask_no_ack=False, force_retry=0): t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 t_retry = (((self._setup_retr & 0xf0) >> 4) * 250 + 380) * \ (self._setup_retr & 0x0f) / 1000000 - timeout = (((8 * (len(buf) + packet_data)) + 9) / - bitrate) + stby2active + t_irq + t_retry + t_ack self.write(buf, ask_no_ack) time.sleep(0.00001) self.ce_pin.value = 0 - self._wait_for_result(timeout) + self._wait_for_result((((8 * (len(buf) + packet_data)) + 9) / + bitrate) + stby2active + t_irq + t_retry + t_ack) if self._setup_retr & 0x0f and self.irq_df: for _ in range(force_retry): result = self.resend() if result is None or result: - break # retry succeeded + break else: result = self.irq_ds if self.ack and self.irq_dr and not ask_no_ack: @@ -345,7 +341,6 @@ def irq_df(self): def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): """This clears the interrupt flags in the status register.""" - # 0x07 = STATUS register self._reg_write(0x07, (data_recv << 6) | ( data_sent << 5) | (data_fail << 4)) @@ -359,7 +354,7 @@ def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): def what_happened(self, dump_pipes=False): """This debuggung function aggregates and outputs all status/condition related information""" - watchdog = self._reg_read(8) # 8 == OBSERVE_TX register + watchdog = self._reg_read(8) print("Channel___________________{} ~ {} GHz".format( self.channel, (self.channel + 2400) / 1000)) print("RF Data Rate______________{} {}".format( @@ -393,15 +388,11 @@ def what_happened(self, dump_pipes=False): 'RX' if self.listen else 'TX', ('Standby-II' if self.ce_pin.value else 'Standby-I') if self._config & 2 else 'Off')) if dump_pipes: - print('TX address____________', self._tx_address) - for i, address in enumerate(self._pipes): + print('TX address____________', self.address()) + for i in range(6): is_open = "( open )" if self._open_pipes & (1 << i) else "(closed)" - if i <= 1: # print full address - print("Pipe", i, is_open, "bound:", address) - else: # print unique byte + shared bytes = actual address used by radio - print("Pipe", i, is_open, "bound:", - bytes([self._pipes[i]]) + self._pipes[1][1:]) - if self._open_pipes & (1 << i): + print("Pipe", i, is_open, "bound:", self.address(i)) + if is_open: print('\t\texpecting', self._payload_widths[i], 'byte static payloads') @property @@ -417,7 +408,7 @@ def dynamic_payloads(self, enable): if self._features & 4 != enable: self._features = (self._features & 3) | (enable << 2) self._reg_write(FEATURE, self._features) - self._dyn_pl = 0x3F if enable else 0 # 0x3F = enable dynamic payloads all pipes + self._dyn_pl = 0x3F if enable else 0 self._reg_write(DYNPD, self._dyn_pl) @property @@ -427,12 +418,11 @@ def payload_length(self): @payload_length.setter def payload_length(self, length): - # max payload size is 32 bytes if not length or length <= 32: self._payload_length = length else: raise ValueError( - "{}: payload length can only be set in range [1,32] bytes".format(length)) + "{}: payload length can only be set in range [1, 32] bytes".format(length)) @property def arc(self): @@ -449,7 +439,7 @@ def arc(self, count): self._reg_write(SETUP_RETR, self._setup_retr) else: raise ValueError( - "automatic re-transmit count(/attempts) must in range [0,15]") + "automatic re-transmit count(/attempts) must in range [0, 15]") @property def ard(self): @@ -468,7 +458,7 @@ def ard(self, delta_t): self._reg_write(SETUP_RETR, self._setup_retr) else: raise ValueError("automatic re-transmit delay can only be a multiple of 250 in range " - "[250,4000]") + "[250, 4000]") @property def auto_ack(self): @@ -479,7 +469,7 @@ def auto_ack(self): @auto_ack.setter def auto_ack(self, enable): assert isinstance(enable, (bool, int)) - self._aa = 0x3F if enable else 0 # 0x3F = enable auto_ack on all pipes + self._aa = 0x3F if enable else 0 self._reg_write(EN_AA, self._aa) @property @@ -505,14 +495,13 @@ def load_ack(self, buf, pipe_number): """This allows the MCU to specify a payload to be allocated into the TX FIFO buffer for use on a specific data pipe.""" if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0,5]") + raise ValueError("pipe number must be in range [0, 5]") if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with a byte length of" - "\nat least 1 and no greater than 32") + raise ValueError("buf must be a buffer protocol object with length in range [1, 32]") if not self.ack: self.ack = True if not self.tx_full: - self._reg_write_bytes(0xA8 | pipe_number, buf) # 0xA8 = W_ACK_PAYLOAD + self._reg_write_bytes(0xA8 | pipe_number, buf) return True return False @@ -550,7 +539,7 @@ def channel(self, channel): self._channel = channel self._reg_write(RF_CH, channel) else: - raise ValueError("channel acn only be set in range [0,125]") + raise ValueError("channel acn only be set in range [0, 125]") @property def crc(self): @@ -582,14 +571,13 @@ def power(self, is_on): if self.power != is_on: self._config = (self._config & 0x7d) | (is_on << 1) self._reg_write(CONFIG, self._config) - # power up/down takes < 150 µs + 4 µs time.sleep(0.00016) @property def pa_level(self): """This `int` attribute specifies the nRF24L01's power amplifier level (in dBm).""" - self._rf_setup = self._reg_read(RF_SETUP) # refresh data + self._rf_setup = self._reg_read(RF_SETUP) return (3 - ((self._rf_setup & RF_SETUP) >> 1)) * -6 @pa_level.setter @@ -617,7 +605,7 @@ def tx_full(self): def update(self): """This function is only used to get an updated status byte over SPI from the nRF24L01""" - self._reg_write(0xFF) # 0xFF = non-operation command + self._reg_write(0xFF) def resend(self): """Use this function to maunally re-send the previous payload in the @@ -625,8 +613,7 @@ def resend(self): result = False if not self.fifo(True, True): self.clear_status_flags(False) - self._reg_write(0xE3) # 0xE3 == REUSE_TX_PL command - # timeout calc assumes 32 byte payload (& 32-byte ACK if needed) + self._reg_write(0xE3) pl_coef = 1 + bool(self._setup_retr & 0x0f) pl_len = 1 + self._addr_len + ( max(0, ((self._config & 12) >> 2) - 1)) @@ -644,7 +631,7 @@ def resend(self): self.ce_pin.value = 0 self._wait_for_result(timeout) result = self.irq_ds - if self.ack and self.irq_dr: # is there an ACK payload + if self.ack and self.irq_dr: result = self.recv() self.clear_status_flags(False) return result @@ -654,27 +641,22 @@ def write(self, buf, ask_no_ack=False): asynchronous applications and can only handle one payload at a time as it is a helper function to `send()`.""" if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with a byte length of" - "\nat least 1 and no greater than 32") + raise ValueError("buf must be a buffer protocol object with length in range [1, 32]") self.clear_status_flags(False) if self._config & 3 != 2: - # ensures tx mode & powered up self._config = (self._reg_read(CONFIG) & 0x7c) | 2 self._reg_write(CONFIG, self._config) - time.sleep(0.00016) # power up/down takes < 150 µs + 4 µs - # pad out or truncate data to fill payload_length if dynamic_payloads == False + time.sleep(0.00016) if not self.dynamic_payloads: if len(buf) < self.payload_length: for _ in range(self.payload_length - len(buf)): buf += b'\x00' elif len(buf) > self.payload_length: buf = buf[:self.payload_length] - # now upload the payload accordingly with appropriate command - if ask_no_ack: # payload doesn't want acknowledgment + if ask_no_ack: if self._features & 1 == 0: - self._features = self._features & 0xFE | 1 # set EN_DYN_ACK flag high + self._features = self._features & 0xFE | 1 self._reg_write(FEATURE, self._features) - # 0xA0 = W_TX_PAYLOAD; 0xB0 = W_TX_PAYLOAD_NO_ACK self._reg_write_bytes(0xA0 | (ask_no_ack << 4), buf) self.ce_pin.value = 1 @@ -691,7 +673,7 @@ def fifo(self, about_tx=False, check_empty=None): (read-only)""" if (check_empty is None and isinstance(about_tx, (bool, int))) or \ (isinstance(check_empty, (bool, int)) and isinstance(about_tx, (bool, int))): - self._fifo = self._reg_read(FIFO) # refresh the data + self._fifo = self._reg_read(FIFO) if check_empty is None: return (self._fifo & (0x30 if about_tx else 0x03)) >> (4 * about_tx) return bool(self._fifo & ((2 - check_empty) << (4 * about_tx))) @@ -702,7 +684,7 @@ def pipe(self): """This function returns information about the data pipe that received the next available payload in the RX FIFO buffer.""" self.update() - result = (self._status & 0x0E) >> 1 # 0x0E==RX_P_NO + result = (self._status & 0x0E) >> 1 if result <= 5: return result return None From 1b7651ea85b9b5992be30dd214d09f9075bb6fff Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Thu, 26 Mar 2020 06:13:43 -0700 Subject: [PATCH 023/127] decided to not inherit RF24; now a class wrapper --- circuitpython_nrf24l01/fake_ble.py | 204 ++++++++++------------------- 1 file changed, 70 insertions(+), 134 deletions(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index 98ff0c1..0d84a21 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -49,8 +49,6 @@ other event, "on data fail", is ignored because it will never get thrown with "auto_ack" off. However the interrupt settings can be modified AFTER instantiation """ -import time -from .rf24 import RF24 def _swap_bits(orig): """reverses the bit order into LSbit to MSBit""" @@ -107,63 +105,61 @@ def _ble_whitening(data, whiten_coef): result += bytes([byte]) return result -class FakeBLE(RF24): +class FakeBLE: """Per the limitations of this technique, only power amplifier level is available for configuration when advertising BLE data. - :param ~busio.SPI spi: The object for the SPI bus that the nRF24L01 is connected to. - - .. tip:: This object is meant to be shared amongst other driver classes (like - adafruit_mcp3xxx.mcp3008 for example) that use the same SPI bus. Otherwise, multiple - devices on the same SPI bus with different spi objects may produce errors or - undesirable behavior. - :param ~digitalio.DigitalInOut csn: The digital output pin that is connected to the - nRF24L01's CSN (Chip Select Not) pin. This is required. - :param ~digitalio.DigitalInOut ce: The digital output pin that is connected to the - nRF24L01's CE (Chip Enable) pin. This is required. - :param bytearray name: This will be the nRF24L01-emulated BLE device's broadcasted name. - This is option and defaults to `None` to allow for larger paylaods because the name's - byte length borrows from the same buffer space that the payload data occupies. See - `name` attribute for more details. - :param int pa_level: This parameter controls the RF power amplifier setting of transmissions. - Options are ``0`` (dBm), ``-6`` (dBm), ``-12`` (dBm), or ``-18`` (dBm). This can be - changed at any time by using the `pa_level` attribute. + :param ~circuitpython_nrf24l01 nrf: The object for the nRF24L01 transceiver to use + for fake BLE advertisements. + :param bytearray name: The BLE device name to be advertised with the payload. """ - def __init__(self, spi, csn, ce, name=None, pa_level=False, irq_dr=False, irq_ds=True): - super(FakeBLE, self).__init__(spi, csn, ce, - pa_level=pa_level, - crc=0, - dynamic_payloads=False, - arc=0, - address_length=4, - ask_no_ack=False, - irq_df=False, - irq_dr=irq_dr, - irq_ds=irq_ds) + def __init__(self, nrf, name=None): + self._device = nrf + self._device.address_length = 4 + self._device.dynamic_payloads = False + self._device.auto_ack = False + self._device.crc = 0 + self._device.arc = 0 + self._device.irq_df = False + self._device.irq_dr = False + self._device.irq_ds = True self._chan = 0 self._ble_name = None self.name = name + self._device.open_tx_pipe(_reverse_bits(b'\x8E\x89\xBE\xD6')) + # b'\x8E\x89\xBE\xD6' = proper address for BLE advertisments + + def __enter__(self): + return self._device.__enter__() + + def __exit(self, *exc): + return self._device.__exit__(exc) @property def name(self): - """Represents the emulated BLE device name during braodcasts. must be a buffer protocol - object (`bytearray`) , and can be any length (less than 14) of UTF-8 freindly characters. - - .. note:: the BLE device's name will occupy the same space as your TX data. While space - is limited to 32 bytes on the nRF24L01, actual usable BLE TX data = 16 - (name length - + 2). The other 16 bytes available on the nRF24L01 TX FIFO buffer are reserved for - the [arbitrary] MAC address and other BLE related stuff. + """Represents the emulated BLE device name during braodcasts. This must + be a buffer protocol object (`bytearray`) , and can be any length (less + than 14) of UTF-8 freindly characters. Set this to `None` to disable + advertising a BLE device name. + + .. note:: the BLE device's name will occupy the same space as your TX + data. While space is limited to 32 bytes on the nRF24L01, actual + usable BLE TX data = 16 - (name length + 2). The other 16 bytes + available on the nRF24L01 TX FIFO buffer are reserved for the + [arbitrary] MAC address and other BLE related stuff. """ return self._ble_name[2:] if self._ble_name is not None else None @name.setter def name(self, n): - """The broadcasted BLE name of the nRF24L01. This is not required. In fact, setting this - attribute will subtract from the available payload length (in bytes). + """The broadcasted BLE name of the nRF24L01. This is not required. In + fact setting this attribute will subtract from the available payload + length (in bytes). - * payload_length has maximum of 19 bytes when NOT broadcasting a name for itself. - * payload_length has a maximum of (17 - length of name) bytes when broadcasting a + * payload_length has a maximum of 19 bytes when NOT broadcasting a name for itself. + * payload_length has a maximum of (17 - length of name) bytes when + broadcasting a name for itself. """ if n is not None and 1 <= len(n) <= 12: # max defined by 1 byte payload data requisite self._ble_name = bytes([len(n) + 1]) + b'\x08' + n @@ -171,42 +167,38 @@ def name(self, n): self._ble_name = None # name will not be advertised def _chan_hop(self): - """BLE protocol specs mandate the BLE device cyle through the following 3 channels: - - - nRF channel 2 == BLE channel 37 - - nRF channel 26 == BLE channel 38 - - nRF channel 80 == BLE channel 39 - - .. note:: then BLE channel number is different from the nRF channel number. - """ + # NOTE BLE channel number is different from the nRF channel number. + # - nRF channel 2 == BLE channel 37 + # - nRF channel 26 == BLE channel 38 + # - nRF channel 80 == BLE channel 39 self._chan = (self._chan + 1) if (self._chan + 1) < 3 else 0 - self.channel = 26 if self._chan == 1 else (80 if self._chan == 2 else 2) + self._device.channel = 26 if self._chan == 1 else (80 if self._chan == 2 else 2) - # pylint: disable=arguments-differ - def send(self, buf): - """This blocking function is used to transmit payload. + def advertise(self, buf): + """This blocking function is used to transmit a payload. :returns: Nothing as every transmission will register as a success under these required settings. - :param bytearray buf: The payload to transmit. This bytearray must have a length greater - than 0 and less than 20, otherwise a `ValueError` exception is thrown. This can also - be a list or tuple of payloads (`bytearray`); in which case, all items in the - list/tuple are processed for consecutive transmissions. - - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is less - than the `payload_length` attribute, then this bytearray is padded with zeros until - its length is equal to the `payload_length` attribute. - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is - greater than `payload_length` attribute, then this bytearray's length is truncated - to equal the `payload_length` attribute. - - .. note:: If the name of the emulated BLE device is also to be braodcast, then the 'name' - attribute should be set prior to calling `send()`. + :param bytearray buf: The payload to transmit. This bytearray must have + a length greater than 0 and less than 20, otherwise a `ValueError` + exception is thrown. This can also be a list or tuple of payloads + (`bytearray`); in which case, all items in the list/tuple are + processed for consecutive transmissions. + + - If the `dynamic_payloads` attribute is disabled and this + bytearray's length is less than the `payload_length` attribute, + then this bytearray is padded with zeros until its length is + equal to the `payload_length` attribute. + - If the `dynamic_payloads` attribute is disabled and this + bytearray's length is greater than `payload_length` attribute, + then this bytearray's length is truncated to equal the + `payload_length` attribute. + + .. note:: If the name of the emulated BLE device is also to be + broadcast, then the 'name' attribute should be set prior to calling + `advertise()`. """ - self.ce_pin.value = 0 - self.flush_tx() - self.clear_status_flags(False) # clears TX related flags only # max payload_length = 32 - 14(header, MAC, & CRC) - 2(container header) - 3(BLE flags) # = 13 - (BLE name length + 2 if any) name_len = len(self._ble_name) if self._ble_name is not None else 0 @@ -214,19 +206,17 @@ def send(self, buf): raise ValueError("buf must be a buffer protocol object with a byte length of" " at least 1 and no greater than 13 - " "{} = {}".format(name_len, 13 - name_len)) - - # BLE payload = header(1) + payload length(1) + MAC address(6) + containers + CRC(3) bytes + # BLE payload = + # header(1) + payload length(1) + MAC address(6) + containers + CRC(3) bytes # header == PDU type, given MAC address is random/arbitrary # type == 0x42 for Android or 0x40 for iPhone # containers (in bytes) = length(1) + type(1) + data # the 1 byte about container's length excludes only itself - payload = b'\x42' # init a temp payload buffer with header type byte - # to avoid padding when dynamic_payloads is disabled, set payload_length attribute - self.payload_length = len(buf) + 16 + name_len + self._device.payload_length = len(buf) + 16 + name_len # payload length excludes the header, itself, and crc lengths - payload += bytes([self.payload_length - 5]) + payload += bytes([self._device.payload_length - 5]) payload += b'\x11\x22\x33\x44\x55\x66' # a bogus MAC address # payload will have at least 2 containers: # 3 bytes of flags (required for BLE discoverable), & at least (1+2) byte of data @@ -242,60 +232,6 @@ def send(self, buf): # the whiten_coef value we need is the BLE channel (37,38, or 39) left shifted one whiten_coef = 37 + self._chan whiten_coef = _swap_bits(whiten_coef) | 2 - - print('transmitting {} as {}'.format(payload, - _reverse_bits(_ble_whitening(payload, whiten_coef)))) - # init using non-blocking helper - self.write(_reverse_bits(_ble_whitening(payload, whiten_coef))) - time.sleep(0.00001) # ensure CE pulse is >= 10 µs - # pulse is stopped here; the nRF24L01 only handles the top level payload in the FIFO. - self.ce_pin.value = 0 # go to Standby-I power mode (power attribute still == True) - - # T_upload is done before timeout begins (after payload write action AKA upload) - timeout = (((8 * (5 + len(payload))) + 9) / 125000) + 0.0002682 - start = time.monotonic() - while not self.irq_ds and (time.monotonic() - start) < timeout: - self.update() # perform Non-operation command to get status byte (should be faster) - # print('status: DR={} DS={} DF={}'.format(self.irq_dr, self.irq_ds, self.irq_df)) - self.clear_status_flags(False) # only TX related IRQ flags - - # Altering all the following settings is disabled - def open_tx_pipe(self): - super(FakeBLE, self).open_tx_pipe(_reverse_bits(b'\x8E\x89\xBE\xD6')) - # b'\x8E\x89\xBE\xD6' = proper address for BLE advertisments - # pylint: enable=arguments-differ - - # ignore pylint errors when overriding setter functions of inherited attributes - # pylint: disable=no-member - @RF24.address_length.setter - def address_length(self, t): - super(FakeBLE, self).address_length = (4 + t * 0) - - @RF24.listen.setter - def listen(self, rx): - if self.listen or rx: - self._stop_listening() - - @RF24.data_rate.setter - def data_rate(self, t): - super(FakeBLE, self).data_rate = (1 + t * 0) - - @RF24.dynamic_payloads.setter - def dynamic_payloads(self, t): - super(FakeBLE, self).dynamic_payloads = (False & t) - - @RF24.auto_ack.setter - def auto_ack(self, t): - super(FakeBLE, self).auto_ack = (False & t) - - @RF24.ack.setter - def ack(self, t): - super(FakeBLE, self).ack = (False & t) - - @RF24.crc.setter - def crc(self, t): - super(FakeBLE, self).crc = (0 * t) - - @RF24.arc.setter - def arc(self, t): - super(FakeBLE, self).arc = (t * 0) + rev_whiten_pl = _reverse_bits(_ble_whitening(payload, whiten_coef)) + print('transmitting {} as {}'.format(payload, rev_whiten_pl)) + self._device.send(rev_whiten_pl) From cbec57d8dac897f122a31774c99de918219b7770 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 3 Apr 2020 02:51:37 -0700 Subject: [PATCH 024/127] works w/ tinypico (esp32) employing my wrapper modules from another repo called circuitpython2micropython --- circuitpython_nrf24l01/rf24.py | 533 +++++++++++++++------------- circuitpython_nrf24l01/rf24_lite.py | 155 ++++---- docs/api.rst | 4 +- 3 files changed, 384 insertions(+), 308 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index e9df89b..38e816b 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -25,26 +25,22 @@ __repo__ = "https://github.com/2bndy5/CircuitPython_nRF24L01.git" import time from micropython import const -from adafruit_bus_device.spi_device import SPIDevice +try: + from ubus_device import SPIDevice +except ImportError: + from adafruit_bus_device.spi_device import SPIDevice # nRF24L01 registers -# pylint: disable=bad-whitespace -CONFIG = const(0x00) # IRQ, CRC, PWR & RX/TX roles -EN_AA = const(0x01) # auto-ACK -EN_RX = const(0x02) # open/close pipes -SETUP_AW = const(0x03) # address width -SETUP_RETR = const(0x04) # auto-retry count & delay -RF_CH = const(0x05) # channel -RF_SETUP = const(0x06) # RF Power Amplifier & Data Rate -RX_ADDR = const(0x0a) # RX pipe addresses == [0,5]:[0x0a:0x0f] -RX_PW = const(0x11) # RX payload widths on pipes == [0,5]:[0x11,0x16] -FIFO = const(0x17) # info on both RX/TX FIFOs + re-use payload flag -DYNPD = const(0x1c) # dynamic payloads -FEATURE = const(0x1d) # TX flags (dynamic payloads, ACK payloads, & NO_ACK) -TX_ADDR = const(0x10) # Address used for TX transmissions -# pylint: enable=bad-whitespace - -# NOTE expanded documentation lives in api.rst to save space on M0 +CONFIGURE = const(0x00) # IRQ, CRC, PWR control & RX/TX roles +AUTO_ACK = const(0x01) # auto-ACK +OPEN_PIPES = const(0x02) # open/close pipes +SETUP_RETR = const(0x04) # auto-retry count & delay +RF_PA_RATE = const(0x06) # RF Power Amplifier & Data Rate +RX_ADDR_P0 = const(0x0a) # RX pipe addresses == [0, 5]:[0x0a, 0x0f] +RX_PL_LENG = const(0x11) # RX payload widths on pipes == [0, 5]:[0x11, 0x16] +DYN_PL_LEN = const(0x1c) # dynamic payloads +TX_FEATURE = const(0x1d) # TX features dynamic payloads, ACK payloads, NO_ACK +TX_ADDRESS = const(0x10) # Address used for TX transmissions class RF24: """A driver class for the nRF24L01(+) transceiver radios.""" @@ -53,71 +49,69 @@ def __init__(self, spi, csn, ce): self.payload_length = 32 self._fifo = 0 self._status = 0 - # init shadow copy of RX addresses for all pipes + # init shadow copy of RX addresses for all pipes for context manager self._pipes = [b'\xE7' * 5, b'\xC2' * 5, 0, 0, 0, 0] - self._payload_widths = [32] * 6 # payload_length specific to each pipe - # shadow copy of last RX_ADDR written to pipe 0 - self._pipe0_read_addr = None # needed as open_tx_pipe() appropriates pipe 0 for ACK - # init the _open_pipes attribute (reflects only RX state on each pipe) + # init shadow copy of static payload lengths for context manager + self._payload_widths = [32] * 6 # payload_length for each pipe + # shadow copy of last RX_ADDR_P0 written to pipe 0 needed as + # open_tx_pipe() appropriates pipe 0 for ACK packet + self._pipe0_read_addr = None + # _open_pipes attribute reflects only RX state on each pipe self._open_pipes = 0 # 0 = all pipes closed self._spi = SPIDevice(spi, chip_select=csn, baudrate=1250000) self.ce_pin = ce - self.ce_pin.switch_to_output(value=False) - # configure the CONFIG register: - # 0x0E = IRQs are all enabled, CRC is enabled with 2 bytes, and power up in TX mode + self.ce_pin.switch_to_output(value=False) # pre-empt standby-I mode + # pre-configure the CONFIGURE register: + # 0x0E = IRQs are all enabled, CRC is enabled with 2 bytes, and + # power up in TX mode self._config = 0x0E - self._reg_write(CONFIG, self._config) # dump to register - # check for device presence by verifying nRF24L01 is in TX + standby-I mode - if self._reg_read(CONFIG) & 3 == 2: # if in TX + standby-I mode - self.power = False # power down - else: # hardware presence check NOT passed - print(bin(self._reg_read(CONFIG))) + self._reg_write(CONFIGURE, self._config) + if self._reg_read(CONFIGURE) & 3 == 2: + self.power = False + else: # hardware presence check NOT passed raise RuntimeError("nRF24L01 Hardware not responding") + # shadow copies of RX pipe addresses for context manager for i in range(6): if i < 2: - self._pipes[i] = self._reg_read_bytes(RX_ADDR + i) + self._pipes[i] = self._reg_read_bytes(RX_ADDR_P0 + i) else: - self._pipes[i] = self._reg_read(RX_ADDR + i) - # shadow copy of the TX_ADDR - self._tx_address = self._reg_read_bytes(TX_ADDR) - # configure the SETUP_RETR register - self._setup_retr = 0x53 # ard = 1500; arc = 3 - # configure the RF_SETUP register + self._pipes[i] = self._reg_read(RX_ADDR_P0 + i) + # shadow copy of the TX_ADDRESS + self._tx_address = self._reg_read_bytes(TX_ADDRESS) + # pre-configure the SETUP_RETR register + self._retry_setup = 0x53 # ard = 1500; arc = 3 + # pre-configure the RF_SETUP register self._rf_setup = 0x06 # 1 Mbps data_rate, and 0 dbm pa_level - # configure dynamic_payloads & auto_ack for RX operations - self._dyn_pl = 0x3F # 0x3F = dynamic_payloads enabled on all pipes - self._aa = 0x3F # 0x3F = auto_ack enabled on all pipes - # configure features for TX operations - # 5 = enable dynamic_payloads, disable custom ack payloads, & allow ask_no_ack command + # pre-configure dynamic_payloads & auto_ack for RX operations + self._dyn_pl = 0x3F # 0x3F = enable dynamic_payloads on all pipes + self._aa = 0x3F # 0x3F = enable auto_ack on all pipes + # pre-configure features for TX operations: + # 5 = enable dynamic_payloads, disable custom ack payloads, & + # allow ask_no_ack command self._features = 5 - self._channel = 76 - self._addr_len = 5 + self._channel = 76 # 2.476 GHz + self._addr_len = 5 # 5-byte long addresses - with self: - self.ce_pin.value = 0 - self._reg_write(CONFIG, self._config | 1) - time.sleep(0.000015) + with self: # dumps internal attributes to all registers self.flush_rx() - self._reg_write(CONFIG, self._config & 0xC) - time.sleep(0.000015) self.flush_tx() self.clear_status_flags() def __enter__(self): - self._reg_write(CONFIG, self._config & 0x7C) - self._reg_write(RF_SETUP, self._rf_setup) - self._reg_write(EN_RX, self._open_pipes) - self._reg_write(DYNPD, self._dyn_pl) - self._reg_write(EN_AA, self._aa) - self._reg_write(FEATURE, self._features) - self._reg_write(SETUP_RETR, self._setup_retr) + self._reg_write(CONFIGURE, self._config & 0x7C) + self._reg_write(RF_PA_RATE, self._rf_setup) + self._reg_write(OPEN_PIPES, self._open_pipes) + self._reg_write(DYN_PL_LEN, self._dyn_pl) + self._reg_write(AUTO_ACK, self._aa) + self._reg_write(TX_FEATURE, self._features) + self._reg_write(SETUP_RETR, self._retry_setup) for i, address in enumerate(self._pipes): if i < 2: - self._reg_write_bytes(RX_ADDR + i, address) + self._reg_write_bytes(RX_ADDR_P0 + i, address) else: - self._reg_write(RX_ADDR + i, address) - self._reg_write(RX_PW + i, self._payload_widths[i]) - self._reg_write_bytes(TX_ADDR, self._tx_address) + self._reg_write(RX_ADDR_P0 + i, address) + self._reg_write(RX_PL_LENG + i, self._payload_widths[i]) + self._reg_write_bytes(TX_ADDRESS, self._tx_address) self.address_length = self._addr_len self.channel = self._channel return self @@ -128,20 +122,22 @@ def __exit__(self, *exc): # pylint: disable=no-member def _reg_read(self, reg): - buf = bytearray(2) + out_buf = bytearray([reg, 0]) + in_buf = bytearray([0, 0]) with self._spi as spi: time.sleep(0.005) - spi.readinto(buf, write_value=reg) - self._status = buf[0] - return buf[1] + spi.write_readinto(out_buf, in_buf) + self._status = in_buf[0] + return in_buf[1] def _reg_read_bytes(self, reg, buf_len=5): - buf = bytearray(buf_len + 1) + in_buf = bytearray(buf_len + 1) + out_buf = bytearray([reg]) + b'\x00' * buf_len with self._spi as spi: time.sleep(0.005) - spi.readinto(buf, write_value=reg) - self._status = buf[0] - return buf[1:] + spi.write_readinto(out_buf, in_buf) + self._status = in_buf[0] + return in_buf[1:] def _reg_write_bytes(self, reg, out_buf): out_buf = bytes([0x20 | reg]) + out_buf @@ -167,34 +163,35 @@ def _reg_write(self, reg, value=None): def address_length(self): """This `int` attribute specifies the length (in bytes) of addresses to be used for RX/TX pipes.""" - return self._reg_read(SETUP_AW) + 2 + return self._reg_read(0x03) + 2 @address_length.setter def address_length(self, length): if 3 <= length <= 5: self._addr_len = int(length) - self._reg_write(SETUP_AW, length - 2) + self._reg_write(0x03, length - 2) else: raise ValueError( "address length can only be set in range [3, 5] bytes") def open_tx_pipe(self, address): - """This function is used to open a data pipe for OTA (over the air) TX - transmissions.""" + """This function is used to open a data pipe for OTA (over the air) + TX transmissions.""" if len(address) == self.address_length: if self.auto_ack: self._pipes[0] = address - self._reg_write_bytes(RX_ADDR, address) + self._reg_write_bytes(RX_ADDR_P0, address) self._open_pipes = self._open_pipes | 1 - self._reg_write(EN_RX, self._open_pipes) + self._reg_write(OPEN_PIPES, self._open_pipes) self._payload_widths[0] = self.payload_length - self._reg_write(RX_PW, self.payload_length) + self._reg_write(RX_PL_LENG, self.payload_length) self._pipes[0] = address self._tx_address = address - self._reg_write_bytes(TX_ADDR, address) + self._reg_write_bytes(TX_ADDRESS, address) else: - raise ValueError("address must be a buffer protocol object with a byte length\nequal " - "to the address_length attribute (currently set to" + raise ValueError("address must be a buffer protocol object with a" + " byte length\nequal to the address_length " + "attribute (currently set to" " {})".format(self.address_length)) def close_rx_pipe(self, pipe_number): @@ -202,10 +199,10 @@ def close_rx_pipe(self, pipe_number): the air) RX transmissions.""" if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0,5]") - self._open_pipes = self._reg_read(EN_RX) + self._open_pipes = self._reg_read(OPEN_PIPES) if self._open_pipes & (1 << pipe_number): self._open_pipes = self._open_pipes & ~(1 << pipe_number) - self._reg_write(EN_RX, self._open_pipes) + self._reg_write(OPEN_PIPES, self._open_pipes) def open_rx_pipe(self, pipe_number, address): """This function is used to open a specific data pipe for OTA (over @@ -213,21 +210,22 @@ def open_rx_pipe(self, pipe_number, address): if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0,5]") if len(address) != self.address_length: - raise ValueError("address must be a buffer protocol object with a byte length\nequal " - "to the address_length attribute (currently set to " - "{})".format(self.address_length)) + raise ValueError("address must be a buffer protocol object with a" + " byte length\nequal to the address_length " + "attribute (currently set to" + " {})".format(self.address_length)) if pipe_number < 2: if not pipe_number: self._pipe0_read_addr = address self._pipes[pipe_number] = address - self._reg_write_bytes(RX_ADDR + pipe_number, address) + self._reg_write_bytes(RX_ADDR_P0 + pipe_number, address) else: self._pipes[pipe_number] = address[0] - self._reg_write(RX_ADDR + pipe_number, address[0]) - self._open_pipes = self._reg_read(EN_RX) + self._reg_write(RX_ADDR_P0 + pipe_number, address[0]) + self._open_pipes = self._reg_read(OPEN_PIPES) self._open_pipes = self._open_pipes | (1 << pipe_number) - self._reg_write(EN_RX, self._open_pipes) - self._reg_write(RX_PW + pipe_number, self.payload_length) + self._reg_write(OPEN_PIPES, self._open_pipes) + self._reg_write(RX_PL_LENG + pipe_number, self.payload_length) self._payload_widths[pipe_number] = self.payload_length @property @@ -239,19 +237,20 @@ def listen(self): def listen(self, is_rx): assert isinstance(is_rx, (bool, int)) if self.listen != is_rx: - self._start_listening() - else: - self._stop_listening() + if is_rx: + self._start_listening() + else: + self._stop_listening() def _start_listening(self): if self.ce_pin.value: self.ce_pin.value = 0 if self._pipe0_read_addr is not None: - self._reg_write_bytes(RX_ADDR, self._pipe0_read_addr) self._pipes[0] = self._pipe0_read_addr - self._config = self._config & 0xFC | 3 - self._reg_write(CONFIG, self._config) - time.sleep(0.00015) # mandatory wait to power up radio + self._reg_write_bytes(RX_ADDR_P0, self._pipe0_read_addr) + self._config = (self._config & 0xFC) | 3 + self._reg_write(CONFIGURE, self._config) + time.sleep(0.00015) # mandatory wait to power up radio self.flush_rx() self.clear_status_flags(True, False, False) self.ce_pin.value = 1 # mandatory pulse is > 130 µs @@ -261,24 +260,24 @@ def _stop_listening(self): if self.ce_pin.value: self.ce_pin.value = 0 self._config = self._config & 0xFE - self._reg_write(CONFIG, self._config) + self._reg_write(CONFIGURE, self._config) time.sleep(0.00016) def any(self): """This function checks if the nRF24L01 has received any data at all, and then reports the next available payload's length (in bytes)""" - self._features = self._reg_read(FEATURE) - if self._features & 4: - return self._reg_read(0x60) + self._features = self._reg_read(TX_FEATURE) if self.irq_dr: - return self._reg_read(RX_PW + ((self._status & 0xE) >> 1)) + if self._features & 4: + return self._reg_read(0x60) + return self._reg_read(RX_PL_LENG + ((self._status & 0xE) >> 1)) return 0 def recv(self): """This function is used to retrieve the next available payload""" - if not self.irq_dr: - return None curr_pl_size = self.any() + if not curr_pl_size: + return None result = self._reg_read_bytes(0x61, curr_pl_size) self.clear_status_flags(True, False, False) return result @@ -291,35 +290,43 @@ def send(self, buf, ask_no_ack=False, force_retry=0): result = [] for i, b in enumerate(buf): if not b or len(b) > 32: - raise ValueError("buf (item {} in the list/tuple) must be a" - " buffer protocol object with length in range [1, 32]" % i) + raise ValueError("buf (item {} in the list/tuple) must be" + " a buffer protocol object with length " + "in range [1, 32]".format(i)) for i, b in enumerate(buf): result.append(self.send(b, ask_no_ack, force_retry)) return result if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with length in range [1, 32]") - need_ack = self._setup_retr & 0x0f and not ask_no_ack - packet_data = 1 + self._addr_len + (max(0, ((self._config & 12) >> 2) - 1)) - bitrate = ((2000000 if self._rf_setup & 0x28 == 8 else 250000) - if self._rf_setup & 0x28 else 1000000) / 8 - t_ack = (((packet_data + 32) * 8 + 9) / bitrate) if need_ack else 0 - stby2active = (1 + (need_ack)) * 0.00013 + raise ValueError("buf must be a buffer protocol object with " + "length in range [1, 32]") + use_ack = bool(self._aa and not ask_no_ack) + get_ack_pl = bool(self._features & 6 == 6 and self._dyn_pl and use_ack) + if get_ack_pl: + self.flush_rx() + packet_data = 1 + self._addr_len + (max(0, ((self._config & 12) >> 2) + - 1)) + bitrate = ((250000 if self._rf_setup & 0x28 == 8 else 31250) if + self._rf_setup & 0x28 else 125000) + t_ack = 0 + if use_ack: + t_ack = (((packet_data + 32 * get_ack_pl) * 8 + 9) / bitrate) + stby2active = (1 + (use_ack)) * 0.00013 t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 - t_retry = (((self._setup_retr & 0xf0) >> 4) * 250 + 380) * \ - (self._setup_retr & 0x0f) / 1000000 + t_retry = (((self._retry_setup & 0xf0) >> 4) * 250 + 380) * \ + (self._retry_setup & 0x0f) / 1000000 self.write(buf, ask_no_ack) time.sleep(0.00001) self.ce_pin.value = 0 - self._wait_for_result((((8 * (len(buf) + packet_data)) + 9) / - bitrate) + stby2active + t_irq + t_retry + t_ack) - if self._setup_retr & 0x0f and self.irq_df: + self._wait4result((((8 * (len(buf) + packet_data)) + 9) / + bitrate) + stby2active + t_irq + t_retry + t_ack) + if self._retry_setup & 0x0f and self.irq_df: for _ in range(force_retry): result = self.resend() if result is None or result: break else: result = self.irq_ds - if self.ack and self.irq_dr and not ask_no_ack: + if get_ack_pl: result = self.recv() self.clear_status_flags(False) return result @@ -339,45 +346,56 @@ def irq_df(self): """A `bool` that represents the "Data Failed" interrupted flag.""" return bool(self._status & 0x10) - def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): + def clear_status_flags(self, data_recv=True, data_sent=True, + data_fail=True): """This clears the interrupt flags in the status register.""" self._reg_write(0x07, (data_recv << 6) | ( data_sent << 5) | (data_fail << 4)) - def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): + def interrupt_config(self, data_recv=True, data_sent=True, + data_fail=True): """Sets the configuration of the nRF24L01's IRQ (interrupt) pin.""" - self._config = self._reg_read(CONFIG) - self._config = (self._config & 0x0F) | (not data_fail << 4) | (not data_sent << 5) | \ - (not data_recv << 6) - self._reg_write(CONFIG, self._config) + self._config = self._reg_read(CONFIGURE) & 0x0F + self._config |= (not bool(data_fail)) << 4 + self._config |= (not bool(data_sent)) << 5 + self._config |= (not bool(data_recv)) << 6 + self._reg_write(CONFIGURE, self._config) def what_happened(self, dump_pipes=False): """This debuggung function aggregates and outputs all status/condition related information""" - watchdog = self._reg_read(8) + observer = self._reg_read(8) print("Channel___________________{} ~ {} GHz".format( self.channel, (self.channel + 2400) / 1000)) print("RF Data Rate______________{} {}".format( self.data_rate, "Mbps" if self.data_rate != 250 else "Kbps")) print("RF Power Amplifier________{} dbm".format(self.pa_level)) print("CRC bytes_________________{}".format(self.crc)) - print("Address length____________{} bytes".format(self.address_length)) - print("Payload lengths___________{} bytes".format(self.payload_length)) + print("Address length____________{} bytes".format( + self.address_length)) + print("Payload lengths___________{} bytes".format( + self.payload_length)) print("Auto retry delay__________{} microseconds".format(self.ard)) print("Auto retry attempts_______{} maximum".format(self.arc)) print("Packets lost on current channel_____________________{}".format( - (watchdog & 0xF0) >> 4)) - print("Retry attempts made for last transmission___________{}".format(watchdog & 0x0F)) + (observer & 0xF0) >> 4)) + print("Retry attempts made for last transmission___________{}".format( + observer & 0x0F)) print("IRQ - Data Ready______{} Data Ready___________{}".format( - '_True' if not bool(self._config & 0x40) else 'False', self.irq_dr)) + '_True' if not bool(self._config & 0x40) else 'False', + self.irq_dr)) print("IRQ - Data Fail_______{} Data Failed__________{}".format( - '_True' if not bool(self._config & 0x20) else 'False', self.irq_df)) + '_True' if not bool(self._config & 0x20) else 'False', + self.irq_df)) print("IRQ - Data Sent_______{} Data Sent____________{}".format( - '_True' if not bool(self._config & 0x10) else 'False', self.irq_ds)) + '_True' if not bool(self._config & 0x10) else 'False', + self.irq_ds)) print("TX FIFO full__________{} TX FIFO empty________{}".format( - '_True' if bool(self.tx_full) else 'False', bool(self.fifo(True, True)))) + '_True' if bool(self.tx_full) else 'False', + bool(self.fifo(True, True)))) print("RX FIFO full__________{} RX FIFO empty________{}".format( - '_True' if bool(self._fifo & 2) else 'False', bool(self._fifo & 1))) + '_True' if bool(self._fifo & 2) else 'False', + bool(self._fifo & 1))) print("Ask no ACK_________{} Custom ACK Payload___{}".format( '_Allowed' if bool(self._features & 1) else 'Disabled', 'Enabled' if self.ack else 'Disabled')) @@ -386,30 +404,36 @@ def what_happened(self, dump_pipes=False): 'Enabled' if self.auto_ack else 'Disabled')) print("Primary Mode_____________{} Power Mode___________{}".format( 'RX' if self.listen else 'TX', - ('Standby-II' if self.ce_pin.value else 'Standby-I') if self._config & 2 else 'Off')) + ('Standby-II' if self.ce_pin.value else 'Standby-I') if + self._config & 2 else 'Off')) if dump_pipes: print('TX address____________', self.address()) - for i in range(6): - is_open = "( open )" if self._open_pipes & (1 << i) else "(closed)" + self._open_pipes = self._reg_read(OPEN_PIPES) + for i in range(6): + is_open = "( open )" if self._open_pipes & (1 << i) else \ + "(closed)" print("Pipe", i, is_open, "bound:", self.address(i)) if is_open: - print('\t\texpecting', self._payload_widths[i], 'byte static payloads') + print('\t\texpecting', self._payload_widths[i], + 'byte static payloads') @property def dynamic_payloads(self): """This `bool` attribute controls the nRF24L01's dynamic payload length feature.""" + self._dyn_pl = self._reg_read(DYN_PL_LEN) + self._features = self._reg_read(TX_FEATURE) return bool(self._dyn_pl and (self._features & 4)) @dynamic_payloads.setter def dynamic_payloads(self, enable): assert isinstance(enable, (bool, int)) - self._features = self._reg_read(FEATURE) - if self._features & 4 != enable: - self._features = (self._features & 3) | (enable << 2) - self._reg_write(FEATURE, self._features) + self._features = self._reg_read(TX_FEATURE) + if bool(self._features & 4) != bool(enable): + self._features = (self._features & 3) | (bool(enable) << 2) + self._reg_write(TX_FEATURE, self._features) self._dyn_pl = 0x3F if enable else 0 - self._reg_write(DYNPD, self._dyn_pl) + self._reg_write(DYN_PL_LEN, self._dyn_pl) @property def payload_length(self): @@ -421,75 +445,77 @@ def payload_length(self, length): if not length or length <= 32: self._payload_length = length else: - raise ValueError( - "{}: payload length can only be set in range [1, 32] bytes".format(length)) + raise ValueError("{}: payload length can only be set in range [1," + " 32] bytes".format(length)) @property def arc(self): """This `int` attribute specifies the nRF24L01's number of attempts to re-transmit TX payload""" - self._setup_retr = self._reg_read(SETUP_RETR) - return self._setup_retr & 0x0f + self._retry_setup = self._reg_read(SETUP_RETR) + return self._retry_setup & 0x0f @arc.setter def arc(self, count): if 0 <= count <= 15: - if self.arc & 0x0F != count: - self._setup_retr = (self._setup_retr & 0xF0) | count - self._reg_write(SETUP_RETR, self._setup_retr) + if self.arc != count: + self._retry_setup = (self._retry_setup & 0xF0) | count + self._reg_write(SETUP_RETR, self._retry_setup) else: - raise ValueError( - "automatic re-transmit count(/attempts) must in range [0, 15]") + raise ValueError("automatic re-transmit count(/attempts) must in" + " range [0, 15]") @property def ard(self): """This `int` attribute specifies the nRF24L01's delay (in microseconds) between attempts to automatically re-transmit the TX payload""" - self._setup_retr = self._reg_read(SETUP_RETR) - return ((self._setup_retr & 0xf0) >> 4) * 250 + 250 + self._retry_setup = self._reg_read(SETUP_RETR) + return ((self._retry_setup & 0xf0) >> 4) * 250 + 250 @ard.setter def ard(self, delta_t): if 250 <= delta_t <= 4000 and delta_t % 250 == 0: if self.ard != delta_t: - self._setup_retr = ( - int((delta_t - 250) / 250) << 4) | (self._setup_retr & 0x0F) - self._reg_write(SETUP_RETR, self._setup_retr) + self._retry_setup &= 0x0F + self._retry_setup |= int((delta_t - 250) / 250) << 4 + self._reg_write(SETUP_RETR, self._retry_setup) else: - raise ValueError("automatic re-transmit delay can only be a multiple of 250 in range " - "[250, 4000]") + raise ValueError("automatic re-transmit delay can only be a " + "multiple of 250 in range [250, 4000]") @property def auto_ack(self): """This `bool` attribute controls the nRF24L01's automatic acknowledgment feature during the process of receiving""" - return self._aa + self._aa = self._reg_read(AUTO_ACK) + return bool(self._aa) @auto_ack.setter def auto_ack(self, enable): assert isinstance(enable, (bool, int)) self._aa = 0x3F if enable else 0 - self._reg_write(EN_AA, self._aa) + self._reg_write(AUTO_ACK, self._aa) @property def ack(self): """This `bool` attribute represents the status of the nRF24L01's capability to use custom payloads as part of the automatic acknowledgment (ACK) packet.""" - return bool((self._features & 2) and self.auto_ack and self.dynamic_payloads) + self._aa = self._reg_read(AUTO_ACK) + self._dyn_pl = self._reg_read(DYN_PL_LEN) + self._features = self._reg_read(TX_FEATURE) + return bool((self._features & 6) == 6 and self._aa and self._dyn_pl) @ack.setter def ack(self, enable): assert isinstance(enable, (bool, int)) - if self.ack != enable: + if self.ack != bool(enable): self.auto_ack = True self._dyn_pl = 0x3F - self._reg_write(DYNPD, self._dyn_pl) - else: - self._features = self._reg_read(FEATURE) + self._reg_write(DYN_PL_LEN, self._dyn_pl) self._features = (self._features & 5) | (6 if enable else 0) - self._reg_write(FEATURE, self._features) + self._reg_write(TX_FEATURE, self._features) def load_ack(self, buf, pipe_number): """This allows the MCU to specify a payload to be allocated into the @@ -497,8 +523,9 @@ def load_ack(self, buf, pipe_number): if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0, 5]") if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with length in range [1, 32]") - if not self.ack: + raise ValueError("buf must be a buffer protocol object with " + "length in range [1, 32]") + if not bool((self._features & 6) == 6 and self._aa and self._dyn_pl): self.ack = True if not self.tx_full: self._reg_write_bytes(0xA8 | pipe_number, buf) @@ -514,8 +541,12 @@ def read_ack(self): def data_rate(self): """This `int` attribute specifies the nRF24L01's frequency data rate for OTA (over the air) transmissions.""" - self._rf_setup = self._reg_read(RF_SETUP) - return (2 if self._rf_setup & 0x28 == 8 else 250) if self._rf_setup & 0x28 else 1 + self._rf_setup = self._reg_read(RF_PA_RATE) + if self._rf_setup & 0x28: + if self._rf_setup & 0x28 == 8: + return 2 + return 250 + return 1 @data_rate.setter def data_rate(self, speed): @@ -523,30 +554,30 @@ def data_rate(self, speed): if self.data_rate != speed: speed = 0 if speed == 1 else (8 if speed == 2 else 0x20) self._rf_setup = self._rf_setup & 0xD7 | speed - self._reg_write(RF_SETUP, self._rf_setup) + self._reg_write(RF_PA_RATE, self._rf_setup) else: - raise ValueError( - "data rate must be one of the following ([M,M,K]bps): 1, 2, 250") + raise ValueError("data rate must be one of the following " + "([M,M,K]bps): 1, 2, 250") @property def channel(self): """This `int` attribute specifies the nRF24L01's frequency.""" - return self._reg_read(RF_CH) + return self._reg_read(0x05) @channel.setter def channel(self, channel): if 0 <= channel <= 125: self._channel = channel - self._reg_write(RF_CH, channel) + self._reg_write(0x05, channel) else: raise ValueError("channel acn only be set in range [0, 125]") @property def crc(self): - """This `int` attribute specifies the nRF24L01's CRC (cyclic redundancy - checking) encoding scheme in terms of byte length.""" - self._config = self._reg_read(CONFIG) - return max(0, ((self._config & 12) >> 2) - 1) + """This `int` attribute specifies the nRF24L01's CRC (cyclic + redundancy checking) encoding scheme in terms of byte length.""" + self._config = self._reg_read(CONFIGURE) + return max(0, ((self._config & 0x0C) >> 2) - 1) @crc.setter def crc(self, length): @@ -567,28 +598,29 @@ def power(self): @power.setter def power(self, is_on): assert isinstance(is_on, (bool, int)) - self._config = self._reg_read(CONFIG) - if self.power != is_on: - self._config = (self._config & 0x7d) | (is_on << 1) - self._reg_write(CONFIG, self._config) + self._config = self._reg_read(CONFIGURE) + if self.power != bool(is_on): + self._config = (self._config & 0x7d) | (bool(is_on) << 1) + self._reg_write(CONFIGURE, self._config) time.sleep(0.00016) @property def pa_level(self): - """This `int` attribute specifies the nRF24L01's power amplifier level - (in dBm).""" - self._rf_setup = self._reg_read(RF_SETUP) - return (3 - ((self._rf_setup & RF_SETUP) >> 1)) * -6 + """This `int` attribute specifies the nRF24L01's power amplifier + level (in dBm).""" + self._rf_setup = self._reg_read(RF_PA_RATE) + return (3 - ((self._rf_setup & RF_PA_RATE) >> 1)) * -6 @pa_level.setter def pa_level(self, power): if power in (-18, -12, -6, 0): power = (3 - int(power / -6)) * 2 - self._rf_setup = (self._rf_setup & 0xF9) | power - self._reg_write(RF_SETUP, self._rf_setup) + if self.pa_level != power: + self._rf_setup = (self._rf_setup & 0xF9) | power + self._reg_write(RF_PA_RATE, self._rf_setup) else: - raise ValueError( - "power amplitude must be one of the following (dBm): -18, -12, -6, 0") + raise ValueError("power amplitude must be one of the following " + "(dBm): -18, -12, -6, 0") @property def rpd(self): @@ -598,8 +630,8 @@ def rpd(self): @property def tx_full(self): - """An attribute to represent the nRF24L01's status flag signaling that - the TX FIFO buffer is full. (read-only)""" + """An attribute to represent the nRF24L01's status flag signaling + that the TX FIFO buffer is full. (read-only)""" return bool(self._status & 1) def update(self): @@ -612,40 +644,50 @@ def resend(self): top level (first out) of the TX FIFO buffer.""" result = False if not self.fifo(True, True): - self.clear_status_flags(False) + get_ack_pl = bool(self._features & 6 == 6 and self._aa and + self._dyn_pl) + if get_ack_pl: + self.flush_rx() + self.clear_status_flags(get_ack_pl) self._reg_write(0xE3) - pl_coef = 1 + bool(self._setup_retr & 0x0f) - pl_len = 1 + self._addr_len + ( - max(0, ((self._config & 12) >> 2) - 1)) - bitrate = ((2000000 if self._rf_setup & 0x28 == 8 else 250000) - if self._rf_setup & 0x28 else 1000000) / 8 - stby2active = (1 + pl_coef) * 0.00013 - t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 - t_retry = (((self._setup_retr & 0xf0) >> 4) * 250 + - 380) * (self._setup_retr & 0x0f) / 1000000 - timeout = pl_coef * (((8 * (32 + pl_len)) + 9) / bitrate) + \ - stby2active + t_irq + t_retry + tx_packet_len = 8 * (33 + self._addr_len + ( + max(0, ((self._config & 12) >> 2) - 1))) + 9 + ack_packet_len = 0 + if self._aa: + ack_packet_len = ( + (1 + self._addr_len + 32 * get_ack_pl) * 8 + 9) + rf_rate = self._rf_setup & 0x28 + bitrate = ( + (250000 if rf_rate == 8 else 31250) if rf_rate else 125000) + stby2active = (1 + bool(self._aa)) * 0.00013 + t_irq = 0.0000082 if not rf_rate else 0.000006 + t_retry = (((self._retry_setup & 0xf0) >> 4) * 250 + + 380) * (self._retry_setup & 0x0f) / 1000000 self.ce_pin.value = 0 self.ce_pin.value = 1 time.sleep(0.00001) self.ce_pin.value = 0 - self._wait_for_result(timeout) + self._wait4result(((tx_packet_len + ack_packet_len) / bitrate) + + stby2active + t_irq + t_retry) result = self.irq_ds - if self.ack and self.irq_dr: - result = self.recv() + if get_ack_pl: + result = self.recv() # get ACK payload self.clear_status_flags(False) return result def write(self, buf, ask_no_ack=False): - """This non-blocking function (when used as alternative to `send()`) is meant for - asynchronous applications and can only handle one payload at a time as it is a - helper function to `send()`.""" + """This non-blocking function (when used as alternative to `send()`) + is meant for asynchronous applications and can only handle one + payload at a time as it is a helper function to `send()`.""" if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with length in range [1, 32]") - self.clear_status_flags(False) - if self._config & 3 != 2: - self._config = (self._reg_read(CONFIG) & 0x7c) | 2 - self._reg_write(CONFIG, self._config) + raise ValueError("buf must be a buffer protocol object with " + "length in range [1, 32]") + self.clear_status_flags( + bool(self._features & 6 == 6 and self._aa and self._dyn_pl)) + self._features = self._reg_read(TX_FEATURE) + if self._config & 3 != 2: # is radio powered up in TX mode? + self._config = (self._reg_read(CONFIGURE) & 0x7c) | 2 + self._reg_write(CONFIGURE, self._config) time.sleep(0.00016) if not self.dynamic_payloads: if len(buf) < self.payload_length: @@ -656,33 +698,39 @@ def write(self, buf, ask_no_ack=False): if ask_no_ack: if self._features & 1 == 0: self._features = self._features & 0xFE | 1 - self._reg_write(FEATURE, self._features) + self._reg_write(TX_FEATURE, self._features) self._reg_write_bytes(0xA0 | (ask_no_ack << 4), buf) self.ce_pin.value = 1 def flush_rx(self): - """A helper function to flush the nRF24L01's internal RX FIFO buffer. (write-only)""" + """A helper function to flush the nRF24L01's internal RX FIFO buffer. + (write-only)""" self._reg_write(0xE2) def flush_tx(self): - """A helper function to flush the nRF24L01's internal TX FIFO buffer. (write-only)""" + """A helper function to flush the nRF24L01's internal TX FIFO buffer. + (write-only)""" self._reg_write(0xE1) def fifo(self, about_tx=False, check_empty=None): - """This provides some precision determining the status of the TX/RX FIFO buffers. - (read-only)""" + """This provides some precision determining the status of the TX/RX + FIFO buffers. (read-only)""" if (check_empty is None and isinstance(about_tx, (bool, int))) or \ - (isinstance(check_empty, (bool, int)) and isinstance(about_tx, (bool, int))): - self._fifo = self._reg_read(FIFO) + (isinstance(check_empty, (bool, int)) and + isinstance(about_tx, (bool, int))): + self._fifo = self._reg_read(0x17) + mask = 4 * about_tx if check_empty is None: - return (self._fifo & (0x30 if about_tx else 0x03)) >> (4 * about_tx) - return bool(self._fifo & ((2 - check_empty) << (4 * about_tx))) - raise ValueError("Argument 1 ('about_tx') must always be a bool or int. Argument 2" - " ('check_empty'), if specified, must be a bool or int") + return (self._fifo & (0x30 if about_tx else 0x03)) >> mask + return bool(self._fifo & ((2 - check_empty) << mask)) + raise ValueError("Argument 1 ('about_tx') must always be a bool or " + "int. Argument 2 ('check_empty'), if specified, must" + " be a bool or int") + @property def pipe(self): - """This function returns information about the data pipe that received the next - available payload in the RX FIFO buffer.""" + """This function returns information about the data pipe that received + the next available payload in the RX FIFO buffer.""" self.update() result = (self._status & 0x0E) >> 1 if result <= 5: @@ -690,8 +738,8 @@ def pipe(self): return None def address(self, index=-1): - """Returns the current address set to a specified data pipe or the TX address. - (read-only)""" + """Returns the current address set to a specified data pipe or the TX + address. (read-only)""" if index > 5: raise IndexError("index {} is out of bounds [0,5]".format(index)) if index < 0: @@ -700,7 +748,16 @@ def address(self, index=-1): return self._pipes[index] return bytes(self._pipes[index]) + self._pipes[1][1:] - def _wait_for_result(self, timeout): - start = time.monotonic() - while not self.irq_ds and not self.irq_df and (time.monotonic() - start) < timeout: - self.update() + def _wait4result(self, timeout): + try: + start = time.monotonic() + while not self._status & 0x30 and ( + time.monotonic() - start) < timeout: + self.update() + except AttributeError: + # in micropython, time.ticks_ms() / 1000.0 = time.monotonic() + # pylint: disable=no-member + start = time.ticks_ms() / 1000.0 + while not self._status & 0x30 and ( + time.ticks_ms() / 1000.0 - start) < timeout: + self.update() diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index ad2b165..5dacf5c 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -1,9 +1,13 @@ # see license and copyright information in rf24.py of this directory -# pylint: disable=missing-class-docstring,missing-function-docstring,missing-module-docstring +# pylint: disable=missing-class-docstring,missing-function-docstring +# pylint: disable=missing-module-docstring __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/2bndy5/CircuitPython_nRF24L01.git" import time -from adafruit_bus_device.spi_device import SPIDevice +try: + from ubus_device import SPIDevice +except ImportError: + from adafruit_bus_device.spi_device import SPIDevice class RF24: def __init__(self, spi, csn, ce): @@ -33,20 +37,22 @@ def __init__(self, spi, csn, ce): # pylint: disable=no-member def _reg_read(self, reg): - buf = bytearray(2) + out_buf = bytearray([reg, 0]) + in_buf = bytearray([0, 0]) with self._spi as spi: time.sleep(0.005) - spi.readinto(buf, write_value=reg) - self._status = buf[0] - return buf[1] + spi.write_readinto(out_buf, in_buf) + self._status = in_buf[0] + return in_buf[1] def _reg_read_bytes(self, reg, buf_len=5): - buf = bytearray(buf_len + 1) + in_buf = bytearray(buf_len + 1) + out_buf = bytearray([reg]) + b'\x00' * buf_len with self._spi as spi: time.sleep(0.005) - spi.readinto(buf, write_value=reg) - self._status = buf[0] - return buf[1:] + spi.write_readinto(out_buf, in_buf) + self._status = in_buf[0] + return in_buf[1:] def _reg_write_bytes(self, reg, out_buf): out_buf = bytes([0x20 | reg]) + out_buf @@ -75,9 +81,9 @@ def open_tx_pipe(self, address): self._reg_write(0x11, self.payload_length) self._reg_write_bytes(0x10, address) else: - raise ValueError( - "address must be a buffer protocol object with a byte length\nequal " - "to the address_length attribute (currently set to 5)") + raise ValueError("address must be a buffer protocol object with" + " a byte length\nequal to the address_length " + "attribute (currently set to 5)") def close_rx_pipe(self, pipe_number): if pipe_number < 0 or pipe_number > 5: @@ -90,9 +96,9 @@ def open_rx_pipe(self, pipe_number, address): if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0, 5]") if len(address) != 5: - raise ValueError( - "address must be a buffer protocol object with a byte length\nequal " - "to the address_length attribute (currently set to 5)") + raise ValueError("address must be a buffer protocol object with" + " a byte length\nequal to the address_length " + "attribute (currently set to 5)") if pipe_number < 2: if not pipe_number: self._pipe0_read_addr = address @@ -110,9 +116,10 @@ def listen(self): def listen(self, is_rx): assert isinstance(is_rx, (bool, int)) if self.listen != is_rx: - self._start_listening() - else: - self._stop_listening() + if is_rx: + self._start_listening() + else: + self._stop_listening() def _start_listening(self): if self.ce_pin.value: @@ -133,7 +140,7 @@ def _stop_listening(self): time.sleep(0.00016) def any(self): - if self._reg_read(0x1D) & 4: + if self._reg_read(0x1D) & 4 and self.irq_dr: return self._reg_read(0x60) if self.irq_dr: return self._reg_read(0x11 + ((self._status & 0xE) >> 1)) @@ -141,9 +148,11 @@ def any(self): def recv(self): pl_wid = self.any() - result = self._reg_read_bytes(0x61, pl_wid) - self.clear_status_flags(True, False, False) - return result + if pl_wid: + result = self._reg_read_bytes(0x61, pl_wid) + self.clear_status_flags(True, False, False) + return result + return None def send(self, buf, ask_no_ack=False, force_retry=0): self.ce_pin.value = 0 @@ -152,30 +161,27 @@ def send(self, buf, ask_no_ack=False, force_retry=0): result = [] for i, b in enumerate(buf): if not b or len(b) > 32: - raise ValueError( - "buf (item {} in the list/tuple) must be a" - " buffer protocol object with length in range [1, 32]" % i) + raise ValueError("buf (item {} in the list/tuple) must" + " be a buffer protocol object with " + "length in range [1, 32]".format(i)) for i, b in enumerate(buf): result.append(self.send(b, ask_no_ack, force_retry)) return result if not buf or len(buf) > 32: - raise ValueError( - "buf must be a buffer protocol object with length in range [1, 32]") + raise ValueError("buf must be a buffer protocol object with " + "length in range [1, 32]") arc_d = self._reg_read(4) + get_ack_pl = bool(self._reg_read(0x1D) & 2) need_ack = bool((arc_d & 0xF) and not ask_no_ack) - t_ack = 0 - if need_ack: - t_ack = 329 / 250000 + t_ack = 0 if need_ack else (329 if get_ack_pl else 73) t_retry = ((arc_d >> 4) * 250 + 380) * (arc_d & 0xF) / 1000000 timeout = ( - (((8 * (len(buf) + 8)) + 9) / 250000) - + (1 + need_ack) * 0.00013 + 0.0000082 + t_retry + t_ack) + (((8 * (len(buf) + 8)) + 9 + t_ack) / 250000) + + (1 + need_ack) * 0.00013 + 0.0000082 + t_retry) self.write(buf, ask_no_ack) time.sleep(0.00001) self.ce_pin.value = 0 - start = time.monotonic() - while not (self._status & 0x30) and (time.monotonic() - start) < timeout: - self.update() + self._wait4result(timeout) if need_ack and self.irq_df: for _ in range(force_retry): result = self.resend() @@ -183,7 +189,7 @@ def send(self, buf, ask_no_ack=False, force_retry=0): break else: result = self.irq_ds - if self._reg_read(0x1D) & 2 and self.irq_dr and not ask_no_ack: + if get_ack_pl and self.irq_dr and not ask_no_ack: result = self.recv() self.clear_status_flags(False) return result @@ -200,12 +206,16 @@ def irq_ds(self): def irq_df(self): return bool(self._status & 0x10) - def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): - self._reg_write(7, (data_recv << 6) | (data_sent << 5) | (data_fail << 4)) + def clear_status_flags(self, data_recv=True, data_sent=True, + data_fail=True): + flag_config = (bool(data_recv) << 6) | (bool(data_sent) << 5) + flag_config |= (bool(data_fail) << 4) + self._reg_write(7, flag_config) - def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): - config = self._reg_read(0) & 0x0F - config |= (not data_fail << 4) | (not data_sent << 5) | (not data_recv << 6) + def interrupt_config(self, data_recv=True, data_sent=True, + data_fail=True): + config = (self._reg_read(0) & 0x0F) | (not bool(data_recv) << 6) + config |= (not bool(data_fail) << 4) | (not bool(data_sent) << 5) self._reg_write(0, config) @property @@ -231,7 +241,8 @@ def payload_length(self, length): if not length or length <= 32: self._payload_length = length else: - raise ValueError("{}: payload length can only be in range [1, 32] bytes" % length) + raise ValueError("{}: payload length can only be in range [1, " + "32] bytes".format(length)) @property def arc(self): @@ -245,7 +256,8 @@ def arc(self, count): setup_retr = (setup_retr & 0xF0) | count self._reg_write(4, setup_retr) else: - raise ValueError("automatic re-transmit count must in range [0, 15]") + raise ValueError( + "automatic re-transmit count must in range [0, 15]") @property def ard(self): @@ -254,17 +266,16 @@ def ard(self): @ard.setter def ard(self, delta_t): if 250 <= delta_t <= 4000 and delta_t % 250 == 0: - setup_retr = self._reg_read(4) - if ((setup_retr & 0xF0) >> 4) * 250 + 250 != delta_t: - setup_retr = (int((delta_t - 250) / 250) << 4) | (setup_retr & 0x0F) - self._reg_write(4, setup_retr) + setup_retr = self._reg_read(4) & 0x0F + setup_retr |= (int((delta_t - 250) / 250) << 4) + self._reg_write(4, setup_retr) else: - raise ValueError( - "automatic re-transmit delay must be a multiple of 250 in range [250,4000]") + raise ValueError("automatic re-transmit delay must be a multiple" + " of 250 in range [250,4000]") @property def ack(self): - return bool((self._reg_read(0x1D) & 6) and self._reg_read(1)) + return bool((self._reg_read(0x1D) & 6 == 6) and self._reg_read(1)) @ack.setter def ack(self, enable): @@ -281,8 +292,8 @@ def load_ack(self, buf, pipe_number): if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0, 5]") if not buf or len(buf) > 32: - raise ValueError( - "buf must be a buffer protocol object with length in range [1, 32]") + raise ValueError("buf must be a buffer protocol object with" + " length in range [1, 32]") if not self._reg_read(0x1D) & 2: self.ack = True if not self._status & 1: @@ -299,13 +310,11 @@ def data_rate(self): def data_rate(self, speed): if speed in (1, 2, 250): speed = 0 if speed == 1 else (8 if speed == 2 else 0x20) - rf_setup = self._reg_read(6) - if ((2 if rf_setup == 8 else 250) if rf_setup else 1) != speed: - rf_setup = (rf_setup & 0xD7) | speed - self._reg_write(6, rf_setup) + rf_setup = (self._reg_read(6) & 0xD7) | speed + self._reg_write(6, rf_setup) else: - raise ValueError( - "data rate must be one of the following ([M,M,K]bps): 1, 2, 250") + raise ValueError("data rate must be one of the following " + "([M,M,K]bps): 1, 2, 250") @property def channel(self): @@ -316,7 +325,7 @@ def channel(self, channel): if 0 <= channel <= 125: self._reg_write(5, channel) else: - raise ValueError("channel acn only be set in range [0,125]") + raise ValueError("channel can only be set in range [0,125]") @property def power(self): @@ -329,7 +338,6 @@ def power(self, is_on): if bool(config & 2) != is_on: config = (config & 0x7D) | (is_on << 1) self._reg_write(0, config) - # power up/down takes < 150 µs + 4 µs time.sleep(0.00016) @property @@ -344,8 +352,8 @@ def pa_level(self, power): rf_setup = (self._reg_read(6) & 0xF9) | power self._reg_write(6, rf_setup) else: - raise ValueError( - "power amplitude must be one of the following (dBm): -18, -12, -6, 0") + raise ValueError("power amplitude must be one of the following" + " (dBm): -18, -12, -6, 0") def update(self): self._reg_write(0xFF) @@ -362,9 +370,7 @@ def resend(self): self.ce_pin.value = 1 time.sleep(0.00001) self.ce_pin.value = 0 - start = time.monotonic() - while not (self._status & 0x30) and (time.monotonic() - start) < timeout: - self.update() + self._wait4result(timeout) result = self.irq_ds if self._reg_read(0x1D) & 2 and self.irq_dr: result = self.recv() @@ -373,8 +379,8 @@ def resend(self): def write(self, buf, ask_no_ack=False): if not buf or len(buf) > 32: - raise ValueError( - "buf must be a buffer protocol object with length in range [1, 32]") + raise ValueError("buf must be a buffer protocol object with " + "length in range [1, 32]") self.clear_status_flags(False) config = self._reg_read(0) if config & 3 != 2: @@ -394,3 +400,16 @@ def flush_rx(self): def flush_tx(self): self._reg_write(0xE1) + + def _wait4result(self, timeout): + try: + start = time.monotonic() + while not self._status & 0x30 and ( + time.monotonic() - start) < timeout: + self.update() + except AttributeError: + # pylint: disable=no-member + start = time.ticks_ms() / 1000.0 + while not self._status & 0x30 and ( + time.ticks_ms() / 1000.0 - start) < timeout: + self.update() diff --git a/docs/api.rst b/docs/api.rst index 5842580..e5c567a 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -839,10 +839,10 @@ fifo() - ``2`` means the specified FIFO buffer is empty - ``0`` means the specified FIFO buffer is neither full nor empty -pipe() +pipe ****************************** -.. automethod:: circuitpython_nrf24l01.rf24.RF24.pipe +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pipe :returns: - `None` if there is no payload in RX FIFO. From a3d898b7a0f85b3b65973ffcb8aad95ff837e0c1 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 3 Apr 2020 03:36:54 -0700 Subject: [PATCH 025/127] pylint: disabling too-many-locals check --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bbc1ea1..0bc7473 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,7 +44,7 @@ jobs: run: git describe --dirty --always --tags - name: PyLint run: | - pylint --disable=too-many-instance-attributes,too-many-public-methods circuitpython_nrf24l01/*.py + pylint --disable=too-many-instance-attributes,too-many-public-methods,too-many-locals circuitpython_nrf24l01/*.py ([[ ! -d "examples" ]] || pylint --disable=invalid-name $( find . -path "./examples/*.py" )) - name: Build assets run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . --package_folder_prefix ${{ steps.repo-name.outputs.repo-name }} From 868325bd53d05124973f70372c8a1d5009e77c41 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 3 Apr 2020 03:47:44 -0700 Subject: [PATCH 026/127] "pipe()" is now read-only attribute "pipe" --- examples/nrf24l01_ack_payload_test.py | 2 +- examples/nrf24l01_simple_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/nrf24l01_ack_payload_test.py b/examples/nrf24l01_ack_payload_test.py index 371a480..a9ac72f 100644 --- a/examples/nrf24l01_ack_payload_test.py +++ b/examples/nrf24l01_ack_payload_test.py @@ -93,7 +93,7 @@ def slave(count=3): count -= 1 # print details about the received packet (if any) print("Found {} bytes on pipe {}\ - ".format(repr(nrf.any()), nrf.pipe())) + ".format(repr(nrf.any()), nrf.pipe)) # retreive the received packet's payload rx = nrf.recv() # clears flags & empties RX FIFO print("Received (raw): {}".format(repr(rx))) diff --git a/examples/nrf24l01_simple_test.py b/examples/nrf24l01_simple_test.py index cd43349..625bab5 100644 --- a/examples/nrf24l01_simple_test.py +++ b/examples/nrf24l01_simple_test.py @@ -65,7 +65,7 @@ def slave(count=3): if nrf.any(): # print details about the received packet (if any) print("Found {} bytes on pipe {}\ - ".format(repr(nrf.any()), nrf.pipe())) + ".format(repr(nrf.any()), nrf.pipe)) # retreive the received packet's payload rx = nrf.recv() # clears flags & empties RX FIFO # expecting an int, thus the string format ' Date: Fri, 3 Apr 2020 03:56:53 -0700 Subject: [PATCH 027/127] weird sphinx complaint about indent --- docs/api.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index e5c567a..3080114 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -845,9 +845,10 @@ pipe .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pipe :returns: - - `None` if there is no payload in RX FIFO. - - The `int` identifying pipe number [0,5] that received the next available payload in - the RX FIFO buffer. + + * `None` if there is no payload in RX FIFO. + * The `int` identifying pipe number [0,5] that received the next available payload in + the RX FIFO buffer. address() ****************************** From cd2ec4ac03d540cad6c5c56c90b9ed7e11849c81 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 3 Apr 2020 04:12:09 -0700 Subject: [PATCH 028/127] oh-k fixed docs about "pipe" --- circuitpython_nrf24l01/rf24.py | 2 +- docs/api.rst | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 38e816b..1ab9b6d 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -729,7 +729,7 @@ def fifo(self, about_tx=False, check_empty=None): @property def pipe(self): - """This function returns information about the data pipe that received + """This attribute returns information about the data pipe that received the next available payload in the RX FIFO buffer.""" self.update() result = (self._status & 0x0E) >> 1 diff --git a/docs/api.rst b/docs/api.rst index 3080114..df555fb 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -80,7 +80,7 @@ using the ATSAMD21 M0). The following functionality has been removed from the li * `address` * `rpd` - * `pipe()` + * `pipe` * `fifo()` * `tx_full` * `address_length` (this is always set to 5 bytes) @@ -844,8 +844,9 @@ pipe .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pipe - :returns: + (read only) + :returns: * `None` if there is no payload in RX FIFO. * The `int` identifying pipe number [0,5] that received the next available payload in the RX FIFO buffer. From e0d5e6c19af7c6e5a93df7caa1342c4774b60af0 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 5 Apr 2020 15:55:47 -0700 Subject: [PATCH 029/127] no more timeout calc; payload_length attribute changed abandon timeout calculations in send() & resend(). `payload_length` is set to all pipes & removed internal `_pl_widths[]` for context manager as it was synonomous with `paylaod_length` anyway --- circuitpython_nrf24l01/rf24.py | 74 +++++++---------------------- circuitpython_nrf24l01/rf24_lite.py | 48 ++++++------------- docs/api.rst | 24 ++++------ 3 files changed, 40 insertions(+), 106 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 1ab9b6d..8259d41 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -45,14 +45,12 @@ class RF24: """A driver class for the nRF24L01(+) transceiver radios.""" def __init__(self, spi, csn, ce): - self._payload_length = 32 + self._pl_len = 32 self.payload_length = 32 self._fifo = 0 self._status = 0 # init shadow copy of RX addresses for all pipes for context manager self._pipes = [b'\xE7' * 5, b'\xC2' * 5, 0, 0, 0, 0] - # init shadow copy of static payload lengths for context manager - self._payload_widths = [32] * 6 # payload_length for each pipe # shadow copy of last RX_ADDR_P0 written to pipe 0 needed as # open_tx_pipe() appropriates pipe 0 for ACK packet self._pipe0_read_addr = None @@ -110,7 +108,7 @@ def __enter__(self): self._reg_write_bytes(RX_ADDR_P0 + i, address) else: self._reg_write(RX_ADDR_P0 + i, address) - self._reg_write(RX_PL_LENG + i, self._payload_widths[i]) + self._reg_write(RX_PL_LENG + i, self._pl_len) self._reg_write_bytes(TX_ADDRESS, self._tx_address) self.address_length = self._addr_len self.channel = self._channel @@ -183,8 +181,6 @@ def open_tx_pipe(self, address): self._reg_write_bytes(RX_ADDR_P0, address) self._open_pipes = self._open_pipes | 1 self._reg_write(OPEN_PIPES, self._open_pipes) - self._payload_widths[0] = self.payload_length - self._reg_write(RX_PL_LENG, self.payload_length) self._pipes[0] = address self._tx_address = address self._reg_write_bytes(TX_ADDRESS, address) @@ -225,8 +221,6 @@ def open_rx_pipe(self, pipe_number, address): self._open_pipes = self._reg_read(OPEN_PIPES) self._open_pipes = self._open_pipes | (1 << pipe_number) self._reg_write(OPEN_PIPES, self._open_pipes) - self._reg_write(RX_PL_LENG + pipe_number, self.payload_length) - self._payload_widths[pipe_number] = self.payload_length @property def listen(self): @@ -303,22 +297,11 @@ def send(self, buf, ask_no_ack=False, force_retry=0): get_ack_pl = bool(self._features & 6 == 6 and self._dyn_pl and use_ack) if get_ack_pl: self.flush_rx() - packet_data = 1 + self._addr_len + (max(0, ((self._config & 12) >> 2) - - 1)) - bitrate = ((250000 if self._rf_setup & 0x28 == 8 else 31250) if - self._rf_setup & 0x28 else 125000) - t_ack = 0 - if use_ack: - t_ack = (((packet_data + 32 * get_ack_pl) * 8 + 9) / bitrate) - stby2active = (1 + (use_ack)) * 0.00013 - t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 - t_retry = (((self._retry_setup & 0xf0) >> 4) * 250 + 380) * \ - (self._retry_setup & 0x0f) / 1000000 self.write(buf, ask_no_ack) time.sleep(0.00001) self.ce_pin.value = 0 - self._wait4result((((8 * (len(buf) + packet_data)) + 9) / - bitrate) + stby2active + t_irq + t_retry + t_ack) + while not self._status & 0x30: + self.update() if self._retry_setup & 0x0f and self.irq_df: for _ in range(force_retry): result = self.resend() @@ -414,7 +397,7 @@ def what_happened(self, dump_pipes=False): "(closed)" print("Pipe", i, is_open, "bound:", self.address(i)) if is_open: - print('\t\texpecting', self._payload_widths[i], + print('\t\texpecting', self._pl_len, 'byte static payloads') @property @@ -438,12 +421,14 @@ def dynamic_payloads(self, enable): @property def payload_length(self): """This `int` attribute specifies the length (in bytes) of payload""" - return self._payload_length + return self._pl_len @payload_length.setter def payload_length(self, length): if not length or length <= 32: - self._payload_length = length + self._pl_len = length + for i in range(6): + self._reg_write(RX_PL_LENG + i, length) else: raise ValueError("{}: payload length can only be set in range [1," " 32] bytes".format(length)) @@ -650,25 +635,12 @@ def resend(self): self.flush_rx() self.clear_status_flags(get_ack_pl) self._reg_write(0xE3) - tx_packet_len = 8 * (33 + self._addr_len + ( - max(0, ((self._config & 12) >> 2) - 1))) + 9 - ack_packet_len = 0 - if self._aa: - ack_packet_len = ( - (1 + self._addr_len + 32 * get_ack_pl) * 8 + 9) - rf_rate = self._rf_setup & 0x28 - bitrate = ( - (250000 if rf_rate == 8 else 31250) if rf_rate else 125000) - stby2active = (1 + bool(self._aa)) * 0.00013 - t_irq = 0.0000082 if not rf_rate else 0.000006 - t_retry = (((self._retry_setup & 0xf0) >> 4) * 250 + - 380) * (self._retry_setup & 0x0f) / 1000000 self.ce_pin.value = 0 self.ce_pin.value = 1 time.sleep(0.00001) self.ce_pin.value = 0 - self._wait4result(((tx_packet_len + ack_packet_len) / bitrate) - + stby2active + t_irq + t_retry) + while not self._status & 0x30: + self.update() result = self.irq_ds if get_ack_pl: result = self.recv() # get ACK payload @@ -690,11 +662,11 @@ def write(self, buf, ask_no_ack=False): self._reg_write(CONFIGURE, self._config) time.sleep(0.00016) if not self.dynamic_payloads: - if len(buf) < self.payload_length: - for _ in range(self.payload_length - len(buf)): - buf += b'\x00' - elif len(buf) > self.payload_length: - buf = buf[:self.payload_length] + if len(buf) < self._pl_len: + for _ in range(self._pl_len - len(buf)): + buf += b"\x00" + elif len(buf) > self._pl_len: + buf = buf[ : self._pl_len] if ask_no_ack: if self._features & 1 == 0: self._features = self._features & 0xFE | 1 @@ -747,17 +719,3 @@ def address(self, index=-1): if index <= 1: return self._pipes[index] return bytes(self._pipes[index]) + self._pipes[1][1:] - - def _wait4result(self, timeout): - try: - start = time.monotonic() - while not self._status & 0x30 and ( - time.monotonic() - start) < timeout: - self.update() - except AttributeError: - # in micropython, time.ticks_ms() / 1000.0 = time.monotonic() - # pylint: disable=no-member - start = time.ticks_ms() / 1000.0 - while not self._status & 0x30 and ( - time.ticks_ms() / 1000.0 - start) < timeout: - self.update() diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index 5dacf5c..bb61ec7 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -13,7 +13,6 @@ class RF24: def __init__(self, spi, csn, ce): self._pipe0_read_addr = None self._status = 0 - self._payload_length = 32 self.ce_pin = ce self.ce_pin.switch_to_output(value=False) self._spi = SPIDevice(spi, chip_select=csn, baudrate=1250000) @@ -30,6 +29,7 @@ def __init__(self, spi, csn, ce): self._reg_write(1, 0x3F) self._reg_write(0x1D, 5) self._reg_write(4, 0x53) + self.payload_length = 32 self.flush_rx() self.flush_tx() @@ -78,7 +78,6 @@ def open_tx_pipe(self, address): if len(address) == 5: self._reg_write_bytes(0x0A, address) self._reg_write(2, self._reg_read(2) | 1) - self._reg_write(0x11, self.payload_length) self._reg_write_bytes(0x10, address) else: raise ValueError("address must be a buffer protocol object with" @@ -106,7 +105,6 @@ def open_rx_pipe(self, pipe_number, address): else: self._reg_write(0x0A + pipe_number, address[0]) self._reg_write(2, self._reg_read(2) | (1 << pipe_number)) - self._reg_write(0x11 + pipe_number, self._payload_length) @property def listen(self): @@ -170,18 +168,13 @@ def send(self, buf, ask_no_ack=False, force_retry=0): if not buf or len(buf) > 32: raise ValueError("buf must be a buffer protocol object with " "length in range [1, 32]") - arc_d = self._reg_read(4) get_ack_pl = bool(self._reg_read(0x1D) & 2) - need_ack = bool((arc_d & 0xF) and not ask_no_ack) - t_ack = 0 if need_ack else (329 if get_ack_pl else 73) - t_retry = ((arc_d >> 4) * 250 + 380) * (arc_d & 0xF) / 1000000 - timeout = ( - (((8 * (len(buf) + 8)) + 9 + t_ack) / 250000) - + (1 + need_ack) * 0.00013 + 0.0000082 + t_retry) + need_ack = bool((self._reg_read(4) & 0xF) and not ask_no_ack) self.write(buf, ask_no_ack) time.sleep(0.00001) self.ce_pin.value = 0 - self._wait4result(timeout) + while not self._status & 0x30: + self.update() if need_ack and self.irq_df: for _ in range(force_retry): result = self.resend() @@ -233,13 +226,14 @@ def dynamic_payloads(self, enable): @property def payload_length(self): - return self._payload_length + return self._reg_read(0x11) @payload_length.setter def payload_length(self, length): # max payload size is 32 bytes if not length or length <= 32: - self._payload_length = length + for i in range(6): + self._reg_write(0x11 + i, length) else: raise ValueError("{}: payload length can only be in range [1, " "32] bytes".format(length)) @@ -363,14 +357,12 @@ def resend(self): if not self._reg_read(0x17) & 0x10: self.clear_status_flags(False) self._reg_write(0xE3) - arc_d = self._reg_read(4) - t_retry = ((arc_d >> 4) * 250 + 380) * (arc_d & 0xF) / 1000000 - timeout = (329 / 125000 + 0.0002682 + t_retry) self.ce_pin.value = 0 self.ce_pin.value = 1 time.sleep(0.00001) self.ce_pin.value = 0 - self._wait4result(timeout) + while not self._status & 0x30: + self.update() result = self.irq_ds if self._reg_read(0x1D) & 2 and self.irq_dr: result = self.recv() @@ -387,11 +379,12 @@ def write(self, buf, ask_no_ack=False): self._reg_write(0, (config & 0x7C) | 2) time.sleep(0.00016) if not self.dynamic_payloads: - if len(buf) < self.payload_length: - for _ in range(self.payload_length - len(buf)): + pl_width = self.payload_length + if len(buf) < pl_width: + for _ in range(pl_width - len(buf)): buf += b"\x00" - elif len(buf) > self.payload_length: - buf = buf[: self.payload_length] + elif len(buf) > pl_width: + buf = buf[ : pl_width] self._reg_write_bytes(0xA0 | (ask_no_ack << 4), buf) self.ce_pin.value = 1 @@ -400,16 +393,3 @@ def flush_rx(self): def flush_tx(self): self._reg_write(0xE1) - - def _wait4result(self, timeout): - try: - start = time.monotonic() - while not self._status & 0x30 and ( - time.monotonic() - start) < timeout: - self.update() - except AttributeError: - # pylint: disable=no-member - start = time.ticks_ms() / 1000.0 - while not self._status & 0x30 and ( - time.ticks_ms() / 1000.0 - start) < timeout: - self.update() diff --git a/docs/api.rst b/docs/api.rst index df555fb..b978835 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2,11 +2,8 @@ .. currentmodule:: circuitpython_nrf24l01.rf24 -RF24 class -============== - Troubleshooting info --------------------- +==================== .. important:: The nRF24L01 has 3 key features that can be interdependent of each other. Their priority of dependence is as follows: @@ -72,23 +69,27 @@ With the `auto_ack` feature enabled, you get: parameters for more details). About the lite version ----------------------- +====================== This library contains a "lite" version of ``rf24.py`` titled ``rf24_lite.py``. It has been developed to save space on microcontrollers with limited amount of RAM and/or storage (like boards using the ATSAMD21 M0). The following functionality has been removed from the lite version: * `address` + * `what_happened()` * `rpd` * `pipe` * `fifo()` * `tx_full` * `address_length` (this is always set to 5 bytes) - * `read_ack()` (depricated anyway; use `recv()` instead) + * `read_ack()` (deprecated anyway; use `recv()` instead) * `crc` (always using 2 bytes encoding scheme) * `auto_ack` (always on) * all comments and docstrings (meaning ``help()`` will not provide any specific information) +RF24 class +============== + Basic API --------- @@ -230,12 +231,8 @@ send() in the returned list will contain the returned status for each corresponding payload in the list/tuple that was passed. The return statuses will be in one of the following forms: - * `False` if transmission fails or reaches the timeout sentinal. The timeout condition - is very rare and could mean something/anything went wrong with either/both TX/RX - transceivers. The timeout sentinal for transmission is calculated using `table 18 in - the nRF24L01 specification sheet `_. - Transmission failure can only be returned if `arc` is greater than ``0``. + * `False` if transmission fails. Transmission failure can only be detected if `auto_ack` + is `False`. * `True` if transmission succeeds. * `bytearray` or `None` when the `ack` attribute is `True`. Because the payload expects a responding custom ACK payload, the response is returned (upon successful @@ -791,8 +788,7 @@ write() radio damage or misbehavior as a result of disobeying the 4 ms rule. See also `table 18 in the nRF24L01 specification sheet `_ for calculating - necessary transmission time (these calculations are used in the `send()` and `resend()` - functions). + an adequate transmission timeout sentinal. flush_rx() ****************************** From 53b18de124f4448561cac0056167d44fca03c383 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 5 Apr 2020 16:01:31 -0700 Subject: [PATCH 030/127] typo in docs --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index b978835..8231b12 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -232,7 +232,7 @@ send() in the list/tuple that was passed. The return statuses will be in one of the following forms: * `False` if transmission fails. Transmission failure can only be detected if `auto_ack` - is `False`. + is `True`. * `True` if transmission succeeds. * `bytearray` or `None` when the `ack` attribute is `True`. Because the payload expects a responding custom ACK payload, the response is returned (upon successful From e924bcdc34e0c9a68e55bdc2aa016fc3d03ba52d Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 5 Apr 2020 16:41:02 -0700 Subject: [PATCH 031/127] (-) `pa_level` from lite ver --- circuitpython_nrf24l01/rf24_lite.py | 54 ++++++++--------------------- docs/api.rst | 12 +++++-- 2 files changed, 23 insertions(+), 43 deletions(-) diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index bb61ec7..7b37846 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -80,13 +80,11 @@ def open_tx_pipe(self, address): self._reg_write(2, self._reg_read(2) | 1) self._reg_write_bytes(0x10, address) else: - raise ValueError("address must be a buffer protocol object with" - " a byte length\nequal to the address_length " - "attribute (currently set to 5)") + raise ValueError("address length must equal 5") def close_rx_pipe(self, pipe_number): if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0, 5]") + raise ValueError("pipe_number must be in range [0, 5]") open_pipes = self._reg_read(2) if open_pipes & (1 << pipe_number): self._reg_write(2, open_pipes & ~(1 << pipe_number)) @@ -95,9 +93,7 @@ def open_rx_pipe(self, pipe_number, address): if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0, 5]") if len(address) != 5: - raise ValueError("address must be a buffer protocol object with" - " a byte length\nequal to the address_length " - "attribute (currently set to 5)") + raise ValueError("address length must equal 5") if pipe_number < 2: if not pipe_number: self._pipe0_read_addr = address @@ -159,15 +155,13 @@ def send(self, buf, ask_no_ack=False, force_retry=0): result = [] for i, b in enumerate(buf): if not b or len(b) > 32: - raise ValueError("buf (item {} in the list/tuple) must" - " be a buffer protocol object with " - "length in range [1, 32]".format(i)) + raise ValueError("index {}: buffer must have a length in " + "range [1, 32] ".format(i)) for i, b in enumerate(buf): result.append(self.send(b, ask_no_ack, force_retry)) return result if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with " - "length in range [1, 32]") + raise ValueError("buffer must have a length in range [1, 32]") get_ack_pl = bool(self._reg_read(0x1D) & 2) need_ack = bool((self._reg_read(4) & 0xF) and not ask_no_ack) self.write(buf, ask_no_ack) @@ -235,8 +229,7 @@ def payload_length(self, length): for i in range(6): self._reg_write(0x11 + i, length) else: - raise ValueError("{}: payload length can only be in range [1, " - "32] bytes".format(length)) + raise ValueError("payload length must be in range [1, 32] bytes") @property def arc(self): @@ -250,8 +243,7 @@ def arc(self, count): setup_retr = (setup_retr & 0xF0) | count self._reg_write(4, setup_retr) else: - raise ValueError( - "automatic re-transmit count must in range [0, 15]") + raise ValueError("arc must in range [0, 15]") @property def ard(self): @@ -264,8 +256,8 @@ def ard(self, delta_t): setup_retr |= (int((delta_t - 250) / 250) << 4) self._reg_write(4, setup_retr) else: - raise ValueError("automatic re-transmit delay must be a multiple" - " of 250 in range [250,4000]") + raise ValueError("ard must be a multiple of 250 in range " + "[250, 4000]") @property def ack(self): @@ -286,8 +278,7 @@ def load_ack(self, buf, pipe_number): if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0, 5]") if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with" - " length in range [1, 32]") + raise ValueError("buffer must have a length in range [1, 32]") if not self._reg_read(0x1D) & 2: self.ack = True if not self._status & 1: @@ -307,8 +298,7 @@ def data_rate(self, speed): rf_setup = (self._reg_read(6) & 0xD7) | speed self._reg_write(6, rf_setup) else: - raise ValueError("data rate must be one of the following " - "([M,M,K]bps): 1, 2, 250") + raise ValueError("data_rate options limited to 1, 2, 250") @property def channel(self): @@ -319,7 +309,7 @@ def channel(self, channel): if 0 <= channel <= 125: self._reg_write(5, channel) else: - raise ValueError("channel can only be set in range [0,125]") + raise ValueError("channel can only be set in range [0, 125]") @property def power(self): @@ -334,21 +324,6 @@ def power(self, is_on): self._reg_write(0, config) time.sleep(0.00016) - @property - def pa_level(self): - rf_setup = self._reg_read(6) - return (3 - ((rf_setup & 6) >> 1)) * -6 - - @pa_level.setter - def pa_level(self, power): - if power in (-18, -12, -6, 0): - power = (3 - int(power / -6)) * 2 - rf_setup = (self._reg_read(6) & 0xF9) | power - self._reg_write(6, rf_setup) - else: - raise ValueError("power amplitude must be one of the following" - " (dBm): -18, -12, -6, 0") - def update(self): self._reg_write(0xFF) @@ -371,8 +346,7 @@ def resend(self): def write(self, buf, ask_no_ack=False): if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with " - "length in range [1, 32]") + raise ValueError("buffer must have a length in range [1, 32]") self.clear_status_flags(False) config = self._reg_read(0) if config & 3 != 2: diff --git a/docs/api.rst b/docs/api.rst index 8231b12..862a30d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -72,8 +72,9 @@ About the lite version ====================== This library contains a "lite" version of ``rf24.py`` titled ``rf24_lite.py``. It has been -developed to save space on microcontrollers with limited amount of RAM and/or storage (like boards -using the ATSAMD21 M0). The following functionality has been removed from the lite version: +developed to save space on microcontrollers with limited amount of RAM and/or storage (like +boards using the ATSAMD21 M0). The following functionality has been removed from the lite +version: * `address` * `what_happened()` @@ -81,11 +82,16 @@ using the ATSAMD21 M0). The following functionality has been removed from the li * `pipe` * `fifo()` * `tx_full` + * `pa_level` (always set to 0 dBm) * `address_length` (this is always set to 5 bytes) * `read_ack()` (deprecated anyway; use `recv()` instead) * `crc` (always using 2 bytes encoding scheme) * `auto_ack` (always on) - * all comments and docstrings (meaning ``help()`` will not provide any specific information) + * all comments and docstrings (meaning ``help()`` will not provide any specific information). + Exception prompts have also been reduced and adjusted accordingly. + * switching between different radio configurations using context manager (the `with` blocks). + It is advised that only one `RF24` object be instantiated when RAM is limited (less than or + equal to 32KB). RF24 class ============== From 3e130e672e92ed16c631bfc6ef34270d987adc14 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 5 Apr 2020 18:43:43 -0700 Subject: [PATCH 032/127] `pipe` does not call `update()`; doc tweaks --- circuitpython_nrf24l01/rf24.py | 15 ++++++--------- circuitpython_nrf24l01/rf24_lite.py | 4 ++-- docs/api.rst | 8 +++++--- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 8259d41..f1405b7 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -629,8 +629,8 @@ def resend(self): top level (first out) of the TX FIFO buffer.""" result = False if not self.fifo(True, True): - get_ack_pl = bool(self._features & 6 == 6 and self._aa and - self._dyn_pl) + get_ack_pl = bool(self._features & 6 == 6 and self._aa & 1 and + self._dyn_pl & 1) if get_ack_pl: self.flush_rx() self.clear_status_flags(get_ack_pl) @@ -656,12 +656,11 @@ def write(self, buf, ask_no_ack=False): "length in range [1, 32]") self.clear_status_flags( bool(self._features & 6 == 6 and self._aa and self._dyn_pl)) - self._features = self._reg_read(TX_FEATURE) if self._config & 3 != 2: # is radio powered up in TX mode? self._config = (self._reg_read(CONFIGURE) & 0x7c) | 2 self._reg_write(CONFIGURE, self._config) time.sleep(0.00016) - if not self.dynamic_payloads: + if not bool((self._dyn_pl & 1) and (self._features & 4)): if len(buf) < self._pl_len: for _ in range(self._pl_len - len(buf)): buf += b"\x00" @@ -675,13 +674,11 @@ def write(self, buf, ask_no_ack=False): self.ce_pin.value = 1 def flush_rx(self): - """A helper function to flush the nRF24L01's internal RX FIFO buffer. - (write-only)""" + """A helper function to flush the nRF24L01's RX FIFO buffer.""" self._reg_write(0xE2) def flush_tx(self): - """A helper function to flush the nRF24L01's internal TX FIFO buffer. - (write-only)""" + """A helper function to flush the nRF24L01's TX FIFO buffer.""" self._reg_write(0xE1) def fifo(self, about_tx=False, check_empty=None): @@ -701,7 +698,7 @@ def fifo(self, about_tx=False, check_empty=None): @property def pipe(self): - """This attribute returns information about the data pipe that received + """The identifying number of the data pipe that received the next available payload in the RX FIFO buffer.""" self.update() result = (self._status & 0x0E) >> 1 diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index 7b37846..d3da137 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -213,8 +213,8 @@ def dynamic_payloads(self): def dynamic_payloads(self, enable): assert isinstance(enable, (bool, int)) features = self._reg_read(0x1D) - if bool(features & 4) != enable: - features = (features & 3) | (enable << 2) + if bool(features & 4) != bool(enable): + features = (features & 3) | (bool(enable) << 2) self._reg_write(0x1D, features) self._reg_write(0x1C, 0x3F if enable else 0) diff --git a/docs/api.rst b/docs/api.rst index 862a30d..f0fb24b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -82,11 +82,12 @@ version: * `pipe` * `fifo()` * `tx_full` - * `pa_level` (always set to 0 dBm) + * `pa_level` (this is always set to 0 dBm) * `address_length` (this is always set to 5 bytes) * `read_ack()` (deprecated anyway; use `recv()` instead) * `crc` (always using 2 bytes encoding scheme) - * `auto_ack` (always on) + * `auto_ack` (this is always on). Pass ``ask_no_ack`` parameter as `True` to `send()` or + `write()` to disable automatic acknowledgement for TX operations. * all comments and docstrings (meaning ``help()`` will not provide any specific information). Exception prompts have also been reduced and adjusted accordingly. * switching between different radio configurations using context manager (the `with` blocks). @@ -275,7 +276,8 @@ send() advantage of `arc` & `ard` attributes. During multi-payload processing, this parameter is meant to slow down CircuitPython devices just enough for the Raspberry Pi to catch up (due to the Raspberry Pi's seemingly slower SPI speeds). See also - `resend()` as using this parameter carries the same implications documented there. + notes on `resend()` as using this parameter carries the same implications documented + there. .. tip:: It is highly recommended that `arc` attribute is enabled when sending multiple payloads. Test results with the `arc` attribute disabled were very poor From db1e1dc221ffc11f9494444db978d7abd0884685 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sat, 25 Jul 2020 01:37:52 -0700 Subject: [PATCH 033/127] some doc revision; pipe doesn't call update() --- circuitpython_nrf24l01/rf24.py | 31 +- docs/api.rst | 1729 ++++++++++++++++---------------- 2 files changed, 882 insertions(+), 878 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index f1405b7..37292e3 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -259,16 +259,17 @@ def _stop_listening(self): def any(self): """This function checks if the nRF24L01 has received any data at all, - and then reports the next available payload's length (in bytes)""" + and then reports the next available payload's length (in bytes).""" self._features = self._reg_read(TX_FEATURE) if self.irq_dr: if self._features & 4: return self._reg_read(0x60) - return self._reg_read(RX_PL_LENG + ((self._status & 0xE) >> 1)) + return self._reg_read(RX_PL_LENG + self.pipe) return 0 def recv(self): - """This function is used to retrieve the next available payload""" + """This function is used to retrieve the next available payload in the + RX FIFO buffer, then clears the `irq_dr` status flag.""" curr_pl_size = self.any() if not curr_pl_size: return None @@ -316,17 +317,20 @@ def send(self, buf, ask_no_ack=False, force_retry=0): @property def irq_dr(self): - """A `bool` that represents the "Data Ready" interrupted flag.""" + """A `bool` that represents the "Data Ready" interrupted flag. + (read-only)""" return bool(self._status & 0x40) @property def irq_ds(self): - """A `bool` that represents the "Data Sent" interrupted flag.""" + """A `bool` that represents the "Data Sent" interrupted flag. + (read-only)""" return bool(self._status & 0x20) @property def irq_df(self): - """A `bool` that represents the "Data Failed" interrupted flag.""" + """A `bool` that represents the "Data Failed" interrupted flag. + (read-only)""" return bool(self._status & 0x10) def clear_status_flags(self, data_recv=True, data_sent=True, @@ -346,7 +350,7 @@ def interrupt_config(self, data_recv=True, data_sent=True, def what_happened(self, dump_pipes=False): """This debuggung function aggregates and outputs all status/condition - related information""" + related information from the nRF24L01.""" observer = self._reg_read(8) print("Channel___________________{} ~ {} GHz".format( self.channel, (self.channel + 2400) / 1000)) @@ -436,7 +440,8 @@ def payload_length(self, length): @property def arc(self): """This `int` attribute specifies the nRF24L01's number of attempts - to re-transmit TX payload""" + to re-transmit TX payload when acknowledgment packet is not received. + """ self._retry_setup = self._reg_read(SETUP_RETR) return self._retry_setup & 0x0f @@ -454,7 +459,8 @@ def arc(self, count): def ard(self): """This `int` attribute specifies the nRF24L01's delay (in microseconds) between attempts to automatically re-transmit the - TX payload""" + TX payload when an expected acknowledgement (ACK) packet is not + received.""" self._retry_setup = self._reg_read(SETUP_RETR) return ((self._retry_setup & 0xf0) >> 4) * 250 + 250 @@ -472,7 +478,7 @@ def ard(self, delta_t): @property def auto_ack(self): """This `bool` attribute controls the nRF24L01's automatic - acknowledgment feature during the process of receiving""" + acknowledgment feature during the process of receiving a packet.""" self._aa = self._reg_read(AUTO_ACK) return bool(self._aa) @@ -621,7 +627,7 @@ def tx_full(self): def update(self): """This function is only used to get an updated status byte over SPI - from the nRF24L01""" + from the nRF24L01.""" self._reg_write(0xFF) def resend(self): @@ -699,8 +705,7 @@ def fifo(self, about_tx=False, check_empty=None): @property def pipe(self): """The identifying number of the data pipe that received - the next available payload in the RX FIFO buffer.""" - self.update() + the next available payload in the RX FIFO buffer. (read only)""" result = (self._status & 0x0E) >> 1 if result <= 5: return result diff --git a/docs/api.rst b/docs/api.rst index f0fb24b..1447d86 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,865 +1,864 @@ - - -.. currentmodule:: circuitpython_nrf24l01.rf24 - -Troubleshooting info -==================== - -.. important:: The nRF24L01 has 3 key features that can be interdependent of each other. Their - priority of dependence is as follows: - - 1. `dynamic_payloads` feature allowing either TX/RX nRF24L01 to be able to send/receive - payloads with their size written into the payloads' packet. With this disabled, both RX/TX - nRF24L01 must use matching `payload_length` attributes. - 2. `auto_ack` feature provides transmission verification by using the RX nRF24L01 to - automatically and imediatedly send an acknowledgment (ACK) packet in response to freshly - received payloads. `auto_ack` does not require `dynamic_payloads` to be enabled. - 3. `ack` feature allows the MCU to append a payload to the ACK packet, thus instant - bi-directional communication. A transmitting ACK payload must be loaded into the nRF24L01's - TX FIFO buffer (done using `load_ack()`) BEFORE receiving the payload that is to be - acknowledged. Once transmitted, the payload is released from the TX FIFO buffer. This - feature requires the `auto_ack` and `dynamic_payloads` features enabled. - -Remeber that the nRF24L01's FIFO (first-in,first-out) buffer has 3 levels. This means that there -can be up to 3 payloads waiting to be read (RX) and up to 3 payloads waiting to be transmit (TX). - -With the `auto_ack` feature enabled, you get: - - * cyclic redundancy checking (`crc`) automatically enabled - * to change amount of automatic re-transmit attempts and the delay time between them. See the - `arc` and `ard` attributes. - -.. note:: A word on pipes vs addresses vs channels. - - You should think of the data pipes as a "parking spot" for your payload. There are only six - data pipes on the nRF24L01, thus it can simultaneously "listen" to a maximum of 6 other nRF24L01 - radios. However, it can only "talk" to 1 other nRF24L01 at a time). - - The specified address is not the address of an nRF24L01 radio, rather it is more like a path - that connects the endpoints. When assigning addresses to a data pipe, you can use any 5 byte - long address you can think of (as long as the first byte is unique among simultaneously - broadcasting addresses), so you're not limited to communicating with only the same 6 nRF24L01 - radios (more on this when we officially support "Multiciever" mode). - - Finnaly, the radio's channel is not be confused with the radio's pipes. Channel selection is a - way of specifying a certain radio frequency (frequency = [2400 + channel] MHz). Channel - defaults to 76 (like the arduino library), but options range from 0 to 125 -- that's 2.4 - GHz to 2.525 GHz. The channel can be tweaked to find a less occupied frequency amongst - Bluetooth, WiFi, or other ambient signals that use the same spectrum of frequencies. - -.. warning:: For successful transmissions, most of the endpoint trasceivers' settings/features must - match. These settings/features include: - - * The RX pipe's address on the receiving nRF24L01 (passed to `open_rx_pipe()`) MUST match the - TX pipe's address on the transmitting nRF24L01 (passed to `open_tx_pipe()`) - * `address_length` - * `channel` - * `data_rate` - * `dynamic_payloads` - * `payload_length` only when `dynamic_payloads` is disabled - * `auto_ack` on the recieving nRF24L01 must be enabled if `arc` is greater than 0 on the - transmitting nRF24L01 - * custom `ack` payloads - * `crc` - - In fact the only attributes that aren't required to match on both endpoint transceivers would - be the identifying data pipe number (passed to `open_rx_pipe()` or `load_ack()`), `pa_level`, - `arc`, & `ard` attributes. The ``ask_no_ack`` feature can be used despite the settings/features - configuration (see `send()` & `write()` function - parameters for more details). - -About the lite version -====================== - -This library contains a "lite" version of ``rf24.py`` titled ``rf24_lite.py``. It has been -developed to save space on microcontrollers with limited amount of RAM and/or storage (like -boards using the ATSAMD21 M0). The following functionality has been removed from the lite -version: - - * `address` - * `what_happened()` - * `rpd` - * `pipe` - * `fifo()` - * `tx_full` - * `pa_level` (this is always set to 0 dBm) - * `address_length` (this is always set to 5 bytes) - * `read_ack()` (deprecated anyway; use `recv()` instead) - * `crc` (always using 2 bytes encoding scheme) - * `auto_ack` (this is always on). Pass ``ask_no_ack`` parameter as `True` to `send()` or - `write()` to disable automatic acknowledgement for TX operations. - * all comments and docstrings (meaning ``help()`` will not provide any specific information). - Exception prompts have also been reduced and adjusted accordingly. - * switching between different radio configurations using context manager (the `with` blocks). - It is advised that only one `RF24` object be instantiated when RAM is limited (less than or - equal to 32KB). - -RF24 class -============== - -Basic API ---------- - -Contructor -****************** - -.. autoclass:: circuitpython_nrf24l01.rf24.RF24 - :no-members: - - This class aims to be compatible with - other devices in the nRF24xxx product line that implement the Nordic proprietary Enhanced - ShockBurst Protocol (and/or the legacy ShockBurst Protocol), but officially only supports - (through testing) the nRF24L01 and nRF24L01+ devices. - - :param ~busio.SPI spi: The object for the SPI bus that the nRF24L01 is connected to. - - .. tip:: This object is meant to be shared amongst other driver classes (like - adafruit_mcp3xxx.mcp3008 for example) that use the same SPI bus. Otherwise, multiple - devices on the same SPI bus with different spi objects may produce errors or - undesirable behavior. - :param ~digitalio.DigitalInOut csn: The digital output pin that is connected to the nRF24L01's - CSN (Chip Select Not) pin. This is required. - :param ~digitalio.DigitalInOut ce: The digital output pin that is connected to the nRF24L01's - CE (Chip Enable) pin. This is required. - -address_length -****************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.address_length - - The addresses assigned to the data pipes must have byte length equal to the value - set for this attribute. - - A valid input value must be an `int` in range [3,5]. Otherwise a `ValueError` exception is - thrown. Default is set to the nRF24L01's maximum of 5. - -open_tx_pipe() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_tx_pipe - - :param bytearray address: The virtual address of the receiving nRF24L01. This must have a - length equal to the `address_length` attribute (see `address_length` attribute). - Otherwise a `ValueError` exception is thrown. The address specified here must match the - address set to one of the RX data pipes of the receiving nRF24L01. - - .. note:: There is no option to specify which data pipe to use because the nRF24L01 only - uses data pipe 0 in TX mode. Additionally, the nRF24L01 uses the same data pipe (pipe - 0) for receiving acknowledgement (ACK) packets in TX mode when the `auto_ack` attribute - is enabled. Thus, RX pipe 0 is appropriated with the TX address (specified here) when - `auto_ack` is set to `True`. - -close_rx_pipe() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.close_rx_pipe - - :param int pipe_number: The data pipe to use for RX transactions. This must be in range - [0,5]. Otherwise a `ValueError` exception is thrown. - -open_rx_pipe() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_rx_pipe - - If `dynamic_payloads` attribute is `False`, then the `payload_length` - attribute is used to specify the expected length of the RX payload on the specified data - pipe. - - :param int pipe_number: The data pipe to use for RX transactions. This must be in range - [0,5]. Otherwise a `ValueError` exception is thrown. - :param bytearray address: The virtual address to the receiving nRF24L01. This must have a - byte length equal to the `address_length` attribute. Otherwise a `ValueError` - exception is thrown. If using a ``pipe_number`` greater than 1, then only the MSByte - of the address is written, so make sure MSByte (first character) is unique among other - simultaneously receiving addresses). - - .. note:: The nRF24L01 shares the addresses' LSBytes (address[1:5]) on data pipes 2 through - 5. These shared LSBytes are determined by the address set to pipe 1. - -listen -****************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.listen - - Setting this attribute incorporates the proper transitioning to/from RX mode as it involves - playing with the `power` attribute and the nRF24L01's CE pin. This attribute does not power - down the nRF24L01, but will power it up when needed; use `power` attribute set to `False` - to put the nRF24L01 to sleep. - - A valid input value is a `bool` in which: - - `True` enables RX mode. Additionally, per `Appendix B of the nRF24L01+ Specifications - Sheet - `_, this attribute - flushes the RX FIFO, clears the `irq_dr` status flag, and puts nRF24L01 in power up - mode. Notice the CE pin is be held HIGH during RX mode. - - `False` disables RX mode. As mentioned in above link, this puts nRF24L01's power in - Standby-I (CE pin is LOW meaning low current & no transmissions) mode which is ideal - for post-reception work. Disabing RX mode doesn't flush the RX/TX FIFO buffers, so - remember to flush your 3-level FIFO buffers when appropriate using `flush_tx()` or - `flush_rx()` (see also the `recv()` function). - -any() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.any - - -- if there is any. - - :returns: - - `int` of the size (in bytes) of an available RX payload (if any). - - ``0`` if there is no payload in the RX FIFO buffer. - -recv() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.recv - - in the RX FIFO buffer, then clears the `irq_dr` status flag. This function synonomous to - `read_ack()`. - - :returns: A `bytearray` of the RX payload data or `None` if there is no payload - - - If the `dynamic_payloads` attribute is disabled, then the returned bytearray's length - is equal to the user defined `payload_length` attribute (which defaults to 32). - - If the `dynamic_payloads` attribute is enabled, then the returned bytearray's length - is equal to the payload's length - -send() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.send - - :returns: - * `list` if a list or tuple of payloads was passed as the ``buf`` parameter. Each item - in the returned list will contain the returned status for each corresponding payload - in the list/tuple that was passed. The return statuses will be in one of the - following forms: - * `False` if transmission fails. Transmission failure can only be detected if `auto_ack` - is `True`. - * `True` if transmission succeeds. - * `bytearray` or `None` when the `ack` attribute is `True`. Because the payload expects - a responding custom ACK payload, the response is returned (upon successful - transmission) as a - `bytearray` (or `None` if ACK payload is empty) - - :param bytearray,list,tuple buf: The payload to transmit. This bytearray must have a length - greater than 0 and less than 32, otherwise a `ValueError` exception is thrown. This can - also be a list or tuple of payloads (`bytearray`); in which case, all items in the - list/tuple are processed for consecutive transmissions. - - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is less - than the `payload_length` attribute, then this bytearray is padded with zeros until - its length is equal to the `payload_length` attribute. - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is - greater than `payload_length` attribute, then this bytearray's length is truncated to - equal the `payload_length` attribute. - :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for - an acknowledgment from the receiving nRF24L01. This parameter directly controls a - ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about - the payload). Therefore, it takes advantage of an nRF24L01 feature specific to - individual payloads, and its value is not saved anywhere. You do not need to specify - this for every payload if the `arc` attribute is disabled, however this parameter - will work despite the `arc` attribute's setting. - - .. note:: Each transmission is in the form of a packet. This packet contains sections - of data around and including the payload. `See Chapter 7.3 in the nRF24L01 - Specifications Sheet `_ for more - details. - :param int force_retry: The number of brute-force attempts to `resend()` a failed - transmission. Default is 0. This parameter has no affect on transmissions if `arc` is - ``0`` or ``ask_no_ack`` parameter is set to `True`. Each re-attempt still takes - advantage of `arc` & `ard` attributes. During multi-payload processing, this - parameter is meant to slow down CircuitPython devices just enough for the Raspberry - Pi to catch up (due to the Raspberry Pi's seemingly slower SPI speeds). See also - notes on `resend()` as using this parameter carries the same implications documented - there. - - .. tip:: It is highly recommended that `arc` attribute is enabled when sending - multiple payloads. Test results with the `arc` attribute disabled were very poor - (much < 50% received). This same advice applies to the ``ask_no_ack`` parameter (leave - it as `False` for multiple payloads). - .. warning:: The nRF24L01 will block usage of the TX FIFO buffer upon failed - transmissions. Failed transmission's payloads stay in TX FIFO buffer until the MCU - calls `flush_tx()` and `clear_status_flags()`. Therefore, this function will discard - failed transmissions' payloads when sending a list or tuple of payloads, so it can - continue to process through the list/tuple even if any payload fails to be - acknowledged. - -Advanced API ------------- - -what_happened() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.what_happened - - from the nRF24L01. Some information may be irrelevant depending on nRF24L01's - state/condition. (not available in ``rf24_m0.py`` variant for M0 based boards) - - :prints: - - - ``Channel`` The current setting of the `channel` attribute - - ``RF Data Rate`` The current setting of the RF `data_rate` attribute. - - ``RF Power Amplifier`` The current setting of the `pa_level` attribute. - - ``CRC bytes`` The current setting of the `crc` attribute - - ``Address length`` The current setting of the `address_length` attribute - - ``Payload lengths`` The current setting of the `payload_length` attribute - - ``Auto retry delay`` The current setting of the `ard` attribute - - ``Auto retry attempts`` The current setting of the `arc` attribute - - ``Packets lost on current channel`` Total amount of packets lost (transmission - failures). This only resets when the `channel` is changed. This count will - only go up 15. - - ``Retry attempts made for last transmission`` Amount of attempts to re-transmit - during last - transmission (resets per payload) - - ``IRQ - Data Ready`` The current setting of the IRQ pin on "Data Ready" event - - ``IRQ - Data Sent`` The current setting of the IRQ pin on "Data Sent" event - - ``IRQ - Data Fail`` The current setting of the IRQ pin on "Data Fail" event - - ``Data Ready`` Is there RX data ready to be read? - (state of the `irq_dr` flag) - - ``Data Sent`` Has the TX data been sent? (state of the `irq_ds` flag) - - ``Data Failed`` Has the maximum attempts to re-transmit been reached? - (state of the `irq_df` flag) - - ``TX FIFO full`` Is the TX FIFO buffer full? (state of the `tx_full` flag) - - ``TX FIFO empty`` Is the TX FIFO buffer empty? - - ``RX FIFO full`` Is the RX FIFO buffer full? - - ``RX FIFO empty`` Is the RX FIFO buffer empty? - - ``Custom ACK payload`` Is the nRF24L01 setup to use an extra (user defined) payload - attached to the acknowledgment packet? (state of the `ack` attribute) - - ``Ask no ACK`` Is the nRF24L01 setup to transmit individual packets that don't - require acknowledgment? - - ``Automatic Acknowledgment`` Is the `auto_ack` attribute enabled? - - ``Dynamic Payloads`` Is the `dynamic_payloads` attribute enabled? - - ``Primary Mode`` The current mode (RX or TX) of communication of the nRF24L01 device. - - ``Power Mode`` The power state can be Off, Standby-I, Standby-II, or On. - - :param bool dump_pipes: `True` appends the output and prints: - - * the current address used for TX transmissions - * ``Pipe [#] ([open/closed]) bound: [address]`` where ``#`` represent the pipe number, - the ``open/closed`` status is relative to the pipe's RX status, and ``address`` is - read directly from the nRF24L01 registers. - * if the pipe is open, then the output also prints ``expecting [X] byte static - payloads`` where ``X`` is the `payload_length` (in bytes) the pipe is setup to - receive when `dynamic_payloads` is disabled. - - Default is `False` and skips this extra information. - -dynamic_payloads -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.dynamic_payloads - - Default setting is `True`. - - - `True` enables nRF24L01's dynamic payload length feature. The `payload_length` attribute is - ignored when this feature is enabled. - - `False` disables nRF24L01's dynamic payload length feature. Be sure to adjust the - `payload_length` attribute accordingly when `dynamic_payloads` feature is disabled. - -payload_length -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.payload_length - - If the `dynamic_payloads` attribute is enabled, this attribute has no affect. When - `dynamic_payloads` is disabled, this attribute is used to specify the payload length when - entering RX mode. - - A valid input value must be an `int` in range [1,32]. Otherwise a `ValueError` exception is - thrown. Default is set to the nRF24L01's maximum of 32. - - .. note:: When `dynamic_payloads` is disabled during transmissions: - - - Payloads' size of greater than this attribute's value will be truncated to match. - - Payloads' size of less than this attribute's value will be padded with zeros to - match. - -auto_ack -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.auto_ack - - a packet. Default setting is `True`. - - - `True` enables transmitting automatic acknowledgment packets. The CRC (cyclic redundancy - checking) is enabled automatically by the nRF24L01 if the `auto_ack` attribute is enabled - (see also `crc` attribute). - - `False` disables transmitting automatic acknowledgment packets. The `crc` attribute will - remain unaffected when disabling the `auto_ack` attribute. - -arc -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.arc - - when acknowledgment packet is not received. The `auto_ack` must be enabled on the - receiving nRF24L01, otherwise this attribute will make `send()` seem like it failed. - - A valid input value must be in range [0,15]. Otherwise a `ValueError` exception is thrown. - Default is set to 3. A value of ``0`` disables the automatic re-transmit feature and - considers all payload transmissions a success. - -ard -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ard - - when an expected acknowledgement (ACK) packet is - not received. During this time, the nRF24L01 is listening for the ACK packet. If the - `auto_ack` attribute is disabled, this attribute is not applied. - - A valid input value must be a multiple of 250 in range [250,4000]. Otherwise a `ValueError` - exception is thrown. Default is 1500 for reliability. - - .. note:: Paraphrased from nRF24L01 specifications sheet: - - Please take care when setting this parameter. If the custom ACK payload is more than 15 - bytes in 2 Mbps data rate, the `ard` must be 500µS or more. If the custom ACK payload - is more than 5 bytes in 1 Mbps data rate, the `ard` must be 500µS or more. In 250kbps - data rate (even when there is no custom ACK payload) the `ard` must be 500µS or more. - - See `data_rate` attribute on how to set the data rate of the nRF24L01's transmissions. - -ack -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ack - - Use this attribute to set/check if the custom ACK payloads feature is enabled. Default setting - is `False`. - - - `True` enables the use of custom ACK payloads in the ACK packet when responding to - receiving transmissions. As `dynamic_payloads` and `auto_ack` attributes are required for - this feature to work, they are automatically enabled as needed. - - `False` disables the use of custom ACK payloads. Disabling this feature does not disable - the `auto_ack` and `dynamic_payloads` attributes (they work just fine without this - feature). - -load_ack() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.load_ack - - This payload will then be appended to the automatic acknowledgment - (ACK) packet that is sent when fresh data is received on the specified pipe. See - `read_ack()` on how to fetch a received custom ACK payloads. - - :param bytearray buf: This will be the data attached to an automatic ACK packet on the - incoming transmission about the specified ``pipe_number`` parameter. This must have a - length in range [1,32] bytes, otherwise a `ValueError` exception is thrown. Any ACK - payloads will remain in the TX FIFO buffer until transmitted successfully or - `flush_tx()` is called. - :param int pipe_number: This will be the pipe number to use for deciding which - transmissions get a response with the specified ``buf`` parameter's data. This number - must be in range [0,5], otherwise a `ValueError` exception is thrown. - - :returns: `True` if payload was successfully loaded onto the TX FIFO buffer. `False` if it - wasn't because TX FIFO buffer is full. - - .. note:: this function takes advantage of a special feature on the nRF24L01 and needs to - be called for every time a customized ACK payload is to be used (not for every - automatic ACK packet -- this just appends a payload to the ACK packet). The `ack`, - `auto_ack`, and `dynamic_payloads` attributes are also automatically enabled by this - function when necessary. - - .. tip:: The ACK payload must be set prior to receiving a transmission. It is also worth - noting that the nRF24L01 can hold up to 3 ACK payloads pending transmission. Using this - function does not over-write existing ACK payloads pending; it only adds to the queue - (TX FIFO buffer) if it can. Use `flush_tx()` to discard unused ACK payloads when done - listening. - -read_ack() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.read_ack - - This function is called from a blocking `send()` call if the `ack` attribute - is enabled. Alternatively, this function can be called directly in case of calling the - non-blocking `write()` function during asychronous applications. This function is an alias - of `recv()` and remains for bakward compatibility with older versions of this library. - - .. note:: See also the `ack`, `dynamic_payloads`, and `auto_ack` attributes as they must be - enabled to use custom ACK payloads. - -irq_dr -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_dr - - (read-only) - - * `True` represents Data is in the RX FIFO buffer - * `False` represents anything depending on context (state/condition of FIFO buffers) -- - usually this means the flag's been reset. - - Pass ``dataReady`` parameter as `True` to `clear_status_flags()` and reset this. As this is - a virtual representation of the interrupt event, this attribute will always be updated - despite what the actual IRQ pin is configured to do about this event. - - Calling this does not execute an SPI transaction. It only exposes that latest data - contained in the STATUS byte that's always returned from any other SPI transactions. Use - the `update()` function to manually refresh this data when needed. - -irq_df -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_df - - (read-only) - - * `True` signifies the nRF24L01 attemped all configured retries - * `False` represents anything depending on context (state/condition) -- usually this means - the flag's been reset. - - Pass ``dataFail`` parameter as `True` to `clear_status_flags()` to reset this. As this is a - virtual representation of the interrupt event, this attribute will always be updated - despite what the actual IRQ pin is configured to do about this event.see also the `arc` and - `ard` attributes. - - Calling this does not execute an SPI transaction. It only exposes that latest data - contained in the STATUS byte that's always returned from any other SPI transactions. Use - the `update()` function to manually refresh this data when needed. - -irq_ds -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_ds - - (read-only) - - * `True` represents a successful transmission - * `False` represents anything depending on context (state/condition of FIFO buffers) -- - usually this means the flag's been reset. - - Pass ``dataSent`` parameter as `True` to `clear_status_flags()` to reset this. As this is a - virtual representation of the interrupt event, this attribute will always be updated - despite what the actual IRQ pin is configured to do about this event. - - Calling this does not execute an SPI transaction. It only exposes that latest data - contained in the STATUS byte that's always returned from any other SPI transactions. Use - the `update()` function to manually refresh this data when needed. - -clear_status_flags() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.clear_status_flags - - Internally, this is automatically called by `send()`, `write()`, `recv()`, and when `listen` - changes from `False` to `True`. - - :param bool data_recv: specifies wheather to clear the "RX Data Ready" flag. - :param bool data_sent: specifies wheather to clear the "TX Data Sent" flag. - :param bool data_fail: specifies wheather to clear the "Max Re-transmit reached" flag. - - .. note:: Clearing the ``data_fail`` flag is necessary for continued transmissions from the - nRF24L01 (locks the TX FIFO buffer when `irq_df` is `True`) despite wheather or not the - MCU is taking advantage of the interrupt (IRQ) pin. Call this function only when there - is an antiquated status flag (after you've dealt with the specific payload related to - the staus flags that were set), otherwise it can cause payloads to be ignored and - occupy the RX/TX FIFO buffers. See `Appendix A of the nRF24L01+ Specifications Sheet - `_ for an outline of - proper behavior. - -interrupt_config() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.interrupt_config - - The digital signal from the nRF24L01's IRQ pin is active LOW. (write-only) - - :param bool data_recv: If this is `True`, then IRQ pin goes active when there is new data - to read in the RX FIFO buffer. Default setting is `True` - :param bool data_sent: If this is `True`, then IRQ pin goes active when a payload from TX - buffer is successfully transmit. Default setting is `True` - :param bool data_fail: If this is `True`, then IRQ pin goes active when maximum number of - attempts to re-transmit the packet have been reached. If `auto_ack` attribute is - disabled, then this IRQ event is not used. Default setting is `True` - - .. note:: To fetch the status (not configuration) of these IRQ flags, use the `irq_df`, - `irq_ds`, `irq_dr` attributes respectively. - - .. tip:: Paraphrased from nRF24L01+ Specification Sheet: - - The procedure for handling ``data_recv`` IRQ should be: - - 1. read payload through `recv()` - 2. clear ``dataReady`` status flag (taken care of by using `recv()` in previous step) - 3. read FIFO_STATUS register to check if there are more payloads available in RX FIFO - buffer. (a call to `pipe()`, `any()` or even ``(False,True)`` as parameters to - `fifo()` will get this result) - 4. if there is more data in RX FIFO, repeat from step 1 - -data_rate -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.data_rate - - A valid input value is: - - - ``1`` sets the frequency data rate to 1 Mbps - - ``2`` sets the frequency data rate to 2 Mbps - - ``250`` sets the frequency data rate to 250 Kbps - - Any invalid input throws a `ValueError` exception. Default is 1 Mbps. - - .. warning:: 250 Kbps is be buggy on the non-plus models of the nRF24L01 product line. If - you use 250 Kbps data rate, and some transmissions report failed by the transmitting - nRF24L01, even though the same packet in question actually reports received by the - receiving nRF24L01, then try a higher data rate. CAUTION: Higher data rates mean less - maximum distance between nRF24L01 transceivers (and vise versa). - -channel -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.channel - - A valid input value must be in range [0, 125] (that means [2.4, 2.525] GHz). Otherwise a - `ValueError` exception is thrown. Default is 76. - -crc -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.crc - - CRC is a way of making sure that the transmission didn't get corrupted over the air. - - A valid input value is in range [0,2]: - - - ``0`` disables CRC (no anti-corruption of data) - - ``1`` enables CRC encoding scheme using 1 byte (weak anti-corruption of data) - - ``2`` enables CRC encoding scheme using 2 bytes (better anti-corruption of data) - - Any invalid input throws a `ValueError` exception. Default is enabled using 2 bytes. - - .. note:: The nRF24L01 automatically enables CRC if automatic acknowledgment feature is - enabled (see `auto_ack` attribute). - -power -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.power - - This is exposed for asynchronous applications and user preference. - - - `False` basically puts the nRF24L01 to sleep (AKA power down mode) with ultra-low - current consumption. No transmissions are executed when sleeping, but the nRF24L01 can - still be accessed through SPI. Upon instantiation, this driver class puts the nRF24L01 - to sleep until the MCU invokes RX/TX transmissions. This driver class doesn't power down - the nRF24L01 after RX/TX transmissions are complete (avoiding the required power up/down - 130 µs wait time), that preference is left to the user. - - `True` powers up the nRF24L01. This is the first step towards entering RX/TX modes (see - also `listen` attribute). Powering up is automatically handled by the `listen` attribute - as well as the `send()` and `write()` functions. - - .. note:: This attribute needs to be `True` if you want to put radio on Standby-II (highest - current consumption) or Standby-I (moderate current consumption) modes. TX - transmissions are only executed during Standby-II by calling `send()` or `write()`. RX - transmissions are received during Standby-II by setting `listen` attribute to `True` - (see `Chapter 6.1.2-7 of the nRF24L01+ Specifications Sheet `_). After using `send()` or setting `listen` to `False`, the nRF24L01 - is left in Standby-I mode (see also notes on the `write()` function). - -pa_level -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pa_level - - Higher levels mean the transmission will cover a longer distance. Use this attribute to tweak the - nRF24L01 current consumption on projects that don't span large areas. - - A valid input value is: - - - ``-18`` sets the nRF24L01's power amplifier to -18 dBm (lowest) - - ``-12`` sets the nRF24L01's power amplifier to -12 dBm - - ``-6`` sets the nRF24L01's power amplifier to -6 dBm - - ``0`` sets the nRF24L01's power amplifier to 0 dBm (highest) - - Any invalid input throws a `ValueError` exception. Default is 0 dBm. - -tx_full -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.tx_full - - Calling this does not execute an SPI transaction. It only exposes that latest data - contained in the STATUS byte that's always returned from any SPI transactions with the - nRF24L01. Use the `update()` function to manually refresh this data when needed. - - :returns: - * `True` for TX FIFO buffer is full - * `False` for TX FIFO buffer is not full. This doesn't mean the TX FIFO buffer is - empty. - -rpd -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.rpd - - .. note:: The RPD flag is triggered in the following cases: - - 1. During RX mode (`listen` = `True`) and a RF transmission with a gain above a preset - (non-adjustable) -64 dBm threshold. - 2. When a packet is received (indicative of the nRF24L01 used to detect/"listen" for - incoming packets). - 3. When the nRF24L01's CE pin goes from HIGH to LOW (or when the `listen` attribute - changes from `True` to `False`). - 4. When the underlying ESB (Enhanced ShockBurst) protocol reaches a hardcoded - (non-adjustable) RX timeout. - -update() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.update - - and is exposed to the MCU for asynchronous applications. Refreshing the status byte is vital to - checking status of the interrupts, RX pipe number related to current RX payload, and if the - TX FIFO buffer is full. This function returns nothing, but internally updates the `irq_dr`, - `irq_ds`, `irq_df`, and `tx_full` attributes. Internally this is a helper function to - `pipe()`, `send()`, and `resend()` functions - -resend() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.resend - - All returned data follows the same patttern that `send()` returns with the added condition that - this function will return `False` if the TX FIFO buffer is empty. - - .. note:: The nRF24L01 normally removes a payload from the TX FIFO buffer after successful - transmission, but not when this function is called. The payload (successfully - transmitted or not) will remain in the TX FIFO buffer until `flush_tx()` is called to - remove them. Alternatively, using this function also allows the failed payload to be - over-written by using `send()` or `write()` to load a new payload. - -write() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.write - - :param bytearray buf: The payload to transmit. This bytearray must have a length greater - than 0 and less than 32 bytes, otherwise a `ValueError` exception is thrown. - - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is less - than the `payload_length` attribute, then this bytearray is padded with zeros until - its length is equal to the `payload_length` attribute. - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is - greater than `payload_length` attribute, then this bytearray's length is truncated to - equal the `payload_length` attribute. - :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for - an acknowledgment from the receiving nRF24L01. This parameter directly controls a - ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about - the payload). Therefore, it takes advantage of an nRF24L01 feature specific to - individual payloads, and its value is not saved anywhere. You do not need to specify - this for every payload if the `auto_ack` attribute is disabled, however this parameter - should work despite the `auto_ack` attribute's setting. - - .. note:: Each transmission is in the form of a packet. This packet contains sections - of data around and including the payload. `See Chapter 7.3 in the nRF24L01 - Specifications Sheet `_ for more - details. - - This function isn't completely non-blocking as we still need to wait just under 5 ms for - the CSN pin to settle (allowing a clean SPI transaction). - - .. note:: The nRF24L01 doesn't initiate sending until a mandatory minimum 10 µs pulse on - the CE pin is acheived. That pulse is initiated before this function exits. However, we - have left that 10 µs wait time to be managed by the MCU in cases of asychronous - application, or it is managed by using `send()` instead of this function. According to - the Specification sheet, if the CE pin remains HIGH for longer than 10 µs, then the - nRF24L01 will continue to transmit all payloads found in the TX FIFO buffer. - - .. warning:: A note paraphrased from the `nRF24L01+ Specifications Sheet `_: - - It is important to NEVER to keep the nRF24L01+ in TX mode for more than 4 ms at a time. - If the [`arc` attribute is] enabled, nRF24L01+ is never in TX mode longer than 4 - ms. - - .. tip:: Use this function at your own risk. Because of the underlying `"Enhanced - ShockBurst Protocol" `_, disobeying the 4 - ms rule is easily avoided if you enable the `auto_ack` attribute. Alternatively, you - MUST use interrupt flags or IRQ pin with user defined timer(s) to AVOID breaking the - 4 ms rule. If the `nRF24L01+ Specifications Sheet explicitly states this - `_, we have to assume - radio damage or misbehavior as a result of disobeying the 4 ms rule. See also `table 18 - in the nRF24L01 specification sheet `_ for calculating - an adequate transmission timeout sentinal. - -flush_rx() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_rx - - .. note:: The nRF24L01 RX FIFO is 3 level stack that holds payload data. This means that - there can be up to 3 received payloads (each of a maximum length equal to 32 bytes) - waiting to be read (and popped from the stack) by `recv()` or `read_ack()`. This - function clears all 3 levels. - -flush_tx() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_tx - - .. note:: The nRF24L01 TX FIFO is 3 level stack that holds payload data. This means that - there can be up to 3 payloads (each of a maximum length equal to 32 bytes) waiting to - be transmit by `send()`, `resend()` or `write()`. This function clears all 3 levels. It - is worth noting that the payload data is only popped from the TX FIFO stack upon - successful transmission (see also `resend()` as the handling of failed transmissions - can be altered). - -fifo() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.fifo - - :param bool about_tx: - * `True` means information returned is about the TX FIFO buffer. - * `False` means information returned is about the RX FIFO buffer. This parameter - defaults to `False` when not specified. - :param bool check_empty: - * `True` tests if the specified FIFO buffer is empty. - * `False` tests if the specified FIFO buffer is full. - * `None` (when not specified) returns a 2 bit number representing both empty (bit 1) & - full (bit 0) tests related to the FIFO buffer specified using the ``tx`` parameter. - :returns: - * A `bool` answer to the question: - "Is the [TX/RX]:[`True`/`False`] FIFO buffer [empty/full]:[`True`/`False`]? - * If the ``check_empty`` parameter is not specified: an `int` in range [0,2] for which: - - - ``1`` means the specified FIFO buffer is full - - ``2`` means the specified FIFO buffer is empty - - ``0`` means the specified FIFO buffer is neither full nor empty - -pipe -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pipe - - (read only) - - :returns: - * `None` if there is no payload in RX FIFO. - * The `int` identifying pipe number [0,5] that received the next available payload in - the RX FIFO buffer. - -address() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.address - - :param int index: the number of the data pipe whose address is to be returned. Defaults to - ``-1``. A valid index ranges [0,5] for RX addresses or any negative `int` for the TX - address. Otherwise an `IndexError` is thown. + + +.. currentmodule:: circuitpython_nrf24l01.rf24 + +.. |irq note| replace:: parameter as `True` to `clear_status_flags()` and reset this. As this + is a virtual representation of the interrupt event, this attribute will always be updated + despite what the actual IRQ pin is configured to do about this event. + +.. |update manually| replace:: Calling this does not execute an SPI transaction. It only + exposes that latest data contained in the STATUS byte that's always returned from any + other SPI transactions. Use the `update()` function to manually refresh this data when + needed. + +Troubleshooting info +==================== + +.. important:: The nRF24L01 has 3 key features that can be interdependent of each other. Their + priority of dependence is as follows: + + 1. `dynamic_payloads` feature allowing either TX/RX nRF24L01 to be able to send/receive + payloads with their size written into the payloads' packet. With this disabled, both RX/TX + nRF24L01 must use matching `payload_length` attributes. + 2. `auto_ack` feature provides transmission verification by using the RX nRF24L01 to + automatically and imediatedly send an acknowledgment (ACK) packet in response to freshly + received payloads. `auto_ack` does not require `dynamic_payloads` to be enabled. + 3. `ack` feature allows the MCU to append a payload to the ACK packet, thus instant + bi-directional communication. A transmitting ACK payload must be loaded into the nRF24L01's + TX FIFO buffer (done using `load_ack()`) BEFORE receiving the payload that is to be + acknowledged. Once transmitted, the payload is released from the TX FIFO buffer. This + feature requires the `auto_ack` and `dynamic_payloads` features enabled. + +Remeber that the nRF24L01's FIFO (first-in,first-out) buffer has 3 levels. This means that there +can be up to 3 payloads waiting to be read (RX) and up to 3 payloads waiting to be transmit (TX). + +With the `auto_ack` feature enabled, you get: + + * cyclic redundancy checking (`crc`) automatically enabled + * to change amount of automatic re-transmit attempts and the delay time between them. See the + `arc` and `ard` attributes. + +.. note:: A word on pipes vs addresses vs channels. + + You should think of the data pipes as a "parking spot" for your payload. There are only six + data pipes on the nRF24L01, thus it can simultaneously "listen" to a maximum of 6 other nRF24L01 + radios. However, it can only "talk" to 1 other nRF24L01 at a time). + + The specified address is not the address of an nRF24L01 radio, rather it is more like a path + that connects the endpoints. When assigning addresses to a data pipe, you can use any 5 byte + long address you can think of (as long as the first byte is unique among simultaneously + broadcasting addresses), so you're not limited to communicating with only the same 6 nRF24L01 + radios (more on this when we officially support "Multiciever" mode). + + Finnaly, the radio's channel is not be confused with the radio's pipes. Channel selection is a + way of specifying a certain radio frequency (frequency = [2400 + channel] MHz). Channel + defaults to 76 (like the arduino library), but options range from 0 to 125 -- that's 2.4 + GHz to 2.525 GHz. The channel can be tweaked to find a less occupied frequency amongst + Bluetooth, WiFi, or other ambient signals that use the same spectrum of frequencies. + +.. warning:: For successful transmissions, most of the endpoint trasceivers' settings/features must + match. These settings/features include: + + * The RX pipe's address on the receiving nRF24L01 (passed to `open_rx_pipe()`) MUST match the + TX pipe's address on the transmitting nRF24L01 (passed to `open_tx_pipe()`) + * `address_length` + * `channel` + * `data_rate` + * `dynamic_payloads` + * `payload_length` only when `dynamic_payloads` is disabled + * `auto_ack` on the recieving nRF24L01 must be enabled if `arc` is greater than 0 on the + transmitting nRF24L01 + * custom `ack` payloads + * `crc` + + In fact the only attributes that aren't required to match on both endpoint transceivers would + be the identifying data pipe number (passed to `open_rx_pipe()` or `load_ack()`), `pa_level`, + `arc`, & `ard` attributes. The ``ask_no_ack`` feature can be used despite the settings/features + configuration (see `send()` & `write()` function + parameters for more details). + +About the lite version +====================== + +This library contains a "lite" version of ``rf24.py`` titled ``rf24_lite.py``. It has been +developed to save space on microcontrollers with limited amount of RAM and/or storage (like +boards using the ATSAMD21 M0). The following functionality has been removed from the lite +version: + + * `address` + * `what_happened()` + * `rpd` + * `pipe` + * `fifo()` + * `tx_full` + * `pa_level` (this is always set to 0 dBm) + * `address_length` (this is always set to 5 bytes) + * `read_ack()` (deprecated anyway; use `recv()` instead) + * `crc` (always using 2 bytes encoding scheme) + * `auto_ack` (this is always on). Pass ``ask_no_ack`` parameter as `True` to `send()` or + `write()` to disable automatic acknowledgement for TX operations. + * all comments and docstrings (meaning ``help()`` will not provide any specific information). + Exception prompts have also been reduced and adjusted accordingly. + * switching between different radio configurations using context manager (the `with` blocks). + It is advised that only one `RF24` object be instantiated when RAM is limited (less than or + equal to 32KB). + +RF24 class +============== + +Basic API +--------- + +Contructor +****************** + +.. autoclass:: circuitpython_nrf24l01.rf24.RF24 + :no-members: + + This class aims to be compatible with other devices in the nRF24xxx product line that implement + the Nordic proprietary Enhanced ShockBurst Protocol (and/or the legacy ShockBurst Protocol), + but officially only supports (through testing) the nRF24L01 and nRF24L01+ devices. + + :param ~busio.SPI spi: The object for the SPI bus that the nRF24L01 is connected to. + + .. tip:: This object is meant to be shared amongst other driver classes (like + adafruit_mcp3xxx.mcp3008 for example) that use the same SPI bus. Otherwise, multiple + devices on the same SPI bus with different spi objects may produce errors or + undesirable behavior. + :param ~digitalio.DigitalInOut csn: The digital output pin that is connected to the nRF24L01's + CSN (Chip Select Not) pin. This is required. + :param ~digitalio.DigitalInOut ce: The digital output pin that is connected to the nRF24L01's + CE (Chip Enable) pin. This is required. + +address_length +****************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.address_length + + The addresses assigned to the data pipes must have byte length equal to the value + set for this attribute. + + A valid input value must be an `int` in range [3,5]. Otherwise a `ValueError` exception is + thrown. Default is set to the nRF24L01's maximum of 5. + +open_tx_pipe() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_tx_pipe + + :param bytearray address: The virtual address of the receiving nRF24L01. This must have a + length equal to the `address_length` attribute (see `address_length` attribute). + Otherwise a `ValueError` exception is thrown. The address specified here must match the + address set to one of the RX data pipes of the receiving nRF24L01. + + .. note:: There is no option to specify which data pipe to use because the nRF24L01 only + uses data pipe 0 in TX mode. Additionally, the nRF24L01 uses the same data pipe (pipe + 0) for receiving acknowledgement (ACK) packets in TX mode when the `auto_ack` attribute + is enabled. Thus, RX pipe 0 is appropriated with the TX address (specified here) when + `auto_ack` is set to `True`. + +close_rx_pipe() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.close_rx_pipe + + :param int pipe_number: The data pipe to use for RX transactions. This must be in range + [0,5]. Otherwise a `ValueError` exception is thrown. + +open_rx_pipe() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_rx_pipe + + If `dynamic_payloads` attribute is `False`, then the `payload_length` + attribute is used to specify the expected length of the RX payload on the specified data + pipe. + + :param int pipe_number: The data pipe to use for RX transactions. This must be in range + [0,5]. Otherwise a `ValueError` exception is thrown. + :param bytearray address: The virtual address to the receiving nRF24L01. This must have a + byte length equal to the `address_length` attribute. Otherwise a `ValueError` + exception is thrown. If using a ``pipe_number`` greater than 1, then only the MSByte + of the address is written, so make sure MSByte (first character) is unique among other + simultaneously receiving addresses). + + .. note:: The nRF24L01 shares the addresses' LSBytes (address[1:5]) on data pipes 2 through + 5. These shared LSBytes are determined by the address set to pipe 1. + +listen +****************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.listen + + Setting this attribute incorporates the proper transitioning to/from RX mode as it involves + playing with the `power` attribute and the nRF24L01's CE pin. This attribute does not power + down the nRF24L01, but will power it up when needed; use `power` attribute set to `False` + to put the nRF24L01 to sleep. + + A valid input value is a `bool` in which: + + - `True` enables RX mode. Additionally, per `Appendix B of the nRF24L01+ Specifications + Sheet `_, this attribute + flushes the RX FIFO, clears the `irq_dr` status flag, and puts nRF24L01 in power up + mode. Notice the CE pin is be held HIGH during RX mode. + + - `False` disables RX mode. As mentioned in above link, this puts nRF24L01's power in + Standby-I (CE pin is LOW meaning low current & no transmissions) mode which is ideal + for post-reception work. Disabing RX mode doesn't flush the RX/TX FIFO buffers, so + remember to flush your 3-level FIFO buffers when appropriate using `flush_tx()` or + `flush_rx()` (see also the `recv()` function). + +any() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.any + + :returns: + - `int` of the size (in bytes) of an available RX payload (if any). + - ``0`` if there is no payload in the RX FIFO buffer. + +recv() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.recv + + This function synonomous to `read_ack()`. + + :returns: A `bytearray` of the RX payload data or `None` if there is no payload + + - If the `dynamic_payloads` attribute is disabled, then the returned bytearray's length + is equal to the user defined `payload_length` attribute (which defaults to 32). + - If the `dynamic_payloads` attribute is enabled, then the returned bytearray's length + is equal to the payload's length + +send() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.send + + + :returns: + - `list` if a list or tuple of payloads was passed as the ``buf`` parameter. Each item + in the returned list will contain the returned status for each corresponding payload + in the list/tuple that was passed. The return statuses will be in one of the + following forms: + - `False` if transmission fails. Transmission failure can only be detected if `auto_ack` + is `True`. + - `True` if transmission succeeds. + - `bytearray` or `None` when the `ack` attribute is `True`. Because the payload expects + a responding custom ACK payload, the response is returned (upon successful + transmission) as a `bytearray` (or `None` if ACK payload is empty) + + :param bytearray,list,tuple buf: The payload to transmit. This bytearray must have a length + greater than 0 and less than 32, otherwise a `ValueError` exception is thrown. This can + also be a list or tuple of payloads (`bytearray`); in which case, all items in the + list/tuple are processed for consecutive transmissions. + + - If the `dynamic_payloads` attribute is disabled and this bytearray's length is less + than the `payload_length` attribute, then this bytearray is padded with zeros until + its length is equal to the `payload_length` attribute. + - If the `dynamic_payloads` attribute is disabled and this bytearray's length is + greater than `payload_length` attribute, then this bytearray's length is truncated to + equal the `payload_length` attribute. + :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for + an acknowledgment from the receiving nRF24L01. This parameter directly controls a + ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about + the payload). Therefore, it takes advantage of an nRF24L01 feature specific to + individual payloads, and its value is not saved anywhere. You do not need to specify + this for every payload if the `arc` attribute is disabled, however this parameter + will work despite the `arc` attribute's setting. + + .. note:: Each transmission is in the form of a packet. This packet contains sections + of data around and including the payload. `See Chapter 7.3 in the nRF24L01 + Specifications Sheet `_ for more + details. + :param int force_retry: The number of brute-force attempts to `resend()` a failed + transmission. Default is 0. This parameter has no affect on transmissions if `arc` is + ``0`` or ``ask_no_ack`` parameter is set to `True`. Each re-attempt still takes + advantage of `arc` & `ard` attributes. During multi-payload processing, this + parameter is meant to slow down CircuitPython devices just enough for the Raspberry + Pi to catch up (due to the Raspberry Pi's seemingly slower SPI speeds). See also + notes on `resend()` as using this parameter carries the same implications documented + there. + + .. tip:: It is highly recommended that `arc` attribute is enabled when sending + multiple payloads. Test results with the `arc` attribute disabled were very poor + (much < 50% received). This same advice applies to the ``ask_no_ack`` parameter (leave + it as `False` for multiple payloads). + .. warning:: The nRF24L01 will block usage of the TX FIFO buffer upon failed + transmissions. Failed transmission's payloads stay in TX FIFO buffer until the MCU + calls `flush_tx()` and `clear_status_flags()`. Therefore, this function will discard + failed transmissions' payloads when sending a list or tuple of payloads, so it can + continue to process through the list/tuple even if any payload fails to be + acknowledged. + +Advanced API +------------ + +what_happened() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.what_happened + + Some information may be irrelevant depending on nRF24L01's state/condition. + + :prints: + + - ``Channel`` The current setting of the `channel` attribute + - ``RF Data Rate`` The current setting of the RF `data_rate` attribute. + - ``RF Power Amplifier`` The current setting of the `pa_level` attribute. + - ``CRC bytes`` The current setting of the `crc` attribute + - ``Address length`` The current setting of the `address_length` attribute + - ``Payload lengths`` The current setting of the `payload_length` attribute + - ``Auto retry delay`` The current setting of the `ard` attribute + - ``Auto retry attempts`` The current setting of the `arc` attribute + - ``Packets lost on current channel`` Total amount of packets lost (transmission + failures). This only resets when the `channel` is changed. This count will + only go up 15. + - ``Retry attempts made for last transmission`` Amount of attempts to re-transmit + during last transmission (resets per payload) + - ``IRQ - Data Ready`` The current setting of the IRQ pin on "Data Ready" event + - ``IRQ - Data Sent`` The current setting of the IRQ pin on "Data Sent" event + - ``IRQ - Data Fail`` The current setting of the IRQ pin on "Data Fail" event + - ``Data Ready`` Is there RX data ready to be read? (state of the `irq_dr` flag) + - ``Data Sent`` Has the TX data been sent? (state of the `irq_ds` flag) + - ``Data Failed`` Has the maximum attempts to re-transmit been reached? + (state of the `irq_df` flag) + - ``TX FIFO full`` Is the TX FIFO buffer full? (state of the `tx_full` flag) + - ``TX FIFO empty`` Is the TX FIFO buffer empty? + - ``RX FIFO full`` Is the RX FIFO buffer full? + - ``RX FIFO empty`` Is the RX FIFO buffer empty? + - ``Custom ACK payload`` Is the nRF24L01 setup to use an extra (user defined) payload + attached to the acknowledgment packet? (state of the `ack` attribute) + - ``Ask no ACK`` Is the nRF24L01 setup to transmit individual packets that don't + require acknowledgment? + - ``Automatic Acknowledgment`` Is the `auto_ack` attribute enabled? + - ``Dynamic Payloads`` Is the `dynamic_payloads` attribute enabled? + - ``Primary Mode`` The current mode (RX or TX) of communication of the nRF24L01 device. + - ``Power Mode`` The power state can be Off, Standby-I, Standby-II, or On. + + :param bool dump_pipes: `True` appends the output and prints: + + - the current address used for TX transmissions + - ``Pipe [#] ([open/closed]) bound: [address]`` where ``#`` represent the pipe number, + the ``open/closed`` status is relative to the pipe's RX status, and ``address`` is + read directly from the nRF24L01 registers. + - if the pipe is open, then the output also prints ``expecting [X] byte static + payloads`` where ``X`` is the `payload_length` (in bytes) the pipe is setup to + receive when `dynamic_payloads` is disabled. + + Default is `False` and skips this extra information. + +dynamic_payloads +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.dynamic_payloads + + Default setting is `True`. + + - `True` enables nRF24L01's dynamic payload length feature. The `payload_length` attribute is + ignored when this feature is enabled. + - `False` disables nRF24L01's dynamic payload length feature. Be sure to adjust the + `payload_length` attribute accordingly when `dynamic_payloads` feature is disabled. + +payload_length +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.payload_length + + If the `dynamic_payloads` attribute is enabled, this attribute has no affect. When + `dynamic_payloads` is disabled, this attribute is used to specify the payload length when + entering RX mode. + + A valid input value must be an `int` in range [1,32]. Otherwise a `ValueError` exception is + thrown. Default is set to the nRF24L01's maximum of 32. + + .. note:: When `dynamic_payloads` is disabled during transmissions: + + - Payloads' size of greater than this attribute's value will be truncated to match. + - Payloads' size of less than this attribute's value will be padded with zeros to + match. + +auto_ack +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.auto_ack + + Default setting is `True`. + + - `True` enables transmitting automatic acknowledgment packets. The CRC (cyclic redundancy + checking) is enabled automatically by the nRF24L01 if the `auto_ack` attribute is enabled + (see also `crc` attribute). + - `False` disables transmitting automatic acknowledgment packets. The `crc` attribute will + remain unaffected when disabling the `auto_ack` attribute. + +arc +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.arc + + The `auto_ack` must be enabled on the receiving nRF24L01, otherwise this attribute will make + `send()` seem like it failed. + + A valid input value must be in range [0,15]. Otherwise a `ValueError` exception is thrown. + Default is set to 3. A value of ``0`` disables the automatic re-transmit feature and + considers all payload transmissions a success. + +ard +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ard + + During this time, the nRF24L01 is listening for the ACK packet. If the + `auto_ack` attribute is disabled, this attribute is not applied. + + A valid input value must be a multiple of 250 in range [250,4000]. Otherwise a `ValueError` + exception is thrown. Default is 1500 for reliability. + + .. note:: Paraphrased from nRF24L01 specifications sheet: + + Please take care when setting this parameter. If the custom ACK payload is more than 15 + bytes in 2 Mbps data rate, the `ard` must be 500µS or more. If the custom ACK payload + is more than 5 bytes in 1 Mbps data rate, the `ard` must be 500µS or more. In 250kbps + data rate (even when there is no custom ACK payload) the `ard` must be 500µS or more. + + See `data_rate` attribute on how to set the data rate of the nRF24L01's transmissions. + +ack +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ack + + Use this attribute to set/check if the custom ACK payloads feature is enabled. Default setting + is `False`. + + - `True` enables the use of custom ACK payloads in the ACK packet when responding to + receiving transmissions. As `dynamic_payloads` and `auto_ack` attributes are required for + this feature to work, they are automatically enabled as needed. + - `False` disables the use of custom ACK payloads. Disabling this feature does not disable + the `auto_ack` and `dynamic_payloads` attributes (they work just fine without this + feature). + +load_ack() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.load_ack + + This payload will then be appended to the automatic acknowledgment + (ACK) packet that is sent when fresh data is received on the specified pipe. See + `read_ack()` on how to fetch a received custom ACK payloads. + + :param bytearray buf: This will be the data attached to an automatic ACK packet on the + incoming transmission about the specified ``pipe_number`` parameter. This must have a + length in range [1,32] bytes, otherwise a `ValueError` exception is thrown. Any ACK + payloads will remain in the TX FIFO buffer until transmitted successfully or + `flush_tx()` is called. + :param int pipe_number: This will be the pipe number to use for deciding which + transmissions get a response with the specified ``buf`` parameter's data. This number + must be in range [0,5], otherwise a `ValueError` exception is thrown. + + :returns: `True` if payload was successfully loaded onto the TX FIFO buffer. `False` if it + wasn't because TX FIFO buffer is full. + + .. note:: this function takes advantage of a special feature on the nRF24L01 and needs to + be called for every time a customized ACK payload is to be used (not for every + automatic ACK packet -- this just appends a payload to the ACK packet). The `ack`, + `auto_ack`, and `dynamic_payloads` attributes are also automatically enabled by this + function when necessary. + + .. tip:: The ACK payload must be set prior to receiving a transmission. It is also worth + noting that the nRF24L01 can hold up to 3 ACK payloads pending transmission. Using this + function does not over-write existing ACK payloads pending; it only adds to the queue + (TX FIFO buffer) if it can. Use `flush_tx()` to discard unused ACK payloads when done + listening. + +read_ack() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.read_ack + + This function is called from a blocking `send()` call if the `ack` attribute + is enabled. Alternatively, this function can be called directly in case of calling the + non-blocking `write()` function during asychronous applications. This function is an alias + of `recv()` and remains for bakward compatibility with older versions of this library. + + .. note:: See also the `ack`, `dynamic_payloads`, and `auto_ack` attributes as they must be + enabled to use custom ACK payloads. + +irq_dr +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_dr + + . + + :Returns: + + - `True` represents Data is in the RX FIFO buffer + - `False` represents anything depending on context (state/condition of FIFO buffers) + ; usually this means the flag's been reset. + + Pass ``dataReady`` |irq note| + + |update manually| + +irq_df +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_df + + . + + :Returns: + + - `True` signifies the nRF24L01 attemped all configured retries + - `False` represents anything depending on context (state/condition) + ; usually this means the flag's been reset. + + Pass ``dataFail`` |irq note| + + |update manually| + +irq_ds +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_ds + + . + + :Returns: + + - `True` represents a successful transmission + - `False` represents anything depending on context (state/condition of FIFO buffers) + ; usually this means the flag's been reset. + + Pass ``dataSent`` |irq note| + + |update manually| + +clear_status_flags() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.clear_status_flags + + Internally, this is automatically called by `send()`, `write()`, `recv()`, and when `listen` + changes from `False` to `True`. + + :param bool data_recv: specifies wheather to clear the "RX Data Ready" flag. + :param bool data_sent: specifies wheather to clear the "TX Data Sent" flag. + :param bool data_fail: specifies wheather to clear the "Max Re-transmit reached" flag. + + .. note:: Clearing the ``data_fail`` flag is necessary for continued transmissions from the + nRF24L01 (locks the TX FIFO buffer when `irq_df` is `True`) despite wheather or not the + MCU is taking advantage of the interrupt (IRQ) pin. Call this function only when there + is an antiquated status flag (after you've dealt with the specific payload related to + the staus flags that were set), otherwise it can cause payloads to be ignored and + occupy the RX/TX FIFO buffers. See `Appendix A of the nRF24L01+ Specifications Sheet + `_ for an outline of + proper behavior. + +interrupt_config() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.interrupt_config + + The digital signal from the nRF24L01's IRQ pin is active LOW. (write-only) + + :param bool data_recv: If this is `True`, then IRQ pin goes active when there is new data + to read in the RX FIFO buffer. Default setting is `True` + :param bool data_sent: If this is `True`, then IRQ pin goes active when a payload from TX + buffer is successfully transmit. Default setting is `True` + :param bool data_fail: If this is `True`, then IRQ pin goes active when maximum number of + attempts to re-transmit the packet have been reached. If `auto_ack` attribute is + disabled, then this IRQ event is not used. Default setting is `True` + + .. note:: To fetch the status (not configuration) of these IRQ flags, use the `irq_df`, + `irq_ds`, `irq_dr` attributes respectively. + + .. tip:: Paraphrased from nRF24L01+ Specification Sheet: + + The procedure for handling ``data_recv`` IRQ should be: + + 1. read payload through `recv()` + 2. clear ``dataReady`` status flag (taken care of by using `recv()` in previous step) + 3. read FIFO_STATUS register to check if there are more payloads available in RX FIFO + buffer. (a call to `pipe()`, `any()` or even ``(False,True)`` as parameters to + `fifo()` will get this result) + 4. if there is more data in RX FIFO, repeat from step 1 + +data_rate +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.data_rate + + A valid input value is: + + - ``1`` sets the frequency data rate to 1 Mbps + - ``2`` sets the frequency data rate to 2 Mbps + - ``250`` sets the frequency data rate to 250 Kbps + + Any invalid input throws a `ValueError` exception. Default is 1 Mbps. + + .. warning:: 250 Kbps is be buggy on the non-plus models of the nRF24L01 product line. If + you use 250 Kbps data rate, and some transmissions report failed by the transmitting + nRF24L01, even though the same packet in question actually reports received by the + receiving nRF24L01, then try a higher data rate. CAUTION: Higher data rates mean less + maximum distance between nRF24L01 transceivers (and vise versa). + +channel +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.channel + + A valid input value must be in range [0, 125] (that means [2.4, 2.525] GHz). Otherwise a + `ValueError` exception is thrown. Default is 76. + +crc +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.crc + + CRC is a way of making sure that the transmission didn't get corrupted over the air. + + A valid input value is in range [0,2]: + + - ``0`` disables CRC (no anti-corruption of data) + - ``1`` enables CRC encoding scheme using 1 byte (weak anti-corruption of data) + - ``2`` enables CRC encoding scheme using 2 bytes (better anti-corruption of data) + + Any invalid input throws a `ValueError` exception. Default is enabled using 2 bytes. + + .. note:: The nRF24L01 automatically enables CRC if automatic acknowledgment feature is + enabled (see `auto_ack` attribute). + +power +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.power + + This is exposed for asynchronous applications and user preference. + + - `False` basically puts the nRF24L01 to sleep (AKA power down mode) with ultra-low + current consumption. No transmissions are executed when sleeping, but the nRF24L01 can + still be accessed through SPI. Upon instantiation, this driver class puts the nRF24L01 + to sleep until the MCU invokes RX/TX transmissions. This driver class doesn't power down + the nRF24L01 after RX/TX transmissions are complete (avoiding the required power up/down + 130 µs wait time), that preference is left to the user. + - `True` powers up the nRF24L01. This is the first step towards entering RX/TX modes (see + also `listen` attribute). Powering up is automatically handled by the `listen` attribute + as well as the `send()` and `write()` functions. + + .. note:: This attribute needs to be `True` if you want to put radio on Standby-II (highest + current consumption) or Standby-I (moderate current consumption) modes. TX + transmissions are only executed during Standby-II by calling `send()` or `write()`. RX + transmissions are received during Standby-II by setting `listen` attribute to `True` + (see `Chapter 6.1.2-7 of the nRF24L01+ Specifications Sheet `_). After using `send()` or setting `listen` to `False`, the nRF24L01 + is left in Standby-I mode (see also notes on the `write()` function). + +pa_level +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pa_level + + Higher levels mean the transmission will cover a longer distance. Use this attribute to tweak the + nRF24L01 current consumption on projects that don't span large areas. + + A valid input value is: + + - ``-18`` sets the nRF24L01's power amplifier to -18 dBm (lowest) + - ``-12`` sets the nRF24L01's power amplifier to -12 dBm + - ``-6`` sets the nRF24L01's power amplifier to -6 dBm + - ``0`` sets the nRF24L01's power amplifier to 0 dBm (highest) + + Any invalid input throws a `ValueError` exception. Default is 0 dBm. + +tx_full +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.tx_full + + . + + |update manually| + + :returns: + + - `True` for TX FIFO buffer is full + - `False` for TX FIFO buffer is not full. This doesn't mean the TX FIFO buffer is + empty. + +rpd +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.rpd + + The RPD flag is triggered in the following cases: + + 1. During RX mode (`listen` = `True`) and an arbitrary RF transmission with a gain above + -64 dBm threshold is/was present. + 2. When a packet is received (instigated by the nRF24L01 used to detect/"listen" for + incoming packets). + + .. note:: See also + `section 6.4 of the Specification Sheet concerning the RPD flag + `_. Ambient temperature + affects the -64 dBm threshold. The latching of this flag happens differently under certain + conditions. + +update() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.update + + Refreshing the status byte is vital to checking status of the interrupts, RX pipe number related + to current RX payload, and if the TX FIFO buffer is full. This function returns nothing, but + internally updates the `irq_dr`, `irq_ds`, `irq_df`, `pipe`, and `tx_full` attributes. Internally + this is a helper function to `send()`, and `resend()` functions. + +resend() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.resend + + All returned data follows the same patttern that `send()` returns with the added condition that + this function will return `False` if the TX FIFO buffer is empty. + + .. note:: The nRF24L01 normally removes a payload from the TX FIFO buffer after successful + transmission, but not when this function is called. The payload (successfully + transmitted or not) will remain in the TX FIFO buffer until `flush_tx()` is called to + remove them. Alternatively, using this function also allows the failed payload to be + over-written by using `send()` or `write()` to load a new payload. + +write() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.write + + :param bytearray buf: The payload to transmit. This bytearray must have a length greater + than 0 and less than 32 bytes, otherwise a `ValueError` exception is thrown. + + - If the `dynamic_payloads` attribute is disabled and this bytearray's length is less + than the `payload_length` attribute, then this bytearray is padded with zeros until + its length is equal to the `payload_length` attribute. + - If the `dynamic_payloads` attribute is disabled and this bytearray's length is + greater than `payload_length` attribute, then this bytearray's length is truncated to + equal the `payload_length` attribute. + :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for + an acknowledgment from the receiving nRF24L01. This parameter directly controls a + ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about + the payload). Therefore, it takes advantage of an nRF24L01 feature specific to + individual payloads, and its value is not saved anywhere. You do not need to specify + this for every payload if the `auto_ack` attribute is disabled, however this parameter + should work despite the `auto_ack` attribute's setting. + + .. note:: Each transmission is in the form of a packet. This packet contains sections + of data around and including the payload. `See Chapter 7.3 in the nRF24L01 + Specifications Sheet `_ for more + details. + + This function isn't completely non-blocking as we still need to wait just under 5 ms for + the CSN pin to settle (allowing a clean SPI transaction). + + .. note:: The nRF24L01 doesn't initiate sending until a mandatory minimum 10 µs pulse on + the CE pin is acheived. That pulse is initiated before this function exits. However, we + have left that 10 µs wait time to be managed by the MCU in cases of asychronous + application, or it is managed by using `send()` instead of this function. According to + the Specification sheet, if the CE pin remains HIGH for longer than 10 µs, then the + nRF24L01 will continue to transmit all payloads found in the TX FIFO buffer. + + .. warning:: A note paraphrased from the `nRF24L01+ Specifications Sheet + `_: + + It is important to NEVER to keep the nRF24L01+ in TX mode for more than 4 ms at a time. + If the [`arc` attribute is] enabled, nRF24L01+ is never in TX mode longer than 4 + ms. + + .. tip:: Use this function at your own risk. Because of the underlying `"Enhanced + ShockBurst Protocol" `_, disobeying the 4 + ms rule is easily avoided if you enable the `auto_ack` attribute. Alternatively, you + MUST use interrupt flags or IRQ pin with user defined timer(s) to AVOID breaking the + 4 ms rule. If the `nRF24L01+ Specifications Sheet explicitly states this + `_, we have to assume + radio damage or misbehavior as a result of disobeying the 4 ms rule. See also `table 18 + in the nRF24L01 specification sheet `_ for calculating + an adequate transmission timeout sentinal. + +flush_rx() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_rx + + .. note:: The nRF24L01 RX FIFO is 3 level stack that holds payload data. This means that + there can be up to 3 received payloads (each of a maximum length equal to 32 bytes) + waiting to be read (and popped from the stack) by `recv()` or `read_ack()`. This + function clears all 3 levels. + +flush_tx() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_tx + + .. note:: The nRF24L01 TX FIFO is 3 level stack that holds payload data. This means that + there can be up to 3 payloads (each of a maximum length equal to 32 bytes) waiting to + be transmit by `send()`, `resend()` or `write()`. This function clears all 3 levels. It + is worth noting that the payload data is only popped from the TX FIFO stack upon + successful transmission (see also `resend()` as the handling of failed transmissions + can be altered). + +fifo() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.fifo + + :param bool about_tx: + - `True` means information returned is about the TX FIFO buffer. + - `False` means information returned is about the RX FIFO buffer. This parameter + defaults to `False` when not specified. + :param bool check_empty: + - `True` tests if the specified FIFO buffer is empty. + - `False` tests if the specified FIFO buffer is full. + - `None` (when not specified) returns a 2 bit number representing both empty (bit 1) & + full (bit 0) tests related to the FIFO buffer specified using the ``tx`` parameter. + :returns: + - A `bool` answer to the question: + "Is the [TX/RX]:[`True`/`False`] FIFO buffer [empty/full]:[`True`/`False`]? + - If the ``check_empty`` parameter is not specified: an `int` in range [0,2] for which: + + - ``1`` means the specified FIFO buffer is full + - ``2`` means the specified FIFO buffer is empty + - ``0`` means the specified FIFO buffer is neither full nor empty + +pipe +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pipe + + . + + |update manually| + + :Returns: + + - `None` if there is no payload in RX FIFO. + - The `int` identifying pipe number [0,5] that received the next + available payload in the RX FIFO buffer. + +address() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.address + + :param int index: the number of the data pipe whose address is to be returned. Defaults to + ``-1``. A valid index ranges [0,5] for RX addresses or any negative `int` for the TX + address. Otherwise an `IndexError` is thown. From 141b2fe687b0cda12ee0781a5c359ff75237b3ca Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sat, 25 Jul 2020 02:33:00 -0700 Subject: [PATCH 034/127] formatting fixes; (+) pipe & rpd in lite ver --- circuitpython_nrf24l01/rf24.py | 245 +++++++++++++++++----------- circuitpython_nrf24l01/rf24_lite.py | 45 +++-- docs/api.rst | 5 +- 3 files changed, 178 insertions(+), 117 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 37292e3..ef96207 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -25,6 +25,7 @@ __repo__ = "https://github.com/2bndy5/CircuitPython_nRF24L01.git" import time from micropython import const + try: from ubus_device import SPIDevice except ImportError: @@ -36,21 +37,23 @@ OPEN_PIPES = const(0x02) # open/close pipes SETUP_RETR = const(0x04) # auto-retry count & delay RF_PA_RATE = const(0x06) # RF Power Amplifier & Data Rate -RX_ADDR_P0 = const(0x0a) # RX pipe addresses == [0, 5]:[0x0a, 0x0f] +RX_ADDR_P0 = const(0x0A) # RX pipe addresses == [0, 5]:[0x0a, 0x0f] RX_PL_LENG = const(0x11) # RX payload widths on pipes == [0, 5]:[0x11, 0x16] -DYN_PL_LEN = const(0x1c) # dynamic payloads -TX_FEATURE = const(0x1d) # TX features dynamic payloads, ACK payloads, NO_ACK +DYN_PL_LEN = const(0x1C) # dynamic payloads +TX_FEATURE = const(0x1D) # TX features dynamic payloads, ACK payloads, NO_ACK TX_ADDRESS = const(0x10) # Address used for TX transmissions + class RF24: """A driver class for the nRF24L01(+) transceiver radios.""" + def __init__(self, spi, csn, ce): self._pl_len = 32 self.payload_length = 32 self._fifo = 0 self._status = 0 # init shadow copy of RX addresses for all pipes for context manager - self._pipes = [b'\xE7' * 5, b'\xC2' * 5, 0, 0, 0, 0] + self._pipes = [b"\xE7" * 5, b"\xC2" * 5, 0, 0, 0, 0] # shadow copy of last RX_ADDR_P0 written to pipe 0 needed as # open_tx_pipe() appropriates pipe 0 for ACK packet self._pipe0_read_addr = None @@ -130,7 +133,7 @@ def _reg_read(self, reg): def _reg_read_bytes(self, reg, buf_len=5): in_buf = bytearray(buf_len + 1) - out_buf = bytearray([reg]) + b'\x00' * buf_len + out_buf = bytearray([reg]) + b"\x00" * buf_len with self._spi as spi: time.sleep(0.005) spi.write_readinto(out_buf, in_buf) @@ -155,6 +158,7 @@ def _reg_write(self, reg, value=None): time.sleep(0.005) spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] + # pylint: enable=no-member @property @@ -169,8 +173,7 @@ def address_length(self, length): self._addr_len = int(length) self._reg_write(0x03, length - 2) else: - raise ValueError( - "address length can only be set in range [3, 5] bytes") + raise ValueError("address length can only be set in range [3, 5] bytes") def open_tx_pipe(self, address): """This function is used to open a data pipe for OTA (over the air) @@ -185,10 +188,12 @@ def open_tx_pipe(self, address): self._tx_address = address self._reg_write_bytes(TX_ADDRESS, address) else: - raise ValueError("address must be a buffer protocol object with a" - " byte length\nequal to the address_length " - "attribute (currently set to" - " {})".format(self.address_length)) + raise ValueError( + "address must be a buffer protocol object with a" + " byte length\nequal to the address_length " + "attribute (currently set to" + " {})".format(self.address_length) + ) def close_rx_pipe(self, pipe_number): """This function is used to close a specific data pipe from OTA (over @@ -206,10 +211,12 @@ def open_rx_pipe(self, pipe_number, address): if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0,5]") if len(address) != self.address_length: - raise ValueError("address must be a buffer protocol object with a" - " byte length\nequal to the address_length " - "attribute (currently set to" - " {})".format(self.address_length)) + raise ValueError( + "address must be a buffer protocol object with a" + " byte length\nequal to the address_length " + "attribute (currently set to" + " {})".format(self.address_length) + ) if pipe_number < 2: if not pipe_number: self._pipe0_read_addr = address @@ -285,15 +292,18 @@ def send(self, buf, ask_no_ack=False, force_retry=0): result = [] for i, b in enumerate(buf): if not b or len(b) > 32: - raise ValueError("buf (item {} in the list/tuple) must be" - " a buffer protocol object with length " - "in range [1, 32]".format(i)) + raise ValueError( + "buf (item {} in the list/tuple) must be" + " a buffer protocol object with length " + "in range [1, 32]".format(i) + ) for i, b in enumerate(buf): result.append(self.send(b, ask_no_ack, force_retry)) return result if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with " - "length in range [1, 32]") + raise ValueError( + "buf must be a buffer protocol object with " "length in range [1, 32]" + ) use_ack = bool(self._aa and not ask_no_ack) get_ack_pl = bool(self._features & 6 == 6 and self._dyn_pl and use_ack) if get_ack_pl: @@ -303,7 +313,7 @@ def send(self, buf, ask_no_ack=False, force_retry=0): self.ce_pin.value = 0 while not self._status & 0x30: self.update() - if self._retry_setup & 0x0f and self.irq_df: + if self._retry_setup & 0x0F and self.irq_df: for _ in range(force_retry): result = self.resend() if result is None or result: @@ -333,14 +343,11 @@ def irq_df(self): (read-only)""" return bool(self._status & 0x10) - def clear_status_flags(self, data_recv=True, data_sent=True, - data_fail=True): + def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): """This clears the interrupt flags in the status register.""" - self._reg_write(0x07, (data_recv << 6) | ( - data_sent << 5) | (data_fail << 4)) + self._reg_write(0x07, (data_recv << 6) | (data_sent << 5) | (data_fail << 4)) - def interrupt_config(self, data_recv=True, data_sent=True, - data_fail=True): + def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): """Sets the configuration of the nRF24L01's IRQ (interrupt) pin.""" self._config = self._reg_read(CONFIGURE) & 0x0F self._config |= (not bool(data_fail)) << 4 @@ -352,57 +359,85 @@ def what_happened(self, dump_pipes=False): """This debuggung function aggregates and outputs all status/condition related information from the nRF24L01.""" observer = self._reg_read(8) - print("Channel___________________{} ~ {} GHz".format( - self.channel, (self.channel + 2400) / 1000)) - print("RF Data Rate______________{} {}".format( - self.data_rate, "Mbps" if self.data_rate != 250 else "Kbps")) + print( + "Channel___________________{} ~ {} GHz".format( + self.channel, (self.channel + 2400) / 1000 + ) + ) + print( + "RF Data Rate______________{} {}".format( + self.data_rate, "Mbps" if self.data_rate != 250 else "Kbps" + ) + ) print("RF Power Amplifier________{} dbm".format(self.pa_level)) print("CRC bytes_________________{}".format(self.crc)) - print("Address length____________{} bytes".format( - self.address_length)) - print("Payload lengths___________{} bytes".format( - self.payload_length)) + print("Address length____________{} bytes".format(self.address_length)) + print("Payload lengths___________{} bytes".format(self.payload_length)) print("Auto retry delay__________{} microseconds".format(self.ard)) print("Auto retry attempts_______{} maximum".format(self.arc)) - print("Packets lost on current channel_____________________{}".format( - (observer & 0xF0) >> 4)) - print("Retry attempts made for last transmission___________{}".format( - observer & 0x0F)) - print("IRQ - Data Ready______{} Data Ready___________{}".format( - '_True' if not bool(self._config & 0x40) else 'False', - self.irq_dr)) - print("IRQ - Data Fail_______{} Data Failed__________{}".format( - '_True' if not bool(self._config & 0x20) else 'False', - self.irq_df)) - print("IRQ - Data Sent_______{} Data Sent____________{}".format( - '_True' if not bool(self._config & 0x10) else 'False', - self.irq_ds)) - print("TX FIFO full__________{} TX FIFO empty________{}".format( - '_True' if bool(self.tx_full) else 'False', - bool(self.fifo(True, True)))) - print("RX FIFO full__________{} RX FIFO empty________{}".format( - '_True' if bool(self._fifo & 2) else 'False', - bool(self._fifo & 1))) - print("Ask no ACK_________{} Custom ACK Payload___{}".format( - '_Allowed' if bool(self._features & 1) else 'Disabled', - 'Enabled' if self.ack else 'Disabled')) - print("Dynamic Payloads___{} Auto Acknowledgment__{}".format( - '_Enabled' if self.dynamic_payloads else 'Disabled', - 'Enabled' if self.auto_ack else 'Disabled')) - print("Primary Mode_____________{} Power Mode___________{}".format( - 'RX' if self.listen else 'TX', - ('Standby-II' if self.ce_pin.value else 'Standby-I') if - self._config & 2 else 'Off')) + print( + "Packets lost on current channel_____________________{}".format( + (observer & 0xF0) >> 4 + ) + ) + print( + "Retry attempts made for last transmission___________{}".format( + observer & 0x0F + ) + ) + print( + "IRQ - Data Ready______{} Data Ready___________{}".format( + "_True" if not bool(self._config & 0x40) else "False", self.irq_dr + ) + ) + print( + "IRQ - Data Fail_______{} Data Failed__________{}".format( + "_True" if not bool(self._config & 0x20) else "False", self.irq_df + ) + ) + print( + "IRQ - Data Sent_______{} Data Sent____________{}".format( + "_True" if not bool(self._config & 0x10) else "False", self.irq_ds + ) + ) + print( + "TX FIFO full__________{} TX FIFO empty________{}".format( + "_True" if bool(self.tx_full) else "False", bool(self.fifo(True, True)) + ) + ) + print( + "RX FIFO full__________{} RX FIFO empty________{}".format( + "_True" if bool(self._fifo & 2) else "False", bool(self._fifo & 1) + ) + ) + print( + "Ask no ACK_________{} Custom ACK Payload___{}".format( + "_Allowed" if bool(self._features & 1) else "Disabled", + "Enabled" if self.ack else "Disabled", + ) + ) + print( + "Dynamic Payloads___{} Auto Acknowledgment__{}".format( + "_Enabled" if self.dynamic_payloads else "Disabled", + "Enabled" if self.auto_ack else "Disabled", + ) + ) + print( + "Primary Mode_____________{} Power Mode___________{}".format( + "RX" if self.listen else "TX", + ("Standby-II" if self.ce_pin.value else "Standby-I") + if self._config & 2 + else "Off", + ) + ) if dump_pipes: - print('TX address____________', self.address()) + print("TX address____________", self.address()) self._open_pipes = self._reg_read(OPEN_PIPES) for i in range(6): - is_open = "( open )" if self._open_pipes & (1 << i) else \ - "(closed)" + is_open = "( open )" if self._open_pipes & (1 << i) else "(closed)" print("Pipe", i, is_open, "bound:", self.address(i)) if is_open: - print('\t\texpecting', self._pl_len, - 'byte static payloads') + print("\t\texpecting", self._pl_len, "byte static payloads") @property def dynamic_payloads(self): @@ -434,8 +469,10 @@ def payload_length(self, length): for i in range(6): self._reg_write(RX_PL_LENG + i, length) else: - raise ValueError("{}: payload length can only be set in range [1," - " 32] bytes".format(length)) + raise ValueError( + "{}: payload length can only be set in range [1," + " 32] bytes".format(length) + ) @property def arc(self): @@ -443,7 +480,7 @@ def arc(self): to re-transmit TX payload when acknowledgment packet is not received. """ self._retry_setup = self._reg_read(SETUP_RETR) - return self._retry_setup & 0x0f + return self._retry_setup & 0x0F @arc.setter def arc(self, count): @@ -452,8 +489,9 @@ def arc(self, count): self._retry_setup = (self._retry_setup & 0xF0) | count self._reg_write(SETUP_RETR, self._retry_setup) else: - raise ValueError("automatic re-transmit count(/attempts) must in" - " range [0, 15]") + raise ValueError( + "automatic re-transmit count(/attempts) must in" " range [0, 15]" + ) @property def ard(self): @@ -462,7 +500,7 @@ def ard(self): TX payload when an expected acknowledgement (ACK) packet is not received.""" self._retry_setup = self._reg_read(SETUP_RETR) - return ((self._retry_setup & 0xf0) >> 4) * 250 + 250 + return ((self._retry_setup & 0xF0) >> 4) * 250 + 250 @ard.setter def ard(self, delta_t): @@ -472,8 +510,10 @@ def ard(self, delta_t): self._retry_setup |= int((delta_t - 250) / 250) << 4 self._reg_write(SETUP_RETR, self._retry_setup) else: - raise ValueError("automatic re-transmit delay can only be a " - "multiple of 250 in range [250, 4000]") + raise ValueError( + "automatic re-transmit delay can only be a " + "multiple of 250 in range [250, 4000]" + ) @property def auto_ack(self): @@ -514,8 +554,9 @@ def load_ack(self, buf, pipe_number): if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0, 5]") if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with " - "length in range [1, 32]") + raise ValueError( + "buf must be a buffer protocol object with " "length in range [1, 32]" + ) if not bool((self._features & 6) == 6 and self._aa and self._dyn_pl): self.ack = True if not self.tx_full: @@ -547,8 +588,9 @@ def data_rate(self, speed): self._rf_setup = self._rf_setup & 0xD7 | speed self._reg_write(RF_PA_RATE, self._rf_setup) else: - raise ValueError("data rate must be one of the following " - "([M,M,K]bps): 1, 2, 250") + raise ValueError( + "data rate must be one of the following " "([M,M,K]bps): 1, 2, 250" + ) @property def channel(self): @@ -578,8 +620,7 @@ def crc(self, length): self._config = self._config & 0x73 | length self._reg_write(0, self._config) else: - raise ValueError( - "CRC byte length must be an int equal to 0 (off), 1, or 2") + raise ValueError("CRC byte length must be an int equal to 0 (off), 1, or 2") @property def power(self): @@ -591,7 +632,7 @@ def power(self, is_on): assert isinstance(is_on, (bool, int)) self._config = self._reg_read(CONFIGURE) if self.power != bool(is_on): - self._config = (self._config & 0x7d) | (bool(is_on) << 1) + self._config = (self._config & 0x7D) | (bool(is_on) << 1) self._reg_write(CONFIGURE, self._config) time.sleep(0.00016) @@ -610,8 +651,9 @@ def pa_level(self, power): self._rf_setup = (self._rf_setup & 0xF9) | power self._reg_write(RF_PA_RATE, self._rf_setup) else: - raise ValueError("power amplitude must be one of the following " - "(dBm): -18, -12, -6, 0") + raise ValueError( + "power amplitude must be one of the following " "(dBm): -18, -12, -6, 0" + ) @property def rpd(self): @@ -635,8 +677,9 @@ def resend(self): top level (first out) of the TX FIFO buffer.""" result = False if not self.fifo(True, True): - get_ack_pl = bool(self._features & 6 == 6 and self._aa & 1 and - self._dyn_pl & 1) + get_ack_pl = bool( + self._features & 6 == 6 and self._aa & 1 and self._dyn_pl & 1 + ) if get_ack_pl: self.flush_rx() self.clear_status_flags(get_ack_pl) @@ -658,12 +701,14 @@ def write(self, buf, ask_no_ack=False): is meant for asynchronous applications and can only handle one payload at a time as it is a helper function to `send()`.""" if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with " - "length in range [1, 32]") + raise ValueError( + "buf must be a buffer protocol object with " "length in range [1, 32]" + ) self.clear_status_flags( - bool(self._features & 6 == 6 and self._aa and self._dyn_pl)) + bool(self._features & 6 == 6 and self._aa and self._dyn_pl) + ) if self._config & 3 != 2: # is radio powered up in TX mode? - self._config = (self._reg_read(CONFIGURE) & 0x7c) | 2 + self._config = (self._reg_read(CONFIGURE) & 0x7C) | 2 self._reg_write(CONFIGURE, self._config) time.sleep(0.00016) if not bool((self._dyn_pl & 1) and (self._features & 4)): @@ -671,7 +716,7 @@ def write(self, buf, ask_no_ack=False): for _ in range(self._pl_len - len(buf)): buf += b"\x00" elif len(buf) > self._pl_len: - buf = buf[ : self._pl_len] + buf = buf[: self._pl_len] if ask_no_ack: if self._features & 1 == 0: self._features = self._features & 0xFE | 1 @@ -690,17 +735,19 @@ def flush_tx(self): def fifo(self, about_tx=False, check_empty=None): """This provides some precision determining the status of the TX/RX FIFO buffers. (read-only)""" - if (check_empty is None and isinstance(about_tx, (bool, int))) or \ - (isinstance(check_empty, (bool, int)) and - isinstance(about_tx, (bool, int))): + if (check_empty is None and isinstance(about_tx, (bool, int))) or ( + isinstance(check_empty, (bool, int)) and isinstance(about_tx, (bool, int)) + ): self._fifo = self._reg_read(0x17) mask = 4 * about_tx if check_empty is None: return (self._fifo & (0x30 if about_tx else 0x03)) >> mask return bool(self._fifo & ((2 - check_empty) << mask)) - raise ValueError("Argument 1 ('about_tx') must always be a bool or " - "int. Argument 2 ('check_empty'), if specified, must" - " be a bool or int") + raise ValueError( + "Argument 1 ('about_tx') must always be a bool or " + "int. Argument 2 ('check_empty'), if specified, must" + " be a bool or int" + ) @property def pipe(self): diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index d3da137..9739fc3 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -4,11 +4,13 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/2bndy5/CircuitPython_nRF24L01.git" import time + try: from ubus_device import SPIDevice except ImportError: from adafruit_bus_device.spi_device import SPIDevice + class RF24: def __init__(self, spi, csn, ce): self._pipe0_read_addr = None @@ -47,7 +49,7 @@ def _reg_read(self, reg): def _reg_read_bytes(self, reg, buf_len=5): in_buf = bytearray(buf_len + 1) - out_buf = bytearray([reg]) + b'\x00' * buf_len + out_buf = bytearray([reg]) + b"\x00" * buf_len with self._spi as spi: time.sleep(0.005) spi.write_readinto(out_buf, in_buf) @@ -72,6 +74,7 @@ def _reg_write(self, reg, value=None): time.sleep(0.005) spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] + # pylint: enable=no-member def open_tx_pipe(self, address): @@ -137,7 +140,7 @@ def any(self): if self._reg_read(0x1D) & 4 and self.irq_dr: return self._reg_read(0x60) if self.irq_dr: - return self._reg_read(0x11 + ((self._status & 0xE) >> 1)) + return self._reg_read(0x11 + self.pipe) return 0 def recv(self): @@ -155,8 +158,10 @@ def send(self, buf, ask_no_ack=False, force_retry=0): result = [] for i, b in enumerate(buf): if not b or len(b) > 32: - raise ValueError("index {}: buffer must have a length in " - "range [1, 32] ".format(i)) + raise ValueError( + "index {}: buffer must have a length in " + "range [1, 32] ".format(i) + ) for i, b in enumerate(buf): result.append(self.send(b, ask_no_ack, force_retry)) return result @@ -193,14 +198,27 @@ def irq_ds(self): def irq_df(self): return bool(self._status & 0x10) - def clear_status_flags(self, data_recv=True, data_sent=True, - data_fail=True): + @property + def tx_full(self): + return bool(self._status & 1) + + @property + def rpd(self): + return bool(self._reg_read(0x09)) + + @property + def pipe(self): + result = (self._status & 0x0E) >> 1 + if result <= 5: + return result + return None + + def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): flag_config = (bool(data_recv) << 6) | (bool(data_sent) << 5) - flag_config |= (bool(data_fail) << 4) + flag_config |= bool(data_fail) << 4 self._reg_write(7, flag_config) - def interrupt_config(self, data_recv=True, data_sent=True, - data_fail=True): + def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): config = (self._reg_read(0) & 0x0F) | (not bool(data_recv) << 6) config |= (not bool(data_fail) << 4) | (not bool(data_sent) << 5) self._reg_write(0, config) @@ -253,11 +271,10 @@ def ard(self): def ard(self, delta_t): if 250 <= delta_t <= 4000 and delta_t % 250 == 0: setup_retr = self._reg_read(4) & 0x0F - setup_retr |= (int((delta_t - 250) / 250) << 4) + setup_retr |= int((delta_t - 250) / 250) << 4 self._reg_write(4, setup_retr) else: - raise ValueError("ard must be a multiple of 250 in range " - "[250, 4000]") + raise ValueError("ard must be a multiple of 250 in range " "[250, 4000]") @property def ack(self): @@ -271,7 +288,7 @@ def ack(self, enable): self._reg_write(1, 0x3F) self._reg_write(0x1C, 0x3F) features = (features & 3) | 4 - features |= (2 if enable else 0) + features |= 2 if enable else 0 self._reg_write(0x1D, features) def load_ack(self, buf, pipe_number): @@ -358,7 +375,7 @@ def write(self, buf, ask_no_ack=False): for _ in range(pl_width - len(buf)): buf += b"\x00" elif len(buf) > pl_width: - buf = buf[ : pl_width] + buf = buf[:pl_width] self._reg_write_bytes(0xA0 | (ask_no_ack << 4), buf) self.ce_pin.value = 1 diff --git a/docs/api.rst b/docs/api.rst index 1447d86..f57ec36 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -85,12 +85,9 @@ developed to save space on microcontrollers with limited amount of RAM and/or st boards using the ATSAMD21 M0). The following functionality has been removed from the lite version: - * `address` + * `address()` * `what_happened()` - * `rpd` - * `pipe` * `fifo()` - * `tx_full` * `pa_level` (this is always set to 0 dBm) * `address_length` (this is always set to 5 bytes) * `read_ack()` (deprecated anyway; use `recv()` instead) From 58d88d9e4cddeca3f092c4b6d673b7a34aadb0eb Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sun, 6 Sep 2020 06:00:52 -0700 Subject: [PATCH 035/127] use self._addr_len in open_rx/tx_pipe() --- circuitpython_nrf24l01/rf24.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index ef96207..153630b 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -178,7 +178,7 @@ def address_length(self, length): def open_tx_pipe(self, address): """This function is used to open a data pipe for OTA (over the air) TX transmissions.""" - if len(address) == self.address_length: + if len(address) == self._addr_len: if self.auto_ack: self._pipes[0] = address self._reg_write_bytes(RX_ADDR_P0, address) @@ -192,7 +192,7 @@ def open_tx_pipe(self, address): "address must be a buffer protocol object with a" " byte length\nequal to the address_length " "attribute (currently set to" - " {})".format(self.address_length) + " {})".format(self._addr_len) ) def close_rx_pipe(self, pipe_number): @@ -210,12 +210,12 @@ def open_rx_pipe(self, pipe_number, address): the air) RX transmissions.""" if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0,5]") - if len(address) != self.address_length: + if len(address) != self._addr_len: raise ValueError( "address must be a buffer protocol object with a" " byte length\nequal to the address_length " "attribute (currently set to" - " {})".format(self.address_length) + " {})".format(self._addr_len) ) if pipe_number < 2: if not pipe_number: From cff92671394b70467067e5dfdb4f6b1ce95dec0a Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Fri, 11 Sep 2020 03:41:00 -0700 Subject: [PATCH 036/127] typo in readme; change to csv-table --- README.rst | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/README.rst b/README.rst index caafc4e..16b1fd4 100644 --- a/README.rst +++ b/README.rst @@ -102,25 +102,17 @@ Pinout The nRF24L01 is controlled through SPI so there are 3 pins (SCK, MOSI, & MISO) that can only be connected to their counterparts on the MCU (microcontroller unit). The other 2 essential pins (CE & CSN) can be connected to any digital output pins. Lastly, the only optional pin on the nRf24L01 GPIOs is the IRQ (interrupt; a digital output that's active when low) pin and is only connected to the MCU via a digital input pin during the interrupt example. The following pinout is used in the example codes of this library's `example directory `_. -+------------+----------------+----------------+ -| nRF24L01 | Raspberry Pi | ItsyBitsy M4 | -+============+================+================+ -| GND | GND | GND | -+------------+----------------+----------------+ -| VCC | 3V | 3.3V | -+------------+----------------+----------------+ -| CE | GPIO4 | D4 | -+------------+----------------+----------------+ -| CSN | GPIO5 | D5 | -+------------+----------------+----------------+ -| SCK | GPIO11 (SCK) | SCK | -+------------+----------------+----------------+ -| MOSI | GPIO10 (MOSI) | MOSI | -+------------+----------------+----------------+ -| MISO | GPIO9 (MISO) | MISO | -+------------+----------------+----------------+ -| IRQ | GPIO4 | D12 | -+------------+----------------+----------------+ +.. csv-table:: + :header: nRF2401, "Raspberry Pi", "ItsyBitsy M4" + + GND, GND, GND + VCC, 3V, 3.3V + CE, GPIO4, D4 + CSN, GPIO5, D5 + SCK, "GPIO11 (SCK)", SCK + MOSI, "GPIO10 (MOSI)", MOSI + MISO, "GPIO9 (MISO)", MISO + IRQ, GPIO12, D12 .. tip:: User reports and personal experiences have improved results if there is a capacitor of 100 mirofarads [+ another optional 0.1 microfarads capacitor for added stability] connected in parrallel to the VCC and GND pins. From 510effd9717fb23cc929261226ad2ad87393e617 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 11 Sep 2020 12:59:00 -0700 Subject: [PATCH 037/127] add build-tools output to gitignore; fix no _spi --- .gitignore | 3 ++- circuitpython_nrf24l01/rf24.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index d608cec..5c0044c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,8 @@ __pycache__/ # Distribution / packaging .Python -build/ +build*/ +bundles/ develop-eggs/ dist/ downloads/ diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 153630b..dcc9541 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -49,7 +49,6 @@ class RF24: def __init__(self, spi, csn, ce): self._pl_len = 32 - self.payload_length = 32 self._fifo = 0 self._status = 0 # init shadow copy of RX addresses for all pipes for context manager From d19f8488105ecdd1d22bdfdd1cf34cb301b48a26 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 11 Sep 2020 13:06:40 -0700 Subject: [PATCH 038/127] fix result ref'd b4 assignment in send() --- circuitpython_nrf24l01/rf24.py | 1 + 1 file changed, 1 insertion(+) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index dcc9541..eab400c 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -305,6 +305,7 @@ def send(self, buf, ask_no_ack=False, force_retry=0): ) use_ack = bool(self._aa and not ask_no_ack) get_ack_pl = bool(self._features & 6 == 6 and self._dyn_pl and use_ack) + result = None if get_ack_pl: self.flush_rx() self.write(buf, ask_no_ack) From d6be50f3b2d4c94ebb6d8a9297d3012736ddf74a Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 11 Sep 2020 14:44:33 -0700 Subject: [PATCH 039/127] fix address() returning long bytearray of 0s --- circuitpython_nrf24l01/rf24.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index eab400c..c8ade47 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -767,4 +767,4 @@ def address(self, index=-1): return self._tx_address if index <= 1: return self._pipes[index] - return bytes(self._pipes[index]) + self._pipes[1][1:] + return bytes([self._pipes[index]]) + self._pipes[1][1:] From d80db74f2dc33ff70d9de60b81a96e3394a598fc Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 11 Sep 2020 15:36:27 -0700 Subject: [PATCH 040/127] debugged context example & what_happened() --- circuitpython_nrf24l01/rf24.py | 12 ++++++------ examples/nrf24l01_context_test.py | 10 ++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index c8ade47..237c323 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -52,7 +52,7 @@ def __init__(self, spi, csn, ce): self._fifo = 0 self._status = 0 # init shadow copy of RX addresses for all pipes for context manager - self._pipes = [b"\xE7" * 5, b"\xC2" * 5, 0, 0, 0, 0] + self._pipes = [b"\xE7" * 5, b"\xC2" * 5, 0xC3, 0xC4, 0xC5, 0xC6] # shadow copy of last RX_ADDR_P0 written to pipe 0 needed as # open_tx_pipe() appropriates pipe 0 for ACK packet self._pipe0_read_addr = None @@ -105,11 +105,11 @@ def __enter__(self): self._reg_write(AUTO_ACK, self._aa) self._reg_write(TX_FEATURE, self._features) self._reg_write(SETUP_RETR, self._retry_setup) - for i, address in enumerate(self._pipes): + for i, addr in enumerate(self._pipes): if i < 2: - self._reg_write_bytes(RX_ADDR_P0 + i, address) + self._reg_write_bytes(RX_ADDR_P0 + i, addr) else: - self._reg_write(RX_ADDR_P0 + i, address) + self._reg_write(RX_ADDR_P0 + i, addr) self._reg_write(RX_PL_LENG + i, self._pl_len) self._reg_write_bytes(TX_ADDRESS, self._tx_address) self.address_length = self._addr_len @@ -434,8 +434,8 @@ def what_happened(self, dump_pipes=False): print("TX address____________", self.address()) self._open_pipes = self._reg_read(OPEN_PIPES) for i in range(6): - is_open = "( open )" if self._open_pipes & (1 << i) else "(closed)" - print("Pipe", i, is_open, "bound:", self.address(i)) + is_open = self._open_pipes & (1 << i) + print("Pipe", i, "( open )" if is_open else "(closed)", "bound:", self.address(i)) if is_open: print("\t\texpecting", self._pl_len, "byte static payloads") diff --git a/examples/nrf24l01_context_test.py b/examples/nrf24l01_context_test.py index ca2c574..885411e 100644 --- a/examples/nrf24l01_context_test.py +++ b/examples/nrf24l01_context_test.py @@ -58,17 +58,15 @@ print("\nsettings configured by the basicRF object") with basicRF as nerf: # the "as nerf" part is optional - nerf.open_rx_pipe(2, b'SOS') # again only uses the first character + nerf.open_rx_pipe(2, b'?') # again only uses the first character nerf.what_happened(1) # if you examine the outputs from what_happened() you'll see: # pipe 5 is opened using the nrf object, but closed using the basicRF object. # pipe 2 is closed using the nrf object, but opened using the basicRF object. +# also notice the different addresses bound to the RX pipes # this is because the "with" statements load the existing settings # for the RF24 object specified after the word "with". -# the things that remain consistent despite the use of "with" -# statements includes the power mode (standby or sleep), and -# primary role (RX/TX mode) -# NOTE this library uses the adresses' reset values and closes all pipes upon -# instantiation +# exiting a with statement will always set the nRF24L01's power mode to sleep +# NOTE this library's RF24 class closes all pipes upon instantiation From 9ec3a433caa764ca9606a3f7c104596c04ed1607 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 11 Sep 2020 16:19:50 -0700 Subject: [PATCH 041/127] improving returned results from send() --- circuitpython_nrf24l01/rf24.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 237c323..f82c536 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -303,9 +303,8 @@ def send(self, buf, ask_no_ack=False, force_retry=0): raise ValueError( "buf must be a buffer protocol object with " "length in range [1, 32]" ) - use_ack = bool(self._aa and not ask_no_ack) - get_ack_pl = bool(self._features & 6 == 6 and self._dyn_pl and use_ack) - result = None + get_ack_pl = bool(self._features & 6 == 6 and self._dyn_pl and self._aa and not ask_no_ack) + result = False if get_ack_pl: self.flush_rx() self.write(buf, ask_no_ack) @@ -313,16 +312,15 @@ def send(self, buf, ask_no_ack=False, force_retry=0): self.ce_pin.value = 0 while not self._status & 0x30: self.update() - if self._retry_setup & 0x0F and self.irq_df: + if self.irq_df: # irq_df only asserted if arc > 0 for _ in range(force_retry): result = self.resend() if result is None or result: break - else: - result = self.irq_ds - if get_ack_pl: - result = self.recv() - self.clear_status_flags(False) + result = self.irq_ds + if get_ack_pl and self.irq_ds: # if successful and expecting ack payload + result = self.recv() + self.clear_status_flags(False) return result @property From 10de8e4b469d41efb4ff82af02bf166f2e14f859 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 11 Sep 2020 16:30:53 -0700 Subject: [PATCH 042/127] make simple test more successful --- examples/nrf24l01_simple_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/nrf24l01_simple_test.py b/examples/nrf24l01_simple_test.py index 625bab5..484481d 100644 --- a/examples/nrf24l01_simple_test.py +++ b/examples/nrf24l01_simple_test.py @@ -50,7 +50,7 @@ def master(count=5): # count = 5 will only transmit 5 packets time.sleep(1) count -= 1 -def slave(count=3): +def slave(count=5): """Polls the radio and prints the received value. This method expires after 6 seconds of no received transmission""" # set address of TX node into an RX pipe. NOTE you MUST specify From 784ad6f388684bfd7b9778f45fa6c2474526df1b Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 11 Sep 2020 16:42:20 -0700 Subject: [PATCH 043/127] make ack_payload example more successful --- examples/nrf24l01_ack_payload_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/nrf24l01_ack_payload_test.py b/examples/nrf24l01_ack_payload_test.py index a9ac72f..3667c39 100644 --- a/examples/nrf24l01_ack_payload_test.py +++ b/examples/nrf24l01_ack_payload_test.py @@ -72,7 +72,7 @@ def master(count=5): # count = 5 will only transmit 5 packets time.sleep(1) count -= 1 -def slave(count=3): +def slave(count=5): """Prints the received value and sends a dummy ACK payload""" # set address of TX node into an RX pipe. NOTE you MUST specify # which pipe number to use for RX, we'll be using pipe 0 From 0c4f98269739527b4900b88b4118635cc32ea812 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 11 Sep 2020 20:45:48 -0700 Subject: [PATCH 044/127] adjustments for rf24_lite.py --- circuitpython_nrf24l01/rf24.py | 24 ++++++++++-------- circuitpython_nrf24l01/rf24_lite.py | 28 ++++++++++----------- docs/api.rst | 4 ++- examples/nrf24l01_2arduino_handling_data.py | 2 +- examples/nrf24l01_ack_payload_test.py | 2 +- examples/nrf24l01_context_test.py | 2 +- examples/nrf24l01_interrupt_test.py | 2 +- examples/nrf24l01_simple_test.py | 2 +- examples/nrf24l01_stream_test.py | 2 +- 9 files changed, 36 insertions(+), 32 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index f82c536..5a64f11 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -303,8 +303,7 @@ def send(self, buf, ask_no_ack=False, force_retry=0): raise ValueError( "buf must be a buffer protocol object with " "length in range [1, 32]" ) - get_ack_pl = bool(self._features & 6 == 6 and self._dyn_pl and self._aa and not ask_no_ack) - result = False + get_ack_pl = bool(self._features & 6 == 6 and self._dyn_pl and self._aa) if get_ack_pl: self.flush_rx() self.write(buf, ask_no_ack) @@ -312,13 +311,13 @@ def send(self, buf, ask_no_ack=False, force_retry=0): self.ce_pin.value = 0 while not self._status & 0x30: self.update() - if self.irq_df: # irq_df only asserted if arc > 0 + result = self.irq_ds + if self.irq_df: for _ in range(force_retry): result = self.resend() if result is None or result: break - result = self.irq_ds - if get_ack_pl and self.irq_ds: # if successful and expecting ack payload + if get_ack_pl and not ask_no_ack and self.irq_ds: result = self.recv() self.clear_status_flags(False) return result @@ -433,7 +432,13 @@ def what_happened(self, dump_pipes=False): self._open_pipes = self._reg_read(OPEN_PIPES) for i in range(6): is_open = self._open_pipes & (1 << i) - print("Pipe", i, "( open )" if is_open else "(closed)", "bound:", self.address(i)) + print( + "Pipe", + i, + "( open )" if is_open else "(closed)", + "bound:", + self.address(i), + ) if is_open: print("\t\texpecting", self._pl_len, "byte static payloads") @@ -467,10 +472,7 @@ def payload_length(self, length): for i in range(6): self._reg_write(RX_PL_LENG + i, length) else: - raise ValueError( - "{}: payload length can only be set in range [1," - " 32] bytes".format(length) - ) + raise ValueError("payload length must be in range [1, 32] bytes") @property def arc(self): @@ -734,7 +736,7 @@ def fifo(self, about_tx=False, check_empty=None): """This provides some precision determining the status of the TX/RX FIFO buffers. (read-only)""" if (check_empty is None and isinstance(about_tx, (bool, int))) or ( - isinstance(check_empty, (bool, int)) and isinstance(about_tx, (bool, int)) + isinstance(check_empty, (bool, int)) and isinstance(about_tx, (bool, int)) ): self._fifo = self._reg_read(0x17) mask = 4 * about_tx diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index 9739fc3..40b1fb5 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -145,11 +145,11 @@ def any(self): def recv(self): pl_wid = self.any() - if pl_wid: - result = self._reg_read_bytes(0x61, pl_wid) - self.clear_status_flags(True, False, False) - return result - return None + if not pl_wid: + return None + result = self._reg_read_bytes(0x61, pl_wid) + self.clear_status_flags(True, False, False) + return result def send(self, buf, ask_no_ack=False, force_retry=0): self.ce_pin.value = 0 @@ -167,23 +167,23 @@ def send(self, buf, ask_no_ack=False, force_retry=0): return result if not buf or len(buf) > 32: raise ValueError("buffer must have a length in range [1, 32]") - get_ack_pl = bool(self._reg_read(0x1D) & 2) - need_ack = bool((self._reg_read(4) & 0xF) and not ask_no_ack) + get_ack_pl = bool((self._reg_read(0x1D) & 6) == 6 and self._reg_read(4) & 0xF) + if get_ack_pl: + self.flush_rx() self.write(buf, ask_no_ack) time.sleep(0.00001) self.ce_pin.value = 0 while not self._status & 0x30: self.update() - if need_ack and self.irq_df: + result = self.irq_ds + if self.irq_df: for _ in range(force_retry): result = self.resend() if result is None or result: break - else: - result = self.irq_ds - if get_ack_pl and self.irq_dr and not ask_no_ack: - result = self.recv() - self.clear_status_flags(False) + if get_ack_pl and not ask_no_ack and self.irq_ds: + result = self.recv() + self.clear_status_flags(False) return result @property @@ -274,7 +274,7 @@ def ard(self, delta_t): setup_retr |= int((delta_t - 250) / 250) << 4 self._reg_write(4, setup_retr) else: - raise ValueError("ard must be a multiple of 250 in range " "[250, 4000]") + raise ValueError("ard must be a multiple of 250 in range [250, 4000]") @property def ack(self): diff --git a/docs/api.rst b/docs/api.rst index f57ec36..d5c1578 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -479,11 +479,13 @@ read_ack() This function is called from a blocking `send()` call if the `ack` attribute is enabled. Alternatively, this function can be called directly in case of calling the non-blocking `write()` function during asychronous applications. This function is an alias - of `recv()` and remains for bakward compatibility with older versions of this library. + of `recv()` and remains for backward compatibility with older versions of this library. .. note:: See also the `ack`, `dynamic_payloads`, and `auto_ack` attributes as they must be enabled to use custom ACK payloads. + .. warning:: This function will be deprecated on next major release. Use `recv()` instead. + irq_dr ****************************** diff --git a/examples/nrf24l01_2arduino_handling_data.py b/examples/nrf24l01_2arduino_handling_data.py index efed24e..fd785b0 100644 --- a/examples/nrf24l01_2arduino_handling_data.py +++ b/examples/nrf24l01_2arduino_handling_data.py @@ -9,7 +9,7 @@ import board import digitalio as dio # if running this on a ATSAMD21 M0 based board -# from circuitpython_nrf24l01.rf24_m0 import RF24 +# from circuitpython_nrf24l01.rf24_lite import RF24 from circuitpython_nrf24l01.rf24 import RF24 # addresses needs to be in a buffer protocol object (bytearray) diff --git a/examples/nrf24l01_ack_payload_test.py b/examples/nrf24l01_ack_payload_test.py index 3667c39..3cb8a7a 100644 --- a/examples/nrf24l01_ack_payload_test.py +++ b/examples/nrf24l01_ack_payload_test.py @@ -6,7 +6,7 @@ import board import digitalio as dio # if running this on a ATSAMD21 M0 based board -# from circuitpython_nrf24l01.rf24_m0 import RF24 +# from circuitpython_nrf24l01.rf24_lite import RF24 from circuitpython_nrf24l01.rf24 import RF24 # change these (digital output) pins accordingly diff --git a/examples/nrf24l01_context_test.py b/examples/nrf24l01_context_test.py index 885411e..876e10c 100644 --- a/examples/nrf24l01_context_test.py +++ b/examples/nrf24l01_context_test.py @@ -5,7 +5,7 @@ """ import board import digitalio as dio -# this script is not compatible with rf24_m0 for ATSAMD21 M0 based board +# this script is not compatible with rf24_lite for ATSAMD21 M0 based board from circuitpython_nrf24l01.rf24 import RF24 # change these (digital output) pins accordingly diff --git a/examples/nrf24l01_interrupt_test.py b/examples/nrf24l01_interrupt_test.py index 8d761fb..e474a6f 100644 --- a/examples/nrf24l01_interrupt_test.py +++ b/examples/nrf24l01_interrupt_test.py @@ -6,7 +6,7 @@ import board import digitalio as dio # if running this on a ATSAMD21 M0 based board -# from circuitpython_nrf24l01.rf24_m0 import RF24 +# from circuitpython_nrf24l01.rf24_lite import RF24 from circuitpython_nrf24l01.rf24 import RF24 # address needs to be in a buffer protocol object (bytearray is preferred) diff --git a/examples/nrf24l01_simple_test.py b/examples/nrf24l01_simple_test.py index 484481d..79dd2e8 100644 --- a/examples/nrf24l01_simple_test.py +++ b/examples/nrf24l01_simple_test.py @@ -6,7 +6,7 @@ import board import digitalio as dio # if running this on a ATSAMD21 M0 based board -# from circuitpython_nrf24l01.rf24_m0 import RF24 +# from circuitpython_nrf24l01.rf24_lite import RF24 from circuitpython_nrf24l01.rf24 import RF24 # addresses needs to be in a buffer protocol object (bytearray) diff --git a/examples/nrf24l01_stream_test.py b/examples/nrf24l01_stream_test.py index 7a99832..4b9781e 100644 --- a/examples/nrf24l01_stream_test.py +++ b/examples/nrf24l01_stream_test.py @@ -5,7 +5,7 @@ import board import digitalio as dio # if running this on a ATSAMD21 M0 based board -# from circuitpython_nrf24l01.rf24_m0 import RF24 +# from circuitpython_nrf24l01.rf24_lite import RF24 from circuitpython_nrf24l01.rf24 import RF24 # addresses needs to be in a buffer protocol object (bytearray) From f8b4fe82d9e3162908e2fa612a3160749fd72522 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 11 Sep 2020 21:08:22 -0700 Subject: [PATCH 045/127] pleasing black and pylint --- circuitpython_nrf24l01/rf24.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 5a64f11..881de41 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -735,14 +735,13 @@ def flush_tx(self): def fifo(self, about_tx=False, check_empty=None): """This provides some precision determining the status of the TX/RX FIFO buffers. (read-only)""" - if (check_empty is None and isinstance(about_tx, (bool, int))) or ( - isinstance(check_empty, (bool, int)) and isinstance(about_tx, (bool, int)) - ): - self._fifo = self._reg_read(0x17) - mask = 4 * about_tx - if check_empty is None: - return (self._fifo & (0x30 if about_tx else 0x03)) >> mask - return bool(self._fifo & ((2 - check_empty) << mask)) + if isinstance(about_tx, (bool, int)): + if check_empty is None or isinstance(check_empty, (bool, int)): + self._fifo = self._reg_read(0x17) + mask = 4 * about_tx + if check_empty is None: + return (self._fifo & (0x30 if about_tx else 0x03)) >> mask + return bool(self._fifo & ((2 - check_empty) << mask)) raise ValueError( "Argument 1 ('about_tx') must always be a bool or " "int. Argument 2 ('check_empty'), if specified, must" From 08cbc58f6a51c2c075102b96ba51834cafd376ee Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sat, 12 Sep 2020 04:11:31 -0700 Subject: [PATCH 046/127] proofreading --- README.rst | 25 ++++++++++++------------- circuitpython_nrf24l01/rf24.py | 10 +++------- circuitpython_nrf24l01/rf24_lite.py | 14 +++----------- 3 files changed, 18 insertions(+), 31 deletions(-) diff --git a/README.rst b/README.rst index 16b1fd4..521cbfc 100644 --- a/README.rst +++ b/README.rst @@ -44,7 +44,7 @@ Features currently supported * flag a single payload for no acknowledgment (ACK) from the receiving nRF24L01 * "re-use the same payload" feature (for manually re-transmitting failed transmissions that remain in the buffer) * multiple payload transmissions with one function call (MUST read documentation on the `send()` function) -* context manager compatible for easily switching between different radio configurations using "with" statements (not available in ``rf24_m0.py`` variant for M0 based boards) +* context manager compatible for easily switching between different radio configurations using "with" statements (not available in ``rf24_lite.py`` variant for M0 based boards) * configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or failed transmissions (these 3 flags control the 1 IRQ pin). There's also virtual representations of these interrupt flags available (see `irq_dr`, `irq_ds`, `irq_df` attributes) * invoke sleep mode (AKA power down mode) for ultra-low current consumption * cyclic redundancy checking (CRC) up to 2 bytes long @@ -126,21 +126,20 @@ To run the simple example, navigate to this repository's "examples" folder in th .. code-block:: python >>> from nrf24l01_simple_test import * - nRF24L01 Simple test + nRF24L01 Simple test. Run slave() on receiver Run master() on transmitter - >>> master(3) + >>> master() + Sending: 5 as struct: b'\x05\x00\x00\x00' + send() successful + Transmission took 36.0 ms + Sending: 4 as struct: b'\x04\x00\x00\x00' + send() successful + Transmission took 28.0 ms Sending: 3 as struct: b'\x03\x00\x00\x00' - send() succeessful - Transmission took 86.0 ms - Sending: 2 as struct: b'\x02\x00\x00\x00' - send() succeessful - Transmission took 109.0 ms - Sending: 1 as struct: b'\x01\x00\x00\x00' - send() succeessful - Transmission took 109.0 ms - # these results were observed from a test on the Raspberry Pi 3 - # transmissions from a CircuitPython device took 32 to 64 ms + send() successful + Transmission took 24.0 ms + About the nRF24L01 diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 881de41..1722f64 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -148,6 +148,7 @@ def _reg_write_bytes(self, reg, out_buf): self._status = in_buf[0] def _reg_write(self, reg, value=None): + out_buf = bytes(0) if value is None: out_buf = bytes([reg]) else: @@ -183,7 +184,6 @@ def open_tx_pipe(self, address): self._reg_write_bytes(RX_ADDR_P0, address) self._open_pipes = self._open_pipes | 1 self._reg_write(OPEN_PIPES, self._open_pipes) - self._pipes[0] = address self._tx_address = address self._reg_write_bytes(TX_ADDRESS, address) else: @@ -296,13 +296,9 @@ def send(self, buf, ask_no_ack=False, force_retry=0): " a buffer protocol object with length " "in range [1, 32]".format(i) ) - for i, b in enumerate(buf): + for b in buf: result.append(self.send(b, ask_no_ack, force_retry)) return result - if not buf or len(buf) > 32: - raise ValueError( - "buf must be a buffer protocol object with " "length in range [1, 32]" - ) get_ack_pl = bool(self._features & 6 == 6 and self._dyn_pl and self._aa) if get_ack_pl: self.flush_rx() @@ -692,7 +688,7 @@ def resend(self): self.update() result = self.irq_ds if get_ack_pl: - result = self.recv() # get ACK payload + result = self.recv() self.clear_status_flags(False) return result diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index 40b1fb5..7959e66 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -1,6 +1,5 @@ # see license and copyright information in rf24.py of this directory -# pylint: disable=missing-class-docstring,missing-function-docstring -# pylint: disable=missing-module-docstring +# pylint: disable=missing-class-docstring,missing-function-docstring,missing-module-docstring __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/2bndy5/CircuitPython_nRF24L01.git" import time @@ -65,6 +64,7 @@ def _reg_write_bytes(self, reg, out_buf): self._status = in_buf[0] def _reg_write(self, reg, value=None): + out_buf = bytes(0) if value is None: out_buf = bytes([reg]) else: @@ -156,17 +156,9 @@ def send(self, buf, ask_no_ack=False, force_retry=0): self.flush_tx() if isinstance(buf, (list, tuple)): result = [] - for i, b in enumerate(buf): - if not b or len(b) > 32: - raise ValueError( - "index {}: buffer must have a length in " - "range [1, 32] ".format(i) - ) - for i, b in enumerate(buf): + for b in buf: result.append(self.send(b, ask_no_ack, force_retry)) return result - if not buf or len(buf) > 32: - raise ValueError("buffer must have a length in range [1, 32]") get_ack_pl = bool((self._reg_read(0x1D) & 6) == 6 and self._reg_read(4) & 0xF) if get_ack_pl: self.flush_rx() From 2f1893de64ed7de78fa4d019f2da127f5f6f28bf Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sat, 12 Sep 2020 04:59:45 -0700 Subject: [PATCH 047/127] doc tweaks --- README.rst | 53 +++++++-------------------------------- docs/_static/darkness.css | 5 ++++ docs/api.rst | 2 +- 3 files changed, 15 insertions(+), 45 deletions(-) diff --git a/README.rst b/README.rst index dc3cbec..2cdd425 100644 --- a/README.rst +++ b/README.rst @@ -149,58 +149,22 @@ To run the simple example, navigate to this repository's "examples" folder in th About the nRF24L01 ================== -Here are the features listed directly from the datasheet (referenced here in the documentation as the `nRF24L01+ Specification Sheet `_): - -Key Features: -------------- - - * Worldwide 2.4GHz ISM band operation - * 250kbps, 1Mbps and 2Mbps on air data rates - * Ultra low power operation - * 11.3mA TX at 0dBm output power - * 13.5mA RX at 2Mbps air data rate - * 900nA in power down - * 26μA in standby-I - * On chip voltage regulator - * 1.9 to 3.6V supply range - * Enhanced ShockBurst™ - * Automatic packet handling - * Auto packet transaction handling - * 6 data pipe MultiCeiver™ - * Drop-in compatibility with nRF24L01 - * On-air compatible in 250kbps and 1Mbps with nRF2401A, nRF2402, nRF24E1 and nRF24E2 - * Low cost BOM - * ±60ppm 16MHz crystal - * 5V tolerant inputs - * Compact 20-pin 4x4mm QFN package - -Applications ------------- +More finite details about the nRF24L01 are available from the datasheet (referenced here in the documentation as the `nRF24L01+ Specification Sheet `_) + +Future Project Ideas/Additions +============================== - * Wireless PC Peripherals - * Mouse, keyboards and remotes - * 3-in-1 desktop bundles - * Advanced Media center remote controls - * VoIP headsets - * Game controllers - * Sports watches and sensors - * RF remote controls for consumer electronics - * Home and commercial automation - * Ultra low power sensor networks - * Active RFID - * Asset tracking systems - * Toys - -Future Project Ideas/Additions using the nRF24L01 (not currently supported by this circuitpython library): + The following are only ideas; they are not currently supported by this circuitpython library. * `There's a few blog posts by Nerd Ralph demonstrating how to use the nRF24L01 via 2 or 3 pins `_ (uses custom bitbanging SPI functions and an external circuit involving a resistor and a capacitor) * network linking layer, maybe something like `TMRh20's RF24Network `_ * add a fake BLE module for sending BLE beacon advertisments from the nRF24L01 as outlined by `Dmitry Grinberg in his write-up (including C source code) `_. We've started developing this, but fell short of success in `the BLEfake branch of this library's repository `_ + * implement the Gazelle-based protocol used by the BBC micro-bit (`makecode.com's radio blocks `_). Where do I get 1? ================= -See the store links on the sidebar or just google "nRF24L01". It is worth noting that you generally don't want to buy just 1 as you need 2 for testing -- 1 to send & 1 to receive and vise versa. This library has been tested on a cheaply bought 10 pack from Amazon.com using a highly recommended capacitor (100 µF) on the power pins. Don't get lost on Amazon or eBay! There are other wireless transceivers that are NOT compatible with this library. For instance, the esp8266-01 (also sold in packs) is NOT compatible with this library, but looks very similar to the nRF24L01(+) and could lead to an accidental purchase. +See the store links on the sidebar or just google "nRF24L01". It is worth noting that you generally don't want to buy just 1 as you need 2 for testing -- 1 to send & 1 to receive and vise versa. This library has been tested on a cheaply bought 10 pack from Amazon.com using a highly recommended capacitor (100 µF) on the power pins. Don't get lost on Amazon or eBay! There are other wireless transceivers that are NOT compatible with this library. For instance, the esp8266-01 (also sold in packs) is NOT compatible with this library, but looks very similar to the nRF24L01(+) and could lead to an accidental purchase. Beware there are counterfeit nRF24L01 modules out there. To determine if your purchase is a counterfeit, please `read this article `_. Contributing ============ @@ -229,4 +193,5 @@ Now, once you have the virtual environment activated: sphinx-build -E -W -b html . _build/html This will output the documentation to ``docs/_build/html``. Open the index.html in your browser to -view them. It will also (due to -W) error out on any warning like Travis CI does. This is a good way to locally verify it will pass. +view them. It will also (due to -W) error out on any warning like the Github action, Build CI, +does. This is a good way to locally verify it will pass. diff --git a/docs/_static/darkness.css b/docs/_static/darkness.css index c78d90e..89a5052 100644 --- a/docs/_static/darkness.css +++ b/docs/_static/darkness.css @@ -314,3 +314,8 @@ code, .rst-content tt, .rst-content code { .rst-content h1:hover .headerlink::after, .rst-content h2:hover .headerlink::after, .rst-content .toctree-wrapper p.caption:hover .headerlink::after, .rst-content h3:hover .headerlink::after, .rst-content h4:hover .headerlink::after, .rst-content h5:hover .headerlink::after, .rst-content h6:hover .headerlink::after, .rst-content dl dt:hover .headerlink::after, .rst-content p.caption:hover .headerlink::after, .rst-content table > caption:hover .headerlink::after, .rst-content .code-block-caption:hover .headerlink::after { color: #29ae5b !important; } + +html.writer-html4 .rst-content dl:not(.docutils)>dt, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt { + background: #2e363c; +} diff --git a/docs/api.rst b/docs/api.rst index d5c1578..127f938 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -110,7 +110,7 @@ Contructor ****************** .. autoclass:: circuitpython_nrf24l01.rf24.RF24 - :no-members: + :no-members: This class aims to be compatible with other devices in the nRF24xxx product line that implement the Nordic proprietary Enhanced ShockBurst Protocol (and/or the legacy ShockBurst Protocol), From cc50da9903654dc5562e6b1045493043fbfb03ee Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sat, 12 Sep 2020 13:24:32 -0700 Subject: [PATCH 048/127] (-) addr_len check on functions about pipe addr --- circuitpython_nrf24l01/rf24.py | 29 +++++++---------------------- circuitpython_nrf24l01/rf24_lite.py | 11 +++-------- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 1722f64..d663a54 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -178,21 +178,13 @@ def address_length(self, length): def open_tx_pipe(self, address): """This function is used to open a data pipe for OTA (over the air) TX transmissions.""" - if len(address) == self._addr_len: - if self.auto_ack: - self._pipes[0] = address - self._reg_write_bytes(RX_ADDR_P0, address) - self._open_pipes = self._open_pipes | 1 - self._reg_write(OPEN_PIPES, self._open_pipes) - self._tx_address = address - self._reg_write_bytes(TX_ADDRESS, address) - else: - raise ValueError( - "address must be a buffer protocol object with a" - " byte length\nequal to the address_length " - "attribute (currently set to" - " {})".format(self._addr_len) - ) + if self.auto_ack: + self._pipes[0] = address + self._reg_write_bytes(RX_ADDR_P0, address) + self._open_pipes = self._open_pipes | 1 + self._reg_write(OPEN_PIPES, self._open_pipes) + self._tx_address = address + self._reg_write_bytes(TX_ADDRESS, address) def close_rx_pipe(self, pipe_number): """This function is used to close a specific data pipe from OTA (over @@ -209,13 +201,6 @@ def open_rx_pipe(self, pipe_number, address): the air) RX transmissions.""" if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0,5]") - if len(address) != self._addr_len: - raise ValueError( - "address must be a buffer protocol object with a" - " byte length\nequal to the address_length " - "attribute (currently set to" - " {})".format(self._addr_len) - ) if pipe_number < 2: if not pipe_number: self._pipe0_read_addr = address diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index 7959e66..a58401d 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -78,12 +78,9 @@ def _reg_write(self, reg, value=None): # pylint: enable=no-member def open_tx_pipe(self, address): - if len(address) == 5: - self._reg_write_bytes(0x0A, address) - self._reg_write(2, self._reg_read(2) | 1) - self._reg_write_bytes(0x10, address) - else: - raise ValueError("address length must equal 5") + self._reg_write_bytes(0x0A, address) + self._reg_write(2, self._reg_read(2) | 1) + self._reg_write_bytes(0x10, address) def close_rx_pipe(self, pipe_number): if pipe_number < 0 or pipe_number > 5: @@ -95,8 +92,6 @@ def close_rx_pipe(self, pipe_number): def open_rx_pipe(self, pipe_number, address): if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0, 5]") - if len(address) != 5: - raise ValueError("address length must equal 5") if pipe_number < 2: if not pipe_number: self._pipe0_read_addr = address From 6c3930fd2886e24bdb50525733211c1f55514ab9 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sat, 12 Sep 2020 13:39:29 -0700 Subject: [PATCH 049/127] condensed listen.setter --- circuitpython_nrf24l01/rf24.py | 38 ++++++++++++----------------- circuitpython_nrf24l01/rf24_lite.py | 32 +++++++++--------------- 2 files changed, 27 insertions(+), 43 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index d663a54..ae4fab4 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -222,31 +222,23 @@ def listen(self): def listen(self, is_rx): assert isinstance(is_rx, (bool, int)) if self.listen != is_rx: + if self.ce_pin.value: + self.ce_pin.value = 0 if is_rx: - self._start_listening() + if self._pipe0_read_addr is not None: + self._pipes[0] = self._pipe0_read_addr + self._reg_write_bytes(RX_ADDR_P0, self._pipe0_read_addr) + self._config = (self._config & 0xFC) | 3 + self._reg_write(CONFIGURE, self._config) + time.sleep(0.00015) # mandatory wait to power up radio + self.flush_rx() + self.clear_status_flags(True, False, False) + self.ce_pin.value = 1 # mandatory pulse is > 130 µs + time.sleep(0.00013) else: - self._stop_listening() - - def _start_listening(self): - if self.ce_pin.value: - self.ce_pin.value = 0 - if self._pipe0_read_addr is not None: - self._pipes[0] = self._pipe0_read_addr - self._reg_write_bytes(RX_ADDR_P0, self._pipe0_read_addr) - self._config = (self._config & 0xFC) | 3 - self._reg_write(CONFIGURE, self._config) - time.sleep(0.00015) # mandatory wait to power up radio - self.flush_rx() - self.clear_status_flags(True, False, False) - self.ce_pin.value = 1 # mandatory pulse is > 130 µs - time.sleep(0.00013) - - def _stop_listening(self): - if self.ce_pin.value: - self.ce_pin.value = 0 - self._config = self._config & 0xFE - self._reg_write(CONFIGURE, self._config) - time.sleep(0.00016) + self._config = self._config & 0xFE + self._reg_write(CONFIGURE, self._config) + time.sleep(0.00016) def any(self): """This function checks if the nRF24L01 has received any data at all, diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index a58401d..336a82d 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -108,28 +108,20 @@ def listen(self): def listen(self, is_rx): assert isinstance(is_rx, (bool, int)) if self.listen != is_rx: + if self.ce_pin.value: + self.ce_pin.value = 0 if is_rx: - self._start_listening() + if self._pipe0_read_addr is not None: + self._reg_write_bytes(0x0A, self._pipe0_read_addr) + self._reg_write(0, self._reg_read(0) & 0xFC | 3) + time.sleep(0.00015) + self.flush_rx() + self.clear_status_flags(True, False, False) + self.ce_pin.value = 1 + time.sleep(0.00013) else: - self._stop_listening() - - def _start_listening(self): - if self.ce_pin.value: - self.ce_pin.value = 0 - if self._pipe0_read_addr is not None: - self._reg_write_bytes(0x0A, self._pipe0_read_addr) - self._reg_write(0, self._reg_read(0) & 0xFC | 3) - time.sleep(0.00015) - self.flush_rx() - self.clear_status_flags(True, False, False) - self.ce_pin.value = 1 - time.sleep(0.00013) - - def _stop_listening(self): - if self.ce_pin.value: - self.ce_pin.value = 0 - self._reg_write(0, self._reg_read(0) & 0xFE) - time.sleep(0.00016) + self._reg_write(0, self._reg_read(0) & 0xFE) + time.sleep(0.00016) def any(self): if self._reg_read(0x1D) & 4 and self.irq_dr: From 878523a3a5d7d5dc4f8a512cc276562877dc7e06 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sun, 13 Sep 2020 02:06:37 -0700 Subject: [PATCH 050/127] multi-payloads rely on write() format check --- circuitpython_nrf24l01/rf24.py | 7 ------- circuitpython_nrf24l01/rf24_lite.py | 5 ++--- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index ae4fab4..c792d8c 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -266,13 +266,6 @@ def send(self, buf, ask_no_ack=False, force_retry=0): self.flush_tx() if isinstance(buf, (list, tuple)): result = [] - for i, b in enumerate(buf): - if not b or len(b) > 32: - raise ValueError( - "buf (item {} in the list/tuple) must be" - " a buffer protocol object with length " - "in range [1, 32]".format(i) - ) for b in buf: result.append(self.send(b, ask_no_ack, force_retry)) return result diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index 336a82d..93b4f72 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -22,7 +22,6 @@ def __init__(self, spi, csn, ce): self.power = False else: raise RuntimeError("nRF24L01 Hardware not responding") - self.channel = 76 self._reg_write(3, 3) self._reg_write(6, 6) self._reg_write(2, 0) @@ -30,8 +29,8 @@ def __init__(self, spi, csn, ce): self._reg_write(1, 0x3F) self._reg_write(0x1D, 5) self._reg_write(4, 0x53) + self.channel = 76 self.payload_length = 32 - self.flush_rx() self.flush_tx() self.clear_status_flags() @@ -107,7 +106,7 @@ def listen(self): @listen.setter def listen(self, is_rx): assert isinstance(is_rx, (bool, int)) - if self.listen != is_rx: + if self.listen != bool(is_rx): if self.ce_pin.value: self.ce_pin.value = 0 if is_rx: From af0dcea9f0a230ec2a3f99692ecd2743fd4d3f23 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 13 Sep 2020 11:59:01 -0700 Subject: [PATCH 051/127] prioritize init(); get HW check done 1st --- circuitpython_nrf24l01/rf24.py | 24 ++++++++++++------------ circuitpython_nrf24l01/rf24_lite.py | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index c792d8c..90b8743 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -48,19 +48,10 @@ class RF24: """A driver class for the nRF24L01(+) transceiver radios.""" def __init__(self, spi, csn, ce): - self._pl_len = 32 - self._fifo = 0 - self._status = 0 - # init shadow copy of RX addresses for all pipes for context manager - self._pipes = [b"\xE7" * 5, b"\xC2" * 5, 0xC3, 0xC4, 0xC5, 0xC6] - # shadow copy of last RX_ADDR_P0 written to pipe 0 needed as - # open_tx_pipe() appropriates pipe 0 for ACK packet - self._pipe0_read_addr = None - # _open_pipes attribute reflects only RX state on each pipe - self._open_pipes = 0 # 0 = all pipes closed self._spi = SPIDevice(spi, chip_select=csn, baudrate=1250000) self.ce_pin = ce self.ce_pin.switch_to_output(value=False) # pre-empt standby-I mode + self._status = 0 # status byte returned on all SPI transactions # pre-configure the CONFIGURE register: # 0x0E = IRQs are all enabled, CRC is enabled with 2 bytes, and # power up in TX mode @@ -70,12 +61,20 @@ def __init__(self, spi, csn, ce): self.power = False else: # hardware presence check NOT passed raise RuntimeError("nRF24L01 Hardware not responding") - # shadow copies of RX pipe addresses for context manager - for i in range(6): + # init shadow copy of RX addresses for all pipes for context manager + self._pipes = [b"\xE7" * 5, b"\xC2" * 5, 0xC3, 0xC4, 0xC5, 0xC6] + # _open_pipes attribute reflects only RX state on each pipe + self._open_pipes = 0 # 0 = all pipes closed + for i in range(6): # capture RX addresses from registers if i < 2: self._pipes[i] = self._reg_read_bytes(RX_ADDR_P0 + i) else: self._pipes[i] = self._reg_read(RX_ADDR_P0 + i) + # init shadow copy of last RX_ADDR_P0 written to pipe 0 needed as + # open_tx_pipe() appropriates pipe 0 for ACK packet + self._pipe0_read_addr = None + # init shadow copy of register about FIFO info + self._fifo = 0 # shadow copy of the TX_ADDRESS self._tx_address = self._reg_read_bytes(TX_ADDRESS) # pre-configure the SETUP_RETR register @@ -91,6 +90,7 @@ def __init__(self, spi, csn, ce): self._features = 5 self._channel = 76 # 2.476 GHz self._addr_len = 5 # 5-byte long addresses + self._pl_len = 32 with self: # dumps internal attributes to all registers self.flush_rx() diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index 93b4f72..9a322f8 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -1,5 +1,5 @@ # see license and copyright information in rf24.py of this directory -# pylint: disable=missing-class-docstring,missing-function-docstring,missing-module-docstring +# pylint: disable=missing-docstring __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/2bndy5/CircuitPython_nRF24L01.git" import time @@ -12,11 +12,10 @@ class RF24: def __init__(self, spi, csn, ce): - self._pipe0_read_addr = None - self._status = 0 self.ce_pin = ce self.ce_pin.switch_to_output(value=False) self._spi = SPIDevice(spi, chip_select=csn, baudrate=1250000) + self._status = 0 self._reg_write(0, 0x0E) if self._reg_read(0) & 3 == 2: self.power = False @@ -29,6 +28,7 @@ def __init__(self, spi, csn, ce): self._reg_write(1, 0x3F) self._reg_write(0x1D, 5) self._reg_write(4, 0x53) + self._pipe0_read_addr = None self.channel = 76 self.payload_length = 32 self.flush_rx() From 20a6d9efaa207c8f01aee540754a6b2223a290dc Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Tue, 15 Sep 2020 02:35:02 -0700 Subject: [PATCH 052/127] (+) per pipe control; increased SPI baudrate auto_ack, dynamic_payloads, & payload_length can now be controlled on a per pipe basis. Revised docs and what_happened() to reflect changes. Also shortened some exception prompts. lite version dose not use per pipe control options. --- circuitpython_nrf24l01/rf24.py | 187 +++++++++++---------- circuitpython_nrf24l01/rf24_lite.py | 14 +- docs/api.rst | 251 ++++++++++++++++------------ 3 files changed, 249 insertions(+), 203 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 90b8743..99f968e 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -48,7 +48,7 @@ class RF24: """A driver class for the nRF24L01(+) transceiver radios.""" def __init__(self, spi, csn, ce): - self._spi = SPIDevice(spi, chip_select=csn, baudrate=1250000) + self._spi = SPIDevice(spi, chip_select=csn, baudrate=10000000) self.ce_pin = ce self.ce_pin.switch_to_output(value=False) # pre-empt standby-I mode self._status = 0 # status byte returned on all SPI transactions @@ -90,7 +90,7 @@ def __init__(self, spi, csn, ce): self._features = 5 self._channel = 76 # 2.476 GHz self._addr_len = 5 # 5-byte long addresses - self._pl_len = 32 + self._pl_len = [32] * 6 # 32-byte static payloads for all pipes with self: # dumps internal attributes to all registers self.flush_rx() @@ -110,10 +110,10 @@ def __enter__(self): self._reg_write_bytes(RX_ADDR_P0 + i, addr) else: self._reg_write(RX_ADDR_P0 + i, addr) - self._reg_write(RX_PL_LENG + i, self._pl_len) self._reg_write_bytes(TX_ADDRESS, self._tx_address) - self.address_length = self._addr_len - self.channel = self._channel + self._reg_write(0x05, self._channel) + self._reg_write(0x03, self._addr_len - 2) + self.payload_length = self._pl_len return self def __exit__(self, *exc): @@ -200,18 +200,22 @@ def open_rx_pipe(self, pipe_number, address): """This function is used to open a specific data pipe for OTA (over the air) RX transmissions.""" if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0,5]") - if pipe_number < 2: - if not pipe_number: - self._pipe0_read_addr = address - self._pipes[pipe_number] = address - self._reg_write_bytes(RX_ADDR_P0 + pipe_number, address) + raise ValueError("pipe number must be in range [0, 5]") + if address: + if pipe_number < 2: + if not pipe_number: + self._pipe0_read_addr = address + for i, val in enumerate(address): + self._pipes[pipe_number][i] = val + self._reg_write_bytes(RX_ADDR_P0 + pipe_number, address) + else: + self._pipes[pipe_number] = address[0] + self._reg_write(RX_ADDR_P0 + pipe_number, address[0]) + self._open_pipes = self._reg_read(OPEN_PIPES) + self._open_pipes = self._open_pipes | (1 << pipe_number) + self._reg_write(OPEN_PIPES, self._open_pipes) else: - self._pipes[pipe_number] = address[0] - self._reg_write(RX_ADDR_P0 + pipe_number, address[0]) - self._open_pipes = self._reg_read(OPEN_PIPES) - self._open_pipes = self._open_pipes | (1 << pipe_number) - self._reg_write(OPEN_PIPES, self._open_pipes) + raise ValueError("address length cannot be 0") @property def listen(self): @@ -247,7 +251,7 @@ def any(self): if self.irq_dr: if self._features & 4: return self._reg_read(0x60) - return self._reg_read(RX_PL_LENG + self.pipe) + return self._pl_len[self.pipe] return 0 def recv(self): @@ -263,14 +267,13 @@ def recv(self): def send(self, buf, ask_no_ack=False, force_retry=0): """This blocking function is used to transmit payload(s).""" self.ce_pin.value = 0 - self.flush_tx() if isinstance(buf, (list, tuple)): result = [] for b in buf: result.append(self.send(b, ask_no_ack, force_retry)) return result - get_ack_pl = bool(self._features & 6 == 6 and self._dyn_pl and self._aa) - if get_ack_pl: + self.flush_tx() + if self.pipe is not None: self.flush_rx() self.write(buf, ask_no_ack) time.sleep(0.00001) @@ -283,7 +286,7 @@ def send(self, buf, ask_no_ack=False, force_retry=0): result = self.resend() if result is None or result: break - if get_ack_pl and not ask_no_ack and self.irq_ds: + if not ask_no_ack and self._status & 0x60 == 0x60: result = self.recv() self.clear_status_flags(False) return result @@ -335,7 +338,7 @@ def what_happened(self, dump_pipes=False): print("RF Power Amplifier________{} dbm".format(self.pa_level)) print("CRC bytes_________________{}".format(self.crc)) print("Address length____________{} bytes".format(self.address_length)) - print("Payload lengths___________{} bytes".format(self.payload_length)) + print("TX Payload lengths________{} bytes".format(self.payload_length[0])) print("Auto retry delay__________{} microseconds".format(self.ard)) print("Auto retry attempts_______{} maximum".format(self.arc)) print( @@ -381,8 +384,22 @@ def what_happened(self, dump_pipes=False): ) print( "Dynamic Payloads___{} Auto Acknowledgment__{}".format( - "_Enabled" if self.dynamic_payloads else "Disabled", - "Enabled" if self.auto_ack else "Disabled", + "_Enabled" + if self._dyn_pl == 0x3F + else ( + bin(self._dyn_pl).replace( + "0b", "0b" + "0" * (8 - len(bin(self._dyn_pl))) + ) + if self._dyn_pl + else "Disabled" + ), + "Enabled" + if self._aa == 0x3F + else ( + bin(self._aa).replace("0b", "0b" + "0" * (8 - len(bin(self._aa)))) + if self._aa + else "Disabled" + ), ) ) print( @@ -406,39 +423,51 @@ def what_happened(self, dump_pipes=False): self.address(i), ) if is_open: - print("\t\texpecting", self._pl_len, "byte static payloads") + print("\t\texpecting", self._pl_len[i], "byte static payloads") @property def dynamic_payloads(self): """This `bool` attribute controls the nRF24L01's dynamic payload - length feature.""" + length feature for each pipe.""" self._dyn_pl = self._reg_read(DYN_PL_LEN) self._features = self._reg_read(TX_FEATURE) return bool(self._dyn_pl and (self._features & 4)) @dynamic_payloads.setter def dynamic_payloads(self, enable): - assert isinstance(enable, (bool, int)) self._features = self._reg_read(TX_FEATURE) - if bool(self._features & 4) != bool(enable): - self._features = (self._features & 3) | (bool(enable) << 2) + if isinstance(enable, (bool, int)): + self._dyn_pl = 0x3F if enable else 0 + elif isinstance(enable, (list, tuple)): + for i, val in enumerate(enable): + if i < 6: + self._dyn_pl = (self._dyn_pl & ~(1 << i)) | (bool(val) << i) + else: + raise ValueError( + "{} is not a valid input for dynamic_payloads".format(enable) + ) + if bool(self._features & 4) != (self._dyn_pl & 1): + self._features = (self._features & 3) | ((self._dyn_pl & 1) << 2) self._reg_write(TX_FEATURE, self._features) - self._dyn_pl = 0x3F if enable else 0 self._reg_write(DYN_PL_LEN, self._dyn_pl) @property def payload_length(self): - """This `int` attribute specifies the length (in bytes) of payload""" + """This `int` attribute specifies the length (in bytes) of static payloads for each + pipe.""" return self._pl_len @payload_length.setter def payload_length(self, length): - if not length or length <= 32: - self._pl_len = length - for i in range(6): - self._reg_write(RX_PL_LENG + i, length) - else: - raise ValueError("payload length must be in range [1, 32] bytes") + if isinstance(length, int): + length = [length] * 6 + elif not isinstance(length, (list, tuple)): + raise ValueError("length {} is not a valid input".format(length)) + for i, val in enumerate(length): + if i < 6: + if not val or val <= 32: # don't throw exception, just skip pipe + self._pl_len[i] = val + self._reg_write(RX_PL_LENG + i, val) @property def arc(self): @@ -455,9 +484,7 @@ def arc(self, count): self._retry_setup = (self._retry_setup & 0xF0) | count self._reg_write(SETUP_RETR, self._retry_setup) else: - raise ValueError( - "automatic re-transmit count(/attempts) must in" " range [0, 15]" - ) + raise ValueError("automatic re-transmit count must in range [0, 15]") @property def ard(self): @@ -470,16 +497,13 @@ def ard(self): @ard.setter def ard(self, delta_t): - if 250 <= delta_t <= 4000 and delta_t % 250 == 0: - if self.ard != delta_t: - self._retry_setup &= 0x0F - self._retry_setup |= int((delta_t - 250) / 250) << 4 - self._reg_write(SETUP_RETR, self._retry_setup) - else: - raise ValueError( - "automatic re-transmit delay can only be a " - "multiple of 250 in range [250, 4000]" + if 250 <= delta_t <= 4000: + self._retry_setup = (self._retry_setup & 15) | ( + int((delta_t - 250) / 250) << 4 ) + self._reg_write(SETUP_RETR, self._retry_setup) + else: + raise ValueError("automatic re-transmit delay must be in range [250, 4000]") @property def auto_ack(self): @@ -490,9 +514,17 @@ def auto_ack(self): @auto_ack.setter def auto_ack(self, enable): - assert isinstance(enable, (bool, int)) - self._aa = 0x3F if enable else 0 + if isinstance(enable, (bool, int)): + self._aa = 0x3F if enable else 0 + elif isinstance(enable, (list, tuple)): + for i, val in enumerate(enable): + if i < 6: + self._aa = (self._aa & ~(1 << i)) | (bool(val) << i) + else: + raise ValueError("{} is not a valid input for auto_ack".format(enable)) self._reg_write(AUTO_ACK, self._aa) + if self._aa: + self._config = self._reg_read(CONFIGURE) @property def ack(self): @@ -502,14 +534,14 @@ def ack(self): self._aa = self._reg_read(AUTO_ACK) self._dyn_pl = self._reg_read(DYN_PL_LEN) self._features = self._reg_read(TX_FEATURE) - return bool((self._features & 6) == 6 and self._aa and self._dyn_pl) + return bool((self._features & 6) == 6 and ((self._aa & self._dyn_pl) & 1)) @ack.setter def ack(self, enable): assert isinstance(enable, (bool, int)) if self.ack != bool(enable): - self.auto_ack = True - self._dyn_pl = 0x3F + self.auto_ack = 1 + self._dyn_pl = (self._dyn_pl & ~(1)) | 1 self._reg_write(DYN_PL_LEN, self._dyn_pl) self._features = (self._features & 5) | (6 if enable else 0) self._reg_write(TX_FEATURE, self._features) @@ -520,19 +552,16 @@ def load_ack(self, buf, pipe_number): if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0, 5]") if not buf or len(buf) > 32: - raise ValueError( - "buf must be a buffer protocol object with " "length in range [1, 32]" - ) - if not bool((self._features & 6) == 6 and self._aa and self._dyn_pl): + raise ValueError("payload must have a length in range [1, 32] (in bytes)") + if not bool((self._features & 6) == 6 and ((self._aa & self._dyn_pl) & 1)): self.ack = True - if not self.tx_full: + if not self.fifo(True, False): self._reg_write_bytes(0xA8 | pipe_number, buf) return True return False def read_ack(self): - """Allows user to read the automatic acknowledgement (ACK) payload (if - any) when nRF24L01 is in TX mode.""" + """Allows user to read the automatic acknowledgement (ACK) payload (if any).""" return self.recv() @property @@ -554,9 +583,7 @@ def data_rate(self, speed): self._rf_setup = self._rf_setup & 0xD7 | speed self._reg_write(RF_PA_RATE, self._rf_setup) else: - raise ValueError( - "data rate must be one of the following " "([M,M,K]bps): 1, 2, 250" - ) + raise ValueError("data rate must be 1 (Mbps), 2 (Mbps), or 250 (kbps)") @property def channel(self): @@ -604,8 +631,7 @@ def power(self, is_on): @property def pa_level(self): - """This `int` attribute specifies the nRF24L01's power amplifier - level (in dBm).""" + """This `int` attribute specifies the nRF24L01's power amplifier level (in dBm).""" self._rf_setup = self._reg_read(RF_PA_RATE) return (3 - ((self._rf_setup & RF_PA_RATE) >> 1)) * -6 @@ -617,9 +643,7 @@ def pa_level(self, power): self._rf_setup = (self._rf_setup & 0xF9) | power self._reg_write(RF_PA_RATE, self._rf_setup) else: - raise ValueError( - "power amplitude must be one of the following " "(dBm): -18, -12, -6, 0" - ) + raise ValueError("power amplitude must be -18, -12, -6, or 0 (in dBm)") @property def rpd(self): @@ -643,12 +667,9 @@ def resend(self): top level (first out) of the TX FIFO buffer.""" result = False if not self.fifo(True, True): - get_ack_pl = bool( - self._features & 6 == 6 and self._aa & 1 and self._dyn_pl & 1 - ) - if get_ack_pl: + if self.pipe is not None: self.flush_rx() - self.clear_status_flags(get_ack_pl) + self.clear_status_flags() self._reg_write(0xE3) self.ce_pin.value = 0 self.ce_pin.value = 1 @@ -657,7 +678,7 @@ def resend(self): while not self._status & 0x30: self.update() result = self.irq_ds - if get_ack_pl: + if self._status & 0x60 == 0x60: result = self.recv() self.clear_status_flags(False) return result @@ -667,22 +688,18 @@ def write(self, buf, ask_no_ack=False): is meant for asynchronous applications and can only handle one payload at a time as it is a helper function to `send()`.""" if not buf or len(buf) > 32: - raise ValueError( - "buf must be a buffer protocol object with " "length in range [1, 32]" - ) - self.clear_status_flags( - bool(self._features & 6 == 6 and self._aa and self._dyn_pl) - ) + raise ValueError("buffer must have a length in range [1, 32]") + self.clear_status_flags() if self._config & 3 != 2: # is radio powered up in TX mode? self._config = (self._reg_read(CONFIGURE) & 0x7C) | 2 self._reg_write(CONFIGURE, self._config) time.sleep(0.00016) if not bool((self._dyn_pl & 1) and (self._features & 4)): - if len(buf) < self._pl_len: - for _ in range(self._pl_len - len(buf)): + if len(buf) < self._pl_len[0]: + for _ in range(self._pl_len[0] - len(buf)): buf += b"\x00" - elif len(buf) > self._pl_len: - buf = buf[: self._pl_len] + elif len(buf) > self._pl_len[0]: + buf = buf[: self._pl_len[0]] if ask_no_ack: if self._features & 1 == 0: self._features = self._features & 0xFE | 1 diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index 9a322f8..48ba261 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -139,15 +139,13 @@ def recv(self): def send(self, buf, ask_no_ack=False, force_retry=0): self.ce_pin.value = 0 - self.flush_tx() if isinstance(buf, (list, tuple)): result = [] for b in buf: result.append(self.send(b, ask_no_ack, force_retry)) return result - get_ack_pl = bool((self._reg_read(0x1D) & 6) == 6 and self._reg_read(4) & 0xF) - if get_ack_pl: - self.flush_rx() + self.flush_tx() + self.flush_rx() self.write(buf, ask_no_ack) time.sleep(0.00001) self.ce_pin.value = 0 @@ -159,7 +157,7 @@ def send(self, buf, ask_no_ack=False, force_retry=0): result = self.resend() if result is None or result: break - if get_ack_pl and not ask_no_ack and self.irq_ds: + if not ask_no_ack and self._status & 0x60: result = self.recv() self.clear_status_flags(False) return result @@ -325,7 +323,9 @@ def update(self): def resend(self): result = False if not self._reg_read(0x17) & 0x10: - self.clear_status_flags(False) + if self.pipe is not None: + self.flush_rx() + self.clear_status_flags() self._reg_write(0xE3) self.ce_pin.value = 0 self.ce_pin.value = 1 @@ -342,7 +342,7 @@ def resend(self): def write(self, buf, ask_no_ack=False): if not buf or len(buf) > 32: raise ValueError("buffer must have a length in range [1, 32]") - self.clear_status_flags(False) + self.clear_status_flags() config = self._reg_read(0) if config & 3 != 2: self._reg_write(0, (config & 0x7C) | 2) diff --git a/docs/api.rst b/docs/api.rst index 127f938..8d1354b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -90,15 +90,17 @@ version: * `fifo()` * `pa_level` (this is always set to 0 dBm) * `address_length` (this is always set to 5 bytes) - * `read_ack()` (deprecated anyway; use `recv()` instead) + * `read_ack()` (deprecated on next major release anyway; use `recv()` instead) * `crc` (always using 2 bytes encoding scheme) - * `auto_ack` (this is always on). Pass ``ask_no_ack`` parameter as `True` to `send()` or - `write()` to disable automatic acknowledgement for TX operations. + * `auto_ack` (this is always on for all pipes). Pass ``ask_no_ack`` parameter as `True` to + `send()` or `write()` to disable automatic acknowledgement for TX operations. * all comments and docstrings (meaning ``help()`` will not provide any specific information). Exception prompts have also been reduced and adjusted accordingly. * switching between different radio configurations using context manager (the `with` blocks). It is advised that only one `RF24` object be instantiated when RAM is limited (less than or equal to 32KB). + * `dynamic_payloads` applies to all pipes, not individual pipes. + * `payload_length` applies to all pipes, not individual pipes. RF24 class ============== @@ -132,10 +134,7 @@ address_length .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.address_length - The addresses assigned to the data pipes must have byte length equal to the value - set for this attribute. - - A valid input value must be an `int` in range [3,5]. Otherwise a `ValueError` exception is + A valid input value must be an `int` in range [3, 5]. Otherwise a `ValueError` exception is thrown. Default is set to the nRF24L01's maximum of 5. open_tx_pipe() @@ -143,16 +142,15 @@ open_tx_pipe() .. automethod:: circuitpython_nrf24l01.rf24.RF24.open_tx_pipe - :param bytearray address: The virtual address of the receiving nRF24L01. This must have a - length equal to the `address_length` attribute (see `address_length` attribute). - Otherwise a `ValueError` exception is thrown. The address specified here must match the - address set to one of the RX data pipes of the receiving nRF24L01. + :param bytearray address: The virtual address of the receiving nRF24L01. The address specified + here must match the address set to one of the RX data pipes of the receiving nRF24L01. + The existing address can be altered by writting a bytearray with a length less than 5. The nRF24L01 will use the first `address_length` number of bytes for the RX address on the specified data pipe. .. note:: There is no option to specify which data pipe to use because the nRF24L01 only uses data pipe 0 in TX mode. Additionally, the nRF24L01 uses the same data pipe (pipe 0) for receiving acknowledgement (ACK) packets in TX mode when the `auto_ack` attribute - is enabled. Thus, RX pipe 0 is appropriated with the TX address (specified here) when - `auto_ack` is set to `True`. + is enabled for data pipe 0. Thus, RX pipe 0 is appropriated with the TX address (specified + here) when `auto_ack` is enabled for data pipe 0. close_rx_pipe() ****************** @@ -160,27 +158,26 @@ close_rx_pipe() .. automethod:: circuitpython_nrf24l01.rf24.RF24.close_rx_pipe :param int pipe_number: The data pipe to use for RX transactions. This must be in range - [0,5]. Otherwise a `ValueError` exception is thrown. + [0, 5]. Otherwise a `ValueError` exception is thrown. open_rx_pipe() ****************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.open_rx_pipe - If `dynamic_payloads` attribute is `False`, then the `payload_length` - attribute is used to specify the expected length of the RX payload on the specified data - pipe. + If `dynamic_payloads` attribute is disabled for the specifed data pipe, then the + `payload_length` attribute is used to define the expected length of the static RX payload + on the specified data pipe. :param int pipe_number: The data pipe to use for RX transactions. This must be in range - [0,5]. Otherwise a `ValueError` exception is thrown. - :param bytearray address: The virtual address to the receiving nRF24L01. This must have a - byte length equal to the `address_length` attribute. Otherwise a `ValueError` - exception is thrown. If using a ``pipe_number`` greater than 1, then only the MSByte - of the address is written, so make sure MSByte (first character) is unique among other - simultaneously receiving addresses). + [0, 5]. Otherwise a `ValueError` exception is thrown. + :param bytearray address: The virtual address to the receiving nRF24L01. If using a + ``pipe_number`` greater than 1, then only the MSByte of the address is written, so make + sure MSByte (first character) is unique among other simultaneously receiving addresses. + The existing address can be altered by writing a bytearray with a length less than 5. The nRF24L01 will use the first `address_length` number of bytes for the RX address on the specified data pipe. - .. note:: The nRF24L01 shares the addresses' LSBytes (address[1:5]) on data pipes 2 through - 5. These shared LSBytes are determined by the address set to pipe 1. + .. note:: The nRF24L01 shares the addresses' last 4 LSBytes on data pipes 2 through + 5. These shared LSBytes are determined by the address set to data pipe 1. listen ****************** @@ -220,12 +217,12 @@ recv() .. automethod:: circuitpython_nrf24l01.rf24.RF24.recv - This function synonomous to `read_ack()`. + This function can also be used to fetch the last ACK packet's payload if `ack` is enabled. :returns: A `bytearray` of the RX payload data or `None` if there is no payload - If the `dynamic_payloads` attribute is disabled, then the returned bytearray's length - is equal to the user defined `payload_length` attribute (which defaults to 32). + is equal to the user defined `payload_length` attribute for the data pipe that received the payload. - If the `dynamic_payloads` attribute is enabled, then the returned bytearray's length is equal to the payload's length @@ -234,37 +231,35 @@ send() .. automethod:: circuitpython_nrf24l01.rf24.RF24.send - :returns: - `list` if a list or tuple of payloads was passed as the ``buf`` parameter. Each item in the returned list will contain the returned status for each corresponding payload in the list/tuple that was passed. The return statuses will be in one of the following forms: - - `False` if transmission fails. Transmission failure can only be detected if `auto_ack` - is `True`. + - `False` if transmission fails. Transmission failure can only be detected if `arc` + is greater than ``0``. - `True` if transmission succeeds. - `bytearray` or `None` when the `ack` attribute is `True`. Because the payload expects a responding custom ACK payload, the response is returned (upon successful transmission) as a `bytearray` (or `None` if ACK payload is empty) :param bytearray,list,tuple buf: The payload to transmit. This bytearray must have a length - greater than 0 and less than 32, otherwise a `ValueError` exception is thrown. This can + in range [1, 32], otherwise a `ValueError` exception is thrown. This can also be a list or tuple of payloads (`bytearray`); in which case, all items in the list/tuple are processed for consecutive transmissions. - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is less - than the `payload_length` attribute, then this bytearray is padded with zeros until - its length is equal to the `payload_length` attribute. - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is - greater than `payload_length` attribute, then this bytearray's length is truncated to - equal the `payload_length` attribute. + - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's + length is less than the `payload_length` attribute for pipe 0, then this bytearray is padded with zeros until its length is equal to the `payload_length` attribute for pipe 0. + - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's + length is greater than `payload_length` attribute for pipe 0, then this bytearray's length + is truncated to equal the `payload_length` attribute for pipe 0. :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for an acknowledgment from the receiving nRF24L01. This parameter directly controls a ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about the payload). Therefore, it takes advantage of an nRF24L01 feature specific to individual payloads, and its value is not saved anywhere. You do not need to specify - this for every payload if the `arc` attribute is disabled, however this parameter - will work despite the `arc` attribute's setting. + this for every payload if the `arc` attribute is disabled, however setting this parameter + to `True` will work despite the `arc` attribute's setting. .. note:: Each transmission is in the form of a packet. This packet contains sections of data around and including the payload. `See Chapter 7.3 in the nRF24L01 @@ -280,8 +275,8 @@ send() notes on `resend()` as using this parameter carries the same implications documented there. - .. tip:: It is highly recommended that `arc` attribute is enabled when sending - multiple payloads. Test results with the `arc` attribute disabled were very poor + .. tip:: It is highly recommended that `arc` attribute is enabled (greater than ``0``) when + sending multiple payloads. Test results with the `arc` attribute disabled were very poor (much < 50% received). This same advice applies to the ``ask_no_ack`` parameter (leave it as `False` for multiple payloads). .. warning:: The nRF24L01 will block usage of the TX FIFO buffer upon failed @@ -308,7 +303,7 @@ what_happened() - ``RF Power Amplifier`` The current setting of the `pa_level` attribute. - ``CRC bytes`` The current setting of the `crc` attribute - ``Address length`` The current setting of the `address_length` attribute - - ``Payload lengths`` The current setting of the `payload_length` attribute + - ``TX Payload lengths`` The current setting of the `payload_length` attribute for TX operations (concerning data pipe 0) - ``Auto retry delay`` The current setting of the `ard` attribute - ``Auto retry attempts`` The current setting of the `arc` attribute - ``Packets lost on current channel`` Total amount of packets lost (transmission @@ -331,20 +326,24 @@ what_happened() attached to the acknowledgment packet? (state of the `ack` attribute) - ``Ask no ACK`` Is the nRF24L01 setup to transmit individual packets that don't require acknowledgment? - - ``Automatic Acknowledgment`` Is the `auto_ack` attribute enabled? - - ``Dynamic Payloads`` Is the `dynamic_payloads` attribute enabled? + - ``Automatic Acknowledgment`` The status of the `auto_ack` feature. If this value is a + binary representation, then each bit represents the feature's status for each pipe. + - ``Dynamic Payloads`` The status of the `dynamic_payloads` feature. If this value is a + binary representation, then each bit represents the feature's status for each pipe. - ``Primary Mode`` The current mode (RX or TX) of communication of the nRF24L01 device. - ``Power Mode`` The power state can be Off, Standby-I, Standby-II, or On. :param bool dump_pipes: `True` appends the output and prints: - - the current address used for TX transmissions + - the current address used for TX transmissions. This value is the entire content of the + nRF24L01's register about the TX address (despite what `address_length` is set to). - ``Pipe [#] ([open/closed]) bound: [address]`` where ``#`` represent the pipe number, the ``open/closed`` status is relative to the pipe's RX status, and ``address`` is - read directly from the nRF24L01 registers. + the full value stored in the nRF24L01's RX address registers (despite what + `address_length` is set to. - if the pipe is open, then the output also prints ``expecting [X] byte static payloads`` where ``X`` is the `payload_length` (in bytes) the pipe is setup to - receive when `dynamic_payloads` is disabled. + receive when `dynamic_payloads` is disabled for that pipe. Default is `False` and skips this extra information. @@ -353,53 +352,73 @@ dynamic_payloads .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.dynamic_payloads - Default setting is `True`. + Default setting is enabled on all pipes. + + - `True` or ``1`` enables nRF24L01's dynamic payload length feature for all data pipes. The + `payload_length` attribute is ignored when this feature is enabled for respective or all data + pipes. + - `False` or ``0`` disables nRF24L01's dynamic payload length feature for all data pipes. Be sure + to adjust the `payload_length` attribute accordingly when this feature is disabled for any + data pipes. + - A `list` or `tuple` containing booleans or integers can be used control this feature per data + pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be ignored + since there are only 6 data pipes. - - `True` enables nRF24L01's dynamic payload length feature. The `payload_length` attribute is - ignored when this feature is enabled. - - `False` disables nRF24L01's dynamic payload length feature. Be sure to adjust the - `payload_length` attribute accordingly when `dynamic_payloads` feature is disabled. + .. note:: + This attribute mostly relates to RX operations, but data pipe 0 applies to TX operations + also. payload_length ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.payload_length - If the `dynamic_payloads` attribute is enabled, this attribute has no affect. When - `dynamic_payloads` is disabled, this attribute is used to specify the payload length when - entering RX mode. + If the `dynamic_payloads` attribute is enabled for a certain data pipe, this attribute has no + affect on that data pipe. When `dynamic_payloads` is disabled for a certain data pipe, this + attribute is used to specify the payload length on that data pipe in RX mode. - A valid input value must be an `int` in range [1,32]. Otherwise a `ValueError` exception is - thrown. Default is set to the nRF24L01's maximum of 32. - - .. note:: When `dynamic_payloads` is disabled during transmissions: + A valid input value must be: + + * an `int` in range [1, 32]. Otherwise a `ValueError` exception is thrown. + * a `list` or `tuple` containing integers can be used to control this attribute per data + pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be ignored + since there are only 6 data pipes. if a index's value is ``0``, then the existing setting + will persist (not be changed). + + Default is set to the nRF24L01's maximum of 32 (on all data pipes). - - Payloads' size of greater than this attribute's value will be truncated to match. - - Payloads' size of less than this attribute's value will be padded with zeros to - match. + .. note:: + This attribute mostly relates to RX operations, but data pipe 0 applies to TX operations + also. auto_ack ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.auto_ack - Default setting is `True`. + Default setting is enabled on all data pipes. + + - `True` or ``1`` enables transmitting automatic acknowledgment packets for all data pipes. + The CRC (cyclic redundancy checking) is enabled automatically by the nRF24L01 if the `auto_ack` attribute is enabled for any data pipe (see also `crc` attribute). + - `False` or ``0`` disables transmitting automatic acknowledgment packets for all data pipes. + The `crc` attribute will remain unaffected when disabling this attribute for any data pipes. + - A `list` or `tuple` containing booleans or integers can be used control this feature per data + pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be ignored + since there are only 6 data pipes. - - `True` enables transmitting automatic acknowledgment packets. The CRC (cyclic redundancy - checking) is enabled automatically by the nRF24L01 if the `auto_ack` attribute is enabled - (see also `crc` attribute). - - `False` disables transmitting automatic acknowledgment packets. The `crc` attribute will - remain unaffected when disabling the `auto_ack` attribute. + .. note:: + This attribute mostly relates to RX operations, but data pipe 0 applies to TX operations + also. arc ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.arc - The `auto_ack` must be enabled on the receiving nRF24L01, otherwise this attribute will make - `send()` seem like it failed. + The `auto_ack` attribute must be enabled on the receiving nRF24L01 respective data pipe, + otherwise this attribute will make `send()` seem like it failed. - A valid input value must be in range [0,15]. Otherwise a `ValueError` exception is thrown. + A valid input value must be in range [0, 15]. Otherwise a `ValueError` exception is thrown. Default is set to 3. A value of ``0`` disables the automatic re-transmit feature and considers all payload transmissions a success. @@ -411,8 +430,9 @@ ard During this time, the nRF24L01 is listening for the ACK packet. If the `auto_ack` attribute is disabled, this attribute is not applied. - A valid input value must be a multiple of 250 in range [250,4000]. Otherwise a `ValueError` - exception is thrown. Default is 1500 for reliability. + A valid input value must be in range [250, 4000]. Otherwise a `ValueError` exception is thrown. + Default is 1500 for reliability. If this is set to a value that is not multiple of 250, then + the highest multiple of 250 that is no greater than the input value is used. .. note:: Paraphrased from nRF24L01 specifications sheet: @@ -432,11 +452,15 @@ ack is `False`. - `True` enables the use of custom ACK payloads in the ACK packet when responding to - receiving transmissions. As `dynamic_payloads` and `auto_ack` attributes are required for - this feature to work, they are automatically enabled as needed. - - `False` disables the use of custom ACK payloads. Disabling this feature does not disable - the `auto_ack` and `dynamic_payloads` attributes (they work just fine without this - feature). + receiving transmissions. + - `False` disables the use of custom ACK payloads in the ACK packet when responding to + receiving transmissions. + + .. important:: + As `dynamic_payloads` and `auto_ack` attributes are required for this feature to work, they + are automatically enabled (on data pipe 0) as needed. However, it is required to enable the + `auto_ack` and `dynamic_payloads` features on all applicable pipes. Disabling this feature + does not disable the `auto_ack` and `dynamic_payloads` attributes for any data pipe; they work just fine without this feature. load_ack() ****************************** @@ -449,12 +473,12 @@ load_ack() :param bytearray buf: This will be the data attached to an automatic ACK packet on the incoming transmission about the specified ``pipe_number`` parameter. This must have a - length in range [1,32] bytes, otherwise a `ValueError` exception is thrown. Any ACK + length in range [1, 32] bytes, otherwise a `ValueError` exception is thrown. Any ACK payloads will remain in the TX FIFO buffer until transmitted successfully or `flush_tx()` is called. :param int pipe_number: This will be the pipe number to use for deciding which transmissions get a response with the specified ``buf`` parameter's data. This number - must be in range [0,5], otherwise a `ValueError` exception is thrown. + must be in range [0, 5], otherwise a `ValueError` exception is thrown. :returns: `True` if payload was successfully loaded onto the TX FIFO buffer. `False` if it wasn't because TX FIFO buffer is full. @@ -476,7 +500,7 @@ read_ack() .. automethod:: circuitpython_nrf24l01.rf24.RF24.read_ack - This function is called from a blocking `send()` call if the `ack` attribute + This function was internally called from a blocking `send()` call if the `ack` attribute is enabled. Alternatively, this function can be called directly in case of calling the non-blocking `write()` function during asychronous applications. This function is an alias of `recv()` and remains for backward compatibility with older versions of this library. @@ -584,8 +608,7 @@ interrupt_config() 1. read payload through `recv()` 2. clear ``dataReady`` status flag (taken care of by using `recv()` in previous step) 3. read FIFO_STATUS register to check if there are more payloads available in RX FIFO - buffer. (a call to `pipe()`, `any()` or even ``(False,True)`` as parameters to - `fifo()` will get this result) + buffer. A call to `pipe` (may require `update()` to be called), `any()` or even ``(False,True)`` as parameters to `fifo()` will get this result. 4. if there is more data in RX FIFO, repeat from step 1 data_rate @@ -601,7 +624,7 @@ data_rate Any invalid input throws a `ValueError` exception. Default is 1 Mbps. - .. warning:: 250 Kbps is be buggy on the non-plus models of the nRF24L01 product line. If + .. warning:: 250 Kbps can be buggy on the non-plus models of the nRF24L01 product line. If you use 250 Kbps data rate, and some transmissions report failed by the transmitting nRF24L01, even though the same packet in question actually reports received by the receiving nRF24L01, then try a higher data rate. CAUTION: Higher data rates mean less @@ -613,7 +636,7 @@ channel .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.channel A valid input value must be in range [0, 125] (that means [2.4, 2.525] GHz). Otherwise a - `ValueError` exception is thrown. Default is 76. + `ValueError` exception is thrown. Default is ``76`` (2.476 GHz). crc ****************************** @@ -622,7 +645,7 @@ crc CRC is a way of making sure that the transmission didn't get corrupted over the air. - A valid input value is in range [0,2]: + A valid input value must be: - ``0`` disables CRC (no anti-corruption of data) - ``1`` enables CRC encoding scheme using 1 byte (weak anti-corruption of data) @@ -638,26 +661,27 @@ power .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.power - This is exposed for asynchronous applications and user preference. + This is exposed for convenience. - `False` basically puts the nRF24L01 to sleep (AKA power down mode) with ultra-low current consumption. No transmissions are executed when sleeping, but the nRF24L01 can still be accessed through SPI. Upon instantiation, this driver class puts the nRF24L01 to sleep until the MCU invokes RX/TX transmissions. This driver class doesn't power down the nRF24L01 after RX/TX transmissions are complete (avoiding the required power up/down - 130 µs wait time), that preference is left to the user. + 150 µs wait time), that preference is left to the application. - `True` powers up the nRF24L01. This is the first step towards entering RX/TX modes (see also `listen` attribute). Powering up is automatically handled by the `listen` attribute as well as the `send()` and `write()` functions. .. note:: This attribute needs to be `True` if you want to put radio on Standby-II (highest - current consumption) or Standby-I (moderate current consumption) modes. TX - transmissions are only executed during Standby-II by calling `send()` or `write()`. RX - transmissions are received during Standby-II by setting `listen` attribute to `True` - (see `Chapter 6.1.2-7 of the nRF24L01+ Specifications Sheet `_). After using `send()` or setting `listen` to `False`, the nRF24L01 - is left in Standby-I mode (see also notes on the `write()` function). + current consumption) or Standby-I (moderate current consumption) modes, which Standby + mode depends on the state of the CE pin. TX transmissions are only executed during + Standby-II by calling `send()` or `write()`. RX transmissions are received during + Standby-II by setting `listen` attribute to `True` (see `Chapter 6.1.2-7 of the nRF24L01+ + Specifications Sheet `_). After using + `send()` or setting `listen` to `False`, the nRF24L01 is left in Standby-I mode (see also + notes on the `write()` function). pa_level ****************************** @@ -715,24 +739,24 @@ update() .. automethod:: circuitpython_nrf24l01.rf24.RF24.update - Refreshing the status byte is vital to checking status of the interrupts, RX pipe number related - to current RX payload, and if the TX FIFO buffer is full. This function returns nothing, but - internally updates the `irq_dr`, `irq_ds`, `irq_df`, `pipe`, and `tx_full` attributes. Internally - this is a helper function to `send()`, and `resend()` functions. + Refreshing the status byte is vital to checking status of the interrupt flags, RX pipe number + related to current RX payload, and if the TX FIFO buffer is full. This function returns nothing, + but internally updates the `irq_dr`, `irq_ds`, `irq_df`, `pipe`, and `tx_full` attributes. + Internally this is a helper function to `send()`, and `resend()` functions. resend() ****************************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.resend - All returned data follows the same patttern that `send()` returns with the added condition that - this function will return `False` if the TX FIFO buffer is empty. + All returned data from this function follows the same patttern that `send()` returns with the + added condition that this function will return `False` if the TX FIFO buffer is empty. .. note:: The nRF24L01 normally removes a payload from the TX FIFO buffer after successful transmission, but not when this function is called. The payload (successfully transmitted or not) will remain in the TX FIFO buffer until `flush_tx()` is called to remove them. Alternatively, using this function also allows the failed payload to be - over-written by using `send()` or `write()` to load a new payload. + over-written by using `send()` or `write()` to load a new payload into the TX FIFO buffer. write() ****************************** @@ -742,19 +766,20 @@ write() :param bytearray buf: The payload to transmit. This bytearray must have a length greater than 0 and less than 32 bytes, otherwise a `ValueError` exception is thrown. - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is less - than the `payload_length` attribute, then this bytearray is padded with zeros until - its length is equal to the `payload_length` attribute. - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is - greater than `payload_length` attribute, then this bytearray's length is truncated to - equal the `payload_length` attribute. + - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's + length is less than the `payload_length` attribute for data pipe 0, then this bytearray + is padded with zeros until its length is equal to the `payload_length` attribute for + data pipe 0. + - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's + length is greater than `payload_length` attribute for data pipe 0, then this bytearray's + length is truncated to equal the `payload_length` attribute for data pipe 0. :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for an acknowledgment from the receiving nRF24L01. This parameter directly controls a ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about the payload). Therefore, it takes advantage of an nRF24L01 feature specific to individual payloads, and its value is not saved anywhere. You do not need to specify - this for every payload if the `auto_ack` attribute is disabled, however this parameter - should work despite the `auto_ack` attribute's setting. + this for every payload if the `arc` attribute is disabled, however setting this + parameter to `True` will work despite the `arc` attribute's setting. .. note:: Each transmission is in the form of a packet. This packet contains sections of data around and including the payload. `See Chapter 7.3 in the nRF24L01 @@ -772,7 +797,8 @@ write() the Specification sheet, if the CE pin remains HIGH for longer than 10 µs, then the nRF24L01 will continue to transmit all payloads found in the TX FIFO buffer. - .. warning:: A note paraphrased from the `nRF24L01+ Specifications Sheet + .. warning:: + A note paraphrased from the `nRF24L01+ Specifications Sheet `_: @@ -858,6 +884,9 @@ address() .. automethod:: circuitpython_nrf24l01.rf24.RF24.address + This function returns the full content of the nRF24L01's registers about RX/TX addresses + despite what `address_length` is set to. + :param int index: the number of the data pipe whose address is to be returned. Defaults to ``-1``. A valid index ranges [0,5] for RX addresses or any negative `int` for the TX address. Otherwise an `IndexError` is thown. From 9dc34b9deb360aff39a368edc1403bc980c1395d Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Tue, 15 Sep 2020 02:46:34 -0700 Subject: [PATCH 053/127] fixed saving address shadow copy in open_tx_pipe() --- circuitpython_nrf24l01/rf24.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 99f968e..f869ff6 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -62,7 +62,7 @@ def __init__(self, spi, csn, ce): else: # hardware presence check NOT passed raise RuntimeError("nRF24L01 Hardware not responding") # init shadow copy of RX addresses for all pipes for context manager - self._pipes = [b"\xE7" * 5, b"\xC2" * 5, 0xC3, 0xC4, 0xC5, 0xC6] + self._pipes = [bytearray(5), bytearray(5), 0xC3, 0xC4, 0xC5, 0xC6] # _open_pipes attribute reflects only RX state on each pipe self._open_pipes = 0 # 0 = all pipes closed for i in range(6): # capture RX addresses from registers @@ -132,7 +132,7 @@ def _reg_read(self, reg): def _reg_read_bytes(self, reg, buf_len=5): in_buf = bytearray(buf_len + 1) - out_buf = bytearray([reg]) + b"\x00" * buf_len + out_buf = bytes([reg]) + b"\x00" * buf_len with self._spi as spi: time.sleep(0.005) spi.write_readinto(out_buf, in_buf) @@ -179,7 +179,8 @@ def open_tx_pipe(self, address): """This function is used to open a data pipe for OTA (over the air) TX transmissions.""" if self.auto_ack: - self._pipes[0] = address + for i, val in enumerate(address): + self._pipes[0][i] = val self._reg_write_bytes(RX_ADDR_P0, address) self._open_pipes = self._open_pipes | 1 self._reg_write(OPEN_PIPES, self._open_pipes) From 8370ade655a29c133580f7fd79e2a2b671f82482 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Tue, 15 Sep 2020 03:21:08 -0700 Subject: [PATCH 054/127] keep _pipes[0] mutable --- circuitpython_nrf24l01/rf24.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index f869ff6..d10ffc9 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -122,7 +122,7 @@ def __exit__(self, *exc): # pylint: disable=no-member def _reg_read(self, reg): - out_buf = bytearray([reg, 0]) + out_buf = bytes([reg, 0]) in_buf = bytearray([0, 0]) with self._spi as spi: time.sleep(0.005) @@ -178,13 +178,14 @@ def address_length(self, length): def open_tx_pipe(self, address): """This function is used to open a data pipe for OTA (over the air) TX transmissions.""" - if self.auto_ack: + if self.arc: for i, val in enumerate(address): self._pipes[0][i] = val self._reg_write_bytes(RX_ADDR_P0, address) self._open_pipes = self._open_pipes | 1 self._reg_write(OPEN_PIPES, self._open_pipes) - self._tx_address = address + for i, val in enumerate(address): + self._tx_address[i] = val self._reg_write_bytes(TX_ADDRESS, address) def close_rx_pipe(self, pipe_number): @@ -205,7 +206,7 @@ def open_rx_pipe(self, pipe_number, address): if address: if pipe_number < 2: if not pipe_number: - self._pipe0_read_addr = address + self._pipe0_read_addr = bytearray(address) for i, val in enumerate(address): self._pipes[pipe_number][i] = val self._reg_write_bytes(RX_ADDR_P0 + pipe_number, address) From e2244eb10f3ace5ff7c46dd67b1d17ebbbca6bb4 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Tue, 15 Sep 2020 05:27:30 -0700 Subject: [PATCH 055/127] recv() exits RX mode while fetching payload --- circuitpython_nrf24l01/rf24.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index d10ffc9..6b2c566 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -262,8 +262,13 @@ def recv(self): curr_pl_size = self.any() if not curr_pl_size: return None + last_ce_state = self.ce_pin.value + self.ce_pin.value = 0 result = self._reg_read_bytes(0x61, curr_pl_size) self.clear_status_flags(True, False, False) + if self.ce_pin.value != last_ce_state: + self.ce_pin.value = 1 + time.sleep(0.00013 if self._config & 1 else 0.00001) return result def send(self, buf, ask_no_ack=False, force_retry=0): From 319dd69d638d38df5767dc59b23adb9ff0721c6a Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Tue, 15 Sep 2020 05:56:32 -0700 Subject: [PATCH 056/127] undo last commit; any() uses update() & pipe attr --- circuitpython_nrf24l01/rf24.py | 9 ++------- docs/api.rst | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 6b2c566..d861994 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -249,8 +249,8 @@ def listen(self, is_rx): def any(self): """This function checks if the nRF24L01 has received any data at all, and then reports the next available payload's length (in bytes).""" - self._features = self._reg_read(TX_FEATURE) - if self.irq_dr: + self.update() + if self.pipe is not None: if self._features & 4: return self._reg_read(0x60) return self._pl_len[self.pipe] @@ -262,13 +262,8 @@ def recv(self): curr_pl_size = self.any() if not curr_pl_size: return None - last_ce_state = self.ce_pin.value - self.ce_pin.value = 0 result = self._reg_read_bytes(0x61, curr_pl_size) self.clear_status_flags(True, False, False) - if self.ce_pin.value != last_ce_state: - self.ce_pin.value = 1 - time.sleep(0.00013 if self._config & 1 else 0.00001) return result def send(self, buf, ask_no_ack=False, force_retry=0): diff --git a/docs/api.rst b/docs/api.rst index 8d1354b..b1cea74 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -854,7 +854,7 @@ fifo() - `True` tests if the specified FIFO buffer is empty. - `False` tests if the specified FIFO buffer is full. - `None` (when not specified) returns a 2 bit number representing both empty (bit 1) & - full (bit 0) tests related to the FIFO buffer specified using the ``tx`` parameter. + full (bit 0) tests related to the FIFO buffer specified using the ``about_tx`` parameter. :returns: - A `bool` answer to the question: "Is the [TX/RX]:[`True`/`False`] FIFO buffer [empty/full]:[`True`/`False`]? From 82f7cebce7b393defd954413fe85fcd078a2bd73 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Tue, 15 Sep 2020 17:11:01 -0700 Subject: [PATCH 057/127] (+) carrier wave funcs; example code in API docs --- circuitpython_nrf24l01/rf24.py | 22 ++++++-- circuitpython_nrf24l01/rf24_lite.py | 78 ++++++++++++++++++++--------- docs/api.rst | 36 ++++++++++++- 3 files changed, 108 insertions(+), 28 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index d861994..bb215da 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -192,7 +192,7 @@ def close_rx_pipe(self, pipe_number): """This function is used to close a specific data pipe from OTA (over the air) RX transmissions.""" if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0,5]") + raise ValueError("pipe number must be in range [0, 5]") self._open_pipes = self._reg_read(OPEN_PIPES) if self._open_pipes & (1 << pipe_number): self._open_pipes = self._open_pipes & ~(1 << pipe_number) @@ -213,8 +213,7 @@ def open_rx_pipe(self, pipe_number, address): else: self._pipes[pipe_number] = address[0] self._reg_write(RX_ADDR_P0 + pipe_number, address[0]) - self._open_pipes = self._reg_read(OPEN_PIPES) - self._open_pipes = self._open_pipes | (1 << pipe_number) + self._open_pipes = self._reg_read(OPEN_PIPES) | (1 << pipe_number) self._reg_write(OPEN_PIPES, self._open_pipes) else: raise ValueError("address length cannot be 0") @@ -752,3 +751,20 @@ def address(self, index=-1): if index <= 1: return self._pipes[index] return bytes([self._pipes[index]]) + self._pipes[1][1:] + + def start_carrier_wave(self): + """Starts a continuous carrier wave test.""" + self.power = 0 + self.ce_pin.value = 0 + self.listen = 0 + self._rf_setup |= 0x90 + self._reg_write(RF_PA_RATE, self._rf_setup) + self.power = 1 + self.ce_pin.value = 1 + + def stop_carrier_wave(self): + """Stops a continuous carrier wave test.""" + self.ce_pin.value = 0 + self.power = 0 + self._rf_setup &= ~0x90 + self._reg_write(RF_PA_RATE, self._rf_setup) diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index 48ba261..e15132c 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -76,14 +76,26 @@ def _reg_write(self, reg, value=None): # pylint: enable=no-member + @property + def address_length(self): + return self._reg_read(0x03) + 2 + + @address_length.setter + def address_length(self, length): + if 3 <= length <= 5: + self._reg_write(0x03, length - 2) + else: + raise ValueError("address length can only be set in range [3, 5] bytes") + def open_tx_pipe(self, address): - self._reg_write_bytes(0x0A, address) - self._reg_write(2, self._reg_read(2) | 1) + if self.arc: + self._reg_write_bytes(0x0a, address) + self._reg_write(2, self._reg_read(2) | 1) self._reg_write_bytes(0x10, address) def close_rx_pipe(self, pipe_number): if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe_number must be in range [0, 5]") + raise ValueError("pipe number must be in range [0, 5]") open_pipes = self._reg_read(2) if open_pipes & (1 << pipe_number): self._reg_write(2, open_pipes & ~(1 << pipe_number)) @@ -91,49 +103,52 @@ def close_rx_pipe(self, pipe_number): def open_rx_pipe(self, pipe_number, address): if pipe_number < 0 or pipe_number > 5: raise ValueError("pipe number must be in range [0, 5]") - if pipe_number < 2: - if not pipe_number: - self._pipe0_read_addr = address - self._reg_write_bytes(0x0A + pipe_number, address) + if address: + if pipe_number < 2: + if not pipe_number: + self._pipe0_read_addr = address + self._reg_write_bytes(0x0a + pipe_number, address) + else: + self._reg_write(0x0a + pipe_number, address[0]) + self._reg_write(2, self._reg_read(2) | (1 << pipe_number)) else: - self._reg_write(0x0A + pipe_number, address[0]) - self._reg_write(2, self._reg_read(2) | (1 << pipe_number)) + raise ValueError("address length cannot be 0") @property def listen(self): - return (self._reg_read(0) & 3) == 3 + return self.power and bool(self._reg_read(0) & 1) @listen.setter def listen(self, is_rx): assert isinstance(is_rx, (bool, int)) - if self.listen != bool(is_rx): + if self.listen != is_rx: if self.ce_pin.value: self.ce_pin.value = 0 if is_rx: if self._pipe0_read_addr is not None: - self._reg_write_bytes(0x0A, self._pipe0_read_addr) - self._reg_write(0, self._reg_read(0) & 0xFC | 3) - time.sleep(0.00015) + self._reg_write_bytes(0x0a, self._pipe0_read_addr) + self._reg_write(0, (self._reg_read(0) & 0xFC) | 3) + time.sleep(0.00015) # mandatory wait to power up radio self.flush_rx() self.clear_status_flags(True, False, False) - self.ce_pin.value = 1 + self.ce_pin.value = 1 # mandatory pulse is > 130 µs time.sleep(0.00013) else: self._reg_write(0, self._reg_read(0) & 0xFE) time.sleep(0.00016) def any(self): - if self._reg_read(0x1D) & 4 and self.irq_dr: + if self._reg_read(0x1D) & 4 and self.pipe is not None: return self._reg_read(0x60) - if self.irq_dr: + if self.pipe is not None: return self._reg_read(0x11 + self.pipe) return 0 def recv(self): - pl_wid = self.any() - if not pl_wid: + curr_pl_size = self.any() + if not curr_pl_size: return None - result = self._reg_read_bytes(0x61, pl_wid) + result = self._reg_read_bytes(0x61, curr_pl_size) self.clear_status_flags(True, False, False) return result @@ -145,7 +160,8 @@ def send(self, buf, ask_no_ack=False, force_retry=0): result.append(self.send(b, ask_no_ack, force_retry)) return result self.flush_tx() - self.flush_rx() + if self.pipe is not None: + self.flush_rx() self.write(buf, ask_no_ack) time.sleep(0.00001) self.ce_pin.value = 0 @@ -157,7 +173,7 @@ def send(self, buf, ask_no_ack=False, force_retry=0): result = self.resend() if result is None or result: break - if not ask_no_ack and self._status & 0x60: + if not ask_no_ack and self._status & 0x60 == 0x60: result = self.recv() self.clear_status_flags(False) return result @@ -201,7 +217,7 @@ def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): @property def dynamic_payloads(self): - return bool(self._reg_read(0x1D) & 4) + return bool(self._reg_read(0x1c) and self._reg_read(0x1d) & 4) @dynamic_payloads.setter def dynamic_payloads(self, enable): @@ -334,7 +350,7 @@ def resend(self): while not self._status & 0x30: self.update() result = self.irq_ds - if self._reg_read(0x1D) & 2 and self.irq_dr: + if self._status & 0x60 == 0x60: result = self.recv() self.clear_status_flags(False) return result @@ -362,3 +378,17 @@ def flush_rx(self): def flush_tx(self): self._reg_write(0xE1) + + def start_carrier_wave(self): + self.power = 0 + self.ce_pin.value = 0 + self.listen = 0 + self._reg_write(6, self._reg_read(6) | 0x90) + self.power = 1 + self.ce_pin.value = 1 + time.sleep(0.00028) + + def stop_carrier_wave(self): + self.ce_pin.value = 0 + self.power = 0 + self._reg_write(6, self._reg_read(6) & ~0x90) diff --git a/docs/api.rst b/docs/api.rst index b1cea74..4c31380 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -89,7 +89,6 @@ version: * `what_happened()` * `fifo()` * `pa_level` (this is always set to 0 dBm) - * `address_length` (this is always set to 5 bytes) * `read_ack()` (deprecated on next major release anyway; use `recv()` instead) * `crc` (always using 2 bytes encoding scheme) * `auto_ack` (this is always on for all pipes). Pass ``ask_no_ack`` parameter as `True` to @@ -890,3 +889,38 @@ address() :param int index: the number of the data pipe whose address is to be returned. Defaults to ``-1``. A valid index ranges [0,5] for RX addresses or any negative `int` for the TX address. Otherwise an `IndexError` is thown. + +start_carrier_wave() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.start_carrier_wave + + This is a basic test of the nRF24L01's TX output. It is a commonly required + test for telecommunication regulations. Calling this function may introduce + interference with other transceivers that use frequencies in range [2.4, + 2.525] GHz. To verify that this test is working properly, use the following + code on a seperate nRF24L01 transceiver: + + .. code-block:: python + + # declare objects for SPI bus and CSN pin and CE pin + nrf. = RF24(spi, csn, ce) + # set nrf.pa_level, nrf.channel, & nrf.data_rate values to + # match the corresponding attributes on the device that is + # transmitting the carrier wave + nrf.listen = True + if nrf.rpd: + print("carrier wave detected") + + The `pa_level`, `channel` & `data_rate` attributes are vital factors to + the success of this test. See also the `rpd` attribute. + +stop_carrier_wave() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.stop_carrier_wave + + See `start_carrier_wave()` for more details. + + .. note:: + Calling this function puts the nRF24L01 to sleep (AKA power down mode). \ No newline at end of file From db43af0b62f177ebb2d6a9ca17fcdde90107ffac Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Tue, 15 Sep 2020 17:12:04 -0700 Subject: [PATCH 058/127] pleaseing black --- circuitpython_nrf24l01/rf24_lite.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index e15132c..a47eb20 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -89,7 +89,7 @@ def address_length(self, length): def open_tx_pipe(self, address): if self.arc: - self._reg_write_bytes(0x0a, address) + self._reg_write_bytes(0x0A, address) self._reg_write(2, self._reg_read(2) | 1) self._reg_write_bytes(0x10, address) @@ -107,9 +107,9 @@ def open_rx_pipe(self, pipe_number, address): if pipe_number < 2: if not pipe_number: self._pipe0_read_addr = address - self._reg_write_bytes(0x0a + pipe_number, address) + self._reg_write_bytes(0x0A + pipe_number, address) else: - self._reg_write(0x0a + pipe_number, address[0]) + self._reg_write(0x0A + pipe_number, address[0]) self._reg_write(2, self._reg_read(2) | (1 << pipe_number)) else: raise ValueError("address length cannot be 0") @@ -126,7 +126,7 @@ def listen(self, is_rx): self.ce_pin.value = 0 if is_rx: if self._pipe0_read_addr is not None: - self._reg_write_bytes(0x0a, self._pipe0_read_addr) + self._reg_write_bytes(0x0A, self._pipe0_read_addr) self._reg_write(0, (self._reg_read(0) & 0xFC) | 3) time.sleep(0.00015) # mandatory wait to power up radio self.flush_rx() @@ -217,7 +217,7 @@ def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): @property def dynamic_payloads(self): - return bool(self._reg_read(0x1c) and self._reg_read(0x1d) & 4) + return bool(self._reg_read(0x1C) and self._reg_read(0x1D) & 4) @dynamic_payloads.setter def dynamic_payloads(self, enable): From ffd72293196ee47c7b8f1840b41ed960dc1cc78f Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Tue, 15 Sep 2020 17:26:25 -0700 Subject: [PATCH 059/127] move rpd in docs closer to carrier wave funcs --- docs/api.rst | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 4c31380..5df0b2d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -714,25 +714,6 @@ tx_full - `False` for TX FIFO buffer is not full. This doesn't mean the TX FIFO buffer is empty. -rpd -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.rpd - - The RPD flag is triggered in the following cases: - - 1. During RX mode (`listen` = `True`) and an arbitrary RF transmission with a gain above - -64 dBm threshold is/was present. - 2. When a packet is received (instigated by the nRF24L01 used to detect/"listen" for - incoming packets). - - .. note:: See also - `section 6.4 of the Specification Sheet concerning the RPD flag - `_. Ambient temperature - affects the -64 dBm threshold. The latching of this flag happens differently under certain - conditions. - update() ****************************** @@ -890,6 +871,25 @@ address() ``-1``. A valid index ranges [0,5] for RX addresses or any negative `int` for the TX address. Otherwise an `IndexError` is thown. +rpd +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.rpd + + The RPD flag is triggered in the following cases: + + 1. During RX mode (`listen` = `True`) and an arbitrary RF transmission with a gain above + -64 dBm threshold is/was present. + 2. When a packet is received (instigated by the nRF24L01 used to detect/"listen" for + incoming packets). + + .. note:: See also + `section 6.4 of the Specification Sheet concerning the RPD flag + `_. Ambient temperature + affects the -64 dBm threshold. The latching of this flag happens differently under certain + conditions. + start_carrier_wave() ****************************** @@ -923,4 +923,4 @@ stop_carrier_wave() See `start_carrier_wave()` for more details. .. note:: - Calling this function puts the nRF24L01 to sleep (AKA power down mode). \ No newline at end of file + Calling this function puts the nRF24L01 to sleep (AKA power down mode). From aab82e881da374872d98317ee6dc9488eb91192f Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Tue, 15 Sep 2020 18:52:52 -0700 Subject: [PATCH 060/127] (+) info about SI24R1 in readme --- README.rst | 399 +++++++++++++++++++++++++++-------------------------- 1 file changed, 202 insertions(+), 197 deletions(-) diff --git a/README.rst b/README.rst index 2cdd425..0bab6a2 100644 --- a/README.rst +++ b/README.rst @@ -1,197 +1,202 @@ - - -.. image:: https://readthedocs.org/projects/circuitpython-nrf24l01/badge/?version=stable - :target: https://circuitpython-nrf24l01.readthedocs.io/en/stable/ - :alt: Documentation Status - -.. image:: https://github.com/2bndy5/CircuitPython_nRF24L01/workflows/Build%20CI/badge.svg - :target: https://github.com/2bndy5/CircuitPython_nRF24L01/actions?query=workflow%3A%22Build+CI%22 - :alt: Build Status - -.. image:: https://img.shields.io/github/commits-since/2bndy5/CircuitPython_nRF24L01/latest?&style=plastic - :alt: GitHub commits since latest release (by date) - :target: https://github.com/2bndy5/CircuitPython_nRF24L01/commits/master - -.. image:: https://img.shields.io/pypi/v/circuitpython-nrf24l01.svg - :alt: latest version on PyPI - :target: https://pypi.python.org/pypi/circuitpython-nrf24l01 - -.. image:: https://pepy.tech/badge/circuitpython-nrf24l01?label=pypi%20downloads&logo=python - :alt: Total PyPI downloads - :target: https://pepy.tech/project/circuitpython-nrf24l01 - -.. image:: https://img.shields.io/github/downloads/2bndy5/CircuitPython_nRF24L01/total?color=success&label=Downloads&logo=github&style=plastic - :alt: GitHub All Release Downloads - :target: https://github.com/2bndy5/CircuitPython_nRF24L01/releases - -Introduction ------------- - -Circuitpython driver library for the nRF24L01 transceiver - -CircuitPython port of the nRF24L01 library from Micropython. -Original work by Damien P. George & Peter Hinch can be found `here -`_ - -The Micropython source has been rewritten to expose all the nRF24L01's features and for -compatibilty with the Raspberry Pi and other Circuitpython compatible devices. Modified by Brendan Doherty, Rhys Thomas - -* Author(s): Damien P. George, Peter Hinch, Rhys Thomas, Brendan Doherty - -Features currently supported ----------------------------- - -* change the addresses' length (can be 3 to 5 bytes long) -* dynamically sized payloads (max 32 bytes each) or statically sized payloads -* automatic responding acknowledgment (ACK) for verifying transmission success -* custom acknowledgment (ACK) payloads for bi-directional communication -* flag a single payload for no acknowledgment (ACK) from the receiving nRF24L01 -* "re-use the same payload" feature (for manually re-transmitting failed transmissions that remain in the buffer) -* multiple payload transmissions with one function call (MUST read documentation on the `send()` function) -* context manager compatible for easily switching between different radio configurations using "with" statements (not available in ``rf24_lite.py`` variant for M0 based boards) -* configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or failed transmissions (these 3 flags control the 1 IRQ pin). There's also virtual representations of these interrupt flags available (see `irq_dr`, `irq_ds`, `irq_df` attributes) -* invoke sleep mode (AKA power down mode) for ultra-low current consumption -* cyclic redundancy checking (CRC) up to 2 bytes long -* adjust the nRF24L01's builtin automatic re-transmit feature's parameters (`arc`: number of attempts, `ard`: delay between attempts) -* adjust the nRF24L01's frequency channel (2.4-2.525 GHz) -* adjust the nRF24L01's power amplifier level (0, -6, -12, or -18 dBm) -* adjust the nRF24L01's RF data rate (250Kbps is buggy due to hardware design, but 1Mbps and 2Mbps are reliable) -* a nRF24L01 driven by this library can communicate with a nRF24L01 on an Arduino driven by the `TMRh20 RF24 library `_. See the nrf24l01_2arduino_handling_data.py code in the `examples folder of this library's repository `_ - -Features currently unsupported -------------------------------- - -* as of yet, no [intended] implementation for Multiceiver mode (up to 6 TX nRF24L01 "talking" to 1 RX nRF24L01 simultaneously). Although this might be acheived easily using the "automatic retry delay" (`ard`) and "automatic retry count" (`arc`) attributes set accordingly (varyingly high -- this has not been tested). - -Dependencies -============= -This driver depends on: - -* `Adafruit CircuitPython `_ -* `Bus Device `_ - -Please ensure all dependencies are available on the CircuitPython filesystem. -This is easily achieved by downloading -`the Adafruit library and driver bundle `_. - -Installing from PyPI -===================== - -On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from -PyPI `_. To install for current user: - -.. code-block:: shell - - pip3 install circuitpython-nrf24l01 - -To install system-wide (this may be required in some cases): - -.. code-block:: shell - - sudo pip3 install circuitpython-nrf24l01 - -To install in a virtual environment in your current project: - -.. code-block:: shell - - mkdir project-name && cd project-name - python3 -m venv .env - source .env/bin/activate - pip3 install circuitpython-nrf24l01 - -Pinout -====== -.. image:: https://lastminuteengineers.com/wp-content/uploads/2018/07/Pinout-nRF24L01-Wireless-Transceiver-Module.png - :target: https://lastminuteengineers.com/nrf24l01-arduino-wireless-communication/#nrf24l01-transceiver-module-pinout - -The nRF24L01 is controlled through SPI so there are 3 pins (SCK, MOSI, & MISO) that can only be connected to their counterparts on the MCU (microcontroller unit). The other 2 essential pins (CE & CSN) can be connected to any digital output pins. Lastly, the only optional pin on the nRf24L01 GPIOs is the IRQ (interrupt; a digital output that's active when low) pin and is only connected to the MCU via a digital input pin during the interrupt example. The following pinout is used in the example codes of this library's `example directory `_. - -.. csv-table:: - :header: nRF2401, "Raspberry Pi", "ItsyBitsy M4" - - GND, GND, GND - VCC, 3V, 3.3V - CE, GPIO4, D4 - CSN, GPIO5, D5 - SCK, "GPIO11 (SCK)", SCK - MOSI, "GPIO10 (MOSI)", MOSI - MISO, "GPIO9 (MISO)", MISO - IRQ, GPIO12, D12 - -.. tip:: User reports and personal experiences have improved results if there is a capacitor of 100 mirofarads [+ another optional 0.1 microfarads capacitor for added stability] connected in parrallel to the VCC and GND pins. - -Using The Examples -================== - -See `examples `_ for testing certain features of this the library. The examples were developed and tested on both Raspberry Pi and ItsyBitsy M4. Pins have been hard coded in the examples for the corresponding device, so please adjust these accordingly to your circuitpython device if necessary. - -To run the simple example, navigate to this repository's "examples" folder in the terminal. If you're working with a CircuitPython device (not a Raspberry Pi), copy the file named "nrf24l01_simple_test.py" from this repository's "examples" folder to the root directory of your CircuitPython device's CIRCUITPY drive. Now you're ready to open a python REPR and run the following commands: - -.. code-block:: python - - >>> from nrf24l01_simple_test import * - nRF24L01 Simple test. - Run slave() on receiver - Run master() on transmitter - >>> master() - Sending: 5 as struct: b'\x05\x00\x00\x00' - send() successful - Transmission took 36.0 ms - Sending: 4 as struct: b'\x04\x00\x00\x00' - send() successful - Transmission took 28.0 ms - Sending: 3 as struct: b'\x03\x00\x00\x00' - send() successful - Transmission took 24.0 ms - - - -About the nRF24L01 -================== - -More finite details about the nRF24L01 are available from the datasheet (referenced here in the documentation as the `nRF24L01+ Specification Sheet `_) - -Future Project Ideas/Additions -============================== - - The following are only ideas; they are not currently supported by this circuitpython library. - - * `There's a few blog posts by Nerd Ralph demonstrating how to use the nRF24L01 via 2 or 3 pins `_ (uses custom bitbanging SPI functions and an external circuit involving a resistor and a capacitor) - * network linking layer, maybe something like `TMRh20's RF24Network `_ - * add a fake BLE module for sending BLE beacon advertisments from the nRF24L01 as outlined by `Dmitry Grinberg in his write-up (including C source code) `_. We've started developing this, but fell short of success in `the BLEfake branch of this library's repository `_ - * implement the Gazelle-based protocol used by the BBC micro-bit (`makecode.com's radio blocks `_). - -Where do I get 1? -================= - -See the store links on the sidebar or just google "nRF24L01". It is worth noting that you generally don't want to buy just 1 as you need 2 for testing -- 1 to send & 1 to receive and vise versa. This library has been tested on a cheaply bought 10 pack from Amazon.com using a highly recommended capacitor (100 µF) on the power pins. Don't get lost on Amazon or eBay! There are other wireless transceivers that are NOT compatible with this library. For instance, the esp8266-01 (also sold in packs) is NOT compatible with this library, but looks very similar to the nRF24L01(+) and could lead to an accidental purchase. Beware there are counterfeit nRF24L01 modules out there. To determine if your purchase is a counterfeit, please `read this article `_. - -Contributing -============ - -Contributions are welcome! Please read our `Code of Conduct -`_ -before contributing to help this project stay welcoming. To contribute, all you need to do is fork `this repository `_, develop your idea(s) and submit a pull request when stable. To initiate a discussion of idea(s), you need only open an issue on the aforementioned repository (doesn't have to be a bug report). - -Sphinx documentation ------------------------ - -Sphinx is used to build the documentation based on rST files and comments in the code. First, -install dependencies (feel free to reuse the virtual environment from `above `_): - -.. code-block:: shell - - python3 -m venv .env - source .env/bin/activate - pip install Sphinx sphinx-rtd-theme - -Now, once you have the virtual environment activated: - -.. code-block:: shell - - cd docs - sphinx-build -E -W -b html . _build/html - -This will output the documentation to ``docs/_build/html``. Open the index.html in your browser to -view them. It will also (due to -W) error out on any warning like the Github action, Build CI, -does. This is a good way to locally verify it will pass. + + +.. image:: https://readthedocs.org/projects/circuitpython-nrf24l01/badge/?version=stable + :target: https://circuitpython-nrf24l01.readthedocs.io/en/stable/ + :alt: Documentation Status + +.. image:: https://github.com/2bndy5/CircuitPython_nRF24L01/workflows/Build%20CI/badge.svg + :target: https://github.com/2bndy5/CircuitPython_nRF24L01/actions?query=workflow%3A%22Build+CI%22 + :alt: Build Status + +.. image:: https://img.shields.io/github/commits-since/2bndy5/CircuitPython_nRF24L01/latest?&style=plastic + :alt: GitHub commits since latest release (by date) + :target: https://github.com/2bndy5/CircuitPython_nRF24L01/commits/master + +.. image:: https://img.shields.io/pypi/v/circuitpython-nrf24l01.svg + :alt: latest version on PyPI + :target: https://pypi.python.org/pypi/circuitpython-nrf24l01 + +.. image:: https://pepy.tech/badge/circuitpython-nrf24l01?label=pypi%20downloads&logo=python + :alt: Total PyPI downloads + :target: https://pepy.tech/project/circuitpython-nrf24l01 + +.. image:: https://img.shields.io/github/downloads/2bndy5/CircuitPython_nRF24L01/total?color=success&label=Downloads&logo=github&style=plastic + :alt: GitHub All Release Downloads + :target: https://github.com/2bndy5/CircuitPython_nRF24L01/releases + +Introduction +------------ + +Circuitpython driver library for the nRF24L01 transceiver + +CircuitPython port of the nRF24L01 library from Micropython. +Original work by Damien P. George & Peter Hinch can be found `here +`_ + +The Micropython source has been rewritten to expose all the nRF24L01's features and for +compatibilty with the Raspberry Pi and other Circuitpython compatible devices. Modified by Brendan Doherty, Rhys Thomas + +* Author(s): Damien P. George, Peter Hinch, Rhys Thomas, Brendan Doherty + +Features currently supported +---------------------------- + +* change the addresses' length (can be 3 to 5 bytes long) +* dynamically sized payloads (max 32 bytes each) or statically sized payloads +* automatic responding acknowledgment (ACK) for verifying transmission success +* custom acknowledgment (ACK) payloads for bi-directional communication +* flag a single payload for no acknowledgment (ACK) from the receiving nRF24L01 +* "re-use the same payload" feature (for manually re-transmitting failed transmissions that remain in the buffer) +* multiple payload transmissions with one function call (MUST read documentation on the `send()` function) +* context manager compatible for easily switching between different radio configurations using "with" statements (not available in ``rf24_lite.py`` variant for M0 based boards) +* configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or failed transmissions (these 3 flags control the 1 IRQ pin). There's also virtual representations of these interrupt flags available (see `irq_dr`, `irq_ds`, `irq_df` attributes) +* invoke sleep mode (AKA power down mode) for ultra-low current consumption +* cyclic redundancy checking (CRC) up to 2 bytes long +* adjust the nRF24L01's builtin automatic re-transmit feature's parameters (`arc`: number of attempts, `ard`: delay between attempts) +* adjust the nRF24L01's frequency channel (2.4-2.525 GHz) +* adjust the nRF24L01's power amplifier level (0, -6, -12, or -18 dBm) +* adjust the nRF24L01's RF data rate (250Kbps is buggy due to hardware design, but 1Mbps and 2Mbps are reliable) +* a nRF24L01 driven by this library can communicate with a nRF24L01 on an Arduino driven by the `TMRh20 RF24 library `_. See the nrf24l01_2arduino_handling_data.py code in the `examples folder of this library's repository `_ + +Features currently unsupported +------------------------------- + +* as of yet, no [intended] implementation for Multiceiver mode (up to 6 TX nRF24L01 "talking" to 1 RX nRF24L01 simultaneously). Although this might be acheived easily using the "automatic retry delay" (`ard`) and "automatic retry count" (`arc`) attributes set accordingly (varyingly high -- this has not been tested). + +Dependencies +============= +This driver depends on: + +* `Adafruit CircuitPython `_ +* `Bus Device `_ + +Please ensure all dependencies are available on the CircuitPython filesystem. +This is easily achieved by downloading +`the Adafruit library and driver bundle `_. + +Installing from PyPI +===================== + +On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from +PyPI `_. To install for current user: + +.. code-block:: shell + + pip3 install circuitpython-nrf24l01 + +To install system-wide (this may be required in some cases): + +.. code-block:: shell + + sudo pip3 install circuitpython-nrf24l01 + +To install in a virtual environment in your current project: + +.. code-block:: shell + + mkdir project-name && cd project-name + python3 -m venv .env + source .env/bin/activate + pip3 install circuitpython-nrf24l01 + +Pinout +====== +.. image:: https://lastminuteengineers.com/wp-content/uploads/2018/07/Pinout-nRF24L01-Wireless-Transceiver-Module.png + :target: https://lastminuteengineers.com/nrf24l01-arduino-wireless-communication/#nrf24l01-transceiver-module-pinout + +The nRF24L01 is controlled through SPI so there are 3 pins (SCK, MOSI, & MISO) that can only be connected to their counterparts on the MCU (microcontroller unit). The other 2 essential pins (CE & CSN) can be connected to any digital output pins. Lastly, the only optional pin on the nRf24L01 GPIOs is the IRQ (interrupt; a digital output that's active when low) pin and is only connected to the MCU via a digital input pin during the interrupt example. The following pinout is used in the example codes of this library's `example directory `_. + +.. csv-table:: + :header: nRF2401, "Raspberry Pi", "ItsyBitsy M4" + + GND, GND, GND + VCC, 3V, 3.3V + CE, GPIO4, D4 + CSN, GPIO5, D5 + SCK, "GPIO11 (SCK)", SCK + MOSI, "GPIO10 (MOSI)", MOSI + MISO, "GPIO9 (MISO)", MISO + IRQ, GPIO12, D12 + +.. tip:: User reports and personal experiences have improved results if there is a capacitor of 100 mirofarads [+ another optional 0.1 microfarads capacitor for added stability] connected in parrallel to the VCC and GND pins. + +Using The Examples +================== + +See `examples `_ for testing certain features of this the library. The examples were developed and tested on both Raspberry Pi and ItsyBitsy M4. Pins have been hard coded in the examples for the corresponding device, so please adjust these accordingly to your circuitpython device if necessary. + +To run the simple example, navigate to this repository's "examples" folder in the terminal. If you're working with a CircuitPython device (not a Raspberry Pi), copy the file named "nrf24l01_simple_test.py" from this repository's "examples" folder to the root directory of your CircuitPython device's CIRCUITPY drive. Now you're ready to open a python REPR and run the following commands: + +.. code-block:: python + + >>> from nrf24l01_simple_test import * + nRF24L01 Simple test. + Run slave() on receiver + Run master() on transmitter + >>> master() + Sending: 5 as struct: b'\x05\x00\x00\x00' + send() successful + Transmission took 36.0 ms + Sending: 4 as struct: b'\x04\x00\x00\x00' + send() successful + Transmission took 28.0 ms + Sending: 3 as struct: b'\x03\x00\x00\x00' + send() successful + Transmission took 24.0 ms + + + +About the nRF24L01 +================== + +More finite details about the nRF24L01 are available from the datasheet (referenced here in the documentation as the `nRF24L01+ Specification Sheet `_) + +Future Project Ideas/Additions +============================== + + The following are only ideas; they are not currently supported by this circuitpython library. + + * `There's a few blog posts by Nerd Ralph demonstrating how to use the nRF24L01 via 2 or 3 pins `_ (uses custom bitbanging SPI functions and an external circuit involving a resistor and a capacitor) + * network linking layer, maybe something like `TMRh20's RF24Network `_ + * add a fake BLE module for sending BLE beacon advertisments from the nRF24L01 as outlined by `Dmitry Grinberg in his write-up (including C source code) `_. We've started developing this, but fell short of success in `the BLEfake branch of this library's repository `_ + * implement the Gazelle-based protocol used by the BBC micro-bit (`makecode.com's radio blocks `_). + +Where do I get 1? +================= + +See the store links on the sidebar or just google "nRF24L01". It is worth noting that you generally don't want to buy just 1 as you need 2 for testing -- 1 to send & 1 to receive and vise versa. This library has been tested on a cheaply bought 10 pack from Amazon.com using a highly recommended capacitor (100 µF) on the power pins. Don't get lost on Amazon or eBay! There are other wireless transceivers that are NOT compatible with this library. For instance, the esp8266-01 (also sold in packs) is NOT compatible with this library, but looks very similar to the nRF24L01(+) and could lead to an accidental purchase. + +SI24R1 chip (an nRF24L01(+) clone) +================================== + +Beware there are nRF24L01 module clones out there. To determine if your purchase is a counterfeit, please `read this article `_. The most notable clone is the `SI24R1 `_. The `SI24R1 datasheet `_ I could find is not in english. Troubleshooting the SI24R1 may require `replacing the onboard antennae with a wire `_. Furthermore, the SI24R1 has different power amplifier settings as noted in the `RF_PWR section (bits 0 through 2) of the RF_SETUP register (hex address 6) of the datasheet `_. Please note that this library does not directly support the SI24R1 chip as there is no way for the library to differentiate between an actual nRF24L01(+) and a SI24R1. + +Contributing +============ + +Contributions are welcome! Please read our `Code of Conduct +`_ +before contributing to help this project stay welcoming. To contribute, all you need to do is fork `this repository `_, develop your idea(s) and submit a pull request when stable. To initiate a discussion of idea(s), you need only open an issue on the aforementioned repository (doesn't have to be a bug report). + +Sphinx documentation +----------------------- + +Sphinx is used to build the documentation based on rST files and comments in the code. First, +install dependencies (feel free to reuse the virtual environment from `above `_): + +.. code-block:: shell + + python3 -m venv .env + source .env/bin/activate + pip install Sphinx sphinx-rtd-theme + +Now, once you have the virtual environment activated: + +.. code-block:: shell + + cd docs + sphinx-build -E -W -b html . _build/html + +This will output the documentation to ``docs/_build/html``. Open the index.html in your browser to +view them. It will also (due to -W) error out on any warning like the Github action, Build CI, +does. This is a good way to locally verify it will pass. From 45cfb54cf8299db3b25151ee1740517b185b472b Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Wed, 16 Sep 2020 03:29:41 -0700 Subject: [PATCH 061/127] cleanup; lite version works; 2arduino = no joy --- circuitpython_nrf24l01/rf24.py | 217 +++++++++++++--------------- circuitpython_nrf24l01/rf24_lite.py | 181 +++++++++++------------ docs/api.rst | 37 +++-- 3 files changed, 208 insertions(+), 227 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index bb215da..0454125 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -31,17 +31,19 @@ except ImportError: from adafruit_bus_device.spi_device import SPIDevice -# nRF24L01 registers CONFIGURE = const(0x00) # IRQ, CRC, PWR control & RX/TX roles AUTO_ACK = const(0x01) # auto-ACK OPEN_PIPES = const(0x02) # open/close pipes SETUP_RETR = const(0x04) # auto-retry count & delay RF_PA_RATE = const(0x06) # RF Power Amplifier & Data Rate RX_ADDR_P0 = const(0x0A) # RX pipe addresses == [0, 5]:[0x0a, 0x0f] +TX_ADDRESS = const(0x10) # Address used for TX transmissions RX_PL_LENG = const(0x11) # RX payload widths on pipes == [0, 5]:[0x11, 0x16] DYN_PL_LEN = const(0x1C) # dynamic payloads TX_FEATURE = const(0x1D) # TX features dynamic payloads, ACK payloads, NO_ACK -TX_ADDRESS = const(0x10) # Address used for TX transmissions +CSN_DELAY = 0.005 +"""The delay time (in seconds) used to let the CSN pin settle, +allowing a clean SPI transaction.""" class RF24: @@ -57,10 +59,9 @@ def __init__(self, spi, csn, ce): # power up in TX mode self._config = 0x0E self._reg_write(CONFIGURE, self._config) - if self._reg_read(CONFIGURE) & 3 == 2: - self.power = False - else: # hardware presence check NOT passed + if self._reg_read(CONFIGURE) & 3 != 2: raise RuntimeError("nRF24L01 Hardware not responding") + self.power = False # init shadow copy of RX addresses for all pipes for context manager self._pipes = [bytearray(5), bytearray(5), 0xC3, 0xC4, 0xC5, 0xC6] # _open_pipes attribute reflects only RX state on each pipe @@ -125,7 +126,7 @@ def _reg_read(self, reg): out_buf = bytes([reg, 0]) in_buf = bytearray([0, 0]) with self._spi as spi: - time.sleep(0.005) + time.sleep(CSN_DELAY) spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] return in_buf[1] @@ -134,7 +135,7 @@ def _reg_read_bytes(self, reg, buf_len=5): in_buf = bytearray(buf_len + 1) out_buf = bytes([reg]) + b"\x00" * buf_len with self._spi as spi: - time.sleep(0.005) + time.sleep(CSN_DELAY) spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] return in_buf[1:] @@ -143,7 +144,7 @@ def _reg_write_bytes(self, reg, out_buf): out_buf = bytes([0x20 | reg]) + out_buf in_buf = bytearray(len(out_buf)) with self._spi as spi: - time.sleep(0.005) + time.sleep(CSN_DELAY) spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] @@ -155,7 +156,7 @@ def _reg_write(self, reg, value=None): out_buf = bytes([0x20 | reg, value]) in_buf = bytearray(len(out_buf)) with self._spi as spi: - time.sleep(0.005) + time.sleep(CSN_DELAY) spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] @@ -169,11 +170,10 @@ def address_length(self): @address_length.setter def address_length(self, length): - if 3 <= length <= 5: - self._addr_len = int(length) - self._reg_write(0x03, length - 2) - else: - raise ValueError("address length can only be set in range [3, 5] bytes") + if not 3 <= length <= 5: + raise ValueError("address_length can only be set in range [3, 5] bytes") + self._addr_len = int(length) + self._reg_write(0x03, length - 2) def open_tx_pipe(self, address): """This function is used to open a data pipe for OTA (over the air) @@ -201,22 +201,21 @@ def close_rx_pipe(self, pipe_number): def open_rx_pipe(self, pipe_number, address): """This function is used to open a specific data pipe for OTA (over the air) RX transmissions.""" - if pipe_number < 0 or pipe_number > 5: + if not 0 <= pipe_number <= 5: raise ValueError("pipe number must be in range [0, 5]") - if address: - if pipe_number < 2: - if not pipe_number: - self._pipe0_read_addr = bytearray(address) - for i, val in enumerate(address): - self._pipes[pipe_number][i] = val - self._reg_write_bytes(RX_ADDR_P0 + pipe_number, address) - else: - self._pipes[pipe_number] = address[0] - self._reg_write(RX_ADDR_P0 + pipe_number, address[0]) - self._open_pipes = self._reg_read(OPEN_PIPES) | (1 << pipe_number) - self._reg_write(OPEN_PIPES, self._open_pipes) - else: + if not address: raise ValueError("address length cannot be 0") + if pipe_number < 2: + if not pipe_number: + self._pipe0_read_addr = address + for i, val in enumerate(address): + self._pipes[pipe_number][i] = val + self._reg_write_bytes(RX_ADDR_P0 + pipe_number, address) + else: + self._pipes[pipe_number] = address[0] + self._reg_write(RX_ADDR_P0 + pipe_number, address[0]) + self._open_pipes = self._reg_read(OPEN_PIPES) | (1 << pipe_number) + self._reg_write(OPEN_PIPES, self._open_pipes) @property def listen(self): @@ -225,13 +224,12 @@ def listen(self): @listen.setter def listen(self, is_rx): - assert isinstance(is_rx, (bool, int)) - if self.listen != is_rx: - if self.ce_pin.value: - self.ce_pin.value = 0 + if self.listen != bool(is_rx): + self.ce_pin.value = 0 if is_rx: if self._pipe0_read_addr is not None: - self._pipes[0] = self._pipe0_read_addr + for i, val in enumerate(self._pipe0_read_addr): + self._pipes[0][i] = val self._reg_write_bytes(RX_ADDR_P0, self._pipe0_read_addr) self._config = (self._config & 0xFC) | 3 self._reg_write(CONFIGURE, self._config) @@ -287,11 +285,26 @@ def send(self, buf, ask_no_ack=False, force_retry=0): result = self.resend() if result is None or result: break - if not ask_no_ack and self._status & 0x60 == 0x60: + if self._status & 0x60 == 0x60: result = self.recv() self.clear_status_flags(False) return result + @property + def tx_full(self): + """An attribute to represent the nRF24L01's status flag signaling + that the TX FIFO buffer is full. (read-only)""" + return bool(self._status & 1) + + @property + def pipe(self): + """The identifying number of the data pipe that received + the next available payload in the RX FIFO buffer. (read only)""" + result = (self._status & 0x0E) >> 1 + if result <= 5: + return result + return None + @property def irq_dr(self): """A `bool` that represents the "Data Ready" interrupted flag. @@ -312,14 +325,13 @@ def irq_df(self): def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): """This clears the interrupt flags in the status register.""" - self._reg_write(0x07, (data_recv << 6) | (data_sent << 5) | (data_fail << 4)) + config = bool(data_recv) << 6 | bool(data_sent) << 5 | bool(data_fail) << 4 + self._reg_write(7, config) def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): """Sets the configuration of the nRF24L01's IRQ (interrupt) pin.""" - self._config = self._reg_read(CONFIGURE) & 0x0F - self._config |= (not bool(data_fail)) << 4 - self._config |= (not bool(data_sent)) << 5 - self._config |= (not bool(data_recv)) << 6 + self._config = (self._reg_read(CONFIGURE) & 0x0F) | (not data_recv) << 6 + self._config |= (not data_fail) << 4 | (not data_sent) << 5 self._reg_write(CONFIGURE, self._config) def what_happened(self, dump_pipes=False): @@ -432,7 +444,7 @@ def dynamic_payloads(self): length feature for each pipe.""" self._dyn_pl = self._reg_read(DYN_PL_LEN) self._features = self._reg_read(TX_FEATURE) - return bool(self._dyn_pl and (self._features & 4)) + return bool(self._dyn_pl) and self._features & 4 == 4 @dynamic_payloads.setter def dynamic_payloads(self, enable): @@ -444,9 +456,7 @@ def dynamic_payloads(self, enable): if i < 6: self._dyn_pl = (self._dyn_pl & ~(1 << i)) | (bool(val) << i) else: - raise ValueError( - "{} is not a valid input for dynamic_payloads".format(enable) - ) + raise ValueError("dynamic_payloads: {} is an invalid input" % enable) if bool(self._features & 4) != (self._dyn_pl & 1): self._features = (self._features & 3) | ((self._dyn_pl & 1) << 2) self._reg_write(TX_FEATURE, self._features) @@ -480,12 +490,10 @@ def arc(self): @arc.setter def arc(self, count): - if 0 <= count <= 15: - if self.arc != count: - self._retry_setup = (self._retry_setup & 0xF0) | count - self._reg_write(SETUP_RETR, self._retry_setup) - else: + if not 0 <= count <= 15: raise ValueError("automatic re-transmit count must in range [0, 15]") + self._retry_setup = (self._retry_setup & 0xF0) | count + self._reg_write(SETUP_RETR, self._retry_setup) @property def ard(self): @@ -497,14 +505,11 @@ def ard(self): return ((self._retry_setup & 0xF0) >> 4) * 250 + 250 @ard.setter - def ard(self, delta_t): - if 250 <= delta_t <= 4000: - self._retry_setup = (self._retry_setup & 15) | ( - int((delta_t - 250) / 250) << 4 - ) - self._reg_write(SETUP_RETR, self._retry_setup) - else: + def ard(self, delta): + if not 250 <= delta <= 4000: raise ValueError("automatic re-transmit delay must be in range [250, 4000]") + self._retry_setup = (self._retry_setup & 15) | int((delta - 250) / 250) << 4 + self._reg_write(SETUP_RETR, self._retry_setup) @property def auto_ack(self): @@ -522,9 +527,9 @@ def auto_ack(self, enable): if i < 6: self._aa = (self._aa & ~(1 << i)) | (bool(val) << i) else: - raise ValueError("{} is not a valid input for auto_ack".format(enable)) + raise ValueError("auto_ack: {} is not a valid input for" % enable) self._reg_write(AUTO_ACK, self._aa) - if self._aa: + if self._aa: # refresh crc data if enabled self._config = self._reg_read(CONFIGURE) @property @@ -539,24 +544,24 @@ def ack(self): @ack.setter def ack(self, enable): - assert isinstance(enable, (bool, int)) if self.ack != bool(enable): - self.auto_ack = 1 - self._dyn_pl = (self._dyn_pl & ~(1)) | 1 + self.auto_ack = (1,) + self._dyn_pl = self._dyn_pl & ~1 | 1 self._reg_write(DYN_PL_LEN, self._dyn_pl) - self._features = (self._features & 5) | (6 if enable else 0) + self._features = self._features & 3 | 4 + self._features = self._features & 5 | bool(enable) << 1 self._reg_write(TX_FEATURE, self._features) def load_ack(self, buf, pipe_number): """This allows the MCU to specify a payload to be allocated into the TX FIFO buffer for use on a specific data pipe.""" if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0, 5]") + raise ValueError("pipe_number must be in range [0, 5]") if not buf or len(buf) > 32: - raise ValueError("payload must have a length in range [1, 32] (in bytes)") + raise ValueError("payload must have a byte length in range [1, 32]") if not bool((self._features & 6) == 6 and ((self._aa & self._dyn_pl) & 1)): self.ack = True - if not self.fifo(True, False): + if not self.tx_full: self._reg_write_bytes(0xA8 | pipe_number, buf) return True return False @@ -570,34 +575,29 @@ def data_rate(self): """This `int` attribute specifies the nRF24L01's frequency data rate for OTA (over the air) transmissions.""" self._rf_setup = self._reg_read(RF_PA_RATE) - if self._rf_setup & 0x28: - if self._rf_setup & 0x28 == 8: - return 2 - return 250 - return 1 + rf_setup = self._rf_setup & 0x28 + return (2 if rf_setup == 8 else 250) if rf_setup else 1 @data_rate.setter def data_rate(self, speed): - if speed in (1, 2, 250): - if self.data_rate != speed: - speed = 0 if speed == 1 else (8 if speed == 2 else 0x20) + if not speed in (1, 2, 250): + raise ValueError("data_rate must be 1 (Mbps), 2 (Mbps), or 250 (kbps)") + if self.data_rate != speed: + speed = 0 if speed == 1 else (0x20 if speed != 2 else 8) self._rf_setup = self._rf_setup & 0xD7 | speed self._reg_write(RF_PA_RATE, self._rf_setup) - else: - raise ValueError("data rate must be 1 (Mbps), 2 (Mbps), or 250 (kbps)") @property def channel(self): """This `int` attribute specifies the nRF24L01's frequency.""" - return self._reg_read(0x05) + return self._reg_read(5) @channel.setter def channel(self, channel): - if 0 <= channel <= 125: - self._channel = channel - self._reg_write(0x05, channel) - else: - raise ValueError("channel acn only be set in range [0, 125]") + if not 0 <= int(channel) <= 125: + raise ValueError("channel can only be set in range [0, 125]") + self._channel = int(channel) + self._reg_write(5, self._channel) @property def crc(self): @@ -608,25 +608,24 @@ def crc(self): @crc.setter def crc(self, length): - if 0 <= length <= 2: - if self.crc != length: - length = (length + 1) << 2 if length else 0 - self._config = self._config & 0x73 | length - self._reg_write(0, self._config) - else: + if not 0 <= length <= 2: raise ValueError("CRC byte length must be an int equal to 0 (off), 1, or 2") + if self.crc != length: + length = (length + 1) << 2 if length else 0 + self._config = self._config & 0x73 | length + self._reg_write(0, self._config) @property def power(self): """This `bool` attribute controls the power state of the nRF24L01.""" + self._config = self._reg_read(CONFIGURE) return bool(self._config & 2) @power.setter def power(self, is_on): - assert isinstance(is_on, (bool, int)) self._config = self._reg_read(CONFIGURE) if self.power != bool(is_on): - self._config = (self._config & 0x7D) | (bool(is_on) << 1) + self._config = self._config & 0x7D | bool(is_on) << 1 self._reg_write(CONFIGURE, self._config) time.sleep(0.00016) @@ -634,29 +633,16 @@ def power(self, is_on): def pa_level(self): """This `int` attribute specifies the nRF24L01's power amplifier level (in dBm).""" self._rf_setup = self._reg_read(RF_PA_RATE) - return (3 - ((self._rf_setup & RF_PA_RATE) >> 1)) * -6 + return (3 - ((self._rf_setup & 6) >> 1)) * -6 @pa_level.setter def pa_level(self, power): - if power in (-18, -12, -6, 0): - power = (3 - int(power / -6)) * 2 - if self.pa_level != power: - self._rf_setup = (self._rf_setup & 0xF9) | power - self._reg_write(RF_PA_RATE, self._rf_setup) - else: - raise ValueError("power amplitude must be -18, -12, -6, or 0 (in dBm)") - - @property - def rpd(self): - """This read-only attribute returns `True` if RPD (Received Power - Detector) is triggered or `False` if not triggered.""" - return bool(self._reg_read(0x09)) - - @property - def tx_full(self): - """An attribute to represent the nRF24L01's status flag signaling - that the TX FIFO buffer is full. (read-only)""" - return bool(self._status & 1) + if not power in (-18, -12, -6, 0): + raise ValueError("pa_level must be -18, -12, -6, or 0 (in dBm)") + power = (3 - int(power / -6)) * 2 + if self.pa_level != power: + self._rf_setup = (self._rf_setup & 0xF9) | power + self._reg_write(RF_PA_RATE, self._rf_setup) def update(self): """This function is only used to get an updated status byte over SPI @@ -732,15 +718,6 @@ def fifo(self, about_tx=False, check_empty=None): " be a bool or int" ) - @property - def pipe(self): - """The identifying number of the data pipe that received - the next available payload in the RX FIFO buffer. (read only)""" - result = (self._status & 0x0E) >> 1 - if result <= 5: - return result - return None - def address(self, index=-1): """Returns the current address set to a specified data pipe or the TX address. (read-only)""" @@ -752,6 +729,12 @@ def address(self, index=-1): return self._pipes[index] return bytes([self._pipes[index]]) + self._pipes[1][1:] + @property + def rpd(self): + """This read-only attribute returns `True` if RPD (Received Power + Detector) is triggered or `False` if not triggered.""" + return bool(self._reg_read(0x09)) + def start_carrier_wave(self): """Starts a continuous carrier wave test.""" self.power = 0 diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index a47eb20..23f49cb 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -9,18 +9,19 @@ except ImportError: from adafruit_bus_device.spi_device import SPIDevice +CSN_DELAY = 0.005 + class RF24: def __init__(self, spi, csn, ce): + self._spi = SPIDevice(spi, chip_select=csn, baudrate=10000000) self.ce_pin = ce self.ce_pin.switch_to_output(value=False) - self._spi = SPIDevice(spi, chip_select=csn, baudrate=1250000) self._status = 0 self._reg_write(0, 0x0E) - if self._reg_read(0) & 3 == 2: - self.power = False - else: + if self._reg_read(0) & 3 != 2: raise RuntimeError("nRF24L01 Hardware not responding") + self.power = False self._reg_write(3, 3) self._reg_write(6, 6) self._reg_write(2, 0) @@ -40,7 +41,7 @@ def _reg_read(self, reg): out_buf = bytearray([reg, 0]) in_buf = bytearray([0, 0]) with self._spi as spi: - time.sleep(0.005) + time.sleep(CSN_DELAY) spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] return in_buf[1] @@ -49,7 +50,7 @@ def _reg_read_bytes(self, reg, buf_len=5): in_buf = bytearray(buf_len + 1) out_buf = bytearray([reg]) + b"\x00" * buf_len with self._spi as spi: - time.sleep(0.005) + time.sleep(CSN_DELAY) spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] return in_buf[1:] @@ -58,7 +59,7 @@ def _reg_write_bytes(self, reg, out_buf): out_buf = bytes([0x20 | reg]) + out_buf in_buf = bytearray(len(out_buf)) with self._spi as spi: - time.sleep(0.005) + time.sleep(CSN_DELAY) spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] @@ -70,22 +71,20 @@ def _reg_write(self, reg, value=None): out_buf = bytes([0x20 | reg, value]) in_buf = bytearray(len(out_buf)) with self._spi as spi: - time.sleep(0.005) + time.sleep(CSN_DELAY) spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] # pylint: enable=no-member - @property def address_length(self): return self._reg_read(0x03) + 2 @address_length.setter def address_length(self, length): - if 3 <= length <= 5: - self._reg_write(0x03, length - 2) - else: - raise ValueError("address length can only be set in range [3, 5] bytes") + if not 3 <= length <= 5: + raise ValueError("address_length can only be set in range [3, 5] bytes") + self._reg_write(0x03, length - 2) def open_tx_pipe(self, address): if self.arc: @@ -101,29 +100,26 @@ def close_rx_pipe(self, pipe_number): self._reg_write(2, open_pipes & ~(1 << pipe_number)) def open_rx_pipe(self, pipe_number, address): - if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0, 5]") - if address: - if pipe_number < 2: - if not pipe_number: - self._pipe0_read_addr = address - self._reg_write_bytes(0x0A + pipe_number, address) - else: - self._reg_write(0x0A + pipe_number, address[0]) - self._reg_write(2, self._reg_read(2) | (1 << pipe_number)) - else: + if not 0 <= pipe_number <= 5: + raise ValueError("pipe_number must be in range [0, 5]") + if not address: raise ValueError("address length cannot be 0") + if pipe_number < 2: + if not pipe_number: + self._pipe0_read_addr = address + self._reg_write_bytes(0x0A + pipe_number, address) + else: + self._reg_write(0x0A + pipe_number, address[0]) + self._reg_write(2, self._reg_read(2) | (1 << pipe_number)) @property def listen(self): - return self.power and bool(self._reg_read(0) & 1) + return self._reg_read(0) & 3 == 3 @listen.setter def listen(self, is_rx): - assert isinstance(is_rx, (bool, int)) - if self.listen != is_rx: - if self.ce_pin.value: - self.ce_pin.value = 0 + if self.listen != bool(is_rx): + self.ce_pin.value = 0 if is_rx: if self._pipe0_read_addr is not None: self._reg_write_bytes(0x0A, self._pipe0_read_addr) @@ -173,11 +169,22 @@ def send(self, buf, ask_no_ack=False, force_retry=0): result = self.resend() if result is None or result: break - if not ask_no_ack and self._status & 0x60 == 0x60: + if self._status & 0x60 == 0x60: result = self.recv() self.clear_status_flags(False) return result + @property + def tx_full(self): + return bool(self._status & 1) + + @property + def pipe(self): + result = (self._status & 0x0E) >> 1 + if result <= 5: + return result + return None + @property def irq_dr(self): return bool(self._status & 0x40) @@ -190,42 +197,21 @@ def irq_ds(self): def irq_df(self): return bool(self._status & 0x10) - @property - def tx_full(self): - return bool(self._status & 1) - - @property - def rpd(self): - return bool(self._reg_read(0x09)) - - @property - def pipe(self): - result = (self._status & 0x0E) >> 1 - if result <= 5: - return result - return None - def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): - flag_config = (bool(data_recv) << 6) | (bool(data_sent) << 5) - flag_config |= bool(data_fail) << 4 - self._reg_write(7, flag_config) + config = bool(data_recv) << 6 | bool(data_sent) << 5 | bool(data_fail) << 4 + self._reg_write(7, config) def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): - config = (self._reg_read(0) & 0x0F) | (not bool(data_recv) << 6) - config |= (not bool(data_fail) << 4) | (not bool(data_sent) << 5) - self._reg_write(0, config) + config = (not data_recv) << 6 | (not data_fail) << 4 | (not data_sent) << 5 + self._reg_write(0, (self._reg_read(0) & 0x0F) | config) @property def dynamic_payloads(self): - return bool(self._reg_read(0x1C) and self._reg_read(0x1D) & 4) + return bool(self._reg_read(0x1C)) and self._reg_read(0x1D) & 4 == 4 @dynamic_payloads.setter def dynamic_payloads(self, enable): - assert isinstance(enable, (bool, int)) - features = self._reg_read(0x1D) - if bool(features & 4) != bool(enable): - features = (features & 3) | (bool(enable) << 2) - self._reg_write(0x1D, features) + self._reg_write(0x1D, (self._reg_read(0x1D) & 3) | bool(enable) << 2) self._reg_write(0x1C, 0x3F if enable else 0) @property @@ -234,12 +220,10 @@ def payload_length(self): @payload_length.setter def payload_length(self, length): - # max payload size is 32 bytes - if not length or length <= 32: - for i in range(6): - self._reg_write(0x11 + i, length) - else: - raise ValueError("payload length must be in range [1, 32] bytes") + if not length or length > 32: + raise ValueError("payload_length must be in range [1, 32] bytes") + for i in range(6): + self._reg_write(0x11 + i, length) @property def arc(self): @@ -247,34 +231,28 @@ def arc(self): @arc.setter def arc(self, count): - if 0 <= count <= 15: - setup_retr = self._reg_read(4) - if setup_retr & 0x0F != count: - setup_retr = (setup_retr & 0xF0) | count - self._reg_write(4, setup_retr) - else: + if not 0 <= count <= 15: raise ValueError("arc must in range [0, 15]") + self._reg_write(4, (self._reg_read(4) & 0xF0) | count) @property def ard(self): return ((self._reg_read(4) & 0xF0) >> 4) * 250 + 250 @ard.setter - def ard(self, delta_t): - if 250 <= delta_t <= 4000 and delta_t % 250 == 0: - setup_retr = self._reg_read(4) & 0x0F - setup_retr |= int((delta_t - 250) / 250) << 4 - self._reg_write(4, setup_retr) - else: - raise ValueError("ard must be a multiple of 250 in range [250, 4000]") + def ard(self, delta): + if not 250 <= delta <= 4000: + raise ValueError("ard must be in range [250, 4000]") + self._reg_write(4, (self._reg_read(4) & 0x0F) | int((delta - 250) / 250) << 4) @property def ack(self): - return bool((self._reg_read(0x1D) & 6 == 6) and self._reg_read(1)) + return self._reg_read(0x1D) & 6 == 6 and bool( + self._reg_read(1) & self._reg_read(0x1C) + ) @ack.setter def ack(self, enable): - assert isinstance(enable, (bool, int)) features = self._reg_read(0x1D) & 5 if enable: self._reg_write(1, 0x3F) @@ -284,15 +262,12 @@ def ack(self, enable): self._reg_write(0x1D, features) def load_ack(self, buf, pipe_number): - if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0, 5]") - if not buf or len(buf) > 32: - raise ValueError("buffer must have a length in range [1, 32]") - if not self._reg_read(0x1D) & 2: - self.ack = True - if not self._status & 1: - self._reg_write_bytes(0xA8 | pipe_number, buf) - return True + if 0 <= pipe_number <= 5 and (not buf or len(buf) > 32): + if not self._reg_read(0x1D) & 2: + self.ack = True + if not self.tx_full: + self._reg_write_bytes(0xA8 | pipe_number, buf) + return True return False @property @@ -302,12 +277,8 @@ def data_rate(self): @data_rate.setter def data_rate(self, speed): - if speed in (1, 2, 250): - speed = 0 if speed == 1 else (8 if speed == 2 else 0x20) - rf_setup = (self._reg_read(6) & 0xD7) | speed - self._reg_write(6, rf_setup) - else: - raise ValueError("data_rate options limited to 1, 2, 250") + speed = 0 if speed == 1 else (0x20 if speed != 2 else 8) + self._reg_write(6, (self._reg_read(6) & 0xD7) | speed) @property def channel(self): @@ -315,10 +286,9 @@ def channel(self): @channel.setter def channel(self, channel): - if 0 <= channel <= 125: - self._reg_write(5, channel) - else: + if not 0 <= int(channel) <= 125: raise ValueError("channel can only be set in range [0, 125]") + self._reg_write(5, int(channel)) @property def power(self): @@ -326,13 +296,22 @@ def power(self): @power.setter def power(self, is_on): - assert isinstance(is_on, (bool, int)) config = self._reg_read(0) - if bool(config & 2) != is_on: - config = (config & 0x7D) | (is_on << 1) + if bool(config & 2) != bool(is_on): + config = config & 0x7D | bool(is_on) << 1 self._reg_write(0, config) time.sleep(0.00016) + @property + def pa_level(self): + return (3 - ((self._reg_read(6) & 6) >> 1)) * -6 + + @pa_level.setter + def pa_level(self, power): + if not power in (-18, -12, -6, 0): + raise ValueError("pa_level must be -18, -12, -6, or 0") + self._reg_write(6, (self._reg_read(6) & 0xF9) | ((3 - int(power / -6)) * 2)) + def update(self): self._reg_write(0xFF) @@ -379,6 +358,10 @@ def flush_rx(self): def flush_tx(self): self._reg_write(0xE1) + @property + def rpd(self): + return bool(self._reg_read(0x09)) + def start_carrier_wave(self): self.power = 0 self.ce_pin.value = 0 diff --git a/docs/api.rst b/docs/api.rst index 5df0b2d..6a8e1c5 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -85,21 +85,31 @@ developed to save space on microcontrollers with limited amount of RAM and/or st boards using the ATSAMD21 M0). The following functionality has been removed from the lite version: - * `address()` - * `what_happened()` - * `fifo()` - * `pa_level` (this is always set to 0 dBm) - * `read_ack()` (deprecated on next major release anyway; use `recv()` instead) - * `crc` (always using 2 bytes encoding scheme) - * `auto_ack` (this is always on for all pipes). Pass ``ask_no_ack`` parameter as `True` to + * `address()` removed. + * `what_happened()` removed. However you can use the following function to dump all available registers' values (for advanced users): + + .. code-block:: python + + # let `nrf` be the instantiated RF24 object + def dump_registers(end=0x1e): + for i in range(end): + if i in (0xA, 0xB, 0x10): + print(hex(i), "=", nrf._reg_read_bytes(i)) + elif not i in (0x18, 0x19, 0x1a, 0x1b): + print(hex(i), "=", hex(nrf._reg_read(i))) + * `fifo()` removed. + * `dynamic_payloads` applies to all pipes, not individual pipes. + * `payload_length` applies to all pipes, not individual pipes. + * `read_ack()` removed. This is deprecated on next major release anyway; use `recv()` instead. + * `load_ack()` is available, but it will not throw exceptions for malformed ``buf`` or invalid ``pipe_number`` parameters. + * `crc` removed. 2-bytes encoding scheme (CRC16) is always enabled. + * `auto_ack` removed. This is always enabled for all pipes. Pass ``ask_no_ack`` parameter as `True` to `send()` or `write()` to disable automatic acknowledgement for TX operations. - * all comments and docstrings (meaning ``help()`` will not provide any specific information). + * All comments and docstrings removed, meaning ``help()`` will not provide any specific information. Exception prompts have also been reduced and adjusted accordingly. - * switching between different radio configurations using context manager (the `with` blocks). + * Cannot switch between different radio configurations using context manager (the `with` blocks). It is advised that only one `RF24` object be instantiated when RAM is limited (less than or equal to 32KB). - * `dynamic_payloads` applies to all pipes, not individual pipes. - * `payload_length` applies to all pipes, not individual pipes. RF24 class ============== @@ -288,6 +298,11 @@ send() Advanced API ------------ +CSN_DELAY +****************************** + +.. autodata:: circuitpython_nrf24l01.rf24.CSN_DELAY + what_happened() ****************************** From 73a88964b4b6c80d4384c8244b9f74dc7148435e Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Thu, 17 Sep 2020 03:03:40 -0700 Subject: [PATCH 062/127] fixed 2arduino example; it works w/ TMRh20@v1.3.9 --- examples/nrf24l01_2arduino_handling_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/nrf24l01_2arduino_handling_data.py b/examples/nrf24l01_2arduino_handling_data.py index fd785b0..9ea5761 100644 --- a/examples/nrf24l01_2arduino_handling_data.py +++ b/examples/nrf24l01_2arduino_handling_data.py @@ -28,9 +28,9 @@ nrf.dynamic_payloads = False # this is the default in the TMRh20 arduino library # set address of TX node into a RX pipe -nrf.open_rx_pipe(1, address[1]) +nrf.open_rx_pipe(1, address[0]) # set address of RX node into a TX pipe -nrf.open_tx_pipe(address[0]) +nrf.open_tx_pipe(address[1]) def master(count=5): # count = 5 will only transmit 5 packets """Transmits an arbitrary unsigned long value every second. This method From 504f0f6362de3cc0f86cceb5331e1f7c30e8e143 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 18 Sep 2020 03:58:56 -0700 Subject: [PATCH 063/127] use LNA_HCURR bit; mod'd readme about clones --- README.rst | 8 ++-- circuitpython_nrf24l01/rf24.py | 26 ++++++---- circuitpython_nrf24l01/rf24_lite.py | 73 +++++++++-------------------- docs/api.rst | 23 ++++++++- 4 files changed, 63 insertions(+), 67 deletions(-) diff --git a/README.rst b/README.rst index 0bab6a2..3c2cfea 100644 --- a/README.rst +++ b/README.rst @@ -164,12 +164,12 @@ Future Project Ideas/Additions Where do I get 1? ================= -See the store links on the sidebar or just google "nRF24L01". It is worth noting that you generally don't want to buy just 1 as you need 2 for testing -- 1 to send & 1 to receive and vise versa. This library has been tested on a cheaply bought 10 pack from Amazon.com using a highly recommended capacitor (100 µF) on the power pins. Don't get lost on Amazon or eBay! There are other wireless transceivers that are NOT compatible with this library. For instance, the esp8266-01 (also sold in packs) is NOT compatible with this library, but looks very similar to the nRF24L01(+) and could lead to an accidental purchase. +See the store links on the sidebar or just google "nRF24L01+". It is worth noting that you generally want to buy more than 1 as you need 2 for testing -- 1 to send & 1 to receive and vise versa. This library has been tested on a cheaply bought 10 pack from Amazon.com using a highly recommended capacitor (100 µF) on the power pins. Don't get lost on Amazon or eBay! There are other wireless transceivers that are NOT compatible with this library. For instance, the esp8266-01 (also sold in packs) is NOT compatible with this library, but looks very similar to the nRF24L01+ and could lead to an accidental purchase. -SI24R1 chip (an nRF24L01(+) clone) -================================== +nRF24L01(+) clones and counterfeits +=================================== -Beware there are nRF24L01 module clones out there. To determine if your purchase is a counterfeit, please `read this article `_. The most notable clone is the `SI24R1 `_. The `SI24R1 datasheet `_ I could find is not in english. Troubleshooting the SI24R1 may require `replacing the onboard antennae with a wire `_. Furthermore, the SI24R1 has different power amplifier settings as noted in the `RF_PWR section (bits 0 through 2) of the RF_SETUP register (hex address 6) of the datasheet `_. Please note that this library does not directly support the SI24R1 chip as there is no way for the library to differentiate between an actual nRF24L01(+) and a SI24R1. +This library does not directly support clones/counterfeits as there is no way for the library to differentiate between an actual nRF24L01+ and a clone/counterfeit. To determine if your purchase is a counterfeit, please contact the retailer you purxhased from (`reading this article and its links might help `_). The most notable clone is the `Si24R1 `_. I could not find the `Si24R1 datasheet `_ in english. Troubleshooting the SI24R1 may require `replacing the onboard antennae with a wire `_. Furthermore, the Si24R1 has different power amplifier options as noted in the `RF_PWR section (bits 0 through 2) of the RF_SETUP register (hex address 6) of the datasheet `_. While the options' values differ from those identified by this library's API, the underlying commands to configure those options are almost identical to the nRF24L01. Other known clones include the bk242x (AKA RFM7x). Contributing ============ diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 0454125..45b154b 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -149,10 +149,8 @@ def _reg_write_bytes(self, reg, out_buf): self._status = in_buf[0] def _reg_write(self, reg, value=None): - out_buf = bytes(0) - if value is None: - out_buf = bytes([reg]) - else: + out_buf = bytes([reg]) + if value is not None: out_buf = bytes([0x20 | reg, value]) in_buf = bytearray(len(out_buf)) with self._spi as spi: @@ -637,12 +635,21 @@ def pa_level(self): @pa_level.setter def pa_level(self, power): - if not power in (-18, -12, -6, 0): + lna_bit = True + if isinstance(power, (list, tuple)) and len(power) > 1: + lna_bit, power = bool(power[1]), int(power[0]) + if power not in (-18, -12, -6, 0): raise ValueError("pa_level must be -18, -12, -6, or 0 (in dBm)") power = (3 - int(power / -6)) * 2 - if self.pa_level != power: - self._rf_setup = (self._rf_setup & 0xF9) | power - self._reg_write(RF_PA_RATE, self._rf_setup) + self._rf_setup = (self._rf_setup & 0xF8) | power | lna_bit + self._reg_write(RF_PA_RATE, self._rf_setup) + + @property + def is_lna_enabled(self): + """A read-only `bool` attribute about the LNA (Low Noise Amplifier) gain + feature used in the nRF24L01-PA/LNA modules.""" + self._rf_setup = self._reg_read(RF_PA_RATE) + return bool(self._rf_setup & 1) def update(self): """This function is only used to get an updated status byte over SPI @@ -683,8 +690,7 @@ def write(self, buf, ask_no_ack=False): time.sleep(0.00016) if not bool((self._dyn_pl & 1) and (self._features & 4)): if len(buf) < self._pl_len[0]: - for _ in range(self._pl_len[0] - len(buf)): - buf += b"\x00" + buf += b"\x00" * self._pl_len[0] - len(buf) elif len(buf) > self._pl_len[0]: buf = buf[: self._pl_len[0]] if ask_no_ack: diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index 23f49cb..1ebd72c 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -1,15 +1,9 @@ -# see license and copyright information in rf24.py of this directory +# see license and copyright information in rf24.py # pylint: disable=missing-docstring __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/2bndy5/CircuitPython_nRF24L01.git" import time - -try: - from ubus_device import SPIDevice -except ImportError: - from adafruit_bus_device.spi_device import SPIDevice - -CSN_DELAY = 0.005 +from adafruit_bus_device.spi_device import SPIDevice class RF24: @@ -41,7 +35,7 @@ def _reg_read(self, reg): out_buf = bytearray([reg, 0]) in_buf = bytearray([0, 0]) with self._spi as spi: - time.sleep(CSN_DELAY) + time.sleep(0.005) spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] return in_buf[1] @@ -50,7 +44,7 @@ def _reg_read_bytes(self, reg, buf_len=5): in_buf = bytearray(buf_len + 1) out_buf = bytearray([reg]) + b"\x00" * buf_len with self._spi as spi: - time.sleep(CSN_DELAY) + time.sleep(0.005) spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] return in_buf[1:] @@ -59,19 +53,17 @@ def _reg_write_bytes(self, reg, out_buf): out_buf = bytes([0x20 | reg]) + out_buf in_buf = bytearray(len(out_buf)) with self._spi as spi: - time.sleep(CSN_DELAY) + time.sleep(0.005) spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] def _reg_write(self, reg, value=None): - out_buf = bytes(0) - if value is None: - out_buf = bytes([reg]) - else: + out_buf = bytes([reg]) + if value is not None: out_buf = bytes([0x20 | reg, value]) in_buf = bytearray(len(out_buf)) with self._spi as spi: - time.sleep(CSN_DELAY) + time.sleep(0.005) spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] @@ -83,7 +75,7 @@ def address_length(self): @address_length.setter def address_length(self, length): if not 3 <= length <= 5: - raise ValueError("address_length can only be set in range [3, 5] bytes") + raise ValueError("address_length must be in range [3, 5]") self._reg_write(0x03, length - 2) def open_tx_pipe(self, address): @@ -94,7 +86,7 @@ def open_tx_pipe(self, address): def close_rx_pipe(self, pipe_number): if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0, 5]") + raise ValueError("pipe_number must be in range [0, 5]") open_pipes = self._reg_read(2) if open_pipes & (1 << pipe_number): self._reg_write(2, open_pipes & ~(1 << pipe_number)) @@ -124,10 +116,10 @@ def listen(self, is_rx): if self._pipe0_read_addr is not None: self._reg_write_bytes(0x0A, self._pipe0_read_addr) self._reg_write(0, (self._reg_read(0) & 0xFC) | 3) - time.sleep(0.00015) # mandatory wait to power up radio + time.sleep(0.00015) self.flush_rx() self.clear_status_flags(True, False, False) - self.ce_pin.value = 1 # mandatory pulse is > 130 µs + self.ce_pin.value = 1 time.sleep(0.00013) else: self._reg_write(0, self._reg_read(0) & 0xFE) @@ -221,7 +213,7 @@ def payload_length(self): @payload_length.setter def payload_length(self, length): if not length or length > 32: - raise ValueError("payload_length must be in range [1, 32] bytes") + raise ValueError("payload_length must be in range [1, 32]") for i in range(6): self._reg_write(0x11 + i, length) @@ -232,7 +224,7 @@ def arc(self): @arc.setter def arc(self, count): if not 0 <= count <= 15: - raise ValueError("arc must in range [0, 15]") + raise ValueError("arc must be in range [0, 15]") self._reg_write(4, (self._reg_read(4) & 0xF0) | count) @property @@ -247,15 +239,12 @@ def ard(self, delta): @property def ack(self): - return self._reg_read(0x1D) & 6 == 6 and bool( - self._reg_read(1) & self._reg_read(0x1C) - ) + return self._reg_read(0x1D) & 6 == 6 and bool(self._reg_read(0x1C)) @ack.setter def ack(self, enable): features = self._reg_read(0x1D) & 5 if enable: - self._reg_write(1, 0x3F) self._reg_write(0x1C, 0x3F) features = (features & 3) | 4 features |= 2 if enable else 0 @@ -287,7 +276,7 @@ def channel(self): @channel.setter def channel(self, channel): if not 0 <= int(channel) <= 125: - raise ValueError("channel can only be set in range [0, 125]") + raise ValueError("channel must be in range [0, 125]") self._reg_write(5, int(channel)) @property @@ -298,8 +287,7 @@ def power(self): def power(self, is_on): config = self._reg_read(0) if bool(config & 2) != bool(is_on): - config = config & 0x7D | bool(is_on) << 1 - self._reg_write(0, config) + self._reg_write(0, config & 0x7D | bool(is_on) << 1) time.sleep(0.00016) @property @@ -308,9 +296,9 @@ def pa_level(self): @pa_level.setter def pa_level(self, power): - if not power in (-18, -12, -6, 0): + if power not in (-18, -12, -6, 0): raise ValueError("pa_level must be -18, -12, -6, or 0") - self._reg_write(6, (self._reg_read(6) & 0xF9) | ((3 - int(power / -6)) * 2)) + self._reg_write(6, self._reg_read(6) & 0xF8 | (3 - int(power / -6)) * 2 | 1) def update(self): self._reg_write(0xFF) @@ -336,7 +324,7 @@ def resend(self): def write(self, buf, ask_no_ack=False): if not buf or len(buf) > 32: - raise ValueError("buffer must have a length in range [1, 32]") + raise ValueError("buffer length must be in range [1, 32]") self.clear_status_flags() config = self._reg_read(0) if config & 3 != 2: @@ -345,8 +333,7 @@ def write(self, buf, ask_no_ack=False): if not self.dynamic_payloads: pl_width = self.payload_length if len(buf) < pl_width: - for _ in range(pl_width - len(buf)): - buf += b"\x00" + buf += b"\x00" * pl_width - len(buf) elif len(buf) > pl_width: buf = buf[:pl_width] self._reg_write_bytes(0xA0 | (ask_no_ack << 4), buf) @@ -357,21 +344,3 @@ def flush_rx(self): def flush_tx(self): self._reg_write(0xE1) - - @property - def rpd(self): - return bool(self._reg_read(0x09)) - - def start_carrier_wave(self): - self.power = 0 - self.ce_pin.value = 0 - self.listen = 0 - self._reg_write(6, self._reg_read(6) | 0x90) - self.power = 1 - self.ce_pin.value = 1 - time.sleep(0.00028) - - def stop_carrier_wave(self): - self.ce_pin.value = 0 - self.power = 0 - self._reg_write(6, self._reg_read(6) & ~0x90) diff --git a/docs/api.rst b/docs/api.rst index 6a8e1c5..37184df 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -105,6 +105,9 @@ version: * `crc` removed. 2-bytes encoding scheme (CRC16) is always enabled. * `auto_ack` removed. This is always enabled for all pipes. Pass ``ask_no_ack`` parameter as `True` to `send()` or `write()` to disable automatic acknowledgement for TX operations. + * `is_lna_enabled` removed. This will always be enabled, and `pa_level` will not accept a `list` or `tuple`. This only affects certain boards anyway. + * `rpd`, `start_carrier_wave()`, & `stop_carrier_wave()` removed. These only perform a test of the nRF24L01's hardware. + * `CSN_DELAY` removed. This is hard-coded to 5 milliseconds * All comments and docstrings removed, meaning ``help()`` will not provide any specific information. Exception prompts have also been reduced and adjusted accordingly. * Cannot switch between different radio configurations using context manager (the `with` blocks). @@ -712,7 +715,25 @@ pa_level - ``-6`` sets the nRF24L01's power amplifier to -6 dBm - ``0`` sets the nRF24L01's power amplifier to 0 dBm (highest) - Any invalid input throws a `ValueError` exception. Default is 0 dBm. + If this attribute is set to a `list` or `tuple`, then the list/tuple must contain the + desired power amplifier level (from list above) at index 0 and a `bool` to control + the Low Noise Amplifier (LNA) feature at index 1. All other indices will be discarded. + + .. note:: + The LNA feature only applies to the nRF24L01 (non-plus variant). This + includes boards with the RFX24C01-based PA/LNA muxing IC attached to an + SMA-type detachable antenna. + + Any invalid input will invoke the default of 0 dBm with LNA enabled. + +is_lna_enabled +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.is_lna_enabled + + See `pa_level` attribute about how to set this. Default is always enabled, but this + feature is specific to certain nRF24L01-based circuits. Check with your module's + manufacturer to see is it can toggle the Low Noise Amplifier feature. tx_full ****************************** From d2bb51cc311ed993e86a0261a6bb1ca69a42bebb Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 18 Sep 2020 04:13:32 -0700 Subject: [PATCH 064/127] lite.mpy size down to 10252 bytes --- circuitpython_nrf24l01/rf24_lite.py | 68 ++++++++++++++--------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index 1ebd72c..1e99671 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -57,10 +57,10 @@ def _reg_write_bytes(self, reg, out_buf): spi.write_readinto(out_buf, in_buf) self._status = in_buf[0] - def _reg_write(self, reg, value=None): + def _reg_write(self, reg, val=None): out_buf = bytes([reg]) - if value is not None: - out_buf = bytes([0x20 | reg, value]) + if val is not None: + out_buf = bytes([0x20 | reg, val]) in_buf = bytearray(len(out_buf)) with self._spi as spi: time.sleep(0.005) @@ -78,31 +78,31 @@ def address_length(self, length): raise ValueError("address_length must be in range [3, 5]") self._reg_write(0x03, length - 2) - def open_tx_pipe(self, address): + def open_tx_pipe(self, addr): if self.arc: - self._reg_write_bytes(0x0A, address) + self._reg_write_bytes(0x0A, addr) self._reg_write(2, self._reg_read(2) | 1) - self._reg_write_bytes(0x10, address) + self._reg_write_bytes(0x10, addr) - def close_rx_pipe(self, pipe_number): - if pipe_number < 0 or pipe_number > 5: + def close_rx_pipe(self, pipe_num): + if pipe_num < 0 or pipe_num > 5: raise ValueError("pipe_number must be in range [0, 5]") open_pipes = self._reg_read(2) - if open_pipes & (1 << pipe_number): - self._reg_write(2, open_pipes & ~(1 << pipe_number)) + if open_pipes & (1 << pipe_num): + self._reg_write(2, open_pipes & ~(1 << pipe_num)) - def open_rx_pipe(self, pipe_number, address): - if not 0 <= pipe_number <= 5: + def open_rx_pipe(self, pipe_num, addr): + if not 0 <= pipe_num <= 5: raise ValueError("pipe_number must be in range [0, 5]") - if not address: + if not addr: raise ValueError("address length cannot be 0") - if pipe_number < 2: - if not pipe_number: - self._pipe0_read_addr = address - self._reg_write_bytes(0x0A + pipe_number, address) + if pipe_num < 2: + if not pipe_num: + self._pipe0_read_addr = addr + self._reg_write_bytes(0x0A + pipe_num, addr) else: - self._reg_write(0x0A + pipe_number, address[0]) - self._reg_write(2, self._reg_read(2) | (1 << pipe_number)) + self._reg_write(0x0A + pipe_num, addr[0]) + self._reg_write(2, self._reg_read(2) | (1 << pipe_num)) @property def listen(self): @@ -133,10 +133,10 @@ def any(self): return 0 def recv(self): - curr_pl_size = self.any() - if not curr_pl_size: + pl_size = self.any() + if not pl_size: return None - result = self._reg_read_bytes(0x61, curr_pl_size) + result = self._reg_read_bytes(0x61, pl_size) self.clear_status_flags(True, False, False) return result @@ -222,10 +222,10 @@ def arc(self): return self._reg_read(4) & 0x0F @arc.setter - def arc(self, count): - if not 0 <= count <= 15: + def arc(self, cnt): + if not 0 <= cnt <= 15: raise ValueError("arc must be in range [0, 15]") - self._reg_write(4, (self._reg_read(4) & 0xF0) | count) + self._reg_write(4, (self._reg_read(4) & 0xF0) | cnt) @property def ard(self): @@ -250,12 +250,12 @@ def ack(self, enable): features |= 2 if enable else 0 self._reg_write(0x1D, features) - def load_ack(self, buf, pipe_number): - if 0 <= pipe_number <= 5 and (not buf or len(buf) > 32): + def load_ack(self, buf, pipe_num): + if 0 <= pipe_num <= 5 and (not buf or len(buf) > 32): if not self._reg_read(0x1D) & 2: self.ack = True if not self.tx_full: - self._reg_write_bytes(0xA8 | pipe_number, buf) + self._reg_write_bytes(0xA8 | pipe_num, buf) return True return False @@ -274,10 +274,10 @@ def channel(self): return self._reg_read(5) @channel.setter - def channel(self, channel): - if not 0 <= int(channel) <= 125: + def channel(self, chnl): + if not 0 <= int(chnl) <= 125: raise ValueError("channel must be in range [0, 125]") - self._reg_write(5, int(channel)) + self._reg_write(5, int(chnl)) @property def power(self): @@ -295,10 +295,10 @@ def pa_level(self): return (3 - ((self._reg_read(6) & 6) >> 1)) * -6 @pa_level.setter - def pa_level(self, power): - if power not in (-18, -12, -6, 0): + def pa_level(self, pwr): + if pwr not in (-18, -12, -6, 0): raise ValueError("pa_level must be -18, -12, -6, or 0") - self._reg_write(6, self._reg_read(6) & 0xF8 | (3 - int(power / -6)) * 2 | 1) + self._reg_write(6, self._reg_read(6) & 0xF8 | (3 - int(pwr / -6)) * 2 | 1) def update(self): self._reg_write(0xFF) From d1480906049538dfbae0fb3b8a23527ec7ab5d8e Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sun, 20 Sep 2020 00:56:19 -0700 Subject: [PATCH 065/127] recv() takes length arg --- circuitpython_nrf24l01/rf24.py | 12 +- circuitpython_nrf24l01/rf24_lite.py | 8 +- docs/api.rst | 317 +++++++++++++++------------- 3 files changed, 185 insertions(+), 152 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 45b154b..b0b64dc 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -49,8 +49,8 @@ class RF24: """A driver class for the nRF24L01(+) transceiver radios.""" - def __init__(self, spi, csn, ce): - self._spi = SPIDevice(spi, chip_select=csn, baudrate=10000000) + def __init__(self, spi, csn, ce, spi_frequency=10000000): + self._spi = SPIDevice(spi, chip_select=csn, baudrate=spi_frequency) self.ce_pin = ce self.ce_pin.switch_to_output(value=False) # pre-empt standby-I mode self._status = 0 # status byte returned on all SPI transactions @@ -251,13 +251,13 @@ def any(self): return self._pl_len[self.pipe] return 0 - def recv(self): + def recv(self, length=None): """This function is used to retrieve the next available payload in the RX FIFO buffer, then clears the `irq_dr` status flag.""" - curr_pl_size = self.any() - if not curr_pl_size: + return_size = length if length is not None else self.any() + if not return_size: return None - result = self._reg_read_bytes(0x61, curr_pl_size) + result = self._reg_read_bytes(0x61, return_size) self.clear_status_flags(True, False, False) return result diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index 1e99671..7221c9b 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -132,11 +132,11 @@ def any(self): return self._reg_read(0x11 + self.pipe) return 0 - def recv(self): - pl_size = self.any() - if not pl_size: + def recv(self, length=None): + ret_size = length if length is not None else self.any() + if not ret_size: return None - result = self._reg_read_bytes(0x61, pl_size) + result = self._reg_read_bytes(0x61, ret_size) self.clear_status_flags(True, False, False) return result diff --git a/docs/api.rst b/docs/api.rst index 37184df..52a4177 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -17,50 +17,55 @@ Troubleshooting info .. important:: The nRF24L01 has 3 key features that can be interdependent of each other. Their priority of dependence is as follows: - 1. `dynamic_payloads` feature allowing either TX/RX nRF24L01 to be able to send/receive - payloads with their size written into the payloads' packet. With this disabled, both RX/TX - nRF24L01 must use matching `payload_length` attributes. - 2. `auto_ack` feature provides transmission verification by using the RX nRF24L01 to + 1. `auto_ack` feature provides transmission verification by using the RX nRF24L01 to automatically and imediatedly send an acknowledgment (ACK) packet in response to freshly received payloads. `auto_ack` does not require `dynamic_payloads` to be enabled. + 2. `dynamic_payloads` feature allowing either TX/RX nRF24L01 to be able to send/receive + payloads with their size written into the payloads' packet. With this disabled, both + RX/TX nRF24L01 must use matching `payload_length` attributes. For `dynamic_payloads` to + be enabled, the `auto_ack` feature must be enabled. Although, the `auto_ack` feature + isn't required when the `dynamic_payloads` feature is disabled. 3. `ack` feature allows the MCU to append a payload to the ACK packet, thus instant - bi-directional communication. A transmitting ACK payload must be loaded into the nRF24L01's - TX FIFO buffer (done using `load_ack()`) BEFORE receiving the payload that is to be - acknowledged. Once transmitted, the payload is released from the TX FIFO buffer. This - feature requires the `auto_ack` and `dynamic_payloads` features enabled. + bi-directional communication. A transmitting ACK payload must be loaded into the + nRF24L01's TX FIFO buffer (done using `load_ack()`) BEFORE receiving the payload that + is to be acknowledged. Once transmitted, the payload is released from the TX FIFO + buffer. This feature requires the `auto_ack` and `dynamic_payloads` features enabled. -Remeber that the nRF24L01's FIFO (first-in,first-out) buffer has 3 levels. This means that there -can be up to 3 payloads waiting to be read (RX) and up to 3 payloads waiting to be transmit (TX). +Remeber that the nRF24L01's FIFO (first-in,first-out) buffer has 3 levels. This means that +there can be up to 3 payloads waiting to be read (RX) and up to 3 payloads waiting to be +transmit (TX). With the `auto_ack` feature enabled, you get: * cyclic redundancy checking (`crc`) automatically enabled - * to change amount of automatic re-transmit attempts and the delay time between them. See the - `arc` and `ard` attributes. + * to change amount of automatic re-transmit attempts and the delay time between them. + See the `arc` and `ard` attributes. .. note:: A word on pipes vs addresses vs channels. You should think of the data pipes as a "parking spot" for your payload. There are only six - data pipes on the nRF24L01, thus it can simultaneously "listen" to a maximum of 6 other nRF24L01 - radios. However, it can only "talk" to 1 other nRF24L01 at a time). - - The specified address is not the address of an nRF24L01 radio, rather it is more like a path - that connects the endpoints. When assigning addresses to a data pipe, you can use any 5 byte - long address you can think of (as long as the first byte is unique among simultaneously - broadcasting addresses), so you're not limited to communicating with only the same 6 nRF24L01 - radios (more on this when we officially support "Multiciever" mode). - - Finnaly, the radio's channel is not be confused with the radio's pipes. Channel selection is a - way of specifying a certain radio frequency (frequency = [2400 + channel] MHz). Channel - defaults to 76 (like the arduino library), but options range from 0 to 125 -- that's 2.4 - GHz to 2.525 GHz. The channel can be tweaked to find a less occupied frequency amongst - Bluetooth, WiFi, or other ambient signals that use the same spectrum of frequencies. - -.. warning:: For successful transmissions, most of the endpoint trasceivers' settings/features must + data pipes on the nRF24L01, thus it can simultaneously "listen" to a maximum of 6 other + nRF24L01 radios. However, it can only "talk" to 1 other nRF24L01 at a time). + + The specified address is not the address of an nRF24L01 radio, rather it is more like a + path that connects the endpoints. When assigning addresses to a data pipe, you can use any + 5 byte long address you can think of (as long as the first byte is unique among + simultaneously broadcasting addresses), so you're not limited to communicating with only + the same 6 nRF24L01 radios (more on this when we officially support "Multiciever" mode). + + Finnaly, the radio's channel is not be confused with the radio's pipes. Channel selection + is a way of specifying a certain radio frequency (frequency = [2400 + channel] MHz). + Channel defaults to 76 (like the arduino library), but options range from 0 to 125 -- + that's 2.4 GHz to 2.525 GHz. The channel can be tweaked to find a less occupied frequency + amongst Bluetooth, WiFi, or other ambient signals that use the same spectrum of + frequencies. + +.. warning:: + For successful transmissions, most of the endpoint trasceivers' settings/features must match. These settings/features include: - * The RX pipe's address on the receiving nRF24L01 (passed to `open_rx_pipe()`) MUST match the - TX pipe's address on the transmitting nRF24L01 (passed to `open_tx_pipe()`) + * The RX pipe's address on the receiving nRF24L01 (passed to `open_rx_pipe()`) MUST match + the TX pipe's address on the transmitting nRF24L01 (passed to `open_tx_pipe()`) * `address_length` * `channel` * `data_rate` @@ -71,11 +76,11 @@ With the `auto_ack` feature enabled, you get: * custom `ack` payloads * `crc` - In fact the only attributes that aren't required to match on both endpoint transceivers would - be the identifying data pipe number (passed to `open_rx_pipe()` or `load_ack()`), `pa_level`, - `arc`, & `ard` attributes. The ``ask_no_ack`` feature can be used despite the settings/features - configuration (see `send()` & `write()` function - parameters for more details). + In fact the only attributes that aren't required to match on both endpoint transceivers + would be the identifying data pipe number (passed to `open_rx_pipe()` or `load_ack()`), + `pa_level`, `arc`, & `ard` attributes. The ``ask_no_ack`` feature can be used despite the + settings/features configuration (see `send()` & `write()` function parameters for more + details). About the lite version ====================== @@ -86,7 +91,8 @@ boards using the ATSAMD21 M0). The following functionality has been removed from version: * `address()` removed. - * `what_happened()` removed. However you can use the following function to dump all available registers' values (for advanced users): + * `what_happened()` removed. However you can use the following function to dump all + available registers' values (for advanced users): .. code-block:: python @@ -95,24 +101,29 @@ version: for i in range(end): if i in (0xA, 0xB, 0x10): print(hex(i), "=", nrf._reg_read_bytes(i)) - elif not i in (0x18, 0x19, 0x1a, 0x1b): + elif i not in (0x18, 0x19, 0x1a, 0x1b): print(hex(i), "=", hex(nrf._reg_read(i))) * `fifo()` removed. * `dynamic_payloads` applies to all pipes, not individual pipes. * `payload_length` applies to all pipes, not individual pipes. - * `read_ack()` removed. This is deprecated on next major release anyway; use `recv()` instead. - * `load_ack()` is available, but it will not throw exceptions for malformed ``buf`` or invalid ``pipe_number`` parameters. + * `read_ack()` removed. This is deprecated on next major release anyway; use `recv()` + instead. + * `load_ack()` is available, but it will not throw exceptions for malformed ``buf`` or + invalid ``pipe_number`` parameters. * `crc` removed. 2-bytes encoding scheme (CRC16) is always enabled. - * `auto_ack` removed. This is always enabled for all pipes. Pass ``ask_no_ack`` parameter as `True` to - `send()` or `write()` to disable automatic acknowledgement for TX operations. - * `is_lna_enabled` removed. This will always be enabled, and `pa_level` will not accept a `list` or `tuple`. This only affects certain boards anyway. - * `rpd`, `start_carrier_wave()`, & `stop_carrier_wave()` removed. These only perform a test of the nRF24L01's hardware. + * `auto_ack` removed. This is always enabled for all pipes. Pass ``ask_no_ack`` parameter + as `True` to `send()` or `write()` to disable automatic acknowledgement for TX + operations. + * `is_lna_enabled` removed. This will always be enabled, and `pa_level` will not accept a + `list` or `tuple`. This only affects certain boards anyway. + * `rpd`, `start_carrier_wave()`, & `stop_carrier_wave()` removed. These only perform a + test of the nRF24L01's hardware. * `CSN_DELAY` removed. This is hard-coded to 5 milliseconds - * All comments and docstrings removed, meaning ``help()`` will not provide any specific information. - Exception prompts have also been reduced and adjusted accordingly. - * Cannot switch between different radio configurations using context manager (the `with` blocks). - It is advised that only one `RF24` object be instantiated when RAM is limited (less than or - equal to 32KB). + * All comments and docstrings removed, meaning ``help()`` will not provide any specific + information. Exception prompts have also been reduced and adjusted accordingly. + * Cannot switch between different radio configurations using context manager (the `with` + blocks). It is advised that only one `RF24` object be instantiated when RAM is limited + (less than or equal to 32KB). RF24 class ============== @@ -126,9 +137,10 @@ Contructor .. autoclass:: circuitpython_nrf24l01.rf24.RF24 :no-members: - This class aims to be compatible with other devices in the nRF24xxx product line that implement - the Nordic proprietary Enhanced ShockBurst Protocol (and/or the legacy ShockBurst Protocol), - but officially only supports (through testing) the nRF24L01 and nRF24L01+ devices. + This class aims to be compatible with other devices in the nRF24xxx product line that + implement the Nordic proprietary Enhanced ShockBurst Protocol (and/or the legacy + ShockBurst Protocol), but officially only supports (through testing) the nRF24L01 and + nRF24L01+ devices. :param ~busio.SPI spi: The object for the SPI bus that the nRF24L01 is connected to. @@ -154,15 +166,17 @@ open_tx_pipe() .. automethod:: circuitpython_nrf24l01.rf24.RF24.open_tx_pipe - :param bytearray address: The virtual address of the receiving nRF24L01. The address specified - here must match the address set to one of the RX data pipes of the receiving nRF24L01. - The existing address can be altered by writting a bytearray with a length less than 5. The nRF24L01 will use the first `address_length` number of bytes for the RX address on the specified data pipe. + :param bytearray address: The virtual address of the receiving nRF24L01. The address + specified here must match the address set to one of the RX data pipes of the receiving + nRF24L01. The existing address can be altered by writting a bytearray with a length + less than 5. The nRF24L01 will use the first `address_length` number of bytes for the + RX address on the specified data pipe. .. note:: There is no option to specify which data pipe to use because the nRF24L01 only uses data pipe 0 in TX mode. Additionally, the nRF24L01 uses the same data pipe (pipe 0) for receiving acknowledgement (ACK) packets in TX mode when the `auto_ack` attribute - is enabled for data pipe 0. Thus, RX pipe 0 is appropriated with the TX address (specified - here) when `auto_ack` is enabled for data pipe 0. + is enabled for data pipe 0. Thus, RX pipe 0 is appropriated with the TX address + (specified here) when `auto_ack` is enabled for data pipe 0. close_rx_pipe() ****************** @@ -186,7 +200,9 @@ open_rx_pipe() :param bytearray address: The virtual address to the receiving nRF24L01. If using a ``pipe_number`` greater than 1, then only the MSByte of the address is written, so make sure MSByte (first character) is unique among other simultaneously receiving addresses. - The existing address can be altered by writing a bytearray with a length less than 5. The nRF24L01 will use the first `address_length` number of bytes for the RX address on the specified data pipe. + The existing address can be altered by writing a bytearray with a length less than 5. + The nRF24L01 will use the first `address_length` number of bytes for the RX address on + the specified data pipe. .. note:: The nRF24L01 shares the addresses' last 4 LSBytes on data pipes 2 through 5. These shared LSBytes are determined by the address set to data pipe 1. @@ -208,7 +224,6 @@ listen nRF24L01Pluss_Preliminary_Product_Specification_v1_0.pdf#G1091756>`_, this attribute flushes the RX FIFO, clears the `irq_dr` status flag, and puts nRF24L01 in power up mode. Notice the CE pin is be held HIGH during RX mode. - - `False` disables RX mode. As mentioned in above link, this puts nRF24L01's power in Standby-I (CE pin is LOW meaning low current & no transmissions) mode which is ideal for post-reception work. Disabing RX mode doesn't flush the RX/TX FIFO buffers, so @@ -230,11 +245,17 @@ recv() .. automethod:: circuitpython_nrf24l01.rf24.RF24.recv This function can also be used to fetch the last ACK packet's payload if `ack` is enabled. - + :param int length: An optional parameter to specify how many bytes to read from the RX + FIFO buffer. If this parameter is less than the length of the first available payload + in the RX FIFO buffer, then the payload will remain in the RX FIFO buffer until the + entire payload is fetched by this function. A value greater than the payload's length + will return additional data from other payload(s) in the RX FIFO buffer (if any -- + otherwise just padded zeros). :returns: A `bytearray` of the RX payload data or `None` if there is no payload - If the `dynamic_payloads` attribute is disabled, then the returned bytearray's length - is equal to the user defined `payload_length` attribute for the data pipe that received the payload. + is equal to the user defined `payload_length` attribute for the data pipe that + received the payload. - If the `dynamic_payloads` attribute is enabled, then the returned bytearray's length is equal to the payload's length @@ -261,17 +282,19 @@ send() list/tuple are processed for consecutive transmissions. - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's - length is less than the `payload_length` attribute for pipe 0, then this bytearray is padded with zeros until its length is equal to the `payload_length` attribute for pipe 0. + length is less than the `payload_length` attribute for pipe 0, then this bytearray + is padded with zeros until its length is equal to the `payload_length` attribute for + pipe 0. - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's - length is greater than `payload_length` attribute for pipe 0, then this bytearray's length - is truncated to equal the `payload_length` attribute for pipe 0. + length is greater than `payload_length` attribute for pipe 0, then this bytearray's + length is truncated to equal the `payload_length` attribute for pipe 0. :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for an acknowledgment from the receiving nRF24L01. This parameter directly controls a ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about the payload). Therefore, it takes advantage of an nRF24L01 feature specific to individual payloads, and its value is not saved anywhere. You do not need to specify - this for every payload if the `arc` attribute is disabled, however setting this parameter - to `True` will work despite the `arc` attribute's setting. + this for every payload if the `arc` attribute is disabled, however setting this + parameter to `True` will work despite the `arc` attribute's setting. .. note:: Each transmission is in the form of a packet. This packet contains sections of data around and including the payload. `See Chapter 7.3 in the nRF24L01 @@ -288,9 +311,9 @@ send() there. .. tip:: It is highly recommended that `arc` attribute is enabled (greater than ``0``) when - sending multiple payloads. Test results with the `arc` attribute disabled were very poor - (much < 50% received). This same advice applies to the ``ask_no_ack`` parameter (leave - it as `False` for multiple payloads). + sending multiple payloads. Test results with the `arc` attribute disabled were very + poor (much < 50% received). This same advice applies to the ``ask_no_ack`` parameter + (leave it as `False` for multiple payloads). .. warning:: The nRF24L01 will block usage of the TX FIFO buffer upon failed transmissions. Failed transmission's payloads stay in TX FIFO buffer until the MCU calls `flush_tx()` and `clear_status_flags()`. Therefore, this function will discard @@ -320,7 +343,8 @@ what_happened() - ``RF Power Amplifier`` The current setting of the `pa_level` attribute. - ``CRC bytes`` The current setting of the `crc` attribute - ``Address length`` The current setting of the `address_length` attribute - - ``TX Payload lengths`` The current setting of the `payload_length` attribute for TX operations (concerning data pipe 0) + - ``TX Payload lengths`` The current setting of the `payload_length` attribute for TX + operations (concerning data pipe 0) - ``Auto retry delay`` The current setting of the `ard` attribute - ``Auto retry attempts`` The current setting of the `arc` attribute - ``Packets lost on current channel`` Total amount of packets lost (transmission @@ -352,8 +376,9 @@ what_happened() :param bool dump_pipes: `True` appends the output and prints: - - the current address used for TX transmissions. This value is the entire content of the - nRF24L01's register about the TX address (despite what `address_length` is set to). + - the current address used for TX transmissions. This value is the entire content of + the nRF24L01's register about the TX address (despite what `address_length` is set + to). - ``Pipe [#] ([open/closed]) bound: [address]`` where ``#`` represent the pipe number, the ``open/closed`` status is relative to the pipe's RX status, and ``address`` is the full value stored in the nRF24L01's RX address registers (despite what @@ -372,41 +397,41 @@ dynamic_payloads Default setting is enabled on all pipes. - `True` or ``1`` enables nRF24L01's dynamic payload length feature for all data pipes. The - `payload_length` attribute is ignored when this feature is enabled for respective or all data - pipes. - - `False` or ``0`` disables nRF24L01's dynamic payload length feature for all data pipes. Be sure - to adjust the `payload_length` attribute accordingly when this feature is disabled for any + `payload_length` attribute is ignored when this feature is enabled for respective or all data pipes. - - A `list` or `tuple` containing booleans or integers can be used control this feature per data - pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be ignored - since there are only 6 data pipes. + - `False` or ``0`` disables nRF24L01's dynamic payload length feature for all data pipes. + Be sure to adjust the `payload_length` attribute accordingly when this feature is + disabled for any data pipes. + - A `list` or `tuple` containing booleans or integers can be used control this feature per + data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be + ignored since there are only 6 data pipes. .. note:: - This attribute mostly relates to RX operations, but data pipe 0 applies to TX operations - also. + This attribute mostly relates to RX operations, but data pipe 0 applies to TX + operations also. payload_length ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.payload_length - If the `dynamic_payloads` attribute is enabled for a certain data pipe, this attribute has no - affect on that data pipe. When `dynamic_payloads` is disabled for a certain data pipe, this - attribute is used to specify the payload length on that data pipe in RX mode. + If the `dynamic_payloads` attribute is enabled for a certain data pipe, this attribute has + no affect on that data pipe. When `dynamic_payloads` is disabled for a certain data pipe, + this attribute is used to specify the payload length on that data pipe in RX mode. A valid input value must be: * an `int` in range [1, 32]. Otherwise a `ValueError` exception is thrown. - * a `list` or `tuple` containing integers can be used to control this attribute per data - pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be ignored - since there are only 6 data pipes. if a index's value is ``0``, then the existing setting - will persist (not be changed). + * a `list` or `tuple` containing integers can be used to control this attribute per + data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will + be ignored since there are only 6 data pipes. if a index's value is ``0``, then the + existing setting will persist (not be changed). Default is set to the nRF24L01's maximum of 32 (on all data pipes). .. note:: - This attribute mostly relates to RX operations, but data pipe 0 applies to TX operations - also. + This attribute mostly relates to RX operations, but data pipe 0 applies to TX + operations also. auto_ack ****************************** @@ -416,16 +441,18 @@ auto_ack Default setting is enabled on all data pipes. - `True` or ``1`` enables transmitting automatic acknowledgment packets for all data pipes. - The CRC (cyclic redundancy checking) is enabled automatically by the nRF24L01 if the `auto_ack` attribute is enabled for any data pipe (see also `crc` attribute). - - `False` or ``0`` disables transmitting automatic acknowledgment packets for all data pipes. - The `crc` attribute will remain unaffected when disabling this attribute for any data pipes. - - A `list` or `tuple` containing booleans or integers can be used control this feature per data - pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be ignored - since there are only 6 data pipes. + The CRC (cyclic redundancy checking) is enabled automatically by the nRF24L01 if the + `auto_ack` attribute is enabled for any data pipe (see also `crc` attribute). + - `False` or ``0`` disables transmitting automatic acknowledgment packets for all data + pipes. The `crc` attribute will remain unaffected when disabling this attribute for any + data pipes. + - A `list` or `tuple` containing booleans or integers can be used control this feature per + data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be + ignored since there are only 6 data pipes. .. note:: - This attribute mostly relates to RX operations, but data pipe 0 applies to TX operations - also. + This attribute mostly relates to RX operations, but data pipe 0 applies to TX + operations also. arc ****************************** @@ -447,9 +474,9 @@ ard During this time, the nRF24L01 is listening for the ACK packet. If the `auto_ack` attribute is disabled, this attribute is not applied. - A valid input value must be in range [250, 4000]. Otherwise a `ValueError` exception is thrown. - Default is 1500 for reliability. If this is set to a value that is not multiple of 250, then - the highest multiple of 250 that is no greater than the input value is used. + A valid input value must be in range [250, 4000]. Otherwise a `ValueError` exception is + thrown. Default is 1500 for reliability. If this is set to a value that is not multiple of + 250, then the highest multiple of 250 that is no greater than the input value is used. .. note:: Paraphrased from nRF24L01 specifications sheet: @@ -465,8 +492,8 @@ ack .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ack - Use this attribute to set/check if the custom ACK payloads feature is enabled. Default setting - is `False`. + Use this attribute to set/check if the custom ACK payloads feature is enabled. Default + setting is `False`. - `True` enables the use of custom ACK payloads in the ACK packet when responding to receiving transmissions. @@ -474,10 +501,11 @@ ack receiving transmissions. .. important:: - As `dynamic_payloads` and `auto_ack` attributes are required for this feature to work, they - are automatically enabled (on data pipe 0) as needed. However, it is required to enable the - `auto_ack` and `dynamic_payloads` features on all applicable pipes. Disabling this feature - does not disable the `auto_ack` and `dynamic_payloads` attributes for any data pipe; they work just fine without this feature. + As `dynamic_payloads` and `auto_ack` attributes are required for this feature to work, + they are automatically enabled (on data pipe 0) as needed. However, it is required to + enable the `auto_ack` and `dynamic_payloads` features on all applicable pipes. + Disabling this feature does not disable the `auto_ack` and `dynamic_payloads` + attributes for any data pipe; they work just fine without this feature. load_ack() ****************************** @@ -537,8 +565,8 @@ irq_dr :Returns: - `True` represents Data is in the RX FIFO buffer - - `False` represents anything depending on context (state/condition of FIFO buffers) - ; usually this means the flag's been reset. + - `False` represents anything depending on context (state/condition of FIFO buffers); + usually this means the flag's been reset. Pass ``dataReady`` |irq note| @@ -554,8 +582,8 @@ irq_df :Returns: - `True` signifies the nRF24L01 attemped all configured retries - - `False` represents anything depending on context (state/condition) - ; usually this means the flag's been reset. + - `False` represents anything depending on context (state/condition); usually this + means the flag's been reset. Pass ``dataFail`` |irq note| @@ -571,8 +599,8 @@ irq_ds :Returns: - `True` represents a successful transmission - - `False` represents anything depending on context (state/condition of FIFO buffers) - ; usually this means the flag's been reset. + - `False` represents anything depending on context (state/condition of FIFO buffers); + usually this means the flag's been reset. Pass ``dataSent`` |irq note| @@ -583,8 +611,8 @@ clear_status_flags() .. automethod:: circuitpython_nrf24l01.rf24.RF24.clear_status_flags - Internally, this is automatically called by `send()`, `write()`, `recv()`, and when `listen` - changes from `False` to `True`. + Internally, this is automatically called by `send()`, `write()`, `recv()`, and when + `listen` changes from `False` to `True`. :param bool data_recv: specifies wheather to clear the "RX Data Ready" flag. :param bool data_sent: specifies wheather to clear the "TX Data Sent" flag. @@ -625,7 +653,8 @@ interrupt_config() 1. read payload through `recv()` 2. clear ``dataReady`` status flag (taken care of by using `recv()` in previous step) 3. read FIFO_STATUS register to check if there are more payloads available in RX FIFO - buffer. A call to `pipe` (may require `update()` to be called), `any()` or even ``(False,True)`` as parameters to `fifo()` will get this result. + buffer. A call to `pipe` (may require `update()` to be called), `any()` or even + ``(False,True)`` as parameters to `fifo()` will get this result. 4. if there is more data in RX FIFO, repeat from step 1 data_rate @@ -694,19 +723,19 @@ power current consumption) or Standby-I (moderate current consumption) modes, which Standby mode depends on the state of the CE pin. TX transmissions are only executed during Standby-II by calling `send()` or `write()`. RX transmissions are received during - Standby-II by setting `listen` attribute to `True` (see `Chapter 6.1.2-7 of the nRF24L01+ - Specifications Sheet `_). After using - `send()` or setting `listen` to `False`, the nRF24L01 is left in Standby-I mode (see also - notes on the `write()` function). + `send()` or setting `listen` to `False`, the nRF24L01 is left in Standby-I mode (see + also notes on the `write()` function). pa_level ****************************** .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pa_level - Higher levels mean the transmission will cover a longer distance. Use this attribute to tweak the - nRF24L01 current consumption on projects that don't span large areas. + Higher levels mean the transmission will cover a longer distance. Use this attribute to + tweak the nRF24L01 current consumption on projects that don't span large areas. A valid input value is: @@ -755,24 +784,26 @@ update() .. automethod:: circuitpython_nrf24l01.rf24.RF24.update - Refreshing the status byte is vital to checking status of the interrupt flags, RX pipe number - related to current RX payload, and if the TX FIFO buffer is full. This function returns nothing, - but internally updates the `irq_dr`, `irq_ds`, `irq_df`, `pipe`, and `tx_full` attributes. - Internally this is a helper function to `send()`, and `resend()` functions. + Refreshing the status byte is vital to checking status of the interrupt flags, RX pipe + number related to current RX payload, and if the TX FIFO buffer is full. This function + returns nothing, but internally updates the `irq_dr`, `irq_ds`, `irq_df`, `pipe`, and + `tx_full` attributes. Internally this is a helper function to `send()`, and `resend()` + functions. resend() ****************************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.resend - All returned data from this function follows the same patttern that `send()` returns with the - added condition that this function will return `False` if the TX FIFO buffer is empty. + All returned data from this function follows the same patttern that `send()` returns with + the added condition that this function will return `False` if the TX FIFO buffer is empty. .. note:: The nRF24L01 normally removes a payload from the TX FIFO buffer after successful transmission, but not when this function is called. The payload (successfully transmitted or not) will remain in the TX FIFO buffer until `flush_tx()` is called to remove them. Alternatively, using this function also allows the failed payload to be - over-written by using `send()` or `write()` to load a new payload into the TX FIFO buffer. + over-written by using `send()` or `write()` to load a new payload into the TX FIFO + buffer. write() ****************************** @@ -783,12 +814,13 @@ write() than 0 and less than 32 bytes, otherwise a `ValueError` exception is thrown. - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's - length is less than the `payload_length` attribute for data pipe 0, then this bytearray - is padded with zeros until its length is equal to the `payload_length` attribute for - data pipe 0. + length is less than the `payload_length` attribute for data pipe 0, then this + bytearray is padded with zeros until its length is equal to the `payload_length` + attribute for data pipe 0. - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's - length is greater than `payload_length` attribute for data pipe 0, then this bytearray's - length is truncated to equal the `payload_length` attribute for data pipe 0. + length is greater than `payload_length` attribute for data pipe 0, then this + bytearray's length is truncated to equal the `payload_length` attribute for data + pipe 0. :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for an acknowledgment from the receiving nRF24L01. This parameter directly controls a ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about @@ -831,9 +863,9 @@ write() `_, we have to assume radio damage or misbehavior as a result of disobeying the 4 ms rule. See also `table 18 - in the nRF24L01 specification sheet `_ for calculating - an adequate transmission timeout sentinal. + in the nRF24L01 specification sheet `_ for + calculating an adequate transmission timeout sentinal. flush_rx() ****************************** @@ -870,7 +902,8 @@ fifo() - `True` tests if the specified FIFO buffer is empty. - `False` tests if the specified FIFO buffer is full. - `None` (when not specified) returns a 2 bit number representing both empty (bit 1) & - full (bit 0) tests related to the FIFO buffer specified using the ``about_tx`` parameter. + full (bit 0) tests related to the FIFO buffer specified using the ``about_tx`` + parameter. :returns: - A `bool` answer to the question: "Is the [TX/RX]:[`True`/`False`] FIFO buffer [empty/full]:[`True`/`False`]? @@ -914,17 +947,17 @@ rpd The RPD flag is triggered in the following cases: - 1. During RX mode (`listen` = `True`) and an arbitrary RF transmission with a gain above - -64 dBm threshold is/was present. + 1. During RX mode (`listen` = `True`) and an arbitrary RF transmission with a gain + above -64 dBm threshold is/was present. 2. When a packet is received (instigated by the nRF24L01 used to detect/"listen" for incoming packets). .. note:: See also `section 6.4 of the Specification Sheet concerning the RPD flag `_. Ambient temperature - affects the -64 dBm threshold. The latching of this flag happens differently under certain - conditions. + nRF24L01Pluss_Preliminary_Product_Specification_v1_0.pdf#G1160291>`_. Ambient + temperature affects the -64 dBm threshold. The latching of this flag happens + differently under certain conditions. start_carrier_wave() ****************************** From 47ce3660a225d74b558aa31de4ba03ac79425737 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sun, 20 Sep 2020 01:08:28 -0700 Subject: [PATCH 066/127] fix sphinx build --- docs/api.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api.rst b/docs/api.rst index 52a4177..02c8865 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -245,6 +245,7 @@ recv() .. automethod:: circuitpython_nrf24l01.rf24.RF24.recv This function can also be used to fetch the last ACK packet's payload if `ack` is enabled. + :param int length: An optional parameter to specify how many bytes to read from the RX FIFO buffer. If this parameter is less than the length of the first available payload in the RX FIFO buffer, then the payload will remain in the RX FIFO buffer until the From 01f4b53f73f8ea03f4afbd89728924c4268fb132 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sun, 20 Sep 2020 01:19:56 -0700 Subject: [PATCH 067/127] use latest pylint and black in buld.yml --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7c01db9..34241a2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,7 @@ jobs: - name: Pip install pylint, black, & Sphinx run: | - pip install --force-reinstall pylint==1.9.2 black==19.10b0 Sphinx sphinx-rtd-theme + pip install pylint black Sphinx sphinx-rtd-theme - name: Library version run: git describe --dirty --always --tags - name: PyLint From 6dfa00ffbffe2628fed0fc3af205460d7f620897 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sun, 20 Sep 2020 01:53:30 -0700 Subject: [PATCH 068/127] revise info about recv()'d data --- docs/api.rst | 53 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 02c8865..77bf3e3 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -247,16 +247,29 @@ recv() This function can also be used to fetch the last ACK packet's payload if `ack` is enabled. :param int length: An optional parameter to specify how many bytes to read from the RX - FIFO buffer. If this parameter is less than the length of the first available payload - in the RX FIFO buffer, then the payload will remain in the RX FIFO buffer until the - entire payload is fetched by this function. A value greater than the payload's length - will return additional data from other payload(s) in the RX FIFO buffer (if any -- - otherwise just padded zeros). - :returns: A `bytearray` of the RX payload data or `None` if there is no payload - - - If the `dynamic_payloads` attribute is disabled, then the returned bytearray's length - is equal to the user defined `payload_length` attribute for the data pipe that - received the payload. + FIFO buffer. This parameter is not contrained in any way. + + - If this parameter is less than the length of the first available payload in the + RX FIFO buffer, then the payload will remain in the RX FIFO buffer until the + entire payload is fetched by this function. + - If this parameter is greater than the next available payload's length, then + additional data from other payload(s) in the RX FIFO buffer are returned. + + .. note:: + The nRF24L01 will repeatedly return the last byte fetched from the RX FIFO + buffer when there is no data to return (even if the RX FIFO is empty). Be + aware that a payload is only removed from the RX FIFO buffer when the entire + payload has been fetched by this function. Notice that this function always + starts reading data from the first byte of the first available payload (if + any) in the RX FIFO buffer. + :returns: + A `bytearray` of the RX payload data or `None` if there is no payload. If the + ``length`` parameter is not specified, then one of the following two scenarios is + applied. + + - If the `dynamic_payloads` attribute is disabled, then the returned bytearray's + length is equal to the user defined `payload_length` attribute for the data pipe + that received the payload. - If the `dynamic_payloads` attribute is enabled, then the returned bytearray's length is equal to the payload's length @@ -273,12 +286,12 @@ send() - `False` if transmission fails. Transmission failure can only be detected if `arc` is greater than ``0``. - `True` if transmission succeeds. - - `bytearray` or `None` when the `ack` attribute is `True`. Because the payload expects - a responding custom ACK payload, the response is returned (upon successful + - `bytearray` or `None` when the `ack` attribute is `True`. Because the payload + expects a responding custom ACK payload, the response is returned (upon successful transmission) as a `bytearray` (or `None` if ACK payload is empty) - :param bytearray,list,tuple buf: The payload to transmit. This bytearray must have a length - in range [1, 32], otherwise a `ValueError` exception is thrown. This can + :param bytearray,list,tuple buf: The payload to transmit. This bytearray must have a + length in range [1, 32], otherwise a `ValueError` exception is thrown. This can also be a list or tuple of payloads (`bytearray`); in which case, all items in the list/tuple are processed for consecutive transmissions. @@ -291,8 +304,8 @@ send() length is truncated to equal the `payload_length` attribute for pipe 0. :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for an acknowledgment from the receiving nRF24L01. This parameter directly controls a - ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about - the payload). Therefore, it takes advantage of an nRF24L01 feature specific to + ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information + about the payload). Therefore, it takes advantage of an nRF24L01 feature specific to individual payloads, and its value is not saved anywhere. You do not need to specify this for every payload if the `arc` attribute is disabled, however setting this parameter to `True` will work despite the `arc` attribute's setting. @@ -311,10 +324,10 @@ send() notes on `resend()` as using this parameter carries the same implications documented there. - .. tip:: It is highly recommended that `arc` attribute is enabled (greater than ``0``) when - sending multiple payloads. Test results with the `arc` attribute disabled were very - poor (much < 50% received). This same advice applies to the ``ask_no_ack`` parameter - (leave it as `False` for multiple payloads). + .. tip:: It is highly recommended that `arc` attribute is enabled (greater than ``0``) + when sending multiple payloads. Test results with the `arc` attribute disabled were + very poor (much < 50% received). This same advice applies to the ``ask_no_ack`` + parameter (leave it as `False` for multiple payloads). .. warning:: The nRF24L01 will block usage of the TX FIFO buffer upon failed transmissions. Failed transmission's payloads stay in TX FIFO buffer until the MCU calls `flush_tx()` and `clear_status_flags()`. Therefore, this function will discard From ad41d57bea44feec9ae21074c37664c541b4ebb7 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sun, 20 Sep 2020 03:14:26 -0700 Subject: [PATCH 069/127] (+) configuration API to match TMRh20 docs --- docs/api.rst | 490 ++++++++++++++++++++++++++------------------------- 1 file changed, 247 insertions(+), 243 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 77bf3e3..27f9f66 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -153,14 +153,6 @@ Contructor :param ~digitalio.DigitalInOut ce: The digital output pin that is connected to the nRF24L01's CE (Chip Enable) pin. This is required. -address_length -****************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.address_length - - A valid input value must be an `int` in range [3, 5]. Otherwise a `ValueError` exception is - thrown. Default is set to the nRF24L01's maximum of 5. - open_tx_pipe() ****************** @@ -322,12 +314,13 @@ send() parameter is meant to slow down CircuitPython devices just enough for the Raspberry Pi to catch up (due to the Raspberry Pi's seemingly slower SPI speeds). See also notes on `resend()` as using this parameter carries the same implications documented - there. + there. This parameter has no effect if the ``ask_no_ack`` parameter is set to `True` + or if `arc` is disabled. .. tip:: It is highly recommended that `arc` attribute is enabled (greater than ``0``) when sending multiple payloads. Test results with the `arc` attribute disabled were - very poor (much < 50% received). This same advice applies to the ``ask_no_ack`` - parameter (leave it as `False` for multiple payloads). + rather poor (less than 79% received by a Raspberry Pi). This same advice applies to + the ``ask_no_ack`` parameter (leave it as `False` for multiple payloads). .. warning:: The nRF24L01 will block usage of the TX FIFO buffer upon failed transmissions. Failed transmission's payloads stay in TX FIFO buffer until the MCU calls `flush_tx()` and `clear_status_flags()`. Therefore, this function will discard @@ -338,11 +331,6 @@ send() Advanced API ------------ -CSN_DELAY -****************************** - -.. autodata:: circuitpython_nrf24l01.rf24.CSN_DELAY - what_happened() ****************************** @@ -403,124 +391,6 @@ what_happened() Default is `False` and skips this extra information. -dynamic_payloads -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.dynamic_payloads - - Default setting is enabled on all pipes. - - - `True` or ``1`` enables nRF24L01's dynamic payload length feature for all data pipes. The - `payload_length` attribute is ignored when this feature is enabled for respective or all - data pipes. - - `False` or ``0`` disables nRF24L01's dynamic payload length feature for all data pipes. - Be sure to adjust the `payload_length` attribute accordingly when this feature is - disabled for any data pipes. - - A `list` or `tuple` containing booleans or integers can be used control this feature per - data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be - ignored since there are only 6 data pipes. - - .. note:: - This attribute mostly relates to RX operations, but data pipe 0 applies to TX - operations also. - -payload_length -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.payload_length - - If the `dynamic_payloads` attribute is enabled for a certain data pipe, this attribute has - no affect on that data pipe. When `dynamic_payloads` is disabled for a certain data pipe, - this attribute is used to specify the payload length on that data pipe in RX mode. - - A valid input value must be: - - * an `int` in range [1, 32]. Otherwise a `ValueError` exception is thrown. - * a `list` or `tuple` containing integers can be used to control this attribute per - data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will - be ignored since there are only 6 data pipes. if a index's value is ``0``, then the - existing setting will persist (not be changed). - - Default is set to the nRF24L01's maximum of 32 (on all data pipes). - - .. note:: - This attribute mostly relates to RX operations, but data pipe 0 applies to TX - operations also. - -auto_ack -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.auto_ack - - Default setting is enabled on all data pipes. - - - `True` or ``1`` enables transmitting automatic acknowledgment packets for all data pipes. - The CRC (cyclic redundancy checking) is enabled automatically by the nRF24L01 if the - `auto_ack` attribute is enabled for any data pipe (see also `crc` attribute). - - `False` or ``0`` disables transmitting automatic acknowledgment packets for all data - pipes. The `crc` attribute will remain unaffected when disabling this attribute for any - data pipes. - - A `list` or `tuple` containing booleans or integers can be used control this feature per - data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be - ignored since there are only 6 data pipes. - - .. note:: - This attribute mostly relates to RX operations, but data pipe 0 applies to TX - operations also. - -arc -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.arc - - The `auto_ack` attribute must be enabled on the receiving nRF24L01 respective data pipe, - otherwise this attribute will make `send()` seem like it failed. - - A valid input value must be in range [0, 15]. Otherwise a `ValueError` exception is thrown. - Default is set to 3. A value of ``0`` disables the automatic re-transmit feature and - considers all payload transmissions a success. - -ard -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ard - - During this time, the nRF24L01 is listening for the ACK packet. If the - `auto_ack` attribute is disabled, this attribute is not applied. - - A valid input value must be in range [250, 4000]. Otherwise a `ValueError` exception is - thrown. Default is 1500 for reliability. If this is set to a value that is not multiple of - 250, then the highest multiple of 250 that is no greater than the input value is used. - - .. note:: Paraphrased from nRF24L01 specifications sheet: - - Please take care when setting this parameter. If the custom ACK payload is more than 15 - bytes in 2 Mbps data rate, the `ard` must be 500µS or more. If the custom ACK payload - is more than 5 bytes in 1 Mbps data rate, the `ard` must be 500µS or more. In 250kbps - data rate (even when there is no custom ACK payload) the `ard` must be 500µS or more. - - See `data_rate` attribute on how to set the data rate of the nRF24L01's transmissions. - -ack -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ack - - Use this attribute to set/check if the custom ACK payloads feature is enabled. Default - setting is `False`. - - - `True` enables the use of custom ACK payloads in the ACK packet when responding to - receiving transmissions. - - `False` disables the use of custom ACK payloads in the ACK packet when responding to - receiving transmissions. - - .. important:: - As `dynamic_payloads` and `auto_ack` attributes are required for this feature to work, - they are automatically enabled (on data pipe 0) as needed. However, it is required to - enable the `auto_ack` and `dynamic_payloads` features on all applicable pipes. - Disabling this feature does not disable the `auto_ack` and `dynamic_payloads` - attributes for any data pipe; they work just fine without this feature. - load_ack() ****************************** @@ -642,80 +512,6 @@ clear_status_flags() nRF24L01Pluss_Preliminary_Product_Specification_v1_0.pdf#G1047965>`_ for an outline of proper behavior. -interrupt_config() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.interrupt_config - - The digital signal from the nRF24L01's IRQ pin is active LOW. (write-only) - - :param bool data_recv: If this is `True`, then IRQ pin goes active when there is new data - to read in the RX FIFO buffer. Default setting is `True` - :param bool data_sent: If this is `True`, then IRQ pin goes active when a payload from TX - buffer is successfully transmit. Default setting is `True` - :param bool data_fail: If this is `True`, then IRQ pin goes active when maximum number of - attempts to re-transmit the packet have been reached. If `auto_ack` attribute is - disabled, then this IRQ event is not used. Default setting is `True` - - .. note:: To fetch the status (not configuration) of these IRQ flags, use the `irq_df`, - `irq_ds`, `irq_dr` attributes respectively. - - .. tip:: Paraphrased from nRF24L01+ Specification Sheet: - - The procedure for handling ``data_recv`` IRQ should be: - - 1. read payload through `recv()` - 2. clear ``dataReady`` status flag (taken care of by using `recv()` in previous step) - 3. read FIFO_STATUS register to check if there are more payloads available in RX FIFO - buffer. A call to `pipe` (may require `update()` to be called), `any()` or even - ``(False,True)`` as parameters to `fifo()` will get this result. - 4. if there is more data in RX FIFO, repeat from step 1 - -data_rate -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.data_rate - - A valid input value is: - - - ``1`` sets the frequency data rate to 1 Mbps - - ``2`` sets the frequency data rate to 2 Mbps - - ``250`` sets the frequency data rate to 250 Kbps - - Any invalid input throws a `ValueError` exception. Default is 1 Mbps. - - .. warning:: 250 Kbps can be buggy on the non-plus models of the nRF24L01 product line. If - you use 250 Kbps data rate, and some transmissions report failed by the transmitting - nRF24L01, even though the same packet in question actually reports received by the - receiving nRF24L01, then try a higher data rate. CAUTION: Higher data rates mean less - maximum distance between nRF24L01 transceivers (and vise versa). - -channel -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.channel - - A valid input value must be in range [0, 125] (that means [2.4, 2.525] GHz). Otherwise a - `ValueError` exception is thrown. Default is ``76`` (2.476 GHz). - -crc -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.crc - - CRC is a way of making sure that the transmission didn't get corrupted over the air. - - A valid input value must be: - - - ``0`` disables CRC (no anti-corruption of data) - - ``1`` enables CRC encoding scheme using 1 byte (weak anti-corruption of data) - - ``2`` enables CRC encoding scheme using 2 bytes (better anti-corruption of data) - - Any invalid input throws a `ValueError` exception. Default is enabled using 2 bytes. - - .. note:: The nRF24L01 automatically enables CRC if automatic acknowledgment feature is - enabled (see `auto_ack` attribute). - power ****************************** @@ -743,41 +539,6 @@ power `send()` or setting `listen` to `False`, the nRF24L01 is left in Standby-I mode (see also notes on the `write()` function). -pa_level -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pa_level - - Higher levels mean the transmission will cover a longer distance. Use this attribute to - tweak the nRF24L01 current consumption on projects that don't span large areas. - - A valid input value is: - - - ``-18`` sets the nRF24L01's power amplifier to -18 dBm (lowest) - - ``-12`` sets the nRF24L01's power amplifier to -12 dBm - - ``-6`` sets the nRF24L01's power amplifier to -6 dBm - - ``0`` sets the nRF24L01's power amplifier to 0 dBm (highest) - - If this attribute is set to a `list` or `tuple`, then the list/tuple must contain the - desired power amplifier level (from list above) at index 0 and a `bool` to control - the Low Noise Amplifier (LNA) feature at index 1. All other indices will be discarded. - - .. note:: - The LNA feature only applies to the nRF24L01 (non-plus variant). This - includes boards with the RFX24C01-based PA/LNA muxing IC attached to an - SMA-type detachable antenna. - - Any invalid input will invoke the default of 0 dBm with LNA enabled. - -is_lna_enabled -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.is_lna_enabled - - See `pa_level` attribute about how to set this. Default is always enabled, but this - feature is specific to certain nRF24L01-based circuits. Check with your module's - manufacturer to see is it can toggle the Low Noise Amplifier feature. - tx_full ****************************** @@ -942,6 +703,14 @@ pipe - The `int` identifying pipe number [0,5] that received the next available payload in the RX FIFO buffer. +address_length +****************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.address_length + + A valid input value must be an `int` in range [3, 5]. Otherwise a `ValueError` exception is + thrown. Default is set to the nRF24L01's maximum of 5. + address() ****************************** @@ -1007,3 +776,238 @@ stop_carrier_wave() .. note:: Calling this function puts the nRF24L01 to sleep (AKA power down mode). + +Configuration API +----------------- + +CSN_DELAY +****************************** + +.. autodata:: circuitpython_nrf24l01.rf24.CSN_DELAY + +dynamic_payloads +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.dynamic_payloads + + Default setting is enabled on all pipes. + + - `True` or ``1`` enables nRF24L01's dynamic payload length feature for all data pipes. The + `payload_length` attribute is ignored when this feature is enabled for respective or all + data pipes. + - `False` or ``0`` disables nRF24L01's dynamic payload length feature for all data pipes. + Be sure to adjust the `payload_length` attribute accordingly when this feature is + disabled for any data pipes. + - A `list` or `tuple` containing booleans or integers can be used control this feature per + data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be + ignored since there are only 6 data pipes. + + .. note:: + This attribute mostly relates to RX operations, but data pipe 0 applies to TX + operations also. + +payload_length +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.payload_length + + If the `dynamic_payloads` attribute is enabled for a certain data pipe, this attribute has + no affect on that data pipe. When `dynamic_payloads` is disabled for a certain data pipe, + this attribute is used to specify the payload length on that data pipe in RX mode. + + A valid input value must be: + + * an `int` in range [1, 32]. Otherwise a `ValueError` exception is thrown. + * a `list` or `tuple` containing integers can be used to control this attribute per + data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will + be ignored since there are only 6 data pipes. if a index's value is ``0``, then the + existing setting will persist (not be changed). + + Default is set to the nRF24L01's maximum of 32 (on all data pipes). + + .. note:: + This attribute mostly relates to RX operations, but data pipe 0 applies to TX + operations also. + +auto_ack +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.auto_ack + + Default setting is enabled on all data pipes. + + - `True` or ``1`` enables transmitting automatic acknowledgment packets for all data pipes. + The CRC (cyclic redundancy checking) is enabled automatically by the nRF24L01 if the + `auto_ack` attribute is enabled for any data pipe (see also `crc` attribute). + - `False` or ``0`` disables transmitting automatic acknowledgment packets for all data + pipes. The `crc` attribute will remain unaffected when disabling this attribute for any + data pipes. + - A `list` or `tuple` containing booleans or integers can be used control this feature per + data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be + ignored since there are only 6 data pipes. + + .. note:: + This attribute mostly relates to RX operations, but data pipe 0 applies to TX + operations also. + +arc +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.arc + + The `auto_ack` attribute must be enabled on the receiving nRF24L01 respective data pipe, + otherwise this attribute will make `send()` seem like it failed. + + A valid input value must be in range [0, 15]. Otherwise a `ValueError` exception is thrown. + Default is set to 3. A value of ``0`` disables the automatic re-transmit feature and + considers all payload transmissions a success. + +ard +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ard + + During this time, the nRF24L01 is listening for the ACK packet. If the + `auto_ack` attribute is disabled, this attribute is not applied. + + A valid input value must be in range [250, 4000]. Otherwise a `ValueError` exception is + thrown. Default is 1500 for reliability. If this is set to a value that is not multiple of + 250, then the highest multiple of 250 that is no greater than the input value is used. + + .. note:: Paraphrased from nRF24L01 specifications sheet: + + Please take care when setting this parameter. If the custom ACK payload is more than 15 + bytes in 2 Mbps data rate, the `ard` must be 500µS or more. If the custom ACK payload + is more than 5 bytes in 1 Mbps data rate, the `ard` must be 500µS or more. In 250kbps + data rate (even when there is no custom ACK payload) the `ard` must be 500µS or more. + + See `data_rate` attribute on how to set the data rate of the nRF24L01's transmissions. + +ack +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ack + + Use this attribute to set/check if the custom ACK payloads feature is enabled. Default + setting is `False`. + + - `True` enables the use of custom ACK payloads in the ACK packet when responding to + receiving transmissions. + - `False` disables the use of custom ACK payloads in the ACK packet when responding to + receiving transmissions. + + .. important:: + As `dynamic_payloads` and `auto_ack` attributes are required for this feature to work, + they are automatically enabled (on data pipe 0) as needed. However, it is required to + enable the `auto_ack` and `dynamic_payloads` features on all applicable pipes. + Disabling this feature does not disable the `auto_ack` and `dynamic_payloads` + attributes for any data pipe; they work just fine without this feature. + +interrupt_config() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.interrupt_config + + The digital signal from the nRF24L01's IRQ pin is active LOW. (write-only) + + :param bool data_recv: If this is `True`, then IRQ pin goes active when there is new data + to read in the RX FIFO buffer. Default setting is `True` + :param bool data_sent: If this is `True`, then IRQ pin goes active when a payload from TX + buffer is successfully transmit. Default setting is `True` + :param bool data_fail: If this is `True`, then IRQ pin goes active when maximum number of + attempts to re-transmit the packet have been reached. If `auto_ack` attribute is + disabled, then this IRQ event is not used. Default setting is `True` + + .. note:: To fetch the status (not configuration) of these IRQ flags, use the `irq_df`, + `irq_ds`, `irq_dr` attributes respectively. + + .. tip:: Paraphrased from nRF24L01+ Specification Sheet: + + The procedure for handling ``data_recv`` IRQ should be: + + 1. read payload through `recv()` + 2. clear ``dataReady`` status flag (taken care of by using `recv()` in previous step) + 3. read FIFO_STATUS register to check if there are more payloads available in RX FIFO + buffer. A call to `pipe` (may require `update()` to be called), `any()` or even + ``(False,True)`` as parameters to `fifo()` will get this result. + 4. if there is more data in RX FIFO, repeat from step 1 + +data_rate +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.data_rate + + A valid input value is: + + - ``1`` sets the frequency data rate to 1 Mbps + - ``2`` sets the frequency data rate to 2 Mbps + - ``250`` sets the frequency data rate to 250 Kbps + + Any invalid input throws a `ValueError` exception. Default is 1 Mbps. + + .. warning:: 250 Kbps can be buggy on the non-plus models of the nRF24L01 product line. If + you use 250 Kbps data rate, and some transmissions report failed by the transmitting + nRF24L01, even though the same packet in question actually reports received by the + receiving nRF24L01, then try a higher data rate. CAUTION: Higher data rates mean less + maximum distance between nRF24L01 transceivers (and vise versa). + +channel +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.channel + + A valid input value must be in range [0, 125] (that means [2.4, 2.525] GHz). Otherwise a + `ValueError` exception is thrown. Default is ``76`` (2.476 GHz). + +crc +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.crc + + CRC is a way of making sure that the transmission didn't get corrupted over the air. + + A valid input value must be: + + - ``0`` disables CRC (no anti-corruption of data) + - ``1`` enables CRC encoding scheme using 1 byte (weak anti-corruption of data) + - ``2`` enables CRC encoding scheme using 2 bytes (better anti-corruption of data) + + Any invalid input throws a `ValueError` exception. Default is enabled using 2 bytes. + + .. note:: The nRF24L01 automatically enables CRC if automatic acknowledgment feature is + enabled (see `auto_ack` attribute). + +pa_level +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pa_level + + Higher levels mean the transmission will cover a longer distance. Use this attribute to + tweak the nRF24L01 current consumption on projects that don't span large areas. + + A valid input value is: + + - ``-18`` sets the nRF24L01's power amplifier to -18 dBm (lowest) + - ``-12`` sets the nRF24L01's power amplifier to -12 dBm + - ``-6`` sets the nRF24L01's power amplifier to -6 dBm + - ``0`` sets the nRF24L01's power amplifier to 0 dBm (highest) + + If this attribute is set to a `list` or `tuple`, then the list/tuple must contain the + desired power amplifier level (from list above) at index 0 and a `bool` to control + the Low Noise Amplifier (LNA) feature at index 1. All other indices will be discarded. + + .. note:: + The LNA feature only applies to the nRF24L01 (non-plus variant). This + includes boards with the RFX24C01-based PA/LNA muxing IC attached to an + SMA-type detachable antenna. + + Any invalid input will invoke the default of 0 dBm with LNA enabled. + +is_lna_enabled +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.is_lna_enabled + + See `pa_level` attribute about how to set this. Default is always enabled, but this + feature is specific to certain nRF24L01-based circuits. Check with your module's + manufacturer to see is it can toggle the Low Noise Amplifier feature. From f2594e33d47d03cc05a67275fe297d0bf9225875 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sun, 20 Sep 2020 05:07:25 -0700 Subject: [PATCH 070/127] enable LNA by default; discuss PA/LNA in readme --- README.rst | 92 ++++++++++++++++++++++------- circuitpython_nrf24l01/rf24.py | 7 ++- circuitpython_nrf24l01/rf24_lite.py | 2 +- 3 files changed, 78 insertions(+), 23 deletions(-) diff --git a/README.rst b/README.rst index 3c2cfea..9f91482 100644 --- a/README.rst +++ b/README.rst @@ -20,12 +20,8 @@ :alt: Total PyPI downloads :target: https://pepy.tech/project/circuitpython-nrf24l01 -.. image:: https://img.shields.io/github/downloads/2bndy5/CircuitPython_nRF24L01/total?color=success&label=Downloads&logo=github&style=plastic - :alt: GitHub All Release Downloads - :target: https://github.com/2bndy5/CircuitPython_nRF24L01/releases - Introduction ------------- +============ Circuitpython driver library for the nRF24L01 transceiver @@ -39,7 +35,7 @@ compatibilty with the Raspberry Pi and other Circuitpython compatible devices. M * Author(s): Damien P. George, Peter Hinch, Rhys Thomas, Brendan Doherty Features currently supported ----------------------------- +============================ * change the addresses' length (can be 3 to 5 bytes long) * dynamically sized payloads (max 32 bytes each) or statically sized payloads @@ -59,7 +55,7 @@ Features currently supported * a nRF24L01 driven by this library can communicate with a nRF24L01 on an Arduino driven by the `TMRh20 RF24 library `_. See the nrf24l01_2arduino_handling_data.py code in the `examples folder of this library's repository `_ Features currently unsupported -------------------------------- +============================== * as of yet, no [intended] implementation for Multiceiver mode (up to 6 TX nRF24L01 "talking" to 1 RX nRF24L01 simultaneously). Although this might be acheived easily using the "automatic retry delay" (`ard`) and "automatic retry count" (`arc`) attributes set accordingly (varyingly high -- this has not been tested). @@ -145,31 +141,85 @@ To run the simple example, navigate to this repository's "examples" folder in th Transmission took 24.0 ms +Where do I get 1? +================= -About the nRF24L01 -================== +See the store links on the sidebar or just google "nRF24L01+". It is worth noting that you +generally want to buy more than 1 as you need 2 for testing -- 1 to send & 1 to receive and +vise versa. This library has been tested on a cheaply bought 6 pack from Amazon.com, but don't +take Amazon or eBay for granted! There are other wireless transceivers that are NOT compatible +with this library. For instance, the esp8266-01 (also sold in packs) is NOT compatible with +this library, but looks very similar to the nRF24L01+ and could lead to an accidental purchase. -More finite details about the nRF24L01 are available from the datasheet (referenced here in the documentation as the `nRF24L01+ Specification Sheet `_) +About the nRF24L01+ +=================== -Future Project Ideas/Additions -============================== +Stablizing the power input to the VCC and GND using parallel capacitors (100 µF + an optional +0.1µF) provides significant performance increases. This stability is very highly recommended! +More finite details about the nRF24L01 are available from the datasheet (referenced here in +the documentation as the `nRF24L01+ Specification Sheet `_) - The following are only ideas; they are not currently supported by this circuitpython library. +About the nRF24L01+PA+LNA modules +================================= - * `There's a few blog posts by Nerd Ralph demonstrating how to use the nRF24L01 via 2 or 3 pins `_ (uses custom bitbanging SPI functions and an external circuit involving a resistor and a capacitor) - * network linking layer, maybe something like `TMRh20's RF24Network `_ - * add a fake BLE module for sending BLE beacon advertisments from the nRF24L01 as outlined by `Dmitry Grinberg in his write-up (including C source code) `_. We've started developing this, but fell short of success in `the BLEfake branch of this library's repository `_ - * implement the Gazelle-based protocol used by the BBC micro-bit (`makecode.com's radio blocks `_). +You may find variants of the nRF24L01 transceiver that are marketed as "nRF24L01+PA+LNA". +These modules are distinct in the fact that they come with a detachable (SMA-type) antenna. +They employ seperate RFX24C01 IC with the antenna for enhanced Power Amplification (PA) and +Low Noise Amplification (LNA) features. While they boast greater range with the same +functionality, they are subject to a couple lesser known (and lesser advertised) drawbacks: -Where do I get 1? -================= +1. Stronger power source. Below is a chart of advertised current requirements that many MCU + boards' 3V regulators may not be able to handle. + + .. csv-table:: + :header: Specification, Value + :widths: 10,5 -See the store links on the sidebar or just google "nRF24L01+". It is worth noting that you generally want to buy more than 1 as you need 2 for testing -- 1 to send & 1 to receive and vise versa. This library has been tested on a cheaply bought 10 pack from Amazon.com using a highly recommended capacitor (100 µF) on the power pins. Don't get lost on Amazon or eBay! There are other wireless transceivers that are NOT compatible with this library. For instance, the esp8266-01 (also sold in packs) is NOT compatible with this library, but looks very similar to the nRF24L01+ and could lead to an accidental purchase. + "Emission mode current(peak)", "115 mA" + "Receive Mode current(peak)", "45 mA" + "Power-down mode current", "4.2 µA" +2. Needs sheilding from electromagnetic interference. Sheilding works best when it has a path + to ground (GND pin) nRF24L01(+) clones and counterfeits =================================== -This library does not directly support clones/counterfeits as there is no way for the library to differentiate between an actual nRF24L01+ and a clone/counterfeit. To determine if your purchase is a counterfeit, please contact the retailer you purxhased from (`reading this article and its links might help `_). The most notable clone is the `Si24R1 `_. I could not find the `Si24R1 datasheet `_ in english. Troubleshooting the SI24R1 may require `replacing the onboard antennae with a wire `_. Furthermore, the Si24R1 has different power amplifier options as noted in the `RF_PWR section (bits 0 through 2) of the RF_SETUP register (hex address 6) of the datasheet `_. While the options' values differ from those identified by this library's API, the underlying commands to configure those options are almost identical to the nRF24L01. Other known clones include the bk242x (AKA RFM7x). +This library does not directly support clones/counterfeits as there is no way for the library +to differentiate between an actual nRF24L01+ and a clone/counterfeit. To determine if your +purchase is a counterfeit, please contact the retailer you purxhased from (`reading this +article and its links might help +`_). The most notable clone is the `Si24R1 `_. I could not find +the `Si24R1 datasheet `_ in english. Troubleshooting +the SI24R1 may require `replacing the onboard antennae with a wire +`_. Furthermore, the Si24R1 has different power +amplifier options as noted in the `RF_PWR section (bits 0 through 2) of the RF_SETUP register +(hex address 6) of the datasheet `_. +While the options' values differ from those identified by this library's API, the +underlying commands to configure those options are almost identical to the nRF24L01. Other +known clones include the bk242x (AKA RFM7x). + +Future Project Ideas/Additions +============================== + + The following are only ideas; they are not currently supported by this circuitpython library. + + * `There's a few blog posts by Nerd Ralph demonstrating how to use the nRF24L01 via 2 or 3 + pins `_ (uses custom bitbanging SPI functions and an external circuit involving a + resistor and a capacitor) + * network linking layer, maybe something like `TMRh20's RF24Network + `_ + * add a fake BLE module for sending BLE beacon advertisments from the nRF24L01 as outlined + by `Dmitry Grinberg in his write-up (including C source code) + `_. + We've started developing this, but fell short of success in `the BLEfake branch of this + library's repository `_ + * implement the Gazelle-based protocol used by the BBC micro-bit (`makecode.com's radio + blocks `_). Contributing ============ diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index b0b64dc..aaac2da 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -81,7 +81,7 @@ def __init__(self, spi, csn, ce, spi_frequency=10000000): # pre-configure the SETUP_RETR register self._retry_setup = 0x53 # ard = 1500; arc = 3 # pre-configure the RF_SETUP register - self._rf_setup = 0x06 # 1 Mbps data_rate, and 0 dbm pa_level + self._rf_setup = 0x07 # 1 Mbps data_rate, and 0 dbm pa_level # pre-configure dynamic_payloads & auto_ack for RX operations self._dyn_pl = 0x3F # 0x3F = enable dynamic_payloads on all pipes self._aa = 0x3F # 0x3F = enable auto_ack on all pipes @@ -347,6 +347,11 @@ def what_happened(self, dump_pipes=False): ) ) print("RF Power Amplifier________{} dbm".format(self.pa_level)) + print( + "RF Low Noise Amplifier____{}".format( + "Enabled" if self.is_lna_enabled else "Disabled" + ) + ) print("CRC bytes_________________{}".format(self.crc)) print("Address length____________{} bytes".format(self.address_length)) print("TX Payload lengths________{} bytes".format(self.payload_length[0])) diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index 7221c9b..2231dc5 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -17,7 +17,7 @@ def __init__(self, spi, csn, ce): raise RuntimeError("nRF24L01 Hardware not responding") self.power = False self._reg_write(3, 3) - self._reg_write(6, 6) + self._reg_write(6, 7) self._reg_write(2, 0) self._reg_write(0x1C, 0x3F) self._reg_write(1, 0x3F) From 1c0fe3cfd135329c7f7e986660462258db7af1fc Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sun, 20 Sep 2020 05:21:59 -0700 Subject: [PATCH 071/127] enable auto_ack when dynamic_payloads are enabled --- circuitpython_nrf24l01/rf24.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index aaac2da..6b9e115 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -463,6 +463,10 @@ def dynamic_payloads(self, enable): if bool(self._features & 4) != (self._dyn_pl & 1): self._features = (self._features & 3) | ((self._dyn_pl & 1) << 2) self._reg_write(TX_FEATURE, self._features) + if self._dyn_pl: + self._aa |= self._dyn_pl + self._reg_write(AUTO_ACK, self._aa) + self._config = self._reg_read(CONFIGURE) self._reg_write(DYN_PL_LEN, self._dyn_pl) @property @@ -530,7 +534,7 @@ def auto_ack(self, enable): if i < 6: self._aa = (self._aa & ~(1 << i)) | (bool(val) << i) else: - raise ValueError("auto_ack: {} is not a valid input for" % enable) + raise ValueError("auto_ack: {} is not a valid input" % enable) self._reg_write(AUTO_ACK, self._aa) if self._aa: # refresh crc data if enabled self._config = self._reg_read(CONFIGURE) From d968234ca0aebe9b7c90d08a01fca53be25f6e06 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Thu, 24 Sep 2020 15:10:37 -0700 Subject: [PATCH 072/127] documented spi_frequency, a kwarg to c'tor --- docs/api.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 27f9f66..117cc8c 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -152,6 +152,9 @@ Contructor CSN (Chip Select Not) pin. This is required. :param ~digitalio.DigitalInOut ce: The digital output pin that is connected to the nRF24L01's CE (Chip Enable) pin. This is required. + :param int spi_frequency: Specify which SPI frequency to use on the SPI bus.This parameter + only applies to the instantiated object and is made persistent via + :py:class:`~adafruit_bus_device.spi_device`. open_tx_pipe() ****************** From b0d6f3a439766ae8d59c958ce8e227e92426f73a Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Thu, 24 Sep 2020 15:35:08 -0700 Subject: [PATCH 073/127] typo in c'tor docs --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 117cc8c..3f2d7ad 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -152,7 +152,7 @@ Contructor CSN (Chip Select Not) pin. This is required. :param ~digitalio.DigitalInOut ce: The digital output pin that is connected to the nRF24L01's CE (Chip Enable) pin. This is required. - :param int spi_frequency: Specify which SPI frequency to use on the SPI bus.This parameter + :param int spi_frequency: Specify which SPI frequency to use on the SPI bus. This parameter only applies to the instantiated object and is made persistent via :py:class:`~adafruit_bus_device.spi_device`. From 81892a5429585400c1c9011d3fb18480fc1b8972 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Thu, 24 Sep 2020 15:38:40 -0700 Subject: [PATCH 074/127] specify units for spi_frequency kwarg in docs --- docs/api.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 3f2d7ad..a409c2a 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -152,8 +152,8 @@ Contructor CSN (Chip Select Not) pin. This is required. :param ~digitalio.DigitalInOut ce: The digital output pin that is connected to the nRF24L01's CE (Chip Enable) pin. This is required. - :param int spi_frequency: Specify which SPI frequency to use on the SPI bus. This parameter - only applies to the instantiated object and is made persistent via + :param int spi_frequency: Specify which SPI frequency (in Hz) to use on the SPI bus. This + parameter only applies to the instantiated object and is made persistent via :py:class:`~adafruit_bus_device.spi_device`. open_tx_pipe() From 07a81b9e44bf31e6266e9027bc09e15ec8c378b8 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Thu, 24 Sep 2020 17:04:47 -0700 Subject: [PATCH 075/127] typo --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index a409c2a..e4f6f09 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -131,7 +131,7 @@ RF24 class Basic API --------- -Contructor +Constructor ****************** .. autoclass:: circuitpython_nrf24l01.rf24.RF24 From 7707ebb2d4ac586ba71096840825c1051308778a Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Thu, 24 Sep 2020 22:48:56 -0700 Subject: [PATCH 076/127] manual rebase from lite-beta --- .github/workflows/build.yml | 54 + .github/workflows/release.yml | 81 + .gitignore | 137 +- .travis.yml | 44 - README.rst | 218 ++- circuitpython_nrf24l01/__init__.py | 6 - circuitpython_nrf24l01/rf24.py | 1818 ++++++------------- circuitpython_nrf24l01/rf24_lite.py | 346 ++++ docs/_static/darkness.css | 14 + docs/api.rst | 1295 ++++++++++--- docs/examples.rst | 9 + examples/nrf24l01_2arduino_handling_data.py | 22 +- examples/nrf24l01_ack_payload_test.py | 33 +- examples/nrf24l01_context_test.py | 66 +- examples/nrf24l01_interrupt_test.py | 7 +- examples/nrf24l01_simple_test.py | 33 +- examples/nrf24l01_stream_test.py | 4 +- 17 files changed, 2380 insertions(+), 1807 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml delete mode 100644 .travis.yml create mode 100644 circuitpython_nrf24l01/rf24_lite.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..34241a2 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,54 @@ +name: Build CI + +on: [pull_request, push] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - name: Translate Repo Name For Build Tools filename_prefix + id: repo-name + run: | + echo ::set-output name=repo-name::$( + echo ${{ github.repository }} | + awk -F '\/' '{ print tolower($2) }' | + tr '_' '-' + ) + - name: Set up Python 3.6 + uses: actions/setup-python@v1 + with: + python-version: 3.6 + - name: Versions + run: | + python3 --version + - name: Checkout Current Repo + uses: actions/checkout@v1 + with: + submodules: true + - name: Checkout tools repo + uses: actions/checkout@v2 + with: + repository: adafruit/actions-ci-circuitpython-libs + path: actions-ci + - name: Install deps + run: | + source actions-ci/install.sh + + - name: Pip install pylint, black, & Sphinx + run: | + pip install pylint black Sphinx sphinx-rtd-theme + - name: Library version + run: git describe --dirty --always --tags + - name: PyLint + run: | + pylint --disable=too-many-instance-attributes,too-many-public-methods,too-many-locals circuitpython_nrf24l01/*.py + ([[ ! -d "examples" ]] || pylint --disable=invalid-name $( find . -path "./examples/*.py" )) + - name: Build assets + run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . --package_folder_prefix ${{ steps.repo-name.outputs.repo-name }} + - name: Build docs + working-directory: docs + run: sphinx-build -E -W -b html . _build/html diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e4dd574 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,81 @@ +name: Release Actions + +on: + release: + types: [published] + +jobs: + upload-release-assets: + runs-on: ubuntu-latest + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - name: Translate Repo Name For Build Tools filename_prefix + id: repo-name + run: | + echo ::set-output name=repo-name::$( + echo ${{ github.repository }} | + awk -F '\/' '{ print tolower($2) }' | + tr '_' '-' + ) + - name: Set up Python 3.6 + uses: actions/setup-python@v1 + with: + python-version: 3.6 + - name: Versions + run: | + python3 --version + - name: Checkout Current Repo + uses: actions/checkout@v1 + with: + submodules: true + - name: Checkout tools repo + uses: actions/checkout@v2 + with: + repository: adafruit/actions-ci-circuitpython-libs + path: actions-ci + - name: Install deps + run: | + source actions-ci/install.sh + - name: Build assets + run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . --package_folder_prefix ${{ steps.repo-name.outputs.repo-name }} + - name: Upload Release Assets + # the 'official' actions version does not yet support dynamically + # supplying asset names to upload. @csexton's version chosen based on + # discussion in the issue below, as its the simplest to implement and + # allows for selecting files with a pattern. + # https://github.com/actions/upload-release-asset/issues/4 + #uses: actions/upload-release-asset@v1.0.1 + uses: csexton/release-asset-action@master + with: + pattern: "bundles/*" + github-token: ${{ secrets.GITHUB_TOKEN }} + + upload-pypi: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Check For setup.py + id: need-pypi + run: | + echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) + - name: Set up Python + if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + uses: actions/setup-python@v1 + with: + python-version: '3.x' + - name: Install dependencies + if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + env: + TWINE_USERNAME: ${{ secrets.pypi_username }} + TWINE_PASSWORD: ${{ secrets.pypi_password }} + run: | + python setup.py sdist + twine upload dist/* diff --git a/.gitignore b/.gitignore index 01d8dc6..5c0044c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,128 @@ -*.mpy -.idea -__pycache__ -_build -*.pyc +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build*/ +bundles/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments .env -build* -bundles -*.DS_Store -.eggs -dist -**/*.egg-info +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# VS Code folder .vscode diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 14f476d..0000000 --- a/.travis.yml +++ /dev/null @@ -1,44 +0,0 @@ -# This is a common .travis.yml for generating library release zip files for -# CircuitPython library releases using circuitpython-build-tools. -# See https://github.com/adafruit/circuitpython-build-tools for detailed setup -# instructions. - -dist: xenial -language: python -python: - - "3.6" - -cache: - pip: true -env: - - DEPLOY_PYPI="true" - -deploy: - - provider: releases - api_key: "$GITHUB_TOKEN" - file_glob: true - file: "$TRAVIS_BUILD_DIR/bundles/*" - skip_cleanup: true - overwrite: true - on: - tags: true - # TODO: Use 'travis encrypt your-password-here' to generate - # the encrypted password for your account. Paste result below. - - provider: pypi - user: 2bndy5 - password: - secure: "Rn5kjXiWeG8Qlex6ZdS8sontrTaYZIFt7O/6xP5mfp9n1uxyumzteN5llI6wCDxlDPMtsvfhbWMvK+zeGJUQLyzK52t5IIWo0vULbIIZRdaJ74Djsnxh43i8siYSlNu+4VCpqfdR5o9wEU8lte53RtST9DLLk1NgokPBrRuC1uj5WGHnbVaq24QjWpDYWFw+/MtFSSbBGDPDf/s1qJnsH4LSUF95hnl1hwEyk7+0euy3wcpmDB1gqC+RRmwACiVP0qXpIroEVjRvKMjq9C0VvUQowZ/urt4isWVYrO/JT+WA2/BcLep1ep7zF46YUob1HxUqgsx04xx4y8EBbwc6yuI8xbukBIqk6DzEaFZAn5b/JFRzAOT9hbg3WYjkmtSQ2WIfb++ThnadfPhgQegrD0yBhb8UsQZeOXVWUEeSwQm4p1qQYeftfptLCkdGkamOzaijABYH8g4Rsj3oisqRMBkKE55lMcX/quggchSvASi/A/OhR/LcNX58m5yOu4rEWxdh6UEV+X7zpWDdqSMwYnCfAvrg+J7SjOYVmrfefCKRbu5I1MHO9tivZ33izUKxYeko5AexAqkoD+y1Na2PfMZi2tv2XtqPBdA6mzQwnWLNNp+xjSgntUc5VS97CpcJ1YDWiXdmhXVwy3bq1+cyN3npsAKeEySowDgtJWsS5yo=" - on: - tags: true - condition: $DEPLOY_PYPI = "true" - -install: - - pip install -r requirements.txt - - pip install circuitpython-build-tools Sphinx sphinx-rtd-theme - - pip install --force-reinstall pylint==1.9.2 - -script: - - pylint --disable=invalid-name,too-many-instance-attributes,too-many-public-methods,too-many-arguments,too-many-function-args,too-many-locals,too-many-branches,too-many-nested-blocks,inconsistent-return-statements,too-many-statements circuitpython_nrf24l01/*.py - - ([[ ! -d "examples" ]] || pylint --disable=invalid-name examples/*.py) - - circuitpython-build-bundles --library_location . --filename_prefix circuitpython-nrf24l01 - - cd docs && sphinx-build -E -W -b html . _build/html && cd .. diff --git a/README.rst b/README.rst index 8e7a5ef..9f91482 100644 --- a/README.rst +++ b/README.rst @@ -1,22 +1,27 @@ -Introduction -============ -.. image:: https://readthedocs.org/projects/circuitpython-nrf24l01/badge/?version=latest - :target: https://circuitpython-nrf24l01.readthedocs.io/ + +.. image:: https://readthedocs.org/projects/circuitpython-nrf24l01/badge/?version=stable + :target: https://circuitpython-nrf24l01.readthedocs.io/en/stable/ :alt: Documentation Status -.. image:: https://travis-ci.org/2bndy5/CircuitPython_nRF24L01.svg?branch=master - :target: https://travis-ci.org/2bndy5/CircuitPython_nRF24L01 +.. image:: https://github.com/2bndy5/CircuitPython_nRF24L01/workflows/Build%20CI/badge.svg + :target: https://github.com/2bndy5/CircuitPython_nRF24L01/actions?query=workflow%3A%22Build+CI%22 :alt: Build Status +.. image:: https://img.shields.io/github/commits-since/2bndy5/CircuitPython_nRF24L01/latest?&style=plastic + :alt: GitHub commits since latest release (by date) + :target: https://github.com/2bndy5/CircuitPython_nRF24L01/commits/master + .. image:: https://img.shields.io/pypi/v/circuitpython-nrf24l01.svg :alt: latest version on PyPI :target: https://pypi.python.org/pypi/circuitpython-nrf24l01 -.. image:: https://pepy.tech/badge/circuitpython-nrf24l01 +.. image:: https://pepy.tech/badge/circuitpython-nrf24l01?label=pypi%20downloads&logo=python :alt: Total PyPI downloads :target: https://pepy.tech/project/circuitpython-nrf24l01 +Introduction +============ Circuitpython driver library for the nRF24L01 transceiver @@ -30,7 +35,7 @@ compatibilty with the Raspberry Pi and other Circuitpython compatible devices. M * Author(s): Damien P. George, Peter Hinch, Rhys Thomas, Brendan Doherty Features currently supported ----------------------------- +============================ * change the addresses' length (can be 3 to 5 bytes long) * dynamically sized payloads (max 32 bytes each) or statically sized payloads @@ -38,21 +43,21 @@ Features currently supported * custom acknowledgment (ACK) payloads for bi-directional communication * flag a single payload for no acknowledgment (ACK) from the receiving nRF24L01 * "re-use the same payload" feature (for manually re-transmitting failed transmissions that remain in the buffer) -* multiple payload transmissions with one function call (MUST read documentation on the :py:meth:`~circuitpython_nrf24l01.RF24.send()` function) -* context manager compatible for easily switching between different radio configurations using "with" statements -* configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or failed transmissions (these 3 flags control the 1 IRQ pin). There's also virtual representations of these interrupt flags available (see :py:attr:`~circuitpython_nrf24l01.RF24.irq_dr`, :py:attr:`~circuitpython_nrf24l01.RF24.irq_ds`, :py:attr:`~circuitpython_nrf24l01.RF24.irq_df` attributes) +* multiple payload transmissions with one function call (MUST read documentation on the `send()` function) +* context manager compatible for easily switching between different radio configurations using "with" statements (not available in ``rf24_lite.py`` variant for M0 based boards) +* configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or failed transmissions (these 3 flags control the 1 IRQ pin). There's also virtual representations of these interrupt flags available (see `irq_dr`, `irq_ds`, `irq_df` attributes) * invoke sleep mode (AKA power down mode) for ultra-low current consumption * cyclic redundancy checking (CRC) up to 2 bytes long -* adjust the nRF24L01's builtin automatic re-transmit feature's parameters (:py:attr:`~circuitpython_nrf24l01.RF24.arc`: number of attempts, :py:attr:`~circuitpython_nrf24l01.RF24.ard`: delay between attempts) +* adjust the nRF24L01's builtin automatic re-transmit feature's parameters (`arc`: number of attempts, `ard`: delay between attempts) * adjust the nRF24L01's frequency channel (2.4-2.525 GHz) * adjust the nRF24L01's power amplifier level (0, -6, -12, or -18 dBm) * adjust the nRF24L01's RF data rate (250Kbps is buggy due to hardware design, but 1Mbps and 2Mbps are reliable) +* a nRF24L01 driven by this library can communicate with a nRF24L01 on an Arduino driven by the `TMRh20 RF24 library `_. See the nrf24l01_2arduino_handling_data.py code in the `examples folder of this library's repository `_ Features currently unsupported -------------------------------- +============================== -* as of yet, no [intended] implementation for Multiceiver mode (up to 6 TX nRF24L01 "talking" to 1 RX nRF24L01 simultaneously). Although this might be acheived easily using the "automatic retry delay" (:py:attr:`~circuitpython_nrf24l01.RF24.ard`) and "automatic retry count" (:py:attr:`~circuitpython_nrf24l01.RF24.arc`) attributes set accordingly (varyingly high). -* for reason(s) unknown, a nRF24L01 driven by this library will not "talk" to a nRF24L01 on an Arduino driven by the `TMRh20 RF24 library `_. There is no problems when a nRF24L01 driven by this library "talks" to another nRF24L01 that's also driven by this library. `Other Arduino-based nRF24L01 libraries are available `_, but they have not been tested to communicate with this CircuitPython-nRF24L01 library. +* as of yet, no [intended] implementation for Multiceiver mode (up to 6 TX nRF24L01 "talking" to 1 RX nRF24L01 simultaneously). Although this might be acheived easily using the "automatic retry delay" (`ard`) and "automatic retry count" (`arc`) attributes set accordingly (varyingly high -- this has not been tested). Dependencies ============= @@ -97,25 +102,17 @@ Pinout The nRF24L01 is controlled through SPI so there are 3 pins (SCK, MOSI, & MISO) that can only be connected to their counterparts on the MCU (microcontroller unit). The other 2 essential pins (CE & CSN) can be connected to any digital output pins. Lastly, the only optional pin on the nRf24L01 GPIOs is the IRQ (interrupt; a digital output that's active when low) pin and is only connected to the MCU via a digital input pin during the interrupt example. The following pinout is used in the example codes of this library's `example directory `_. -+------------+----------------+----------------+ -| nRF24L01 | Raspberry Pi | ItsyBitsy M4 | -+============+================+================+ -| GND | GND | GND | -+------------+----------------+----------------+ -| VCC | 3V | 3.3V | -+------------+----------------+----------------+ -| CE | GPIO4 | D4 | -+------------+----------------+----------------+ -| CSN | GPIO5 | D5 | -+------------+----------------+----------------+ -| SCK | GPIO11 (SCK) | SCK | -+------------+----------------+----------------+ -| MOSI | GPIO10 (MOSI) | MOSI | -+------------+----------------+----------------+ -| MISO | GPIO9 (MISO) | MISO | -+------------+----------------+----------------+ -| IRQ | GPIO4 | D12 | -+------------+----------------+----------------+ +.. csv-table:: + :header: nRF2401, "Raspberry Pi", "ItsyBitsy M4" + + GND, GND, GND + VCC, 3V, 3.3V + CE, GPIO4, D4 + CSN, GPIO5, D5 + SCK, "GPIO11 (SCK)", SCK + MOSI, "GPIO10 (MOSI)", MOSI + MISO, "GPIO9 (MISO)", MISO + IRQ, GPIO12, D12 .. tip:: User reports and personal experiences have improved results if there is a capacitor of 100 mirofarads [+ another optional 0.1 microfarads capacitor for added stability] connected in parrallel to the VCC and GND pins. @@ -129,78 +126,100 @@ To run the simple example, navigate to this repository's "examples" folder in th .. code-block:: python >>> from nrf24l01_simple_test import * - nRF24L01 Simple test + nRF24L01 Simple test. Run slave() on receiver Run master() on transmitter - >>> master(3) + >>> master() + Sending: 5 as struct: b'\x05\x00\x00\x00' + send() successful + Transmission took 36.0 ms + Sending: 4 as struct: b'\x04\x00\x00\x00' + send() successful + Transmission took 28.0 ms Sending: 3 as struct: b'\x03\x00\x00\x00' - send() succeessful - Transmission took 86.0 ms - Sending: 2 as struct: b'\x02\x00\x00\x00' - send() succeessful - Transmission took 109.0 ms - Sending: 1 as struct: b'\x01\x00\x00\x00' - send() succeessful - Transmission took 109.0 ms - # these results were observed from a test on the Raspberry Pi 3 - # transmissions from a CircuitPython device took 32 to 64 ms - - -About the nRF24L01 -================== + send() successful + Transmission took 24.0 ms -Here are the features listed directly from the datasheet (referenced here in the documentation as the `nRF24L01+ Specification Sheet `_): - -Key Features: -------------- - - * Worldwide 2.4GHz ISM band operation - * 250kbps, 1Mbps and 2Mbps on air data rates - * Ultra low power operation - * 11.3mA TX at 0dBm output power - * 13.5mA RX at 2Mbps air data rate - * 900nA in power down - * 26μA in standby-I - * On chip voltage regulator - * 1.9 to 3.6V supply range - * Enhanced ShockBurst™ - * Automatic packet handling - * Auto packet transaction handling - * 6 data pipe MultiCeiver™ - * Drop-in compatibility with nRF24L01 - * On-air compatible in 250kbps and 1Mbps with nRF2401A, nRF2402, nRF24E1 and nRF24E2 - * Low cost BOM - * ±60ppm 16MHz crystal - * 5V tolerant inputs - * Compact 20-pin 4x4mm QFN package - -Applications ------------- - - * Wireless PC Peripherals - * Mouse, keyboards and remotes - * 3-in-1 desktop bundles - * Advanced Media center remote controls - * VoIP headsets - * Game controllers - * Sports watches and sensors - * RF remote controls for consumer electronics - * Home and commercial automation - * Ultra low power sensor networks - * Active RFID - * Asset tracking systems - * Toys - -Future Project Ideas/Additions using the nRF24L01 (not currently supported by this circuitpython library): - - * `There's a few blog posts by Nerd Ralph demonstrating how to use the nRF24L01 via 2 or 3 pins `_ (uses custom bitbanging SPI functions and an external circuit involving a resistor and a capacitor) - * network linking layer, maybe something like `TMRh20's RF24Network `_ - * add a fake BLE module for sending BLE beacon advertisments from the nRF24L01 as outlined by `Dmitry Grinberg in his write-up (including C source code) `_. We've started developing this, but fell short of success in `the BLEfake branch of this library's repository `_ Where do I get 1? ================= -See the store links on the sidebar or just google "nRF24L01". It is worth noting that you generally don't want to buy just 1 as you need 2 for testing -- 1 to send & 1 to receive and vise versa. This library has been tested on a cheaply bought 10 pack from Amazon.com using a highly recommended capacitor (100 µF) on the power pins. Don't get lost on Amazon or eBay! There are other wireless transceivers that are NOT compatible with this library. For instance, the esp8266-01 (also sold in packs) is NOT compatible with this library, but looks very similar to the nRF24L01(+) and could lead to an accidental purchase. +See the store links on the sidebar or just google "nRF24L01+". It is worth noting that you +generally want to buy more than 1 as you need 2 for testing -- 1 to send & 1 to receive and +vise versa. This library has been tested on a cheaply bought 6 pack from Amazon.com, but don't +take Amazon or eBay for granted! There are other wireless transceivers that are NOT compatible +with this library. For instance, the esp8266-01 (also sold in packs) is NOT compatible with +this library, but looks very similar to the nRF24L01+ and could lead to an accidental purchase. + +About the nRF24L01+ +=================== + +Stablizing the power input to the VCC and GND using parallel capacitors (100 µF + an optional +0.1µF) provides significant performance increases. This stability is very highly recommended! +More finite details about the nRF24L01 are available from the datasheet (referenced here in +the documentation as the `nRF24L01+ Specification Sheet `_) + +About the nRF24L01+PA+LNA modules +================================= + +You may find variants of the nRF24L01 transceiver that are marketed as "nRF24L01+PA+LNA". +These modules are distinct in the fact that they come with a detachable (SMA-type) antenna. +They employ seperate RFX24C01 IC with the antenna for enhanced Power Amplification (PA) and +Low Noise Amplification (LNA) features. While they boast greater range with the same +functionality, they are subject to a couple lesser known (and lesser advertised) drawbacks: + +1. Stronger power source. Below is a chart of advertised current requirements that many MCU + boards' 3V regulators may not be able to handle. + + .. csv-table:: + :header: Specification, Value + :widths: 10,5 + + "Emission mode current(peak)", "115 mA" + "Receive Mode current(peak)", "45 mA" + "Power-down mode current", "4.2 µA" +2. Needs sheilding from electromagnetic interference. Sheilding works best when it has a path + to ground (GND pin) + +nRF24L01(+) clones and counterfeits +=================================== + +This library does not directly support clones/counterfeits as there is no way for the library +to differentiate between an actual nRF24L01+ and a clone/counterfeit. To determine if your +purchase is a counterfeit, please contact the retailer you purxhased from (`reading this +article and its links might help +`_). The most notable clone is the `Si24R1 `_. I could not find +the `Si24R1 datasheet `_ in english. Troubleshooting +the SI24R1 may require `replacing the onboard antennae with a wire +`_. Furthermore, the Si24R1 has different power +amplifier options as noted in the `RF_PWR section (bits 0 through 2) of the RF_SETUP register +(hex address 6) of the datasheet `_. +While the options' values differ from those identified by this library's API, the +underlying commands to configure those options are almost identical to the nRF24L01. Other +known clones include the bk242x (AKA RFM7x). + +Future Project Ideas/Additions +============================== + + The following are only ideas; they are not currently supported by this circuitpython library. + + * `There's a few blog posts by Nerd Ralph demonstrating how to use the nRF24L01 via 2 or 3 + pins `_ (uses custom bitbanging SPI functions and an external circuit involving a + resistor and a capacitor) + * network linking layer, maybe something like `TMRh20's RF24Network + `_ + * add a fake BLE module for sending BLE beacon advertisments from the nRF24L01 as outlined + by `Dmitry Grinberg in his write-up (including C source code) + `_. + We've started developing this, but fell short of success in `the BLEfake branch of this + library's repository `_ + * implement the Gazelle-based protocol used by the BBC micro-bit (`makecode.com's radio + blocks `_). Contributing ============ @@ -229,4 +248,5 @@ Now, once you have the virtual environment activated: sphinx-build -E -W -b html . _build/html This will output the documentation to ``docs/_build/html``. Open the index.html in your browser to -view them. It will also (due to -W) error out on any warning like Travis CI does. This is a good way to locally verify it will pass. +view them. It will also (due to -W) error out on any warning like the Github action, Build CI, +does. This is a good way to locally verify it will pass. diff --git a/circuitpython_nrf24l01/__init__.py b/circuitpython_nrf24l01/__init__.py index a1d8ff9..e69de29 100644 --- a/circuitpython_nrf24l01/__init__.py +++ b/circuitpython_nrf24l01/__init__.py @@ -1,6 +0,0 @@ -"""module managment for the circuitpython-nrf24l01 package""" - -from .rf24 import RF24 -from .fake_ble import FakeBLE - -__all__ = ['RF24', 'FakeBLE'] diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 36b27fe..6b9e115 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -20,227 +20,101 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -# pylint: disable=too-many-lines -""" -rf24 module containing the base class RF24 -""" +"""rf24 module containing the base class RF24""" __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/2bndy5/CircuitPython_nRF24L01.git" import time -from adafruit_bus_device.spi_device import SPIDevice - -# nRF24L01 registers -# pylint: disable=bad-whitespace -CONFIG = 0x00 #: register for configuring IRQ, CRC, PWR & RX/TX roles -EN_AA = 0x01 #: register for auto-ACK feature. Each bit represents this feature per pipe -EN_RX = 0x02 #: register to open/close pipes. Each bit represents this feature per pipe -SETUP_AW = 0x03 #: address width register -SETUP_RETR = 0x04 #: auto-retry count and delay register -RF_CH = 0x05 #: channel register -RF_SETUP = 0x06 #: RF Power Amplifier & Data Rate -RX_ADDR = 0x0a #: RX pipe addresses == [0,5]:[0x0a:0x0f] -RX_PW = 0x11 #: RX payload widths on pipes == [0,5]:[0x11,0x16] -FIFO = 0x17 #: register containing info on both RX/TX FIFOs + re-use payload flag -DYNPD = 0x1c #: dynamic payloads feature. Each bit represents this feature per pipe -FEATURE = 0x1d #: global flags for dynamic payloads, custom ACK payloads, & Ask no ACK -TX_ADDR = 0x10 #: Address that is used for TX transmissions -# pylint: enable=bad-whitespace +from micropython import const + +try: + from ubus_device import SPIDevice +except ImportError: + from adafruit_bus_device.spi_device import SPIDevice + +CONFIGURE = const(0x00) # IRQ, CRC, PWR control & RX/TX roles +AUTO_ACK = const(0x01) # auto-ACK +OPEN_PIPES = const(0x02) # open/close pipes +SETUP_RETR = const(0x04) # auto-retry count & delay +RF_PA_RATE = const(0x06) # RF Power Amplifier & Data Rate +RX_ADDR_P0 = const(0x0A) # RX pipe addresses == [0, 5]:[0x0a, 0x0f] +TX_ADDRESS = const(0x10) # Address used for TX transmissions +RX_PL_LENG = const(0x11) # RX payload widths on pipes == [0, 5]:[0x11, 0x16] +DYN_PL_LEN = const(0x1C) # dynamic payloads +TX_FEATURE = const(0x1D) # TX features dynamic payloads, ACK payloads, NO_ACK +CSN_DELAY = 0.005 +"""The delay time (in seconds) used to let the CSN pin settle, +allowing a clean SPI transaction.""" + class RF24: - """A driver class for the nRF24L01(+) transceiver radios. This class aims to be compatible with - other devices in the nRF24xxx product line that implement the Nordic proprietary Enhanced - ShockBurst Protocol (and/or the legacy ShockBurst Protocol), but officially only supports - (through testing) the nRF24L01 and nRF24L01+ devices. - - :param ~busio.SPI spi: The object for the SPI bus that the nRF24L01 is connected to. - - .. tip:: This object is meant to be shared amongst other driver classes (like - adafruit_mcp3xxx.mcp3008 for example) that use the same SPI bus. Otherwise, multiple - devices on the same SPI bus with different spi objects may produce errors or - undesirable behavior. - :param ~digitalio.DigitalInOut csn: The digital output pin that is connected to the nRF24L01's - CSN (Chip Select Not) pin. This is required. - :param ~digitalio.DigitalInOut ce: The digital output pin that is connected to the nRF24L01's - CE (Chip Enable) pin. This is required. - :param int channel: This is used to specify a certain radio frequency that the nRF24L01 uses. - Defaults to 76 and can be changed at any time by using the `channel` attribute. - :param int payload_length: This is the length (in bytes) of a single payload to be transmitted - or received. This is ignored if the `dynamic_payloads` attribute is enabled. Defaults to 32 - and must be in range [1,32]. This can be changed at any time by using the `payload_length` - attribute. - :param int address_length: This is the length (in bytes) of the addresses that are assigned to - the data pipes for transmitting/receiving. Defaults to 5 and must be in range [3,5]. This - can be changed at any time by using the `address_length` attribute. - :param int ard: This specifies the delay time (in µs) between attempts to automatically - re-transmit. This can be changed at any time by using the `ard` attribute. This parameter - must be a multiple of 250 in the range [250,4000]. Defualts to 1500 µs. - :param int arc: This specifies the automatic re-transmit count (maximum number of automatically - attempts to re-transmit). This can be changed at any time by using the `arc` attribute. - This parameter must be in the range [0,15]. Defaults to 3. - :param int crc: This parameter controls the CRC setting of transmitted packets. Options are - ``0`` (off), ``1`` or ``2`` (byte long CRC enabled). This can be changed at any time by - using the `crc` attribute. Defaults to 2. - :param int data_rate: This parameter controls the RF data rate setting of transmissions. - Options are ``1`` (Mbps), ``2`` (Mbps), or ``250`` (Kbps). This can be changed at any time - by using the `data_rate` attribute. Defaults to 1. - :param int pa_level: This parameter controls the RF power amplifier setting of transmissions. - Options are ``0`` (dBm), ``-6`` (dBm), ``-12`` (dBm), or ``-18`` (dBm). This can be changed - at any time by using the `pa_level` attribute. Defaults to 0. - :param bool dynamic_payloads: This parameter enables/disables the dynamic payload length - feature of the nRF24L01. Defaults to enabled. This can be changed at any time by using the - `dynamic_payloads` attribute. - :param bool auto_ack: This parameter enables/disables the automatic acknowledgment (ACK) - feature of the nRF24L01. Defaults to enabled if `dynamic_payloads` is enabled. This can be - changed at any time by using the `auto_ack` attribute. - :param bool ask_no_ack: This represents a special flag that has to be thrown to enable a - feature specific to individual payloads. Setting this parameter only enables access to this - feature; it does not invoke it (see parameters for `send()` or `write()` functions). - Enabling/Disabling this does not affect `auto_ack` attribute. - :param bool ack: This represents a special flag that has to be thrown to enable a feature - allowing custom response payloads appended to the ACK packets. Enabling this also requires - the `auto_ack` attribute enabled. This can be changed at any time by using the `ack` - attribute. - :param bool irq_dr: When "Data is Ready", this configures the interrupt (IRQ) trigger of the - nRF24L01's IRQ pin (active low). Defaults to enabled. This can be changed at any time by - using the `interrupt_config()` function. - :param bool irq_ds: When "Data is Sent", this configures the interrupt (IRQ) trigger of the - nRF24L01's IRQ pin (active low). Defaults to enabled. This can be changed at any time by - using the `interrupt_config()` function. - :param bool irq_df: When "max retry attempts are reached" (specified by the `arc` attribute), - this configures the interrupt (IRQ) trigger of the nRF24L01's IRQ pin (active low) and - represents transmission failure. Defaults to enabled. This can be changed at any time by - using the `interrupt_config()` function. - """ - def __init__(self, spi, csn, ce, - channel=76, - payload_length=32, - address_length=5, - ard=1500, - arc=3, - crc=2, - data_rate=1, - pa_level=0, - dynamic_payloads=True, - auto_ack=True, - ask_no_ack=True, - ack=False, - irq_dr=True, - irq_ds=True, - irq_df=True): - self._payload_length = payload_length # inits internal attribute - self.payload_length = payload_length - # last address assigned to pipe0 for reading. init to None - self._fifo = 0 - self._status = 0 - # init shadow copy of RX addresses for all pipes - self._pipes = [b'', b'', 0, 0, 0, 0] - self._payload_widths = [0, 0, 0, 0, 0, 0] # payload_length specific to each pipe - # shadow copy of last RX_ADDR written to pipe 0 - self._pipe0_read_addr = None # needed as open_tx_pipe() appropriates pipe 0 for ACK - # init the _open_pipes attribute (reflects only RX state on each pipe) - self._open_pipes = 0 # <- means all pipes closed - # init the SPI bus and pins - self._spi = SPIDevice(spi, chip_select=csn, baudrate=1250000) - - # store the ce pin + """A driver class for the nRF24L01(+) transceiver radios.""" + + def __init__(self, spi, csn, ce, spi_frequency=10000000): + self._spi = SPIDevice(spi, chip_select=csn, baudrate=spi_frequency) self.ce_pin = ce - # reset ce.value & disable the chip comms - self.ce_pin.switch_to_output(value=False) - # if radio is powered up and CE is LOW: standby-I mode - # if radio is powered up and CE is HIGH: standby-II mode - - # NOTE per spec sheet: nRF24L01+ must be in a standby or power down mode before writing - # to the configuration register - # configure the CONFIG register:IRQ(s) config, setup CRC feature, and trigger standby-I & - # TX mode (the "| 2") - if 0 <= crc <= 2: - self._config = ((not irq_dr) << 6) | ((not irq_ds) << 5) | ((not irq_df) << 4) | \ - ((crc + 1) << 2 if crc else 0) | 2 - self._reg_write(CONFIG, self._config) # dump to register - else: - raise ValueError( - "CRC byte length must be an int equal to 0 (off), 1, or 2") - - # check for device presence by verifying nRF24L01 is in TX + standby-I mode - if self._reg_read(CONFIG) & 3 == 2: # if in TX + standby-I mode - self.power = False # power down - else: # hardware presence check NOT passed - print(bin(self._reg_read(CONFIG))) + self.ce_pin.switch_to_output(value=False) # pre-empt standby-I mode + self._status = 0 # status byte returned on all SPI transactions + # pre-configure the CONFIGURE register: + # 0x0E = IRQs are all enabled, CRC is enabled with 2 bytes, and + # power up in TX mode + self._config = 0x0E + self._reg_write(CONFIGURE, self._config) + if self._reg_read(CONFIGURE) & 3 != 2: raise RuntimeError("nRF24L01 Hardware not responding") - - # capture all pipe's RX addresses & the TX address from last usage - for i in range(6): + self.power = False + # init shadow copy of RX addresses for all pipes for context manager + self._pipes = [bytearray(5), bytearray(5), 0xC3, 0xC4, 0xC5, 0xC6] + # _open_pipes attribute reflects only RX state on each pipe + self._open_pipes = 0 # 0 = all pipes closed + for i in range(6): # capture RX addresses from registers if i < 2: - self._pipes[i] = self._reg_read_bytes(RX_ADDR + i) + self._pipes[i] = self._reg_read_bytes(RX_ADDR_P0 + i) else: - self._pipes[i] = self._reg_read(RX_ADDR + i) - - # shadow copy of the TX_ADDR - self._tx_address = self._reg_read_bytes(TX_ADDR) - - # configure the SETUP_RETR register - if 250 <= ard <= 4000 and ard % 250 == 0 and 0 <= arc <= 15: - self._setup_retr = (int((ard - 250) / 250) << 4) | arc - else: - raise ValueError("automatic re-transmit delay can only be a multiple of 250 in range " - "[250,4000]\nautomatic re-transmit count(/attempts) must range " - "[0,15]") - - # configure the RF_SETUP register - if data_rate in (1, 2, 250) and pa_level in (-18, -12, -6, 0): - data_rate = 0 if data_rate == 1 else ( - 8 if data_rate == 2 else 0x20) - pa_level = (3 - int(pa_level / -6)) * 2 - self._rf_setup = data_rate | pa_level - else: - raise ValueError("data rate must be one of the following ([M,M,K]bps): 1, 2, 250" - "\npower amplifier must be one of the following (dBm): -18, -12," - " -6, 0") - - # manage dynamic_payloads, auto_ack, and ack features - self._dyn_pl = 0x3F if dynamic_payloads else 0 # 0x3F == enabled on all pipes - self._aa = 0x3F if auto_ack else 0 # 0x3F == enabled on all pipes - self._features = (dynamic_payloads << 2) | ((ack if auto_ack and dynamic_payloads - else False) << 1) | ask_no_ack - - # init the last few singleton attribute - self._channel = channel - self._addr_len = address_length - - with self: # write to registers & power up - # using __enter__() configures all virtual features and settings to the hardware - # registers - self.ce_pin.value = 0 # ensure standby-I mode to write to CONFIG register - self._reg_write(CONFIG, self._config | 1) # enable RX mode - time.sleep(0.000015) # wait time for transitioning modes RX/TX - self.flush_rx() # spec sheet say "used in RX mode" - self._reg_write(CONFIG, self._config & 0xC) # power down + TX mode - time.sleep(0.000015) # wait time for transitioning modes RX/TX - self.flush_tx() # spec sheet say "used in TX mode" - self.clear_status_flags() # writes directly to STATUS register + self._pipes[i] = self._reg_read(RX_ADDR_P0 + i) + # init shadow copy of last RX_ADDR_P0 written to pipe 0 needed as + # open_tx_pipe() appropriates pipe 0 for ACK packet + self._pipe0_read_addr = None + # init shadow copy of register about FIFO info + self._fifo = 0 + # shadow copy of the TX_ADDRESS + self._tx_address = self._reg_read_bytes(TX_ADDRESS) + # pre-configure the SETUP_RETR register + self._retry_setup = 0x53 # ard = 1500; arc = 3 + # pre-configure the RF_SETUP register + self._rf_setup = 0x07 # 1 Mbps data_rate, and 0 dbm pa_level + # pre-configure dynamic_payloads & auto_ack for RX operations + self._dyn_pl = 0x3F # 0x3F = enable dynamic_payloads on all pipes + self._aa = 0x3F # 0x3F = enable auto_ack on all pipes + # pre-configure features for TX operations: + # 5 = enable dynamic_payloads, disable custom ack payloads, & + # allow ask_no_ack command + self._features = 5 + self._channel = 76 # 2.476 GHz + self._addr_len = 5 # 5-byte long addresses + self._pl_len = [32] * 6 # 32-byte static payloads for all pipes + + with self: # dumps internal attributes to all registers + self.flush_rx() + self.flush_tx() + self.clear_status_flags() def __enter__(self): - # dump IRQ and CRC data to CONFIG register - self._reg_write(CONFIG, self._config & 0x7C) - self._reg_write(RF_SETUP, self._rf_setup) # dump to RF_SETUP register - # dump open/close pipe status to EN_RXADDR register (for all pipes) - self._reg_write(EN_RX, self._open_pipes) - self._reg_write(DYNPD, self._dyn_pl) # dump to DYNPD register - self._reg_write(EN_AA, self._aa) # dump to EN_AA register - self._reg_write(FEATURE, self._features) # dump to FEATURE register - # dump to SETUP_RETR register - self._reg_write(SETUP_RETR, self._setup_retr) - # dump pipes' RX addresses and static payload lengths - for i, address in enumerate(self._pipes): + self._reg_write(CONFIGURE, self._config & 0x7C) + self._reg_write(RF_PA_RATE, self._rf_setup) + self._reg_write(OPEN_PIPES, self._open_pipes) + self._reg_write(DYN_PL_LEN, self._dyn_pl) + self._reg_write(AUTO_ACK, self._aa) + self._reg_write(TX_FEATURE, self._features) + self._reg_write(SETUP_RETR, self._retry_setup) + for i, addr in enumerate(self._pipes): if i < 2: - self._reg_write_bytes(RX_ADDR + i, address) + self._reg_write_bytes(RX_ADDR_P0 + i, addr) else: - self._reg_write(RX_ADDR + i, address) - self._reg_write(RX_PW + i, self._payload_widths[i]) - # dump last used TX address - self._reg_write_bytes(TX_ADDR, self._tx_address) - self.address_length = self._addr_len # writes directly to SETUP_AW register - self.channel = self._channel # writes directly to RF_CH register + self._reg_write(RX_ADDR_P0 + i, addr) + self._reg_write_bytes(TX_ADDRESS, self._tx_address) + self._reg_write(0x05, self._channel) + self._reg_write(0x03, self._addr_len - 2) + self.payload_length = self._pl_len return self def __exit__(self, *exc): @@ -249,1274 +123,646 @@ def __exit__(self, *exc): # pylint: disable=no-member def _reg_read(self, reg): - buf = bytearray(2) # 2 = 1 status byte + 1 byte of returned content + out_buf = bytes([reg, 0]) + in_buf = bytearray([0, 0]) with self._spi as spi: - time.sleep(0.005) # time for CSN to settle - spi.readinto(buf, write_value=reg) - self._status = buf[0] # save status byte - return buf[1] # drop status byte and return the rest + time.sleep(CSN_DELAY) + spi.write_readinto(out_buf, in_buf) + self._status = in_buf[0] + return in_buf[1] def _reg_read_bytes(self, reg, buf_len=5): - # allow an extra byte for status data - buf = bytearray(buf_len + 1) + in_buf = bytearray(buf_len + 1) + out_buf = bytes([reg]) + b"\x00" * buf_len with self._spi as spi: - time.sleep(0.005) # time for CSN to settle - spi.readinto(buf, write_value=reg) - self._status = buf[0] # save status byte - return buf[1:] # drop status byte and return the rest + time.sleep(CSN_DELAY) + spi.write_readinto(out_buf, in_buf) + self._status = in_buf[0] + return in_buf[1:] def _reg_write_bytes(self, reg, out_buf): out_buf = bytes([0x20 | reg]) + out_buf in_buf = bytearray(len(out_buf)) with self._spi as spi: - time.sleep(0.005) # time for CSN to settle + time.sleep(CSN_DELAY) spi.write_readinto(out_buf, in_buf) - self._status = in_buf[0] # save status byte + self._status = in_buf[0] def _reg_write(self, reg, value=None): - if value is None: - out_buf = bytes([reg]) - else: + out_buf = bytes([reg]) + if value is not None: out_buf = bytes([0x20 | reg, value]) in_buf = bytearray(len(out_buf)) with self._spi as spi: - time.sleep(0.005) # time for CSN to settle + time.sleep(CSN_DELAY) spi.write_readinto(out_buf, in_buf) - self._status = in_buf[0] # save status byte + self._status = in_buf[0] + # pylint: enable=no-member @property def address_length(self): - """This `int` attribute specifies the length (in bytes) of addresses to be used for RX/TX - pipes. The addresses assigned to the data pipes must have byte length equal to the value - set for this attribute. - - A valid input value must be an `int` in range [3,5]. Otherwise a `ValueError` exception is - thrown. Default is set to the nRF24L01's maximum of 5. - """ - return self._reg_read(SETUP_AW) + 2 + """This `int` attribute specifies the length (in bytes) of addresses + to be used for RX/TX pipes.""" + return self._reg_read(0x03) + 2 @address_length.setter def address_length(self, length): - # nRF24L01+ must be in a standby or power down mode before writing to the configuration - # registers. - if 3 <= length <= 5: - # address width is saved in 2 bits making range = [3,5] - self._addr_len = int(length) - self._reg_write(SETUP_AW, length - 2) - else: - raise ValueError( - "address length can only be set in range [3,5] bytes") + if not 3 <= length <= 5: + raise ValueError("address_length can only be set in range [3, 5] bytes") + self._addr_len = int(length) + self._reg_write(0x03, length - 2) def open_tx_pipe(self, address): - """This function is used to open a data pipe for OTA (over the air) TX transmissions. - - :param bytearray address: The virtual address of the receiving nRF24L01. This must have a - length equal to the `address_length` attribute (see `address_length` attribute). - Otherwise a `ValueError` exception is thrown. The address specified here must match the - address set to one of the RX data pipes of the receiving nRF24L01. - - .. note:: There is no option to specify which data pipe to use because the nRF24L01 only - uses data pipe 0 in TX mode. Additionally, the nRF24L01 uses the same data pipe (pipe - 0) for receiving acknowledgement (ACK) packets in TX mode when the `auto_ack` attribute - is enabled. Thus, RX pipe 0 is appropriated with the TX address (specified here) when - `auto_ack` is set to `True`. - """ - if len(address) == self.address_length: - # if auto_ack == True, then use this TX address as the RX address for ACK - if self.auto_ack: - # settings need to match on both transceivers: dynamic_payloads and payload_length - self._pipes[0] = address - self._reg_write_bytes(RX_ADDR, address) # using pipe 0 - self._open_pipes = self._open_pipes | 1 # open pipe 0 for RX-ing ACK - self._reg_write(EN_RX, self._open_pipes) - self._payload_widths[0] = self.payload_length - self._reg_write(RX_PW, self.payload_length) # set expected payload_length - self._pipes[0] = address # update the context as well - self._tx_address = address - self._reg_write_bytes(TX_ADDR, address) - else: - raise ValueError("address must be a buffer protocol object with a byte length\nequal " - "to the address_length attribute (currently set to" - " {})".format(self.address_length)) - - def close_rx_pipe(self, pipe_number, reset=True): - """This function is used to close a specific data pipe from OTA (over the air) RX - transmissions. - - :param int pipe_number: The data pipe to use for RX transactions. This must be in range - [0,5]. Otherwise a `ValueError` exception is thrown. - :param bool reset: `True` resets the address for the specified ``pipe_number`` to the - factory address (different for each pipe). `False` leaves the address on the specified - ``pipe_number`` alone. Be aware that the addresses will remain despite loss of power. - """ + """This function is used to open a data pipe for OTA (over the air) + TX transmissions.""" + if self.arc: + for i, val in enumerate(address): + self._pipes[0][i] = val + self._reg_write_bytes(RX_ADDR_P0, address) + self._open_pipes = self._open_pipes | 1 + self._reg_write(OPEN_PIPES, self._open_pipes) + for i, val in enumerate(address): + self._tx_address[i] = val + self._reg_write_bytes(TX_ADDRESS, address) + + def close_rx_pipe(self, pipe_number): + """This function is used to close a specific data pipe from OTA (over + the air) RX transmissions.""" if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0,5]") - self._open_pipes = self._reg_read(EN_RX) # refresh data - if reset:# reset pipe address accordingly - if not pipe_number: - # NOTE this does not clear the shadow copy (pipe0_read_addr) of address for pipe 0 - self._reg_write_bytes(pipe_number + RX_ADDR, b'\xe7' * 5) - self._pipes[pipe_number] = b'\xe7' * 5 - elif pipe_number == 1: # write the full address for pipe 1 - self._reg_write_bytes(pipe_number + RX_ADDR, b'\xc2' * 5) - self._pipes[pipe_number] = b'\xc2' * 5 - else: # write just MSB for 2 <= pipes <= 5 - self._reg_write(pipe_number + RX_ADDR, pipe_number + 0xc1) - self._pipes[pipe_number] = pipe_number + 0xc1 - # disable the specified data pipe if not already + raise ValueError("pipe number must be in range [0, 5]") + self._open_pipes = self._reg_read(OPEN_PIPES) if self._open_pipes & (1 << pipe_number): self._open_pipes = self._open_pipes & ~(1 << pipe_number) - self._reg_write(EN_RX, self._open_pipes) + self._reg_write(OPEN_PIPES, self._open_pipes) def open_rx_pipe(self, pipe_number, address): - """This function is used to open a specific data pipe for OTA (over the air) RX - transmissions. If `dynamic_payloads` attribute is `False`, then the `payload_length` - attribute is used to specify the expected length of the RX payload on the specified data - pipe. - - :param int pipe_number: The data pipe to use for RX transactions. This must be in range - [0,5]. Otherwise a `ValueError` exception is thrown. - :param bytearray address: The virtual address to the receiving nRF24L01. This must have a - byte length equal to the `address_length` attribute. Otherwise a `ValueError` - exception is thrown. If using a ``pipe_number`` greater than 1, then only the MSByte - of the address is written, so make sure MSByte (first character) is unique among other - simultaneously receiving addresses). - - .. note:: The nRF24L01 shares the addresses' LSBytes (address[1:5]) on data pipes 2 through - 5. These shared LSBytes are determined by the address set to pipe 1. - """ - if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0,5]") - if len(address) != self.address_length: - raise ValueError("address must be a buffer protocol object with a byte length\nequal " - "to the address_length attribute (currently set to " - "{})".format(self.address_length)) - - # write the address - if pipe_number < 2: # write entire address if pipe_number is 0 or 1 + """This function is used to open a specific data pipe for OTA (over + the air) RX transmissions.""" + if not 0 <= pipe_number <= 5: + raise ValueError("pipe number must be in range [0, 5]") + if not address: + raise ValueError("address length cannot be 0") + if pipe_number < 2: if not pipe_number: - # save shadow copy of address if target pipe_number is 0. This is done to help - # ensure the proper address is set to pipe 0 via _start_listening() as - # open_tx_pipe() will appropriate the address on pipe 0 if auto_ack is enabled for - # TX mode self._pipe0_read_addr = address - self._pipes[pipe_number] = address - self._reg_write_bytes(RX_ADDR + pipe_number, address) + for i, val in enumerate(address): + self._pipes[pipe_number][i] = val + self._reg_write_bytes(RX_ADDR_P0 + pipe_number, address) else: - # only write MSByte if pipe_number is not 0 or 1 self._pipes[pipe_number] = address[0] - self._reg_write(RX_ADDR + pipe_number, address[0]) - - # now manage the pipe - self._open_pipes = self._reg_read(EN_RX) # refresh data - # enable the specified data pipe - self._open_pipes = self._open_pipes | (1 << pipe_number) - self._reg_write(EN_RX, self._open_pipes) - - # now adjust payload_length accordingly despite dynamic_payload setting - # radio only uses this info in RX mode when dynamic_payloads == True - self._reg_write(RX_PW + pipe_number, self.payload_length) - self._payload_widths[pipe_number] = self.payload_length + self._reg_write(RX_ADDR_P0 + pipe_number, address[0]) + self._open_pipes = self._reg_read(OPEN_PIPES) | (1 << pipe_number) + self._reg_write(OPEN_PIPES, self._open_pipes) @property def listen(self): - """An attribute to represent the nRF24L01 primary role as a radio. - - Setting this attribute incorporates the proper transitioning to/from RX mode as it involves - playing with the `power` attribute and the nRF24L01's CE pin. This attribute does not power - down the nRF24L01, but will power it up when needed; use `power` attribute set to `False` - to put the nRF24L01 to sleep. - - A valid input value is a `bool` in which: - - `True` enables RX mode. Additionally, per `Appendix B of the nRF24L01+ Specifications - Sheet - `_, this attribute - flushes the RX FIFO, clears the `irq_dr` status flag, and puts nRF24L01 in power up - mode. Notice the CE pin is be held HIGH during RX mode. - - `False` disables RX mode. As mentioned in above link, this puts nRF24L01's power in - Standby-I (CE pin is LOW meaning low current & no transmissions) mode which is ideal - for post-reception work. Disabing RX mode doesn't flush the RX/TX FIFO buffers, so - remember to flush your 3-level FIFO buffers when appropriate using `flush_tx()` or - `flush_rx()` (see also the `recv()` function). - """ + """An attribute to represent the nRF24L01 primary role as a radio.""" return self.power and bool(self._config & 1) @listen.setter def listen(self, is_rx): - assert isinstance(is_rx, (bool, int)) - if self.listen != is_rx: - self._start_listening() - else: - self._stop_listening() - - def _start_listening(self): - # ensure radio is in power down or standby-I mode - if self.ce_pin.value: + if self.listen != bool(is_rx): self.ce_pin.value = 0 - - if self._pipe0_read_addr is not None: - # make sure the last call to open_rx_pipe(0) sticks if initialized - self._reg_write_bytes(RX_ADDR, self._pipe0_read_addr) - self._pipes[0] = self._pipe0_read_addr # update the context as well - - # power up radio & set radio in RX mode - self._config = self._config & 0xFC | 3 - self._reg_write(CONFIG, self._config) - time.sleep(0.00015) # mandatory wait time to power up radio or switch modes (RX/TX) - self.flush_rx() # spec sheet says "used in RX mode" - self.clear_status_flags(True, False, False) # only Data Ready flag - - # enable radio comms - self.ce_pin.value = 1 # radio begins listening after CE pulse is > 130 µs - time.sleep(0.00013) # ensure pulse is > 130 µs - # nRF24L01 has just entered active RX + standby-II mode - - def _stop_listening(self): - # ensure radio is in standby-I mode - if self.ce_pin.value: - self.ce_pin.value = 0 - # set radio in TX mode as recommended behavior per spec sheet. - self._config = self._config & 0xFE # does not put radio to sleep - self._reg_write(CONFIG, self._config) - # mandated wait for transitioning between modes RX/TX - time.sleep(0.00016) - # exits while still in Standby-I (low current & no transmissions) + if is_rx: + if self._pipe0_read_addr is not None: + for i, val in enumerate(self._pipe0_read_addr): + self._pipes[0][i] = val + self._reg_write_bytes(RX_ADDR_P0, self._pipe0_read_addr) + self._config = (self._config & 0xFC) | 3 + self._reg_write(CONFIGURE, self._config) + time.sleep(0.00015) # mandatory wait to power up radio + self.flush_rx() + self.clear_status_flags(True, False, False) + self.ce_pin.value = 1 # mandatory pulse is > 130 µs + time.sleep(0.00013) + else: + self._config = self._config & 0xFE + self._reg_write(CONFIGURE, self._config) + time.sleep(0.00016) def any(self): - """This function checks if the nRF24L01 has received any data at all, and then reports the - next available payload's length (in bytes) -- if there is any. - - :returns: - - `int` of the size (in bytes) of an available RX payload (if any). - - ``0`` if there is no payload in the RX FIFO buffer. - """ - # 0x60 == R_RX_PL_WID command - return self._reg_read(0x60) # top-level payload length - - def recv(self): - """This function is used to retrieve the next available payload in the RX FIFO buffer, then - clears the `irq_dr` status flag. This function synonomous to `read_ack()`. - - :returns: A `bytearray` of the RX payload data or `None` if there is no payload - - - If the `dynamic_payloads` attribute is disabled, then the returned bytearray's length - is equal to the user defined `payload_length` attribute (which defaults to 32). - - If the `dynamic_payloads` attribute is enabled, then the returned bytearray's length - is equal to the payload's length - """ - if not self.irq_dr: + """This function checks if the nRF24L01 has received any data at all, + and then reports the next available payload's length (in bytes).""" + self.update() + if self.pipe is not None: + if self._features & 4: + return self._reg_read(0x60) + return self._pl_len[self.pipe] + return 0 + + def recv(self, length=None): + """This function is used to retrieve the next available payload in the + RX FIFO buffer, then clears the `irq_dr` status flag.""" + return_size = length if length is not None else self.any() + if not return_size: return None - # buffer size = current payload size - curr_pl_size = self.payload_length if not self.dynamic_payloads else self.any() - # get the data (0x61 = R_RX_PAYLOAD) - result = None if not curr_pl_size else self._reg_read_bytes(0x61, curr_pl_size) - # clear only Data Ready IRQ flag for accurate RX FIFO read operations + result = self._reg_read_bytes(0x61, return_size) self.clear_status_flags(True, False, False) - # return all available bytes from payload return result def send(self, buf, ask_no_ack=False, force_retry=0): - """This blocking function is used to transmit payload(s). - - :returns: - * `list` if a list or tuple of payloads was passed as the ``buf`` parameter. Each item - in the returned list will contain the returned status for each corresponding payload - in the list/tuple that was passed. The return statuses will be in one of the - following forms: - * `False` if transmission fails or reaches the timeout sentinal. The timeout condition - is very rare and could mean something/anything went wrong with either/both TX/RX - transceivers. The timeout sentinal for transmission is calculated using `table 18 in - the nRF24L01 specification sheet `_. - Transmission failure can only be returned if `arc` is greater than ``0``. - * `True` if transmission succeeds. - * `bytearray` or `None` when the `ack` attribute is `True`. Because the payload expects - a responding custom ACK payload, the response is returned (upon successful - transmission) as a - `bytearray` (or `None` if ACK payload is empty) - - :param bytearray,list,tuple buf: The payload to transmit. This bytearray must have a length - greater than 0 and less than 32, otherwise a `ValueError` exception is thrown. This can - also be a list or tuple of payloads (`bytearray`); in which case, all items in the - list/tuple are processed for consecutive transmissions. - - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is less - than the `payload_length` attribute, then this bytearray is padded with zeros until - its length is equal to the `payload_length` attribute. - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is - greater than `payload_length` attribute, then this bytearray's length is truncated to - equal the `payload_length` attribute. - :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for - an acknowledgment from the receiving nRF24L01. This parameter directly controls a - ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about - the payload). Therefore, it takes advantage of an nRF24L01 feature specific to - individual payloads, and its value is not saved anywhere. You do not need to specify - this for every payload if the `arc` attribute is disabled, however this parameter - will work despite the `arc` attribute's setting. - - .. note:: Each transmission is in the form of a packet. This packet contains sections - of data around and including the payload. `See Chapter 7.3 in the nRF24L01 - Specifications Sheet `_ for more - details. - :param int force_retry: The number of brute-force attempts to `resend()` a failed - transmission. Default is 0. This parameter has no affect on transmissions if `arc` is - ``0`` or ``ask_no_ack`` parameter is set to `True`. Each re-attempt still takes - advantage of `arc` & `ard` attributes. During multi-payload processing, this - parameter is meant to slow down CircuitPython devices just enough for the Raspberry - Pi to catch up (due to the Raspberry Pi's seemingly slower SPI speeds). See also - `resend()` as using this parameter carries the same implications documented there. - - .. tip:: It is highly recommended that `arc` attribute is enabled when sending - multiple payloads. Test results with the `arc` attribute disabled were very poor - (much < 50% received). This same advice applies to the ``ask_no_ack`` parameter (leave - it as `False` for multiple payloads). - .. warning:: The nRF24L01 will block usage of the TX FIFO buffer upon failed - transmissions. Failed transmission's payloads stay in TX FIFO buffer until the MCU - calls `flush_tx()` and `clear_status_flags()`. Therefore, this function will discard - failed transmissions' payloads when sending a list or tuple of payloads, so it can - continue to process through the list/tuple even if any payload fails to be - acknowledged. - """ - # ensure power down/standby-I for proper manipulation of PWR_UP & PRIM_RX bits in - # CONFIG register + """This blocking function is used to transmit payload(s).""" self.ce_pin.value = 0 - self.flush_tx() # be sure there is space in the TX FIFO - # using spec sheet calculations: - # timeout total = T_upload + 2 * stby2active + T_overAir + T_ack + T_irq + T_retry - # T_upload = payload length (in bits) / spi data rate (bits per second = - # baudrate / bits per byte) - # T_upload is finished before timeout begins - # T_download == T_upload, however RX devices spi settings must match TX's for - # accurate calc - # let 2 * stby2active (in µs) ~= (2 + (1 if getting ack else 0)) * 130 - # let T_ack = T_overAir as the payload size is the only distictive variable between - # the 2 - # T_overAir (in seconds) = ( 8 (bits/byte) * (1 byte preamble + address length + - # payload length + crc length) + 9 bit packet ID ) / RF data rate (in bits/sec) - # T_irq (in seconds) = (0.0000082 if self.data_rate == 1 else 0.000006) - # T_retry (in microseconds)= (arc * ard) - need_ack = self._setup_retr & 0x0f and not ask_no_ack - packet_data = 1 + self._addr_len + (max(0, ((self._config & 12) >> 2) - 1)) - bitrate = ((2000000 if self._rf_setup & 0x28 == 8 else 250000) - if self._rf_setup & 0x28 else 1000000) / 8 - t_ack = (((packet_data + 32) * 8 + 9) / bitrate) if need_ack else 0 # assumes 32-byte ACK - stby2active = (1 + (need_ack)) * 0.00013 - t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 - t_retry = (((self._setup_retr & 0xf0) >> 4) * 250 + 380) * \ - (self._setup_retr & 0x0f) / 1000000 - if isinstance(buf, (list, tuple)): # writing a set of payloads + if isinstance(buf, (list, tuple)): result = [] - for i, b in enumerate(buf): # check invalid payloads first - # this way when we raise a ValueError exception we don't leave the nRF24L01 in an - # unknown frozen state. - if not b or len(b) > 32: - raise ValueError("buf (item {} in the list/tuple) must be a" - " buffer protocol object with a byte length of\nat least 1 " - "and no greater than 32".format(i)) - for i, b in enumerate(buf): - timeout = (((8 * (len(b) + packet_data)) + 9) / bitrate) + \ - stby2active + t_irq + t_retry + t_ack + \ - (len(b) * 64 / self._spi.baudrate) # t_upload - self.clear_status_flags(False) # clear TX related flags - self.write(b, ask_no_ack) # clears TX flags on entering - time.sleep(0.00001) - self.ce_pin.value = 0 - self._wait_for_result(timeout) # now get result - if self._setup_retr & 0x0f and self.irq_df: - # need to clear for continuing transmissions - result.append(self._attempt2resend(force_retry)) - else: # if auto_ack is disabled - if self.ack and self.irq_dr and not ask_no_ack: - result.append(self.recv()) # save ACK payload & clears RX flag - else: - result.append(self.irq_ds) # will always be True (in this case) + for b in buf: + result.append(self.send(b, ask_no_ack, force_retry)) return result - if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with a byte length of" - "\nat least 1 and no greater than 32") - # T_upload is done before timeout begins (after payload write action AKA upload) - timeout = (((8 * (len(buf) + packet_data)) + 9) / - bitrate) + stby2active + t_irq + t_retry + t_ack - self.write(buf, ask_no_ack) # init using non-blocking helper - time.sleep(0.00001) # ensure CE pulse is >= 10 µs - # if pulse is stopped here, the nRF24L01 only handles the top level payload in the FIFO. - # we could hold CE HIGH to continue processing through the rest of the TX FIFO bound for - # the address passed to open_tx_pipe() - self.ce_pin.value = 0 # go to Standby-I power mode (power attribute still True) - self._wait_for_result(timeout) - if self._setup_retr & 0x0f and self.irq_df: - # if auto-retransmit is on and last attempt failed - result = self._attempt2resend(force_retry) - else: # if auto_ack is disabled - if self.ack and self.irq_dr and not ask_no_ack: - result = self.recv() # save ACK payload & clears RX flag - else: - result = self.irq_ds # will always be True (in this case) - self.clear_status_flags(False) # only TX related IRQ flags + self.flush_tx() + if self.pipe is not None: + self.flush_rx() + self.write(buf, ask_no_ack) + time.sleep(0.00001) + self.ce_pin.value = 0 + while not self._status & 0x30: + self.update() + result = self.irq_ds + if self.irq_df: + for _ in range(force_retry): + result = self.resend() + if result is None or result: + break + if self._status & 0x60 == 0x60: + result = self.recv() + self.clear_status_flags(False) return result @property - def irq_dr(self): - """A `bool` that represents the "Data Ready" interrupted flag. (read-only) - - * `True` represents Data is in the RX FIFO buffer - * `False` represents anything depending on context (state/condition of FIFO buffers) -- - usually this means the flag's been reset. + def tx_full(self): + """An attribute to represent the nRF24L01's status flag signaling + that the TX FIFO buffer is full. (read-only)""" + return bool(self._status & 1) - Pass ``dataReady`` parameter as `True` to `clear_status_flags()` and reset this. As this is - a virtual representation of the interrupt event, this attribute will always be updated - despite what the actual IRQ pin is configured to do about this event. + @property + def pipe(self): + """The identifying number of the data pipe that received + the next available payload in the RX FIFO buffer. (read only)""" + result = (self._status & 0x0E) >> 1 + if result <= 5: + return result + return None - Calling this does not execute an SPI transaction. It only exposes that latest data - contained in the STATUS byte that's always returned from any other SPI transactions. Use - the `update()` function to manually refresh this data when needed. - """ + @property + def irq_dr(self): + """A `bool` that represents the "Data Ready" interrupted flag. + (read-only)""" return bool(self._status & 0x40) @property def irq_ds(self): - """A `bool` that represents the "Data Sent" interrupted flag. (read-only) - - * `True` represents a successful transmission - * `False` represents anything depending on context (state/condition of FIFO buffers) -- - usually this means the flag's been reset. - - Pass ``dataSent`` parameter as `True` to `clear_status_flags()` to reset this. As this is a - virtual representation of the interrupt event, this attribute will always be updated - despite what the actual IRQ pin is configured to do about this event. - - Calling this does not execute an SPI transaction. It only exposes that latest data - contained in the STATUS byte that's always returned from any other SPI transactions. Use - the `update()` function to manually refresh this data when needed. - """ + """A `bool` that represents the "Data Sent" interrupted flag. + (read-only)""" return bool(self._status & 0x20) @property def irq_df(self): - """A `bool` that represents the "Data Failed" interrupted flag. (read-only) - - * `True` signifies the nRF24L01 attemped all configured retries - * `False` represents anything depending on context (state/condition) -- usually this means - the flag's been reset. - - Pass ``dataFail`` parameter as `True` to `clear_status_flags()` to reset this. As this is a - virtual representation of the interrupt event, this attribute will always be updated - despite what the actual IRQ pin is configured to do about this event.see also the `arc` and - `ard` attributes. - - Calling this does not execute an SPI transaction. It only exposes that latest data - contained in the STATUS byte that's always returned from any other SPI transactions. Use - the `update()` function to manually refresh this data when needed. - """ + """A `bool` that represents the "Data Failed" interrupted flag. + (read-only)""" return bool(self._status & 0x10) def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): - """This clears the interrupt flags in the status register. Internally, this is - automatically called by `send()`, `write()`, `recv()`, and when `listen` changes from - `False` to `True`. - - :param bool data_recv: specifies wheather to clear the "RX Data Ready" flag. - :param bool data_sent: specifies wheather to clear the "TX Data Sent" flag. - :param bool data_fail: specifies wheather to clear the "Max Re-transmit reached" flag. - - .. note:: Clearing the ``data_fail`` flag is necessary for continued transmissions from the - nRF24L01 (locks the TX FIFO buffer when `irq_df` is `True`) despite wheather or not the - MCU is taking advantage of the interrupt (IRQ) pin. Call this function only when there - is an antiquated status flag (after you've dealt with the specific payload related to - the staus flags that were set), otherwise it can cause payloads to be ignored and - occupy the RX/TX FIFO buffers. See `Appendix A of the nRF24L01+ Specifications Sheet - `_ for an outline of - proper behavior. - """ - # 0x07 = STATUS register; only bits 6 through 4 are write-able - self._reg_write(0x07, (data_recv << 6) | ( - data_sent << 5) | (data_fail << 4)) + """This clears the interrupt flags in the status register.""" + config = bool(data_recv) << 6 | bool(data_sent) << 5 | bool(data_fail) << 4 + self._reg_write(7, config) def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): - """Sets the configuration of the nRF24L01's IRQ (interrupt) pin. The signal from the - nRF24L01's IRQ pin is active LOW. (write-only) - - :param bool data_recv: If this is `True`, then IRQ pin goes active when there is new data - to read in the RX FIFO buffer. - :param bool data_sent: If this is `True`, then IRQ pin goes active when a payload from TX - buffer is successfully transmit. - :param bool data_fail: If this is `True`, then IRQ pin goes active when maximum number of - attempts to re-transmit the packet have been reached. If `auto_ack` attribute is - disabled, then this IRQ event is not used. - - .. note:: To fetch the status (not configuration) of these IRQ flags, use the `irq_df`, - `irq_ds`, `irq_dr` attributes respectively. - - .. tip:: Paraphrased from nRF24L01+ Specification Sheet: - - The procedure for handling ``data_recv`` IRQ should be: - - 1. read payload through `recv()` - 2. clear ``dataReady`` status flag (taken care of by using `recv()` in previous step) - 3. read FIFO_STATUS register to check if there are more payloads available in RX FIFO - buffer. (a call to `pipe()`, `any()` or even ``(False,True)`` as parameters to - `fifo()` will get this result) - 4. if there is more data in RX FIFO, repeat from step 1 - """ - self._config = self._reg_read(CONFIG) # refresh data - # save to register and update local copy of pwr & RX/TX modes' flags - self._config = (self._config & 0x0F) | (not data_fail << 4) | (not data_sent << 5) | \ - (not data_recv << 6) - self._reg_write(CONFIG, self._config) + """Sets the configuration of the nRF24L01's IRQ (interrupt) pin.""" + self._config = (self._reg_read(CONFIGURE) & 0x0F) | (not data_recv) << 6 + self._config |= (not data_fail) << 4 | (not data_sent) << 5 + self._reg_write(CONFIGURE, self._config) def what_happened(self, dump_pipes=False): - """This debuggung function aggregates and outputs all status/condition related information - from the nRF24L01. Some information may be irrelevant depending on nRF24L01's - state/condition. - - :prints: - - - ``Channel`` The current setting of the `channel` attribute - - ``RF Data Rate`` The current setting of the RF `data_rate` attribute. - - ``RF Power Amplifier`` The current setting of the `pa_level` attribute. - - ``CRC bytes`` The current setting of the `crc` attribute - - ``Address length`` The current setting of the `address_length` attribute - - ``Payload lengths`` The current setting of the `payload_length` attribute - - ``Auto retry delay`` The current setting of the `ard` attribute - - ``Auto retry attempts`` The current setting of the `arc` attribute - - ``Packets lost on current channel`` Total amount of packets lost (transmission - failures). This only resets when the `channel` is changed. This count will - only go up 15. - - ``Retry attempts made for last transmission`` Amount of attempts to re-transmit - during last - transmission (resets per payload) - - ``IRQ - Data Ready`` The current setting of the IRQ pin on "Data Ready" event - - ``IRQ - Data Sent`` The current setting of the IRQ pin on "Data Sent" event - - ``IRQ - Data Fail`` The current setting of the IRQ pin on "Data Fail" event - - ``Data Ready`` Is there RX data ready to be read? - (state of the `irq_dr` flag) - - ``Data Sent`` Has the TX data been sent? (state of the `irq_ds` flag) - - ``Data Failed`` Has the maximum attempts to re-transmit been reached? - (state of the `irq_df` flag) - - ``TX FIFO full`` Is the TX FIFO buffer full? (state of the `tx_full` flag) - - ``TX FIFO empty`` Is the TX FIFO buffer empty? - - ``RX FIFO full`` Is the RX FIFO buffer full? - - ``RX FIFO empty`` Is the RX FIFO buffer empty? - - ``Custom ACK payload`` Is the nRF24L01 setup to use an extra (user defined) payload - attached to the acknowledgment packet? (state of the `ack` attribute) - - ``Ask no ACK`` Is the nRF24L01 setup to transmit individual packets that don't - require acknowledgment? - - ``Automatic Acknowledgment`` Is the `auto_ack` attribute enabled? - - ``Dynamic Payloads`` Is the `dynamic_payloads` attribute enabled? - - ``Primary Mode`` The current mode (RX or TX) of communication of the nRF24L01 device. - - ``Power Mode`` The power state can be Off, Standby-I, Standby-II, or On. - - :param bool dump_pipes: `True` appends the output and prints: - - * the current address used for TX transmissions - * ``Pipe [#] ([open/closed]) bound: [address]`` where ``#`` represent the pipe number, - the ``open/closed`` status is relative to the pipe's RX status, and ``address`` is - read directly from the nRF24L01 registers. - * if the pipe is open, then the output also prints ``expecting [X] byte static - payloads`` where ``X`` is the `payload_length` (in bytes) the pipe is setup to - receive when `dynamic_payloads` is disabled. - - Default is `False` and skips this extra information. - """ - watchdog = self._reg_read(8) # 8 == OBSERVE_TX register - print("Channel___________________{} ~ {} GHz".format( - self.channel, (self.channel + 2400) / 1000)) - print("RF Data Rate______________{} {}".format( - self.data_rate, "Mbps" if self.data_rate != 250 else "Kbps")) + """This debuggung function aggregates and outputs all status/condition + related information from the nRF24L01.""" + observer = self._reg_read(8) + print( + "Channel___________________{} ~ {} GHz".format( + self.channel, (self.channel + 2400) / 1000 + ) + ) + print( + "RF Data Rate______________{} {}".format( + self.data_rate, "Mbps" if self.data_rate != 250 else "Kbps" + ) + ) print("RF Power Amplifier________{} dbm".format(self.pa_level)) + print( + "RF Low Noise Amplifier____{}".format( + "Enabled" if self.is_lna_enabled else "Disabled" + ) + ) print("CRC bytes_________________{}".format(self.crc)) print("Address length____________{} bytes".format(self.address_length)) - print("Payload lengths___________{} bytes".format(self.payload_length)) + print("TX Payload lengths________{} bytes".format(self.payload_length[0])) print("Auto retry delay__________{} microseconds".format(self.ard)) print("Auto retry attempts_______{} maximum".format(self.arc)) - print("Packets lost on current channel_____________________{}".format( - (watchdog & 0xF0) >> 4)) - print("Retry attempts made for last transmission___________{}".format(watchdog & 0x0F)) - print("IRQ - Data Ready______{} Data Ready___________{}".format( - '_True' if not bool(self._config & 0x40) else 'False', self.irq_dr)) - print("IRQ - Data Fail_______{} Data Failed__________{}".format( - '_True' if not bool(self._config & 0x20) else 'False', self.irq_df)) - print("IRQ - Data Sent_______{} Data Sent____________{}".format( - '_True' if not bool(self._config & 0x10) else 'False', self.irq_ds)) - print("TX FIFO full__________{} TX FIFO empty________{}".format( - '_True' if bool(self.tx_full) else 'False', bool(self.fifo(True, True)))) - print("RX FIFO full__________{} RX FIFO empty________{}".format( - '_True' if bool(self._fifo & 2) else 'False', bool(self._fifo & 1))) - print("Ask no ACK_________{} Custom ACK Payload___{}".format( - '_Allowed' if bool(self._features & 1) else 'Disabled', - 'Enabled' if self.ack else 'Disabled')) - print("Dynamic Payloads___{} Auto Acknowledgment__{}".format( - '_Enabled' if self.dynamic_payloads else 'Disabled', - 'Enabled' if self.auto_ack else 'Disabled')) - print("Primary Mode_____________{} Power Mode___________{}".format( - 'RX' if self.listen else 'TX', - ('Standby-II' if self.ce_pin.value else 'Standby-I') if self._config & 2 else 'Off')) + print( + "Packets lost on current channel_____________________{}".format( + (observer & 0xF0) >> 4 + ) + ) + print( + "Retry attempts made for last transmission___________{}".format( + observer & 0x0F + ) + ) + print( + "IRQ - Data Ready______{} Data Ready___________{}".format( + "_True" if not bool(self._config & 0x40) else "False", self.irq_dr + ) + ) + print( + "IRQ - Data Fail_______{} Data Failed__________{}".format( + "_True" if not bool(self._config & 0x20) else "False", self.irq_df + ) + ) + print( + "IRQ - Data Sent_______{} Data Sent____________{}".format( + "_True" if not bool(self._config & 0x10) else "False", self.irq_ds + ) + ) + print( + "TX FIFO full__________{} TX FIFO empty________{}".format( + "_True" if bool(self.tx_full) else "False", bool(self.fifo(True, True)) + ) + ) + print( + "RX FIFO full__________{} RX FIFO empty________{}".format( + "_True" if bool(self._fifo & 2) else "False", bool(self._fifo & 1) + ) + ) + print( + "Ask no ACK_________{} Custom ACK Payload___{}".format( + "_Allowed" if bool(self._features & 1) else "Disabled", + "Enabled" if self.ack else "Disabled", + ) + ) + print( + "Dynamic Payloads___{} Auto Acknowledgment__{}".format( + "_Enabled" + if self._dyn_pl == 0x3F + else ( + bin(self._dyn_pl).replace( + "0b", "0b" + "0" * (8 - len(bin(self._dyn_pl))) + ) + if self._dyn_pl + else "Disabled" + ), + "Enabled" + if self._aa == 0x3F + else ( + bin(self._aa).replace("0b", "0b" + "0" * (8 - len(bin(self._aa)))) + if self._aa + else "Disabled" + ), + ) + ) + print( + "Primary Mode_____________{} Power Mode___________{}".format( + "RX" if self.listen else "TX", + ("Standby-II" if self.ce_pin.value else "Standby-I") + if self._config & 2 + else "Off", + ) + ) if dump_pipes: - print('TX address____________', self._tx_address) - for i, address in enumerate(self._pipes): - is_open = "( open )" if self._open_pipes & (1 << i) else "(closed)" - if i <= 1: # print full address - print("Pipe", i, is_open, "bound:", address) - else: # print unique byte + shared bytes = actual address used by radio - print("Pipe", i, is_open, "bound:", - bytes([self._pipes[i]]) + self._pipes[1][1:]) - if self._open_pipes & (1 << i): - print('\t\texpecting', self._payload_widths[i], 'byte static payloads') + print("TX address____________", self.address()) + self._open_pipes = self._reg_read(OPEN_PIPES) + for i in range(6): + is_open = self._open_pipes & (1 << i) + print( + "Pipe", + i, + "( open )" if is_open else "(closed)", + "bound:", + self.address(i), + ) + if is_open: + print("\t\texpecting", self._pl_len[i], "byte static payloads") @property def dynamic_payloads(self): - """This `bool` attribute controls the nRF24L01's dynamic payload length feature. - - - `True` enables nRF24L01's dynamic payload length feature. The `payload_length` - attribute is ignored when this feature is enabled. - - `False` disables nRF24L01's dynamic payload length feature. Be sure to adjust - the `payload_length` attribute accordingly when `dynamic_payloads` feature is disabled. - """ - return bool(self._dyn_pl and (self._features & 4)) + """This `bool` attribute controls the nRF24L01's dynamic payload + length feature for each pipe.""" + self._dyn_pl = self._reg_read(DYN_PL_LEN) + self._features = self._reg_read(TX_FEATURE) + return bool(self._dyn_pl) and self._features & 4 == 4 @dynamic_payloads.setter def dynamic_payloads(self, enable): - assert isinstance(enable, (bool, int)) - self._features = self._reg_read(FEATURE) # refresh data - # save changes to registers(& their shadows) - if self._features & 4 != enable: # if not already - # throw a specific global flag for enabling dynamic payloads - self._features = (self._features & 3) | (enable << 2) - self._reg_write(FEATURE, self._features) - # 0x3F == all pipes have enabled dynamic payloads - self._dyn_pl = 0x3F if enable else 0 - self._reg_write(DYNPD, self._dyn_pl) + self._features = self._reg_read(TX_FEATURE) + if isinstance(enable, (bool, int)): + self._dyn_pl = 0x3F if enable else 0 + elif isinstance(enable, (list, tuple)): + for i, val in enumerate(enable): + if i < 6: + self._dyn_pl = (self._dyn_pl & ~(1 << i)) | (bool(val) << i) + else: + raise ValueError("dynamic_payloads: {} is an invalid input" % enable) + if bool(self._features & 4) != (self._dyn_pl & 1): + self._features = (self._features & 3) | ((self._dyn_pl & 1) << 2) + self._reg_write(TX_FEATURE, self._features) + if self._dyn_pl: + self._aa |= self._dyn_pl + self._reg_write(AUTO_ACK, self._aa) + self._config = self._reg_read(CONFIGURE) + self._reg_write(DYN_PL_LEN, self._dyn_pl) @property def payload_length(self): - """This `int` attribute specifies the length (in bytes) of payload that is regarded, - meaning "how big of a payload should the radio care about?" If the `dynamic_payloads` - attribute is enabled, this attribute has no affect. When `dynamic_payloads` is disabled, - this attribute is used to specify the payload length when entering RX mode. - - A valid input value must be an `int` in range [1,32]. Otherwise a `ValueError` exception is - thrown. Default is set to the nRF24L01's maximum of 32. - - .. note:: When `dynamic_payloads` is disabled during transmissions: - - - Payloads' size of greater than this attribute's value will be truncated to match. - - Payloads' size of less than this attribute's value will be padded with zeros to - match. - """ - return self._payload_length + """This `int` attribute specifies the length (in bytes) of static payloads for each + pipe.""" + return self._pl_len @payload_length.setter def payload_length(self, length): - # max payload size is 32 bytes - if not length or length <= 32: - # save for access via getter property - self._payload_length = length - else: - raise ValueError( - "{}: payload length can only be set in range [1,32] bytes".format(length)) + if isinstance(length, int): + length = [length] * 6 + elif not isinstance(length, (list, tuple)): + raise ValueError("length {} is not a valid input".format(length)) + for i, val in enumerate(length): + if i < 6: + if not val or val <= 32: # don't throw exception, just skip pipe + self._pl_len[i] = val + self._reg_write(RX_PL_LENG + i, val) @property def arc(self): - """"This `int` attribute specifies the nRF24L01's number of attempts to re-transmit TX - payload when acknowledgment packet is not received. The `auto_ack` must be enabled on the - receiving nRF24L01, otherwise this attribute will make `send()` seem like it failed. - - A valid input value must be in range [0,15]. Otherwise a `ValueError` exception is thrown. - Default is set to 3. A value of ``0`` disables the automatic re-transmit feature and - considers all payload transmissions a success. + """This `int` attribute specifies the nRF24L01's number of attempts + to re-transmit TX payload when acknowledgment packet is not received. """ - self._setup_retr = self._reg_read(SETUP_RETR) # refresh data - return self._setup_retr & 0x0f + self._retry_setup = self._reg_read(SETUP_RETR) + return self._retry_setup & 0x0F @arc.setter def arc(self, count): - if 0 <= count <= 15: - if self.arc & 0x0F != count: # write only if needed - # save changes to register(& its shadow) - self._setup_retr = (self._setup_retr & 0xF0) | count - self._reg_write(SETUP_RETR, self._setup_retr) - else: - raise ValueError( - "automatic re-transmit count(/attempts) must in range [0,15]") + if not 0 <= count <= 15: + raise ValueError("automatic re-transmit count must in range [0, 15]") + self._retry_setup = (self._retry_setup & 0xF0) | count + self._reg_write(SETUP_RETR, self._retry_setup) @property def ard(self): - """This `int` attribute specifies the nRF24L01's delay (in µs) between attempts to - automatically re-transmit the TX payload when an expected acknowledgement (ACK) packet is - not received. During this time, the nRF24L01 is listening for the ACK packet. If the - `auto_ack` attribute is disabled, this attribute is not applied. - - A valid input value must be a multiple of 250 in range [250,4000]. Otherwise a `ValueError` - exception is thrown. Default is 1500 for reliability. - - .. note:: Paraphrased from nRF24L01 specifications sheet: - - Please take care when setting this parameter. If the custom ACK payload is more than 15 - bytes in 2 Mbps data rate, the `ard` must be 500µS or more. If the custom ACK payload - is more than 5 bytes in 1 Mbps data rate, the `ard` must be 500µS or more. In 250kbps - data rate (even when there is no custom ACK payload) the `ard` must be 500µS or more. - - See `data_rate` attribute on how to set the data rate of the nRF24L01's transmissions. - """ - self._setup_retr = self._reg_read(SETUP_RETR) # refresh data - return ((self._setup_retr & 0xf0) >> 4) * 250 + 250 + """This `int` attribute specifies the nRF24L01's delay (in + microseconds) between attempts to automatically re-transmit the + TX payload when an expected acknowledgement (ACK) packet is not + received.""" + self._retry_setup = self._reg_read(SETUP_RETR) + return ((self._retry_setup & 0xF0) >> 4) * 250 + 250 @ard.setter - def ard(self, delta_t): - if 250 <= delta_t <= 4000 and delta_t % 250 == 0: - # set new ARD data and current ARC data to register - if self.ard != delta_t: # write only if needed - # save changes to register(& its Shadow) - self._setup_retr = (int((delta_t - 250) / 250) - << 4) | (self._setup_retr & 0x0F) - self._reg_write(SETUP_RETR, self._setup_retr) - else: - raise ValueError("automatic re-transmit delay can only be a multiple of 250 in range " - "[250,4000]") + def ard(self, delta): + if not 250 <= delta <= 4000: + raise ValueError("automatic re-transmit delay must be in range [250, 4000]") + self._retry_setup = (self._retry_setup & 15) | int((delta - 250) / 250) << 4 + self._reg_write(SETUP_RETR, self._retry_setup) @property def auto_ack(self): - """This `bool` attribute controls the nRF24L01's automatic acknowledgment feature during - the process of receiving a packet. - - - `True` enables transmitting automatic acknowledgment packets. The CRC (cyclic redundancy - checking) is enabled automatically by the nRF24L01 if the `auto_ack` attribute is enabled - (see also `crc` attribute). - - `False` disables transmitting automatic acknowledgment packets. The `crc` attribute will - remain unaffected when disabling the `auto_ack` attribute. - """ - return self._aa + """This `bool` attribute controls the nRF24L01's automatic + acknowledgment feature during the process of receiving a packet.""" + self._aa = self._reg_read(AUTO_ACK) + return bool(self._aa) @auto_ack.setter def auto_ack(self, enable): - assert isinstance(enable, (bool, int)) - # the following 0x3F == enabled auto_ack on all pipes - self._aa = 0x3F if enable else 0 - self._reg_write(EN_AA, self._aa) # 1 == EN_AA register for ACK feature - # nRF24L01 automatically enables CRC if ACK packets are enabled in the FEATURE register + if isinstance(enable, (bool, int)): + self._aa = 0x3F if enable else 0 + elif isinstance(enable, (list, tuple)): + for i, val in enumerate(enable): + if i < 6: + self._aa = (self._aa & ~(1 << i)) | (bool(val) << i) + else: + raise ValueError("auto_ack: {} is not a valid input" % enable) + self._reg_write(AUTO_ACK, self._aa) + if self._aa: # refresh crc data if enabled + self._config = self._reg_read(CONFIGURE) @property def ack(self): - """This `bool` attribute represents the status of the nRF24L01's capability to use custom - payloads as part of the automatic acknowledgment (ACK) packet. Use this attribute to - set/check if the custom ACK payloads feature is enabled. - - - `True` enables the use of custom ACK payloads in the ACK packet when responding to - receiving transmissions. As `dynamic_payloads` and `auto_ack` attributes are required for - this feature to work, they are automatically enabled as needed. - - `False` disables the use of custom ACK payloads. Disabling this feature does not disable - the `auto_ack` and `dynamic_payloads` attributes (they work just fine without this - feature). - """ - return bool((self._features & 2) and self.auto_ack and self.dynamic_payloads) + """This `bool` attribute represents the status of the nRF24L01's + capability to use custom payloads as part of the automatic + acknowledgment (ACK) packet.""" + self._aa = self._reg_read(AUTO_ACK) + self._dyn_pl = self._reg_read(DYN_PL_LEN) + self._features = self._reg_read(TX_FEATURE) + return bool((self._features & 6) == 6 and ((self._aa & self._dyn_pl) & 1)) @ack.setter def ack(self, enable): - assert isinstance(enable, (bool, int)) - # we need to throw the EN_ACK_PAY flag in the FEATURES register accordingly on both - # TX & RX nRF24L01s - if self.ack != enable: # if enabling - self.auto_ack = True # ensure auto_ack feature is enabled - # dynamic_payloads required for custom ACK payloads - self._dyn_pl = 0x3F - self._reg_write(DYNPD, self._dyn_pl) - else: - # setting auto_ack feature automatically updated the _features attribute, so - self._features = self._reg_read(FEATURE) # refresh data here - self._features = (self._features & 5) | (6 if enable else 0) - self._reg_write(FEATURE, self._features) + if self.ack != bool(enable): + self.auto_ack = (1,) + self._dyn_pl = self._dyn_pl & ~1 | 1 + self._reg_write(DYN_PL_LEN, self._dyn_pl) + self._features = self._features & 3 | 4 + self._features = self._features & 5 | bool(enable) << 1 + self._reg_write(TX_FEATURE, self._features) def load_ack(self, buf, pipe_number): - """This allows the MCU to specify a payload to be allocated into the TX FIFO buffer for use - on a specific data pipe. This payload will then be appended to the automatic acknowledgment - (ACK) packet that is sent when fresh data is received on the specified pipe. See - `read_ack()` on how to fetch a received custom ACK payloads. - - :param bytearray buf: This will be the data attached to an automatic ACK packet on the - incoming transmission about the specified ``pipe_number`` parameter. This must have a - length in range [1,32] bytes, otherwise a `ValueError` exception is thrown. Any ACK - payloads will remain in the TX FIFO buffer until transmitted successfully or - `flush_tx()` is called. - :param int pipe_number: This will be the pipe number to use for deciding which - transmissions get a response with the specified ``buf`` parameter's data. This number - must be in range [0,5], otherwise a `ValueError` exception is thrown. - - :returns: `True` if payload was successfully loaded onto the TX FIFO buffer. `False` if it - wasn't because TX FIFO buffer is full. - - .. note:: this function takes advantage of a special feature on the nRF24L01 and needs to - be called for every time a customized ACK payload is to be used (not for every - automatic ACK packet -- this just appends a payload to the ACK packet). The `ack`, - `auto_ack`, and `dynamic_payloads` attributes are also automatically enabled by this - function when necessary. - - .. tip:: The ACK payload must be set prior to receiving a transmission. It is also worth - noting that the nRF24L01 can hold up to 3 ACK payloads pending transmission. Using this - function does not over-write existing ACK payloads pending; it only adds to the queue - (TX FIFO buffer) if it can. Use `flush_tx()` to discard unused ACK payloads when done - listening. - """ + """This allows the MCU to specify a payload to be allocated into the + TX FIFO buffer for use on a specific data pipe.""" if pipe_number < 0 or pipe_number > 5: - raise ValueError("pipe number must be in range [0,5]") + raise ValueError("pipe_number must be in range [0, 5]") if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with a byte length of" - "\nat least 1 and no greater than 32") - # only prepare payload if the auto_ack attribute is enabled and ack[0] is not None - if not self.ack: + raise ValueError("payload must have a byte length in range [1, 32]") + if not bool((self._features & 6) == 6 and ((self._aa & self._dyn_pl) & 1)): self.ack = True if not self.tx_full: - # 0xA8 = W_ACK_PAYLOAD self._reg_write_bytes(0xA8 | pipe_number, buf) - return True # payload was loaded - return False # payload wasn't loaded + return True + return False def read_ack(self): - """Allows user to read the automatic acknowledgement (ACK) payload (if any) when nRF24L01 - is in TX mode. This function is called from a blocking `send()` call if the `ack` attribute - is enabled. Alternatively, this function can be called directly in case of calling the - non-blocking `write()` function during asychronous applications. This function is an alias - of `recv()` and remains for bakward compatibility with older versions of this library. - - .. note:: See also the `ack`, `dynamic_payloads`, and `auto_ack` attributes as they must be - enabled to use custom ACK payloads. - """ + """Allows user to read the automatic acknowledgement (ACK) payload (if any).""" return self.recv() @property def data_rate(self): - """This `int` attribute specifies the nRF24L01's frequency data rate for OTA (over the air) - transmissions. - - A valid input value is: - - - ``1`` sets the frequency data rate to 1 Mbps - - ``2`` sets the frequency data rate to 2 Mbps - - ``250`` sets the frequency data rate to 250 Kbps - - Any invalid input throws a `ValueError` exception. Default is 1 Mbps. - - .. warning:: 250 Kbps is be buggy on the non-plus models of the nRF24L01 product line. If - you use 250 Kbps data rate, and some transmissions report failed by the transmitting - nRF24L01, even though the same packet in question actually reports received by the - receiving nRF24L01, then try a higher data rate. CAUTION: Higher data rates mean less - maximum distance between nRF24L01 transceivers (and vise versa). - """ - self._rf_setup = self._reg_read(RF_SETUP) # refresh data - return (2 if self._rf_setup & 0x28 == 8 else 250) if self._rf_setup & 0x28 else 1 + """This `int` attribute specifies the nRF24L01's frequency data rate + for OTA (over the air) transmissions.""" + self._rf_setup = self._reg_read(RF_PA_RATE) + rf_setup = self._rf_setup & 0x28 + return (2 if rf_setup == 8 else 250) if rf_setup else 1 @data_rate.setter def data_rate(self, speed): - # nRF24L01+ must be in a standby or power down mode before writing to the configuration - # registers. - if speed in (1, 2, 250): - if self.data_rate != speed: - speed = 0 if speed == 1 else (8 if speed == 2 else 0x20) - # save changes to register(& its shadow) + if not speed in (1, 2, 250): + raise ValueError("data_rate must be 1 (Mbps), 2 (Mbps), or 250 (kbps)") + if self.data_rate != speed: + speed = 0 if speed == 1 else (0x20 if speed != 2 else 8) self._rf_setup = self._rf_setup & 0xD7 | speed - self._reg_write(RF_SETUP, self._rf_setup) - else: - raise ValueError( - "data rate must be one of the following ([M,M,K]bps): 1, 2, 250") + self._reg_write(RF_PA_RATE, self._rf_setup) @property def channel(self): - """This `int` attribute specifies the nRF24L01's frequency (in 2400 + `channel` MHz). - - A valid input value must be in range [0, 125] (that means [2.4, 2.525] GHz). Otherwise a - `ValueError` exception is thrown. Default is 76. - """ - return self._reg_read(RF_CH) + """This `int` attribute specifies the nRF24L01's frequency.""" + return self._reg_read(5) @channel.setter def channel(self, channel): - if 0 <= channel <= 125: - self._channel = channel - self._reg_write(RF_CH, channel) # always writes to reg - else: - raise ValueError("channel acn only be set in range [0,125]") + if not 0 <= int(channel) <= 125: + raise ValueError("channel can only be set in range [0, 125]") + self._channel = int(channel) + self._reg_write(5, self._channel) @property def crc(self): - """This `int` attribute specifies the nRF24L01's CRC (cyclic redundancy checking) encoding - scheme in terms of byte length. CRC is a way of making sure that the transmission didn't - get corrupted over the air. - - A valid input value is in range [0,2]: - - - ``0`` disables CRC (no anti-corruption of data) - - ``1`` enables CRC encoding scheme using 1 byte (weak anti-corruption of data) - - ``2`` enables CRC encoding scheme using 2 bytes (better anti-corruption of data) - - Any invalid input throws a `ValueError` exception. Default is enabled using 2 bytes. - - .. note:: The nRF24L01 automatically enables CRC if automatic acknowledgment feature is - enabled (see `auto_ack` attribute). - """ - self._config = self._reg_read(CONFIG) # refresh data - return max(0, ((self._config & 12) >> 2) - 1) # this works + """This `int` attribute specifies the nRF24L01's CRC (cyclic + redundancy checking) encoding scheme in terms of byte length.""" + self._config = self._reg_read(CONFIGURE) + return max(0, ((self._config & 0x0C) >> 2) - 1) @crc.setter def crc(self, length): - if 0 <= length <= 2: - if self.crc != length: - length = (length + 1) << 2 if length else 0 # this works - # save changes to register(& its Shadow) - self._config = self._config & 0x73 | length - self._reg_write(0, self._config) - else: - raise ValueError( - "CRC byte length must be an int equal to 0 (off), 1, or 2") + if not 0 <= length <= 2: + raise ValueError("CRC byte length must be an int equal to 0 (off), 1, or 2") + if self.crc != length: + length = (length + 1) << 2 if length else 0 + self._config = self._config & 0x73 | length + self._reg_write(0, self._config) @property def power(self): - """This `bool` attribute controls the power state of the nRF24L01. This is exposed for - asynchronous applications and user preference. - - - `False` basically puts the nRF24L01 to sleep (AKA power down mode) with ultra-low - current consumption. No transmissions are executed when sleeping, but the nRF24L01 can - still be accessed through SPI. Upon instantiation, this driver class puts the nRF24L01 - to sleep until the MCU invokes RX/TX transmissions. This driver class doesn't power down - the nRF24L01 after RX/TX transmissions are complete (avoiding the required power up/down - 130 µs wait time), that preference is left to the user. - - `True` powers up the nRF24L01. This is the first step towards entering RX/TX modes (see - also `listen` attribute). Powering up is automatically handled by the `listen` attribute - as well as the `send()` and `write()` functions. - - .. note:: This attribute needs to be `True` if you want to put radio on Standby-II (highest - current consumption) or Standby-I (moderate current consumption) modes. TX - transmissions are only executed during Standby-II by calling `send()` or `write()`. RX - transmissions are received during Standby-II by setting `listen` attribute to `True` - (see `Chapter 6.1.2-7 of the nRF24L01+ Specifications Sheet `_). After using `send()` or setting `listen` to `False`, the nRF24L01 - is left in Standby-I mode (see also notes on the `write()` function). - """ + """This `bool` attribute controls the power state of the nRF24L01.""" + self._config = self._reg_read(CONFIGURE) return bool(self._config & 2) @power.setter def power(self, is_on): - assert isinstance(is_on, (bool, int)) - # capture surrounding flags and set PWR_UP flag according to is_on boolean - self._config = self._reg_read(CONFIG) # refresh data - if self.power != is_on: - # only write changes - self._config = (self._config & 0x7d) | ( - is_on << 1) # doesn't affect TX?RX mode - self._reg_write(CONFIG, self._config) - # power up/down takes < 150 µs + 4 µs + self._config = self._reg_read(CONFIGURE) + if self.power != bool(is_on): + self._config = self._config & 0x7D | bool(is_on) << 1 + self._reg_write(CONFIGURE, self._config) time.sleep(0.00016) @property def pa_level(self): - """This `int` attribute specifies the nRF24L01's power amplifier level (in dBm). Higher - levels mean the transmission will cover a longer distance. Use this attribute to tweak the - nRF24L01 current consumption on projects that don't span large areas. - - A valid input value is: - - - ``-18`` sets the nRF24L01's power amplifier to -18 dBm (lowest) - - ``-12`` sets the nRF24L01's power amplifier to -12 dBm - - ``-6`` sets the nRF24L01's power amplifier to -6 dBm - - ``0`` sets the nRF24L01's power amplifier to 0 dBm (highest) - - Any invalid input throws a `ValueError` exception. Default is 0 dBm. - """ - self._rf_setup = self._reg_read(RF_SETUP) # refresh data - return (3 - ((self._rf_setup & RF_SETUP) >> 1)) * -6 + """This `int` attribute specifies the nRF24L01's power amplifier level (in dBm).""" + self._rf_setup = self._reg_read(RF_PA_RATE) + return (3 - ((self._rf_setup & 6) >> 1)) * -6 @pa_level.setter def pa_level(self, power): - # nRF24L01+ must be in a standby or power down mode before writing to the - # configuration registers. - if power in (-18, -12, -6, 0): - power = (3 - int(power / -6)) * 2 # this works - # save changes to register (& its shadow) - self._rf_setup = (self._rf_setup & 0xF9) | power - self._reg_write(RF_SETUP, self._rf_setup) - else: - raise ValueError( - "power amplitude must be one of the following (dBm): -18, -12, -6, 0") - - @property - def rpd(self): - """This read-only attribute returns `True` if RPD (Received Power Detector) is triggered - or `False` if not triggered. - - .. note:: The RPD flag is triggered in the following cases: - - 1. During RX mode (`listen` = `True`) and a RF transmission with a gain above a preset - (non-adjustable) -64 dBm threshold. - 2. When a packet is received (indicative of the nRF24L01 used to detect/"listen" for - incoming packets). - 3. When the nRF24L01's CE pin goes from HIGH to LOW (or when the `listen` attribute - changes from `True` to `False`). - 4. When the underlying ESB (Enhanced ShockBurst) protocol reaches a hardcoded - (non-adjustable) RX timeout. - - """ - return bool(self._reg_read(0x09)) + lna_bit = True + if isinstance(power, (list, tuple)) and len(power) > 1: + lna_bit, power = bool(power[1]), int(power[0]) + if power not in (-18, -12, -6, 0): + raise ValueError("pa_level must be -18, -12, -6, or 0 (in dBm)") + power = (3 - int(power / -6)) * 2 + self._rf_setup = (self._rf_setup & 0xF8) | power | lna_bit + self._reg_write(RF_PA_RATE, self._rf_setup) @property - def tx_full(self): - """An attribute to represent the nRF24L01's status flag signaling that the TX FIFO buffer - is full. (read-only) - - Calling this does not execute an SPI transaction. It only exposes that latest data - contained in the STATUS byte that's always returned from any SPI transactions with the - nRF24L01. Use the `update()` function to manually refresh this data when needed. - - :returns: - * `True` for TX FIFO buffer is full - * `False` for TX FIFO buffer is not full. This doesn't mean the TX FIFO buffer is - empty. - """ - return bool(self._status & 1) + def is_lna_enabled(self): + """A read-only `bool` attribute about the LNA (Low Noise Amplifier) gain + feature used in the nRF24L01-PA/LNA modules.""" + self._rf_setup = self._reg_read(RF_PA_RATE) + return bool(self._rf_setup & 1) def update(self): - """This function is only used to get an updated status byte over SPI from the nRF24L01 and - is exposed to the MCU for asynchronous applications. Refreshing the status byte is vital to - checking status of the interrupts, RX pipe number related to current RX payload, and if the - TX FIFO buffer is full. This function returns nothing, but internally updates the `irq_dr`, - `irq_ds`, `irq_df`, and `tx_full` attributes. Internally this is a helper function to - `pipe()`, `send()`, and `resend()` functions""" - # perform non-operation to get status byte - # should be faster than reading the STATUS register + """This function is only used to get an updated status byte over SPI + from the nRF24L01.""" self._reg_write(0xFF) def resend(self): """Use this function to maunally re-send the previous payload in the - top level (first out) of the TX FIFO buffer. All returned data follows the same patttern - that `send()` returns with the added condition that this function will return `False` - if the TX FIFO buffer is empty. - - .. note:: The nRF24L01 normally removes a payload from the TX FIFO buffer after successful - transmission, but not when this function is called. The payload (successfully - transmitted or not) will remain in the TX FIFO buffer until `flush_tx()` is called to - remove them. Alternatively, using this function also allows the failed payload to be - over-written by using `send()` or `write()` to load a new payload. - """ + top level (first out) of the TX FIFO buffer.""" result = False - if not self.fifo(True, True): # is there a pre-existing payload - self.clear_status_flags(False) # clear TX related flags - # indicate existing payload will get re-used. - # This command tells the radio not pop TX payload from FIFO on success - self._reg_write(0xE3) # 0xE3 == REUSE_TX_PL command - # timeout calc assumes 32 byte payload because there is no way to tell when payload - # has already been loaded into TX FIFO; also assemues 32-byte ACK if needed - pl_coef = 1 + bool(self._setup_retr & 0x0f) - pl_len = 1 + self._addr_len + ( - max(0, ((self._config & 12) >> 2) - 1)) - bitrate = ((2000000 if self._rf_setup & 0x28 == 8 else 250000) - if self._rf_setup & 0x28 else 1000000) / 8 - stby2active = (1 + pl_coef) * 0.00013 - t_irq = 0.0000082 if not self._rf_setup & 0x28 else 0.000006 - t_retry = (((self._setup_retr & 0xf0) >> 4) * 250 + - 380) * (self._setup_retr & 0x0f) / 1000000 - timeout = pl_coef * (((8 * (32 + pl_len)) + 9) / bitrate) + \ - stby2active + t_irq + t_retry - # cycle the CE pin to re-enable transmission of re-used payload + if not self.fifo(True, True): + if self.pipe is not None: + self.flush_rx() + self.clear_status_flags() + self._reg_write(0xE3) self.ce_pin.value = 0 self.ce_pin.value = 1 time.sleep(0.00001) - self.ce_pin.value = 0 # only send one payload - self._wait_for_result(timeout) + self.ce_pin.value = 0 + while not self._status & 0x30: + self.update() result = self.irq_ds - if self.ack and self.irq_dr: # check if there is an ACK payload - result = self.recv() # save ACK payload & clear RX related IRQ flag - self.clear_status_flags(False) # only clear TX related IRQ flags + if self._status & 0x60 == 0x60: + result = self.recv() + self.clear_status_flags(False) return result def write(self, buf, ask_no_ack=False): - """This non-blocking function (when used as alternative to `send()`) is meant for - asynchronous applications and can only handle one payload at a time as it is a helper - function to `send()`. - - :param bytearray buf: The payload to transmit. This bytearray must have a length greater - than 0 and less than 32 bytes, otherwise a `ValueError` exception is thrown. - - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is less - than the `payload_length` attribute, then this bytearray is padded with zeros until - its length is equal to the `payload_length` attribute. - - If the `dynamic_payloads` attribute is disabled and this bytearray's length is - greater than `payload_length` attribute, then this bytearray's length is truncated to - equal the `payload_length` attribute. - :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for - an acknowledgment from the receiving nRF24L01. This parameter directly controls a - ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about - the payload). Therefore, it takes advantage of an nRF24L01 feature specific to - individual payloads, and its value is not saved anywhere. You do not need to specify - this for every payload if the `auto_ack` attribute is disabled, however this parameter - should work despite the `auto_ack` attribute's setting. - - .. note:: Each transmission is in the form of a packet. This packet contains sections - of data around and including the payload. `See Chapter 7.3 in the nRF24L01 - Specifications Sheet `_ for more - details. - - This function isn't completely non-blocking as we still need to wait just under 5 ms for - the CSN pin to settle (allowing a clean SPI transaction). - - .. note:: The nRF24L01 doesn't initiate sending until a mandatory minimum 10 µs pulse on - the CE pin is acheived. That pulse is initiated before this function exits. However, we - have left that 10 µs wait time to be managed by the MCU in cases of asychronous - application, or it is managed by using `send()` instead of this function. According to - the Specification sheet, if the CE pin remains HIGH for longer than 10 µs, then the - nRF24L01 will continue to transmit all payloads found in the TX FIFO buffer. - - .. warning:: A note paraphrased from the `nRF24L01+ Specifications Sheet `_: - - It is important to NEVER to keep the nRF24L01+ in TX mode for more than 4 ms at a time. - If the [`arc` attribute is] enabled, nRF24L01+ is never in TX mode longer than 4 - ms. - - .. tip:: Use this function at your own risk. Because of the underlying `"Enhanced - ShockBurst Protocol" `_, disobeying the 4 - ms rule is easily avoided if you enable the `auto_ack` attribute. Alternatively, you - MUST use interrupt flags or IRQ pin with user defined timer(s) to AVOID breaking the - 4 ms rule. If the `nRF24L01+ Specifications Sheet explicitly states this - `_, we have to assume - radio damage or misbehavior as a result of disobeying the 4 ms rule. See also `table 18 - in the nRF24L01 specification sheet `_ for calculating - necessary transmission time (these calculations are used in the `send()` and `resend()` - functions). - """ + """This non-blocking function (when used as alternative to `send()`) + is meant for asynchronous applications and can only handle one + payload at a time as it is a helper function to `send()`.""" if not buf or len(buf) > 32: - raise ValueError("buf must be a buffer protocol object with a byte length of" - "\nat least 1 and no greater than 32") - self.clear_status_flags(False) # only TX related IRQ flags - if self._config & 3 != 2: # ready radio if it isn't yet - # ensures tx mode & powered up - self._config = (self._reg_read(CONFIG) & 0x7c) | 2 - self._reg_write(CONFIG, self._config) - time.sleep(0.00016) # power up/down takes < 150 µs + 4 µs - # pad out or truncate data to fill payload_length if dynamic_payloads == False - if not self.dynamic_payloads: - if len(buf) < self.payload_length: - for _ in range(self.payload_length - len(buf)): - buf += b'\x00' - elif len(buf) > self.payload_length: - buf = buf[:self.payload_length] - # now upload the payload accordingly with appropriate command - if ask_no_ack: # payload doesn't want acknowledgment - # ensure this feature is allowed by setting EN_DYN_ACK flag in the FEATURE register + raise ValueError("buffer must have a length in range [1, 32]") + self.clear_status_flags() + if self._config & 3 != 2: # is radio powered up in TX mode? + self._config = (self._reg_read(CONFIGURE) & 0x7C) | 2 + self._reg_write(CONFIGURE, self._config) + time.sleep(0.00016) + if not bool((self._dyn_pl & 1) and (self._features & 4)): + if len(buf) < self._pl_len[0]: + buf += b"\x00" * self._pl_len[0] - len(buf) + elif len(buf) > self._pl_len[0]: + buf = buf[: self._pl_len[0]] + if ask_no_ack: if self._features & 1 == 0: - self._features = self._features & 0xFE | 1 # set EN_DYN_ACK flag high - self._reg_write(FEATURE, self._features) - # write appropriate command with payload - # 0xA0 = W_TX_PAYLOAD; 0xB0 = W_TX_PAYLOAD_NO_ACK + self._features = self._features & 0xFE | 1 + self._reg_write(TX_FEATURE, self._features) self._reg_write_bytes(0xA0 | (ask_no_ack << 4), buf) - # enable radio comms so it can send the data by starting the mandatory minimum 10 µs pulse - # on CE. Let send() or resend() measure this pulse for blocking reasons self.ce_pin.value = 1 - # while CE is still HIGH only if dynamic_payloads and auto_ack are enabled - # automatically goes to standby-II after successful TX of all payloads in the FIFO def flush_rx(self): - """A helper function to flush the nRF24L01's internal RX FIFO buffer. (write-only) - - .. note:: The nRF24L01 RX FIFO is 3 level stack that holds payload data. This means that - there can be up to 3 received payloads (each of a maximum length equal to 32 bytes) - waiting to be read (and popped from the stack) by `recv()` or `read_ack()`. This - function clears all 3 levels. - """ + """A helper function to flush the nRF24L01's RX FIFO buffer.""" self._reg_write(0xE2) def flush_tx(self): - """A helper function to flush the nRF24L01's internal TX FIFO buffer. (write-only) - - .. note:: The nRF24L01 TX FIFO is 3 level stack that holds payload data. This means that - there can be up to 3 payloads (each of a maximum length equal to 32 bytes) waiting to - be transmit by `send()`, `resend()` or `write()`. This function clears all 3 levels. It - is worth noting that the payload data is only popped from the TX FIFO stack upon - successful transmission (see also `resend()` as the handling of failed transmissions - can be altered). - """ + """A helper function to flush the nRF24L01's TX FIFO buffer.""" self._reg_write(0xE1) def fifo(self, about_tx=False, check_empty=None): - """This provides some precision determining the status of the TX/RX FIFO buffers. - (read-only) - - :param bool about_tx: - * `True` means information returned is about the TX FIFO buffer. - * `False` means information returned is about the RX FIFO buffer. This parameter - defaults to `False` when not specified. - :param bool check_empty: - * `True` tests if the specified FIFO buffer is empty. - * `False` tests if the specified FIFO buffer is full. - * `None` (when not specified) returns a 2 bit number representing both empty (bit 1) & - full (bit 0) tests related to the FIFO buffer specified using the ``tx`` parameter. - :returns: - * A `bool` answer to the question: - "Is the [TX/RX]:[`True`/`False`] FIFO buffer [empty/full]:[`True`/`False`]? - * If the ``check_empty`` parameter is not specified: an `int` in range [0,2] for which: - - - ``1`` means the specified FIFO buffer is full - - ``2`` means the specified FIFO buffer is empty - - ``0`` means the specified FIFO buffer is neither full nor empty - """ - if (check_empty is None and isinstance(about_tx, (bool, int))) or \ - (isinstance(check_empty, (bool, int)) and isinstance(about_tx, (bool, int))): - self._fifo = self._reg_read(FIFO) # refresh the data - if check_empty is None: - return (self._fifo & (0x30 if about_tx else 0x03)) >> (4 * about_tx) - return bool(self._fifo & ((2 - check_empty) << (4 * about_tx))) - raise ValueError("Argument 1 ('about_tx') must always be a bool or int. Argument 2" - " ('check_empty'), if specified, must be a bool or int") - - def pipe(self): - """This function returns information about the data pipe that received the next available - payload in the RX FIFO buffer. - - :returns: - - `None` if there is no payload in RX FIFO. - - The `int` identifying pipe number [0,5] that received the next available payload in - the RX FIFO buffer. - """ - self.update() # perform Non-operation command to get status byte (should be faster) - result = (self._status & 0x0E) >> 1 # 0x0E==RX_P_NO - if result <= 5: # is there data in RX FIFO? - return result - return None # RX FIFO is empty + """This provides some precision determining the status of the TX/RX + FIFO buffers. (read-only)""" + if isinstance(about_tx, (bool, int)): + if check_empty is None or isinstance(check_empty, (bool, int)): + self._fifo = self._reg_read(0x17) + mask = 4 * about_tx + if check_empty is None: + return (self._fifo & (0x30 if about_tx else 0x03)) >> mask + return bool(self._fifo & ((2 - check_empty) << mask)) + raise ValueError( + "Argument 1 ('about_tx') must always be a bool or " + "int. Argument 2 ('check_empty'), if specified, must" + " be a bool or int" + ) def address(self, index=-1): - """Returns the current address set to a specified data pipe or the TX address. (read-only) - - :param int index: the number of the data pipe whose address is to be returned. Defaults to - ``-1``. A valid index ranges [0,5] for RX addresses or any negative `int` for the TX - address. Otherwise an `IndexError` is thown. - """ + """Returns the current address set to a specified data pipe or the TX + address. (read-only)""" if index > 5: raise IndexError("index {} is out of bounds [0,5]".format(index)) if index < 0: return self._tx_address if index <= 1: return self._pipes[index] - return bytes(self._pipes[index]) + self._pipes[1][1:] - - def _wait_for_result(self, timeout): - start = time.monotonic() - while not self.irq_ds and not self.irq_df and (time.monotonic() - start) < timeout: - self.update() # perform Non-operation command to get status byte (should be faster) - # print('status: DR={} DS={} DF={}'.format(self.irq_dr, self.irq_ds, self.irq_df)) - - def _attempt2resend(self, attempts): - retry = False - for _ in range(attempts): - # resend() clears flags upon entering and exiting - retry = self.resend() - if retry is None or retry: - break # retry succeeded - return retry + return bytes([self._pipes[index]]) + self._pipes[1][1:] + + @property + def rpd(self): + """This read-only attribute returns `True` if RPD (Received Power + Detector) is triggered or `False` if not triggered.""" + return bool(self._reg_read(0x09)) + + def start_carrier_wave(self): + """Starts a continuous carrier wave test.""" + self.power = 0 + self.ce_pin.value = 0 + self.listen = 0 + self._rf_setup |= 0x90 + self._reg_write(RF_PA_RATE, self._rf_setup) + self.power = 1 + self.ce_pin.value = 1 + + def stop_carrier_wave(self): + """Stops a continuous carrier wave test.""" + self.ce_pin.value = 0 + self.power = 0 + self._rf_setup &= ~0x90 + self._reg_write(RF_PA_RATE, self._rf_setup) diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py new file mode 100644 index 0000000..2231dc5 --- /dev/null +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -0,0 +1,346 @@ +# see license and copyright information in rf24.py +# pylint: disable=missing-docstring +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/2bndy5/CircuitPython_nRF24L01.git" +import time +from adafruit_bus_device.spi_device import SPIDevice + + +class RF24: + def __init__(self, spi, csn, ce): + self._spi = SPIDevice(spi, chip_select=csn, baudrate=10000000) + self.ce_pin = ce + self.ce_pin.switch_to_output(value=False) + self._status = 0 + self._reg_write(0, 0x0E) + if self._reg_read(0) & 3 != 2: + raise RuntimeError("nRF24L01 Hardware not responding") + self.power = False + self._reg_write(3, 3) + self._reg_write(6, 7) + self._reg_write(2, 0) + self._reg_write(0x1C, 0x3F) + self._reg_write(1, 0x3F) + self._reg_write(0x1D, 5) + self._reg_write(4, 0x53) + self._pipe0_read_addr = None + self.channel = 76 + self.payload_length = 32 + self.flush_rx() + self.flush_tx() + self.clear_status_flags() + + # pylint: disable=no-member + def _reg_read(self, reg): + out_buf = bytearray([reg, 0]) + in_buf = bytearray([0, 0]) + with self._spi as spi: + time.sleep(0.005) + spi.write_readinto(out_buf, in_buf) + self._status = in_buf[0] + return in_buf[1] + + def _reg_read_bytes(self, reg, buf_len=5): + in_buf = bytearray(buf_len + 1) + out_buf = bytearray([reg]) + b"\x00" * buf_len + with self._spi as spi: + time.sleep(0.005) + spi.write_readinto(out_buf, in_buf) + self._status = in_buf[0] + return in_buf[1:] + + def _reg_write_bytes(self, reg, out_buf): + out_buf = bytes([0x20 | reg]) + out_buf + in_buf = bytearray(len(out_buf)) + with self._spi as spi: + time.sleep(0.005) + spi.write_readinto(out_buf, in_buf) + self._status = in_buf[0] + + def _reg_write(self, reg, val=None): + out_buf = bytes([reg]) + if val is not None: + out_buf = bytes([0x20 | reg, val]) + in_buf = bytearray(len(out_buf)) + with self._spi as spi: + time.sleep(0.005) + spi.write_readinto(out_buf, in_buf) + self._status = in_buf[0] + + # pylint: enable=no-member + @property + def address_length(self): + return self._reg_read(0x03) + 2 + + @address_length.setter + def address_length(self, length): + if not 3 <= length <= 5: + raise ValueError("address_length must be in range [3, 5]") + self._reg_write(0x03, length - 2) + + def open_tx_pipe(self, addr): + if self.arc: + self._reg_write_bytes(0x0A, addr) + self._reg_write(2, self._reg_read(2) | 1) + self._reg_write_bytes(0x10, addr) + + def close_rx_pipe(self, pipe_num): + if pipe_num < 0 or pipe_num > 5: + raise ValueError("pipe_number must be in range [0, 5]") + open_pipes = self._reg_read(2) + if open_pipes & (1 << pipe_num): + self._reg_write(2, open_pipes & ~(1 << pipe_num)) + + def open_rx_pipe(self, pipe_num, addr): + if not 0 <= pipe_num <= 5: + raise ValueError("pipe_number must be in range [0, 5]") + if not addr: + raise ValueError("address length cannot be 0") + if pipe_num < 2: + if not pipe_num: + self._pipe0_read_addr = addr + self._reg_write_bytes(0x0A + pipe_num, addr) + else: + self._reg_write(0x0A + pipe_num, addr[0]) + self._reg_write(2, self._reg_read(2) | (1 << pipe_num)) + + @property + def listen(self): + return self._reg_read(0) & 3 == 3 + + @listen.setter + def listen(self, is_rx): + if self.listen != bool(is_rx): + self.ce_pin.value = 0 + if is_rx: + if self._pipe0_read_addr is not None: + self._reg_write_bytes(0x0A, self._pipe0_read_addr) + self._reg_write(0, (self._reg_read(0) & 0xFC) | 3) + time.sleep(0.00015) + self.flush_rx() + self.clear_status_flags(True, False, False) + self.ce_pin.value = 1 + time.sleep(0.00013) + else: + self._reg_write(0, self._reg_read(0) & 0xFE) + time.sleep(0.00016) + + def any(self): + if self._reg_read(0x1D) & 4 and self.pipe is not None: + return self._reg_read(0x60) + if self.pipe is not None: + return self._reg_read(0x11 + self.pipe) + return 0 + + def recv(self, length=None): + ret_size = length if length is not None else self.any() + if not ret_size: + return None + result = self._reg_read_bytes(0x61, ret_size) + self.clear_status_flags(True, False, False) + return result + + def send(self, buf, ask_no_ack=False, force_retry=0): + self.ce_pin.value = 0 + if isinstance(buf, (list, tuple)): + result = [] + for b in buf: + result.append(self.send(b, ask_no_ack, force_retry)) + return result + self.flush_tx() + if self.pipe is not None: + self.flush_rx() + self.write(buf, ask_no_ack) + time.sleep(0.00001) + self.ce_pin.value = 0 + while not self._status & 0x30: + self.update() + result = self.irq_ds + if self.irq_df: + for _ in range(force_retry): + result = self.resend() + if result is None or result: + break + if self._status & 0x60 == 0x60: + result = self.recv() + self.clear_status_flags(False) + return result + + @property + def tx_full(self): + return bool(self._status & 1) + + @property + def pipe(self): + result = (self._status & 0x0E) >> 1 + if result <= 5: + return result + return None + + @property + def irq_dr(self): + return bool(self._status & 0x40) + + @property + def irq_ds(self): + return bool(self._status & 0x20) + + @property + def irq_df(self): + return bool(self._status & 0x10) + + def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True): + config = bool(data_recv) << 6 | bool(data_sent) << 5 | bool(data_fail) << 4 + self._reg_write(7, config) + + def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True): + config = (not data_recv) << 6 | (not data_fail) << 4 | (not data_sent) << 5 + self._reg_write(0, (self._reg_read(0) & 0x0F) | config) + + @property + def dynamic_payloads(self): + return bool(self._reg_read(0x1C)) and self._reg_read(0x1D) & 4 == 4 + + @dynamic_payloads.setter + def dynamic_payloads(self, enable): + self._reg_write(0x1D, (self._reg_read(0x1D) & 3) | bool(enable) << 2) + self._reg_write(0x1C, 0x3F if enable else 0) + + @property + def payload_length(self): + return self._reg_read(0x11) + + @payload_length.setter + def payload_length(self, length): + if not length or length > 32: + raise ValueError("payload_length must be in range [1, 32]") + for i in range(6): + self._reg_write(0x11 + i, length) + + @property + def arc(self): + return self._reg_read(4) & 0x0F + + @arc.setter + def arc(self, cnt): + if not 0 <= cnt <= 15: + raise ValueError("arc must be in range [0, 15]") + self._reg_write(4, (self._reg_read(4) & 0xF0) | cnt) + + @property + def ard(self): + return ((self._reg_read(4) & 0xF0) >> 4) * 250 + 250 + + @ard.setter + def ard(self, delta): + if not 250 <= delta <= 4000: + raise ValueError("ard must be in range [250, 4000]") + self._reg_write(4, (self._reg_read(4) & 0x0F) | int((delta - 250) / 250) << 4) + + @property + def ack(self): + return self._reg_read(0x1D) & 6 == 6 and bool(self._reg_read(0x1C)) + + @ack.setter + def ack(self, enable): + features = self._reg_read(0x1D) & 5 + if enable: + self._reg_write(0x1C, 0x3F) + features = (features & 3) | 4 + features |= 2 if enable else 0 + self._reg_write(0x1D, features) + + def load_ack(self, buf, pipe_num): + if 0 <= pipe_num <= 5 and (not buf or len(buf) > 32): + if not self._reg_read(0x1D) & 2: + self.ack = True + if not self.tx_full: + self._reg_write_bytes(0xA8 | pipe_num, buf) + return True + return False + + @property + def data_rate(self): + rf_setup = self._reg_read(6) & 0x28 + return (2 if rf_setup == 8 else 250) if rf_setup else 1 + + @data_rate.setter + def data_rate(self, speed): + speed = 0 if speed == 1 else (0x20 if speed != 2 else 8) + self._reg_write(6, (self._reg_read(6) & 0xD7) | speed) + + @property + def channel(self): + return self._reg_read(5) + + @channel.setter + def channel(self, chnl): + if not 0 <= int(chnl) <= 125: + raise ValueError("channel must be in range [0, 125]") + self._reg_write(5, int(chnl)) + + @property + def power(self): + return bool(self._reg_read(0) & 2) + + @power.setter + def power(self, is_on): + config = self._reg_read(0) + if bool(config & 2) != bool(is_on): + self._reg_write(0, config & 0x7D | bool(is_on) << 1) + time.sleep(0.00016) + + @property + def pa_level(self): + return (3 - ((self._reg_read(6) & 6) >> 1)) * -6 + + @pa_level.setter + def pa_level(self, pwr): + if pwr not in (-18, -12, -6, 0): + raise ValueError("pa_level must be -18, -12, -6, or 0") + self._reg_write(6, self._reg_read(6) & 0xF8 | (3 - int(pwr / -6)) * 2 | 1) + + def update(self): + self._reg_write(0xFF) + + def resend(self): + result = False + if not self._reg_read(0x17) & 0x10: + if self.pipe is not None: + self.flush_rx() + self.clear_status_flags() + self._reg_write(0xE3) + self.ce_pin.value = 0 + self.ce_pin.value = 1 + time.sleep(0.00001) + self.ce_pin.value = 0 + while not self._status & 0x30: + self.update() + result = self.irq_ds + if self._status & 0x60 == 0x60: + result = self.recv() + self.clear_status_flags(False) + return result + + def write(self, buf, ask_no_ack=False): + if not buf or len(buf) > 32: + raise ValueError("buffer length must be in range [1, 32]") + self.clear_status_flags() + config = self._reg_read(0) + if config & 3 != 2: + self._reg_write(0, (config & 0x7C) | 2) + time.sleep(0.00016) + if not self.dynamic_payloads: + pl_width = self.payload_length + if len(buf) < pl_width: + buf += b"\x00" * pl_width - len(buf) + elif len(buf) > pl_width: + buf = buf[:pl_width] + self._reg_write_bytes(0xA0 | (ask_no_ack << 4), buf) + self.ce_pin.value = 1 + + def flush_rx(self): + self._reg_write(0xE2) + + def flush_tx(self): + self._reg_write(0xE1) diff --git a/docs/_static/darkness.css b/docs/_static/darkness.css index c14a4cf..89a5052 100644 --- a/docs/_static/darkness.css +++ b/docs/_static/darkness.css @@ -259,6 +259,15 @@ body { } /* -----------------------------------------API sections-------------------------------------- */ + +.wy-table-bordered-all td, .rst-content table.docutils td { + text-align: left; +} + +.rst-content table.docutils.citation, .rst-content table.docutils.footnote { + color: white; +} + .rst-content dl:not(.docutils) dl dt { background: #343434; color: #e5df8e; @@ -305,3 +314,8 @@ code, .rst-content tt, .rst-content code { .rst-content h1:hover .headerlink::after, .rst-content h2:hover .headerlink::after, .rst-content .toctree-wrapper p.caption:hover .headerlink::after, .rst-content h3:hover .headerlink::after, .rst-content h4:hover .headerlink::after, .rst-content h5:hover .headerlink::after, .rst-content h6:hover .headerlink::after, .rst-content dl dt:hover .headerlink::after, .rst-content p.caption:hover .headerlink::after, .rst-content table > caption:hover .headerlink::after, .rst-content .code-block-caption:hover .headerlink::after { color: #29ae5b !important; } + +html.writer-html4 .rst-content dl:not(.docutils)>dt, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt { + background: #2e363c; +} diff --git a/docs/api.rst b/docs/api.rst index 16d9025..e4f6f09 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,279 +1,1016 @@ - -.. If you created a package, create one automodule per module in the package. - -.. If your library file(s) are nested in a directory (e.g. /adafruit_foo/foo.py) -.. use this format as the module name: "adafruit_foo.foo" - - -RF24 class -============== - -Troubleshooting info --------------------- - -.. currentmodule:: circuitpython_nrf24l01.rf24 -.. important:: The nRF24L01 has 3 key features that can be interdependent of each other. Their - priority of dependence is as follows: - - 1. `dynamic_payloads` feature allowing either TX/RX nRF24L01 to be able to send/receive - payloads with their size written into the payloads' packet. With this disabled, both RX/TX - nRF24L01 must use matching `payload_length` attributes. - 2. `auto_ack` feature provides transmission verification by using the RX nRF24L01 to - automatically and imediatedly send an acknowledgment (ACK) packet in response to freshly - received payloads. `auto_ack` does not require `dynamic_payloads` to be enabled. - 3. `ack` feature allows the MCU to append a payload to the ACK packet, thus instant - bi-directional communication. A transmitting ACK payload must be loaded into the nRF24L01's - TX FIFO buffer (done using `load_ack()`) BEFORE receiving the payload that is to be - acknowledged. Once transmitted, the payload is released from the TX FIFO buffer. This - feature requires the `auto_ack` and `dynamic_payloads` features enabled. - -Remeber that the nRF24L01's FIFO (first-in,first-out) buffer has 3 levels. This means that there -can be up to 3 payloads waiting to be read (RX) and up to 3 payloads waiting to be transmit (TX). - -With the `auto_ack` feature enabled, you get: - - * cyclic redundancy checking (`crc`) automatically enabled - * to change amount of automatic re-transmit attempts and the delay time between them. See the - `arc` and `ard` attributes. - -.. note:: A word on pipes vs addresses vs channels. - - You should think of the data pipes as a "parking spot" for your payload. There are only six - data pipes on the nRF24L01, thus it can simultaneously "listen" to a maximum of 6 other nRF24L01 - radios. However, it can only "talk" to 1 other nRF24L01 at a time). - - The specified address is not the address of an nRF24L01 radio, rather it is more like a path - that connects the endpoints. When assigning addresses to a data pipe, you can use any 5 byte - long address you can think of (as long as the first byte is unique among simultaneously - broadcasting addresses), so you're not limited to communicating with only the same 6 nRF24L01 - radios (more on this when we officially support "Multiciever" mode). - - Finnaly, the radio's channel is not be confused with the radio's pipes. Channel selection is a - way of specifying a certain radio frequency (frequency = [2400 + channel] MHz). Channel - defaults to 76 (like the arduino library), but options range from 0 to 125 -- that's 2.4 - GHz to 2.525 GHz. The channel can be tweaked to find a less occupied frequency amongst - Bluetooth, WiFi, or other ambient signals that use the same spectrum of frequencies. - -.. warning:: For successful transmissions, most of the endpoint trasceivers' settings/features must - match. These settings/features include: - - * The RX pipe's address on the receiving nRF24L01 (passed to `open_rx_pipe()`) MUST match the - TX pipe's address on the transmitting nRF24L01 (passed to `open_tx_pipe()`) - * `address_length` - * `channel` - * `data_rate` - * `dynamic_payloads` - * `payload_length` only when `dynamic_payloads` is disabled - * `auto_ack` on the recieving nRF24L01 must be enabled if `arc` is greater than 0 on the - transmitting nRF24L01 - * custom `ack` payloads - * `crc` - - In fact the only attributes that aren't required to match on both endpoint transceivers would - be the identifying data pipe number (passed to `open_rx_pipe()` or `load_ack()`), `pa_level`, - `arc`, & `ard` attributes. The ``ask_no_ack`` feature can be used despite the settings/features - configuration (see `send()` & `write()` function - parameters for more details). - -Basic API ---------- - -Contrusctor -****************** - -.. autoclass:: circuitpython_nrf24l01.rf24.RF24 - :no-members: - -address_length -****************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.address_length - -open_tx_pipe() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_tx_pipe - -close_rx_pipe() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.close_rx_pipe - -open_rx_pipe() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_rx_pipe - -listen -****************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.listen - -any() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.any - -recv() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.recv - -send() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.send - -Advanced API ------------- - -what_happened() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.what_happened - -dynamic_payloads -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.dynamic_payloads - -payload_length -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.payload_length - -auto_ack -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.auto_ack - -arc -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.arc - -ard -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ard - -ack -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ack - -load_ack() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.load_ack - -read_ack() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.read_ack - -irq_dr -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_dr - -irq_df -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_df - -irq_ds -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_ds - -clear_status_flags() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.clear_status_flags - -interrupt_config() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.interrupt_config - -data_rate -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.data_rate - -channel -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.channel - -crc -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.crc - -power -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.power - -pa_level -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pa_level - -tx_full -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.tx_full - -rpd -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.rpd - -update() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.update - -resend() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.resend - -write() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.write - -flush_rx() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_rx - -flush_tx() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_tx - -fifo() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.fifo - -pipe() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.pipe - -address() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.address - -Fake BLE -============== - - .. autoclass:: circuitpython_nrf24l01.fake_ble - :members: send, name + + +.. currentmodule:: circuitpython_nrf24l01.rf24 + +.. |irq note| replace:: parameter as `True` to `clear_status_flags()` and reset this. As this + is a virtual representation of the interrupt event, this attribute will always be updated + despite what the actual IRQ pin is configured to do about this event. + +.. |update manually| replace:: Calling this does not execute an SPI transaction. It only + exposes that latest data contained in the STATUS byte that's always returned from any + other SPI transactions. Use the `update()` function to manually refresh this data when + needed. + +Troubleshooting info +==================== + +.. important:: The nRF24L01 has 3 key features that can be interdependent of each other. Their + priority of dependence is as follows: + + 1. `auto_ack` feature provides transmission verification by using the RX nRF24L01 to + automatically and imediatedly send an acknowledgment (ACK) packet in response to freshly + received payloads. `auto_ack` does not require `dynamic_payloads` to be enabled. + 2. `dynamic_payloads` feature allowing either TX/RX nRF24L01 to be able to send/receive + payloads with their size written into the payloads' packet. With this disabled, both + RX/TX nRF24L01 must use matching `payload_length` attributes. For `dynamic_payloads` to + be enabled, the `auto_ack` feature must be enabled. Although, the `auto_ack` feature + isn't required when the `dynamic_payloads` feature is disabled. + 3. `ack` feature allows the MCU to append a payload to the ACK packet, thus instant + bi-directional communication. A transmitting ACK payload must be loaded into the + nRF24L01's TX FIFO buffer (done using `load_ack()`) BEFORE receiving the payload that + is to be acknowledged. Once transmitted, the payload is released from the TX FIFO + buffer. This feature requires the `auto_ack` and `dynamic_payloads` features enabled. + +Remeber that the nRF24L01's FIFO (first-in,first-out) buffer has 3 levels. This means that +there can be up to 3 payloads waiting to be read (RX) and up to 3 payloads waiting to be +transmit (TX). + +With the `auto_ack` feature enabled, you get: + + * cyclic redundancy checking (`crc`) automatically enabled + * to change amount of automatic re-transmit attempts and the delay time between them. + See the `arc` and `ard` attributes. + +.. note:: A word on pipes vs addresses vs channels. + + You should think of the data pipes as a "parking spot" for your payload. There are only six + data pipes on the nRF24L01, thus it can simultaneously "listen" to a maximum of 6 other + nRF24L01 radios. However, it can only "talk" to 1 other nRF24L01 at a time). + + The specified address is not the address of an nRF24L01 radio, rather it is more like a + path that connects the endpoints. When assigning addresses to a data pipe, you can use any + 5 byte long address you can think of (as long as the first byte is unique among + simultaneously broadcasting addresses), so you're not limited to communicating with only + the same 6 nRF24L01 radios (more on this when we officially support "Multiciever" mode). + + Finnaly, the radio's channel is not be confused with the radio's pipes. Channel selection + is a way of specifying a certain radio frequency (frequency = [2400 + channel] MHz). + Channel defaults to 76 (like the arduino library), but options range from 0 to 125 -- + that's 2.4 GHz to 2.525 GHz. The channel can be tweaked to find a less occupied frequency + amongst Bluetooth, WiFi, or other ambient signals that use the same spectrum of + frequencies. + +.. warning:: + For successful transmissions, most of the endpoint trasceivers' settings/features must + match. These settings/features include: + + * The RX pipe's address on the receiving nRF24L01 (passed to `open_rx_pipe()`) MUST match + the TX pipe's address on the transmitting nRF24L01 (passed to `open_tx_pipe()`) + * `address_length` + * `channel` + * `data_rate` + * `dynamic_payloads` + * `payload_length` only when `dynamic_payloads` is disabled + * `auto_ack` on the recieving nRF24L01 must be enabled if `arc` is greater than 0 on the + transmitting nRF24L01 + * custom `ack` payloads + * `crc` + + In fact the only attributes that aren't required to match on both endpoint transceivers + would be the identifying data pipe number (passed to `open_rx_pipe()` or `load_ack()`), + `pa_level`, `arc`, & `ard` attributes. The ``ask_no_ack`` feature can be used despite the + settings/features configuration (see `send()` & `write()` function parameters for more + details). + +About the lite version +====================== + +This library contains a "lite" version of ``rf24.py`` titled ``rf24_lite.py``. It has been +developed to save space on microcontrollers with limited amount of RAM and/or storage (like +boards using the ATSAMD21 M0). The following functionality has been removed from the lite +version: + + * `address()` removed. + * `what_happened()` removed. However you can use the following function to dump all + available registers' values (for advanced users): + + .. code-block:: python + + # let `nrf` be the instantiated RF24 object + def dump_registers(end=0x1e): + for i in range(end): + if i in (0xA, 0xB, 0x10): + print(hex(i), "=", nrf._reg_read_bytes(i)) + elif i not in (0x18, 0x19, 0x1a, 0x1b): + print(hex(i), "=", hex(nrf._reg_read(i))) + * `fifo()` removed. + * `dynamic_payloads` applies to all pipes, not individual pipes. + * `payload_length` applies to all pipes, not individual pipes. + * `read_ack()` removed. This is deprecated on next major release anyway; use `recv()` + instead. + * `load_ack()` is available, but it will not throw exceptions for malformed ``buf`` or + invalid ``pipe_number`` parameters. + * `crc` removed. 2-bytes encoding scheme (CRC16) is always enabled. + * `auto_ack` removed. This is always enabled for all pipes. Pass ``ask_no_ack`` parameter + as `True` to `send()` or `write()` to disable automatic acknowledgement for TX + operations. + * `is_lna_enabled` removed. This will always be enabled, and `pa_level` will not accept a + `list` or `tuple`. This only affects certain boards anyway. + * `rpd`, `start_carrier_wave()`, & `stop_carrier_wave()` removed. These only perform a + test of the nRF24L01's hardware. + * `CSN_DELAY` removed. This is hard-coded to 5 milliseconds + * All comments and docstrings removed, meaning ``help()`` will not provide any specific + information. Exception prompts have also been reduced and adjusted accordingly. + * Cannot switch between different radio configurations using context manager (the `with` + blocks). It is advised that only one `RF24` object be instantiated when RAM is limited + (less than or equal to 32KB). + +RF24 class +============== + +Basic API +--------- + +Constructor +****************** + +.. autoclass:: circuitpython_nrf24l01.rf24.RF24 + :no-members: + + This class aims to be compatible with other devices in the nRF24xxx product line that + implement the Nordic proprietary Enhanced ShockBurst Protocol (and/or the legacy + ShockBurst Protocol), but officially only supports (through testing) the nRF24L01 and + nRF24L01+ devices. + + :param ~busio.SPI spi: The object for the SPI bus that the nRF24L01 is connected to. + + .. tip:: This object is meant to be shared amongst other driver classes (like + adafruit_mcp3xxx.mcp3008 for example) that use the same SPI bus. Otherwise, multiple + devices on the same SPI bus with different spi objects may produce errors or + undesirable behavior. + :param ~digitalio.DigitalInOut csn: The digital output pin that is connected to the nRF24L01's + CSN (Chip Select Not) pin. This is required. + :param ~digitalio.DigitalInOut ce: The digital output pin that is connected to the nRF24L01's + CE (Chip Enable) pin. This is required. + :param int spi_frequency: Specify which SPI frequency (in Hz) to use on the SPI bus. This + parameter only applies to the instantiated object and is made persistent via + :py:class:`~adafruit_bus_device.spi_device`. + +open_tx_pipe() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_tx_pipe + + :param bytearray address: The virtual address of the receiving nRF24L01. The address + specified here must match the address set to one of the RX data pipes of the receiving + nRF24L01. The existing address can be altered by writting a bytearray with a length + less than 5. The nRF24L01 will use the first `address_length` number of bytes for the + RX address on the specified data pipe. + + .. note:: There is no option to specify which data pipe to use because the nRF24L01 only + uses data pipe 0 in TX mode. Additionally, the nRF24L01 uses the same data pipe (pipe + 0) for receiving acknowledgement (ACK) packets in TX mode when the `auto_ack` attribute + is enabled for data pipe 0. Thus, RX pipe 0 is appropriated with the TX address + (specified here) when `auto_ack` is enabled for data pipe 0. + +close_rx_pipe() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.close_rx_pipe + + :param int pipe_number: The data pipe to use for RX transactions. This must be in range + [0, 5]. Otherwise a `ValueError` exception is thrown. + +open_rx_pipe() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_rx_pipe + + If `dynamic_payloads` attribute is disabled for the specifed data pipe, then the + `payload_length` attribute is used to define the expected length of the static RX payload + on the specified data pipe. + + :param int pipe_number: The data pipe to use for RX transactions. This must be in range + [0, 5]. Otherwise a `ValueError` exception is thrown. + :param bytearray address: The virtual address to the receiving nRF24L01. If using a + ``pipe_number`` greater than 1, then only the MSByte of the address is written, so make + sure MSByte (first character) is unique among other simultaneously receiving addresses. + The existing address can be altered by writing a bytearray with a length less than 5. + The nRF24L01 will use the first `address_length` number of bytes for the RX address on + the specified data pipe. + + .. note:: The nRF24L01 shares the addresses' last 4 LSBytes on data pipes 2 through + 5. These shared LSBytes are determined by the address set to data pipe 1. + +listen +****************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.listen + + Setting this attribute incorporates the proper transitioning to/from RX mode as it involves + playing with the `power` attribute and the nRF24L01's CE pin. This attribute does not power + down the nRF24L01, but will power it up when needed; use `power` attribute set to `False` + to put the nRF24L01 to sleep. + + A valid input value is a `bool` in which: + + - `True` enables RX mode. Additionally, per `Appendix B of the nRF24L01+ Specifications + Sheet `_, this attribute + flushes the RX FIFO, clears the `irq_dr` status flag, and puts nRF24L01 in power up + mode. Notice the CE pin is be held HIGH during RX mode. + - `False` disables RX mode. As mentioned in above link, this puts nRF24L01's power in + Standby-I (CE pin is LOW meaning low current & no transmissions) mode which is ideal + for post-reception work. Disabing RX mode doesn't flush the RX/TX FIFO buffers, so + remember to flush your 3-level FIFO buffers when appropriate using `flush_tx()` or + `flush_rx()` (see also the `recv()` function). + +any() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.any + + :returns: + - `int` of the size (in bytes) of an available RX payload (if any). + - ``0`` if there is no payload in the RX FIFO buffer. + +recv() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.recv + + This function can also be used to fetch the last ACK packet's payload if `ack` is enabled. + + :param int length: An optional parameter to specify how many bytes to read from the RX + FIFO buffer. This parameter is not contrained in any way. + + - If this parameter is less than the length of the first available payload in the + RX FIFO buffer, then the payload will remain in the RX FIFO buffer until the + entire payload is fetched by this function. + - If this parameter is greater than the next available payload's length, then + additional data from other payload(s) in the RX FIFO buffer are returned. + + .. note:: + The nRF24L01 will repeatedly return the last byte fetched from the RX FIFO + buffer when there is no data to return (even if the RX FIFO is empty). Be + aware that a payload is only removed from the RX FIFO buffer when the entire + payload has been fetched by this function. Notice that this function always + starts reading data from the first byte of the first available payload (if + any) in the RX FIFO buffer. + :returns: + A `bytearray` of the RX payload data or `None` if there is no payload. If the + ``length`` parameter is not specified, then one of the following two scenarios is + applied. + + - If the `dynamic_payloads` attribute is disabled, then the returned bytearray's + length is equal to the user defined `payload_length` attribute for the data pipe + that received the payload. + - If the `dynamic_payloads` attribute is enabled, then the returned bytearray's length + is equal to the payload's length + +send() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.send + + :returns: + - `list` if a list or tuple of payloads was passed as the ``buf`` parameter. Each item + in the returned list will contain the returned status for each corresponding payload + in the list/tuple that was passed. The return statuses will be in one of the + following forms: + - `False` if transmission fails. Transmission failure can only be detected if `arc` + is greater than ``0``. + - `True` if transmission succeeds. + - `bytearray` or `None` when the `ack` attribute is `True`. Because the payload + expects a responding custom ACK payload, the response is returned (upon successful + transmission) as a `bytearray` (or `None` if ACK payload is empty) + + :param bytearray,list,tuple buf: The payload to transmit. This bytearray must have a + length in range [1, 32], otherwise a `ValueError` exception is thrown. This can + also be a list or tuple of payloads (`bytearray`); in which case, all items in the + list/tuple are processed for consecutive transmissions. + + - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's + length is less than the `payload_length` attribute for pipe 0, then this bytearray + is padded with zeros until its length is equal to the `payload_length` attribute for + pipe 0. + - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's + length is greater than `payload_length` attribute for pipe 0, then this bytearray's + length is truncated to equal the `payload_length` attribute for pipe 0. + :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for + an acknowledgment from the receiving nRF24L01. This parameter directly controls a + ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information + about the payload). Therefore, it takes advantage of an nRF24L01 feature specific to + individual payloads, and its value is not saved anywhere. You do not need to specify + this for every payload if the `arc` attribute is disabled, however setting this + parameter to `True` will work despite the `arc` attribute's setting. + + .. note:: Each transmission is in the form of a packet. This packet contains sections + of data around and including the payload. `See Chapter 7.3 in the nRF24L01 + Specifications Sheet `_ for more + details. + :param int force_retry: The number of brute-force attempts to `resend()` a failed + transmission. Default is 0. This parameter has no affect on transmissions if `arc` is + ``0`` or ``ask_no_ack`` parameter is set to `True`. Each re-attempt still takes + advantage of `arc` & `ard` attributes. During multi-payload processing, this + parameter is meant to slow down CircuitPython devices just enough for the Raspberry + Pi to catch up (due to the Raspberry Pi's seemingly slower SPI speeds). See also + notes on `resend()` as using this parameter carries the same implications documented + there. This parameter has no effect if the ``ask_no_ack`` parameter is set to `True` + or if `arc` is disabled. + + .. tip:: It is highly recommended that `arc` attribute is enabled (greater than ``0``) + when sending multiple payloads. Test results with the `arc` attribute disabled were + rather poor (less than 79% received by a Raspberry Pi). This same advice applies to + the ``ask_no_ack`` parameter (leave it as `False` for multiple payloads). + .. warning:: The nRF24L01 will block usage of the TX FIFO buffer upon failed + transmissions. Failed transmission's payloads stay in TX FIFO buffer until the MCU + calls `flush_tx()` and `clear_status_flags()`. Therefore, this function will discard + failed transmissions' payloads when sending a list or tuple of payloads, so it can + continue to process through the list/tuple even if any payload fails to be + acknowledged. + +Advanced API +------------ + +what_happened() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.what_happened + + Some information may be irrelevant depending on nRF24L01's state/condition. + + :prints: + + - ``Channel`` The current setting of the `channel` attribute + - ``RF Data Rate`` The current setting of the RF `data_rate` attribute. + - ``RF Power Amplifier`` The current setting of the `pa_level` attribute. + - ``CRC bytes`` The current setting of the `crc` attribute + - ``Address length`` The current setting of the `address_length` attribute + - ``TX Payload lengths`` The current setting of the `payload_length` attribute for TX + operations (concerning data pipe 0) + - ``Auto retry delay`` The current setting of the `ard` attribute + - ``Auto retry attempts`` The current setting of the `arc` attribute + - ``Packets lost on current channel`` Total amount of packets lost (transmission + failures). This only resets when the `channel` is changed. This count will + only go up 15. + - ``Retry attempts made for last transmission`` Amount of attempts to re-transmit + during last transmission (resets per payload) + - ``IRQ - Data Ready`` The current setting of the IRQ pin on "Data Ready" event + - ``IRQ - Data Sent`` The current setting of the IRQ pin on "Data Sent" event + - ``IRQ - Data Fail`` The current setting of the IRQ pin on "Data Fail" event + - ``Data Ready`` Is there RX data ready to be read? (state of the `irq_dr` flag) + - ``Data Sent`` Has the TX data been sent? (state of the `irq_ds` flag) + - ``Data Failed`` Has the maximum attempts to re-transmit been reached? + (state of the `irq_df` flag) + - ``TX FIFO full`` Is the TX FIFO buffer full? (state of the `tx_full` flag) + - ``TX FIFO empty`` Is the TX FIFO buffer empty? + - ``RX FIFO full`` Is the RX FIFO buffer full? + - ``RX FIFO empty`` Is the RX FIFO buffer empty? + - ``Custom ACK payload`` Is the nRF24L01 setup to use an extra (user defined) payload + attached to the acknowledgment packet? (state of the `ack` attribute) + - ``Ask no ACK`` Is the nRF24L01 setup to transmit individual packets that don't + require acknowledgment? + - ``Automatic Acknowledgment`` The status of the `auto_ack` feature. If this value is a + binary representation, then each bit represents the feature's status for each pipe. + - ``Dynamic Payloads`` The status of the `dynamic_payloads` feature. If this value is a + binary representation, then each bit represents the feature's status for each pipe. + - ``Primary Mode`` The current mode (RX or TX) of communication of the nRF24L01 device. + - ``Power Mode`` The power state can be Off, Standby-I, Standby-II, or On. + + :param bool dump_pipes: `True` appends the output and prints: + + - the current address used for TX transmissions. This value is the entire content of + the nRF24L01's register about the TX address (despite what `address_length` is set + to). + - ``Pipe [#] ([open/closed]) bound: [address]`` where ``#`` represent the pipe number, + the ``open/closed`` status is relative to the pipe's RX status, and ``address`` is + the full value stored in the nRF24L01's RX address registers (despite what + `address_length` is set to. + - if the pipe is open, then the output also prints ``expecting [X] byte static + payloads`` where ``X`` is the `payload_length` (in bytes) the pipe is setup to + receive when `dynamic_payloads` is disabled for that pipe. + + Default is `False` and skips this extra information. + +load_ack() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.load_ack + + This payload will then be appended to the automatic acknowledgment + (ACK) packet that is sent when fresh data is received on the specified pipe. See + `read_ack()` on how to fetch a received custom ACK payloads. + + :param bytearray buf: This will be the data attached to an automatic ACK packet on the + incoming transmission about the specified ``pipe_number`` parameter. This must have a + length in range [1, 32] bytes, otherwise a `ValueError` exception is thrown. Any ACK + payloads will remain in the TX FIFO buffer until transmitted successfully or + `flush_tx()` is called. + :param int pipe_number: This will be the pipe number to use for deciding which + transmissions get a response with the specified ``buf`` parameter's data. This number + must be in range [0, 5], otherwise a `ValueError` exception is thrown. + + :returns: `True` if payload was successfully loaded onto the TX FIFO buffer. `False` if it + wasn't because TX FIFO buffer is full. + + .. note:: this function takes advantage of a special feature on the nRF24L01 and needs to + be called for every time a customized ACK payload is to be used (not for every + automatic ACK packet -- this just appends a payload to the ACK packet). The `ack`, + `auto_ack`, and `dynamic_payloads` attributes are also automatically enabled by this + function when necessary. + + .. tip:: The ACK payload must be set prior to receiving a transmission. It is also worth + noting that the nRF24L01 can hold up to 3 ACK payloads pending transmission. Using this + function does not over-write existing ACK payloads pending; it only adds to the queue + (TX FIFO buffer) if it can. Use `flush_tx()` to discard unused ACK payloads when done + listening. + +read_ack() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.read_ack + + This function was internally called from a blocking `send()` call if the `ack` attribute + is enabled. Alternatively, this function can be called directly in case of calling the + non-blocking `write()` function during asychronous applications. This function is an alias + of `recv()` and remains for backward compatibility with older versions of this library. + + .. note:: See also the `ack`, `dynamic_payloads`, and `auto_ack` attributes as they must be + enabled to use custom ACK payloads. + + .. warning:: This function will be deprecated on next major release. Use `recv()` instead. + +irq_dr +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_dr + + . + + :Returns: + + - `True` represents Data is in the RX FIFO buffer + - `False` represents anything depending on context (state/condition of FIFO buffers); + usually this means the flag's been reset. + + Pass ``dataReady`` |irq note| + + |update manually| + +irq_df +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_df + + . + + :Returns: + + - `True` signifies the nRF24L01 attemped all configured retries + - `False` represents anything depending on context (state/condition); usually this + means the flag's been reset. + + Pass ``dataFail`` |irq note| + + |update manually| + +irq_ds +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_ds + + . + + :Returns: + + - `True` represents a successful transmission + - `False` represents anything depending on context (state/condition of FIFO buffers); + usually this means the flag's been reset. + + Pass ``dataSent`` |irq note| + + |update manually| + +clear_status_flags() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.clear_status_flags + + Internally, this is automatically called by `send()`, `write()`, `recv()`, and when + `listen` changes from `False` to `True`. + + :param bool data_recv: specifies wheather to clear the "RX Data Ready" flag. + :param bool data_sent: specifies wheather to clear the "TX Data Sent" flag. + :param bool data_fail: specifies wheather to clear the "Max Re-transmit reached" flag. + + .. note:: Clearing the ``data_fail`` flag is necessary for continued transmissions from the + nRF24L01 (locks the TX FIFO buffer when `irq_df` is `True`) despite wheather or not the + MCU is taking advantage of the interrupt (IRQ) pin. Call this function only when there + is an antiquated status flag (after you've dealt with the specific payload related to + the staus flags that were set), otherwise it can cause payloads to be ignored and + occupy the RX/TX FIFO buffers. See `Appendix A of the nRF24L01+ Specifications Sheet + `_ for an outline of + proper behavior. + +power +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.power + + This is exposed for convenience. + + - `False` basically puts the nRF24L01 to sleep (AKA power down mode) with ultra-low + current consumption. No transmissions are executed when sleeping, but the nRF24L01 can + still be accessed through SPI. Upon instantiation, this driver class puts the nRF24L01 + to sleep until the MCU invokes RX/TX transmissions. This driver class doesn't power down + the nRF24L01 after RX/TX transmissions are complete (avoiding the required power up/down + 150 µs wait time), that preference is left to the application. + - `True` powers up the nRF24L01. This is the first step towards entering RX/TX modes (see + also `listen` attribute). Powering up is automatically handled by the `listen` attribute + as well as the `send()` and `write()` functions. + + .. note:: This attribute needs to be `True` if you want to put radio on Standby-II (highest + current consumption) or Standby-I (moderate current consumption) modes, which Standby + mode depends on the state of the CE pin. TX transmissions are only executed during + Standby-II by calling `send()` or `write()`. RX transmissions are received during + Standby-II by setting `listen` attribute to `True` (see `Chapter 6.1.2-7 of the + nRF24L01+ Specifications Sheet `_). After using + `send()` or setting `listen` to `False`, the nRF24L01 is left in Standby-I mode (see + also notes on the `write()` function). + +tx_full +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.tx_full + + . + + |update manually| + + :returns: + + - `True` for TX FIFO buffer is full + - `False` for TX FIFO buffer is not full. This doesn't mean the TX FIFO buffer is + empty. + +update() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.update + + Refreshing the status byte is vital to checking status of the interrupt flags, RX pipe + number related to current RX payload, and if the TX FIFO buffer is full. This function + returns nothing, but internally updates the `irq_dr`, `irq_ds`, `irq_df`, `pipe`, and + `tx_full` attributes. Internally this is a helper function to `send()`, and `resend()` + functions. + +resend() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.resend + + All returned data from this function follows the same patttern that `send()` returns with + the added condition that this function will return `False` if the TX FIFO buffer is empty. + + .. note:: The nRF24L01 normally removes a payload from the TX FIFO buffer after successful + transmission, but not when this function is called. The payload (successfully + transmitted or not) will remain in the TX FIFO buffer until `flush_tx()` is called to + remove them. Alternatively, using this function also allows the failed payload to be + over-written by using `send()` or `write()` to load a new payload into the TX FIFO + buffer. + +write() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.write + + :param bytearray buf: The payload to transmit. This bytearray must have a length greater + than 0 and less than 32 bytes, otherwise a `ValueError` exception is thrown. + + - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's + length is less than the `payload_length` attribute for data pipe 0, then this + bytearray is padded with zeros until its length is equal to the `payload_length` + attribute for data pipe 0. + - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's + length is greater than `payload_length` attribute for data pipe 0, then this + bytearray's length is truncated to equal the `payload_length` attribute for data + pipe 0. + :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for + an acknowledgment from the receiving nRF24L01. This parameter directly controls a + ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about + the payload). Therefore, it takes advantage of an nRF24L01 feature specific to + individual payloads, and its value is not saved anywhere. You do not need to specify + this for every payload if the `arc` attribute is disabled, however setting this + parameter to `True` will work despite the `arc` attribute's setting. + + .. note:: Each transmission is in the form of a packet. This packet contains sections + of data around and including the payload. `See Chapter 7.3 in the nRF24L01 + Specifications Sheet `_ for more + details. + + This function isn't completely non-blocking as we still need to wait just under 5 ms for + the CSN pin to settle (allowing a clean SPI transaction). + + .. note:: The nRF24L01 doesn't initiate sending until a mandatory minimum 10 µs pulse on + the CE pin is acheived. That pulse is initiated before this function exits. However, we + have left that 10 µs wait time to be managed by the MCU in cases of asychronous + application, or it is managed by using `send()` instead of this function. According to + the Specification sheet, if the CE pin remains HIGH for longer than 10 µs, then the + nRF24L01 will continue to transmit all payloads found in the TX FIFO buffer. + + .. warning:: + A note paraphrased from the `nRF24L01+ Specifications Sheet + `_: + + It is important to NEVER to keep the nRF24L01+ in TX mode for more than 4 ms at a time. + If the [`arc` attribute is] enabled, nRF24L01+ is never in TX mode longer than 4 + ms. + + .. tip:: Use this function at your own risk. Because of the underlying `"Enhanced + ShockBurst Protocol" `_, disobeying the 4 + ms rule is easily avoided if you enable the `auto_ack` attribute. Alternatively, you + MUST use interrupt flags or IRQ pin with user defined timer(s) to AVOID breaking the + 4 ms rule. If the `nRF24L01+ Specifications Sheet explicitly states this + `_, we have to assume + radio damage or misbehavior as a result of disobeying the 4 ms rule. See also `table 18 + in the nRF24L01 specification sheet `_ for + calculating an adequate transmission timeout sentinal. + +flush_rx() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_rx + + .. note:: The nRF24L01 RX FIFO is 3 level stack that holds payload data. This means that + there can be up to 3 received payloads (each of a maximum length equal to 32 bytes) + waiting to be read (and popped from the stack) by `recv()` or `read_ack()`. This + function clears all 3 levels. + +flush_tx() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_tx + + .. note:: The nRF24L01 TX FIFO is 3 level stack that holds payload data. This means that + there can be up to 3 payloads (each of a maximum length equal to 32 bytes) waiting to + be transmit by `send()`, `resend()` or `write()`. This function clears all 3 levels. It + is worth noting that the payload data is only popped from the TX FIFO stack upon + successful transmission (see also `resend()` as the handling of failed transmissions + can be altered). + +fifo() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.fifo + + :param bool about_tx: + - `True` means information returned is about the TX FIFO buffer. + - `False` means information returned is about the RX FIFO buffer. This parameter + defaults to `False` when not specified. + :param bool check_empty: + - `True` tests if the specified FIFO buffer is empty. + - `False` tests if the specified FIFO buffer is full. + - `None` (when not specified) returns a 2 bit number representing both empty (bit 1) & + full (bit 0) tests related to the FIFO buffer specified using the ``about_tx`` + parameter. + :returns: + - A `bool` answer to the question: + "Is the [TX/RX]:[`True`/`False`] FIFO buffer [empty/full]:[`True`/`False`]? + - If the ``check_empty`` parameter is not specified: an `int` in range [0,2] for which: + + - ``1`` means the specified FIFO buffer is full + - ``2`` means the specified FIFO buffer is empty + - ``0`` means the specified FIFO buffer is neither full nor empty + +pipe +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pipe + + . + + |update manually| + + :Returns: + + - `None` if there is no payload in RX FIFO. + - The `int` identifying pipe number [0,5] that received the next + available payload in the RX FIFO buffer. + +address_length +****************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.address_length + + A valid input value must be an `int` in range [3, 5]. Otherwise a `ValueError` exception is + thrown. Default is set to the nRF24L01's maximum of 5. + +address() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.address + + This function returns the full content of the nRF24L01's registers about RX/TX addresses + despite what `address_length` is set to. + + :param int index: the number of the data pipe whose address is to be returned. Defaults to + ``-1``. A valid index ranges [0,5] for RX addresses or any negative `int` for the TX + address. Otherwise an `IndexError` is thown. + +rpd +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.rpd + + The RPD flag is triggered in the following cases: + + 1. During RX mode (`listen` = `True`) and an arbitrary RF transmission with a gain + above -64 dBm threshold is/was present. + 2. When a packet is received (instigated by the nRF24L01 used to detect/"listen" for + incoming packets). + + .. note:: See also + `section 6.4 of the Specification Sheet concerning the RPD flag + `_. Ambient + temperature affects the -64 dBm threshold. The latching of this flag happens + differently under certain conditions. + +start_carrier_wave() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.start_carrier_wave + + This is a basic test of the nRF24L01's TX output. It is a commonly required + test for telecommunication regulations. Calling this function may introduce + interference with other transceivers that use frequencies in range [2.4, + 2.525] GHz. To verify that this test is working properly, use the following + code on a seperate nRF24L01 transceiver: + + .. code-block:: python + + # declare objects for SPI bus and CSN pin and CE pin + nrf. = RF24(spi, csn, ce) + # set nrf.pa_level, nrf.channel, & nrf.data_rate values to + # match the corresponding attributes on the device that is + # transmitting the carrier wave + nrf.listen = True + if nrf.rpd: + print("carrier wave detected") + + The `pa_level`, `channel` & `data_rate` attributes are vital factors to + the success of this test. See also the `rpd` attribute. + +stop_carrier_wave() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.stop_carrier_wave + + See `start_carrier_wave()` for more details. + + .. note:: + Calling this function puts the nRF24L01 to sleep (AKA power down mode). + +Configuration API +----------------- + +CSN_DELAY +****************************** + +.. autodata:: circuitpython_nrf24l01.rf24.CSN_DELAY + +dynamic_payloads +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.dynamic_payloads + + Default setting is enabled on all pipes. + + - `True` or ``1`` enables nRF24L01's dynamic payload length feature for all data pipes. The + `payload_length` attribute is ignored when this feature is enabled for respective or all + data pipes. + - `False` or ``0`` disables nRF24L01's dynamic payload length feature for all data pipes. + Be sure to adjust the `payload_length` attribute accordingly when this feature is + disabled for any data pipes. + - A `list` or `tuple` containing booleans or integers can be used control this feature per + data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be + ignored since there are only 6 data pipes. + + .. note:: + This attribute mostly relates to RX operations, but data pipe 0 applies to TX + operations also. + +payload_length +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.payload_length + + If the `dynamic_payloads` attribute is enabled for a certain data pipe, this attribute has + no affect on that data pipe. When `dynamic_payloads` is disabled for a certain data pipe, + this attribute is used to specify the payload length on that data pipe in RX mode. + + A valid input value must be: + + * an `int` in range [1, 32]. Otherwise a `ValueError` exception is thrown. + * a `list` or `tuple` containing integers can be used to control this attribute per + data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will + be ignored since there are only 6 data pipes. if a index's value is ``0``, then the + existing setting will persist (not be changed). + + Default is set to the nRF24L01's maximum of 32 (on all data pipes). + + .. note:: + This attribute mostly relates to RX operations, but data pipe 0 applies to TX + operations also. + +auto_ack +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.auto_ack + + Default setting is enabled on all data pipes. + + - `True` or ``1`` enables transmitting automatic acknowledgment packets for all data pipes. + The CRC (cyclic redundancy checking) is enabled automatically by the nRF24L01 if the + `auto_ack` attribute is enabled for any data pipe (see also `crc` attribute). + - `False` or ``0`` disables transmitting automatic acknowledgment packets for all data + pipes. The `crc` attribute will remain unaffected when disabling this attribute for any + data pipes. + - A `list` or `tuple` containing booleans or integers can be used control this feature per + data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be + ignored since there are only 6 data pipes. + + .. note:: + This attribute mostly relates to RX operations, but data pipe 0 applies to TX + operations also. + +arc +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.arc + + The `auto_ack` attribute must be enabled on the receiving nRF24L01 respective data pipe, + otherwise this attribute will make `send()` seem like it failed. + + A valid input value must be in range [0, 15]. Otherwise a `ValueError` exception is thrown. + Default is set to 3. A value of ``0`` disables the automatic re-transmit feature and + considers all payload transmissions a success. + +ard +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ard + + During this time, the nRF24L01 is listening for the ACK packet. If the + `auto_ack` attribute is disabled, this attribute is not applied. + + A valid input value must be in range [250, 4000]. Otherwise a `ValueError` exception is + thrown. Default is 1500 for reliability. If this is set to a value that is not multiple of + 250, then the highest multiple of 250 that is no greater than the input value is used. + + .. note:: Paraphrased from nRF24L01 specifications sheet: + + Please take care when setting this parameter. If the custom ACK payload is more than 15 + bytes in 2 Mbps data rate, the `ard` must be 500µS or more. If the custom ACK payload + is more than 5 bytes in 1 Mbps data rate, the `ard` must be 500µS or more. In 250kbps + data rate (even when there is no custom ACK payload) the `ard` must be 500µS or more. + + See `data_rate` attribute on how to set the data rate of the nRF24L01's transmissions. + +ack +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ack + + Use this attribute to set/check if the custom ACK payloads feature is enabled. Default + setting is `False`. + + - `True` enables the use of custom ACK payloads in the ACK packet when responding to + receiving transmissions. + - `False` disables the use of custom ACK payloads in the ACK packet when responding to + receiving transmissions. + + .. important:: + As `dynamic_payloads` and `auto_ack` attributes are required for this feature to work, + they are automatically enabled (on data pipe 0) as needed. However, it is required to + enable the `auto_ack` and `dynamic_payloads` features on all applicable pipes. + Disabling this feature does not disable the `auto_ack` and `dynamic_payloads` + attributes for any data pipe; they work just fine without this feature. + +interrupt_config() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.interrupt_config + + The digital signal from the nRF24L01's IRQ pin is active LOW. (write-only) + + :param bool data_recv: If this is `True`, then IRQ pin goes active when there is new data + to read in the RX FIFO buffer. Default setting is `True` + :param bool data_sent: If this is `True`, then IRQ pin goes active when a payload from TX + buffer is successfully transmit. Default setting is `True` + :param bool data_fail: If this is `True`, then IRQ pin goes active when maximum number of + attempts to re-transmit the packet have been reached. If `auto_ack` attribute is + disabled, then this IRQ event is not used. Default setting is `True` + + .. note:: To fetch the status (not configuration) of these IRQ flags, use the `irq_df`, + `irq_ds`, `irq_dr` attributes respectively. + + .. tip:: Paraphrased from nRF24L01+ Specification Sheet: + + The procedure for handling ``data_recv`` IRQ should be: + + 1. read payload through `recv()` + 2. clear ``dataReady`` status flag (taken care of by using `recv()` in previous step) + 3. read FIFO_STATUS register to check if there are more payloads available in RX FIFO + buffer. A call to `pipe` (may require `update()` to be called), `any()` or even + ``(False,True)`` as parameters to `fifo()` will get this result. + 4. if there is more data in RX FIFO, repeat from step 1 + +data_rate +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.data_rate + + A valid input value is: + + - ``1`` sets the frequency data rate to 1 Mbps + - ``2`` sets the frequency data rate to 2 Mbps + - ``250`` sets the frequency data rate to 250 Kbps + + Any invalid input throws a `ValueError` exception. Default is 1 Mbps. + + .. warning:: 250 Kbps can be buggy on the non-plus models of the nRF24L01 product line. If + you use 250 Kbps data rate, and some transmissions report failed by the transmitting + nRF24L01, even though the same packet in question actually reports received by the + receiving nRF24L01, then try a higher data rate. CAUTION: Higher data rates mean less + maximum distance between nRF24L01 transceivers (and vise versa). + +channel +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.channel + + A valid input value must be in range [0, 125] (that means [2.4, 2.525] GHz). Otherwise a + `ValueError` exception is thrown. Default is ``76`` (2.476 GHz). + +crc +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.crc + + CRC is a way of making sure that the transmission didn't get corrupted over the air. + + A valid input value must be: + + - ``0`` disables CRC (no anti-corruption of data) + - ``1`` enables CRC encoding scheme using 1 byte (weak anti-corruption of data) + - ``2`` enables CRC encoding scheme using 2 bytes (better anti-corruption of data) + + Any invalid input throws a `ValueError` exception. Default is enabled using 2 bytes. + + .. note:: The nRF24L01 automatically enables CRC if automatic acknowledgment feature is + enabled (see `auto_ack` attribute). + +pa_level +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pa_level + + Higher levels mean the transmission will cover a longer distance. Use this attribute to + tweak the nRF24L01 current consumption on projects that don't span large areas. + + A valid input value is: + + - ``-18`` sets the nRF24L01's power amplifier to -18 dBm (lowest) + - ``-12`` sets the nRF24L01's power amplifier to -12 dBm + - ``-6`` sets the nRF24L01's power amplifier to -6 dBm + - ``0`` sets the nRF24L01's power amplifier to 0 dBm (highest) + + If this attribute is set to a `list` or `tuple`, then the list/tuple must contain the + desired power amplifier level (from list above) at index 0 and a `bool` to control + the Low Noise Amplifier (LNA) feature at index 1. All other indices will be discarded. + + .. note:: + The LNA feature only applies to the nRF24L01 (non-plus variant). This + includes boards with the RFX24C01-based PA/LNA muxing IC attached to an + SMA-type detachable antenna. + + Any invalid input will invoke the default of 0 dBm with LNA enabled. + +is_lna_enabled +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.is_lna_enabled + + See `pa_level` attribute about how to set this. Default is always enabled, but this + feature is specific to certain nRF24L01-based circuits. Check with your module's + manufacturer to see is it can toggle the Low Noise Amplifier feature. diff --git a/docs/examples.rst b/docs/examples.rst index 7192373..b044e2b 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -43,3 +43,12 @@ This is a test to show how to use "with" statements to manage multiple different .. literalinclude:: ../examples/nrf24l01_context_test.py :caption: examples/nrf24l01_context_test.py :linenos: + +Working with TMRh20's Arduino library +------------------------------------- + +This test is meant to prove compatibility with the popular Arduino library for the nRF24L01 by TMRh20 (available for install via the Arduino IDE's Library Manager). The following code has been designed/test with the TMRh20 library example named "GettingStarted_HandlingData.ino". + +.. literalinclude:: ../examples/nrf24l01_2arduino_handling_data.py + :caption: examples/nrf24l01_2arduino_handling_data.py + :linenos: diff --git a/examples/nrf24l01_2arduino_handling_data.py b/examples/nrf24l01_2arduino_handling_data.py index e7b64e8..9ea5761 100644 --- a/examples/nrf24l01_2arduino_handling_data.py +++ b/examples/nrf24l01_2arduino_handling_data.py @@ -8,7 +8,9 @@ import struct import board import digitalio as dio -from circuitpython_nrf24l01 import RF24 +# if running this on a ATSAMD21 M0 based board +# from circuitpython_nrf24l01.rf24_lite import RF24 +from circuitpython_nrf24l01.rf24 import RF24 # addresses needs to be in a buffer protocol object (bytearray) address = [b'1Node', b'2Node'] @@ -22,13 +24,13 @@ spi = board.SPI() # init spi bus object # initialize the nRF24L01 on the spi bus object -nrf = RF24(spi, csn, ce, ask_no_ack=False) +nrf = RF24(spi, csn, ce) nrf.dynamic_payloads = False # this is the default in the TMRh20 arduino library # set address of TX node into a RX pipe -nrf.open_rx_pipe(1, address[1]) +nrf.open_rx_pipe(1, address[0]) # set address of RX node into a TX pipe -nrf.open_tx_pipe(address[0]) +nrf.open_tx_pipe(address[1]) def master(count=5): # count = 5 will only transmit 5 packets """Transmits an arbitrary unsigned long value every second. This method @@ -46,10 +48,8 @@ def master(count=5): # count = 5 will only transmit 5 packets # 'f' means a single 4 byte float value. buffer = struct.pack(' 1 + nrf.open_rx_pipe(5, b'1Node') # NOTE we do this inside the "with" block + # only the first character gets written because it is on a pipe_number > 1 # NOTE if opening pipes outside of the "with" block, you may encounter # conflicts in the differences between address_length attributes. # the address_length attribute must equal the length of addresses - # display current setting of the nrf object + # display current settings of the nrf object nrf.what_happened(True) # True dumps pipe info print("\nsettings configured by the basicRF object") with basicRF as nerf: # the "as nerf" part is optional + nerf.open_rx_pipe(2, b'?') # again only uses the first character nerf.what_happened(1) -# if you examine the outputs from what_happened() you'll -# see that pipe 5 is still open but the radio settings have changed. +# if you examine the outputs from what_happened() you'll see: +# pipe 5 is opened using the nrf object, but closed using the basicRF object. +# pipe 2 is closed using the nrf object, but opened using the basicRF object. +# also notice the different addresses bound to the RX pipes # this is because the "with" statements load the existing settings # for the RF24 object specified after the word "with". -# the things that remain consistent despite the use of "with" -# statements include: power mode (standby or sleep), state of the -# pipes and their addresses, and primary role (RX/TX mode) - -# close all pipes because their state remains even if power is lost -with nrf: # addresses also remain despite power loss - for i in range(6): - nrf.close_rx_pipe(i) # resets addresses also +# exiting a with statement will always set the nRF24L01's power mode to sleep +# NOTE this library's RF24 class closes all pipes upon instantiation diff --git a/examples/nrf24l01_interrupt_test.py b/examples/nrf24l01_interrupt_test.py index a3dc8b7..e474a6f 100644 --- a/examples/nrf24l01_interrupt_test.py +++ b/examples/nrf24l01_interrupt_test.py @@ -5,7 +5,9 @@ import time import board import digitalio as dio -from circuitpython_nrf24l01 import RF24 +# if running this on a ATSAMD21 M0 based board +# from circuitpython_nrf24l01.rf24_lite import RF24 +from circuitpython_nrf24l01.rf24 import RF24 # address needs to be in a buffer protocol object (bytearray is preferred) address = b'1Node' @@ -96,7 +98,8 @@ def slave(timeout=10): # will listen for 10 seconds before timing out nrf.flush_rx() nrf.listen = 0 nrf.open_tx_pipe(address) - nrf.send(b'pong') # send a payload to complete the on data ready test + # send a payload to complete the on data ready test + nrf.send(b'pong', force_retry=1) # we're done on this side print("""\ diff --git a/examples/nrf24l01_simple_test.py b/examples/nrf24l01_simple_test.py index 4d690b1..79dd2e8 100644 --- a/examples/nrf24l01_simple_test.py +++ b/examples/nrf24l01_simple_test.py @@ -5,7 +5,9 @@ import struct import board import digitalio as dio -from circuitpython_nrf24l01 import RF24 +# if running this on a ATSAMD21 M0 based board +# from circuitpython_nrf24l01.rf24_lite import RF24 +from circuitpython_nrf24l01.rf24 import RF24 # addresses needs to be in a buffer protocol object (bytearray) address = b'1Node' @@ -29,30 +31,28 @@ def master(count=5): # count = 5 will only transmit 5 packets # ensures the nRF24L01 is in TX mode nrf.listen = False - counter = count - while counter: + while count: # use struct.pack to packetize your data # into a usable payload - buffer = struct.pack(' Date: Thu, 24 Sep 2020 23:24:46 -0700 Subject: [PATCH 077/127] copied crc work from my roboclaw lib --- circuitpython_nrf24l01/data_manip.py | 111 +++++++++++++++++++++++++++ circuitpython_nrf24l01/fake_ble.py | 74 ++++++------------ docs/api.rst | 6 ++ 3 files changed, 140 insertions(+), 51 deletions(-) create mode 100644 circuitpython_nrf24l01/data_manip.py diff --git a/circuitpython_nrf24l01/data_manip.py b/circuitpython_nrf24l01/data_manip.py new file mode 100644 index 0000000..e07f565 --- /dev/null +++ b/circuitpython_nrf24l01/data_manip.py @@ -0,0 +1,111 @@ +"""A module for manipulating dat including generating CRC values and datatype constraints. +For more information on how CRC algorithms work: https://www.zlib.net/crc_v3.txt""" + +def make_poly(bit_length, msb=False): + """Make `int` "degree polynomial" in which each bit represents a degree who's coefficient is 1 + + :param int bit_length: The amount of bits to play with + :param bool msb: `True` make only the MSBit 1 and the rest a 0. `False` makes all bits 1. + """ + if msb: + return 1 << ((8 * int(bit_length / 8)) - 1) + result = 0 + for x in range(int(bit_length / 8)): + result += 0xff << int(x * 8) + return result + +def crc16(data, deg_poly=0x1021, init_value=0): + """Calculates a checksum of 16-bit length""" + return crc_bits(data, 16, deg_poly, init_value) + +def crc32(data, deg_poly=0x5b06, init_value=0x555555): + """Calculates a checksum of 32-bit length. Default ``deg_poly`` and ``init_value`` values + are BLE compliant.""" + return crc_bits(data, 32, deg_poly, init_value) + +def crc_bits(data, bit_length, deg_poly, init_value): + """Calculates a checksum of various sized buffers + + :param bytearray data: This `bytearray` of data to be uncorrupted. + :param int bit_length: The length of bits that will represent the checksum. + :param int deg_poly: A preset "degree polynomial" in which each bit represents a degree who's + coefficient is 1. + :param int init_value: This will be the value that the checksum will use while shifting in the + buffer data. + """ + crc = init_value + mask = make_poly(bit_length, msb=True) # 0x8000 + for _ in range(8): # shift out initial value 1 bit @ a time. + if crc & mask: # if divisible + # 0x1021 is a standard polynomial used for crc16 algorithms + # behaves like unsigned subtraction + crc = (crc << 1) ^ deg_poly + else: + crc = crc << 1 # bring down next bit for binary + for byte in data: # for each byte + crc ^= (byte << 8) + for _ in range(8): # for each bit + if crc & mask: # if divisible + # 0x1021 is a standard polynomial used for crc16 algorithms + # behaves like unsigned subtraction + crc = (crc << 1) ^ deg_poly + else: + crc = crc << 1 # bring down next bit for binary long-division + return crc & make_poly(bit_length) # return only the remainder + +def validate16(data, deg_poly=0x1021, init_value=0): + """Validates a received data by comparing the calculated 16-bit checksum with the + checksum included at the end of the data""" + return validate(data, 16, deg_poly, init_value) + +def validate(data, bit_length, deg_poly, init_value): + """Validates a received checksum of various sized buffers + + :param bytearray data: This `bytearray` of data to be uncorrupted. + :param int bit_length: The length of bits that will represent the checksum. + :param int deg_poly: A preset "degree polynomial" (in which each bit represents a degree who's + coefficient is 1) as a quotient. + :param int init_value: This will be the value that the checksum will use while shifting in the + buffer data. + + :Returns: `True` if data was uncorrupted. `False` if something went wrong. + (either checksum didn't match or payload is altered). + """ + cal_d = crc_bits(data[:-(bit_length / 8)], bit_length, deg_poly, init_value) + rcv_d = 0 + for byte in data[-(bit_length / 8):]: + rcv_d = (rcv_d << 8) | byte + print(cal_d == rcv_d) + return cal_d == rcv_d + +def swap_bits(original): + """reverses the bit order into LSbit to MSBit in a single byte. + + :returns: + An `int` containing the byte whose bits go from LSBit to MSBit + compared to the value passed to the ``original`` parameter. + :param int original: This should be a single unsigned byte, meaning the + parameters value can only range from 0 to 255. + """ + original &= 0xFF # truncate the MSBytes just in case. + reverse = 0 + for _ in range(8): + reverse <<= 1 + reverse |= original & 1 + original >>= 1 + return reverse # we're done here + + +def reverse_bits(original): + """reverses the bit order into LSbit to MSBit without touching the byte order + + :returns: + A bytearray whose bytes still go from MSByte to LSByte, but each + byte's bits go from LSBit to MSBit. + :param bytearray original: The original buffer whose bits are to be + reversed. + """ + r = b"" + for byte in original: + r += bytes([swap_bits(byte)]) + return bytearray([r]) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index 0d84a21..ed6ef46 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -49,39 +49,7 @@ other event, "on data fail", is ignored because it will never get thrown with "auto_ack" off. However the interrupt settings can be modified AFTER instantiation """ - -def _swap_bits(orig): - """reverses the bit order into LSbit to MSBit""" - reverse = 0 - for _ in range(8): - reverse <<= 1 - reverse |= orig & 1 - orig >>= 1 - return reverse # we're done here - -def _reverse_bits(orig): - """reverses the bit order into LSbit to MSBit without touching the byte order""" - r = b'' - for byte in orig: - r += bytes([_swap_bits(byte)]) - return r - -def _make_crc(data): - """use this to create the 3 byte-long CRC data. returns a bytearray""" - # taken from source code - # https://github.com/adafruit/Adafruit_CircuitPython_SGP30/blob/d209c7c76f941dc60b24d85fdef177b5fb2e9943/adafruit_sgp30.py#L177 - # zlib or binascii modules would be ideal alternatives on the raspberry pi, but - # MicroPython & CircuitPython doesn't have the crc32() included in the uzlib or ubinascii - # modules. - crc = 0xFF - for byte in data: - crc ^= byte - for _ in range(8): - if crc & 0x80: - crc = (crc << 1) ^ 0x31 - else: - crc <<= 1 - return crc & 0xFF +from .data_manip import crc32, swap_bits, reverse_bits def _ble_whitening(data, whiten_coef): """for "whiten"ing the BLE packet data according to expected parameters""" @@ -95,8 +63,8 @@ def _ble_whitening(data, whiten_coef): # whitenCoeff <<= 1; # } # data++; - result = b'' - for byte in data: # for every byte + result = b"" + for byte in data: # for every byte for i in range(8): if whiten_coef & 0x80: whiten_coef ^= 0x11 @@ -105,6 +73,7 @@ def _ble_whitening(data, whiten_coef): result += bytes([byte]) return result + class FakeBLE: """Per the limitations of this technique, only power amplifier level is available for configuration when advertising BLE data. @@ -113,6 +82,7 @@ class FakeBLE: for fake BLE advertisements. :param bytearray name: The BLE device name to be advertised with the payload. """ + def __init__(self, nrf, name=None): self._device = nrf self._device.address_length = 4 @@ -126,7 +96,7 @@ def __init__(self, nrf, name=None): self._chan = 0 self._ble_name = None self.name = name - self._device.open_tx_pipe(_reverse_bits(b'\x8E\x89\xBE\xD6')) + self._device.open_tx_pipe(reverse_bits(b"\x8E\x89\xBE\xD6")) # b'\x8E\x89\xBE\xD6' = proper address for BLE advertisments def __enter__(self): @@ -161,10 +131,12 @@ def name(self, n): * payload_length has a maximum of (17 - length of name) bytes when broadcasting a name for itself. """ - if n is not None and 1 <= len(n) <= 12: # max defined by 1 byte payload data requisite - self._ble_name = bytes([len(n) + 1]) + b'\x08' + n + if ( + n is not None and 1 <= len(n) <= 12 + ): # max defined by 1 byte payload data requisite + self._ble_name = bytes([len(n) + 1]) + b"\x08" + n else: - self._ble_name = None # name will not be advertised + self._ble_name = None # name will not be advertised def _chan_hop(self): # NOTE BLE channel number is different from the nRF channel number. @@ -203,35 +175,35 @@ def advertise(self, buf): # = 13 - (BLE name length + 2 if any) name_len = len(self._ble_name) if self._ble_name is not None else 0 if not buf or len(buf) > (13 - name_len): - raise ValueError("buf must be a buffer protocol object with a byte length of" - " at least 1 and no greater than 13 - " - "{} = {}".format(name_len, 13 - name_len)) + raise ValueError( + "buf must have a length in range [1, {}]".format(13 - name_len) + ) # BLE payload = # header(1) + payload length(1) + MAC address(6) + containers + CRC(3) bytes # header == PDU type, given MAC address is random/arbitrary # type == 0x42 for Android or 0x40 for iPhone # containers (in bytes) = length(1) + type(1) + data # the 1 byte about container's length excludes only itself - payload = b'\x42' # init a temp payload buffer with header type byte + payload = b"\x42" # init a temp payload buffer with header type byte # to avoid padding when dynamic_payloads is disabled, set payload_length attribute self._device.payload_length = len(buf) + 16 + name_len # payload length excludes the header, itself, and crc lengths payload += bytes([self._device.payload_length - 5]) - payload += b'\x11\x22\x33\x44\x55\x66' # a bogus MAC address + payload += b"\x11\x22\x33\x44\x55\x66" # a bogus MAC address # payload will have at least 2 containers: # 3 bytes of flags (required for BLE discoverable), & at least (1+2) byte of data - payload += b'\x02\x01\x06' # BLE flags for discoverability and non-pairable etc + payload += b"\x02\x01\x06" # BLE flags for discoverability and non-pairable etc # payload will also have to fit the optional BLE device name as # a seperate container ([name length + 2] bytes) if self._ble_name is not None: payload += self._ble_name - payload += (bytes([len(buf) + 1]) + b'\xFF' + buf) # append the data container + payload += bytes([len(buf) + 1]) + b"\xFF" + buf # append the data container # crc is generated from b'\x55\x55\x55' about everything except itself - payload += _make_crc(payload) - self._chan_hop() # cycle to next BLE channel per specs + payload += crc32(payload) + self._chan_hop() # cycle to next BLE channel per specs # the whiten_coef value we need is the BLE channel (37,38, or 39) left shifted one whiten_coef = 37 + self._chan - whiten_coef = _swap_bits(whiten_coef) | 2 - rev_whiten_pl = _reverse_bits(_ble_whitening(payload, whiten_coef)) - print('transmitting {} as {}'.format(payload, rev_whiten_pl)) + whiten_coef = swap_bits(whiten_coef) | 2 + rev_whiten_pl = reverse_bits(_ble_whitening(payload, whiten_coef)) + print("transmitting {} as {}".format(payload, rev_whiten_pl)) self._device.send(rev_whiten_pl) diff --git a/docs/api.rst b/docs/api.rst index e4f6f09..ac11aea 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1014,3 +1014,9 @@ is_lna_enabled See `pa_level` attribute about how to set this. Default is always enabled, but this feature is specific to certain nRF24L01-based circuits. Check with your module's manufacturer to see is it can toggle the Low Noise Amplifier feature. + +Fake BLE +============== + + .. autoclass:: circuitpython_nrf24l01.fake_ble + :members: From d8294ceeb4ef64226b97c95b28399645aa6f0d67 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 25 Sep 2020 00:30:13 -0700 Subject: [PATCH 078/127] payload assembly is broken --- circuitpython_nrf24l01/data_manip.py | 6 +++--- circuitpython_nrf24l01/fake_ble.py | 16 +++++++++------- examples/nrf24l01_fake_ble_test.py | 12 +++++++----- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/circuitpython_nrf24l01/data_manip.py b/circuitpython_nrf24l01/data_manip.py index e07f565..7551bcc 100644 --- a/circuitpython_nrf24l01/data_manip.py +++ b/circuitpython_nrf24l01/data_manip.py @@ -80,7 +80,7 @@ def validate(data, bit_length, deg_poly, init_value): def swap_bits(original): """reverses the bit order into LSbit to MSBit in a single byte. - + :returns: An `int` containing the byte whose bits go from LSBit to MSBit compared to the value passed to the ``original`` parameter. @@ -98,7 +98,7 @@ def swap_bits(original): def reverse_bits(original): """reverses the bit order into LSbit to MSBit without touching the byte order - + :returns: A bytearray whose bytes still go from MSByte to LSByte, but each byte's bits go from LSBit to MSBit. @@ -108,4 +108,4 @@ def reverse_bits(original): r = b"" for byte in original: r += bytes([swap_bits(byte)]) - return bytearray([r]) + return bytearray(r) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index ed6ef46..4b6c235 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -90,9 +90,6 @@ def __init__(self, nrf, name=None): self._device.auto_ack = False self._device.crc = 0 self._device.arc = 0 - self._device.irq_df = False - self._device.irq_dr = False - self._device.irq_ds = True self._chan = 0 self._ble_name = None self.name = name @@ -100,9 +97,10 @@ def __init__(self, nrf, name=None): # b'\x8E\x89\xBE\xD6' = proper address for BLE advertisments def __enter__(self): - return self._device.__enter__() + self._device.__enter__() + return self - def __exit(self, *exc): + def __exit__(self, *exc): return self._device.__exit__(exc) @property @@ -188,7 +186,7 @@ def advertise(self, buf): # to avoid padding when dynamic_payloads is disabled, set payload_length attribute self._device.payload_length = len(buf) + 16 + name_len # payload length excludes the header, itself, and crc lengths - payload += bytes([self._device.payload_length - 5]) + payload += bytes(self._device.payload_length[0] - 5) payload += b"\x11\x22\x33\x44\x55\x66" # a bogus MAC address # payload will have at least 2 containers: # 3 bytes of flags (required for BLE discoverable), & at least (1+2) byte of data @@ -199,11 +197,15 @@ def advertise(self, buf): payload += self._ble_name payload += bytes([len(buf) + 1]) + b"\xFF" + buf # append the data container # crc is generated from b'\x55\x55\x55' about everything except itself - payload += crc32(payload) + checksum = crc32(payload) + for i in range(4): + shifted = (8 * (2 - i)) + payload += bytes([(checksum & (0xFF << shifted)) >> shifted]) self._chan_hop() # cycle to next BLE channel per specs # the whiten_coef value we need is the BLE channel (37,38, or 39) left shifted one whiten_coef = 37 + self._chan whiten_coef = swap_bits(whiten_coef) | 2 rev_whiten_pl = reverse_bits(_ble_whitening(payload, whiten_coef)) print("transmitting {} as {}".format(payload, rev_whiten_pl)) + print("payload length (whitened) =", len(rev_whiten_pl)) self._device.send(rev_whiten_pl) diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index 8166902..b7912c9 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -5,10 +5,11 @@ import struct import board import digitalio as dio +from circuitpython_nrf24l01.rf24 import RF24 from circuitpython_nrf24l01.fake_ble import FakeBLE # change these (digital output) pins accordingly -ce = dio.DigitalInOut(board.D7) +ce = dio.DigitalInOut(board.D4) csn = dio.DigitalInOut(board.D5) # using board.SPI() automatically selects the MCU's @@ -16,20 +17,21 @@ spi = board.SPI() # init spi bus object # initialize the nRF24L01 on the spi bus object as a BLE radio using -nrf = FakeBLE(spi, csn, ce, name=b'nRF24') +radio = RF24(spi, csn, ce) +nrf = FakeBLE(radio, name=b'nRF24') + # the name parameter is going to be its braodcasted BLE name # this can be changed at any time using the attribute nrf.name = b'RFtest' def master(count=15): - """Sends out an advertisement once a second (default 15 secs)""" + """Sends out an advertisement once a second for a default count of 15""" with nrf as ble: - ble.open_tx_pipe() # endure the tx pip is properly addressed for i in range(count): # advertise data this many times if (count - i) % 5 == 0 or (count - i) < 5: print( count - i, 'advertisment{}left to go!'.format('s ' if count - i - 1 else ' ')) # pack into bytearray using struct.pack() - ble.send(struct.pack('i', count)) # 'i' = 4 byte integer + ble.advertise(struct.pack('i', count)) # 'i' = 4 byte integer # channel is automatically managed by send() per BLE specs time.sleep(1) # wait till next broadcast From 259d45dd6a751166b4f4bf612bc4cebacfa58be6 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 25 Sep 2020 00:55:42 -0700 Subject: [PATCH 079/127] docs build; FakeBLE ctx independent of RF24 ctx --- circuitpython_nrf24l01/fake_ble.py | 33 +++++++++++++++--------------- docs/api.rst | 2 +- examples/nrf24l01_fake_ble_test.py | 3 +-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index 4b6c235..a981926 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -75,33 +75,35 @@ def _ble_whitening(data, whiten_coef): class FakeBLE: - """Per the limitations of this technique, only power amplifier level is available for - configuration when advertising BLE data. + """Per the limitations of this technique, only `RF24.pa_level` is + available for configuration when advertising BLE data. - :param ~circuitpython_nrf24l01 nrf: The object for the nRF24L01 transceiver to use - for fake BLE advertisements. - :param bytearray name: The BLE device name to be advertised with the payload. + :param ~circuitpython_nrf24l01.RF24 nrf: The object for the nRF24L01 + transceiver to use for fake BLE advertisements. + :param bytearray name: The BLE device name to be advertised with the + payload. """ def __init__(self, nrf, name=None): self._device = nrf - self._device.address_length = 4 - self._device.dynamic_payloads = False - self._device.auto_ack = False - self._device.crc = 0 - self._device.arc = 0 self._chan = 0 self._ble_name = None self.name = name - self._device.open_tx_pipe(reverse_bits(b"\x8E\x89\xBE\xD6")) + with self: + self._device.open_tx_pipe(reverse_bits(b"\x8E\x89\xBE\xD6")) # b'\x8E\x89\xBE\xD6' = proper address for BLE advertisments def __enter__(self): - self._device.__enter__() + self._device.address_length = 4 + self._device.dynamic_payloads = False + self._device.auto_ack = False + self._device.crc = 0 + self._device.arc = 0 return self def __exit__(self, *exc): - return self._device.__exit__(exc) + self._device.power = 0 + return False @property def name(self): @@ -129,9 +131,8 @@ def name(self, n): * payload_length has a maximum of (17 - length of name) bytes when broadcasting a name for itself. """ - if ( - n is not None and 1 <= len(n) <= 12 - ): # max defined by 1 byte payload data requisite + if (n is not None and 1 <= len(n) <= 12): + # max defined by 1 byte payload data requisite self._ble_name = bytes([len(n) + 1]) + b"\x08" + n else: self._ble_name = None # name will not be advertised diff --git a/docs/api.rst b/docs/api.rst index ac11aea..a9e3944 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1018,5 +1018,5 @@ is_lna_enabled Fake BLE ============== - .. autoclass:: circuitpython_nrf24l01.fake_ble + .. automodule:: circuitpython_nrf24l01.fake_ble :members: diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index b7912c9..05f6578 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -17,8 +17,7 @@ spi = board.SPI() # init spi bus object # initialize the nRF24L01 on the spi bus object as a BLE radio using -radio = RF24(spi, csn, ce) -nrf = FakeBLE(radio, name=b'nRF24') +nrf = FakeBLE(RF24(spi, csn, ce), name=b'nRF24') # the name parameter is going to be its braodcasted BLE name # this can be changed at any time using the attribute From 571ae4819e91653249d1408e8f456b5d3df46e8d Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sat, 26 Sep 2020 16:11:19 -0700 Subject: [PATCH 080/127] swing and a miss --- circuitpython_nrf24l01/data_manip.py | 111 ------------ circuitpython_nrf24l01/fake_ble.py | 241 ++++++++++++++++----------- 2 files changed, 144 insertions(+), 208 deletions(-) delete mode 100644 circuitpython_nrf24l01/data_manip.py diff --git a/circuitpython_nrf24l01/data_manip.py b/circuitpython_nrf24l01/data_manip.py deleted file mode 100644 index 7551bcc..0000000 --- a/circuitpython_nrf24l01/data_manip.py +++ /dev/null @@ -1,111 +0,0 @@ -"""A module for manipulating dat including generating CRC values and datatype constraints. -For more information on how CRC algorithms work: https://www.zlib.net/crc_v3.txt""" - -def make_poly(bit_length, msb=False): - """Make `int` "degree polynomial" in which each bit represents a degree who's coefficient is 1 - - :param int bit_length: The amount of bits to play with - :param bool msb: `True` make only the MSBit 1 and the rest a 0. `False` makes all bits 1. - """ - if msb: - return 1 << ((8 * int(bit_length / 8)) - 1) - result = 0 - for x in range(int(bit_length / 8)): - result += 0xff << int(x * 8) - return result - -def crc16(data, deg_poly=0x1021, init_value=0): - """Calculates a checksum of 16-bit length""" - return crc_bits(data, 16, deg_poly, init_value) - -def crc32(data, deg_poly=0x5b06, init_value=0x555555): - """Calculates a checksum of 32-bit length. Default ``deg_poly`` and ``init_value`` values - are BLE compliant.""" - return crc_bits(data, 32, deg_poly, init_value) - -def crc_bits(data, bit_length, deg_poly, init_value): - """Calculates a checksum of various sized buffers - - :param bytearray data: This `bytearray` of data to be uncorrupted. - :param int bit_length: The length of bits that will represent the checksum. - :param int deg_poly: A preset "degree polynomial" in which each bit represents a degree who's - coefficient is 1. - :param int init_value: This will be the value that the checksum will use while shifting in the - buffer data. - """ - crc = init_value - mask = make_poly(bit_length, msb=True) # 0x8000 - for _ in range(8): # shift out initial value 1 bit @ a time. - if crc & mask: # if divisible - # 0x1021 is a standard polynomial used for crc16 algorithms - # behaves like unsigned subtraction - crc = (crc << 1) ^ deg_poly - else: - crc = crc << 1 # bring down next bit for binary - for byte in data: # for each byte - crc ^= (byte << 8) - for _ in range(8): # for each bit - if crc & mask: # if divisible - # 0x1021 is a standard polynomial used for crc16 algorithms - # behaves like unsigned subtraction - crc = (crc << 1) ^ deg_poly - else: - crc = crc << 1 # bring down next bit for binary long-division - return crc & make_poly(bit_length) # return only the remainder - -def validate16(data, deg_poly=0x1021, init_value=0): - """Validates a received data by comparing the calculated 16-bit checksum with the - checksum included at the end of the data""" - return validate(data, 16, deg_poly, init_value) - -def validate(data, bit_length, deg_poly, init_value): - """Validates a received checksum of various sized buffers - - :param bytearray data: This `bytearray` of data to be uncorrupted. - :param int bit_length: The length of bits that will represent the checksum. - :param int deg_poly: A preset "degree polynomial" (in which each bit represents a degree who's - coefficient is 1) as a quotient. - :param int init_value: This will be the value that the checksum will use while shifting in the - buffer data. - - :Returns: `True` if data was uncorrupted. `False` if something went wrong. - (either checksum didn't match or payload is altered). - """ - cal_d = crc_bits(data[:-(bit_length / 8)], bit_length, deg_poly, init_value) - rcv_d = 0 - for byte in data[-(bit_length / 8):]: - rcv_d = (rcv_d << 8) | byte - print(cal_d == rcv_d) - return cal_d == rcv_d - -def swap_bits(original): - """reverses the bit order into LSbit to MSBit in a single byte. - - :returns: - An `int` containing the byte whose bits go from LSBit to MSBit - compared to the value passed to the ``original`` parameter. - :param int original: This should be a single unsigned byte, meaning the - parameters value can only range from 0 to 255. - """ - original &= 0xFF # truncate the MSBytes just in case. - reverse = 0 - for _ in range(8): - reverse <<= 1 - reverse |= original & 1 - original >>= 1 - return reverse # we're done here - - -def reverse_bits(original): - """reverses the bit order into LSbit to MSBit without touching the byte order - - :returns: - A bytearray whose bytes still go from MSByte to LSByte, but each - byte's bits go from LSBit to MSBit. - :param bytearray original: The original buffer whose bits are to be - reversed. - """ - r = b"" - for byte in original: - r += bytes([swap_bits(byte)]) - return bytearray(r) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index a981926..a6bb96b 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -49,31 +49,123 @@ other event, "on data fail", is ignored because it will never get thrown with "auto_ack" off. However the interrupt settings can be modified AFTER instantiation """ -from .data_manip import crc32, swap_bits, reverse_bits -def _ble_whitening(data, whiten_coef): - """for "whiten"ing the BLE packet data according to expected parameters""" - # uint8_t m; - # while(len--){ - # for(m = 1; m; m <<= 1){ - # if(whitenCoeff & 0x80){ - # whitenCoeff ^= 0x11; - # (*data) ^= m; - # } - # whitenCoeff <<= 1; - # } - # data++; - result = b"" - for byte in data: # for every byte - for i in range(8): - if whiten_coef & 0x80: - whiten_coef ^= 0x11 - byte ^= 1 << i - whiten_coef <<= 1 - result += bytes([byte]) + +def swap_bits(original): + """reverses the bit order into LSbit to MSBit in a single byte. + + :returns: + An `int` containing the byte whose bits go from LSBit to MSBit + compared to the value passed to the ``original`` parameter. + :param int original: This should be a single unsigned byte, meaning the + parameters value can only range from 0 to 255. + """ + original &= 0xFF + reverse = 0 + for _ in range(8): + reverse <<= 1 + reverse |= original & 1 + original >>= 1 + return reverse + + +def reverse_bits(original): + """reverses the bit the byte order into LSB to MSB + + :returns: + A `bytearray` whose bytes still go from MSByte to LSByte, but each + byte's bits go from LSBit to MSBit. + :param bytearray original: The original buffer whose bits are to be + reversed. + """ + length = len(original) - 1 + ret = bytearray(length + 1) + for i, byte in enumerate(original): + ret[i] = swap_bits(byte) + return ret + + +def reverse_bytes(original): + """Reverses the byte order for all bytes passed to the ``original`` + `bytearray`""" + result = bytearray(3) + for i, byte in enumerate(original): + result[len(original) - 1 - i] = byte return result +def add_chunk(data_type, buf): + """containerize a chunk of data according to BLE specs. + This chunk makes up a part of the advertising payload.""" + # container = 1 Byte info about container length + # 1 Byte info describing datatype + # X bytes holding the data + return bytearray([len(buf) + 1, data_type & 0xFF]) + buf + + +def make_payload(mac, name, payload): + """assemble the entire packet to be transmitted as a payload.""" + # data is ordered like so: + # 1 byte PDU type (always 0x42) + # 1 byte payload size + # 6 byte random mac address + # 21 bytes of containerized data including descriptor and name + # 3 bytes for CRC24 + pl_size = 11 + (len(name) + 2 if name is not None else 0) + len(payload) + buf = bytes([0x42, pl_size]) + mac # header + buf += add_chunk(1, b"\x05") # device descriptor + if name is not None: + buf += add_chunk(0x09, name) # device name + return buf + payload + + +def _ble_whitening(data, ble_channel): + """for "whiten"ing the BLE packet data according to expected parameters""" + data = bytearray(data) + coef = reverse_bits(bytes([ble_channel]))[0] & 2 + for i, byte in enumerate(data): # for every byte + mask = 1 + for _ in range(8): + if coef & 0x80: + coef ^= 0x11 + byte ^= mask + mask <<= 1 + coef <<= 1 + data[i] = byte + return data + + +def crc24_ble(data, deg_poly=0x00065B, init_val=0x555555): + """Calculates a checksum of various sized buffers + + :param bytearray data: This `bytearray` of data to be uncorrupted. + :param int deg_poly: A preset "degree polynomial" in which each bit represents a degree who's + coefficient is 1. + :param int init_val: This will be the initial value that the checksum will use while shifting in + the buffer data. + :returns: A 24-bit `bytearray` representing the checksum of the data. + """ + crc = init_val + for byte in data: + crc ^= (byte << 16) + for _ in range(8): + if crc & 0x800000: + crc = (crc << 1) ^ deg_poly + else: + crc = (crc << 1) + crc &= 0xFFFFFF + crc = (crc).to_bytes(3, "big") + return crc + +BLE_FREQ = (2, 26, 80) +""" BLE channel number is different from the nRF channel number. +These are the predefined channels used. +- nRF channel 2 == BLE channel 37 +- nRF channel 26 == BLE channel 38 +- nRF channel 80 == BLE channel 39 +""" + + class FakeBLE: """Per the limitations of this technique, only `RF24.pa_level` is available for configuration when advertising BLE data. @@ -90,8 +182,10 @@ def __init__(self, nrf, name=None): self._ble_name = None self.name = name with self: - self._device.open_tx_pipe(reverse_bits(b"\x8E\x89\xBE\xD6")) + self._device.open_rx_pipe(0, b"\x6B\x7D\x91\x71") + self._device.open_tx_pipe(b"\x6B\x7D\x91\x71") # b'\x8E\x89\xBE\xD6' = proper address for BLE advertisments + # with bits and bytes reversed address is b'\x6B\x7D\x91\x71' def __enter__(self): self._device.address_length = 4 @@ -99,6 +193,8 @@ def __enter__(self): self._device.auto_ack = False self._device.crc = 0 self._device.arc = 0 + self._device.power = 1 + self._device.payload_length = 32 return self def __exit__(self, *exc): @@ -107,45 +203,29 @@ def __exit__(self, *exc): @property def name(self): - """Represents the emulated BLE device name during braodcasts. This must - be a buffer protocol object (`bytearray`) , and can be any length (less - than 14) of UTF-8 freindly characters. Set this to `None` to disable - advertising a BLE device name. - - .. note:: the BLE device's name will occupy the same space as your TX - data. While space is limited to 32 bytes on the nRF24L01, actual - usable BLE TX data = 16 - (name length + 2). The other 16 bytes - available on the nRF24L01 TX FIFO buffer are reserved for the - [arbitrary] MAC address and other BLE related stuff. - """ - return self._ble_name[2:] if self._ble_name is not None else None - - @name.setter - def name(self, n): """The broadcasted BLE name of the nRF24L01. This is not required. In fact setting this attribute will subtract from the available payload - length (in bytes). + length (in bytes). Set this attribute to `None` to disable advertising the device name - * payload_length has a maximum of 19 bytes when NOT broadcasting a + * payload_length has a maximum of 21 bytes when NOT broadcasting a name for itself. - * payload_length has a maximum of (17 - length of name) bytes when + * payload_length has a maximum of [19 - length of name] bytes when broadcasting a name for itself. """ - if (n is not None and 1 <= len(n) <= 12): - # max defined by 1 byte payload data requisite - self._ble_name = bytes([len(n) + 1]) + b"\x08" + n - else: - self._ble_name = None # name will not be advertised - - def _chan_hop(self): - # NOTE BLE channel number is different from the nRF channel number. - # - nRF channel 2 == BLE channel 37 - # - nRF channel 26 == BLE channel 38 - # - nRF channel 80 == BLE channel 39 - self._chan = (self._chan + 1) if (self._chan + 1) < 3 else 0 - self._device.channel = 26 if self._chan == 1 else (80 if self._chan == 2 else 2) - - def advertise(self, buf): + return self._ble_name + + @name.setter + def name(self, n): + self._ble_name = n + + def hop_channel(self): + """trigger an automatic change of BLE compliant channels.""" + self._chan += 1 + if self._chan > 2: + self._chan = 0 + self._device.channel = BLE_FREQ[self._chan] + + def advertise(self, buf, data_type=0xFF): """This blocking function is used to transmit a payload. :returns: Nothing as every transmission will register as a success under these required @@ -157,56 +237,23 @@ def advertise(self, buf): (`bytearray`); in which case, all items in the list/tuple are processed for consecutive transmissions. - - If the `dynamic_payloads` attribute is disabled and this - bytearray's length is less than the `payload_length` attribute, - then this bytearray is padded with zeros until its length is - equal to the `payload_length` attribute. - - If the `dynamic_payloads` attribute is disabled and this - bytearray's length is greater than `payload_length` attribute, - then this bytearray's length is truncated to equal the - `payload_length` attribute. - .. note:: If the name of the emulated BLE device is also to be broadcast, then the 'name' attribute should be set prior to calling `advertise()`. """ - # max payload_length = 32 - 14(header, MAC, & CRC) - 2(container header) - 3(BLE flags) - # = 13 - (BLE name length + 2 if any) - name_len = len(self._ble_name) if self._ble_name is not None else 0 - if not buf or len(buf) > (13 - name_len): + if not buf or len(buf) > (21 - len(self.name)): raise ValueError( - "buf must have a length in range [1, {}]".format(13 - name_len) + "buf must have a length in range [1, {}]".format(21 - len(self.name)) ) - # BLE payload = - # header(1) + payload length(1) + MAC address(6) + containers + CRC(3) bytes - # header == PDU type, given MAC address is random/arbitrary - # type == 0x42 for Android or 0x40 for iPhone - # containers (in bytes) = length(1) + type(1) + data - # the 1 byte about container's length excludes only itself - payload = b"\x42" # init a temp payload buffer with header type byte - # to avoid padding when dynamic_payloads is disabled, set payload_length attribute - self._device.payload_length = len(buf) + 16 + name_len - # payload length excludes the header, itself, and crc lengths - payload += bytes(self._device.payload_length[0] - 5) - payload += b"\x11\x22\x33\x44\x55\x66" # a bogus MAC address + mac = b"\x11\x22\x33\x44\x55\x66" # a bogus MAC address # payload will have at least 2 containers: # 3 bytes of flags (required for BLE discoverable), & at least (1+2) byte of data - payload += b"\x02\x01\x06" # BLE flags for discoverability and non-pairable etc - # payload will also have to fit the optional BLE device name as - # a seperate container ([name length + 2] bytes) - if self._ble_name is not None: - payload += self._ble_name - payload += bytes([len(buf) + 1]) + b"\xFF" + buf # append the data container + # b"\x02\x01\x06" # BLE flags for discoverability and non-pairable etc + payload = make_payload(mac, self.name, add_chunk(data_type, buf)) # crc is generated from b'\x55\x55\x55' about everything except itself - checksum = crc32(payload) - for i in range(4): - shifted = (8 * (2 - i)) - payload += bytes([(checksum & (0xFF << shifted)) >> shifted]) - self._chan_hop() # cycle to next BLE channel per specs + payload += crc24_ble(payload) + self.hop_channel() # cycle to next BLE channel per specs # the whiten_coef value we need is the BLE channel (37,38, or 39) left shifted one - whiten_coef = 37 + self._chan - whiten_coef = swap_bits(whiten_coef) | 2 - rev_whiten_pl = reverse_bits(_ble_whitening(payload, whiten_coef)) - print("transmitting {} as {}".format(payload, rev_whiten_pl)) - print("payload length (whitened) =", len(rev_whiten_pl)) + rev_whiten_pl = reverse_bits(_ble_whitening(payload, self._chan + 37)) + print("transmitting \n{}\nas\n{}".format(payload, rev_whiten_pl)) self._device.send(rev_whiten_pl) From ccd8be07a9ee8d107aefd494a5ac31e5b4b399d5 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sat, 26 Sep 2020 22:09:38 -0700 Subject: [PATCH 081/127] crc works --- circuitpython_nrf24l01/fake_ble.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index a6bb96b..9aa9c80 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -135,7 +135,7 @@ def _ble_whitening(data, ble_channel): return data -def crc24_ble(data, deg_poly=0x00065B, init_val=0x555555): +def crc24_ble(data, deg_poly=0x65B, init_val=0x555555): """Calculates a checksum of various sized buffers :param bytearray data: This `bytearray` of data to be uncorrupted. @@ -147,15 +147,14 @@ def crc24_ble(data, deg_poly=0x00065B, init_val=0x555555): """ crc = init_val for byte in data: - crc ^= (byte << 16) + crc ^= (swap_bits(byte) << 16) for _ in range(8): if crc & 0x800000: crc = (crc << 1) ^ deg_poly else: - crc = (crc << 1) + crc <<= 1 crc &= 0xFFFFFF - crc = (crc).to_bytes(3, "big") - return crc + return reverse_bits((crc).to_bytes(3, "big")) BLE_FREQ = (2, 26, 80) """ BLE channel number is different from the nRF channel number. @@ -241,9 +240,9 @@ def advertise(self, buf, data_type=0xFF): broadcast, then the 'name' attribute should be set prior to calling `advertise()`. """ - if not buf or len(buf) > (21 - len(self.name)): + if len(buf) > (21 - len(self.name)): raise ValueError( - "buf must have a length in range [1, {}]".format(21 - len(self.name)) + "buf must have a length less than {}".format(21 - len(self.name)) ) mac = b"\x11\x22\x33\x44\x55\x66" # a bogus MAC address # payload will have at least 2 containers: From b3a839cb561d1455bdca3ec39a86ebc77ea875ae Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 27 Sep 2020 00:13:32 -0700 Subject: [PATCH 082/127] fix bug when padding static payloads --- circuitpython_nrf24l01/rf24.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 6b9e115..f88350d 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -699,7 +699,7 @@ def write(self, buf, ask_no_ack=False): time.sleep(0.00016) if not bool((self._dyn_pl & 1) and (self._features & 4)): if len(buf) < self._pl_len[0]: - buf += b"\x00" * self._pl_len[0] - len(buf) + buf += b"\x00" * (self._pl_len[0] - len(buf)) elif len(buf) > self._pl_len[0]: buf = buf[: self._pl_len[0]] if ask_no_ack: From 6d8fe917d631080c25c6553e86c6519579fc7fc0 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sun, 27 Sep 2020 01:33:57 -0700 Subject: [PATCH 083/127] THIS WORKS!!!!!!!!!! --- circuitpython_nrf24l01/fake_ble.py | 57 +++++++++++++++++++----------- circuitpython_nrf24l01/rf24.py | 2 +- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index 9aa9c80..543a831 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -49,6 +49,7 @@ other event, "on data fail", is ignored because it will never get thrown with "auto_ack" off. However the interrupt settings can be modified AFTER instantiation """ +from os import urandom def swap_bits(original): @@ -111,27 +112,28 @@ def make_payload(mac, name, payload): # 6 byte random mac address # 21 bytes of containerized data including descriptor and name # 3 bytes for CRC24 - pl_size = 11 + (len(name) + 2 if name is not None else 0) + len(payload) + pl_size = 9 + (len(name) + 2 if name is not None else 0) + len(payload) buf = bytes([0x42, pl_size]) + mac # header buf += add_chunk(1, b"\x05") # device descriptor if name is not None: buf += add_chunk(0x09, name) # device name - return buf + payload + return buf + payload + crc24_ble(buf + payload) -def _ble_whitening(data, ble_channel): +def ble_whitening(data, ble_channel): """for "whiten"ing the BLE packet data according to expected parameters""" data = bytearray(data) - coef = reverse_bits(bytes([ble_channel]))[0] & 2 + coef = ble_channel | 0x40 for i, byte in enumerate(data): # for every byte + res = 0 mask = 1 for _ in range(8): - if coef & 0x80: - coef ^= 0x11 + if coef & 1: + coef ^= 0x88 byte ^= mask mask <<= 1 - coef <<= 1 - data[i] = byte + coef >>= 1 + data[i] = byte ^ res return data @@ -180,9 +182,10 @@ def __init__(self, nrf, name=None): self._chan = 0 self._ble_name = None self.name = name + self.mac = None with self: - self._device.open_rx_pipe(0, b"\x6B\x7D\x91\x71") - self._device.open_tx_pipe(b"\x6B\x7D\x91\x71") + self._device.open_rx_pipe(0, b"\x71\x91\x7D\x6B") + self._device.open_tx_pipe(b"\x71\x91\x7D\x6B") # b'\x8E\x89\xBE\xD6' = proper address for BLE advertisments # with bits and bytes reversed address is b'\x6B\x7D\x91\x71' @@ -200,6 +203,26 @@ def __exit__(self, *exc): self._device.power = 0 return False + @property + def mac(self): + """This attribute returns a 6-byte buffer that is used as the + arbitrary mac address of the BLE device being emulated. You can set + this attribute using a 6-byte `int` or `bytearray`. If this is set to + `None`, then a random 6-byte address is generated. + """ + return self._mac + + @mac.setter + def mac(self, address): + if address is None: + self._mac = urandom(6) + if isinstance(address, int): # assume its a 6-byte int + self._mac = (address).to_bytes(6, "little") + elif isinstance(address, (bytearray, bytes)): + self._mac = address + if len(self._mac) < 6: + self._mac += urandom(6 - len(self._mac)) + @property def name(self): """The broadcasted BLE name of the nRF24L01. This is not required. In @@ -244,15 +267,9 @@ def advertise(self, buf, data_type=0xFF): raise ValueError( "buf must have a length less than {}".format(21 - len(self.name)) ) - mac = b"\x11\x22\x33\x44\x55\x66" # a bogus MAC address - # payload will have at least 2 containers: - # 3 bytes of flags (required for BLE discoverable), & at least (1+2) byte of data - # b"\x02\x01\x06" # BLE flags for discoverability and non-pairable etc - payload = make_payload(mac, self.name, add_chunk(data_type, buf)) - # crc is generated from b'\x55\x55\x55' about everything except itself - payload += crc24_ble(payload) - self.hop_channel() # cycle to next BLE channel per specs - # the whiten_coef value we need is the BLE channel (37,38, or 39) left shifted one - rev_whiten_pl = reverse_bits(_ble_whitening(payload, self._chan + 37)) + payload = make_payload(self._mac, self.name, (add_chunk(data_type, buf) if buf else b'')) + self.hop_channel() + # self._device.payload_length = [len(payload)] * 2 + rev_whiten_pl = reverse_bits(ble_whitening(payload, self._chan + 37)) print("transmitting \n{}\nas\n{}".format(payload, rev_whiten_pl)) self._device.send(rev_whiten_pl) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 6b9e115..f88350d 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -699,7 +699,7 @@ def write(self, buf, ask_no_ack=False): time.sleep(0.00016) if not bool((self._dyn_pl & 1) and (self._features & 4)): if len(buf) < self._pl_len[0]: - buf += b"\x00" * self._pl_len[0] - len(buf) + buf += b"\x00" * (self._pl_len[0] - len(buf)) elif len(buf) > self._pl_len[0]: buf = buf[: self._pl_len[0]] if ask_no_ack: From a8b411a99244450ba5cd5ddb5f07639a4e9638b0 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sun, 27 Sep 2020 03:49:17 -0700 Subject: [PATCH 084/127] updated docs --- circuitpython_nrf24l01/fake_ble.py | 229 +++++++++++++++++------------ docs/_static/darkness.css | 6 + docs/api.rst | 6 +- docs/examples.rst | 9 ++ examples/nrf24l01_fake_ble_test.py | 28 +++- 5 files changed, 169 insertions(+), 109 deletions(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index 543a831..b8c160c 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -20,40 +20,57 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ -This module uses the RF24 module to make the nRF24L01 imitate a Bluetooth-Low-Emissions (BLE) -beacon. A BLE beacon can send (referred to as advertise) data to any BLE compatible device -(ie smart devices with Bluetooth 4.0 or later) that is listening. - -Original research was done by `Dmitry Grinberg and his write-up (including C source code) can be -found here `_ As -this technique can prove invaluable in certain project designs, the code here is simply ported to -work on CircuitPython. - -.. important:: Because the nRF24L01 wasn't designed for BLE advertising, it has some limitations - that helps to be aware of. - - 1. the maximum payload length is shortened to 19 bytes - 2. the channels that BLE use are limited to the following three: 2.402 GHz, 2.426 GHz, - and 2.480 GHz - 3. CRC is disabled in the nRF24L01 firmware as BLE requires 3 bytes and nRF24L01 only handles - 2. Thus we have augmented the required 3 bytes of CRC into the payload. - 4. address length of BLE packet only uses 4 bytes, so we have set that acccordingly. - 5. the automatic acknowledgment feature of the nRF24L01 is useless when tranmitting to BLE - devices, thus it is disabled as well as automatic re-transmit and custom ACK payloads - (both depend on the automatic acknowledgments feature) - 6. the dynamic payloads feature of the nRF24L01 isn't compatible with BLE specifications. - Thus we have disabled it in the nRF24L01 firmware and incorporated dynamic payloads - properly into he payload data - 7. BLE specifications only allow using 1 Mbps RF data rate, so that too has been hard coded. - 8. both the "on data sent" & "on data ready" events control the interrupt (IRQ) pin; the - other event, "on data fail", is ignored because it will never get thrown with "auto_ack" - off. However the interrupt settings can be modified AFTER instantiation +This module uses the `RF24` class to make the nRF24L01 imitate a +Bluetooth-Low-Emissions (BLE) beacon. A BLE beacon can send (referred to as +advertise) data to any BLE compatible device (ie smart devices with Bluetooth +4.0 or later) that is listening. + +Original research was done by `Dmitry Grinberg and his write-up (including C +source code) can be found here +`_ +As this technique can prove invaluable in certain project designs, the code +here is simply ported to work on CircuitPython. + +.. important:: Because the nRF24L01 wasn't designed for BLE advertising, it + has some limitations that helps to be aware of. + + 1. the maximum payload length is shortened to 21 bytes (when not + broadcasting a device + :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.name`). + 2. the channels that BLE use are limited to the following three: 2.402 + GHz, 2.426 GHz, and 2.480 GHz + 3. :py:attr:`~circuitpython_nrf24l01.rf24.RF24.crc` is disabled in the + nRF24L01 firmware as BLE requires 3 bytes (`crc24_ble()`) and nRF24L01 + only handles a maximum of 2. Thus, we have appended the required 3 + bytes of CRC24 into the payload. + 4. :py:attr:`~circuitpython_nrf24l01.rf24.RF24.address_length` of BLE + packet only uses 4 bytes, so we have set that accordingly. + 5. The :py:attr:`~circuitpython_nrf24l01.rf24.RF24.auto_ack` (automatic + acknowledgment) feature of the nRF24L01 is useless when tranmitting to + BLE devices, thus it is disabled as well as automatic re-transmit + (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.arc`) and custom ACK + payloads (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.ack`) which both + depend on the automatic acknowledgments feature. + 6. The :py:attr:`~circuitpython_nrf24l01.rf24.RF24.dynamic_payloads` + feature of the nRF24L01 isn't compatible with BLE specifications. Thus, + we have disabled it. + 7. BLE specifications only allow using 1 Mbps RF + :py:attr:`~circuitpython_nrf24l01.rf24.RF24.data_rate`, so that too has + been hard coded. + 8. Only the "on data sent" + (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_ds`) & "on data ready" + (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr`) events will have + an effect on the interrupt (IRQ) pin. The "on data fail" + (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_df`), is never + triggered because + :py:attr:`~circuitpython_nrf24l01.rf24.RF24.auto_ack` feature is + disabled. """ from os import urandom def swap_bits(original): - """reverses the bit order into LSbit to MSBit in a single byte. + """reverses the bit order for a single byte. :returns: An `int` containing the byte whose bits go from LSBit to MSBit @@ -71,12 +88,12 @@ def swap_bits(original): def reverse_bits(original): - """reverses the bit the byte order into LSB to MSB + """reverses the bit order into LSBit to MSBit. :returns: A `bytearray` whose bytes still go from MSByte to LSByte, but each byte's bits go from LSBit to MSBit. - :param bytearray original: The original buffer whose bits are to be + :param bytearray,bytes original: The original buffer whose bits are to be reversed. """ length = len(original) - 1 @@ -86,66 +103,49 @@ def reverse_bits(original): return ret -def reverse_bytes(original): - """Reverses the byte order for all bytes passed to the ``original`` - `bytearray`""" - result = bytearray(3) - for i, byte in enumerate(original): - result[len(original) - 1 - i] = byte - return result +# This is not actually used but it could be useful... +# def reverse_bytes(original): +# """Reverses the byte order for all bytes passed to the ``original`` +# `bytearray`.""" +# result = bytearray(3) +# for i, byte in enumerate(original): +# result[len(original) - 1 - i] = byte +# return result + + +def chunk(data_type, buf): + """containerize a chunk of data according to BLE specifications. + This chunk makes up a part of the advertising payload. + :param int data_type: the type of data contained in the chunk. This is a + predefined number according to BLE specifications. + :param bytearray,bytes buf: The actual data contained in the chunk. -def add_chunk(data_type, buf): - """containerize a chunk of data according to BLE specs. - This chunk makes up a part of the advertising payload.""" + .. important:: This function is called internally, but can also be used + to containerize multiple types of data in a single payload. + Broadcasting multiple types of data may require the `FakeBLE.name` + be set to `None` for reasons about the payload size limitations due + to using a nRf24L01 as a BLE beacon. + """ # container = 1 Byte info about container length # 1 Byte info describing datatype # X bytes holding the data return bytearray([len(buf) + 1, data_type & 0xFF]) + buf -def make_payload(mac, name, payload): - """assemble the entire packet to be transmitted as a payload.""" - # data is ordered like so: - # 1 byte PDU type (always 0x42) - # 1 byte payload size - # 6 byte random mac address - # 21 bytes of containerized data including descriptor and name - # 3 bytes for CRC24 - pl_size = 9 + (len(name) + 2 if name is not None else 0) + len(payload) - buf = bytes([0x42, pl_size]) + mac # header - buf += add_chunk(1, b"\x05") # device descriptor - if name is not None: - buf += add_chunk(0x09, name) # device name - return buf + payload + crc24_ble(buf + payload) - - -def ble_whitening(data, ble_channel): - """for "whiten"ing the BLE packet data according to expected parameters""" - data = bytearray(data) - coef = ble_channel | 0x40 - for i, byte in enumerate(data): # for every byte - res = 0 - mask = 1 - for _ in range(8): - if coef & 1: - coef ^= 0x88 - byte ^= mask - mask <<= 1 - coef >>= 1 - data[i] = byte ^ res - return data - - def crc24_ble(data, deg_poly=0x65B, init_val=0x555555): - """Calculates a checksum of various sized buffers + """Calculates a checksum of various sized buffers. this is exposed for + convenience. :param bytearray data: This `bytearray` of data to be uncorrupted. - :param int deg_poly: A preset "degree polynomial" in which each bit represents a degree who's - coefficient is 1. - :param int init_val: This will be the initial value that the checksum will use while shifting in - the buffer data. - :returns: A 24-bit `bytearray` representing the checksum of the data. + :param int deg_poly: A preset "degree polynomial" in which each bit + represents a degree who's coefficient is 1. BLE specfications require + ``0x00065b`` (default value). + :param int init_val: This will be the initial value that the checksum + will use while shifting in the buffer data. BLE specfications require + ``0x555555`` (default value). + :returns: A 24-bit `bytearray` representing the checksum of the data (in + proper little endian). """ crc = init_val for byte in data: @@ -161,9 +161,10 @@ def crc24_ble(data, deg_poly=0x65B, init_val=0x555555): BLE_FREQ = (2, 26, 80) """ BLE channel number is different from the nRF channel number. These are the predefined channels used. -- nRF channel 2 == BLE channel 37 -- nRF channel 26 == BLE channel 38 -- nRF channel 80 == BLE channel 39 + +* nRF channel 2 == BLE channel 37 +* nRF channel 26 == BLE channel 38 +* nRF channel 80 == BLE channel 39 """ @@ -184,10 +185,7 @@ def __init__(self, nrf, name=None): self.name = name self.mac = None with self: - self._device.open_rx_pipe(0, b"\x71\x91\x7D\x6B") - self._device.open_tx_pipe(b"\x71\x91\x7D\x6B") - # b'\x8E\x89\xBE\xD6' = proper address for BLE advertisments - # with bits and bytes reversed address is b'\x6B\x7D\x91\x71' + self._device.flush_rx() def __enter__(self): self._device.address_length = 4 @@ -196,7 +194,11 @@ def __enter__(self): self._device.crc = 0 self._device.arc = 0 self._device.power = 1 - self._device.payload_length = 32 + self._device.payload_length = [32, 32] + # b"\x8E\x89\xBE\xD6" = proper address for BLE advertisments + # with bits reversed address is b"\x71\x91\x7D\x6B" + self._device.open_rx_pipe(0, b"\x71\x91\x7D\x6B") + self._device.open_tx_pipe(b"\x71\x91\x7D\x6B") return self def __exit__(self, *exc): @@ -247,11 +249,45 @@ def hop_channel(self): self._chan = 0 self._device.channel = BLE_FREQ[self._chan] - def advertise(self, buf, data_type=0xFF): - """This blocking function is used to transmit a payload. - - :returns: Nothing as every transmission will register as a success under these required - settings. + def _whiten(self, data): + """for whitening the BLE packet data so there's no long repeatition + of bits.""" + data = bytearray(data) + coef = (self._chan + 37) | 0x40 + for i, byte in enumerate(data): + res = 0 + mask = 1 + for _ in range(8): + if coef & 1: + coef ^= 0x88 + byte ^= mask + mask <<= 1 + coef >>= 1 + data[i] = byte ^ res + return data + + def _make_payload(self, payload): + """assemble the entire packet to be transmitted as a payload.""" + # data is ordered like so: + # 1 byte PDU type (always 0x42) + # 1 byte payload size + # 6 byte random mac address + # 21 bytes of containerized data including descriptor and name + # 3 bytes for CRC24 + pl_size = 9 + len(payload) + if self.name is not None: + pl_size += len(self.name) + 2 + buf = bytes([0x42, pl_size]) + self.mac # header + buf += chunk(1, b"\x05") # device descriptor + if self.name is not None: + buf += chunk(0x09, self.name) # device name + return buf + payload + crc24_ble(buf + payload) + + def advertise(self, buf=b"", data_type=0xFF): + """This function is used to broadcast a payload. + + :returns: Nothing as every transmission will register as a success + under the required settings for BLE beacons. :param bytearray buf: The payload to transmit. This bytearray must have a length greater than 0 and less than 20, otherwise a `ValueError` @@ -267,9 +303,8 @@ def advertise(self, buf, data_type=0xFF): raise ValueError( "buf must have a length less than {}".format(21 - len(self.name)) ) - payload = make_payload(self._mac, self.name, (add_chunk(data_type, buf) if buf else b'')) + payload = self._make_payload(chunk(data_type, buf) if buf else b'') self.hop_channel() - # self._device.payload_length = [len(payload)] * 2 - rev_whiten_pl = reverse_bits(ble_whitening(payload, self._chan + 37)) - print("transmitting \n{}\nas\n{}".format(payload, rev_whiten_pl)) + rev_whiten_pl = reverse_bits(self._whiten(payload)) + # print("transmitting \n{}\nas\n{}".format(payload, rev_whiten_pl)) self._device.send(rev_whiten_pl) diff --git a/docs/_static/darkness.css b/docs/_static/darkness.css index 89a5052..ea72432 100644 --- a/docs/_static/darkness.css +++ b/docs/_static/darkness.css @@ -319,3 +319,9 @@ html.writer-html4 .rst-content dl:not(.docutils)>dt, html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt { background: #2e363c; } + +html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt, +html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt { + background:#2e363c; + color:#2980b1; +} diff --git a/docs/api.rst b/docs/api.rst index a9e3944..d2c957f 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,7 +1,5 @@ -.. currentmodule:: circuitpython_nrf24l01.rf24 - .. |irq note| replace:: parameter as `True` to `clear_status_flags()` and reset this. As this is a virtual representation of the interrupt event, this attribute will always be updated despite what the actual IRQ pin is configured to do about this event. @@ -1015,8 +1013,8 @@ is_lna_enabled feature is specific to certain nRF24L01-based circuits. Check with your module's manufacturer to see is it can toggle the Low Noise Amplifier feature. -Fake BLE -============== +fake_ble module +=============== .. automodule:: circuitpython_nrf24l01.fake_ble :members: diff --git a/docs/examples.rst b/docs/examples.rst index b044e2b..a45a53d 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -52,3 +52,12 @@ This test is meant to prove compatibility with the popular Arduino library for t .. literalinclude:: ../examples/nrf24l01_2arduino_handling_data.py :caption: examples/nrf24l01_2arduino_handling_data.py :linenos: + +Fake BLE Example +---------------- + +This is a test to show how to use the nRF24L01 as a BLE advertising beacon. + +.. literalinclude:: ../examples/nrf24l01_fake_ble_test.py + :caption: examples/nrf24l01_fake_ble_test.py + :linenos: diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index 05f6578..0706c9f 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -2,7 +2,7 @@ This example of using the nRF24L01 as a 'fake' Buetooth Beacon """ import time -import struct +# import struct import board import digitalio as dio from circuitpython_nrf24l01.rf24 import RF24 @@ -17,20 +17,32 @@ spi = board.SPI() # init spi bus object # initialize the nRF24L01 on the spi bus object as a BLE radio using -nrf = FakeBLE(RF24(spi, csn, ce), name=b'nRF24') +nrf = FakeBLE(RF24(spi, csn, ce)) -# the name parameter is going to be its braodcasted BLE name +# the name parameter is going to be its broadcasted BLE device name # this can be changed at any time using the attribute nrf.name = b'RFtest' -def master(count=15): - """Sends out an advertisement once a second for a default count of 15""" +# you can optionally set the arbitrary MAC address to be used as the +# BLE device's MAC address. This is randomized upon instantiation. +# nrf.mac = b'\x19\x12\x14\x26\x09\xE0' + +def master(count=100): + """Sends out an advertisement once a second for a default count of 100""" + # using the "with" statement is highly recommended if the nRF24L01 is + # to be used for more than a BLE configuration with nrf as ble: for i in range(count): # advertise data this many times if (count - i) % 5 == 0 or (count - i) < 5: print( count - i, 'advertisment{}left to go!'.format('s ' if count - i - 1 else ' ')) - # pack into bytearray using struct.pack() - ble.advertise(struct.pack('i', count)) # 'i' = 4 byte integer + # broadcast only the device name and MAC address + ble.advertise() # channel is automatically managed by send() per BLE specs - time.sleep(1) # wait till next broadcast + time.sleep(0.2) # wait till next broadcast + + +print("""\ + nRF24L01 fake BLE beacon test.\n\ + Run master() to broadcast""") + # Run slave() to listen\n\ From 33830062215fcc283c7f94a071d9cb2a93b2f74b Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sun, 27 Sep 2020 04:20:27 -0700 Subject: [PATCH 085/127] fix ref in docs --- circuitpython_nrf24l01/fake_ble.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index b8c160c..22751e0 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -40,7 +40,8 @@ 2. the channels that BLE use are limited to the following three: 2.402 GHz, 2.426 GHz, and 2.480 GHz 3. :py:attr:`~circuitpython_nrf24l01.rf24.RF24.crc` is disabled in the - nRF24L01 firmware as BLE requires 3 bytes (`crc24_ble()`) and nRF24L01 + nRF24L01 firmware as BLE requires 3 bytes + (:py:func:`~circuitpython_nrf24l01.fake_ble.crc24_ble()`) and nRF24L01 only handles a maximum of 2. Thus, we have appended the required 3 bytes of CRC24 into the payload. 4. :py:attr:`~circuitpython_nrf24l01.rf24.RF24.address_length` of BLE From a88f2fee8d8d2b7b520f502dc6482a3af52323f6 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 27 Sep 2020 04:38:17 -0700 Subject: [PATCH 086/127] Update README.rst --- README.rst | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 9f91482..83de0b5 100644 --- a/README.rst +++ b/README.rst @@ -53,6 +53,7 @@ Features currently supported * adjust the nRF24L01's power amplifier level (0, -6, -12, or -18 dBm) * adjust the nRF24L01's RF data rate (250Kbps is buggy due to hardware design, but 1Mbps and 2Mbps are reliable) * a nRF24L01 driven by this library can communicate with a nRF24L01 on an Arduino driven by the `TMRh20 RF24 library `_. See the nrf24l01_2arduino_handling_data.py code in the `examples folder of this library's repository `_ +* fake BLE module for sending BLE beacon advertisments from the nRF24L01 as outlined by `Dmitry Grinberg in his write-up (including C source code) `_. Features currently unsupported ============================== @@ -213,11 +214,6 @@ Future Project Ideas/Additions resistor and a capacitor) * network linking layer, maybe something like `TMRh20's RF24Network `_ - * add a fake BLE module for sending BLE beacon advertisments from the nRF24L01 as outlined - by `Dmitry Grinberg in his write-up (including C source code) - `_. - We've started developing this, but fell short of success in `the BLEfake branch of this - library's repository `_ * implement the Gazelle-based protocol used by the BBC micro-bit (`makecode.com's radio blocks `_). From 384eb642371203feeca6759fc5e6b041d9ea7402 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sun, 27 Sep 2020 12:25:46 -0700 Subject: [PATCH 087/127] fix fake_ble docs not showing completely --- README.rst | 31 ++++++------ docs/api.rst | 1 + docs/conf.py | 130 +++++++++++++++++++++++++++++++-------------------- 3 files changed, 96 insertions(+), 66 deletions(-) diff --git a/README.rst b/README.rst index 83de0b5..e806e48 100644 --- a/README.rst +++ b/README.rst @@ -1,24 +1,25 @@ +.. only:: html -.. image:: https://readthedocs.org/projects/circuitpython-nrf24l01/badge/?version=stable - :target: https://circuitpython-nrf24l01.readthedocs.io/en/stable/ - :alt: Documentation Status + .. image:: https://readthedocs.org/projects/circuitpython-nrf24l01/badge/?version=stable + :target: https://circuitpython-nrf24l01.readthedocs.io/en/stable/ + :alt: Documentation Status -.. image:: https://github.com/2bndy5/CircuitPython_nRF24L01/workflows/Build%20CI/badge.svg - :target: https://github.com/2bndy5/CircuitPython_nRF24L01/actions?query=workflow%3A%22Build+CI%22 - :alt: Build Status + .. image:: https://github.com/2bndy5/CircuitPython_nRF24L01/workflows/Build%20CI/badge.svg + :target: https://github.com/2bndy5/CircuitPython_nRF24L01/actions?query=workflow%3A%22Build+CI%22 + :alt: Build Status -.. image:: https://img.shields.io/github/commits-since/2bndy5/CircuitPython_nRF24L01/latest?&style=plastic - :alt: GitHub commits since latest release (by date) - :target: https://github.com/2bndy5/CircuitPython_nRF24L01/commits/master + .. image:: https://img.shields.io/github/commits-since/2bndy5/CircuitPython_nRF24L01/latest?&style=plastic + :alt: GitHub commits since latest release (by date) + :target: https://github.com/2bndy5/CircuitPython_nRF24L01/commits/master -.. image:: https://img.shields.io/pypi/v/circuitpython-nrf24l01.svg - :alt: latest version on PyPI - :target: https://pypi.python.org/pypi/circuitpython-nrf24l01 + .. image:: https://img.shields.io/pypi/v/circuitpython-nrf24l01.svg + :alt: latest version on PyPI + :target: https://pypi.python.org/pypi/circuitpython-nrf24l01 -.. image:: https://pepy.tech/badge/circuitpython-nrf24l01?label=pypi%20downloads&logo=python - :alt: Total PyPI downloads - :target: https://pepy.tech/project/circuitpython-nrf24l01 + .. image:: https://pepy.tech/badge/circuitpython-nrf24l01?label=pypi%20downloads&logo=python + :alt: Total PyPI downloads + :target: https://pepy.tech/project/circuitpython-nrf24l01 Introduction ============ diff --git a/docs/api.rst b/docs/api.rst index 0cc4c64..b8769ca 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1017,3 +1017,4 @@ fake_ble module =============== .. automodule:: circuitpython_nrf24l01.fake_ble + :members: diff --git a/docs/conf.py b/docs/conf.py index d73afd3..afce559 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,7 +2,8 @@ import os import sys -sys.path.insert(0, os.path.abspath('..')) + +sys.path.insert(0, os.path.abspath("..")) # -- General configuration ------------------------------------------------ @@ -10,11 +11,12 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.napoleon', - 'sphinx.ext.todo', - 'sphinx.ext.viewcode', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.napoleon", + "sphinx.ext.todo", + "sphinx.ext.viewcode", + # "rst2pdf.pdfbuilder", # for pdf builder support ] # TODO: Please Read! @@ -22,31 +24,38 @@ # digitalio, micropython and busio. List the modules you use. Without it, the # autodoc module docs will fail to generate with a warning. autodoc_mock_imports = ["digitalio", "busio"] -autodoc_member_order = 'bysource' - -intersphinx_mapping = {'python': ('https://docs.python.org/3.4', None),'BusDevice': ('https://circuitpython.readthedocs.io/projects/busdevice/en/latest/', None),'CircuitPython': ('https://circuitpython.readthedocs.io/en/latest/', None)} +autodoc_member_order = "bysource" + +intersphinx_mapping = { + "python": ("https://docs.python.org/3.4", None), + "BusDevice": ( + "https://circuitpython.readthedocs.io/projects/busdevice/en/latest/", + None, + ), + "CircuitPython": ("https://circuitpython.readthedocs.io/en/latest/", None), +} # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'nRF24L01 Library' -copyright = u'2019 Brendan Doherty' -author = u'Brendan Doherty' +project = u"nRF24L01 Library" +copyright = u"2019 Brendan Doherty" +author = u"Brendan Doherty" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = u'1.0' +version = u"1.0" # The full version, including alpha/beta/rc tags. -release = u'1.0' +release = u"1.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -58,7 +67,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.env', 'CODE_OF_CONDUCT.md'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".env", "CODE_OF_CONDUCT.md"] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -70,7 +79,7 @@ add_function_parentheses = True # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -85,66 +94,69 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +on_rtd = os.environ.get("READTHEDOCS", None) == "True" if not on_rtd: # only import and set the theme if we're building docs locally try: import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' - html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), '.'] + + html_theme = "sphinx_rtd_theme" + html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] except: - html_theme = 'default' - html_theme_path = ['.'] + html_theme = "default" + html_theme_path = ["."] else: - html_theme_path = ['.'] + html_theme_path = ["."] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # These paths are either relative to html_static_path # or fully qualified paths (eg. https://...) html_css_files = [ - 'darkness.css', + "darkness.css", ] # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # -html_favicon = '_static/favicon.ico' +html_favicon = "_static/favicon.ico" # Output file base name for HTML help builder. -htmlhelp_basename = 'nRF24L01_Library_doc' +htmlhelp_basename = "nRF24L01_Library_doc" # html_copy_source = True # html_show_sourcelink = True # -- Options for LaTeX output --------------------------------------------- latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'nRF24L01Library.tex', u'nRF24L01 Library Documentation', - author, 'manual'), + ( + master_doc, + "nRF24L01Library.tex", + u"nRF24L01 Library Documentation", + author, + "manual", + ), ] # -- Options for manual page output --------------------------------------- @@ -152,8 +164,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'nRF24L01library', u'nRF24L01 Library Documentation', - [author], 1) + (master_doc, "nRF24L01library", u"nRF24L01 Library Documentation", [author], 1) ] # -- Options for Texinfo output ------------------------------------------- @@ -162,7 +173,24 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'nRF24L01Library', u' nRF24L01 Library Documentation', - author, 'nRF24L01Library', 'nRF24L01 on CircuitPython devices.', - 'Wireless'), + ( + master_doc, + "nRF24L01Library", + u" nRF24L01 Library Documentation", + author, + "nRF24L01Library", + "nRF24L01 on CircuitPython devices.", + "Wireless", + ), ] + +# ---Options for pdf output----------------------------------------- + +# pdf_documents = [ +# ( +# "index", +# u"CircuitPython-nRF24L01", +# u"CircuitPython-nRF24L01 library documentation", +# u"Brendan Doherty", +# ), +# ] From af47fd900f6fe26fbffe0ae361bdac4df6e1118f Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sun, 27 Sep 2020 12:28:07 -0700 Subject: [PATCH 088/127] update readme --- README.rst | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/README.rst b/README.rst index e806e48..62032fa 100644 --- a/README.rst +++ b/README.rst @@ -1,25 +1,25 @@ -.. only:: html +.. .. only:: html - .. image:: https://readthedocs.org/projects/circuitpython-nrf24l01/badge/?version=stable - :target: https://circuitpython-nrf24l01.readthedocs.io/en/stable/ - :alt: Documentation Status +.. image:: https://readthedocs.org/projects/circuitpython-nrf24l01/badge/?version=stable + :target: https://circuitpython-nrf24l01.readthedocs.io/en/stable/ + :alt: Documentation Status - .. image:: https://github.com/2bndy5/CircuitPython_nRF24L01/workflows/Build%20CI/badge.svg - :target: https://github.com/2bndy5/CircuitPython_nRF24L01/actions?query=workflow%3A%22Build+CI%22 - :alt: Build Status +.. image:: https://github.com/2bndy5/CircuitPython_nRF24L01/workflows/Build%20CI/badge.svg + :target: https://github.com/2bndy5/CircuitPython_nRF24L01/actions?query=workflow%3A%22Build+CI%22 + :alt: Build Status - .. image:: https://img.shields.io/github/commits-since/2bndy5/CircuitPython_nRF24L01/latest?&style=plastic - :alt: GitHub commits since latest release (by date) - :target: https://github.com/2bndy5/CircuitPython_nRF24L01/commits/master +.. image:: https://img.shields.io/github/commits-since/2bndy5/CircuitPython_nRF24L01/latest?&style=plastic + :alt: GitHub commits since latest release (by date) + :target: https://github.com/2bndy5/CircuitPython_nRF24L01/commits/master - .. image:: https://img.shields.io/pypi/v/circuitpython-nrf24l01.svg - :alt: latest version on PyPI - :target: https://pypi.python.org/pypi/circuitpython-nrf24l01 +.. image:: https://img.shields.io/pypi/v/circuitpython-nrf24l01.svg + :alt: latest version on PyPI + :target: https://pypi.python.org/pypi/circuitpython-nrf24l01 - .. image:: https://pepy.tech/badge/circuitpython-nrf24l01?label=pypi%20downloads&logo=python - :alt: Total PyPI downloads - :target: https://pepy.tech/project/circuitpython-nrf24l01 +.. image:: https://pepy.tech/badge/circuitpython-nrf24l01?label=pypi%20downloads&logo=python + :alt: Total PyPI downloads + :target: https://pepy.tech/project/circuitpython-nrf24l01 Introduction ============ From 7b6997e6d688954430844f0ca30c75f71585ce3c Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sun, 27 Sep 2020 12:58:32 -0700 Subject: [PATCH 089/127] add flag for broadcasting to iPhone --- circuitpython_nrf24l01/fake_ble.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index 22751e0..fb14dcd 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -185,6 +185,7 @@ def __init__(self, nrf, name=None): self._ble_name = None self.name = name self.mac = None + self._to_iphone = 0x40 with self: self._device.flush_rx() @@ -206,6 +207,18 @@ def __exit__(self, *exc): self._device.power = 0 return False + @property + def to_iphone(self): + """A `bool` to specify if advertisements should be compatible with + the iPhone. A value of `False` should still be compatible with other + Apple devices. Testing with this attribute as `False` showed + compatibility with a Mac desktop.""" + return self._to_iphone == 0x40 + + @to_iphone.setter + def to_iphone(self, enable): + self._to_iphone = 0x40 if enable else 0x42 + @property def mac(self): """This attribute returns a 6-byte buffer that is used as the @@ -278,7 +291,7 @@ def _make_payload(self, payload): pl_size = 9 + len(payload) if self.name is not None: pl_size += len(self.name) + 2 - buf = bytes([0x42, pl_size]) + self.mac # header + buf = bytes([self._to_iphone, pl_size]) + self.mac # header buf += chunk(1, b"\x05") # device descriptor if self.name is not None: buf += chunk(0x09, self.name) # device name From 72c4e6c334a87db6fc0443ebb007e51dbc98c90c Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sun, 27 Sep 2020 14:12:11 -0700 Subject: [PATCH 090/127] expose whitening() for future research --- circuitpython_nrf24l01/fake_ble.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index fb14dcd..659cbb1 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -263,8 +263,8 @@ def hop_channel(self): self._chan = 0 self._device.channel = BLE_FREQ[self._chan] - def _whiten(self, data): - """for whitening the BLE packet data so there's no long repeatition + def whiten(self, data): + """Whitening the BLE packet data so there's no long repeatition of bits.""" data = bytearray(data) coef = (self._chan + 37) | 0x40 @@ -319,6 +319,6 @@ def advertise(self, buf=b"", data_type=0xFF): ) payload = self._make_payload(chunk(data_type, buf) if buf else b'') self.hop_channel() - rev_whiten_pl = reverse_bits(self._whiten(payload)) + rev_whiten_pl = reverse_bits(self.whiten(payload)) # print("transmitting \n{}\nas\n{}".format(payload, rev_whiten_pl)) self._device.send(rev_whiten_pl) From 9d695457aa1ac565078418b15dc284c1b1b1d414 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sun, 27 Sep 2020 14:43:24 -0700 Subject: [PATCH 091/127] expanded docs on whiten() --- circuitpython_nrf24l01/fake_ble.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index 659cbb1..8445f1f 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -264,8 +264,20 @@ def hop_channel(self): self._device.channel = BLE_FREQ[self._chan] def whiten(self, data): - """Whitening the BLE packet data so there's no long repeatition - of bits.""" + """Whitening the BLE packet data ensures there's no long repeatition + of bits. This is done according to BLE specifications. + + :param bytearray data: The packet to whiten. + :returns: A `bytearray` of the ``data`` with the whitening algorythm + applied. + + .. warning:: This function uses the current channel being used as a + base case for the whitening coefficient. Do not call + `hop_channel()` before using this function to de-whiten received + payloads (which isn't officially supported yet). Note that + `advertise()` uses this function internally to prevent such + improper usage. + """ data = bytearray(data) coef = (self._chan + 37) | 0x40 for i, byte in enumerate(data): From 433e1a726fc8db4bfb5bb22e9113a49812245a4b Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Mon, 28 Sep 2020 06:30:56 -0700 Subject: [PATCH 092/127] handling data; needs tweaking --- circuitpython_nrf24l01/fake_ble.py | 129 +++++++++++++++++++++++------ examples/nrf24l01_fake_ble_test.py | 75 +++++++++++++---- 2 files changed, 162 insertions(+), 42 deletions(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index 8445f1f..1f97337 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -68,7 +68,7 @@ disabled. """ from os import urandom - +import struct def swap_bits(original): """reverses the bit order for a single byte. @@ -104,17 +104,16 @@ def reverse_bits(original): return ret -# This is not actually used but it could be useful... -# def reverse_bytes(original): -# """Reverses the byte order for all bytes passed to the ``original`` -# `bytearray`.""" -# result = bytearray(3) -# for i, byte in enumerate(original): -# result[len(original) - 1 - i] = byte -# return result +def reverse_bytes(original): + """Reverses the byte order for all bytes passed to the ``original`` + `bytearray` parameter.""" + result = bytearray(3) + for i, byte in enumerate(original): + result[len(original) - 1 - i] = byte + return result -def chunk(data_type, buf): +def chunk(buf, data_type=0x16): """containerize a chunk of data according to BLE specifications. This chunk makes up a part of the advertising payload. @@ -168,6 +167,57 @@ def crc24_ble(data, deg_poly=0x65B, init_val=0x555555): * nRF channel 80 == BLE channel 39 """ +SERVICE_TYPES = { + "Current Time": 0x1805, + "Glucose": 0x1808, + "Health Thermometer": 0x1809, + "Heart Rate": 0x180D, + "Battery": 0x180F, + "Blood Pressure": 0x1810, + "Location and Navigation": 0x1819, + "Weight Scale": 0x181D, + "Binary Sensor": 0x183B, +} +"""These are some of the common service types provided by the Bluetooth +SIG as part of the 16-bit UUID assigned numbers. There are many other options, +but due to the limitations of the nRF24L01 being used as a BLE beacon, these +would be the most useful.""" + + +class ServiceData: + """A helper class to package specific service data using Bluetooth SIG + defined 16-bit UUID flags to describe the data type. + + :param int type_t: The 16-bit "assigned number" defined by the + Bluetooth SIG to describe the service data. + :param bytearray data: The service data. The format of the data here + depends on the type of service being broadcast. + """ + + def __init__(self, type_t, data=b"0"): + self._type = (type_t).to_bytes(2, "little") + self._data = b"" + self.data = data + + @property + def data(self): + """The service's data. This is a `bytearray` and can have any format + which is usually constructed by python's :py:func:`struct.pack()`""" + return self._data + + @data.setter + def data(self, value): + self._data = value + + @property + def buffer(self): + """Get the representation of the instantiated object as an + immutable bytes object (read-only).""" + return bytes(self._type + self.data) + + def __len__(self): + return len(self._type) + len(self.data) + class FakeBLE: """Per the limitations of this technique, only `RF24.pa_level` is @@ -179,13 +229,13 @@ class FakeBLE: payload. """ - def __init__(self, nrf, name=None): + def __init__(self, nrf): self._device = nrf self._chan = 0 - self._ble_name = None - self.name = name - self.mac = None self._to_iphone = 0x40 + self._show_dbm = False + self._ble_name = None + self._mac = urandom(6) with self: self._device.flush_rx() @@ -204,6 +254,7 @@ def __enter__(self): return self def __exit__(self, *exc): + self._show_dbm = False self._device.power = 0 return False @@ -256,6 +307,22 @@ def name(self): def name(self, n): self._ble_name = n + @property + def show_pa_level(self): + """If this attribute is `True`, the payload will automatically include + the nRF24L01's pa_level in the advertisement. The default value of + `False` will exclude this optional information. + + .. note:: This information takes up an extra 3 bytes, and is really + only useful for some applications to calculate proximity of the + nRF24L01 transceiver. + """ + return bool(self._show_dbm) + + @show_pa_level.setter + def show_pa_level(self, enable): + self._show_dbm = bool(enable) + def hop_channel(self): """trigger an automatic change of BLE compliant channels.""" self._chan += 1 @@ -300,14 +367,20 @@ def _make_payload(self, payload): # 6 byte random mac address # 21 bytes of containerized data including descriptor and name # 3 bytes for CRC24 - pl_size = 9 + len(payload) - if self.name is not None: - pl_size += len(self.name) + 2 + name_length = (len(self.name) + 2) if self.name is not None else 0 + if len(payload) > (21 - name_length - self._show_dbm * 3): + raise ValueError( + "buf must have a length less than {}".format(21 - name_length) + ) + pl_size = 9 + len(payload) + name_length + self._show_dbm * 3 + pa_level = b"" + if self._show_dbm: + pa_level = chunk(struct.pack(">b", self._device.pa_level), 0x0A) buf = bytes([self._to_iphone, pl_size]) + self.mac # header - buf += chunk(1, b"\x05") # device descriptor - if self.name is not None: - buf += chunk(0x09, self.name) # device name - return buf + payload + crc24_ble(buf + payload) + buf += chunk(b"\x05", 1) # device descriptor + if name_length: + buf += chunk(self.name, 0x09) # device name + return buf + pa_level + payload + crc24_ble(buf + pa_level + payload) def advertise(self, buf=b"", data_type=0xFF): """This function is used to broadcast a payload. @@ -325,11 +398,15 @@ def advertise(self, buf=b"", data_type=0xFF): broadcast, then the 'name' attribute should be set prior to calling `advertise()`. """ - if len(buf) > (21 - len(self.name)): - raise ValueError( - "buf must have a length less than {}".format(21 - len(self.name)) - ) - payload = self._make_payload(chunk(data_type, buf) if buf else b'') + if not isinstance(buf, (bytearray, bytes, list, tuple)): + raise ValueError("buffer is an invalid format") + payload = b"" + if isinstance(buf, (list, tuple)): + for b in buf: + payload += b + else: + payload = chunk(buf, data_type) if buf else b"" + payload = self._make_payload(payload) self.hop_channel() rev_whiten_pl = reverse_bits(self.whiten(payload)) # print("transmitting \n{}\nas\n{}".format(payload, rev_whiten_pl)) diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index 0706c9f..04749c3 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -2,11 +2,11 @@ This example of using the nRF24L01 as a 'fake' Buetooth Beacon """ import time -# import struct +import struct import board import digitalio as dio from circuitpython_nrf24l01.rf24 import RF24 -from circuitpython_nrf24l01.fake_ble import FakeBLE +from circuitpython_nrf24l01.fake_ble import FakeBLE, chunk, SERVICE_TYPES, ServiceData # change these (digital output) pins accordingly ce = dio.DigitalInOut(board.D4) @@ -17,32 +17,75 @@ spi = board.SPI() # init spi bus object # initialize the nRF24L01 on the spi bus object as a BLE radio using -nrf = FakeBLE(RF24(spi, csn, ce)) +radio = RF24(spi, csn, ce) +nrf = FakeBLE(radio) -# the name parameter is going to be its broadcasted BLE device name +# the name parameter is going to be its braodcasted BLE name # this can be changed at any time using the attribute -nrf.name = b'RFtest' +nrf.name = b"foobar" + +nrf.to_iphone = False # you can optionally set the arbitrary MAC address to be used as the -# BLE device's MAC address. This is randomized upon instantiation. -# nrf.mac = b'\x19\x12\x14\x26\x09\xE0' +# BLE device's MAC address +nrf.mac = b'\x19\x12\x14\x26\x09\xE0' + +def _prompt(count, iterator): + if (count - iterator) % 5 == 0 or (count - iterator) < 5: + if count - iterator - 1: + print(count - iterator, "advertisments left to go!") + else: + print(count - iterator, "advertisment left to go!") -def master(count=100): - """Sends out an advertisement once a second for a default count of 100""" +def send_name(count=50): + """Sends out the device name twice a second.""" # using the "with" statement is highly recommended if the nRF24L01 is # to be used for more than a BLE configuration with nrf as ble: + nrf.name = b"nRF24L01" + nrf.show_pa_level = True for i in range(count): # advertise data this many times - if (count - i) % 5 == 0 or (count - i) < 5: - print( - count - i, 'advertisment{}left to go!'.format('s ' if count - i - 1 else ' ')) + _prompt(count, i) # broadcast only the device name and MAC address ble.advertise() - # channel is automatically managed by send() per BLE specs - time.sleep(0.2) # wait till next broadcast + # channel hoping is automatically managed by send() per BLE specs + time.sleep(0.5) # wait till next broadcast + # nrf.show_pa_level returns to false when exiting a with statement block + + +def send_temp(count=50): + """Sends out a fake temperature twice a second.""" + temperature_service = ServiceData(SERVICE_TYPES["Health Thermometer"]) + temperature_service.data = struct.pack(">f", 42.0) + nrf.name = b"nRf24L01" + for i in range(count): + _prompt(count, i) + payload = temperature_service.buffer + # broadcast a device temperature; 0x16 means service data + nrf.advertise(payload, data_type=0x16) + time.sleep(0.2) + +def send_battery_time(count=50): + """Sends out a device's battery capacity twice a second.""" + nrf.name = None + time_service = ServiceData(SERVICE_TYPES["Current Time"]) + # the time data is seconds since Jan 1, 1970 + time_service.data = struct.pack(">i", time.time()) + battery_service = ServiceData(SERVICE_TYPES["Battery"]) + # battery data is 1 unsigned byte representing a percentage + battery_service.data = struct.pack(">B", 80) + payload = [ + chunk(time_service.buffer), + chunk(battery_service.buffer) + ] + for i in range(count): + _prompt(count, i) + nrf.advertise(payload) + time.sleep(0.2) print("""\ nRF24L01 fake BLE beacon test.\n\ - Run master() to broadcast""") - # Run slave() to listen\n\ + Run send_name_pa_level() to broadcast the device name and pa_level\n\ + Run send_temperature() to broadcast a temperature\n\ + Run send_battery_time() to broadcast battery and time info""") From c3d5b10e1c005f9e8ad256cb3942d37fa7508c8d Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Tue, 29 Sep 2020 04:43:18 -0700 Subject: [PATCH 093/127] BLE capabilities are very limited; updated docs --- README.rst | 4 - circuitpython_nrf24l01/fake_ble.py | 200 +++--- docs/advanced_api.rst | 458 +++++++++++++ docs/api.rst | 1020 ---------------------------- docs/basic_api.rst | 204 ++++++ docs/ble_api.rst | 140 ++++ docs/configure_api.rst | 235 +++++++ docs/index.rst | 23 +- docs/troubleshooting.rst | 114 ++++ examples/nrf24l01_fake_ble_test.py | 102 +-- 10 files changed, 1317 insertions(+), 1183 deletions(-) create mode 100644 docs/advanced_api.rst delete mode 100644 docs/api.rst create mode 100644 docs/basic_api.rst create mode 100644 docs/ble_api.rst create mode 100644 docs/configure_api.rst create mode 100644 docs/troubleshooting.rst diff --git a/README.rst b/README.rst index 62032fa..86ce827 100644 --- a/README.rst +++ b/README.rst @@ -9,10 +9,6 @@ :target: https://github.com/2bndy5/CircuitPython_nRF24L01/actions?query=workflow%3A%22Build+CI%22 :alt: Build Status -.. image:: https://img.shields.io/github/commits-since/2bndy5/CircuitPython_nRF24L01/latest?&style=plastic - :alt: GitHub commits since latest release (by date) - :target: https://github.com/2bndy5/CircuitPython_nRF24L01/commits/master - .. image:: https://img.shields.io/pypi/v/circuitpython-nrf24l01.svg :alt: latest version on PyPI :target: https://pypi.python.org/pypi/circuitpython-nrf24l01 diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index 1f97337..d2d320a 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -19,8 +19,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -""" -This module uses the `RF24` class to make the nRF24L01 imitate a +"""This module uses the `RF24` class to make the nRF24L01 imitate a Bluetooth-Low-Emissions (BLE) beacon. A BLE beacon can send (referred to as advertise) data to any BLE compatible device (ie smart devices with Bluetooth 4.0 or later) that is listening. @@ -30,46 +29,11 @@ `_ As this technique can prove invaluable in certain project designs, the code here is simply ported to work on CircuitPython. - -.. important:: Because the nRF24L01 wasn't designed for BLE advertising, it - has some limitations that helps to be aware of. - - 1. the maximum payload length is shortened to 21 bytes (when not - broadcasting a device - :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.name`). - 2. the channels that BLE use are limited to the following three: 2.402 - GHz, 2.426 GHz, and 2.480 GHz - 3. :py:attr:`~circuitpython_nrf24l01.rf24.RF24.crc` is disabled in the - nRF24L01 firmware as BLE requires 3 bytes - (:py:func:`~circuitpython_nrf24l01.fake_ble.crc24_ble()`) and nRF24L01 - only handles a maximum of 2. Thus, we have appended the required 3 - bytes of CRC24 into the payload. - 4. :py:attr:`~circuitpython_nrf24l01.rf24.RF24.address_length` of BLE - packet only uses 4 bytes, so we have set that accordingly. - 5. The :py:attr:`~circuitpython_nrf24l01.rf24.RF24.auto_ack` (automatic - acknowledgment) feature of the nRF24L01 is useless when tranmitting to - BLE devices, thus it is disabled as well as automatic re-transmit - (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.arc`) and custom ACK - payloads (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.ack`) which both - depend on the automatic acknowledgments feature. - 6. The :py:attr:`~circuitpython_nrf24l01.rf24.RF24.dynamic_payloads` - feature of the nRF24L01 isn't compatible with BLE specifications. Thus, - we have disabled it. - 7. BLE specifications only allow using 1 Mbps RF - :py:attr:`~circuitpython_nrf24l01.rf24.RF24.data_rate`, so that too has - been hard coded. - 8. Only the "on data sent" - (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_ds`) & "on data ready" - (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr`) events will have - an effect on the interrupt (IRQ) pin. The "on data fail" - (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_df`), is never - triggered because - :py:attr:`~circuitpython_nrf24l01.rf24.RF24.auto_ack` feature is - disabled. """ from os import urandom import struct + def swap_bits(original): """reverses the bit order for a single byte. @@ -104,22 +68,13 @@ def reverse_bits(original): return ret -def reverse_bytes(original): - """Reverses the byte order for all bytes passed to the ``original`` - `bytearray` parameter.""" - result = bytearray(3) - for i, byte in enumerate(original): - result[len(original) - 1 - i] = byte - return result - - def chunk(buf, data_type=0x16): """containerize a chunk of data according to BLE specifications. This chunk makes up a part of the advertising payload. + :param bytearray,bytes buf: The actual data contained in the chunk. :param int data_type: the type of data contained in the chunk. This is a predefined number according to BLE specifications. - :param bytearray,bytes buf: The actual data contained in the chunk. .. important:: This function is called internally, but can also be used to containerize multiple types of data in a single payload. @@ -149,7 +104,7 @@ def crc24_ble(data, deg_poly=0x65B, init_val=0x555555): """ crc = init_val for byte in data: - crc ^= (swap_bits(byte) << 16) + crc ^= swap_bits(byte) << 16 for _ in range(8): if crc & 0x800000: crc = (crc << 1) ^ deg_poly @@ -158,6 +113,7 @@ def crc24_ble(data, deg_poly=0x65B, init_val=0x555555): crc &= 0xFFFFFF return reverse_bits((crc).to_bytes(3, "big")) + BLE_FREQ = (2, 26, 80) """ BLE channel number is different from the nRF channel number. These are the predefined channels used. @@ -167,66 +123,13 @@ def crc24_ble(data, deg_poly=0x65B, init_val=0x555555): * nRF channel 80 == BLE channel 39 """ -SERVICE_TYPES = { - "Current Time": 0x1805, - "Glucose": 0x1808, - "Health Thermometer": 0x1809, - "Heart Rate": 0x180D, - "Battery": 0x180F, - "Blood Pressure": 0x1810, - "Location and Navigation": 0x1819, - "Weight Scale": 0x181D, - "Binary Sensor": 0x183B, -} -"""These are some of the common service types provided by the Bluetooth -SIG as part of the 16-bit UUID assigned numbers. There are many other options, -but due to the limitations of the nRF24L01 being used as a BLE beacon, these -would be the most useful.""" - - -class ServiceData: - """A helper class to package specific service data using Bluetooth SIG - defined 16-bit UUID flags to describe the data type. - - :param int type_t: The 16-bit "assigned number" defined by the - Bluetooth SIG to describe the service data. - :param bytearray data: The service data. The format of the data here - depends on the type of service being broadcast. - """ - - def __init__(self, type_t, data=b"0"): - self._type = (type_t).to_bytes(2, "little") - self._data = b"" - self.data = data - - @property - def data(self): - """The service's data. This is a `bytearray` and can have any format - which is usually constructed by python's :py:func:`struct.pack()`""" - return self._data - - @data.setter - def data(self, value): - self._data = value - - @property - def buffer(self): - """Get the representation of the instantiated object as an - immutable bytes object (read-only).""" - return bytes(self._type + self.data) - - def __len__(self): - return len(self._type) + len(self.data) - class FakeBLE: """Per the limitations of this technique, only `RF24.pa_level` is available for configuration when advertising BLE data. - :param ~circuitpython_nrf24l01.RF24 nrf: The object for the nRF24L01 + :param ~circuitpython_nrf24l01.rf24.RF24 nrf: The object for the nRF24L01 transceiver to use for fake BLE advertisements. - :param bytearray name: The BLE device name to be advertised with the - payload. """ def __init__(self, nrf): @@ -255,6 +158,7 @@ def __enter__(self): def __exit__(self, *exc): self._show_dbm = False + self._ble_name = None self._device.power = 0 return False @@ -305,6 +209,11 @@ def name(self): @name.setter def name(self, n): + if n is not None: + if not isinstance(n, (bytes, bytearray)): + raise ValueError("name must be a bytearray or bytes object.") + if len(n) > (21 - self._show_dbm * 3): + raise ValueError("name length exceeds maximum.") self._ble_name = n @property @@ -314,17 +223,19 @@ def show_pa_level(self): `False` will exclude this optional information. .. note:: This information takes up an extra 3 bytes, and is really - only useful for some applications to calculate proximity of the + only useful for some applications to calculate proximity to the nRF24L01 transceiver. """ return bool(self._show_dbm) @show_pa_level.setter def show_pa_level(self, enable): + if enable and len(self.name) > 18: + raise ValueError("there is not enough room to show the pa_level.") self._show_dbm = bool(enable) def hop_channel(self): - """trigger an automatic change of BLE compliant channels.""" + """Trigger an automatic change of BLE compliant channels.""" self._chan += 1 if self._chan > 2: self._chan = 0 @@ -370,17 +281,22 @@ def _make_payload(self, payload): name_length = (len(self.name) + 2) if self.name is not None else 0 if len(payload) > (21 - name_length - self._show_dbm * 3): raise ValueError( - "buf must have a length less than {}".format(21 - name_length) + "Payload exceeds maximum size. Configuration allows " + "{} bytes".format(21 - name_length - self._show_dbm * 3) ) pl_size = 9 + len(payload) + name_length + self._show_dbm * 3 + buf = bytes([self._to_iphone, pl_size]) + self.mac # header + buf += chunk(b"\x05", 1) # device descriptor pa_level = b"" if self._show_dbm: pa_level = chunk(struct.pack(">b", self._device.pa_level), 0x0A) - buf = bytes([self._to_iphone, pl_size]) + self.mac # header - buf += chunk(b"\x05", 1) # device descriptor + buf += pa_level if name_length: buf += chunk(self.name, 0x09) # device name - return buf + pa_level + payload + crc24_ble(buf + pa_level + payload) + buf += payload + buf += crc24_ble(buf) + # print("Payload size =", len(buf)) + return buf def advertise(self, buf=b"", data_type=0xFF): """This function is used to broadcast a payload. @@ -407,7 +323,65 @@ def advertise(self, buf=b"", data_type=0xFF): else: payload = chunk(buf, data_type) if buf else b"" payload = self._make_payload(payload) - self.hop_channel() - rev_whiten_pl = reverse_bits(self.whiten(payload)) - # print("transmitting \n{}\nas\n{}".format(payload, rev_whiten_pl)) - self._device.send(rev_whiten_pl) + self._device.send(reverse_bits(self.whiten(payload))) + + +class ServiceData: + """An abstract helper class to package specific service data using + Bluetooth SIG defined 16-bit UUID flags to describe the data type. + + :param int type_t: The 16-bit "assigned number" defined by the + Bluetooth SIG to describe the service data. This parameter is + required. + """ + + def __init__(self, type_t): + self._type = struct.pack("B", value) diff --git a/docs/advanced_api.rst b/docs/advanced_api.rst new file mode 100644 index 0000000..d8c5274 --- /dev/null +++ b/docs/advanced_api.rst @@ -0,0 +1,458 @@ + +.. |irq note| replace:: parameter as `True` to `clear_status_flags()` and reset this. As this + is a virtual representation of the interrupt event, this attribute will always be updated + despite what the actual IRQ pin is configured to do about this event. + +.. |update manually| replace:: Calling this does not execute an SPI transaction. It only + exposes that latest data contained in the STATUS byte that's always returned from any + other SPI transactions. Use the `update()` function to manually refresh this data when + needed. + +Advanced API +------------ + +what_happened() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.what_happened + + Some information may be irrelevant depending on nRF24L01's state/condition. + + :prints: + + - ``Channel`` The current setting of the `channel` attribute + - ``RF Data Rate`` The current setting of the RF `data_rate` attribute. + - ``RF Power Amplifier`` The current setting of the `pa_level` attribute. + - ``CRC bytes`` The current setting of the `crc` attribute + - ``Address length`` The current setting of the `address_length` attribute + - ``TX Payload lengths`` The current setting of the `payload_length` attribute for TX + operations (concerning data pipe 0) + - ``Auto retry delay`` The current setting of the `ard` attribute + - ``Auto retry attempts`` The current setting of the `arc` attribute + - ``Packets lost on current channel`` Total amount of packets lost (transmission + failures). This only resets when the `channel` is changed. This count will + only go up 15. + - ``Retry attempts made for last transmission`` Amount of attempts to re-transmit + during last transmission (resets per payload) + - ``IRQ - Data Ready`` The current setting of the IRQ pin on "Data Ready" event + - ``IRQ - Data Sent`` The current setting of the IRQ pin on "Data Sent" event + - ``IRQ - Data Fail`` The current setting of the IRQ pin on "Data Fail" event + - ``Data Ready`` Is there RX data ready to be read? (state of the `irq_dr` flag) + - ``Data Sent`` Has the TX data been sent? (state of the `irq_ds` flag) + - ``Data Failed`` Has the maximum attempts to re-transmit been reached? + (state of the `irq_df` flag) + - ``TX FIFO full`` Is the TX FIFO buffer full? (state of the `tx_full` flag) + - ``TX FIFO empty`` Is the TX FIFO buffer empty? + - ``RX FIFO full`` Is the RX FIFO buffer full? + - ``RX FIFO empty`` Is the RX FIFO buffer empty? + - ``Custom ACK payload`` Is the nRF24L01 setup to use an extra (user defined) payload + attached to the acknowledgment packet? (state of the `ack` attribute) + - ``Ask no ACK`` Is the nRF24L01 setup to transmit individual packets that don't + require acknowledgment? + - ``Automatic Acknowledgment`` The status of the `auto_ack` feature. If this value is a + binary representation, then each bit represents the feature's status for each pipe. + - ``Dynamic Payloads`` The status of the `dynamic_payloads` feature. If this value is a + binary representation, then each bit represents the feature's status for each pipe. + - ``Primary Mode`` The current mode (RX or TX) of communication of the nRF24L01 device. + - ``Power Mode`` The power state can be Off, Standby-I, Standby-II, or On. + + :param bool dump_pipes: `True` appends the output and prints: + + - the current address used for TX transmissions. This value is the entire content of + the nRF24L01's register about the TX address (despite what `address_length` is set + to). + - ``Pipe [#] ([open/closed]) bound: [address]`` where ``#`` represent the pipe number, + the ``open/closed`` status is relative to the pipe's RX status, and ``address`` is + the full value stored in the nRF24L01's RX address registers (despite what + `address_length` is set to. + - if the pipe is open, then the output also prints ``expecting [X] byte static + payloads`` where ``X`` is the `payload_length` (in bytes) the pipe is setup to + receive when `dynamic_payloads` is disabled for that pipe. + + Default is `False` and skips this extra information. + +load_ack() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.load_ack + + This payload will then be appended to the automatic acknowledgment + (ACK) packet that is sent when fresh data is received on the specified pipe. See + `read_ack()` on how to fetch a received custom ACK payloads. + + :param bytearray buf: This will be the data attached to an automatic ACK packet on the + incoming transmission about the specified ``pipe_number`` parameter. This must have a + length in range [1, 32] bytes, otherwise a `ValueError` exception is thrown. Any ACK + payloads will remain in the TX FIFO buffer until transmitted successfully or + `flush_tx()` is called. + :param int pipe_number: This will be the pipe number to use for deciding which + transmissions get a response with the specified ``buf`` parameter's data. This number + must be in range [0, 5], otherwise a `ValueError` exception is thrown. + + :returns: `True` if payload was successfully loaded onto the TX FIFO buffer. `False` if it + wasn't because TX FIFO buffer is full. + + .. note:: this function takes advantage of a special feature on the nRF24L01 and needs to + be called for every time a customized ACK payload is to be used (not for every + automatic ACK packet -- this just appends a payload to the ACK packet). The `ack`, + `auto_ack`, and `dynamic_payloads` attributes are also automatically enabled by this + function when necessary. + + .. tip:: The ACK payload must be set prior to receiving a transmission. It is also worth + noting that the nRF24L01 can hold up to 3 ACK payloads pending transmission. Using this + function does not over-write existing ACK payloads pending; it only adds to the queue + (TX FIFO buffer) if it can. Use `flush_tx()` to discard unused ACK payloads when done + listening. + +read_ack() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.read_ack + + This function was internally called from a blocking `send()` call if the `ack` attribute + is enabled. Alternatively, this function can be called directly in case of calling the + non-blocking `write()` function during asychronous applications. This function is an alias + of `recv()` and remains for backward compatibility with older versions of this library. + + .. note:: See also the `ack`, `dynamic_payloads`, and `auto_ack` attributes as they must be + enabled to use custom ACK payloads. + + .. warning:: This function will be deprecated on next major release. Use `recv()` instead. + +irq_dr +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_dr + + . + + :Returns: + + - `True` represents Data is in the RX FIFO buffer + - `False` represents anything depending on context (state/condition of FIFO buffers); + usually this means the flag's been reset. + + Pass ``dataReady`` |irq note| + + |update manually| + +irq_df +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_df + + . + + :Returns: + + - `True` signifies the nRF24L01 attemped all configured retries + - `False` represents anything depending on context (state/condition); usually this + means the flag's been reset. + + Pass ``dataFail`` |irq note| + + |update manually| + +irq_ds +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_ds + + . + + :Returns: + + - `True` represents a successful transmission + - `False` represents anything depending on context (state/condition of FIFO buffers); + usually this means the flag's been reset. + + Pass ``dataSent`` |irq note| + + |update manually| + +clear_status_flags() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.clear_status_flags + + Internally, this is automatically called by `send()`, `write()`, `recv()`, and when + `listen` changes from `False` to `True`. + + :param bool data_recv: specifies wheather to clear the "RX Data Ready" flag. + :param bool data_sent: specifies wheather to clear the "TX Data Sent" flag. + :param bool data_fail: specifies wheather to clear the "Max Re-transmit reached" flag. + + .. note:: Clearing the ``data_fail`` flag is necessary for continued transmissions from the + nRF24L01 (locks the TX FIFO buffer when `irq_df` is `True`) despite wheather or not the + MCU is taking advantage of the interrupt (IRQ) pin. Call this function only when there + is an antiquated status flag (after you've dealt with the specific payload related to + the staus flags that were set), otherwise it can cause payloads to be ignored and + occupy the RX/TX FIFO buffers. See `Appendix A of the nRF24L01+ Specifications Sheet + `_ for an outline of + proper behavior. + +power +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.power + + This is exposed for convenience. + + - `False` basically puts the nRF24L01 to sleep (AKA power down mode) with ultra-low + current consumption. No transmissions are executed when sleeping, but the nRF24L01 can + still be accessed through SPI. Upon instantiation, this driver class puts the nRF24L01 + to sleep until the MCU invokes RX/TX transmissions. This driver class doesn't power down + the nRF24L01 after RX/TX transmissions are complete (avoiding the required power up/down + 150 µs wait time), that preference is left to the application. + - `True` powers up the nRF24L01. This is the first step towards entering RX/TX modes (see + also `listen` attribute). Powering up is automatically handled by the `listen` attribute + as well as the `send()` and `write()` functions. + + .. note:: This attribute needs to be `True` if you want to put radio on Standby-II (highest + current consumption) or Standby-I (moderate current consumption) modes, which Standby + mode depends on the state of the CE pin. TX transmissions are only executed during + Standby-II by calling `send()` or `write()`. RX transmissions are received during + Standby-II by setting `listen` attribute to `True` (see `Chapter 6.1.2-7 of the + nRF24L01+ Specifications Sheet `_). After using + `send()` or setting `listen` to `False`, the nRF24L01 is left in Standby-I mode (see + also notes on the `write()` function). + +tx_full +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.tx_full + + . + + |update manually| + + :returns: + + - `True` for TX FIFO buffer is full + - `False` for TX FIFO buffer is not full. This doesn't mean the TX FIFO buffer is + empty. + +update() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.update + + Refreshing the status byte is vital to checking status of the interrupt flags, RX pipe + number related to current RX payload, and if the TX FIFO buffer is full. This function + returns nothing, but internally updates the `irq_dr`, `irq_ds`, `irq_df`, `pipe`, and + `tx_full` attributes. Internally this is a helper function to `send()`, and `resend()` + functions. + +resend() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.resend + + All returned data from this function follows the same patttern that `send()` returns with + the added condition that this function will return `False` if the TX FIFO buffer is empty. + + .. note:: The nRF24L01 normally removes a payload from the TX FIFO buffer after successful + transmission, but not when this function is called. The payload (successfully + transmitted or not) will remain in the TX FIFO buffer until `flush_tx()` is called to + remove them. Alternatively, using this function also allows the failed payload to be + over-written by using `send()` or `write()` to load a new payload into the TX FIFO + buffer. + +write() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.write + + :param bytearray buf: The payload to transmit. This bytearray must have a length greater + than 0 and less than 32 bytes, otherwise a `ValueError` exception is thrown. + + - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's + length is less than the `payload_length` attribute for data pipe 0, then this + bytearray is padded with zeros until its length is equal to the `payload_length` + attribute for data pipe 0. + - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's + length is greater than `payload_length` attribute for data pipe 0, then this + bytearray's length is truncated to equal the `payload_length` attribute for data + pipe 0. + :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for + an acknowledgment from the receiving nRF24L01. This parameter directly controls a + ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about + the payload). Therefore, it takes advantage of an nRF24L01 feature specific to + individual payloads, and its value is not saved anywhere. You do not need to specify + this for every payload if the `arc` attribute is disabled, however setting this + parameter to `True` will work despite the `arc` attribute's setting. + + .. note:: Each transmission is in the form of a packet. This packet contains sections + of data around and including the payload. `See Chapter 7.3 in the nRF24L01 + Specifications Sheet `_ for more + details. + + This function isn't completely non-blocking as we still need to wait just under 5 ms for + the CSN pin to settle (allowing a clean SPI transaction). + + .. note:: The nRF24L01 doesn't initiate sending until a mandatory minimum 10 µs pulse on + the CE pin is acheived. That pulse is initiated before this function exits. However, we + have left that 10 µs wait time to be managed by the MCU in cases of asychronous + application, or it is managed by using `send()` instead of this function. According to + the Specification sheet, if the CE pin remains HIGH for longer than 10 µs, then the + nRF24L01 will continue to transmit all payloads found in the TX FIFO buffer. + + .. warning:: + A note paraphrased from the `nRF24L01+ Specifications Sheet + `_: + + It is important to NEVER to keep the nRF24L01+ in TX mode for more than 4 ms at a time. + If the [`arc` attribute is] enabled, nRF24L01+ is never in TX mode longer than 4 + ms. + + .. tip:: Use this function at your own risk. Because of the underlying `"Enhanced + ShockBurst Protocol" `_, disobeying the 4 + ms rule is easily avoided if you enable the `auto_ack` attribute. Alternatively, you + MUST use interrupt flags or IRQ pin with user defined timer(s) to AVOID breaking the + 4 ms rule. If the `nRF24L01+ Specifications Sheet explicitly states this + `_, we have to assume + radio damage or misbehavior as a result of disobeying the 4 ms rule. See also `table 18 + in the nRF24L01 specification sheet `_ for + calculating an adequate transmission timeout sentinal. + +flush_rx() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_rx + + .. note:: The nRF24L01 RX FIFO is 3 level stack that holds payload data. This means that + there can be up to 3 received payloads (each of a maximum length equal to 32 bytes) + waiting to be read (and popped from the stack) by `recv()` or `read_ack()`. This + function clears all 3 levels. + +flush_tx() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_tx + + .. note:: The nRF24L01 TX FIFO is 3 level stack that holds payload data. This means that + there can be up to 3 payloads (each of a maximum length equal to 32 bytes) waiting to + be transmit by `send()`, `resend()` or `write()`. This function clears all 3 levels. It + is worth noting that the payload data is only popped from the TX FIFO stack upon + successful transmission (see also `resend()` as the handling of failed transmissions + can be altered). + +fifo() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.fifo + + :param bool about_tx: + - `True` means information returned is about the TX FIFO buffer. + - `False` means information returned is about the RX FIFO buffer. This parameter + defaults to `False` when not specified. + :param bool check_empty: + - `True` tests if the specified FIFO buffer is empty. + - `False` tests if the specified FIFO buffer is full. + - `None` (when not specified) returns a 2 bit number representing both empty (bit 1) & + full (bit 0) tests related to the FIFO buffer specified using the ``about_tx`` + parameter. + :returns: + - A `bool` answer to the question: + "Is the [TX/RX]:[`True`/`False`] FIFO buffer [empty/full]:[`True`/`False`]? + - If the ``check_empty`` parameter is not specified: an `int` in range [0,2] for which: + + - ``1`` means the specified FIFO buffer is full + - ``2`` means the specified FIFO buffer is empty + - ``0`` means the specified FIFO buffer is neither full nor empty + +pipe +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pipe + + . + + |update manually| + + :Returns: + + - `None` if there is no payload in RX FIFO. + - The `int` identifying pipe number [0,5] that received the next + available payload in the RX FIFO buffer. + +address_length +****************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.address_length + + A valid input value must be an `int` in range [3, 5]. Otherwise a `ValueError` exception is + thrown. Default is set to the nRF24L01's maximum of 5. + +address() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.address + + This function returns the full content of the nRF24L01's registers about RX/TX addresses + despite what `address_length` is set to. + + :param int index: the number of the data pipe whose address is to be returned. Defaults to + ``-1``. A valid index ranges [0,5] for RX addresses or any negative `int` for the TX + address. Otherwise an `IndexError` is thown. + +rpd +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.rpd + + The RPD flag is triggered in the following cases: + + 1. During RX mode (`listen` = `True`) and an arbitrary RF transmission with a gain + above -64 dBm threshold is/was present. + 2. When a packet is received (instigated by the nRF24L01 used to detect/"listen" for + incoming packets). + + .. note:: See also + `section 6.4 of the Specification Sheet concerning the RPD flag + `_. Ambient + temperature affects the -64 dBm threshold. The latching of this flag happens + differently under certain conditions. + +start_carrier_wave() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.start_carrier_wave + + This is a basic test of the nRF24L01's TX output. It is a commonly required + test for telecommunication regulations. Calling this function may introduce + interference with other transceivers that use frequencies in range [2.4, + 2.525] GHz. To verify that this test is working properly, use the following + code on a seperate nRF24L01 transceiver: + + .. code-block:: python + + # declare objects for SPI bus and CSN pin and CE pin + nrf. = RF24(spi, csn, ce) + # set nrf.pa_level, nrf.channel, & nrf.data_rate values to + # match the corresponding attributes on the device that is + # transmitting the carrier wave + nrf.listen = True + if nrf.rpd: + print("carrier wave detected") + + The `pa_level`, `channel` & `data_rate` attributes are vital factors to + the success of this test. See also the `rpd` attribute. + +stop_carrier_wave() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.stop_carrier_wave + + See `start_carrier_wave()` for more details. + + .. note:: + Calling this function puts the nRF24L01 to sleep (AKA power down mode). diff --git a/docs/api.rst b/docs/api.rst deleted file mode 100644 index b8769ca..0000000 --- a/docs/api.rst +++ /dev/null @@ -1,1020 +0,0 @@ - - -.. |irq note| replace:: parameter as `True` to `clear_status_flags()` and reset this. As this - is a virtual representation of the interrupt event, this attribute will always be updated - despite what the actual IRQ pin is configured to do about this event. - -.. |update manually| replace:: Calling this does not execute an SPI transaction. It only - exposes that latest data contained in the STATUS byte that's always returned from any - other SPI transactions. Use the `update()` function to manually refresh this data when - needed. - -Troubleshooting info -==================== - -.. important:: The nRF24L01 has 3 key features that can be interdependent of each other. Their - priority of dependence is as follows: - - 1. `auto_ack` feature provides transmission verification by using the RX nRF24L01 to - automatically and imediatedly send an acknowledgment (ACK) packet in response to freshly - received payloads. `auto_ack` does not require `dynamic_payloads` to be enabled. - 2. `dynamic_payloads` feature allowing either TX/RX nRF24L01 to be able to send/receive - payloads with their size written into the payloads' packet. With this disabled, both - RX/TX nRF24L01 must use matching `payload_length` attributes. For `dynamic_payloads` to - be enabled, the `auto_ack` feature must be enabled. Although, the `auto_ack` feature - isn't required when the `dynamic_payloads` feature is disabled. - 3. `ack` feature allows the MCU to append a payload to the ACK packet, thus instant - bi-directional communication. A transmitting ACK payload must be loaded into the - nRF24L01's TX FIFO buffer (done using `load_ack()`) BEFORE receiving the payload that - is to be acknowledged. Once transmitted, the payload is released from the TX FIFO - buffer. This feature requires the `auto_ack` and `dynamic_payloads` features enabled. - -Remeber that the nRF24L01's FIFO (first-in,first-out) buffer has 3 levels. This means that -there can be up to 3 payloads waiting to be read (RX) and up to 3 payloads waiting to be -transmit (TX). - -With the `auto_ack` feature enabled, you get: - - * cyclic redundancy checking (`crc`) automatically enabled - * to change amount of automatic re-transmit attempts and the delay time between them. - See the `arc` and `ard` attributes. - -.. note:: A word on pipes vs addresses vs channels. - - You should think of the data pipes as a "parking spot" for your payload. There are only six - data pipes on the nRF24L01, thus it can simultaneously "listen" to a maximum of 6 other - nRF24L01 radios. However, it can only "talk" to 1 other nRF24L01 at a time). - - The specified address is not the address of an nRF24L01 radio, rather it is more like a - path that connects the endpoints. When assigning addresses to a data pipe, you can use any - 5 byte long address you can think of (as long as the first byte is unique among - simultaneously broadcasting addresses), so you're not limited to communicating with only - the same 6 nRF24L01 radios (more on this when we officially support "Multiciever" mode). - - Finnaly, the radio's channel is not be confused with the radio's pipes. Channel selection - is a way of specifying a certain radio frequency (frequency = [2400 + channel] MHz). - Channel defaults to 76 (like the arduino library), but options range from 0 to 125 -- - that's 2.4 GHz to 2.525 GHz. The channel can be tweaked to find a less occupied frequency - amongst Bluetooth, WiFi, or other ambient signals that use the same spectrum of - frequencies. - -.. warning:: - For successful transmissions, most of the endpoint trasceivers' settings/features must - match. These settings/features include: - - * The RX pipe's address on the receiving nRF24L01 (passed to `open_rx_pipe()`) MUST match - the TX pipe's address on the transmitting nRF24L01 (passed to `open_tx_pipe()`) - * `address_length` - * `channel` - * `data_rate` - * `dynamic_payloads` - * `payload_length` only when `dynamic_payloads` is disabled - * `auto_ack` on the recieving nRF24L01 must be enabled if `arc` is greater than 0 on the - transmitting nRF24L01 - * custom `ack` payloads - * `crc` - - In fact the only attributes that aren't required to match on both endpoint transceivers - would be the identifying data pipe number (passed to `open_rx_pipe()` or `load_ack()`), - `pa_level`, `arc`, & `ard` attributes. The ``ask_no_ack`` feature can be used despite the - settings/features configuration (see `send()` & `write()` function parameters for more - details). - -About the lite version -====================== - -This library contains a "lite" version of ``rf24.py`` titled ``rf24_lite.py``. It has been -developed to save space on microcontrollers with limited amount of RAM and/or storage (like -boards using the ATSAMD21 M0). The following functionality has been removed from the lite -version: - - * `address()` removed. - * `what_happened()` removed. However you can use the following function to dump all - available registers' values (for advanced users): - - .. code-block:: python - - # let `nrf` be the instantiated RF24 object - def dump_registers(end=0x1e): - for i in range(end): - if i in (0xA, 0xB, 0x10): - print(hex(i), "=", nrf._reg_read_bytes(i)) - elif i not in (0x18, 0x19, 0x1a, 0x1b): - print(hex(i), "=", hex(nrf._reg_read(i))) - * `fifo()` removed. - * `dynamic_payloads` applies to all pipes, not individual pipes. - * `payload_length` applies to all pipes, not individual pipes. - * `read_ack()` removed. This is deprecated on next major release anyway; use `recv()` - instead. - * `load_ack()` is available, but it will not throw exceptions for malformed ``buf`` or - invalid ``pipe_number`` parameters. - * `crc` removed. 2-bytes encoding scheme (CRC16) is always enabled. - * `auto_ack` removed. This is always enabled for all pipes. Pass ``ask_no_ack`` parameter - as `True` to `send()` or `write()` to disable automatic acknowledgement for TX - operations. - * `is_lna_enabled` removed. This will always be enabled, and `pa_level` will not accept a - `list` or `tuple`. This only affects certain boards anyway. - * `rpd`, `start_carrier_wave()`, & `stop_carrier_wave()` removed. These only perform a - test of the nRF24L01's hardware. - * `CSN_DELAY` removed. This is hard-coded to 5 milliseconds - * All comments and docstrings removed, meaning ``help()`` will not provide any specific - information. Exception prompts have also been reduced and adjusted accordingly. - * Cannot switch between different radio configurations using context manager (the `with` - blocks). It is advised that only one `RF24` object be instantiated when RAM is limited - (less than or equal to 32KB). - -RF24 class -============== - -Basic API ---------- - -Constructor -****************** - -.. autoclass:: circuitpython_nrf24l01.rf24.RF24 - :no-members: - - This class aims to be compatible with other devices in the nRF24xxx product line that - implement the Nordic proprietary Enhanced ShockBurst Protocol (and/or the legacy - ShockBurst Protocol), but officially only supports (through testing) the nRF24L01 and - nRF24L01+ devices. - - :param ~busio.SPI spi: The object for the SPI bus that the nRF24L01 is connected to. - - .. tip:: This object is meant to be shared amongst other driver classes (like - adafruit_mcp3xxx.mcp3008 for example) that use the same SPI bus. Otherwise, multiple - devices on the same SPI bus with different spi objects may produce errors or - undesirable behavior. - :param ~digitalio.DigitalInOut csn: The digital output pin that is connected to the nRF24L01's - CSN (Chip Select Not) pin. This is required. - :param ~digitalio.DigitalInOut ce: The digital output pin that is connected to the nRF24L01's - CE (Chip Enable) pin. This is required. - :param int spi_frequency: Specify which SPI frequency (in Hz) to use on the SPI bus. This - parameter only applies to the instantiated object and is made persistent via - :py:class:`~adafruit_bus_device.spi_device`. - -open_tx_pipe() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_tx_pipe - - :param bytearray address: The virtual address of the receiving nRF24L01. The address - specified here must match the address set to one of the RX data pipes of the receiving - nRF24L01. The existing address can be altered by writting a bytearray with a length - less than 5. The nRF24L01 will use the first `address_length` number of bytes for the - RX address on the specified data pipe. - - .. note:: There is no option to specify which data pipe to use because the nRF24L01 only - uses data pipe 0 in TX mode. Additionally, the nRF24L01 uses the same data pipe (pipe - 0) for receiving acknowledgement (ACK) packets in TX mode when the `auto_ack` attribute - is enabled for data pipe 0. Thus, RX pipe 0 is appropriated with the TX address - (specified here) when `auto_ack` is enabled for data pipe 0. - -close_rx_pipe() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.close_rx_pipe - - :param int pipe_number: The data pipe to use for RX transactions. This must be in range - [0, 5]. Otherwise a `ValueError` exception is thrown. - -open_rx_pipe() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_rx_pipe - - If `dynamic_payloads` attribute is disabled for the specifed data pipe, then the - `payload_length` attribute is used to define the expected length of the static RX payload - on the specified data pipe. - - :param int pipe_number: The data pipe to use for RX transactions. This must be in range - [0, 5]. Otherwise a `ValueError` exception is thrown. - :param bytearray address: The virtual address to the receiving nRF24L01. If using a - ``pipe_number`` greater than 1, then only the MSByte of the address is written, so make - sure MSByte (first character) is unique among other simultaneously receiving addresses. - The existing address can be altered by writing a bytearray with a length less than 5. - The nRF24L01 will use the first `address_length` number of bytes for the RX address on - the specified data pipe. - - .. note:: The nRF24L01 shares the addresses' last 4 LSBytes on data pipes 2 through - 5. These shared LSBytes are determined by the address set to data pipe 1. - -listen -****************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.listen - - Setting this attribute incorporates the proper transitioning to/from RX mode as it involves - playing with the `power` attribute and the nRF24L01's CE pin. This attribute does not power - down the nRF24L01, but will power it up when needed; use `power` attribute set to `False` - to put the nRF24L01 to sleep. - - A valid input value is a `bool` in which: - - - `True` enables RX mode. Additionally, per `Appendix B of the nRF24L01+ Specifications - Sheet `_, this attribute - flushes the RX FIFO, clears the `irq_dr` status flag, and puts nRF24L01 in power up - mode. Notice the CE pin is be held HIGH during RX mode. - - `False` disables RX mode. As mentioned in above link, this puts nRF24L01's power in - Standby-I (CE pin is LOW meaning low current & no transmissions) mode which is ideal - for post-reception work. Disabing RX mode doesn't flush the RX/TX FIFO buffers, so - remember to flush your 3-level FIFO buffers when appropriate using `flush_tx()` or - `flush_rx()` (see also the `recv()` function). - -any() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.any - - :returns: - - `int` of the size (in bytes) of an available RX payload (if any). - - ``0`` if there is no payload in the RX FIFO buffer. - -recv() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.recv - - This function can also be used to fetch the last ACK packet's payload if `ack` is enabled. - - :param int length: An optional parameter to specify how many bytes to read from the RX - FIFO buffer. This parameter is not contrained in any way. - - - If this parameter is less than the length of the first available payload in the - RX FIFO buffer, then the payload will remain in the RX FIFO buffer until the - entire payload is fetched by this function. - - If this parameter is greater than the next available payload's length, then - additional data from other payload(s) in the RX FIFO buffer are returned. - - .. note:: - The nRF24L01 will repeatedly return the last byte fetched from the RX FIFO - buffer when there is no data to return (even if the RX FIFO is empty). Be - aware that a payload is only removed from the RX FIFO buffer when the entire - payload has been fetched by this function. Notice that this function always - starts reading data from the first byte of the first available payload (if - any) in the RX FIFO buffer. - :returns: - A `bytearray` of the RX payload data or `None` if there is no payload. If the - ``length`` parameter is not specified, then one of the following two scenarios is - applied. - - - If the `dynamic_payloads` attribute is disabled, then the returned bytearray's - length is equal to the user defined `payload_length` attribute for the data pipe - that received the payload. - - If the `dynamic_payloads` attribute is enabled, then the returned bytearray's length - is equal to the payload's length - -send() -****************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.send - - :returns: - - `list` if a list or tuple of payloads was passed as the ``buf`` parameter. Each item - in the returned list will contain the returned status for each corresponding payload - in the list/tuple that was passed. The return statuses will be in one of the - following forms: - - `False` if transmission fails. Transmission failure can only be detected if `arc` - is greater than ``0``. - - `True` if transmission succeeds. - - `bytearray` or `None` when the `ack` attribute is `True`. Because the payload - expects a responding custom ACK payload, the response is returned (upon successful - transmission) as a `bytearray` (or `None` if ACK payload is empty) - - :param bytearray,list,tuple buf: The payload to transmit. This bytearray must have a - length in range [1, 32], otherwise a `ValueError` exception is thrown. This can - also be a list or tuple of payloads (`bytearray`); in which case, all items in the - list/tuple are processed for consecutive transmissions. - - - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's - length is less than the `payload_length` attribute for pipe 0, then this bytearray - is padded with zeros until its length is equal to the `payload_length` attribute for - pipe 0. - - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's - length is greater than `payload_length` attribute for pipe 0, then this bytearray's - length is truncated to equal the `payload_length` attribute for pipe 0. - :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for - an acknowledgment from the receiving nRF24L01. This parameter directly controls a - ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information - about the payload). Therefore, it takes advantage of an nRF24L01 feature specific to - individual payloads, and its value is not saved anywhere. You do not need to specify - this for every payload if the `arc` attribute is disabled, however setting this - parameter to `True` will work despite the `arc` attribute's setting. - - .. note:: Each transmission is in the form of a packet. This packet contains sections - of data around and including the payload. `See Chapter 7.3 in the nRF24L01 - Specifications Sheet `_ for more - details. - :param int force_retry: The number of brute-force attempts to `resend()` a failed - transmission. Default is 0. This parameter has no affect on transmissions if `arc` is - ``0`` or ``ask_no_ack`` parameter is set to `True`. Each re-attempt still takes - advantage of `arc` & `ard` attributes. During multi-payload processing, this - parameter is meant to slow down CircuitPython devices just enough for the Raspberry - Pi to catch up (due to the Raspberry Pi's seemingly slower SPI speeds). See also - notes on `resend()` as using this parameter carries the same implications documented - there. This parameter has no effect if the ``ask_no_ack`` parameter is set to `True` - or if `arc` is disabled. - - .. tip:: It is highly recommended that `arc` attribute is enabled (greater than ``0``) - when sending multiple payloads. Test results with the `arc` attribute disabled were - rather poor (less than 79% received by a Raspberry Pi). This same advice applies to - the ``ask_no_ack`` parameter (leave it as `False` for multiple payloads). - .. warning:: The nRF24L01 will block usage of the TX FIFO buffer upon failed - transmissions. Failed transmission's payloads stay in TX FIFO buffer until the MCU - calls `flush_tx()` and `clear_status_flags()`. Therefore, this function will discard - failed transmissions' payloads when sending a list or tuple of payloads, so it can - continue to process through the list/tuple even if any payload fails to be - acknowledged. - -Advanced API ------------- - -what_happened() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.what_happened - - Some information may be irrelevant depending on nRF24L01's state/condition. - - :prints: - - - ``Channel`` The current setting of the `channel` attribute - - ``RF Data Rate`` The current setting of the RF `data_rate` attribute. - - ``RF Power Amplifier`` The current setting of the `pa_level` attribute. - - ``CRC bytes`` The current setting of the `crc` attribute - - ``Address length`` The current setting of the `address_length` attribute - - ``TX Payload lengths`` The current setting of the `payload_length` attribute for TX - operations (concerning data pipe 0) - - ``Auto retry delay`` The current setting of the `ard` attribute - - ``Auto retry attempts`` The current setting of the `arc` attribute - - ``Packets lost on current channel`` Total amount of packets lost (transmission - failures). This only resets when the `channel` is changed. This count will - only go up 15. - - ``Retry attempts made for last transmission`` Amount of attempts to re-transmit - during last transmission (resets per payload) - - ``IRQ - Data Ready`` The current setting of the IRQ pin on "Data Ready" event - - ``IRQ - Data Sent`` The current setting of the IRQ pin on "Data Sent" event - - ``IRQ - Data Fail`` The current setting of the IRQ pin on "Data Fail" event - - ``Data Ready`` Is there RX data ready to be read? (state of the `irq_dr` flag) - - ``Data Sent`` Has the TX data been sent? (state of the `irq_ds` flag) - - ``Data Failed`` Has the maximum attempts to re-transmit been reached? - (state of the `irq_df` flag) - - ``TX FIFO full`` Is the TX FIFO buffer full? (state of the `tx_full` flag) - - ``TX FIFO empty`` Is the TX FIFO buffer empty? - - ``RX FIFO full`` Is the RX FIFO buffer full? - - ``RX FIFO empty`` Is the RX FIFO buffer empty? - - ``Custom ACK payload`` Is the nRF24L01 setup to use an extra (user defined) payload - attached to the acknowledgment packet? (state of the `ack` attribute) - - ``Ask no ACK`` Is the nRF24L01 setup to transmit individual packets that don't - require acknowledgment? - - ``Automatic Acknowledgment`` The status of the `auto_ack` feature. If this value is a - binary representation, then each bit represents the feature's status for each pipe. - - ``Dynamic Payloads`` The status of the `dynamic_payloads` feature. If this value is a - binary representation, then each bit represents the feature's status for each pipe. - - ``Primary Mode`` The current mode (RX or TX) of communication of the nRF24L01 device. - - ``Power Mode`` The power state can be Off, Standby-I, Standby-II, or On. - - :param bool dump_pipes: `True` appends the output and prints: - - - the current address used for TX transmissions. This value is the entire content of - the nRF24L01's register about the TX address (despite what `address_length` is set - to). - - ``Pipe [#] ([open/closed]) bound: [address]`` where ``#`` represent the pipe number, - the ``open/closed`` status is relative to the pipe's RX status, and ``address`` is - the full value stored in the nRF24L01's RX address registers (despite what - `address_length` is set to. - - if the pipe is open, then the output also prints ``expecting [X] byte static - payloads`` where ``X`` is the `payload_length` (in bytes) the pipe is setup to - receive when `dynamic_payloads` is disabled for that pipe. - - Default is `False` and skips this extra information. - -load_ack() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.load_ack - - This payload will then be appended to the automatic acknowledgment - (ACK) packet that is sent when fresh data is received on the specified pipe. See - `read_ack()` on how to fetch a received custom ACK payloads. - - :param bytearray buf: This will be the data attached to an automatic ACK packet on the - incoming transmission about the specified ``pipe_number`` parameter. This must have a - length in range [1, 32] bytes, otherwise a `ValueError` exception is thrown. Any ACK - payloads will remain in the TX FIFO buffer until transmitted successfully or - `flush_tx()` is called. - :param int pipe_number: This will be the pipe number to use for deciding which - transmissions get a response with the specified ``buf`` parameter's data. This number - must be in range [0, 5], otherwise a `ValueError` exception is thrown. - - :returns: `True` if payload was successfully loaded onto the TX FIFO buffer. `False` if it - wasn't because TX FIFO buffer is full. - - .. note:: this function takes advantage of a special feature on the nRF24L01 and needs to - be called for every time a customized ACK payload is to be used (not for every - automatic ACK packet -- this just appends a payload to the ACK packet). The `ack`, - `auto_ack`, and `dynamic_payloads` attributes are also automatically enabled by this - function when necessary. - - .. tip:: The ACK payload must be set prior to receiving a transmission. It is also worth - noting that the nRF24L01 can hold up to 3 ACK payloads pending transmission. Using this - function does not over-write existing ACK payloads pending; it only adds to the queue - (TX FIFO buffer) if it can. Use `flush_tx()` to discard unused ACK payloads when done - listening. - -read_ack() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.read_ack - - This function was internally called from a blocking `send()` call if the `ack` attribute - is enabled. Alternatively, this function can be called directly in case of calling the - non-blocking `write()` function during asychronous applications. This function is an alias - of `recv()` and remains for backward compatibility with older versions of this library. - - .. note:: See also the `ack`, `dynamic_payloads`, and `auto_ack` attributes as they must be - enabled to use custom ACK payloads. - - .. warning:: This function will be deprecated on next major release. Use `recv()` instead. - -irq_dr -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_dr - - . - - :Returns: - - - `True` represents Data is in the RX FIFO buffer - - `False` represents anything depending on context (state/condition of FIFO buffers); - usually this means the flag's been reset. - - Pass ``dataReady`` |irq note| - - |update manually| - -irq_df -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_df - - . - - :Returns: - - - `True` signifies the nRF24L01 attemped all configured retries - - `False` represents anything depending on context (state/condition); usually this - means the flag's been reset. - - Pass ``dataFail`` |irq note| - - |update manually| - -irq_ds -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_ds - - . - - :Returns: - - - `True` represents a successful transmission - - `False` represents anything depending on context (state/condition of FIFO buffers); - usually this means the flag's been reset. - - Pass ``dataSent`` |irq note| - - |update manually| - -clear_status_flags() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.clear_status_flags - - Internally, this is automatically called by `send()`, `write()`, `recv()`, and when - `listen` changes from `False` to `True`. - - :param bool data_recv: specifies wheather to clear the "RX Data Ready" flag. - :param bool data_sent: specifies wheather to clear the "TX Data Sent" flag. - :param bool data_fail: specifies wheather to clear the "Max Re-transmit reached" flag. - - .. note:: Clearing the ``data_fail`` flag is necessary for continued transmissions from the - nRF24L01 (locks the TX FIFO buffer when `irq_df` is `True`) despite wheather or not the - MCU is taking advantage of the interrupt (IRQ) pin. Call this function only when there - is an antiquated status flag (after you've dealt with the specific payload related to - the staus flags that were set), otherwise it can cause payloads to be ignored and - occupy the RX/TX FIFO buffers. See `Appendix A of the nRF24L01+ Specifications Sheet - `_ for an outline of - proper behavior. - -power -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.power - - This is exposed for convenience. - - - `False` basically puts the nRF24L01 to sleep (AKA power down mode) with ultra-low - current consumption. No transmissions are executed when sleeping, but the nRF24L01 can - still be accessed through SPI. Upon instantiation, this driver class puts the nRF24L01 - to sleep until the MCU invokes RX/TX transmissions. This driver class doesn't power down - the nRF24L01 after RX/TX transmissions are complete (avoiding the required power up/down - 150 µs wait time), that preference is left to the application. - - `True` powers up the nRF24L01. This is the first step towards entering RX/TX modes (see - also `listen` attribute). Powering up is automatically handled by the `listen` attribute - as well as the `send()` and `write()` functions. - - .. note:: This attribute needs to be `True` if you want to put radio on Standby-II (highest - current consumption) or Standby-I (moderate current consumption) modes, which Standby - mode depends on the state of the CE pin. TX transmissions are only executed during - Standby-II by calling `send()` or `write()`. RX transmissions are received during - Standby-II by setting `listen` attribute to `True` (see `Chapter 6.1.2-7 of the - nRF24L01+ Specifications Sheet `_). After using - `send()` or setting `listen` to `False`, the nRF24L01 is left in Standby-I mode (see - also notes on the `write()` function). - -tx_full -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.tx_full - - . - - |update manually| - - :returns: - - - `True` for TX FIFO buffer is full - - `False` for TX FIFO buffer is not full. This doesn't mean the TX FIFO buffer is - empty. - -update() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.update - - Refreshing the status byte is vital to checking status of the interrupt flags, RX pipe - number related to current RX payload, and if the TX FIFO buffer is full. This function - returns nothing, but internally updates the `irq_dr`, `irq_ds`, `irq_df`, `pipe`, and - `tx_full` attributes. Internally this is a helper function to `send()`, and `resend()` - functions. - -resend() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.resend - - All returned data from this function follows the same patttern that `send()` returns with - the added condition that this function will return `False` if the TX FIFO buffer is empty. - - .. note:: The nRF24L01 normally removes a payload from the TX FIFO buffer after successful - transmission, but not when this function is called. The payload (successfully - transmitted or not) will remain in the TX FIFO buffer until `flush_tx()` is called to - remove them. Alternatively, using this function also allows the failed payload to be - over-written by using `send()` or `write()` to load a new payload into the TX FIFO - buffer. - -write() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.write - - :param bytearray buf: The payload to transmit. This bytearray must have a length greater - than 0 and less than 32 bytes, otherwise a `ValueError` exception is thrown. - - - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's - length is less than the `payload_length` attribute for data pipe 0, then this - bytearray is padded with zeros until its length is equal to the `payload_length` - attribute for data pipe 0. - - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's - length is greater than `payload_length` attribute for data pipe 0, then this - bytearray's length is truncated to equal the `payload_length` attribute for data - pipe 0. - :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for - an acknowledgment from the receiving nRF24L01. This parameter directly controls a - ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information about - the payload). Therefore, it takes advantage of an nRF24L01 feature specific to - individual payloads, and its value is not saved anywhere. You do not need to specify - this for every payload if the `arc` attribute is disabled, however setting this - parameter to `True` will work despite the `arc` attribute's setting. - - .. note:: Each transmission is in the form of a packet. This packet contains sections - of data around and including the payload. `See Chapter 7.3 in the nRF24L01 - Specifications Sheet `_ for more - details. - - This function isn't completely non-blocking as we still need to wait just under 5 ms for - the CSN pin to settle (allowing a clean SPI transaction). - - .. note:: The nRF24L01 doesn't initiate sending until a mandatory minimum 10 µs pulse on - the CE pin is acheived. That pulse is initiated before this function exits. However, we - have left that 10 µs wait time to be managed by the MCU in cases of asychronous - application, or it is managed by using `send()` instead of this function. According to - the Specification sheet, if the CE pin remains HIGH for longer than 10 µs, then the - nRF24L01 will continue to transmit all payloads found in the TX FIFO buffer. - - .. warning:: - A note paraphrased from the `nRF24L01+ Specifications Sheet - `_: - - It is important to NEVER to keep the nRF24L01+ in TX mode for more than 4 ms at a time. - If the [`arc` attribute is] enabled, nRF24L01+ is never in TX mode longer than 4 - ms. - - .. tip:: Use this function at your own risk. Because of the underlying `"Enhanced - ShockBurst Protocol" `_, disobeying the 4 - ms rule is easily avoided if you enable the `auto_ack` attribute. Alternatively, you - MUST use interrupt flags or IRQ pin with user defined timer(s) to AVOID breaking the - 4 ms rule. If the `nRF24L01+ Specifications Sheet explicitly states this - `_, we have to assume - radio damage or misbehavior as a result of disobeying the 4 ms rule. See also `table 18 - in the nRF24L01 specification sheet `_ for - calculating an adequate transmission timeout sentinal. - -flush_rx() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_rx - - .. note:: The nRF24L01 RX FIFO is 3 level stack that holds payload data. This means that - there can be up to 3 received payloads (each of a maximum length equal to 32 bytes) - waiting to be read (and popped from the stack) by `recv()` or `read_ack()`. This - function clears all 3 levels. - -flush_tx() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_tx - - .. note:: The nRF24L01 TX FIFO is 3 level stack that holds payload data. This means that - there can be up to 3 payloads (each of a maximum length equal to 32 bytes) waiting to - be transmit by `send()`, `resend()` or `write()`. This function clears all 3 levels. It - is worth noting that the payload data is only popped from the TX FIFO stack upon - successful transmission (see also `resend()` as the handling of failed transmissions - can be altered). - -fifo() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.fifo - - :param bool about_tx: - - `True` means information returned is about the TX FIFO buffer. - - `False` means information returned is about the RX FIFO buffer. This parameter - defaults to `False` when not specified. - :param bool check_empty: - - `True` tests if the specified FIFO buffer is empty. - - `False` tests if the specified FIFO buffer is full. - - `None` (when not specified) returns a 2 bit number representing both empty (bit 1) & - full (bit 0) tests related to the FIFO buffer specified using the ``about_tx`` - parameter. - :returns: - - A `bool` answer to the question: - "Is the [TX/RX]:[`True`/`False`] FIFO buffer [empty/full]:[`True`/`False`]? - - If the ``check_empty`` parameter is not specified: an `int` in range [0,2] for which: - - - ``1`` means the specified FIFO buffer is full - - ``2`` means the specified FIFO buffer is empty - - ``0`` means the specified FIFO buffer is neither full nor empty - -pipe -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pipe - - . - - |update manually| - - :Returns: - - - `None` if there is no payload in RX FIFO. - - The `int` identifying pipe number [0,5] that received the next - available payload in the RX FIFO buffer. - -address_length -****************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.address_length - - A valid input value must be an `int` in range [3, 5]. Otherwise a `ValueError` exception is - thrown. Default is set to the nRF24L01's maximum of 5. - -address() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.address - - This function returns the full content of the nRF24L01's registers about RX/TX addresses - despite what `address_length` is set to. - - :param int index: the number of the data pipe whose address is to be returned. Defaults to - ``-1``. A valid index ranges [0,5] for RX addresses or any negative `int` for the TX - address. Otherwise an `IndexError` is thown. - -rpd -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.rpd - - The RPD flag is triggered in the following cases: - - 1. During RX mode (`listen` = `True`) and an arbitrary RF transmission with a gain - above -64 dBm threshold is/was present. - 2. When a packet is received (instigated by the nRF24L01 used to detect/"listen" for - incoming packets). - - .. note:: See also - `section 6.4 of the Specification Sheet concerning the RPD flag - `_. Ambient - temperature affects the -64 dBm threshold. The latching of this flag happens - differently under certain conditions. - -start_carrier_wave() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.start_carrier_wave - - This is a basic test of the nRF24L01's TX output. It is a commonly required - test for telecommunication regulations. Calling this function may introduce - interference with other transceivers that use frequencies in range [2.4, - 2.525] GHz. To verify that this test is working properly, use the following - code on a seperate nRF24L01 transceiver: - - .. code-block:: python - - # declare objects for SPI bus and CSN pin and CE pin - nrf. = RF24(spi, csn, ce) - # set nrf.pa_level, nrf.channel, & nrf.data_rate values to - # match the corresponding attributes on the device that is - # transmitting the carrier wave - nrf.listen = True - if nrf.rpd: - print("carrier wave detected") - - The `pa_level`, `channel` & `data_rate` attributes are vital factors to - the success of this test. See also the `rpd` attribute. - -stop_carrier_wave() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.stop_carrier_wave - - See `start_carrier_wave()` for more details. - - .. note:: - Calling this function puts the nRF24L01 to sleep (AKA power down mode). - -Configuration API ------------------ - -CSN_DELAY -****************************** - -.. autodata:: circuitpython_nrf24l01.rf24.CSN_DELAY - -dynamic_payloads -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.dynamic_payloads - - Default setting is enabled on all pipes. - - - `True` or ``1`` enables nRF24L01's dynamic payload length feature for all data pipes. The - `payload_length` attribute is ignored when this feature is enabled for respective or all - data pipes. - - `False` or ``0`` disables nRF24L01's dynamic payload length feature for all data pipes. - Be sure to adjust the `payload_length` attribute accordingly when this feature is - disabled for any data pipes. - - A `list` or `tuple` containing booleans or integers can be used control this feature per - data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be - ignored since there are only 6 data pipes. - - .. note:: - This attribute mostly relates to RX operations, but data pipe 0 applies to TX - operations also. - -payload_length -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.payload_length - - If the `dynamic_payloads` attribute is enabled for a certain data pipe, this attribute has - no affect on that data pipe. When `dynamic_payloads` is disabled for a certain data pipe, - this attribute is used to specify the payload length on that data pipe in RX mode. - - A valid input value must be: - - * an `int` in range [1, 32]. Otherwise a `ValueError` exception is thrown. - * a `list` or `tuple` containing integers can be used to control this attribute per - data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will - be ignored since there are only 6 data pipes. if a index's value is ``0``, then the - existing setting will persist (not be changed). - - Default is set to the nRF24L01's maximum of 32 (on all data pipes). - - .. note:: - This attribute mostly relates to RX operations, but data pipe 0 applies to TX - operations also. - -auto_ack -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.auto_ack - - Default setting is enabled on all data pipes. - - - `True` or ``1`` enables transmitting automatic acknowledgment packets for all data pipes. - The CRC (cyclic redundancy checking) is enabled automatically by the nRF24L01 if the - `auto_ack` attribute is enabled for any data pipe (see also `crc` attribute). - - `False` or ``0`` disables transmitting automatic acknowledgment packets for all data - pipes. The `crc` attribute will remain unaffected when disabling this attribute for any - data pipes. - - A `list` or `tuple` containing booleans or integers can be used control this feature per - data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be - ignored since there are only 6 data pipes. - - .. note:: - This attribute mostly relates to RX operations, but data pipe 0 applies to TX - operations also. - -arc -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.arc - - The `auto_ack` attribute must be enabled on the receiving nRF24L01 respective data pipe, - otherwise this attribute will make `send()` seem like it failed. - - A valid input value must be in range [0, 15]. Otherwise a `ValueError` exception is thrown. - Default is set to 3. A value of ``0`` disables the automatic re-transmit feature and - considers all payload transmissions a success. - -ard -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ard - - During this time, the nRF24L01 is listening for the ACK packet. If the - `auto_ack` attribute is disabled, this attribute is not applied. - - A valid input value must be in range [250, 4000]. Otherwise a `ValueError` exception is - thrown. Default is 1500 for reliability. If this is set to a value that is not multiple of - 250, then the highest multiple of 250 that is no greater than the input value is used. - - .. note:: Paraphrased from nRF24L01 specifications sheet: - - Please take care when setting this parameter. If the custom ACK payload is more than 15 - bytes in 2 Mbps data rate, the `ard` must be 500µS or more. If the custom ACK payload - is more than 5 bytes in 1 Mbps data rate, the `ard` must be 500µS or more. In 250kbps - data rate (even when there is no custom ACK payload) the `ard` must be 500µS or more. - - See `data_rate` attribute on how to set the data rate of the nRF24L01's transmissions. - -ack -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ack - - Use this attribute to set/check if the custom ACK payloads feature is enabled. Default - setting is `False`. - - - `True` enables the use of custom ACK payloads in the ACK packet when responding to - receiving transmissions. - - `False` disables the use of custom ACK payloads in the ACK packet when responding to - receiving transmissions. - - .. important:: - As `dynamic_payloads` and `auto_ack` attributes are required for this feature to work, - they are automatically enabled (on data pipe 0) as needed. However, it is required to - enable the `auto_ack` and `dynamic_payloads` features on all applicable pipes. - Disabling this feature does not disable the `auto_ack` and `dynamic_payloads` - attributes for any data pipe; they work just fine without this feature. - -interrupt_config() -****************************** - -.. automethod:: circuitpython_nrf24l01.rf24.RF24.interrupt_config - - The digital signal from the nRF24L01's IRQ pin is active LOW. (write-only) - - :param bool data_recv: If this is `True`, then IRQ pin goes active when there is new data - to read in the RX FIFO buffer. Default setting is `True` - :param bool data_sent: If this is `True`, then IRQ pin goes active when a payload from TX - buffer is successfully transmit. Default setting is `True` - :param bool data_fail: If this is `True`, then IRQ pin goes active when maximum number of - attempts to re-transmit the packet have been reached. If `auto_ack` attribute is - disabled, then this IRQ event is not used. Default setting is `True` - - .. note:: To fetch the status (not configuration) of these IRQ flags, use the `irq_df`, - `irq_ds`, `irq_dr` attributes respectively. - - .. tip:: Paraphrased from nRF24L01+ Specification Sheet: - - The procedure for handling ``data_recv`` IRQ should be: - - 1. read payload through `recv()` - 2. clear ``dataReady`` status flag (taken care of by using `recv()` in previous step) - 3. read FIFO_STATUS register to check if there are more payloads available in RX FIFO - buffer. A call to `pipe` (may require `update()` to be called), `any()` or even - ``(False,True)`` as parameters to `fifo()` will get this result. - 4. if there is more data in RX FIFO, repeat from step 1 - -data_rate -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.data_rate - - A valid input value is: - - - ``1`` sets the frequency data rate to 1 Mbps - - ``2`` sets the frequency data rate to 2 Mbps - - ``250`` sets the frequency data rate to 250 Kbps - - Any invalid input throws a `ValueError` exception. Default is 1 Mbps. - - .. warning:: 250 Kbps can be buggy on the non-plus models of the nRF24L01 product line. If - you use 250 Kbps data rate, and some transmissions report failed by the transmitting - nRF24L01, even though the same packet in question actually reports received by the - receiving nRF24L01, then try a higher data rate. CAUTION: Higher data rates mean less - maximum distance between nRF24L01 transceivers (and vise versa). - -channel -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.channel - - A valid input value must be in range [0, 125] (that means [2.4, 2.525] GHz). Otherwise a - `ValueError` exception is thrown. Default is ``76`` (2.476 GHz). - -crc -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.crc - - CRC is a way of making sure that the transmission didn't get corrupted over the air. - - A valid input value must be: - - - ``0`` disables CRC (no anti-corruption of data) - - ``1`` enables CRC encoding scheme using 1 byte (weak anti-corruption of data) - - ``2`` enables CRC encoding scheme using 2 bytes (better anti-corruption of data) - - Any invalid input throws a `ValueError` exception. Default is enabled using 2 bytes. - - .. note:: The nRF24L01 automatically enables CRC if automatic acknowledgment feature is - enabled (see `auto_ack` attribute). - -pa_level -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pa_level - - Higher levels mean the transmission will cover a longer distance. Use this attribute to - tweak the nRF24L01 current consumption on projects that don't span large areas. - - A valid input value is: - - - ``-18`` sets the nRF24L01's power amplifier to -18 dBm (lowest) - - ``-12`` sets the nRF24L01's power amplifier to -12 dBm - - ``-6`` sets the nRF24L01's power amplifier to -6 dBm - - ``0`` sets the nRF24L01's power amplifier to 0 dBm (highest) - - If this attribute is set to a `list` or `tuple`, then the list/tuple must contain the - desired power amplifier level (from list above) at index 0 and a `bool` to control - the Low Noise Amplifier (LNA) feature at index 1. All other indices will be discarded. - - .. note:: - The LNA feature only applies to the nRF24L01 (non-plus variant). This - includes boards with the RFX24C01-based PA/LNA muxing IC attached to an - SMA-type detachable antenna. - - Any invalid input will invoke the default of 0 dBm with LNA enabled. - -is_lna_enabled -****************************** - -.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.is_lna_enabled - - See `pa_level` attribute about how to set this. Default is always enabled, but this - feature is specific to certain nRF24L01-based circuits. Check with your module's - manufacturer to see is it can toggle the Low Noise Amplifier feature. - -fake_ble module -=============== - -.. automodule:: circuitpython_nrf24l01.fake_ble - :members: diff --git a/docs/basic_api.rst b/docs/basic_api.rst new file mode 100644 index 0000000..2f61c75 --- /dev/null +++ b/docs/basic_api.rst @@ -0,0 +1,204 @@ + +Basic API +--------- + +Constructor +****************** + +.. autoclass:: circuitpython_nrf24l01.rf24.RF24 + :no-members: + + This class aims to be compatible with other devices in the nRF24xxx product line that + implement the Nordic proprietary Enhanced ShockBurst Protocol (and/or the legacy + ShockBurst Protocol), but officially only supports (through testing) the nRF24L01 and + nRF24L01+ devices. + + :param ~busio.SPI spi: The object for the SPI bus that the nRF24L01 is connected to. + + .. tip:: This object is meant to be shared amongst other driver classes (like + adafruit_mcp3xxx.mcp3008 for example) that use the same SPI bus. Otherwise, multiple + devices on the same SPI bus with different spi objects may produce errors or + undesirable behavior. + :param ~digitalio.DigitalInOut csn: The digital output pin that is connected to the nRF24L01's + CSN (Chip Select Not) pin. This is required. + :param ~digitalio.DigitalInOut ce: The digital output pin that is connected to the nRF24L01's + CE (Chip Enable) pin. This is required. + :param int spi_frequency: Specify which SPI frequency (in Hz) to use on the SPI bus. This + parameter only applies to the instantiated object and is made persistent via + :py:class:`~adafruit_bus_device.spi_device`. + +open_tx_pipe() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_tx_pipe + + :param bytearray address: The virtual address of the receiving nRF24L01. The address + specified here must match the address set to one of the RX data pipes of the receiving + nRF24L01. The existing address can be altered by writting a bytearray with a length + less than 5. The nRF24L01 will use the first `address_length` number of bytes for the + RX address on the specified data pipe. + + .. note:: There is no option to specify which data pipe to use because the nRF24L01 only + uses data pipe 0 in TX mode. Additionally, the nRF24L01 uses the same data pipe (pipe + 0) for receiving acknowledgement (ACK) packets in TX mode when the `auto_ack` attribute + is enabled for data pipe 0. Thus, RX pipe 0 is appropriated with the TX address + (specified here) when `auto_ack` is enabled for data pipe 0. + +close_rx_pipe() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.close_rx_pipe + + :param int pipe_number: The data pipe to use for RX transactions. This must be in range + [0, 5]. Otherwise a `ValueError` exception is thrown. + +open_rx_pipe() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_rx_pipe + + If `dynamic_payloads` attribute is disabled for the specifed data pipe, then the + `payload_length` attribute is used to define the expected length of the static RX payload + on the specified data pipe. + + :param int pipe_number: The data pipe to use for RX transactions. This must be in range + [0, 5]. Otherwise a `ValueError` exception is thrown. + :param bytearray address: The virtual address to the receiving nRF24L01. If using a + ``pipe_number`` greater than 1, then only the MSByte of the address is written, so make + sure MSByte (first character) is unique among other simultaneously receiving addresses. + The existing address can be altered by writing a bytearray with a length less than 5. + The nRF24L01 will use the first `address_length` number of bytes for the RX address on + the specified data pipe. + + .. note:: The nRF24L01 shares the addresses' last 4 LSBytes on data pipes 2 through + 5. These shared LSBytes are determined by the address set to data pipe 1. + +listen +****************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.listen + + Setting this attribute incorporates the proper transitioning to/from RX mode as it involves + playing with the `power` attribute and the nRF24L01's CE pin. This attribute does not power + down the nRF24L01, but will power it up when needed; use `power` attribute set to `False` + to put the nRF24L01 to sleep. + + A valid input value is a `bool` in which: + + - `True` enables RX mode. Additionally, per `Appendix B of the nRF24L01+ Specifications + Sheet `_, this attribute + flushes the RX FIFO, clears the `irq_dr` status flag, and puts nRF24L01 in power up + mode. Notice the CE pin is be held HIGH during RX mode. + - `False` disables RX mode. As mentioned in above link, this puts nRF24L01's power in + Standby-I (CE pin is LOW meaning low current & no transmissions) mode which is ideal + for post-reception work. Disabing RX mode doesn't flush the RX/TX FIFO buffers, so + remember to flush your 3-level FIFO buffers when appropriate using `flush_tx()` or + `flush_rx()` (see also the `recv()` function). + +any() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.any + + :returns: + - `int` of the size (in bytes) of an available RX payload (if any). + - ``0`` if there is no payload in the RX FIFO buffer. + +recv() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.recv + + This function can also be used to fetch the last ACK packet's payload if `ack` is enabled. + + :param int length: An optional parameter to specify how many bytes to read from the RX + FIFO buffer. This parameter is not contrained in any way. + + - If this parameter is less than the length of the first available payload in the + RX FIFO buffer, then the payload will remain in the RX FIFO buffer until the + entire payload is fetched by this function. + - If this parameter is greater than the next available payload's length, then + additional data from other payload(s) in the RX FIFO buffer are returned. + + .. note:: + The nRF24L01 will repeatedly return the last byte fetched from the RX FIFO + buffer when there is no data to return (even if the RX FIFO is empty). Be + aware that a payload is only removed from the RX FIFO buffer when the entire + payload has been fetched by this function. Notice that this function always + starts reading data from the first byte of the first available payload (if + any) in the RX FIFO buffer. + :returns: + A `bytearray` of the RX payload data or `None` if there is no payload. If the + ``length`` parameter is not specified, then one of the following two scenarios is + applied. + + - If the `dynamic_payloads` attribute is disabled, then the returned bytearray's + length is equal to the user defined `payload_length` attribute for the data pipe + that received the payload. + - If the `dynamic_payloads` attribute is enabled, then the returned bytearray's length + is equal to the payload's length + +send() +****************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.send + + :returns: + - `list` if a list or tuple of payloads was passed as the ``buf`` parameter. Each item + in the returned list will contain the returned status for each corresponding payload + in the list/tuple that was passed. The return statuses will be in one of the + following forms: + - `False` if transmission fails. Transmission failure can only be detected if `arc` + is greater than ``0``. + - `True` if transmission succeeds. + - `bytearray` or `None` when the `ack` attribute is `True`. Because the payload + expects a responding custom ACK payload, the response is returned (upon successful + transmission) as a `bytearray` (or `None` if ACK payload is empty) + + :param bytearray,list,tuple buf: The payload to transmit. This bytearray must have a + length in range [1, 32], otherwise a `ValueError` exception is thrown. This can + also be a list or tuple of payloads (`bytearray`); in which case, all items in the + list/tuple are processed for consecutive transmissions. + + - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's + length is less than the `payload_length` attribute for pipe 0, then this bytearray + is padded with zeros until its length is equal to the `payload_length` attribute for + pipe 0. + - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this bytearray's + length is greater than `payload_length` attribute for pipe 0, then this bytearray's + length is truncated to equal the `payload_length` attribute for pipe 0. + :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait for + an acknowledgment from the receiving nRF24L01. This parameter directly controls a + ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information + about the payload). Therefore, it takes advantage of an nRF24L01 feature specific to + individual payloads, and its value is not saved anywhere. You do not need to specify + this for every payload if the `arc` attribute is disabled, however setting this + parameter to `True` will work despite the `arc` attribute's setting. + + .. note:: Each transmission is in the form of a packet. This packet contains sections + of data around and including the payload. `See Chapter 7.3 in the nRF24L01 + Specifications Sheet `_ for more + details. + :param int force_retry: The number of brute-force attempts to `resend()` a failed + transmission. Default is 0. This parameter has no affect on transmissions if `arc` is + ``0`` or ``ask_no_ack`` parameter is set to `True`. Each re-attempt still takes + advantage of `arc` & `ard` attributes. During multi-payload processing, this + parameter is meant to slow down CircuitPython devices just enough for the Raspberry + Pi to catch up (due to the Raspberry Pi's seemingly slower SPI speeds). See also + notes on `resend()` as using this parameter carries the same implications documented + there. This parameter has no effect if the ``ask_no_ack`` parameter is set to `True` + or if `arc` is disabled. + + .. tip:: It is highly recommended that `arc` attribute is enabled (greater than ``0``) + when sending multiple payloads. Test results with the `arc` attribute disabled were + rather poor (less than 79% received by a Raspberry Pi). This same advice applies to + the ``ask_no_ack`` parameter (leave it as `False` for multiple payloads). + .. warning:: The nRF24L01 will block usage of the TX FIFO buffer upon failed + transmissions. Failed transmission's payloads stay in TX FIFO buffer until the MCU + calls `flush_tx()` and `clear_status_flags()`. Therefore, this function will discard + failed transmissions' payloads when sending a list or tuple of payloads, so it can + continue to process through the list/tuple even if any payload fails to be + acknowledged. + diff --git a/docs/ble_api.rst b/docs/ble_api.rst new file mode 100644 index 0000000..5b067d6 --- /dev/null +++ b/docs/ble_api.rst @@ -0,0 +1,140 @@ +fake_ble module +=============== + +Limitations +----------- + +This module uses the `RF24` class to make the nRF24L01 imitate a +Bluetooth-Low-Emissions (BLE) beacon. A BLE beacon can send (referred to as +advertise) data to any BLE compatible device (ie smart devices with Bluetooth +4.0 or later) that is listening. + +Original research was done by `Dmitry Grinberg and his write-up (including C +source code) can be found here +`_ +As this technique can prove invaluable in certain project designs, the code +here is simply ported to work on CircuitPython. + +.. important:: Because the nRF24L01 wasn't designed for BLE advertising, it + has some limitations that helps to be aware of. + + 1. the maximum payload length is shortened to 21 bytes (when not + broadcasting a device + :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.name`). + 2. the channels that BLE use are limited to the following three: 2.402 + GHz, 2.426 GHz, and 2.480 GHz + 3. :py:attr:`~circuitpython_nrf24l01.rf24.RF24.crc` is disabled in the + nRF24L01 firmware as BLE requires 3 bytes + (:py:func:`~circuitpython_nrf24l01.fake_ble.crc24_ble()`) and nRF24L01 + only handles a maximum of 2. Thus, we have appended the required 3 + bytes of CRC24 into the payload. + 4. :py:attr:`~circuitpython_nrf24l01.rf24.RF24.address_length` of BLE + packet only uses 4 bytes, so we have set that accordingly. + 5. The :py:attr:`~circuitpython_nrf24l01.rf24.RF24.auto_ack` (automatic + acknowledgment) feature of the nRF24L01 is useless when tranmitting to + BLE devices, thus it is disabled as well as automatic re-transmit + (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.arc`) and custom ACK + payloads (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.ack`) which both + depend on the automatic acknowledgments feature. + 6. The :py:attr:`~circuitpython_nrf24l01.rf24.RF24.dynamic_payloads` + feature of the nRF24L01 isn't compatible with BLE specifications. Thus, + we have disabled it. + 7. BLE specifications only allow using 1 Mbps RF + :py:attr:`~circuitpython_nrf24l01.rf24.RF24.data_rate`, so that too has + been hard coded. + 8. Only the "on data sent" + (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_ds`) & "on data ready" + (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr`) events will have + an effect on the interrupt (IRQ) pin. The "on data fail" + (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_df`), is never + triggered because + :py:attr:`~circuitpython_nrf24l01.rf24.RF24.auto_ack` feature is + disabled. + +.. currentmodule:: circuitpython_nrf24l01.fake_ble + +helpers +---------------- + +swap_bits +***************** + +.. autofunction:: circuitpython_nrf24l01.fake_ble.swap_bits + +reverse_bits +***************** + +.. autofunction:: circuitpython_nrf24l01.fake_ble.reverse_bits + +chunk +***************** + +.. autofunction:: circuitpython_nrf24l01.fake_ble.chunk + +crc24_ble +***************** + +.. autofunction:: circuitpython_nrf24l01.fake_ble.crc24_ble + +BLE_FREQ +***************** + +.. autodata:: circuitpython_nrf24l01.fake_ble.BLE_FREQ + +FakeBLE class +------------- + +.. autoclass:: circuitpython_nrf24l01.fake_ble.FakeBLE + +to_iphone +************ + +.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.to_iphone + +mac +************ + +.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.mac + +name +************ + +.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.name + +show_pa_level +************* + +.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.show_pa_level + +hop_channel() +************* + +.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.hop_channel + +whiten() +************* + +.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.whiten + +advertise() +************* + +.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.advertise + +Service related classes +----------------------- + +abstract parent +*************** + +.. autoclass:: circuitpython_nrf24l01.fake_ble.ServiceData + :members: + +derivitive children +******************* + +.. autoclass:: circuitpython_nrf24l01.fake_ble.TemperatureServiceData + :members: + +.. autoclass:: circuitpython_nrf24l01.fake_ble.BatteryServiceData + :members: diff --git a/docs/configure_api.rst b/docs/configure_api.rst new file mode 100644 index 0000000..e03b6e5 --- /dev/null +++ b/docs/configure_api.rst @@ -0,0 +1,235 @@ + +Configuration API +----------------- + +CSN_DELAY +****************************** + +.. autodata:: circuitpython_nrf24l01.rf24.CSN_DELAY + +dynamic_payloads +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.dynamic_payloads + + Default setting is enabled on all pipes. + + - `True` or ``1`` enables nRF24L01's dynamic payload length feature for all data pipes. The + `payload_length` attribute is ignored when this feature is enabled for respective or all + data pipes. + - `False` or ``0`` disables nRF24L01's dynamic payload length feature for all data pipes. + Be sure to adjust the `payload_length` attribute accordingly when this feature is + disabled for any data pipes. + - A `list` or `tuple` containing booleans or integers can be used control this feature per + data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be + ignored since there are only 6 data pipes. + + .. note:: + This attribute mostly relates to RX operations, but data pipe 0 applies to TX + operations also. + +payload_length +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.payload_length + + If the `dynamic_payloads` attribute is enabled for a certain data pipe, this attribute has + no affect on that data pipe. When `dynamic_payloads` is disabled for a certain data pipe, + this attribute is used to specify the payload length on that data pipe in RX mode. + + A valid input value must be: + + * an `int` in range [1, 32]. Otherwise a `ValueError` exception is thrown. + * a `list` or `tuple` containing integers can be used to control this attribute per + data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will + be ignored since there are only 6 data pipes. if a index's value is ``0``, then the + existing setting will persist (not be changed). + + Default is set to the nRF24L01's maximum of 32 (on all data pipes). + + .. note:: + This attribute mostly relates to RX operations, but data pipe 0 applies to TX + operations also. + +auto_ack +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.auto_ack + + Default setting is enabled on all data pipes. + + - `True` or ``1`` enables transmitting automatic acknowledgment packets for all data pipes. + The CRC (cyclic redundancy checking) is enabled automatically by the nRF24L01 if the + `auto_ack` attribute is enabled for any data pipe (see also `crc` attribute). + - `False` or ``0`` disables transmitting automatic acknowledgment packets for all data + pipes. The `crc` attribute will remain unaffected when disabling this attribute for any + data pipes. + - A `list` or `tuple` containing booleans or integers can be used control this feature per + data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be + ignored since there are only 6 data pipes. + + .. note:: + This attribute mostly relates to RX operations, but data pipe 0 applies to TX + operations also. + +arc +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.arc + + The `auto_ack` attribute must be enabled on the receiving nRF24L01 respective data pipe, + otherwise this attribute will make `send()` seem like it failed. + + A valid input value must be in range [0, 15]. Otherwise a `ValueError` exception is thrown. + Default is set to 3. A value of ``0`` disables the automatic re-transmit feature and + considers all payload transmissions a success. + +ard +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ard + + During this time, the nRF24L01 is listening for the ACK packet. If the + `auto_ack` attribute is disabled, this attribute is not applied. + + A valid input value must be in range [250, 4000]. Otherwise a `ValueError` exception is + thrown. Default is 1500 for reliability. If this is set to a value that is not multiple of + 250, then the highest multiple of 250 that is no greater than the input value is used. + + .. note:: Paraphrased from nRF24L01 specifications sheet: + + Please take care when setting this parameter. If the custom ACK payload is more than 15 + bytes in 2 Mbps data rate, the `ard` must be 500µS or more. If the custom ACK payload + is more than 5 bytes in 1 Mbps data rate, the `ard` must be 500µS or more. In 250kbps + data rate (even when there is no custom ACK payload) the `ard` must be 500µS or more. + + See `data_rate` attribute on how to set the data rate of the nRF24L01's transmissions. + +ack +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ack + + Use this attribute to set/check if the custom ACK payloads feature is enabled. Default + setting is `False`. + + - `True` enables the use of custom ACK payloads in the ACK packet when responding to + receiving transmissions. + - `False` disables the use of custom ACK payloads in the ACK packet when responding to + receiving transmissions. + + .. important:: + As `dynamic_payloads` and `auto_ack` attributes are required for this feature to work, + they are automatically enabled (on data pipe 0) as needed. However, it is required to + enable the `auto_ack` and `dynamic_payloads` features on all applicable pipes. + Disabling this feature does not disable the `auto_ack` and `dynamic_payloads` + attributes for any data pipe; they work just fine without this feature. + +interrupt_config() +****************************** + +.. automethod:: circuitpython_nrf24l01.rf24.RF24.interrupt_config + + The digital signal from the nRF24L01's IRQ pin is active LOW. (write-only) + + :param bool data_recv: If this is `True`, then IRQ pin goes active when there is new data + to read in the RX FIFO buffer. Default setting is `True` + :param bool data_sent: If this is `True`, then IRQ pin goes active when a payload from TX + buffer is successfully transmit. Default setting is `True` + :param bool data_fail: If this is `True`, then IRQ pin goes active when maximum number of + attempts to re-transmit the packet have been reached. If `auto_ack` attribute is + disabled, then this IRQ event is not used. Default setting is `True` + + .. note:: To fetch the status (not configuration) of these IRQ flags, use the `irq_df`, + `irq_ds`, `irq_dr` attributes respectively. + + .. tip:: Paraphrased from nRF24L01+ Specification Sheet: + + The procedure for handling ``data_recv`` IRQ should be: + + 1. read payload through `recv()` + 2. clear ``dataReady`` status flag (taken care of by using `recv()` in previous step) + 3. read FIFO_STATUS register to check if there are more payloads available in RX FIFO + buffer. A call to `pipe` (may require `update()` to be called), `any()` or even + ``(False,True)`` as parameters to `fifo()` will get this result. + 4. if there is more data in RX FIFO, repeat from step 1 + +data_rate +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.data_rate + + A valid input value is: + + - ``1`` sets the frequency data rate to 1 Mbps + - ``2`` sets the frequency data rate to 2 Mbps + - ``250`` sets the frequency data rate to 250 Kbps + + Any invalid input throws a `ValueError` exception. Default is 1 Mbps. + + .. warning:: 250 Kbps can be buggy on the non-plus models of the nRF24L01 product line. If + you use 250 Kbps data rate, and some transmissions report failed by the transmitting + nRF24L01, even though the same packet in question actually reports received by the + receiving nRF24L01, then try a higher data rate. CAUTION: Higher data rates mean less + maximum distance between nRF24L01 transceivers (and vise versa). + +channel +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.channel + + A valid input value must be in range [0, 125] (that means [2.4, 2.525] GHz). Otherwise a + `ValueError` exception is thrown. Default is ``76`` (2.476 GHz). + +crc +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.crc + + CRC is a way of making sure that the transmission didn't get corrupted over the air. + + A valid input value must be: + + - ``0`` disables CRC (no anti-corruption of data) + - ``1`` enables CRC encoding scheme using 1 byte (weak anti-corruption of data) + - ``2`` enables CRC encoding scheme using 2 bytes (better anti-corruption of data) + + Any invalid input throws a `ValueError` exception. Default is enabled using 2 bytes. + + .. note:: The nRF24L01 automatically enables CRC if automatic acknowledgment feature is + enabled (see `auto_ack` attribute). + +pa_level +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pa_level + + Higher levels mean the transmission will cover a longer distance. Use this attribute to + tweak the nRF24L01 current consumption on projects that don't span large areas. + + A valid input value is: + + - ``-18`` sets the nRF24L01's power amplifier to -18 dBm (lowest) + - ``-12`` sets the nRF24L01's power amplifier to -12 dBm + - ``-6`` sets the nRF24L01's power amplifier to -6 dBm + - ``0`` sets the nRF24L01's power amplifier to 0 dBm (highest) + + If this attribute is set to a `list` or `tuple`, then the list/tuple must contain the + desired power amplifier level (from list above) at index 0 and a `bool` to control + the Low Noise Amplifier (LNA) feature at index 1. All other indices will be discarded. + + .. note:: + The LNA feature only applies to the nRF24L01 (non-plus variant). This + includes boards with the RFX24C01-based PA/LNA muxing IC attached to an + SMA-type detachable antenna. + + Any invalid input will invoke the default of 0 dBm with LNA enabled. + +is_lna_enabled +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.is_lna_enabled + + See `pa_level` attribute about how to set this. Default is always enabled, but this + feature is specific to certain nRF24L01-based circuits. Check with your module's + manufacturer to see is it can toggle the Low Noise Amplifier feature. diff --git a/docs/index.rst b/docs/index.rst index 46a5a3c..af47f39 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -15,10 +15,29 @@ Table of Contents examples .. toctree:: - :caption: API Reference + :caption: Troubleshooting :maxdepth: 5 - api + troubleshooting + +.. toctree:: + :caption: RF24 API Reference + + basic_api + + +.. toctree:: + + advanced_api + +.. toctree:: + + configure_api + +.. toctree:: + :caption: BLE API Reference + + ble_api .. toctree:: :caption: Store Links diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst new file mode 100644 index 0000000..3f51903 --- /dev/null +++ b/docs/troubleshooting.rst @@ -0,0 +1,114 @@ + +Troubleshooting info +==================== + +.. important:: The nRF24L01 has 3 key features that can be interdependent of each other. Their + priority of dependence is as follows: + + 1. `auto_ack` feature provides transmission verification by using the RX nRF24L01 to + automatically and imediatedly send an acknowledgment (ACK) packet in response to freshly + received payloads. `auto_ack` does not require `dynamic_payloads` to be enabled. + 2. `dynamic_payloads` feature allowing either TX/RX nRF24L01 to be able to send/receive + payloads with their size written into the payloads' packet. With this disabled, both + RX/TX nRF24L01 must use matching `payload_length` attributes. For `dynamic_payloads` to + be enabled, the `auto_ack` feature must be enabled. Although, the `auto_ack` feature + isn't required when the `dynamic_payloads` feature is disabled. + 3. `ack` feature allows the MCU to append a payload to the ACK packet, thus instant + bi-directional communication. A transmitting ACK payload must be loaded into the + nRF24L01's TX FIFO buffer (done using `load_ack()`) BEFORE receiving the payload that + is to be acknowledged. Once transmitted, the payload is released from the TX FIFO + buffer. This feature requires the `auto_ack` and `dynamic_payloads` features enabled. + +Remeber that the nRF24L01's FIFO (first-in,first-out) buffer has 3 levels. This means that +there can be up to 3 payloads waiting to be read (RX) and up to 3 payloads waiting to be +transmit (TX). + +With the `auto_ack` feature enabled, you get: + + * cyclic redundancy checking (`crc`) automatically enabled + * to change amount of automatic re-transmit attempts and the delay time between them. + See the `arc` and `ard` attributes. + +.. note:: A word on pipes vs addresses vs channels. + + You should think of the data pipes as a "parking spot" for your payload. There are only six + data pipes on the nRF24L01, thus it can simultaneously "listen" to a maximum of 6 other + nRF24L01 radios. However, it can only "talk" to 1 other nRF24L01 at a time). + + The specified address is not the address of an nRF24L01 radio, rather it is more like a + path that connects the endpoints. When assigning addresses to a data pipe, you can use any + 5 byte long address you can think of (as long as the first byte is unique among + simultaneously broadcasting addresses), so you're not limited to communicating with only + the same 6 nRF24L01 radios (more on this when we officially support "Multiciever" mode). + + Finnaly, the radio's channel is not be confused with the radio's pipes. Channel selection + is a way of specifying a certain radio frequency (frequency = [2400 + channel] MHz). + Channel defaults to 76 (like the arduino library), but options range from 0 to 125 -- + that's 2.4 GHz to 2.525 GHz. The channel can be tweaked to find a less occupied frequency + amongst Bluetooth, WiFi, or other ambient signals that use the same spectrum of + frequencies. + +.. warning:: + For successful transmissions, most of the endpoint trasceivers' settings/features must + match. These settings/features include: + + * The RX pipe's address on the receiving nRF24L01 (passed to `open_rx_pipe()`) MUST match + the TX pipe's address on the transmitting nRF24L01 (passed to `open_tx_pipe()`) + * `address_length` + * `channel` + * `data_rate` + * `dynamic_payloads` + * `payload_length` only when `dynamic_payloads` is disabled + * `auto_ack` on the recieving nRF24L01 must be enabled if `arc` is greater than 0 on the + transmitting nRF24L01 + * custom `ack` payloads + * `crc` + + In fact the only attributes that aren't required to match on both endpoint transceivers + would be the identifying data pipe number (passed to `open_rx_pipe()` or `load_ack()`), + `pa_level`, `arc`, & `ard` attributes. The ``ask_no_ack`` feature can be used despite the + settings/features configuration (see `send()` & `write()` function parameters for more + details). + +About the lite version +====================== + +This library contains a "lite" version of ``rf24.py`` titled ``rf24_lite.py``. It has been +developed to save space on microcontrollers with limited amount of RAM and/or storage (like +boards using the ATSAMD21 M0). The following functionality has been removed from the lite +version: + + * `address()` removed. + * `what_happened()` removed. However you can use the following function to dump all + available registers' values (for advanced users): + + .. code-block:: python + + # let `nrf` be the instantiated RF24 object + def dump_registers(end=0x1e): + for i in range(end): + if i in (0xA, 0xB, 0x10): + print(hex(i), "=", nrf._reg_read_bytes(i)) + elif i not in (0x18, 0x19, 0x1a, 0x1b): + print(hex(i), "=", hex(nrf._reg_read(i))) + * `fifo()` removed. + * `dynamic_payloads` applies to all pipes, not individual pipes. + * `payload_length` applies to all pipes, not individual pipes. + * `read_ack()` removed. This is deprecated on next major release anyway; use `recv()` + instead. + * `load_ack()` is available, but it will not throw exceptions for malformed ``buf`` or + invalid ``pipe_number`` parameters. + * `crc` removed. 2-bytes encoding scheme (CRC16) is always enabled. + * `auto_ack` removed. This is always enabled for all pipes. Pass ``ask_no_ack`` parameter + as `True` to `send()` or `write()` to disable automatic acknowledgement for TX + operations. + * `is_lna_enabled` removed. This will always be enabled, and `pa_level` will not accept a + `list` or `tuple`. This only affects certain boards anyway. + * `rpd`, `start_carrier_wave()`, & `stop_carrier_wave()` removed. These only perform a + test of the nRF24L01's hardware. + * `CSN_DELAY` removed. This is hard-coded to 5 milliseconds + * All comments and docstrings removed, meaning ``help()`` will not provide any specific + information. Exception prompts have also been reduced and adjusted accordingly. + * Cannot switch between different radio configurations using context manager (the `with` + blocks). It is advised that only one `RF24` object be instantiated when RAM is limited + (less than or equal to 32KB). diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index 04749c3..8f8332b 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -2,11 +2,16 @@ This example of using the nRF24L01 as a 'fake' Buetooth Beacon """ import time -import struct import board import digitalio as dio from circuitpython_nrf24l01.rf24 import RF24 -from circuitpython_nrf24l01.fake_ble import FakeBLE, chunk, SERVICE_TYPES, ServiceData +from circuitpython_nrf24l01.fake_ble import ( + FakeBLE, + chunk, + ServiceData, + BatteryServiceData, + TemperatureServiceData, +) # change these (digital output) pins accordingly ce = dio.DigitalInOut(board.D4) @@ -24,11 +29,17 @@ # this can be changed at any time using the attribute nrf.name = b"foobar" +# if broadcasting to an Android, set the to_iphone attribute to False +# if broadcasting to an iPhone, set the to_iphone attribute to True nrf.to_iphone = False # you can optionally set the arbitrary MAC address to be used as the -# BLE device's MAC address -nrf.mac = b'\x19\x12\x14\x26\x09\xE0' +# BLE device's MAC address. Otherwise this is randomly generated. +nrf.mac = b"\x19\x12\x14\x26\x09\xE0" + +# use the eddystone protocol from google to broadcast a URL +url_service = ServiceData(0xFEAA) +url_service.data = bytes([0x10, 0, 0x01]) + b"google.com" def _prompt(count, iterator): if (count - iterator) % 5 == 0 or (count - iterator) < 5: @@ -37,55 +48,58 @@ def _prompt(count, iterator): else: print(count - iterator, "advertisment left to go!") -def send_name(count=50): - """Sends out the device name twice a second.""" + +def master(count=50): + """Sends out the device information twice a second.""" # using the "with" statement is highly recommended if the nRF24L01 is # to be used for more than a BLE configuration + battery_service = BatteryServiceData() + # battery data is 1 unsigned byte representing a percentage + battery_service.data = 85 with nrf as ble: nrf.name = b"nRF24L01" nrf.show_pa_level = True for i in range(count): # advertise data this many times - _prompt(count, i) - # broadcast only the device name and MAC address - ble.advertise() - # channel hoping is automatically managed by send() per BLE specs + _prompt(count, i) # something to show that it isn't frozen + # broadcast only the device name, MAC address, & + # battery charge info; 0x16 means service data + ble.advertise(battery_service.buffer, data_type=0x16) + # channel hoping is recommended per BLE specs + ble.hop_channel() time.sleep(0.5) # wait till next broadcast - # nrf.show_pa_level returns to false when exiting a with statement block + # nrf.show_pa_level & nrf.name both are set to false when + # exiting a with statement block def send_temp(count=50): """Sends out a fake temperature twice a second.""" - temperature_service = ServiceData(SERVICE_TYPES["Health Thermometer"]) - temperature_service.data = struct.pack(">f", 42.0) - nrf.name = b"nRf24L01" - for i in range(count): - _prompt(count, i) - payload = temperature_service.buffer - # broadcast a device temperature; 0x16 means service data - nrf.advertise(payload, data_type=0x16) - time.sleep(0.2) - -def send_battery_time(count=50): - """Sends out a device's battery capacity twice a second.""" - nrf.name = None - time_service = ServiceData(SERVICE_TYPES["Current Time"]) - # the time data is seconds since Jan 1, 1970 - time_service.data = struct.pack(">i", time.time()) - battery_service = ServiceData(SERVICE_TYPES["Battery"]) - # battery data is 1 unsigned byte representing a percentage - battery_service.data = struct.pack(">B", 80) - payload = [ - chunk(time_service.buffer), - chunk(battery_service.buffer) - ] - for i in range(count): - _prompt(count, i) - nrf.advertise(payload) - time.sleep(0.2) - - -print("""\ + temperature_service = TemperatureServiceData() + temperature_service.data = 42.0 + with nrf as ble: + ble.name = b"nRF24L01" + for i in range(count): + _prompt(count, i) + # broadcast a device temperature; 0x16 means service data + ble.advertise(temperature_service.buffer, data_type=0x16) + # channel hoping is recommended per BLE specs + ble.hop_channel() + time.sleep(0.2) + + +def send_chunk(pl_chunk, count=50): + """Sends out a chunk of data twice a second.""" + with nrf as ble: + for i in range(count): + _prompt(count, i) + ble.advertise(pl_chunk, 0x16) + ble.hop_channel() + time.sleep(0.2) + + +print( + """\ nRF24L01 fake BLE beacon test.\n\ - Run send_name_pa_level() to broadcast the device name and pa_level\n\ - Run send_temperature() to broadcast a temperature\n\ - Run send_battery_time() to broadcast battery and time info""") + Run master() to broadcast the device name, pa_level, & battery charge\n\ + Run send_temperature() to broadcast the device name & a temperature\n\ + Run send_chunk() to broadcast custom chunk of info""" +) From a13f33105b9438fa5c58fd17198da5d87e41ceab Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Tue, 29 Sep 2020 04:44:23 -0700 Subject: [PATCH 094/127] keep to_iphone true in BLE example --- examples/nrf24l01_fake_ble_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index 8f8332b..369127f 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -31,7 +31,7 @@ # if broadcasting to an Android, set the to_iphone attribute to False # if broadcasting to an iPhone, set the to_iphone attribute to True -nrf.to_iphone = False +# nrf.to_iphone = False # you can optionally set the arbitrary MAC address to be used as the # BLE device's MAC address. Otherwise this is randomly generated. From 5e9112de887ea75c8f26b8d856928996e81ff78f Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Tue, 29 Sep 2020 04:50:11 -0700 Subject: [PATCH 095/127] trim trailing whitespace; super w/o args --- circuitpython_nrf24l01/fake_ble.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index d2d320a..ba231c9 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -342,7 +342,7 @@ def __init__(self, type_t): @property def data(self): """The service's data. This is a `bytearray`, and its format is - defined by Bluetooth Service Specifications (and GATT supplemental + defined by Bluetooth Service Specifications (and GATT supplemental specifications).""" return self._data @@ -365,7 +365,7 @@ class TemperatureServiceData(ServiceData): temperature data values as a `float` value.""" def __init__(self): - super(TemperatureServiceData, self).__init__(0x1809) + super().__init__(0x1809) @ServiceData.data.setter def data(self, value): @@ -380,7 +380,7 @@ class BatteryServiceData(ServiceData): battery charge percentage as a byte value.""" def __init__(self): - super(BatteryServiceData, self).__init__(0x180F) + super().__init__(0x180F) @ServiceData.data.setter def data(self, value): From 94441f06139bd81320c0fe6da5521f3ea687f745 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Tue, 29 Sep 2020 04:57:23 -0700 Subject: [PATCH 096/127] unused import in BLE example --- examples/nrf24l01_fake_ble_test.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index 369127f..690ddf4 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -7,7 +7,6 @@ from circuitpython_nrf24l01.rf24 import RF24 from circuitpython_nrf24l01.fake_ble import ( FakeBLE, - chunk, ServiceData, BatteryServiceData, TemperatureServiceData, @@ -37,9 +36,6 @@ # BLE device's MAC address. Otherwise this is randomly generated. nrf.mac = b"\x19\x12\x14\x26\x09\xE0" -# use the eddystone protocol from google to broadcast a URL -url_service = ServiceData(0xFEAA) -url_service.data = bytes([0x10, 0, 0x01]) + b"google.com" def _prompt(count, iterator): if (count - iterator) % 5 == 0 or (count - iterator) < 5: @@ -86,12 +82,17 @@ def send_temp(count=50): time.sleep(0.2) -def send_chunk(pl_chunk, count=50): +# use the eddystone protocol from google to broadcast a URL +url_service = ServiceData(0xFEAA) +url_service.data = bytes([0x10, 0, 0x01]) + b"google.com" + + +def send_url(count=50): """Sends out a chunk of data twice a second.""" with nrf as ble: for i in range(count): _prompt(count, i) - ble.advertise(pl_chunk, 0x16) + ble.advertise(url_service.buffer, 0x16) ble.hop_channel() time.sleep(0.2) @@ -101,5 +102,5 @@ def send_chunk(pl_chunk, count=50): nRF24L01 fake BLE beacon test.\n\ Run master() to broadcast the device name, pa_level, & battery charge\n\ Run send_temperature() to broadcast the device name & a temperature\n\ - Run send_chunk() to broadcast custom chunk of info""" + Run send_url() to broadcast custom URL link""" ) From 751fb9cf528d6b5cef2bdb5baecb4869cc47eeb8 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Tue, 29 Sep 2020 05:11:48 -0700 Subject: [PATCH 097/127] BLE API toc tree corrected --- docs/ble_api.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/ble_api.rst b/docs/ble_api.rst index 5b067d6..61af04d 100644 --- a/docs/ble_api.rst +++ b/docs/ble_api.rst @@ -1,5 +1,3 @@ -fake_ble module -=============== Limitations ----------- From 8cccfcc369f6ee88289910407c08e877015b4e1c Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Tue, 29 Sep 2020 22:06:27 -0700 Subject: [PATCH 098/127] this feels stable --- README.rst | 9 + circuitpython_nrf24l01/fake_ble.py | 265 +++++++++++------------------ docs/ble_api.rst | 213 +++++++++++++++++++++-- examples/nrf24l01_fake_ble_test.py | 71 +++++--- 4 files changed, 356 insertions(+), 202 deletions(-) diff --git a/README.rst b/README.rst index 86ce827..e104aad 100644 --- a/README.rst +++ b/README.rst @@ -68,6 +68,15 @@ Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading `the Adafruit library and driver bundle `_. +.. note:: This library supports Python 3.4 or newer, but Python 3.7 introduced + the function `time.monotonic_ns() `_ which returns an arbitrary time "counter" + as an `int` of nanoseconds. However, this function is not used in the + example scripts for backward compatibility reasons. Instead, we used + :py:func:`~time.monotonic()` which returns an arbitrary time "counter" as + a `float` of seconds. CircuitPython firmware supports both functions as of + v4.0. + Installing from PyPI ===================== diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index ba231c9..3703b09 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -19,30 +19,14 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -"""This module uses the `RF24` class to make the nRF24L01 imitate a -Bluetooth-Low-Emissions (BLE) beacon. A BLE beacon can send (referred to as -advertise) data to any BLE compatible device (ie smart devices with Bluetooth -4.0 or later) that is listening. - -Original research was done by `Dmitry Grinberg and his write-up (including C -source code) can be found here -`_ -As this technique can prove invaluable in certain project designs, the code -here is simply ported to work on CircuitPython. -""" +"""Original research was done by `Dmitry Grinberg and his write-up can be +found here `_""" from os import urandom import struct def swap_bits(original): - """reverses the bit order for a single byte. - - :returns: - An `int` containing the byte whose bits go from LSBit to MSBit - compared to the value passed to the ``original`` parameter. - :param int original: This should be a single unsigned byte, meaning the - parameters value can only range from 0 to 255. - """ + """This function reverses the bit order for a single byte.""" original &= 0xFF reverse = 0 for _ in range(8): @@ -53,55 +37,22 @@ def swap_bits(original): def reverse_bits(original): - """reverses the bit order into LSBit to MSBit. - - :returns: - A `bytearray` whose bytes still go from MSByte to LSByte, but each - byte's bits go from LSBit to MSBit. - :param bytearray,bytes original: The original buffer whose bits are to be - reversed. - """ - length = len(original) - 1 - ret = bytearray(length + 1) + """This function reverses the bit order into LSBit to MSBit for an entire + buffer protocol object.""" + ret = bytearray(len(original)) for i, byte in enumerate(original): ret[i] = swap_bits(byte) return ret def chunk(buf, data_type=0x16): - """containerize a chunk of data according to BLE specifications. - This chunk makes up a part of the advertising payload. - - :param bytearray,bytes buf: The actual data contained in the chunk. - :param int data_type: the type of data contained in the chunk. This is a - predefined number according to BLE specifications. - - .. important:: This function is called internally, but can also be used - to containerize multiple types of data in a single payload. - Broadcasting multiple types of data may require the `FakeBLE.name` - be set to `None` for reasons about the payload size limitations due - to using a nRf24L01 as a BLE beacon. - """ - # container = 1 Byte info about container length - # 1 Byte info describing datatype - # X bytes holding the data + """This function is used to pack data values into a block of data that + make up part of the BLE payload per Bluetooth Core Specifications.""" return bytearray([len(buf) + 1, data_type & 0xFF]) + buf def crc24_ble(data, deg_poly=0x65B, init_val=0x555555): - """Calculates a checksum of various sized buffers. this is exposed for - convenience. - - :param bytearray data: This `bytearray` of data to be uncorrupted. - :param int deg_poly: A preset "degree polynomial" in which each bit - represents a degree who's coefficient is 1. BLE specfications require - ``0x00065b`` (default value). - :param int init_val: This will be the initial value that the checksum - will use while shifting in the buffer data. BLE specfications require - ``0x555555`` (default value). - :returns: A 24-bit `bytearray` representing the checksum of the data (in - proper little endian). - """ + """This function calculates a checksum of various sized buffers.""" crc = init_val for byte in data: crc ^= swap_bits(byte) << 16 @@ -114,23 +65,11 @@ def crc24_ble(data, deg_poly=0x65B, init_val=0x555555): return reverse_bits((crc).to_bytes(3, "big")) -BLE_FREQ = (2, 26, 80) -""" BLE channel number is different from the nRF channel number. -These are the predefined channels used. - -* nRF channel 2 == BLE channel 37 -* nRF channel 26 == BLE channel 38 -* nRF channel 80 == BLE channel 39 -""" +BLE_FREQ = (2, 26, 80) #: The BLE channel number is different from the nRF channel number. class FakeBLE: - """Per the limitations of this technique, only `RF24.pa_level` is - available for configuration when advertising BLE data. - - :param ~circuitpython_nrf24l01.rf24.RF24 nrf: The object for the nRF24L01 - transceiver to use for fake BLE advertisements. - """ + """A class to implement BLE advertisements using the nRF24L01.""" def __init__(self, nrf): self._device = nrf @@ -143,17 +82,26 @@ def __init__(self, nrf): self._device.flush_rx() def __enter__(self): - self._device.address_length = 4 self._device.dynamic_payloads = False - self._device.auto_ack = False - self._device.crc = 0 - self._device.arc = 0 - self._device.power = 1 - self._device.payload_length = [32, 32] - # b"\x8E\x89\xBE\xD6" = proper address for BLE advertisments - # with bits reversed address is b"\x71\x91\x7D\x6B" + self._device.data_rate = 1 + self._device.ce_pin.value = 0 + try: + self._device.address_length = 4 + self._device.auto_ack = (0, 0) + self._device.crc = 0 + self._device.arc = 0 + self._device.payload_length = [32, 32] + self._device.power = 1 + except AttributeError: # for rf24_lite.py + self._device._reg_write(0, 6) + self._device._reg_write(1, 0) + self._device._reg_write(3, 2) + self._device._reg_write(4, 0) + self._device._reg_write(0x1D, 1) + self._device.payload_length = 32 self._device.open_rx_pipe(0, b"\x71\x91\x7D\x6B") self._device.open_tx_pipe(b"\x71\x91\x7D\x6B") + self._device.open_rx_pipe(1, b"\x71\x91\x7D\x6B") return self def __exit__(self, *exc): @@ -165,9 +113,7 @@ def __exit__(self, *exc): @property def to_iphone(self): """A `bool` to specify if advertisements should be compatible with - the iPhone. A value of `False` should still be compatible with other - Apple devices. Testing with this attribute as `False` showed - compatibility with a Mac desktop.""" + the iPhone.""" return self._to_iphone == 0x40 @to_iphone.setter @@ -177,17 +123,14 @@ def to_iphone(self, enable): @property def mac(self): """This attribute returns a 6-byte buffer that is used as the - arbitrary mac address of the BLE device being emulated. You can set - this attribute using a 6-byte `int` or `bytearray`. If this is set to - `None`, then a random 6-byte address is generated. - """ + arbitrary mac address of the BLE device being emulated.""" return self._mac @mac.setter def mac(self, address): if address is None: self._mac = urandom(6) - if isinstance(address, int): # assume its a 6-byte int + if isinstance(address, int): self._mac = (address).to_bytes(6, "little") elif isinstance(address, (bytearray, bytes)): self._mac = address @@ -196,15 +139,7 @@ def mac(self, address): @property def name(self): - """The broadcasted BLE name of the nRF24L01. This is not required. In - fact setting this attribute will subtract from the available payload - length (in bytes). Set this attribute to `None` to disable advertising the device name - - * payload_length has a maximum of 21 bytes when NOT broadcasting a - name for itself. - * payload_length has a maximum of [19 - length of name] bytes when - broadcasting a name for itself. - """ + """The broadcasted BLE name of the nRF24L01.""" return self._ble_name @name.setter @@ -219,18 +154,12 @@ def name(self, n): @property def show_pa_level(self): """If this attribute is `True`, the payload will automatically include - the nRF24L01's pa_level in the advertisement. The default value of - `False` will exclude this optional information. - - .. note:: This information takes up an extra 3 bytes, and is really - only useful for some applications to calculate proximity to the - nRF24L01 transceiver. - """ + the nRF24L01's pa_level in the advertisement.""" return bool(self._show_dbm) @show_pa_level.setter def show_pa_level(self, enable): - if enable and len(self.name) > 18: + if enable and len(self.name) > 16: raise ValueError("there is not enough room to show the pa_level.") self._show_dbm = bool(enable) @@ -243,24 +172,10 @@ def hop_channel(self): def whiten(self, data): """Whitening the BLE packet data ensures there's no long repeatition - of bits. This is done according to BLE specifications. - - :param bytearray data: The packet to whiten. - :returns: A `bytearray` of the ``data`` with the whitening algorythm - applied. - - .. warning:: This function uses the current channel being used as a - base case for the whitening coefficient. Do not call - `hop_channel()` before using this function to de-whiten received - payloads (which isn't officially supported yet). Note that - `advertise()` uses this function internally to prevent such - improper usage. - """ - data = bytearray(data) - coef = (self._chan + 37) | 0x40 + of bits.""" + data, coef = (bytearray(data), (self._chan + 37) | 0x40) for i, byte in enumerate(data): - res = 0 - mask = 1 + res, mask = (0, 1) for _ in range(8): if coef & 1: coef ^= 0x88 @@ -271,49 +186,34 @@ def whiten(self, data): return data def _make_payload(self, payload): - """assemble the entire packet to be transmitted as a payload.""" - # data is ordered like so: - # 1 byte PDU type (always 0x42) - # 1 byte payload size - # 6 byte random mac address - # 21 bytes of containerized data including descriptor and name - # 3 bytes for CRC24 - name_length = (len(self.name) + 2) if self.name is not None else 0 - if len(payload) > (21 - name_length - self._show_dbm * 3): + """Assemble the entire packet to be transmitted as a payload.""" + if self.available(payload) < 0: raise ValueError( "Payload exceeds maximum size. Configuration allows " - "{} bytes".format(21 - name_length - self._show_dbm * 3) + "{} bytes".format(self.available()) ) + name_length = (len(self.name) + 2) if self.name is not None else 0 pl_size = 9 + len(payload) + name_length + self._show_dbm * 3 - buf = bytes([self._to_iphone, pl_size]) + self.mac # header - buf += chunk(b"\x05", 1) # device descriptor + buf = bytes([self._to_iphone, pl_size]) + self.mac + buf += chunk(b"\x05", 1) pa_level = b"" if self._show_dbm: pa_level = chunk(struct.pack(">b", self._device.pa_level), 0x0A) buf += pa_level if name_length: - buf += chunk(self.name, 0x09) # device name + buf += chunk(self.name, 0x08) buf += payload buf += crc24_ble(buf) - # print("Payload size =", len(buf)) return buf - def advertise(self, buf=b"", data_type=0xFF): - """This function is used to broadcast a payload. - - :returns: Nothing as every transmission will register as a success - under the required settings for BLE beacons. - - :param bytearray buf: The payload to transmit. This bytearray must have - a length greater than 0 and less than 20, otherwise a `ValueError` - exception is thrown. This can also be a list or tuple of payloads - (`bytearray`); in which case, all items in the list/tuple are - processed for consecutive transmissions. + def available(self, hypothetical=b""): + """This function will calculates how much length (in bytes) is + available in the next payload.""" + name_length = (len(self.name) + 2) if self.name is not None else 0 + return 18 - name_length - self._show_dbm * 3 - len(hypothetical) - .. note:: If the name of the emulated BLE device is also to be - broadcast, then the 'name' attribute should be set prior to calling - `advertise()`. - """ + def advertise(self, buf=b"", data_type=0xFF): + """This function is used to broadcast a payload.""" if not isinstance(buf, (bytearray, bytes, list, tuple)): raise ValueError("buffer is an invalid format") payload = b"" @@ -328,12 +228,7 @@ def advertise(self, buf=b"", data_type=0xFF): class ServiceData: """An abstract helper class to package specific service data using - Bluetooth SIG defined 16-bit UUID flags to describe the data type. - - :param int type_t: The 16-bit "assigned number" defined by the - Bluetooth SIG to describe the service data. This parameter is - required. - """ + Bluetooth SIG defined 16-bit UUID flags to describe the data type.""" def __init__(self, type_t): self._type = struct.pack("B", value) + + +class UrlServiceData(ServiceData): + """This derivitive of the `ServiceData` class can be used to represent + URL data as a `bytes` value.""" + + def __init__(self): + super().__init__(0xFEAA) + self._type += bytes([0x10]) + struct.pack(">b", -25) + + @property + def pa_level_at_1_meter(self): + """The TX power level (in dBm) at 1 meter from the nRF24L01. + This defaults to -25 (due to testing) and must be a signed `int`.""" + return struct.unpack(">b", self._type[-1:])[0] + + @pa_level_at_1_meter.setter + def pa_level_at_1_meter(self, value): + self._type = self._type[:-1] + struct.pack(">b", int(value)) + + @ServiceData.data.setter + def data(self, value): + value = value.replace("http://www.", "\x00") + value = value.replace("https://www.", "\x01") + value = value.replace("http://", "\x02") + value = value.replace("https://", "\x03") + value = value.replace(".com/", "\x00") + value = value.replace(".org/", "\x01") + value = value.replace(".edu/", "\x02") + value = value.replace(".net/", "\x03") + value = value.replace(".info/", "\x04") + value = value.replace(".biz/", "\x05") + value = value.replace(".gov/", "\x06") + value = value.replace(".com", "\x07") + value = value.replace(".org", "\x08") + value = value.replace(".edu", "\x09") + value = value.replace(".net", "\x0A") + value = value.replace(".info", "\x0B") + value = value.replace(".biz", "\x0C") + self._data = value.replace(".gov", "\x0D").encode("utf-8") diff --git a/docs/ble_api.rst b/docs/ble_api.rst index 61af04d..c9f20cd 100644 --- a/docs/ble_api.rst +++ b/docs/ble_api.rst @@ -1,6 +1,6 @@ -Limitations ------------ +BLE Limitations +--------------- This module uses the `RF24` class to make the nRF24L01 imitate a Bluetooth-Low-Emissions (BLE) beacon. A BLE beacon can send (referred to as @@ -16,9 +16,19 @@ here is simply ported to work on CircuitPython. .. important:: Because the nRF24L01 wasn't designed for BLE advertising, it has some limitations that helps to be aware of. - 1. the maximum payload length is shortened to 21 bytes (when not + 1. the maximum payload length is shortened to **18** bytes (when not broadcasting a device - :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.name`). + :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.name`) nor + the nRF24L01 power amplifier level (using + :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.show_pa_level`). + This is can be calculated as: + + **32** (nRF24L01 maximum) - **6** (MAC address) - **5** (required + flags) - **3** (CRC checksum) = **18** + + Use the helper function + :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.available()` to + detirmine if your payload can be transmit. 2. the channels that BLE use are limited to the following three: 2.402 GHz, 2.426 GHz, and 2.480 GHz 3. :py:attr:`~circuitpython_nrf24l01.rf24.RF24.crc` is disabled in the @@ -54,56 +64,136 @@ here is simply ported to work on CircuitPython. helpers ---------------- -swap_bits +swap_bits() ***************** .. autofunction:: circuitpython_nrf24l01.fake_ble.swap_bits -reverse_bits + :returns: + An `int` containing the byte whose bits go from LSBit to MSBit + compared to the value passed to the ``original`` parameter. + :param int original: This should be a single unsigned byte, meaning the + parameters value can only range from 0 to 255. + +reverse_bits() ***************** .. autofunction:: circuitpython_nrf24l01.fake_ble.reverse_bits -chunk + :returns: + A `bytearray` whose bytes still go from MSByte to LSByte, but each + byte's bits go from LSBit to MSBit. + :param bytearray,bytes original: The original buffer whose bits are to be + reversed. + +chunk() ***************** .. autofunction:: circuitpython_nrf24l01.fake_ble.chunk -crc24_ble + :param bytearray,bytes buf: The actual data contained in the block. + :param int data_type: The type of data contained in the chunk. This is a + predefined number according to BLE specifications. The default value + ``0x16`` describes all service data. ``0xFF`` describes manufacturer + information. Any other values are not applicable to BLE + advertisements. + + .. important:: This function is called internally by + :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.advertise()`. + To pack multiple data values into a single payload, use this function + for each data value and pass a `list` or `tuple` of the returned + results to + :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.advertise()` + (see example code in documentation about + :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.advertise()` + for more detail). Remember that broadcasting multiple data values may + require the :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.name` + be set to `None` and/or the + :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.show_pa_level` be + set to `False` for reasons about the payload size with + `BLE Limitations`_. + +crc24_ble() ***************** .. autofunction:: circuitpython_nrf24l01.fake_ble.crc24_ble + This is exposed for convenience but should not be used for other buffer + protocols that require big endian CRC24 format. + + :param bytearray,byes data: The buffer of data to be uncorrupted. + :param int deg_poly: A preset "degree polynomial" in which each bit + represents a degree who's coefficient is 1. BLE specfications require + ``0x00065b`` (default value). + :param int init_val: This will be the initial value that the checksum + will use while shifting in the buffer data. BLE specfications require + ``0x555555`` (default value). + :returns: A 24-bit `bytearray` representing the checksum of the data (in + proper little endian). + BLE_FREQ ***************** .. autodata:: circuitpython_nrf24l01.fake_ble.BLE_FREQ + This tuple contains the relative predefined channels used: + + * nRF channel 2 == BLE channel 37 + * nRF channel 26 == BLE channel 38 + * nRF channel 80 == BLE channel 39 + FakeBLE class ------------- .. autoclass:: circuitpython_nrf24l01.fake_ble.FakeBLE + Per the limitations of this technique, only the + :py:attr:`~circuitpython_nrf24l01.rf24.RF24.pa_level` attribute is + available for useful configuration when advertising BLE data. + + :param ~circuitpython_nrf24l01.rf24.RF24 nrf: The object for the nRF24L01 + transceiver to use for fake BLE advertisements. + to_iphone ************ .. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.to_iphone + A value of `False` should still be compatible with other + Apple devices. Testing with this attribute as `False` showed + compatibility with a Mac desktop. + mac ************ .. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.mac + You can set this attribute using a 6-byte `int` or `bytearray`. If this is + set to `None`, then a random 6-byte address is generated. + name ************ .. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.name + This is not required. In + fact setting this attribute will subtract from the available payload + length (in bytes). Set this attribute to `None` to disable advertising the device name + + .. note:: This information occupies (in the TX FIFO) an extra 2 bytes plus + the length of the name set by this attribute. + show_pa_level ************* .. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.show_pa_level + The default value of `False` will exclude this optional information. + + .. note:: This information occupies (in the TX FIFO) an extra 3 bytes, and is + really only useful for some applications to calculate proximity to the + nRF24L01 transceiver. + hop_channel() ************* @@ -114,11 +204,86 @@ whiten() .. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.whiten + This is done according to BLE specifications. + + :param bytearray data: The packet to whiten. + :returns: A `bytearray` of the ``data`` with the whitening algorythm + applied. + + .. warning:: This function uses the currently set BLE channel as a + base case for the whitening coefficient. Do not call + `hop_channel()` before using this function to de-whiten received + payloads (which isn't officially supported yet). Note that + `advertise()` uses this function internally to prevent such + improper usage. + +available() +************* + +.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.available + + This is detirmined from the current state of `name` and `show_pa_level` + attributes. + + :param bytearray,bytes hypothetical: Pass a potential `chunk()` of + data to this parameter to calculate the resulting left over length + in bytes. This parameter is optional. + :returns: An `int` representing the length of available bytes for the + a single payload. + advertise() ************* .. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.advertise + :returns: Nothing as every transmission will register as a success + under the required settings for BLE beacons. + + :param bytearray buf: The payload to transmit. This bytearray must have + a length greater than 0 and less than 22 bytes Otherwise a + `ValueError` exception is thrown whose prompt will tell you the + maximum length allowed under the current configuration. This can + also be a list or tuple of payloads (`bytearray`); in which case, + all items in the list/tuple are processed are packed into 1 + payload for a single transmissions. See example code below about + passing a `list` or `tuple` to this parameter. + :param int data_type: This is used to describe the buffer data passed + to the ``buf`` parameter. ``0x16`` describes all service data. The + default value ``0xFF`` describes manufacturer information. This + parameter is ignored when a `tuple` or `list` is passed to the + ``buf`` parameter. Any other values are not applicable to BLE + advertisements. + + .. important:: If the name and/or TX power level of the emulated BLE + device is also to be broadcast, then the 'name' and/or + `show_pa_level` attribute(s) should be set prior to calling + `advertise()`. + + To pass multiple data values to the ``buf`` parameter see the + following code as an example: + + .. code-block:: python + + # let UUIDs be the 16-bit identifier that corresponds to the + # BLE service type. The following values are not compatible with + # BLE advertisements. + UUID_1 = 0x1805 + UUID_2 = 0x1806 + service1 = ServiceData(UUID_1) + service2 = ServiceData(UUID_2) + service1.data = b"some value 1" + service2.data = b"some value 2" + + # make a tuple of the buffers + buffers = ( + chunk(service1.buffer), + chunk(service2.buffer) + ) + + # let `ble` be the instantiated object of the FakeBLE class + ble.advertise(buffer) + ble.hop_channel() + Service related classes ----------------------- @@ -126,13 +291,39 @@ abstract parent *************** .. autoclass:: circuitpython_nrf24l01.fake_ble.ServiceData - :members: + :members: + :special-members: __len__ + + :param int type_t: The 16-bit "assigned number" defined by the + Bluetooth SIG to describe the service data. This parameter is + required. derivitive children ******************* .. autoclass:: circuitpython_nrf24l01.fake_ble.TemperatureServiceData - :members: + :show-inheritance: + + This class's `data` attribute accepts a `float` value as + input and returns a `bytes` object that conforms to the Bluetooth + Health Thermometer Measurement format as defined in the `GATT + Specifications Supplement. `_ .. autoclass:: circuitpython_nrf24l01.fake_ble.BatteryServiceData - :members: + :show-inheritance: + + The class's `data` attribute accepts a `int` value as + input and returns a `bytes` object that conforms to the Bluetooth + Battery Level format as defined in the `GATT Specifications + Supplement. `_ + +.. autoclass:: circuitpython_nrf24l01.fake_ble.UrlServiceData + :members: pa_level_at_1_meter + :show-inheritance: + + This class's `data` attribute accepts a `str` of URL data as input, and + returns the URL as a `bytes` object where some of the URL parts are + encoded using `Eddystone byte codes as defined by the specifications. + `_ \ No newline at end of file diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index 690ddf4..c4be8b6 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -6,8 +6,9 @@ import digitalio as dio from circuitpython_nrf24l01.rf24 import RF24 from circuitpython_nrf24l01.fake_ble import ( + chunk, FakeBLE, - ServiceData, + UrlServiceData, BatteryServiceData, TemperatureServiceData, ) @@ -25,15 +26,16 @@ nrf = FakeBLE(radio) # the name parameter is going to be its braodcasted BLE name -# this can be changed at any time using the attribute -nrf.name = b"foobar" +# this can be changed at any time using the `name` attribute +# nrf.name = b"foobar" # if broadcasting to an Android, set the to_iphone attribute to False # if broadcasting to an iPhone, set the to_iphone attribute to True # nrf.to_iphone = False # you can optionally set the arbitrary MAC address to be used as the -# BLE device's MAC address. Otherwise this is randomly generated. +# BLE device's MAC address. Otherwise this is randomly generated upon +# instantiation of the FakeBLE object. nrf.mac = b"\x19\x12\x14\x26\x09\xE0" @@ -45,7 +47,7 @@ def _prompt(count, iterator): print(count - iterator, "advertisment left to go!") -def master(count=50): +def master(count=30): """Sends out the device information twice a second.""" # using the "with" statement is highly recommended if the nRF24L01 is # to be used for more than a BLE configuration @@ -55,46 +57,61 @@ def master(count=50): with nrf as ble: nrf.name = b"nRF24L01" nrf.show_pa_level = True + print( + "available bytes in next payload:", + ble.available(chunk(battery_service.buffer)) + ) for i in range(count): # advertise data this many times - _prompt(count, i) # something to show that it isn't frozen - # broadcast only the device name, MAC address, & - # battery charge info; 0x16 means service data - ble.advertise(battery_service.buffer, data_type=0x16) - # channel hoping is recommended per BLE specs - ble.hop_channel() - time.sleep(0.5) # wait till next broadcast + if ble.available(chunk(battery_service.buffer)) >= 0: + _prompt(count, i) # something to show that it isn't frozen + # broadcast only the device name, MAC address, & + # battery charge info; 0x16 means service data + ble.advertise(battery_service.buffer, data_type=0x16) + # channel hoping is recommended per BLE specs + ble.hop_channel() + time.sleep(0.5) # wait till next broadcast # nrf.show_pa_level & nrf.name both are set to false when # exiting a with statement block -def send_temp(count=50): +def send_temp(count=30): """Sends out a fake temperature twice a second.""" temperature_service = TemperatureServiceData() temperature_service.data = 42.0 with nrf as ble: ble.name = b"nRF24L01" + print( + "available bytes in next payload:", + ble.available(chunk(temperature_service.buffer)) + ) for i in range(count): - _prompt(count, i) - # broadcast a device temperature; 0x16 means service data - ble.advertise(temperature_service.buffer, data_type=0x16) - # channel hoping is recommended per BLE specs - ble.hop_channel() - time.sleep(0.2) + if ble.available(chunk(temperature_service.buffer)) >= 0: + _prompt(count, i) + # broadcast a device temperature; 0x16 means service data + ble.advertise(temperature_service.buffer, data_type=0x16) + # channel hoping is recommended per BLE specs + ble.hop_channel() + time.sleep(0.2) # use the eddystone protocol from google to broadcast a URL -url_service = ServiceData(0xFEAA) -url_service.data = bytes([0x10, 0, 0x01]) + b"google.com" +url_service = UrlServiceData() +url_service.data = "http://www.google.com" -def send_url(count=50): +def send_url(count=30): """Sends out a chunk of data twice a second.""" with nrf as ble: + print( + "available bytes in next payload:", + ble.available(chunk(url_service.buffer)) + ) for i in range(count): - _prompt(count, i) - ble.advertise(url_service.buffer, 0x16) - ble.hop_channel() - time.sleep(0.2) + if ble.available(chunk(url_service.buffer)) >= 0: + _prompt(count, i) + ble.advertise(url_service.buffer, 0x16) + ble.hop_channel() + time.sleep(0.2) print( @@ -102,5 +119,5 @@ def send_url(count=50): nRF24L01 fake BLE beacon test.\n\ Run master() to broadcast the device name, pa_level, & battery charge\n\ Run send_temperature() to broadcast the device name & a temperature\n\ - Run send_url() to broadcast custom URL link""" + Run send_url() to broadcast a custom URL link""" ) From 29bb9eaacdfef1eefb58bfeb89cd3e93ea149725 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Wed, 30 Sep 2020 01:21:41 -0700 Subject: [PATCH 099/127] fix load_ack() in lite ver; FakeBLE.enter modified --- circuitpython_nrf24l01/fake_ble.py | 7 +++---- circuitpython_nrf24l01/rf24_lite.py | 12 +++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index 3703b09..f0e72fa 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -85,18 +85,17 @@ def __enter__(self): self._device.dynamic_payloads = False self._device.data_rate = 1 self._device.ce_pin.value = 0 + self._device.arc = 0 try: self._device.address_length = 4 self._device.auto_ack = (0, 0) self._device.crc = 0 - self._device.arc = 0 self._device.payload_length = [32, 32] self._device.power = 1 except AttributeError: # for rf24_lite.py - self._device._reg_write(0, 6) - self._device._reg_write(1, 0) self._device._reg_write(3, 2) - self._device._reg_write(4, 0) + self._device._reg_write(1, 0) + self._device._reg_write(0, 6) self._device._reg_write(0x1D, 1) self._device.payload_length = 32 self._device.open_rx_pipe(0, b"\x71\x91\x7D\x6B") diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index 2231dc5..b976810 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -7,8 +7,8 @@ class RF24: - def __init__(self, spi, csn, ce): - self._spi = SPIDevice(spi, chip_select=csn, baudrate=10000000) + def __init__(self, spi, csn, ce, spi_frequency=10000000): + self._spi = SPIDevice(spi, chip_select=csn, baudrate=spi_frequency) self.ce_pin = ce self.ce_pin.switch_to_output(value=False) self._status = 0 @@ -32,7 +32,7 @@ def __init__(self, spi, csn, ce): # pylint: disable=no-member def _reg_read(self, reg): - out_buf = bytearray([reg, 0]) + out_buf = bytes([reg, 0]) in_buf = bytearray([0, 0]) with self._spi as spi: time.sleep(0.005) @@ -42,7 +42,7 @@ def _reg_read(self, reg): def _reg_read_bytes(self, reg, buf_len=5): in_buf = bytearray(buf_len + 1) - out_buf = bytearray([reg]) + b"\x00" * buf_len + out_buf = bytes([reg]) + b"\x00" * buf_len with self._spi as spi: time.sleep(0.005) spi.write_readinto(out_buf, in_buf) @@ -251,10 +251,12 @@ def ack(self, enable): self._reg_write(0x1D, features) def load_ack(self, buf, pipe_num): - if 0 <= pipe_num <= 5 and (not buf or len(buf) > 32): + if 0 <= pipe_num <= 5 and (not buf or (len(buf) < 32)): + print("args pass") if not self._reg_read(0x1D) & 2: self.ack = True if not self.tx_full: + print("loading ack") self._reg_write_bytes(0xA8 | pipe_num, buf) return True return False From 69231906b5841ca17b6145fb192608b1f83e15eb Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Wed, 30 Sep 2020 03:25:20 -0700 Subject: [PATCH 100/127] too many ambiant signals to tackle receiving BLE --- circuitpython_nrf24l01/fake_ble.py | 29 +++++++++--------- examples/nrf24l01_fake_ble_test.py | 48 ++++++++++++++++++++---------- 2 files changed, 47 insertions(+), 30 deletions(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index f0e72fa..3b49dac 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -82,25 +82,21 @@ def __init__(self, nrf): self._device.flush_rx() def __enter__(self): + self._device.ce_pin.value = 0 self._device.dynamic_payloads = False + self._device.payload_length = 32 self._device.data_rate = 1 - self._device.ce_pin.value = 0 self._device.arc = 0 - try: - self._device.address_length = 4 - self._device.auto_ack = (0, 0) - self._device.crc = 0 - self._device.payload_length = [32, 32] - self._device.power = 1 - except AttributeError: # for rf24_lite.py - self._device._reg_write(3, 2) - self._device._reg_write(1, 0) - self._device._reg_write(0, 6) - self._device._reg_write(0x1D, 1) - self._device.payload_length = 32 + self._device.address_length = 4 self._device.open_rx_pipe(0, b"\x71\x91\x7D\x6B") self._device.open_tx_pipe(b"\x71\x91\x7D\x6B") - self._device.open_rx_pipe(1, b"\x71\x91\x7D\x6B") + if hasattr(self._device, "auto_ack"): + self._device.auto_ack = False + self._device.crc = 0 + self._device.power = 1 + else: # for rf24_lite.py + self._device._reg_write(1, 0) # disable auto_ack + self._device._reg_write(0, 6) # disable CRC & power up return self def __exit__(self, *exc): @@ -233,6 +229,11 @@ def __init__(self, type_t): self._type = struct.pack("= 0: _prompt(count, i) # something to show that it isn't frozen - # broadcast only the device name, MAC address, & + # broadcast the device name, MAC address, & # battery charge info; 0x16 means service data ble.advertise(battery_service.buffer, data_type=0x16) # channel hoping is recommended per BLE specs @@ -74,10 +82,14 @@ def master(count=30): # exiting a with statement block -def send_temp(count=30): +# create an object for manipulating temperature measurements +temperature_service = TemperatureServiceData() +# temperature's float data has up to 2 decimal places of percision +temperature_service.data = 42.0 + + +def send_temp(count=25): """Sends out a fake temperature twice a second.""" - temperature_service = TemperatureServiceData() - temperature_service.data = 42.0 with nrf as ble: ble.name = b"nRF24L01" print( @@ -87,33 +99,37 @@ def send_temp(count=30): for i in range(count): if ble.available(chunk(temperature_service.buffer)) >= 0: _prompt(count, i) - # broadcast a device temperature; 0x16 means service data + # broadcast a temperature measurement; 0x16 means service data ble.advertise(temperature_service.buffer, data_type=0x16) # channel hoping is recommended per BLE specs ble.hop_channel() time.sleep(0.2) -# use the eddystone protocol from google to broadcast a URL +# use the Eddystone protocol from Google to broadcast a URL as +# service data. We'll need an object to manipulate that also url_service = UrlServiceData() +# the data attribute converts a URL string into a simplified +# bytes object using byte codes defined by the Eddystone protocol. url_service.data = "http://www.google.com" -def send_url(count=30): +def send_url(count=25): """Sends out a chunk of data twice a second.""" with nrf as ble: print( "available bytes in next payload:", ble.available(chunk(url_service.buffer)) ) + # NOTE we did NOT set a device name in this with block for i in range(count): + # URLs easily exceed the nRF24L01's max payload length if ble.available(chunk(url_service.buffer)) >= 0: _prompt(count, i) ble.advertise(url_service.buffer, 0x16) ble.hop_channel() time.sleep(0.2) - print( """\ nRF24L01 fake BLE beacon test.\n\ From dc3f87872b46056fb753dd9238dfe8f80a03e97a Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Wed, 30 Sep 2020 03:44:30 -0700 Subject: [PATCH 101/127] need to advertise for longer to be noticed --- examples/nrf24l01_fake_ble_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index fdf2f88..b94dc5a 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -57,7 +57,7 @@ def _prompt(count, iterator): battery_service.data = 85 -def master(count=25): +def master(count=50): """Sends out the device information twice a second.""" # using the "with" statement is highly recommended if the nRF24L01 is # to be used for more than a BLE configuration @@ -88,7 +88,7 @@ def master(count=25): temperature_service.data = 42.0 -def send_temp(count=25): +def send_temp(count=50): """Sends out a fake temperature twice a second.""" with nrf as ble: ble.name = b"nRF24L01" @@ -114,7 +114,7 @@ def send_temp(count=25): url_service.data = "http://www.google.com" -def send_url(count=25): +def send_url(count=50): """Sends out a chunk of data twice a second.""" with nrf as ble: print( From dc7e2479048c179ad5f9fefda7285cb9ae2ecc14 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Wed, 30 Sep 2020 03:48:12 -0700 Subject: [PATCH 102/127] clarify UUID attr --- circuitpython_nrf24l01/fake_ble.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index 3b49dac..2335d13 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -231,7 +231,8 @@ def __init__(self, type_t): @property def uuid(self): - """This returns the Service UUID as a `bytearray`. (read-only)""" + """This returns the 16-bit Service UUID as a `bytearray` in little + endian. (read-only)""" return self._type @property From 03f921c53c4a71cd7eb0b181bb8e80f9798fc0f1 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Wed, 30 Sep 2020 04:03:19 -0700 Subject: [PATCH 103/127] freaking trailing whitespace --- examples/nrf24l01_fake_ble_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index b94dc5a..763540e 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -1,6 +1,6 @@ """ This example of using the nRF24L01 as a 'fake' Buetooth Beacon - + .. warning:: ATSAMD21 M0-based boards have memory allocation error when loading fake_ble.py """ From 5dbe4d88393438fedc48cd6d5dcddc3f0599acfa Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Wed, 30 Sep 2020 15:17:48 -0700 Subject: [PATCH 104/127] error in docs about send() return values --- docs/basic_api.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/basic_api.rst b/docs/basic_api.rst index 2f61c75..7033a6e 100644 --- a/docs/basic_api.rst +++ b/docs/basic_api.rst @@ -111,10 +111,10 @@ recv() .. automethod:: circuitpython_nrf24l01.rf24.RF24.recv This function can also be used to fetch the last ACK packet's payload if `ack` is enabled. - + :param int length: An optional parameter to specify how many bytes to read from the RX FIFO buffer. This parameter is not contrained in any way. - + - If this parameter is less than the length of the first available payload in the RX FIFO buffer, then the payload will remain in the RX FIFO buffer until the entire payload is fetched by this function. @@ -152,9 +152,9 @@ send() - `False` if transmission fails. Transmission failure can only be detected if `arc` is greater than ``0``. - `True` if transmission succeeds. - - `bytearray` or `None` when the `ack` attribute is `True`. Because the payload + - `bytearray` or `True` when the `ack` attribute is `True`. Because the payload expects a responding custom ACK payload, the response is returned (upon successful - transmission) as a `bytearray` (or `None` if ACK payload is empty) + transmission) as a `bytearray` (or `True` if ACK payload is empty) :param bytearray,list,tuple buf: The payload to transmit. This bytearray must have a length in range [1, 32], otherwise a `ValueError` exception is thrown. This can From bdebe07e17fa347f4313f9d0ff5cdcf1ef5b41b1 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Wed, 30 Sep 2020 15:19:11 -0700 Subject: [PATCH 105/127] clarify docs about UrlServiceData.pa_level (@1m) --- circuitpython_nrf24l01/fake_ble.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index 2335d13..5716cff 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -295,8 +295,8 @@ def __init__(self): @property def pa_level_at_1_meter(self): - """The TX power level (in dBm) at 1 meter from the nRF24L01. - This defaults to -25 (due to testing) and must be a signed `int`.""" + """The TX power level (in dBm) at 1 meter from the nRF24L01. This + defaults to -25 (due to testing) and must be a 1-byte signed `int`.""" return struct.unpack(">b", self._type[-1:])[0] @pa_level_at_1_meter.setter From c6ea5b13e8101d1180a229a8d4a7706f5c69387f Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Thu, 1 Oct 2020 14:24:30 -0700 Subject: [PATCH 106/127] examples use -12 dBm; no debug prompt in rf24_lite --- circuitpython_nrf24l01/rf24_lite.py | 2 -- examples/nrf24l01_2arduino_handling_data.py | 6 +++++- examples/nrf24l01_ack_payload_test.py | 4 ++++ examples/nrf24l01_fake_ble_test.py | 4 ++++ examples/nrf24l01_interrupt_test.py | 5 +++++ examples/nrf24l01_simple_test.py | 4 ++++ examples/nrf24l01_stream_test.py | 4 ++++ 7 files changed, 26 insertions(+), 3 deletions(-) diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index b976810..5d927aa 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -252,11 +252,9 @@ def ack(self, enable): def load_ack(self, buf, pipe_num): if 0 <= pipe_num <= 5 and (not buf or (len(buf) < 32)): - print("args pass") if not self._reg_read(0x1D) & 2: self.ack = True if not self.tx_full: - print("loading ack") self._reg_write_bytes(0xA8 | pipe_num, buf) return True return False diff --git a/examples/nrf24l01_2arduino_handling_data.py b/examples/nrf24l01_2arduino_handling_data.py index 9ea5761..c528f9e 100644 --- a/examples/nrf24l01_2arduino_handling_data.py +++ b/examples/nrf24l01_2arduino_handling_data.py @@ -25,7 +25,11 @@ # initialize the nRF24L01 on the spi bus object nrf = RF24(spi, csn, ce) -nrf.dynamic_payloads = False # this is the default in the TMRh20 arduino library +nrf.dynamic_payloads = False # the default in the TMRh20 arduino library + +# set the Power Amplifier level to -12 dBm since these test examples are +# usually run with nRF24L01 transceivers in close proximity +nrf.pa_level = -12 # set address of TX node into a RX pipe nrf.open_rx_pipe(1, address[0]) diff --git a/examples/nrf24l01_ack_payload_test.py b/examples/nrf24l01_ack_payload_test.py index 3cb8a7a..f9ce549 100644 --- a/examples/nrf24l01_ack_payload_test.py +++ b/examples/nrf24l01_ack_payload_test.py @@ -33,6 +33,10 @@ # to enable the custom ACK payload feature nrf.ack = True # False disables again +# set the Power Amplifier level to -12 dBm since these test examples are +# usually run with nRF24L01 transceivers in close proximity +nrf.pa_level = -12 + # addresses needs to be in a buffer protocol object (bytearray) address = b'1Node' diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index 763540e..a5091a8 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -42,6 +42,10 @@ # instantiation of the FakeBLE object. # nrf.mac = b"\x19\x12\x14\x26\x09\xE0" +# set the Power Amplifier level to -12 dBm since these test examples are +# usually run with nRF24L01 transceivers in close proximity +radio.pa_level = -12 + def _prompt(count, iterator): if (count - iterator) % 5 == 0 or (count - iterator) < 5: diff --git a/examples/nrf24l01_interrupt_test.py b/examples/nrf24l01_interrupt_test.py index e474a6f..27f27b0 100644 --- a/examples/nrf24l01_interrupt_test.py +++ b/examples/nrf24l01_interrupt_test.py @@ -28,6 +28,11 @@ nrf = RF24(spi, csn, ce) nrf.arc = 15 # turn up automatic retries to the max. default is 3 +# set the Power Amplifier level to -12 dBm since these test examples are +# usually run with nRF24L01 transceivers in close proximity +nrf.pa_level = -12 + + def master(timeout=5): # will only wait 5 seconds for slave to respond """Transmits once, receives once, and intentionally fails a transmit""" # set address of RX node into a TX pipe diff --git a/examples/nrf24l01_simple_test.py b/examples/nrf24l01_simple_test.py index 79dd2e8..a1b3076 100644 --- a/examples/nrf24l01_simple_test.py +++ b/examples/nrf24l01_simple_test.py @@ -24,6 +24,10 @@ # initialize the nRF24L01 on the spi bus object nrf = RF24(spi, csn, ce) +# set the Power Amplifier level to -12 dBm since these test examples are +# usually run with nRF24L01 transceivers in close proximity +nrf.pa_level = -12 + def master(count=5): # count = 5 will only transmit 5 packets """Transmits an incrementing integer every second""" # set address of RX node into a TX pipe diff --git a/examples/nrf24l01_stream_test.py b/examples/nrf24l01_stream_test.py index 4b9781e..6c70e74 100644 --- a/examples/nrf24l01_stream_test.py +++ b/examples/nrf24l01_stream_test.py @@ -23,6 +23,10 @@ # initialize the nRF24L01 on the spi bus object nrf = RF24(spi, csn, ce) +# set the Power Amplifier level to -12 dBm since these test examples are +# usually run with nRF24L01 transceivers in close proximity +nrf.pa_level = -12 + # lets create a list of payloads to be streamed to the nRF24L01 running slave() buffers = [] SIZE = 31 From 7ed6adb1ba9052356c6c916fac14ea1dd1faa5ec Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Thu, 1 Oct 2020 15:41:52 -0700 Subject: [PATCH 107/127] fix URLServiceData.uuid property --- circuitpython_nrf24l01/fake_ble.py | 11 ++++++++--- docs/advanced_api.rst | 22 +++++++++++----------- docs/ble_api.rst | 17 +++++++++-------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index 5716cff..d14e8a1 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -225,8 +225,8 @@ class ServiceData: """An abstract helper class to package specific service data using Bluetooth SIG defined 16-bit UUID flags to describe the data type.""" - def __init__(self, type_t): - self._type = struct.pack("b", self._type[-1:])[0] @pa_level_at_1_meter.setter def pa_level_at_1_meter(self, value): self._type = self._type[:-1] + struct.pack(">b", int(value)) + @property + def uuid(self): + return self._type[:2] + @ServiceData.data.setter def data(self, value): value = value.replace("http://www.", "\x00") diff --git a/docs/advanced_api.rst b/docs/advanced_api.rst index d8c5274..4131456 100644 --- a/docs/advanced_api.rst +++ b/docs/advanced_api.rst @@ -2,7 +2,7 @@ .. |irq note| replace:: parameter as `True` to `clear_status_flags()` and reset this. As this is a virtual representation of the interrupt event, this attribute will always be updated despite what the actual IRQ pin is configured to do about this event. - + .. |update manually| replace:: Calling this does not execute an SPI transaction. It only exposes that latest data contained in the STATUS byte that's always returned from any other SPI transactions. Use the `update()` function to manually refresh this data when @@ -117,7 +117,7 @@ read_ack() .. note:: See also the `ack`, `dynamic_payloads`, and `auto_ack` attributes as they must be enabled to use custom ACK payloads. - .. warning:: This function will be deprecated on next major release. Use `recv()` instead. + .. warning:: This function will be deprecated on next major release. Use `recv()` instead. irq_dr ****************************** @@ -127,7 +127,7 @@ irq_dr . :Returns: - + - `True` represents Data is in the RX FIFO buffer - `False` represents anything depending on context (state/condition of FIFO buffers); usually this means the flag's been reset. @@ -144,7 +144,7 @@ irq_df . :Returns: - + - `True` signifies the nRF24L01 attemped all configured retries - `False` represents anything depending on context (state/condition); usually this means the flag's been reset. @@ -161,7 +161,7 @@ irq_ds . :Returns: - + - `True` represents a successful transmission - `False` represents anything depending on context (state/condition of FIFO buffers); usually this means the flag's been reset. @@ -225,11 +225,11 @@ tx_full .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.tx_full . - + |update manually| :returns: - + - `True` for TX FIFO buffer is full - `False` for TX FIFO buffer is not full. This doesn't mean the TX FIFO buffer is empty. @@ -329,7 +329,7 @@ flush_rx() .. note:: The nRF24L01 RX FIFO is 3 level stack that holds payload data. This means that there can be up to 3 received payloads (each of a maximum length equal to 32 bytes) - waiting to be read (and popped from the stack) by `recv()` or `read_ack()`. This + waiting to be read (and removed from the stack) by `recv()` or `read_ack()`. This function clears all 3 levels. flush_tx() @@ -340,7 +340,7 @@ flush_tx() .. note:: The nRF24L01 TX FIFO is 3 level stack that holds payload data. This means that there can be up to 3 payloads (each of a maximum length equal to 32 bytes) waiting to be transmit by `send()`, `resend()` or `write()`. This function clears all 3 levels. It - is worth noting that the payload data is only popped from the TX FIFO stack upon + is worth noting that the payload data is only removed from the TX FIFO stack upon successful transmission (see also `resend()` as the handling of failed transmissions can be altered). @@ -374,11 +374,11 @@ pipe .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pipe . - + |update manually| :Returns: - + - `None` if there is no payload in RX FIFO. - The `int` identifying pipe number [0,5] that received the next available payload in the RX FIFO buffer. diff --git a/docs/ble_api.rst b/docs/ble_api.rst index c9f20cd..baed4c2 100644 --- a/docs/ble_api.rst +++ b/docs/ble_api.rst @@ -22,10 +22,10 @@ here is simply ported to work on CircuitPython. the nRF24L01 power amplifier level (using :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.show_pa_level`). This is can be calculated as: - + **32** (nRF24L01 maximum) - **6** (MAC address) - **5** (required flags) - **3** (CRC checksum) = **18** - + Use the helper function :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.available()` to detirmine if your payload can be transmit. @@ -130,7 +130,7 @@ crc24_ble() ``0x555555`` (default value). :returns: A 24-bit `bytearray` representing the checksum of the data (in proper little endian). - + BLE_FREQ ***************** @@ -182,7 +182,7 @@ name .. note:: This information occupies (in the TX FIFO) an extra 2 bytes plus the length of the name set by this attribute. - + show_pa_level ************* @@ -216,7 +216,7 @@ whiten() payloads (which isn't officially supported yet). Note that `advertise()` uses this function internally to prevent such improper usage. - + available() ************* @@ -230,7 +230,7 @@ available() in bytes. This parameter is optional. :returns: An `int` representing the length of available bytes for the a single payload. - + advertise() ************* @@ -283,7 +283,7 @@ advertise() # let `ble` be the instantiated object of the FakeBLE class ble.advertise(buffer) ble.hop_channel() - + Service related classes ----------------------- @@ -294,7 +294,8 @@ abstract parent :members: :special-members: __len__ - :param int type_t: The 16-bit "assigned number" defined by the + :param int uuid: The 16-bit UUID `"GATT Service assigned number" + `_ defined by the Bluetooth SIG to describe the service data. This parameter is required. From 350245dfe5fa902cea59cdb064186ef8e7f16c81 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Thu, 1 Oct 2020 18:12:00 -0700 Subject: [PATCH 108/127] PR self-review changes --- README.rst | 4 +- docs/conf.py | 393 ++++++++++---------- docs/examples.rst | 2 +- examples/nrf24l01_2arduino_handling_data.py | 68 ++-- examples/nrf24l01_ack_payload_test.py | 30 +- examples/nrf24l01_context_test.py | 4 +- examples/nrf24l01_fake_ble_test.py | 4 +- examples/nrf24l01_interrupt_test.py | 57 +-- examples/nrf24l01_simple_test.py | 29 +- examples/nrf24l01_stream_test.py | 53 +-- 10 files changed, 345 insertions(+), 299 deletions(-) diff --git a/README.rst b/README.rst index e104aad..03b4ba5 100644 --- a/README.rst +++ b/README.rst @@ -247,8 +247,8 @@ Now, once you have the virtual environment activated: .. code-block:: shell cd docs - sphinx-build -E -W -b html . _build/html + sphinx-build -E -W -b html . _build -This will output the documentation to ``docs/_build/html``. Open the index.html in your browser to +This will output the documentation to ``docs/_build``. Open the index.html in your browser to view them. It will also (due to -W) error out on any warning like the Github action, Build CI, does. This is a good way to locally verify it will pass. diff --git a/docs/conf.py b/docs/conf.py index afce559..4927412 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,196 +1,197 @@ -# -*- coding: utf-8 -*- - -import os -import sys - -sys.path.insert(0, os.path.abspath("..")) - -# -- General configuration ------------------------------------------------ - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.intersphinx", - "sphinx.ext.napoleon", - "sphinx.ext.todo", - "sphinx.ext.viewcode", - # "rst2pdf.pdfbuilder", # for pdf builder support -] - -# TODO: Please Read! -# Uncomment the below if you use native CircuitPython modules such as -# digitalio, micropython and busio. List the modules you use. Without it, the -# autodoc module docs will fail to generate with a warning. -autodoc_mock_imports = ["digitalio", "busio"] -autodoc_member_order = "bysource" - -intersphinx_mapping = { - "python": ("https://docs.python.org/3.4", None), - "BusDevice": ( - "https://circuitpython.readthedocs.io/projects/busdevice/en/latest/", - None, - ), - "CircuitPython": ("https://circuitpython.readthedocs.io/en/latest/", None), -} - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -source_suffix = ".rst" - -# The master toctree document. -master_doc = "index" - -# General information about the project. -project = u"nRF24L01 Library" -copyright = u"2019 Brendan Doherty" -author = u"Brendan Doherty" - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = u"1.0" -# The full version, including alpha/beta/rc tags. -release = u"1.0" - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".env", "CODE_OF_CONDUCT.md"] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -# -default_role = "any" - -# If true, '()' will be appended to :func: etc. cross-reference text. -# -add_function_parentheses = True - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - -# If this is True, todo emits a warning for each TODO entries. The default is False. -todo_emit_warnings = False - -napoleon_numpy_docstring = False - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -on_rtd = os.environ.get("READTHEDOCS", None) == "True" - -if not on_rtd: # only import and set the theme if we're building docs locally - try: - import sphinx_rtd_theme - - html_theme = "sphinx_rtd_theme" - html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] - except: - html_theme = "default" - html_theme_path = ["."] -else: - html_theme_path = ["."] - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] - -# These paths are either relative to html_static_path -# or fully qualified paths (eg. https://...) -html_css_files = [ - "darkness.css", -] -# The name of an image file (relative to this directory) to use as a favicon of -# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# -html_favicon = "_static/favicon.ico" - -# Output file base name for HTML help builder. -htmlhelp_basename = "nRF24L01_Library_doc" -# html_copy_source = True -# html_show_sourcelink = True - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ( - master_doc, - "nRF24L01Library.tex", - u"nRF24L01 Library Documentation", - author, - "manual", - ), -] - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, "nRF24L01library", u"nRF24L01 Library Documentation", [author], 1) -] - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ( - master_doc, - "nRF24L01Library", - u" nRF24L01 Library Documentation", - author, - "nRF24L01Library", - "nRF24L01 on CircuitPython devices.", - "Wireless", - ), -] - -# ---Options for pdf output----------------------------------------- - -# pdf_documents = [ -# ( -# "index", -# u"CircuitPython-nRF24L01", -# u"CircuitPython-nRF24L01 library documentation", -# u"Brendan Doherty", -# ), -# ] +# -*- coding: utf-8 -*- +# pylint: disable=invalid-name +"""This file is for `sphinx-build` configuration""" +import os +import sys + +sys.path.insert(0, os.path.abspath("..")) + +# -- General configuration ------------------------------------------------ + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.napoleon", + "sphinx.ext.todo", + "sphinx.ext.viewcode", + # "rst2pdf.pdfbuilder", # for pdf builder support +] + +# TODO: Please Read! +# Uncomment the below if you use native CircuitPython modules such as +# digitalio, micropython and busio. List the modules you use. Without it, the +# autodoc module docs will fail to generate with a warning. +autodoc_mock_imports = ["digitalio", "busio"] +autodoc_member_order = "bysource" + +intersphinx_mapping = { + "python": ("https://docs.python.org/3.4", None), + "BusDevice": ( + "https://circuitpython.readthedocs.io/projects/busdevice/en/latest/", + None, + ), + "CircuitPython": ("https://circuitpython.readthedocs.io/en/latest/", None), +} + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +source_suffix = ".rst" + +# The master toctree document. +master_doc = "index" + +# General information about the project. +# pylint: disable=redefined-builtin +copyright = u"2019 Brendan Doherty" +# pylint: enable=redefined-builtin +project = u"nRF24L01 Library" +author = u"Brendan Doherty" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = u"1.2" +# The full version, including alpha/beta/rc tags. +release = u"1.2" + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".env", "CODE_OF_CONDUCT.md"] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +default_role = "any" + +# If true, '()' will be appended to :func: etc. cross-reference text. +add_function_parentheses = True + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + +# If this is True, todo emits a warning for each TODO entries. The default is False. +todo_emit_warnings = False + +napoleon_numpy_docstring = False + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +on_rtd = os.environ.get("READTHEDOCS", None) == "True" + +if not on_rtd: # only import and set the theme if we're building docs locally + try: + import sphinx_rtd_theme + + html_theme = "sphinx_rtd_theme" + html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] + except ImportError: + html_theme = "default" + html_theme_path = ["."] +else: + html_theme_path = ["."] + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +# These paths are either relative to html_static_path +# or fully qualified paths (eg. https://...) +html_css_files = [ + "darkness.css", +] +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# +html_favicon = "_static/favicon.ico" + +# Output file base name for HTML help builder. +htmlhelp_basename = "nRF24L01_Library_doc" +# html_copy_source = True +# html_show_sourcelink = True + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + # + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + # + # Additional stuff for the LaTeX preamble. + # 'preamble': '', + # + # Latex figure (float) alignment + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ( + master_doc, + "nRF24L01Library.tex", + u"nRF24L01 Library Documentation", + author, + "manual", + ), +] + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, "nRF24L01library", u"nRF24L01 Library Documentation", [author], 1) +] + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, + "nRF24L01Library", + u" nRF24L01 Library Documentation", + author, + "nRF24L01Library", + "nRF24L01 on CircuitPython devices.", + "Wireless", + ), +] + +# ---Options for PDF output----------------------------------------- +# requires `rst2pdf` module which is not builtin to Python 3.4 nor +# readthedocs.org's docker) +# pdf_documents = [ +# ( +# "index", +# u"CircuitPython-nRF24L01", +# u"CircuitPython-nRF24L01 library documentation", +# u"Brendan Doherty", +# ), +# ] diff --git a/docs/examples.rst b/docs/examples.rst index a45a53d..fd325e1 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -47,7 +47,7 @@ This is a test to show how to use "with" statements to manage multiple different Working with TMRh20's Arduino library ------------------------------------- -This test is meant to prove compatibility with the popular Arduino library for the nRF24L01 by TMRh20 (available for install via the Arduino IDE's Library Manager). The following code has been designed/test with the TMRh20 library example named "GettingStarted_HandlingData.ino". +This test is meant to prove compatibility with the popular Arduino library for the nRF24L01 by TMRh20 (available for install via the Arduino IDE's Library Manager). The following code has been designed/test with the TMRh20 library example named "GettingStarted_HandlingData.ino". If you changed the ``role`` variable in the TMRh20 sketch, you will have to adjust the addresses assigned to the pipes in this script. .. literalinclude:: ../examples/nrf24l01_2arduino_handling_data.py :caption: examples/nrf24l01_2arduino_handling_data.py diff --git a/examples/nrf24l01_2arduino_handling_data.py b/examples/nrf24l01_2arduino_handling_data.py index c528f9e..40131a7 100644 --- a/examples/nrf24l01_2arduino_handling_data.py +++ b/examples/nrf24l01_2arduino_handling_data.py @@ -13,7 +13,7 @@ from circuitpython_nrf24l01.rf24 import RF24 # addresses needs to be in a buffer protocol object (bytearray) -address = [b'1Node', b'2Node'] +address = [b"1Node", b"2Node"] # change these (digital output) pins accordingly ce = dio.DigitalInOut(board.D4) @@ -25,9 +25,9 @@ # initialize the nRF24L01 on the spi bus object nrf = RF24(spi, csn, ce) -nrf.dynamic_payloads = False # the default in the TMRh20 arduino library +nrf.dynamic_payloads = False # the default in the TMRh20 arduino library -# set the Power Amplifier level to -12 dBm since these test examples are +# set the Power Amplifier level to -12 dBm since this test example is # usually run with nRF24L01 transceivers in close proximity nrf.pa_level = -12 @@ -36,6 +36,7 @@ # set address of RX node into a TX pipe nrf.open_tx_pipe(address[1]) + def master(count=5): # count = 5 will only transmit 5 packets """Transmits an arbitrary unsigned long value every second. This method will only try to transmit (count) number of attempts""" @@ -43,37 +44,44 @@ def master(count=5): # count = 5 will only transmit 5 packets # for the "HandlingData" part of the test from the TMRh20 library example float_value = 0.01 while count: - nrf.listen = False # ensures the nRF24L01 is in TX mode + nrf.listen = False # ensures the nRF24L01 is in TX mode print("Now Sending") - start_timer = int(time.monotonic() * 1000) # start timer + start_timer = int(time.monotonic() * 1000) # start timer # use struct.pack to packetize your data into a usable payload # '<' means little endian byte order. # 'L' means a single 4 byte unsigned long value. # 'f' means a single 4 byte float value. - buffer = struct.pack(' 1 # NOTE if opening pipes outside of the "with" block, you may encounter # conflicts in the differences between address_length attributes. @@ -58,7 +58,7 @@ print("\nsettings configured by the basicRF object") with basicRF as nerf: # the "as nerf" part is optional - nerf.open_rx_pipe(2, b'?') # again only uses the first character + nerf.open_rx_pipe(2, b"?") # again only uses the first character nerf.what_happened(1) # if you examine the outputs from what_happened() you'll see: diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index a5091a8..094775b 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -2,7 +2,7 @@ This example of using the nRF24L01 as a 'fake' Buetooth Beacon .. warning:: ATSAMD21 M0-based boards have memory allocation - error when loading fake_ble.py + error when loading fake_ble.mpy """ import time import board @@ -42,7 +42,7 @@ # instantiation of the FakeBLE object. # nrf.mac = b"\x19\x12\x14\x26\x09\xE0" -# set the Power Amplifier level to -12 dBm since these test examples are +# set the Power Amplifier level to -12 dBm since this test example is # usually run with nRF24L01 transceivers in close proximity radio.pa_level = -12 diff --git a/examples/nrf24l01_interrupt_test.py b/examples/nrf24l01_interrupt_test.py index 27f27b0..6837713 100644 --- a/examples/nrf24l01_interrupt_test.py +++ b/examples/nrf24l01_interrupt_test.py @@ -10,7 +10,7 @@ from circuitpython_nrf24l01.rf24 import RF24 # address needs to be in a buffer protocol object (bytearray is preferred) -address = b'1Node' +address = b"1Node" # select your digital input pin that's connected to the IRQ pin on the nRF4L01 irq = dio.DigitalInOut(board.D12) @@ -28,7 +28,7 @@ nrf = RF24(spi, csn, ce) nrf.arc = 15 # turn up automatic retries to the max. default is 3 -# set the Power Amplifier level to -12 dBm since these test examples are +# set the Power Amplifier level to -12 dBm since this test example is # usually run with nRF24L01 transceivers in close proximity nrf.pa_level = -12 @@ -42,54 +42,66 @@ def master(timeout=5): # will only wait 5 seconds for slave to respond # on data sent test print("Pinging: enslaved nRF24L01 without auto_ack") - nrf.write(b'ping') + nrf.write(b"ping") time.sleep(0.00001) # mandatory 10 microsecond pulse starts transmission nrf.ce_pin.value = 0 # end 10 us pulse; now in active TX while not nrf.irq_ds and not nrf.irq_df: nrf.update() # updates the current status on IRQ flags if nrf.irq_ds and not irq.value: - print('interrupt on data sent successful') + print("interrupt on data sent successful") else: print( - 'IRQ on data sent is not active, check your wiring and call interrupt_config()') + "IRQ on data sent is not active, check your wiring", + "and call interrupt_config()" + ) nrf.clear_status_flags() # clear all flags for next test # on data ready test nrf.listen = 1 nrf.open_rx_pipe(0, address) start = time.monotonic() - while not nrf.any() and time.monotonic() - start < timeout: # wait for slave to send + while (not nrf.any() and time.monotonic() - start < timeout): + # wait for slave to send pass if nrf.any(): - print('Pong received') + print("Pong received") if nrf.irq_dr and not irq.value: - print('interrupt on data ready successful') + print("interrupt on data ready successful") else: print( - 'IRQ on data ready is not active, check your wiring and call interrupt_config()') + "IRQ on data ready is not active, check your wiring", + "and call interrupt_config()" + ) nrf.flush_rx() else: - print('pong reception timed out!. make sure to run slave() on the other nRF24L01') + print( + "pong reception timed out!. make sure to run slave()", + "on the other nRF24L01" + ) nrf.clear_status_flags() # clear all flags for next test # on data fail test nrf.listen = False # put the nRF24L01 is in TX mode - # the writing pipe should still be open since we didn't call close_tx_pipe() nrf.flush_tx() # just in case the previous "on data sent" test failed - nrf.write(b'dummy') # slave isn't listening anymore + nrf.write(b"dummy") # slave isn't listening anymore time.sleep(0.00001) # mandatory 10 microsecond pulse starts transmission nrf.ce_pin.value = 0 # end 10 us pulse; now in active TX - while not nrf.irq_ds and not nrf.irq_df: # these attributes don't update themselves - nrf.update() # updates the current status on all IRQ flags (irq_dr, irq_df, irq_ds) + while not nrf.irq_ds and not nrf.irq_df: + # irq_df & irq_ds don't update themselves; we need to do this manually + nrf.update() if nrf.irq_df and not irq.value: - print('interrupt on data fail successful') + print("interrupt on data fail successful") else: print( - 'IRQ on data fail is not active, check your wiring and call interrupt_config()') + "IRQ on data fail is not active, check your wiring", + "and call interrupt_config()" + ) nrf.clear_status_flags() # clear all flags for next test + def slave(timeout=10): # will listen for 10 seconds before timing out - """Acts as a ponging RX node to successfully complete the tests on the master""" + """Acts as a ponging RX node to successfully complete the tests on the + master""" # setup radio to recieve ping nrf.open_rx_pipe(0, address) nrf.listen = 1 @@ -99,15 +111,18 @@ def slave(timeout=10): # will listen for 10 seconds before timing out if nrf.any(): print("ping received. sending pong now.") else: - print('listening timed out, please try again') + print("listening timed out, please try again") nrf.flush_rx() nrf.listen = 0 nrf.open_tx_pipe(address) # send a payload to complete the on data ready test - nrf.send(b'pong', force_retry=1) + nrf.send(b"pong", force_retry=1) # we're done on this side -print("""\ + +print( + """\ nRF24L01 Interrupt test\n\ Run master() to run IRQ pin tests\n\ - Run slave() on the non-testing nRF24L01 to complete the test successfully""") + Run slave() on the non-testing nRF24L01 to pass the test""" +) diff --git a/examples/nrf24l01_simple_test.py b/examples/nrf24l01_simple_test.py index a1b3076..cf8f233 100644 --- a/examples/nrf24l01_simple_test.py +++ b/examples/nrf24l01_simple_test.py @@ -1,5 +1,5 @@ """ -Simple example of library usage. +Simple example of using the RF24 class. """ import time import struct @@ -10,7 +10,7 @@ from circuitpython_nrf24l01.rf24 import RF24 # addresses needs to be in a buffer protocol object (bytearray) -address = b'1Node' +address = b"1Node" # change these (digital output) pins accordingly ce = dio.DigitalInOut(board.D4) @@ -24,10 +24,11 @@ # initialize the nRF24L01 on the spi bus object nrf = RF24(spi, csn, ce) -# set the Power Amplifier level to -12 dBm since these test examples are +# set the Power Amplifier level to -12 dBm since this test example is # usually run with nRF24L01 transceivers in close proximity nrf.pa_level = -12 + def master(count=5): # count = 5 will only transmit 5 packets """Transmits an incrementing integer every second""" # set address of RX node into a TX pipe @@ -38,22 +39,22 @@ def master(count=5): # count = 5 will only transmit 5 packets while count: # use struct.pack to packetize your data # into a usable payload - buffer = struct.pack('= SIZE / 2 + abs(SIZE / 2 - i) or j < - SIZE / 2 - abs(SIZE / 2 - i)) + 48]) - buffers.append(buff) - del buff def master(count=1): # count = 5 will transmit the list 5 times """Transmits a massive buffer of payloads""" + # lets create a `list` of payloads to be streamed to + # the nRF24L01 running slave() + buffers = [] + # we'll use SIZE for the number of payloads in the list and the + # payloads' length + size = 32 + for i in range(size): + # prefix payload with a sequential letter to indicate which + # payloads were lost (if any) + buff = bytes([i + (65 if 0 <= i < 26 else 71)]) + for j in range(size - 1): + char = bool(j >= (size - 1) / 2 + abs((size - 1) / 2 - i)) + char |= bool(j < (size - 1) / 2 - abs((size - 1) / 2 - i)) + buff += bytes([char + 48]) + buffers.append(buff) + del buff + # set address of RX node into a TX pipe nrf.open_tx_pipe(address) # ensures the nRF24L01 is in TX mode @@ -51,12 +56,15 @@ def master(count=1): # count = 5 will transmit the list 5 times for _ in range(count): now = time.monotonic() * 1000 # start timer result = nrf.send(buffers, force_retry=2) - print('Transmission took', time.monotonic() * 1000 - now, 'ms') + print("Transmission took", time.monotonic() * 1000 - now, "ms") for r in result: successful += 1 if r else 0 - print('successfully sent {}% ({}/{})'.format( - successful / len(buffers) * 100 * count, - successful, len(buffers) * count)) + print( + "successfully sent {}% ({}/{})".format( + successful / size * 100 * count, successful, size * count + ) + ) + def slave(timeout=5): """Stops listening after timeout with no response""" @@ -74,13 +82,16 @@ def slave(timeout=5): count += 1 # retreive the received packet's payload rx = nrf.recv() # clears flags & empties RX FIFO - print("Received (raw): {} - {}".format(repr(rx), count)) + print("Received: {} - {}".format(repr(rx), count)) now = time.monotonic() # recommended behavior is to keep in TX mode while idle nrf.listen = False # put the nRF24L01 is in TX mode -print("""\ + +print( + """\ nRF24L01 Stream test\n\ Run slave() on receiver\n\ - Run master() on transmitter""") + Run master() on transmitter""" +) From fc9757050f118c43db0e71b514877e45f5e8df17 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 2 Oct 2020 01:49:18 -0700 Subject: [PATCH 109/127] more backward compatibility for non-plus variants --- circuitpython_nrf24l01/rf24.py | 38 ++++++++++++++++++++++++++++++++-- docs/advanced_api.rst | 21 ++++++++++++++++++- docs/troubleshooting.rst | 10 +++++---- 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index f88350d..ff781f9 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -71,6 +71,17 @@ def __init__(self, spi, csn, ce, spi_frequency=10000000): self._pipes[i] = self._reg_read_bytes(RX_ADDR_P0 + i) else: self._pipes[i] = self._reg_read(RX_ADDR_P0 + i) + # test is nRF24L01 is a plus variant using a command specific to + # non-plus variants + self._is_plus_variant = False + b4_toggle = self._reg_read(TX_FEATURE) + # derelict ACTIVATE command toggles bits in the TX_FEATURE register + self._reg_write(0x50, 0x73) + after_toggle = self._reg_read(TX_FEATURE) + if b4_toggle == after_toggle: + self._is_plus_variant = True + if not after_toggle: # if features are disabled + self._reg_write(0x50, 0x73) # ensure they're enabled # init shadow copy of last RX_ADDR_P0 written to pipe 0 needed as # open_tx_pipe() appropriates pipe 0 for ACK packet self._pipe0_read_addr = None @@ -336,6 +347,9 @@ def what_happened(self, dump_pipes=False): """This debuggung function aggregates and outputs all status/condition related information from the nRF24L01.""" observer = self._reg_read(8) + print( + "Is a plus variant_________{}".format(repr(self.is_plus_variant)) + ) print( "Channel___________________{} ~ {} GHz".format( self.channel, (self.channel + 2400) / 1000 @@ -441,6 +455,12 @@ def what_happened(self, dump_pipes=False): if is_open: print("\t\texpecting", self._pl_len[i], "byte static payloads") + @property + def is_plus_variant(self): + """A `bool` attribute to descibe if the nRF24L01 is a plus variant or + not (read-only).""" + return self._is_plus_variant + @property def dynamic_payloads(self): """This `bool` attribute controls the nRF24L01's dynamic payload @@ -463,7 +483,7 @@ def dynamic_payloads(self, enable): if bool(self._features & 4) != (self._dyn_pl & 1): self._features = (self._features & 3) | ((self._dyn_pl & 1) << 2) self._reg_write(TX_FEATURE, self._features) - if self._dyn_pl: + if self._dyn_pl != (self._aa & self._dyn_pl): self._aa |= self._dyn_pl self._reg_write(AUTO_ACK, self._aa) self._config = self._reg_read(CONFIGURE) @@ -754,10 +774,24 @@ def start_carrier_wave(self): """Starts a continuous carrier wave test.""" self.power = 0 self.ce_pin.value = 0 + self.power = 1 self.listen = 0 self._rf_setup |= 0x90 self._reg_write(RF_PA_RATE, self._rf_setup) - self.power = 1 + if not self.is_plus_variant: + self.auto_ack = False + self._retry_setup = 0 + self._reg_write(SETUP_RETR, self._retry_setup) + self._tx_address = bytearray([0xFF] * 5) + self._reg_write_bytes(TX_ADDRESS, self._tx_address) + self._reg_write_bytes(0xA0, b"\xFF" * 32) + self.crc = 0 + self.ce_pin.value = 1 + time.sleep(0.001) + self.ce_pin.value = 0 + while self._status & 0x70: + self.update() + self._reg_write(0x17, 0x40) self.ce_pin.value = 1 def stop_carrier_wave(self): diff --git a/docs/advanced_api.rst b/docs/advanced_api.rst index 4131456..27f9e90 100644 --- a/docs/advanced_api.rst +++ b/docs/advanced_api.rst @@ -20,6 +20,8 @@ what_happened() :prints: + - ``Is a plus variant`` True means the transceiver is a nRF24L01+. False + means the transceiver is a nRF24L01 (not a plus variant). - ``Channel`` The current setting of the `channel` attribute - ``RF Data Rate`` The current setting of the RF `data_rate` attribute. - ``RF Power Amplifier`` The current setting of the `pa_level` attribute. @@ -71,6 +73,13 @@ what_happened() Default is `False` and skips this extra information. +is_plus_variant +****************************** + +.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.is_plus_variant + + Upon instantiation, this class detirmines if the nRF24L01 is a plus variant or not. + load_ack() ****************************** @@ -445,7 +454,17 @@ start_carrier_wave() print("carrier wave detected") The `pa_level`, `channel` & `data_rate` attributes are vital factors to - the success of this test. See also the `rpd` attribute. + the success of this test. Be sure these attributes are set to the desired test + conditions before calling this function. See also the `rpd` attribute. + + .. note:: To preserve backward compatibility with non-plus variants of the + nRF24L01, this function will also change certain settings if `is_plus_variant` + is `False`. These settings changes include disabling `crc`, disabling + `auto_ack`, disabling `arc`, setting `ard` to 250 microseconds, changing the + TX address to ``b"\xFF\xFF\xFF\xFF\xFF``, and loading a dummy payload into the + TX FIFO buffer while continuously behaving like `resend()` to establish the + constant carrier wave. If `is_plus_variant` is `True`, then none of these + changes to settings are needed nor applied. stop_carrier_wave() ****************************** diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index 3f51903..2703bd8 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -45,10 +45,10 @@ With the `auto_ack` feature enabled, you get: is a way of specifying a certain radio frequency (frequency = [2400 + channel] MHz). Channel defaults to 76 (like the arduino library), but options range from 0 to 125 -- that's 2.4 GHz to 2.525 GHz. The channel can be tweaked to find a less occupied frequency - amongst Bluetooth, WiFi, or other ambient signals that use the same spectrum of + amongst Bluetooth, WiFi, or other ambient signals that use the same spectrum of frequencies. -.. warning:: +.. warning:: For successful transmissions, most of the endpoint trasceivers' settings/features must match. These settings/features include: @@ -78,10 +78,12 @@ developed to save space on microcontrollers with limited amount of RAM and/or st boards using the ATSAMD21 M0). The following functionality has been removed from the lite version: + * `is_plus_variant` is removed, meaning the lite version is not compatibility with + the older non-plus variants of the nRF24L01. * `address()` removed. * `what_happened()` removed. However you can use the following function to dump all available registers' values (for advanced users): - + .. code-block:: python # let `nrf` be the instantiated RF24 object @@ -94,7 +96,7 @@ version: * `fifo()` removed. * `dynamic_payloads` applies to all pipes, not individual pipes. * `payload_length` applies to all pipes, not individual pipes. - * `read_ack()` removed. This is deprecated on next major release anyway; use `recv()` + * `read_ack()` removed. This is deprecated on next major release anyway; use `recv()` instead. * `load_ack()` is available, but it will not throw exceptions for malformed ``buf`` or invalid ``pipe_number`` parameters. From 1f5ed98621aca0c4e15336cf3d1d3f1dbb68722c Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 2 Oct 2020 02:04:06 -0700 Subject: [PATCH 110/127] clarify relations of dynamic_payloads & auto_ack --- docs/configure_api.rst | 14 ++++++++------ docs/troubleshooting.rst | 6 +++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/configure_api.rst b/docs/configure_api.rst index e03b6e5..466dcaf 100644 --- a/docs/configure_api.rst +++ b/docs/configure_api.rst @@ -15,8 +15,8 @@ dynamic_payloads Default setting is enabled on all pipes. - `True` or ``1`` enables nRF24L01's dynamic payload length feature for all data pipes. The - `payload_length` attribute is ignored when this feature is enabled for respective or all - data pipes. + `payload_length` attribute is ignored when this feature is enabled for all + respective data pipes. - `False` or ``0`` disables nRF24L01's dynamic payload length feature for all data pipes. Be sure to adjust the `payload_length` attribute accordingly when this feature is disabled for any data pipes. @@ -26,7 +26,9 @@ dynamic_payloads .. note:: This attribute mostly relates to RX operations, but data pipe 0 applies to TX - operations also. + operations also. The `auto_ack` attribute is set accordingly for data pipes that + have this feature enabled. Disabling this feature for any data pipe will not + affect the `auto_ack` feature for the corresponding data pipes. payload_length ****************************** @@ -38,13 +40,13 @@ payload_length this attribute is used to specify the payload length on that data pipe in RX mode. A valid input value must be: - + * an `int` in range [1, 32]. Otherwise a `ValueError` exception is thrown. * a `list` or `tuple` containing integers can be used to control this attribute per data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be ignored since there are only 6 data pipes. if a index's value is ``0``, then the existing setting will persist (not be changed). - + Default is set to the nRF24L01's maximum of 32 (on all data pipes). .. note:: @@ -94,7 +96,7 @@ ard A valid input value must be in range [250, 4000]. Otherwise a `ValueError` exception is thrown. Default is 1500 for reliability. If this is set to a value that is not multiple of - 250, then the highest multiple of 250 that is no greater than the input value is used. + 250, then the highest multiple of 250 that is no greater than the input value is used. .. note:: Paraphrased from nRF24L01 specifications sheet: diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index 2703bd8..931d99b 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -6,13 +6,13 @@ Troubleshooting info priority of dependence is as follows: 1. `auto_ack` feature provides transmission verification by using the RX nRF24L01 to - automatically and imediatedly send an acknowledgment (ACK) packet in response to freshly + automatically and imediatedly send an acknowledgment (ACK) packet in response to received payloads. `auto_ack` does not require `dynamic_payloads` to be enabled. - 2. `dynamic_payloads` feature allowing either TX/RX nRF24L01 to be able to send/receive + 2. `dynamic_payloads` feature allows either TX/RX nRF24L01 to be able to send/receive payloads with their size written into the payloads' packet. With this disabled, both RX/TX nRF24L01 must use matching `payload_length` attributes. For `dynamic_payloads` to be enabled, the `auto_ack` feature must be enabled. Although, the `auto_ack` feature - isn't required when the `dynamic_payloads` feature is disabled. + can be used when the `dynamic_payloads` feature is disabled. 3. `ack` feature allows the MCU to append a payload to the ACK packet, thus instant bi-directional communication. A transmitting ACK payload must be loaded into the nRF24L01's TX FIFO buffer (done using `load_ack()`) BEFORE receiving the payload that From d6b6f178ee042dc5614ac191cb88340c2f08cebd Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 2 Oct 2020 12:13:59 -0700 Subject: [PATCH 111/127] reverses bits not endianess --- circuitpython_nrf24l01/fake_ble.py | 2 +- docs/ble_api.rst | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index d14e8a1..97d4709 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -37,7 +37,7 @@ def swap_bits(original): def reverse_bits(original): - """This function reverses the bit order into LSBit to MSBit for an entire + """This function reverses the bit order for an entire buffer protocol object.""" ret = bytearray(len(original)) for i, byte in enumerate(original): diff --git a/docs/ble_api.rst b/docs/ble_api.rst index baed4c2..c3236c1 100644 --- a/docs/ble_api.rst +++ b/docs/ble_api.rst @@ -70,7 +70,7 @@ swap_bits() .. autofunction:: circuitpython_nrf24l01.fake_ble.swap_bits :returns: - An `int` containing the byte whose bits go from LSBit to MSBit + An `int` containing the byte whose bits are reversed compared to the value passed to the ``original`` parameter. :param int original: This should be a single unsigned byte, meaning the parameters value can only range from 0 to 255. @@ -81,8 +81,8 @@ reverse_bits() .. autofunction:: circuitpython_nrf24l01.fake_ble.reverse_bits :returns: - A `bytearray` whose bytes still go from MSByte to LSByte, but each - byte's bits go from LSBit to MSBit. + A `bytearray` whose byte order remains the same, but each + byte's bit order is reversed. :param bytearray,bytes original: The original buffer whose bits are to be reversed. From 5c2c4c142841f53672f24f1dfd84cb142acdcd86 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 2 Oct 2020 16:12:14 -0700 Subject: [PATCH 112/127] fix bugs about ctx mgr & prompt in what_happened() --- README.rst | 18 +++- circuitpython_nrf24l01/fake_ble.py | 151 ++++++++++++++++++++++++----- circuitpython_nrf24l01/rf24.py | 17 +++- docs/advanced_api.rst | 12 ++- docs/ble_api.rst | 55 +++++++++-- docs/configure_api.rst | 14 ++- docs/troubleshooting.rst | 38 +++++--- examples/nrf24l01_context_test.py | 51 +++++----- 8 files changed, 259 insertions(+), 97 deletions(-) diff --git a/README.rst b/README.rst index 03b4ba5..c8c259e 100644 --- a/README.rst +++ b/README.rst @@ -39,13 +39,21 @@ Features currently supported * automatic responding acknowledgment (ACK) for verifying transmission success * custom acknowledgment (ACK) payloads for bi-directional communication * flag a single payload for no acknowledgment (ACK) from the receiving nRF24L01 -* "re-use the same payload" feature (for manually re-transmitting failed transmissions that remain in the buffer) -* multiple payload transmissions with one function call (MUST read documentation on the `send()` function) -* context manager compatible for easily switching between different radio configurations using "with" statements (not available in ``rf24_lite.py`` variant for M0 based boards) -* configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or failed transmissions (these 3 flags control the 1 IRQ pin). There's also virtual representations of these interrupt flags available (see `irq_dr`, `irq_ds`, `irq_df` attributes) +* "re-use the same payload" feature (for manually re-transmitting failed transmissions that + remain in the buffer) +* multiple payload transmissions with one function call (MUST read documentation on the + `send()` function) +* context manager compatible for easily switching between different radio configurations + using "with" statements (not available in ``rf24_lite.py`` variant for M0 based boards) +* configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or + failed transmissions (these 3 flags control the 1 IRQ pin). There's also virtual + representations of these interrupt flags available (see + :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr`, + :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_ds`, & `irq_df` attributes) * invoke sleep mode (AKA power down mode) for ultra-low current consumption * cyclic redundancy checking (CRC) up to 2 bytes long -* adjust the nRF24L01's builtin automatic re-transmit feature's parameters (`arc`: number of attempts, `ard`: delay between attempts) +* adjust the nRF24L01's builtin automatic re-transmit feature's parameters (`arc`: number + of attempts, `ard`: delay between attempts) * adjust the nRF24L01's frequency channel (2.4-2.525 GHz) * adjust the nRF24L01's power amplifier level (0, -6, -12, or -18 dBm) * adjust the nRF24L01's RF data rate (250Kbps is buggy due to hardware design, but 1Mbps and 2Mbps are reliable) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index 97d4709..43e9721 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -23,7 +23,7 @@ found here `_""" from os import urandom import struct - +from .rf24 import RF24 def swap_bits(original): """This function reverses the bit order for a single byte.""" @@ -71,39 +71,33 @@ def crc24_ble(data, deg_poly=0x65B, init_val=0x555555): class FakeBLE: """A class to implement BLE advertisements using the nRF24L01.""" - def __init__(self, nrf): - self._device = nrf + def __init__(self, spi, csn, ce, spi_frequency=10000000): + self._radio = RF24(spi, csn, ce, spi_frequency=spi_frequency) self._chan = 0 self._to_iphone = 0x40 self._show_dbm = False self._ble_name = None self._mac = urandom(6) with self: - self._device.flush_rx() + self._radio.dynamic_payloads = False + self._radio.payload_length = 32 + self._radio.data_rate = 1 + self._radio.arc = 0 + self._radio.address_length = 4 + self._radio.open_rx_pipe(0, b"\x71\x91\x7D\x6B") + self._radio.open_tx_pipe(b"\x71\x91\x7D\x6B") + self._radio.auto_ack = False + self._radio.crc = 0 + self._radio.flush_rx() def __enter__(self): - self._device.ce_pin.value = 0 - self._device.dynamic_payloads = False - self._device.payload_length = 32 - self._device.data_rate = 1 - self._device.arc = 0 - self._device.address_length = 4 - self._device.open_rx_pipe(0, b"\x71\x91\x7D\x6B") - self._device.open_tx_pipe(b"\x71\x91\x7D\x6B") - if hasattr(self._device, "auto_ack"): - self._device.auto_ack = False - self._device.crc = 0 - self._device.power = 1 - else: # for rf24_lite.py - self._device._reg_write(1, 0) # disable auto_ack - self._device._reg_write(0, 6) # disable CRC & power up + self._radio = self._radio.__enter__() return self def __exit__(self, *exc): self._show_dbm = False self._ble_name = None - self._device.power = 0 - return False + return self._radio.__exit__() @property def to_iphone(self): @@ -163,7 +157,7 @@ def hop_channel(self): self._chan += 1 if self._chan > 2: self._chan = 0 - self._device.channel = BLE_FREQ[self._chan] + self._radio.channel = BLE_FREQ[self._chan] def whiten(self, data): """Whitening the BLE packet data ensures there's no long repeatition @@ -193,7 +187,7 @@ def _make_payload(self, payload): buf += chunk(b"\x05", 1) pa_level = b"" if self._show_dbm: - pa_level = chunk(struct.pack(">b", self._device.pa_level), 0x0A) + pa_level = chunk(struct.pack(">b", self._radio.pa_level), 0x0A) buf += pa_level if name_length: buf += chunk(self.name, 0x08) @@ -208,7 +202,7 @@ def available(self, hypothetical=b""): return 18 - name_length - self._show_dbm * 3 - len(hypothetical) def advertise(self, buf=b"", data_type=0xFF): - """This function is used to broadcast a payload.""" + """This blocking function is used to broadcast a payload.""" if not isinstance(buf, (bytearray, bytes, list, tuple)): raise ValueError("buffer is an invalid format") payload = b"" @@ -218,8 +212,115 @@ def advertise(self, buf=b"", data_type=0xFF): else: payload = chunk(buf, data_type) if buf else b"" payload = self._make_payload(payload) - self._device.send(reverse_bits(self.whiten(payload))) + self._radio.send(reverse_bits(self.whiten(payload))) + + @property + def pa_level(self): + """See :py:attr:`~circuitpython_nrf24l01.rf24.RF24.pa_level` for + more details.""" + return self._radio.pa_level + + @pa_level.setter + def pa_level(self, value): + self._radio.pa_level = value + + @property + def channel(self): + """The only allowed channels are those contained in the `BLE_FREQ` + tuple.""" + return self._radio.channel + + @channel.setter + def channel(self, value): + if value not in BLE_FREQ: + raise ValueError( + "nrf channel {} is not a valid BLE frequency".format(value) + ) + self._radio.channel = value + @property + def data_rate(self): + """See :py:attr:`~circuitpython_nrf24l01.rf24.RF24.data_rate` for + more details. + + .. warning:: 250 kbps is not a valid data rate for BLE operations.""" + return self._radio.data_rate + + @data_rate.setter + def data_rate(self, value): + if value not in (1, 2): + raise ValueError( + "valid data rates for BLE transmissions are 1 or 2 Mbps." + ) + self._radio.data_rate = value + + @property + def payload_length(self): + """This attribute is best left at 32 bytes for all BLE + operations.""" + return self._radio.payload_length + + @payload_length.setter + def payload_length(self, value): + self._radio.payload_length = value + + @property + def power(self): + """See :py:attr:`~circuitpython_nrf24l01.rf24.RF24.power` for more + details.""" + return self._radio.power + + @power.setter + def power(self, is_on): + self._radio.power = is_on + + @property + def is_lna_enabled(self): + """See :py:attr:`~circuitpython_nrf24l01.rf24.RF24.is_lna_enabled` + for more details.""" + return self._radio.is_lna_enabled + + @property + def is_plus_variant(self): + """See :py:attr:`~circuitpython_nrf24l01.rf24.RF24.is_plus_variant` + for more details.""" + return self._radio.is_plus_variant + + def interrupt_config(self, data_recv=True, data_sent=True): + """See :py:func:`~circuitpython_nrf24l01.rf24.RF24.interrupt_config()` + for more details. + + .. warning:: The :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_df` + attribute (and also this function's ``data_fail`` parameter) is + not implemented for BLE operations.""" + self._radio.interrupt_config(data_recv=data_recv, data_sent=data_sent) + + @property + def irq_ds(self): + """See :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_ds` for + more details.""" + return self._radio.irq_ds + + @property + def irq_dr(self): + """See :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr` for + more details.""" + return self._radio.irq_dr + + def clear_status_flags(self): + """See :py:func:`~circuitpython_nrf24l01.rf24.RF24.clear_status_flags()` + for more details.""" + self._radio.clear_status_flags() + + def update(self): + """See :py:func:`~circuitpython_nrf24l01.rf24.RF24.update()` for more + details.""" + self._radio.update() + + def what_happened(self, dump_pipes=False): + """See :py:func:`~circuitpython_nrf24l01.rf24.RF24.what_happened()` + for more details.""" + self._radio.what_happened(dump_pipes=dump_pipes) class ServiceData: """An abstract helper class to package specific service data using diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index ff781f9..a781d80 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -110,6 +110,7 @@ def __init__(self, spi, csn, ce, spi_frequency=10000000): self.clear_status_flags() def __enter__(self): + self.ce_pin.value = False self._reg_write(CONFIGURE, self._config & 0x7C) self._reg_write(RF_PA_RATE, self._rf_setup) self._reg_write(OPEN_PIPES, self._open_pipes) @@ -129,7 +130,8 @@ def __enter__(self): return self def __exit__(self, *exc): - self.power = 0 + self.ce_pin.value = False + self.power = False return False # pylint: disable=no-member @@ -388,12 +390,12 @@ def what_happened(self, dump_pipes=False): ) print( "IRQ - Data Fail_______{} Data Failed__________{}".format( - "_True" if not bool(self._config & 0x20) else "False", self.irq_df + "_True" if not bool(self._config & 0x10) else "False", self.irq_df ) ) print( "IRQ - Data Sent_______{} Data Sent____________{}".format( - "_True" if not bool(self._config & 0x10) else "False", self.irq_ds + "_True" if not bool(self._config & 0x20) else "False", self.irq_ds ) ) print( @@ -608,7 +610,14 @@ def data_rate(self): @data_rate.setter def data_rate(self, speed): if not speed in (1, 2, 250): - raise ValueError("data_rate must be 1 (Mbps), 2 (Mbps), or 250 (kbps)") + raise ValueError( + "data_rate must be 1 (Mbps), 2 (Mbps), or 250 (kbps)" + ) + if self.is_plus_variant and speed == 250: + raise NotImplementedError( + "250 kbps data rate is not available for the non-plus " + "variants of the nRF24L01 transceivers." + ) if self.data_rate != speed: speed = 0 if speed == 1 else (0x20 if speed != 2 else 8) self._rf_setup = self._rf_setup & 0xD7 | speed diff --git a/docs/advanced_api.rst b/docs/advanced_api.rst index 27f9e90..097d8e5 100644 --- a/docs/advanced_api.rst +++ b/docs/advanced_api.rst @@ -1,12 +1,14 @@ -.. |irq note| replace:: parameter as `True` to `clear_status_flags()` and reset this. As this - is a virtual representation of the interrupt event, this attribute will always be updated - despite what the actual IRQ pin is configured to do about this event. +.. |irq note| replace:: parameter as `True` to + :py:func:`~circuitpython_nrf24l01.rf24.RF24.clear_status_flags()` and reset this. + As this is a virtual representation of the interrupt event, this attribute will + always be updated despite what the actual IRQ pin is configured to do about this + event. .. |update manually| replace:: Calling this does not execute an SPI transaction. It only exposes that latest data contained in the STATUS byte that's always returned from any - other SPI transactions. Use the `update()` function to manually refresh this data when - needed. + other SPI transactions. Use the :py:func:`~circuitpython_nrf24l01.rf24.RF24.update()` + function to manually refresh this data when needed. Advanced API ------------ diff --git a/docs/ble_api.rst b/docs/ble_api.rst index c3236c1..7650e0e 100644 --- a/docs/ble_api.rst +++ b/docs/ble_api.rst @@ -147,12 +147,25 @@ FakeBLE class .. autoclass:: circuitpython_nrf24l01.fake_ble.FakeBLE - Per the limitations of this technique, only the - :py:attr:`~circuitpython_nrf24l01.rf24.RF24.pa_level` attribute is - available for useful configuration when advertising BLE data. - - :param ~circuitpython_nrf24l01.rf24.RF24 nrf: The object for the nRF24L01 - transceiver to use for fake BLE advertisements. + Per the limitations of this technique, only some of underlying + :py:class:`~circuitpython_nrf24l01.rf24.RF24` functionality is + available for configuration when implementing BLE transmissions. + See the `Available RF24 functionality`_ for more details. + + + :param ~busio.SPI spi: The object for the SPI bus that the nRF24L01 is connected to. + + .. tip:: This object is meant to be shared amongst other driver classes (like + adafruit_mcp3xxx.mcp3008 for example) that use the same SPI bus. Otherwise, multiple + devices on the same SPI bus with different spi objects may produce errors or + undesirable behavior. + :param ~digitalio.DigitalInOut csn: The digital output pin that is connected to the nRF24L01's + CSN (Chip Select Not) pin. This is required. + :param ~digitalio.DigitalInOut ce: The digital output pin that is connected to the nRF24L01's + CE (Chip Enable) pin. This is required. + :param int spi_frequency: Specify which SPI frequency (in Hz) to use on the SPI bus. This + parameter only applies to the instantiated object and is made persistent via + :py:class:`~adafruit_bus_device.spi_device`. to_iphone ************ @@ -284,6 +297,36 @@ advertise() ble.advertise(buffer) ble.hop_channel() +Available RF24 functionality +***************************** + +.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.pa_level + +.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.channel + +.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.data_rate + +.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.payload_length + +.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.power + +.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.is_lna_enabled + +.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.is_plus_variant + +.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.interrupt_config() + +.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.irq_ds + +.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.irq_dr + +.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.clear_status_flags() + +.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.update() + +.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.what_happened() + + Service related classes ----------------------- diff --git a/docs/configure_api.rst b/docs/configure_api.rst index 466dcaf..d38e114 100644 --- a/docs/configure_api.rst +++ b/docs/configure_api.rst @@ -165,15 +165,13 @@ data_rate - ``1`` sets the frequency data rate to 1 Mbps - ``2`` sets the frequency data rate to 2 Mbps - - ``250`` sets the frequency data rate to 250 Kbps + - ``250`` sets the frequency data rate to 250 Kbps (see warning below) Any invalid input throws a `ValueError` exception. Default is 1 Mbps. - .. warning:: 250 Kbps can be buggy on the non-plus models of the nRF24L01 product line. If - you use 250 Kbps data rate, and some transmissions report failed by the transmitting - nRF24L01, even though the same packet in question actually reports received by the - receiving nRF24L01, then try a higher data rate. CAUTION: Higher data rates mean less - maximum distance between nRF24L01 transceivers (and vise versa). + .. warning:: 250 Kbps is not available for the non-plus models of the + nRF24L01 product line. Trying to set the data rate to 250 kpbs when + `is_plus_variant` is `True` will throw a `NotImplementedError`. channel ****************************** @@ -233,5 +231,5 @@ is_lna_enabled .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.is_lna_enabled See `pa_level` attribute about how to set this. Default is always enabled, but this - feature is specific to certain nRF24L01-based circuits. Check with your module's - manufacturer to see is it can toggle the Low Noise Amplifier feature. + feature is specific to non-plus variants of nRF24L01 transceivers. Use + `is_plus_variant` to see if it can toggle the Low Noise Amplifier feature. diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index 931d99b..c624303 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -10,9 +10,10 @@ Troubleshooting info received payloads. `auto_ack` does not require `dynamic_payloads` to be enabled. 2. `dynamic_payloads` feature allows either TX/RX nRF24L01 to be able to send/receive payloads with their size written into the payloads' packet. With this disabled, both - RX/TX nRF24L01 must use matching `payload_length` attributes. For `dynamic_payloads` to - be enabled, the `auto_ack` feature must be enabled. Although, the `auto_ack` feature - can be used when the `dynamic_payloads` feature is disabled. + RX/TX nRF24L01 must use matching + :py:attr:`~circuitpython_nrf24l01.rf24.RF24.payload_length` attributes. For + `dynamic_payloads` to be enabled, the `auto_ack` feature must be enabled. Although, + the `auto_ack` feature can be used when the `dynamic_payloads` feature is disabled. 3. `ack` feature allows the MCU to append a payload to the ACK packet, thus instant bi-directional communication. A transmitting ACK payload must be loaded into the nRF24L01's TX FIFO buffer (done using `load_ack()`) BEFORE receiving the payload that @@ -55,10 +56,11 @@ With the `auto_ack` feature enabled, you get: * The RX pipe's address on the receiving nRF24L01 (passed to `open_rx_pipe()`) MUST match the TX pipe's address on the transmitting nRF24L01 (passed to `open_tx_pipe()`) * `address_length` - * `channel` - * `data_rate` + * :py:attr:`~circuitpython_nrf24l01.rf24.RF24.channel` + * :py:attr:`~circuitpython_nrf24l01.rf24.RF24.data_rate` * `dynamic_payloads` - * `payload_length` only when `dynamic_payloads` is disabled + * :py:attr:`~circuitpython_nrf24l01.rf24.RF24.payload_length` only when `dynamic_payloads` + is disabled * `auto_ack` on the recieving nRF24L01 must be enabled if `arc` is greater than 0 on the transmitting nRF24L01 * custom `ack` payloads @@ -66,9 +68,9 @@ With the `auto_ack` feature enabled, you get: In fact the only attributes that aren't required to match on both endpoint transceivers would be the identifying data pipe number (passed to `open_rx_pipe()` or `load_ack()`), - `pa_level`, `arc`, & `ard` attributes. The ``ask_no_ack`` feature can be used despite the - settings/features configuration (see `send()` & `write()` function parameters for more - details). + :py:attr:`~circuitpython_nrf24l01.rf24.RF24.pa_level`, `arc`, & `ard` attributes. The + ``ask_no_ack`` feature can be used despite the settings/features configuration (see + `send()` & `write()` function parameters for more details). About the lite version ====================== @@ -78,11 +80,12 @@ developed to save space on microcontrollers with limited amount of RAM and/or st boards using the ATSAMD21 M0). The following functionality has been removed from the lite version: - * `is_plus_variant` is removed, meaning the lite version is not compatibility with - the older non-plus variants of the nRF24L01. + * The `FakeBLE` class is not compatible with the ``rf24_lite.py`` module. + * :py:attr:`~circuitpython_nrf24l01.rf24.RF24.is_plus_variant` is removed, meaning the + lite version is not compatibility with the older non-plus variants of the nRF24L01. * `address()` removed. - * `what_happened()` removed. However you can use the following function to dump all - available registers' values (for advanced users): + * :py:func:`~circuitpython_nrf24l01.rf24.RF24.what_happened()` removed. However you can + use the following function to dump all available registers' values (for advanced users): .. code-block:: python @@ -95,7 +98,8 @@ version: print(hex(i), "=", hex(nrf._reg_read(i))) * `fifo()` removed. * `dynamic_payloads` applies to all pipes, not individual pipes. - * `payload_length` applies to all pipes, not individual pipes. + * :py:attr:`~circuitpython_nrf24l01.rf24.RF24.payload_length` applies to all pipes, not + individual pipes. * `read_ack()` removed. This is deprecated on next major release anyway; use `recv()` instead. * `load_ack()` is available, but it will not throw exceptions for malformed ``buf`` or @@ -104,8 +108,10 @@ version: * `auto_ack` removed. This is always enabled for all pipes. Pass ``ask_no_ack`` parameter as `True` to `send()` or `write()` to disable automatic acknowledgement for TX operations. - * `is_lna_enabled` removed. This will always be enabled, and `pa_level` will not accept a - `list` or `tuple`. This only affects certain boards anyway. + * :py:attr:`~circuitpython_nrf24l01.rf24.RF24.is_lna_enabled` removed as it only affects + non-plus variants of the nRF24L01. + * :py:attr:`~circuitpython_nrf24l01.rf24.RF24.pa_level` is available, but it will not + accept a `list` or `tuple`. * `rpd`, `start_carrier_wave()`, & `stop_carrier_wave()` removed. These only perform a test of the nRF24L01's hardware. * `CSN_DELAY` removed. This is hard-coded to 5 milliseconds diff --git a/examples/nrf24l01_context_test.py b/examples/nrf24l01_context_test.py index ba58925..4ae1b69 100644 --- a/examples/nrf24l01_context_test.py +++ b/examples/nrf24l01_context_test.py @@ -2,11 +2,13 @@ Simple example of library usage in context. This will not transmit anything, but rather display settings after changing contexts ( & thus configurations) + + .. warning:: This script is not compatible with the rf24_lite module """ import board import digitalio as dio -# this script is not compatible with rf24_lite for ATSAMD21 M0 based board from circuitpython_nrf24l01.rf24 import RF24 +from circuitpython_nrf24l01.fake_ble import FakeBLE # change these (digital output) pins accordingly ce = dio.DigitalInOut(board.D4) @@ -21,52 +23,45 @@ nrf = RF24(spi, csn, ce) # enable the option to use custom ACK payloads nrf.ack = True +# set the static payload length to 8 bytes +nrf.payload_length = 8 +# RF power amplifier is set to -18 dbm +nrf.pa_level = -18 # the second object has most features disabled/altered -basicRF = RF24(spi, csn, ce) -# disabled dynamic_payloads, but still using enabled auto_ack -basicRF.dynamic_payloads = False +ble = FakeBLE(spi, csn, ce) # the IRQ pin is configured to only go active on "data fail" -basicRF.interrupt_config(data_recv=False, data_sent=False) +# NOTE BLE operations prevent the IRQ pin going active on "data fail" events +ble.interrupt_config(data_recv=False, data_sent=False) # using a different channel: 2 (default is 76) -basicRF.channel = 2 -# CRC is set to 1 byte long -basicRF.crc = 1 +ble.channel = 2 # data rate is set to 2 Mbps -basicRF.data_rate = 2 -# payload length is set to 8 bytes -basicRF.payload_length = 8 -# NOTE address length is set to 3 bytes -basicRF.address_length = 3 +ble.data_rate = 2 # RF power amplifier is set to -12 dbm -basicRF.pa_level = -12 -# automatic retry delay (between attempts) is set to 1000 microseconds -basicRF.ard = 1000 -# automatic retry attempts is set to 15 (maximum allowed) -basicRF.arc = 15 +ble.pa_level = -12 print("\nsettings configured by the nrf object") with nrf: - nrf.open_rx_pipe(5, b"1Node") # NOTE we do this inside the "with" block # only the first character gets written because it is on a pipe_number > 1 - # NOTE if opening pipes outside of the "with" block, you may encounter - # conflicts in the differences between address_length attributes. - # the address_length attribute must equal the length of addresses + nrf.open_rx_pipe(5, b"1Node") # NOTE we do this inside the "with" block # display current settings of the nrf object nrf.what_happened(True) # True dumps pipe info -print("\nsettings configured by the basicRF object") -with basicRF as nerf: # the "as nerf" part is optional - nerf.open_rx_pipe(2, b"?") # again only uses the first character +print("\nsettings configured by the ble object") +with ble as nerf: # the "as nerf" part is optional nerf.what_happened(1) # if you examine the outputs from what_happened() you'll see: -# pipe 5 is opened using the nrf object, but closed using the basicRF object. -# pipe 2 is closed using the nrf object, but opened using the basicRF object. +# pipe 5 is opened using the nrf object, but closed using the ble object. +# pipe 0 is closed using the nrf object, but opened using the ble object. # also notice the different addresses bound to the RX pipes # this is because the "with" statements load the existing settings # for the RF24 object specified after the word "with". -# exiting a with statement will always set the nRF24L01's power mode to sleep +# NOTE it is not advised to manipulate seperate RF24 objects outside of the +# "with" block; you will encounter bugs about configurations when doing so. +# Be sure to use 1 "with" block per RF24 object when instantiating multiple +# RF24 objects in your program. +# NOTE exiting a "with" block will always power down the nRF24L01 # NOTE this library's RF24 class closes all pipes upon instantiation From 38f8cf116fa75f61e3e365f4c219a44e8c058e3d Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 2 Oct 2020 16:18:35 -0700 Subject: [PATCH 113/127] fix ble example --- examples/nrf24l01_fake_ble_test.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index 094775b..60cfcc4 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -24,10 +24,8 @@ # available SPI pins, board.SCK, board.MOSI, board.MISO spi = board.SPI() # init spi bus object -# initialize the nRF24L01 on the spi bus object -radio = RF24(spi, csn, ce) -# overlay BLE compliance on the radio object -nrf = FakeBLE(radio) +# initialize the nRF24L01 on the spi bus object as a BLE compliant radio +nrf = FakeBLE(spi, csn, ce) # the name parameter is going to be its broadcasted BLE name # this can be changed at any time using the `name` attribute @@ -44,7 +42,7 @@ # set the Power Amplifier level to -12 dBm since this test example is # usually run with nRF24L01 transceivers in close proximity -radio.pa_level = -12 +nrf.pa_level = -12 def _prompt(count, iterator): From aed6497b8fe6cec545d40bcfc0158d74eebb3c39 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 2 Oct 2020 16:22:59 -0700 Subject: [PATCH 114/127] didn't need that import anyway --- examples/nrf24l01_fake_ble_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index 60cfcc4..a82899f 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -7,7 +7,6 @@ import time import board import digitalio as dio -from circuitpython_nrf24l01.rf24 import RF24 from circuitpython_nrf24l01.fake_ble import ( chunk, FakeBLE, From a73d2ca100c91d017530e310741d9f1d444987b4 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 2 Oct 2020 17:07:30 -0700 Subject: [PATCH 115/127] typo --- docs/ble_api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ble_api.rst b/docs/ble_api.rst index 7650e0e..5bc6788 100644 --- a/docs/ble_api.rst +++ b/docs/ble_api.rst @@ -121,7 +121,7 @@ crc24_ble() This is exposed for convenience but should not be used for other buffer protocols that require big endian CRC24 format. - :param bytearray,byes data: The buffer of data to be uncorrupted. + :param bytearray,bytes data: The buffer of data to be uncorrupted. :param int deg_poly: A preset "degree polynomial" in which each bit represents a degree who's coefficient is 1. BLE specfications require ``0x00065b`` (default value). From 6c5d6adba45aaf1c52de9b9b490e5cd82cfc7cb8 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 2 Oct 2020 19:07:57 -0700 Subject: [PATCH 116/127] proofreading --- docs/advanced_api.rst | 2 +- docs/basic_api.rst | 23 ++-- docs/ble_api.rst | 309 ++++++++++++++++++++++++------------------ 3 files changed, 189 insertions(+), 145 deletions(-) diff --git a/docs/advanced_api.rst b/docs/advanced_api.rst index 097d8e5..9005a68 100644 --- a/docs/advanced_api.rst +++ b/docs/advanced_api.rst @@ -91,7 +91,7 @@ load_ack() (ACK) packet that is sent when fresh data is received on the specified pipe. See `read_ack()` on how to fetch a received custom ACK payloads. - :param bytearray buf: This will be the data attached to an automatic ACK packet on the + :param bytearray,bytes buf: This will be the data attached to an automatic ACK packet on the incoming transmission about the specified ``pipe_number`` parameter. This must have a length in range [1, 32] bytes, otherwise a `ValueError` exception is thrown. Any ACK payloads will remain in the TX FIFO buffer until transmitted successfully or diff --git a/docs/basic_api.rst b/docs/basic_api.rst index 7033a6e..37746ed 100644 --- a/docs/basic_api.rst +++ b/docs/basic_api.rst @@ -25,14 +25,14 @@ Constructor CE (Chip Enable) pin. This is required. :param int spi_frequency: Specify which SPI frequency (in Hz) to use on the SPI bus. This parameter only applies to the instantiated object and is made persistent via - :py:class:`~adafruit_bus_device.spi_device`. + :py:class:`~adafruit_bus_device.spi_device.SPIDevice`. open_tx_pipe() ****************** .. automethod:: circuitpython_nrf24l01.rf24.RF24.open_tx_pipe - :param bytearray address: The virtual address of the receiving nRF24L01. The address + :param bytearray,bytes address: The virtual address of the receiving nRF24L01. The address specified here must match the address set to one of the RX data pipes of the receiving nRF24L01. The existing address can be altered by writting a bytearray with a length less than 5. The nRF24L01 will use the first `address_length` number of bytes for the @@ -63,7 +63,7 @@ open_rx_pipe() :param int pipe_number: The data pipe to use for RX transactions. This must be in range [0, 5]. Otherwise a `ValueError` exception is thrown. - :param bytearray address: The virtual address to the receiving nRF24L01. If using a + :param bytearray,bytes address: The virtual address to the receiving nRF24L01. If using a ``pipe_number`` greater than 1, then only the MSByte of the address is written, so make sure MSByte (first character) is unique among other simultaneously receiving addresses. The existing address can be altered by writing a bytearray with a length less than 5. @@ -113,7 +113,7 @@ recv() This function can also be used to fetch the last ACK packet's payload if `ack` is enabled. :param int length: An optional parameter to specify how many bytes to read from the RX - FIFO buffer. This parameter is not contrained in any way. + FIFO buffer. This parameter is not constrained in any way. - If this parameter is less than the length of the first available payload in the RX FIFO buffer, then the payload will remain in the RX FIFO buffer until the @@ -127,11 +127,13 @@ recv() aware that a payload is only removed from the RX FIFO buffer when the entire payload has been fetched by this function. Notice that this function always starts reading data from the first byte of the first available payload (if - any) in the RX FIFO buffer. + any) in the RX FIFO buffer. Remember the RX FIFO buffer can hold up to 3 + payloads at a maximum of 32 bytes each. :returns: - A `bytearray` of the RX payload data or `None` if there is no payload. If the - ``length`` parameter is not specified, then one of the following two scenarios is - applied. + If the ``length`` parameter is not specified, then this function returns `bytearray` + of the RX payload data or `None` if there is no payload. This also depends on the + setting of `dynamic_payloads` & `payload_length` attributes. Consider the following + two scenarios: - If the `dynamic_payloads` attribute is disabled, then the returned bytearray's length is equal to the user defined `payload_length` attribute for the data pipe @@ -139,6 +141,9 @@ recv() - If the `dynamic_payloads` attribute is enabled, then the returned bytearray's length is equal to the payload's length + When the ``length`` parameter is specified, this function strictly returns a `bytearray` + of that length despite the contents of the RX FIFO. + send() ****************** @@ -156,7 +161,7 @@ send() expects a responding custom ACK payload, the response is returned (upon successful transmission) as a `bytearray` (or `True` if ACK payload is empty) - :param bytearray,list,tuple buf: The payload to transmit. This bytearray must have a + :param bytearray,bytes,list,tuple buf: The payload to transmit. This bytearray must have a length in range [1, 32], otherwise a `ValueError` exception is thrown. This can also be a list or tuple of payloads (`bytearray`); in which case, all items in the list/tuple are processed for consecutive transmissions. diff --git a/docs/ble_api.rst b/docs/ble_api.rst index 5bc6788..cbe2505 100644 --- a/docs/ble_api.rst +++ b/docs/ble_api.rst @@ -91,66 +91,66 @@ chunk() .. autofunction:: circuitpython_nrf24l01.fake_ble.chunk - :param bytearray,bytes buf: The actual data contained in the block. - :param int data_type: The type of data contained in the chunk. This is a - predefined number according to BLE specifications. The default value - ``0x16`` describes all service data. ``0xFF`` describes manufacturer - information. Any other values are not applicable to BLE - advertisements. - - .. important:: This function is called internally by - :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.advertise()`. - To pack multiple data values into a single payload, use this function - for each data value and pass a `list` or `tuple` of the returned - results to - :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.advertise()` - (see example code in documentation about - :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.advertise()` - for more detail). Remember that broadcasting multiple data values may - require the :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.name` - be set to `None` and/or the - :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.show_pa_level` be - set to `False` for reasons about the payload size with - `BLE Limitations`_. + :param bytearray,bytes buf: The actual data contained in the block. + :param int data_type: The type of data contained in the chunk. This is a + predefined number according to BLE specifications. The default value + ``0x16`` describes all service data. ``0xFF`` describes manufacturer + information. Any other values are not applicable to BLE + advertisements. + + .. important:: This function is called internally by + :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.advertise()`. + To pack multiple data values into a single payload, use this function + for each data value and pass a `list` or `tuple` of the returned + results to + :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.advertise()` + (see example code in documentation about + :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.advertise()` + for more detail). Remember that broadcasting multiple data values may + require the :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.name` + be set to `None` and/or the + :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.show_pa_level` be + set to `False` for reasons about the payload size with + `BLE Limitations`_. crc24_ble() ***************** .. autofunction:: circuitpython_nrf24l01.fake_ble.crc24_ble - This is exposed for convenience but should not be used for other buffer - protocols that require big endian CRC24 format. + This is exposed for convenience but should not be used for other buffer + protocols that require big endian CRC24 format. - :param bytearray,bytes data: The buffer of data to be uncorrupted. - :param int deg_poly: A preset "degree polynomial" in which each bit - represents a degree who's coefficient is 1. BLE specfications require - ``0x00065b`` (default value). - :param int init_val: This will be the initial value that the checksum - will use while shifting in the buffer data. BLE specfications require - ``0x555555`` (default value). - :returns: A 24-bit `bytearray` representing the checksum of the data (in - proper little endian). + :param bytearray,bytes data: The buffer of data to be uncorrupted. + :param int deg_poly: A preset "degree polynomial" in which each bit + represents a degree who's coefficient is 1. BLE specfications require + ``0x00065b`` (default value). + :param int init_val: This will be the initial value that the checksum + will use while shifting in the buffer data. BLE specfications require + ``0x555555`` (default value). + :returns: A 24-bit `bytearray` representing the checksum of the data (in + proper little endian). BLE_FREQ ***************** .. autodata:: circuitpython_nrf24l01.fake_ble.BLE_FREQ - This tuple contains the relative predefined channels used: + This tuple contains the relative predefined channels used: - * nRF channel 2 == BLE channel 37 - * nRF channel 26 == BLE channel 38 - * nRF channel 80 == BLE channel 39 + * nRF channel 2 == BLE channel 37 + * nRF channel 26 == BLE channel 38 + * nRF channel 80 == BLE channel 39 FakeBLE class ------------- .. autoclass:: circuitpython_nrf24l01.fake_ble.FakeBLE - Per the limitations of this technique, only some of underlying - :py:class:`~circuitpython_nrf24l01.rf24.RF24` functionality is - available for configuration when implementing BLE transmissions. - See the `Available RF24 functionality`_ for more details. + Per the limitations of this technique, only some of underlying + :py:class:`~circuitpython_nrf24l01.rf24.RF24` functionality is + available for configuration when implementing BLE transmissions. + See the `Available RF24 functionality`_ for more details. :param ~busio.SPI spi: The object for the SPI bus that the nRF24L01 is connected to. @@ -165,7 +165,7 @@ FakeBLE class CE (Chip Enable) pin. This is required. :param int spi_frequency: Specify which SPI frequency (in Hz) to use on the SPI bus. This parameter only applies to the instantiated object and is made persistent via - :py:class:`~adafruit_bus_device.spi_device`. + :py:class:`~adafruit_bus_device.spi_device.SPIDevice`. to_iphone ************ @@ -189,23 +189,23 @@ name .. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.name - This is not required. In - fact setting this attribute will subtract from the available payload - length (in bytes). Set this attribute to `None` to disable advertising the device name + This is not required. In fact setting this attribute will subtract from + the available payload length (in bytes). Set this attribute to `None` to + disable advertising the device name. - .. note:: This information occupies (in the TX FIFO) an extra 2 bytes plus - the length of the name set by this attribute. + .. note:: This information occupies (in the TX FIFO) an extra 2 bytes plus + the length of the name set by this attribute. show_pa_level ************* .. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.show_pa_level - The default value of `False` will exclude this optional information. + The default value of `False` will exclude this optional information. - .. note:: This information occupies (in the TX FIFO) an extra 3 bytes, and is - really only useful for some applications to calculate proximity to the - nRF24L01 transceiver. + .. note:: This information occupies (in the TX FIFO) an extra 3 bytes, and is + really only useful for some applications to calculate proximity to the + nRF24L01 transceiver. hop_channel() ************* @@ -217,113 +217,152 @@ whiten() .. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.whiten - This is done according to BLE specifications. + This is done according to BLE specifications. - :param bytearray data: The packet to whiten. - :returns: A `bytearray` of the ``data`` with the whitening algorythm - applied. + :param bytearray,bytes data: The packet to whiten. + :returns: A `bytearray` of the ``data`` with the whitening algorythm + applied. - .. warning:: This function uses the currently set BLE channel as a - base case for the whitening coefficient. Do not call - `hop_channel()` before using this function to de-whiten received - payloads (which isn't officially supported yet). Note that - `advertise()` uses this function internally to prevent such - improper usage. + .. warning:: This function uses the currently set BLE channel as a + base case for the whitening coefficient. Do not call + `hop_channel()` before using this function to de-whiten received + payloads (which isn't officially supported yet). Note that + `advertise()` uses this function internally to prevent such + improper usage. available() ************* .. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.available - This is detirmined from the current state of `name` and `show_pa_level` - attributes. + This is detirmined from the current state of `name` and `show_pa_level` + attributes. - :param bytearray,bytes hypothetical: Pass a potential `chunk()` of - data to this parameter to calculate the resulting left over length - in bytes. This parameter is optional. - :returns: An `int` representing the length of available bytes for the - a single payload. + :param bytearray,bytes hypothetical: Pass a potential `chunk()` of + data to this parameter to calculate the resulting left over length + in bytes. This parameter is optional. + :returns: An `int` representing the length of available bytes for the + a single payload. advertise() ************* .. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.advertise - :returns: Nothing as every transmission will register as a success - under the required settings for BLE beacons. - - :param bytearray buf: The payload to transmit. This bytearray must have - a length greater than 0 and less than 22 bytes Otherwise a - `ValueError` exception is thrown whose prompt will tell you the - maximum length allowed under the current configuration. This can - also be a list or tuple of payloads (`bytearray`); in which case, - all items in the list/tuple are processed are packed into 1 - payload for a single transmissions. See example code below about - passing a `list` or `tuple` to this parameter. - :param int data_type: This is used to describe the buffer data passed - to the ``buf`` parameter. ``0x16`` describes all service data. The - default value ``0xFF`` describes manufacturer information. This - parameter is ignored when a `tuple` or `list` is passed to the - ``buf`` parameter. Any other values are not applicable to BLE - advertisements. - - .. important:: If the name and/or TX power level of the emulated BLE - device is also to be broadcast, then the 'name' and/or - `show_pa_level` attribute(s) should be set prior to calling - `advertise()`. - - To pass multiple data values to the ``buf`` parameter see the - following code as an example: - - .. code-block:: python - - # let UUIDs be the 16-bit identifier that corresponds to the - # BLE service type. The following values are not compatible with - # BLE advertisements. - UUID_1 = 0x1805 - UUID_2 = 0x1806 - service1 = ServiceData(UUID_1) - service2 = ServiceData(UUID_2) - service1.data = b"some value 1" - service2.data = b"some value 2" - - # make a tuple of the buffers - buffers = ( + :returns: Nothing as every transmission will register as a success + under the required settings for BLE beacons. + + :param bytearray buf: The payload to transmit. This bytearray must have + a length greater than 0 and less than 22 bytes Otherwise a + `ValueError` exception is thrown whose prompt will tell you the + maximum length allowed under the current configuration. This can + also be a list or tuple of payloads (`bytearray`); in which case, + all items in the list/tuple are processed are packed into 1 + payload for a single transmissions. See example code below about + passing a `list` or `tuple` to this parameter. + :param int data_type: This is used to describe the buffer data passed + to the ``buf`` parameter. ``0x16`` describes all service data. The + default value ``0xFF`` describes manufacturer information. This + parameter is ignored when a `tuple` or `list` is passed to the + ``buf`` parameter. Any other values are not applicable to BLE + advertisements. + + .. important:: If the name and/or TX power level of the emulated BLE + device is also to be broadcast, then the 'name' and/or + `show_pa_level` attribute(s) should be set prior to calling + `advertise()`. + + To pass multiple data values to the ``buf`` parameter see the + following code as an example: + + .. code-block:: python + + # let UUIDs be the 16-bit identifier that corresponds to the + # BLE service type. The following values are not compatible with + # BLE advertisements. + UUID_1 = 0x1805 + UUID_2 = 0x1806 + service1 = ServiceData(UUID_1) + service2 = ServiceData(UUID_2) + service1.data = b"some value 1" + service2.data = b"some value 2" + + # make a tuple of the buffers + buffers = ( chunk(service1.buffer), chunk(service2.buffer) - ) + ) - # let `ble` be the instantiated object of the FakeBLE class - ble.advertise(buffer) - ble.hop_channel() + # let `ble` be the instantiated object of the FakeBLE class + ble.advertise(buffers) + ble.hop_channel() Available RF24 functionality ***************************** +pa_level +#################### + .. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.pa_level +channel +#################### + .. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.channel +data_rate +#################### + .. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.data_rate +payload_length +#################### + .. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.payload_length +power +#################### + .. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.power +is_lna_enabled +#################### + .. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.is_lna_enabled +is_plus_variant +#################### + .. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.is_plus_variant +interrupt_config() +#################### + .. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.interrupt_config() +irq_ds +#################### + .. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.irq_ds +irq_dr +#################### + .. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.irq_dr +clear_status_flags() +#################### + .. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.clear_status_flags() +update() +#################### + .. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.update() +what_happened() +#################### + .. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.what_happened() @@ -334,40 +373,40 @@ abstract parent *************** .. autoclass:: circuitpython_nrf24l01.fake_ble.ServiceData - :members: - :special-members: __len__ + :members: + :special-members: __len__ - :param int uuid: The 16-bit UUID `"GATT Service assigned number" - `_ defined by the - Bluetooth SIG to describe the service data. This parameter is - required. + :param int uuid: The 16-bit UUID `"GATT Service assigned number" + `_ defined by the + Bluetooth SIG to describe the service data. This parameter is + required. derivitive children ******************* .. autoclass:: circuitpython_nrf24l01.fake_ble.TemperatureServiceData - :show-inheritance: + :show-inheritance: - This class's `data` attribute accepts a `float` value as - input and returns a `bytes` object that conforms to the Bluetooth - Health Thermometer Measurement format as defined in the `GATT - Specifications Supplement. `_ + This class's `data` attribute accepts a `float` value as + input and returns a `bytes` object that conforms to the Bluetooth + Health Thermometer Measurement format as defined in the `GATT + Specifications Supplement. `_ .. autoclass:: circuitpython_nrf24l01.fake_ble.BatteryServiceData - :show-inheritance: + :show-inheritance: - The class's `data` attribute accepts a `int` value as - input and returns a `bytes` object that conforms to the Bluetooth - Battery Level format as defined in the `GATT Specifications - Supplement. `_ + The class's `data` attribute accepts a `int` value as + input and returns a `bytes` object that conforms to the Bluetooth + Battery Level format as defined in the `GATT Specifications + Supplement. `_ .. autoclass:: circuitpython_nrf24l01.fake_ble.UrlServiceData - :members: pa_level_at_1_meter - :show-inheritance: + :members: pa_level_at_1_meter + :show-inheritance: - This class's `data` attribute accepts a `str` of URL data as input, and - returns the URL as a `bytes` object where some of the URL parts are - encoded using `Eddystone byte codes as defined by the specifications. - `_ \ No newline at end of file + This class's `data` attribute accepts a `str` of URL data as input, and + returns the URL as a `bytes` object where some of the URL parts are + encoded using `Eddystone byte codes as defined by the specifications. + `_ \ No newline at end of file From e2488d6144ff514de2c35cf65faabc93eacc5ba7 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Fri, 2 Oct 2020 21:07:40 -0700 Subject: [PATCH 117/127] new send_only param for send() & resend() --- circuitpython_nrf24l01/rf24.py | 26 +++++++++++++------------- circuitpython_nrf24l01/rf24_lite.py | 8 ++++---- docs/advanced_api.rst | 8 ++++++++ docs/basic_api.rst | 10 +++++++++- 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index a781d80..12803a7 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -31,16 +31,16 @@ except ImportError: from adafruit_bus_device.spi_device import SPIDevice -CONFIGURE = const(0x00) # IRQ, CRC, PWR control & RX/TX roles -AUTO_ACK = const(0x01) # auto-ACK -OPEN_PIPES = const(0x02) # open/close pipes -SETUP_RETR = const(0x04) # auto-retry count & delay -RF_PA_RATE = const(0x06) # RF Power Amplifier & Data Rate -RX_ADDR_P0 = const(0x0A) # RX pipe addresses == [0, 5]:[0x0a, 0x0f] +CONFIGURE = const(0x00) # IRQ masking, CRC scheme, PWR control, & RX/TX roles +AUTO_ACK = const(0x01) # auto-ACK status for all pipes +OPEN_PIPES = const(0x02) # open/close RX status for all pipes +SETUP_RETR = const(0x04) # auto-retry count & delay values +RF_PA_RATE = const(0x06) # RF Power Amplifier & Data Rate values +RX_ADDR_P0 = const(0x0A) # RX pipe addresses; pipes 0-5 = 0x0A-0x0F TX_ADDRESS = const(0x10) # Address used for TX transmissions -RX_PL_LENG = const(0x11) # RX payload widths on pipes == [0, 5]:[0x11, 0x16] -DYN_PL_LEN = const(0x1C) # dynamic payloads -TX_FEATURE = const(0x1D) # TX features dynamic payloads, ACK payloads, NO_ACK +RX_PL_LENG = const(0x11) # RX payload widths; pipes 0-5 = 0x11-0x16 +DYN_PL_LEN = const(0x1C) # dynamic payloads status for all pipes +TX_FEATURE = const(0x1D) # dynamic TX-payloads, TX-ACK payloads, TX-NO_ACK CSN_DELAY = 0.005 """The delay time (in seconds) used to let the CSN pin settle, allowing a clean SPI transaction.""" @@ -274,7 +274,7 @@ def recv(self, length=None): self.clear_status_flags(True, False, False) return result - def send(self, buf, ask_no_ack=False, force_retry=0): + def send(self, buf, ask_no_ack=False, force_retry=0, send_only=False): """This blocking function is used to transmit payload(s).""" self.ce_pin.value = 0 if isinstance(buf, (list, tuple)): @@ -296,7 +296,7 @@ def send(self, buf, ask_no_ack=False, force_retry=0): result = self.resend() if result is None or result: break - if self._status & 0x60 == 0x60: + if self._status & 0x60 == 0x60 and not send_only: result = self.recv() self.clear_status_flags(False) return result @@ -694,7 +694,7 @@ def update(self): from the nRF24L01.""" self._reg_write(0xFF) - def resend(self): + def resend(self, send_only=False): """Use this function to maunally re-send the previous payload in the top level (first out) of the TX FIFO buffer.""" result = False @@ -710,7 +710,7 @@ def resend(self): while not self._status & 0x30: self.update() result = self.irq_ds - if self._status & 0x60 == 0x60: + if self._status & 0x60 == 0x60 and not send_only: result = self.recv() self.clear_status_flags(False) return result diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index 5d927aa..dfa7763 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -140,7 +140,7 @@ def recv(self, length=None): self.clear_status_flags(True, False, False) return result - def send(self, buf, ask_no_ack=False, force_retry=0): + def send(self, buf, ask_no_ack=False, force_retry=0, send_only=False): self.ce_pin.value = 0 if isinstance(buf, (list, tuple)): result = [] @@ -161,7 +161,7 @@ def send(self, buf, ask_no_ack=False, force_retry=0): result = self.resend() if result is None or result: break - if self._status & 0x60 == 0x60: + if self._status & 0x60 == 0x60 and not send_only: result = self.recv() self.clear_status_flags(False) return result @@ -303,7 +303,7 @@ def pa_level(self, pwr): def update(self): self._reg_write(0xFF) - def resend(self): + def resend(self, send_only=False): result = False if not self._reg_read(0x17) & 0x10: if self.pipe is not None: @@ -317,7 +317,7 @@ def resend(self): while not self._status & 0x30: self.update() result = self.irq_ds - if self._status & 0x60 == 0x60: + if self._status & 0x60 == 0x60 and not send_only: result = self.recv() self.clear_status_flags(False) return result diff --git a/docs/advanced_api.rst b/docs/advanced_api.rst index 9005a68..4ca4734 100644 --- a/docs/advanced_api.rst +++ b/docs/advanced_api.rst @@ -264,6 +264,14 @@ resend() All returned data from this function follows the same patttern that `send()` returns with the added condition that this function will return `False` if the TX FIFO buffer is empty. + :param bool send_only: This parameter only applies when the `ack` attribute is set to + `True`. Pass this parameter as `True` if you want to handle fetching the ACK + payload (from the RX FIFO) seperately from the sending transmission that recieved + the ACK payload. Many other libraries' behave as though this parameter is `True` + (e.g. The popular TMRh20 Arduino RF24 library). Use `recv()` to get the ACK + payload (if there is any) from the RX FIFO. This parameter defaults to `False`. + Remember that the RX FIFO can only hold up to 3 payloads at once. + .. note:: The nRF24L01 normally removes a payload from the TX FIFO buffer after successful transmission, but not when this function is called. The payload (successfully transmitted or not) will remain in the TX FIFO buffer until `flush_tx()` is called to diff --git a/docs/basic_api.rst b/docs/basic_api.rst index 37746ed..9e0b9c8 100644 --- a/docs/basic_api.rst +++ b/docs/basic_api.rst @@ -159,7 +159,8 @@ send() - `True` if transmission succeeds. - `bytearray` or `True` when the `ack` attribute is `True`. Because the payload expects a responding custom ACK payload, the response is returned (upon successful - transmission) as a `bytearray` (or `True` if ACK payload is empty) + transmission) as a `bytearray` (or `True` if ACK payload is empty). This functionality + can be bypassed by setting the ``send_only`` parameter as `True`. :param bytearray,bytes,list,tuple buf: The payload to transmit. This bytearray must have a length in range [1, 32], otherwise a `ValueError` exception is thrown. This can @@ -195,6 +196,13 @@ send() notes on `resend()` as using this parameter carries the same implications documented there. This parameter has no effect if the ``ask_no_ack`` parameter is set to `True` or if `arc` is disabled. + :param bool send_only: This parameter only applies when the `ack` attribute is set to + `True`. Pass this parameter as `True` if you want to handle fetching the ACK + payload (from the RX FIFO) seperately from the sending transmission that recieved + the ACK payload. Many other libraries' behave as though this parameter is `True` + (e.g. The popular TMRh20 Arduino RF24 library). Use `recv()` to get the ACK + payload (if there is any) from the RX FIFO. This parameter defaults to `False`. + Remember that the RX FIFO can only hold up to 3 payloads at once. .. tip:: It is highly recommended that `arc` attribute is enabled (greater than ``0``) when sending multiple payloads. Test results with the `arc` attribute disabled were From 36aa0ad67a269b0eecd66a7b36ca9e17884a60b6 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sat, 3 Oct 2020 03:18:44 -0700 Subject: [PATCH 118/127] skip a pipe if when index's value is invalid --- circuitpython_nrf24l01/rf24.py | 6 +++--- docs/configure_api.rst | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 12803a7..4ee6800 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -478,7 +478,7 @@ def dynamic_payloads(self, enable): self._dyn_pl = 0x3F if enable else 0 elif isinstance(enable, (list, tuple)): for i, val in enumerate(enable): - if i < 6: + if i < 6 and val >= 0: # skip pipe if val is negative self._dyn_pl = (self._dyn_pl & ~(1 << i)) | (bool(val) << i) else: raise ValueError("dynamic_payloads: {} is an invalid input" % enable) @@ -505,7 +505,7 @@ def payload_length(self, length): raise ValueError("length {} is not a valid input".format(length)) for i, val in enumerate(length): if i < 6: - if not val or val <= 32: # don't throw exception, just skip pipe + if 0 < val <= 32: # don't throw exception, just skip pipe self._pl_len[i] = val self._reg_write(RX_PL_LENG + i, val) @@ -553,7 +553,7 @@ def auto_ack(self, enable): self._aa = 0x3F if enable else 0 elif isinstance(enable, (list, tuple)): for i, val in enumerate(enable): - if i < 6: + if i < 6 and val >= 0: # skip pipe if val is negative self._aa = (self._aa & ~(1 << i)) | (bool(val) << i) else: raise ValueError("auto_ack: {} is not a valid input" % enable) diff --git a/docs/configure_api.rst b/docs/configure_api.rst index d38e114..89d20c0 100644 --- a/docs/configure_api.rst +++ b/docs/configure_api.rst @@ -22,7 +22,8 @@ dynamic_payloads disabled for any data pipes. - A `list` or `tuple` containing booleans or integers can be used control this feature per data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be - ignored since there are only 6 data pipes. + ignored since there are only 6 data pipes. If any index's value is less than 0 (a + negative value), then the pipe corresponding to that index will remain unaffected. .. note:: This attribute mostly relates to RX operations, but data pipe 0 applies to TX @@ -68,7 +69,8 @@ auto_ack data pipes. - A `list` or `tuple` containing booleans or integers can be used control this feature per data pipe. Index 0 controls this feature on data pipe 0. Indices greater than 5 will be - ignored since there are only 6 data pipes. + ignored since there are only 6 data pipes. If any index's value is less than 0 (a + negative value), then the pipe corresponding to that index will remain unaffected. .. note:: This attribute mostly relates to RX operations, but data pipe 0 applies to TX From d40f30988352f1ed6de5398b9e37ec968c3c1b56 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sat, 3 Oct 2020 17:12:33 -0700 Subject: [PATCH 119/127] readme placeholder; adjust ble example --- README.rst | 243 +------------------------- circuitpython_nrf24l01/fake_ble.py | 2 +- docs/greetings.rst | 267 +++++++++++++++++++++++++++++ docs/index.rst | 5 +- docs/troubleshooting.rst | 49 ++++++ examples/nrf24l01_fake_ble_test.py | 19 +- 6 files changed, 340 insertions(+), 245 deletions(-) create mode 100644 docs/greetings.rst diff --git a/README.rst b/README.rst index c8c259e..a71c491 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,4 @@ -.. .. only:: html - .. image:: https://readthedocs.org/projects/circuitpython-nrf24l01/badge/?version=stable :target: https://circuitpython-nrf24l01.readthedocs.io/en/stable/ :alt: Documentation Status @@ -17,8 +15,13 @@ :alt: Total PyPI downloads :target: https://pepy.tech/project/circuitpython-nrf24l01 -Introduction -============ +Read The Docs +============= + +Documentation for this library is hosted at `ReadTheDocs.org `_ + +About this Library +================== Circuitpython driver library for the nRF24L01 transceiver @@ -29,234 +32,4 @@ Original work by Damien P. George & Peter Hinch can be found `here The Micropython source has been rewritten to expose all the nRF24L01's features and for compatibilty with the Raspberry Pi and other Circuitpython compatible devices. Modified by Brendan Doherty, Rhys Thomas -* Author(s): Damien P. George, Peter Hinch, Rhys Thomas, Brendan Doherty - -Features currently supported -============================ - -* change the addresses' length (can be 3 to 5 bytes long) -* dynamically sized payloads (max 32 bytes each) or statically sized payloads -* automatic responding acknowledgment (ACK) for verifying transmission success -* custom acknowledgment (ACK) payloads for bi-directional communication -* flag a single payload for no acknowledgment (ACK) from the receiving nRF24L01 -* "re-use the same payload" feature (for manually re-transmitting failed transmissions that - remain in the buffer) -* multiple payload transmissions with one function call (MUST read documentation on the - `send()` function) -* context manager compatible for easily switching between different radio configurations - using "with" statements (not available in ``rf24_lite.py`` variant for M0 based boards) -* configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or - failed transmissions (these 3 flags control the 1 IRQ pin). There's also virtual - representations of these interrupt flags available (see - :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr`, - :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_ds`, & `irq_df` attributes) -* invoke sleep mode (AKA power down mode) for ultra-low current consumption -* cyclic redundancy checking (CRC) up to 2 bytes long -* adjust the nRF24L01's builtin automatic re-transmit feature's parameters (`arc`: number - of attempts, `ard`: delay between attempts) -* adjust the nRF24L01's frequency channel (2.4-2.525 GHz) -* adjust the nRF24L01's power amplifier level (0, -6, -12, or -18 dBm) -* adjust the nRF24L01's RF data rate (250Kbps is buggy due to hardware design, but 1Mbps and 2Mbps are reliable) -* a nRF24L01 driven by this library can communicate with a nRF24L01 on an Arduino driven by the `TMRh20 RF24 library `_. See the nrf24l01_2arduino_handling_data.py code in the `examples folder of this library's repository `_ -* fake BLE module for sending BLE beacon advertisments from the nRF24L01 as outlined by `Dmitry Grinberg in his write-up (including C source code) `_. - -Features currently unsupported -============================== - -* as of yet, no [intended] implementation for Multiceiver mode (up to 6 TX nRF24L01 "talking" to 1 RX nRF24L01 simultaneously). Although this might be acheived easily using the "automatic retry delay" (`ard`) and "automatic retry count" (`arc`) attributes set accordingly (varyingly high -- this has not been tested). - -Dependencies -============= -This driver depends on: - -* `Adafruit CircuitPython `_ -* `Bus Device `_ - -Please ensure all dependencies are available on the CircuitPython filesystem. -This is easily achieved by downloading -`the Adafruit library and driver bundle `_. - -.. note:: This library supports Python 3.4 or newer, but Python 3.7 introduced - the function `time.monotonic_ns() `_ which returns an arbitrary time "counter" - as an `int` of nanoseconds. However, this function is not used in the - example scripts for backward compatibility reasons. Instead, we used - :py:func:`~time.monotonic()` which returns an arbitrary time "counter" as - a `float` of seconds. CircuitPython firmware supports both functions as of - v4.0. - -Installing from PyPI -===================== - -On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from -PyPI `_. To install for current user: - -.. code-block:: shell - - pip3 install circuitpython-nrf24l01 - -To install system-wide (this may be required in some cases): - -.. code-block:: shell - - sudo pip3 install circuitpython-nrf24l01 - -To install in a virtual environment in your current project: - -.. code-block:: shell - - mkdir project-name && cd project-name - python3 -m venv .env - source .env/bin/activate - pip3 install circuitpython-nrf24l01 - -Pinout -====== -.. image:: https://lastminuteengineers.com/wp-content/uploads/2018/07/Pinout-nRF24L01-Wireless-Transceiver-Module.png - :target: https://lastminuteengineers.com/nrf24l01-arduino-wireless-communication/#nrf24l01-transceiver-module-pinout - -The nRF24L01 is controlled through SPI so there are 3 pins (SCK, MOSI, & MISO) that can only be connected to their counterparts on the MCU (microcontroller unit). The other 2 essential pins (CE & CSN) can be connected to any digital output pins. Lastly, the only optional pin on the nRf24L01 GPIOs is the IRQ (interrupt; a digital output that's active when low) pin and is only connected to the MCU via a digital input pin during the interrupt example. The following pinout is used in the example codes of this library's `example directory `_. - -.. csv-table:: - :header: nRF2401, "Raspberry Pi", "ItsyBitsy M4" - - GND, GND, GND - VCC, 3V, 3.3V - CE, GPIO4, D4 - CSN, GPIO5, D5 - SCK, "GPIO11 (SCK)", SCK - MOSI, "GPIO10 (MOSI)", MOSI - MISO, "GPIO9 (MISO)", MISO - IRQ, GPIO12, D12 - -.. tip:: User reports and personal experiences have improved results if there is a capacitor of 100 mirofarads [+ another optional 0.1 microfarads capacitor for added stability] connected in parrallel to the VCC and GND pins. - -Using The Examples -================== - -See `examples `_ for testing certain features of this the library. The examples were developed and tested on both Raspberry Pi and ItsyBitsy M4. Pins have been hard coded in the examples for the corresponding device, so please adjust these accordingly to your circuitpython device if necessary. - -To run the simple example, navigate to this repository's "examples" folder in the terminal. If you're working with a CircuitPython device (not a Raspberry Pi), copy the file named "nrf24l01_simple_test.py" from this repository's "examples" folder to the root directory of your CircuitPython device's CIRCUITPY drive. Now you're ready to open a python REPR and run the following commands: - -.. code-block:: python - - >>> from nrf24l01_simple_test import * - nRF24L01 Simple test. - Run slave() on receiver - Run master() on transmitter - >>> master() - Sending: 5 as struct: b'\x05\x00\x00\x00' - send() successful - Transmission took 36.0 ms - Sending: 4 as struct: b'\x04\x00\x00\x00' - send() successful - Transmission took 28.0 ms - Sending: 3 as struct: b'\x03\x00\x00\x00' - send() successful - Transmission took 24.0 ms - - -Where do I get 1? -================= - -See the store links on the sidebar or just google "nRF24L01+". It is worth noting that you -generally want to buy more than 1 as you need 2 for testing -- 1 to send & 1 to receive and -vise versa. This library has been tested on a cheaply bought 6 pack from Amazon.com, but don't -take Amazon or eBay for granted! There are other wireless transceivers that are NOT compatible -with this library. For instance, the esp8266-01 (also sold in packs) is NOT compatible with -this library, but looks very similar to the nRF24L01+ and could lead to an accidental purchase. - -About the nRF24L01+ -=================== - -Stablizing the power input to the VCC and GND using parallel capacitors (100 µF + an optional -0.1µF) provides significant performance increases. This stability is very highly recommended! -More finite details about the nRF24L01 are available from the datasheet (referenced here in -the documentation as the `nRF24L01+ Specification Sheet `_) - -About the nRF24L01+PA+LNA modules -================================= - -You may find variants of the nRF24L01 transceiver that are marketed as "nRF24L01+PA+LNA". -These modules are distinct in the fact that they come with a detachable (SMA-type) antenna. -They employ seperate RFX24C01 IC with the antenna for enhanced Power Amplification (PA) and -Low Noise Amplification (LNA) features. While they boast greater range with the same -functionality, they are subject to a couple lesser known (and lesser advertised) drawbacks: - -1. Stronger power source. Below is a chart of advertised current requirements that many MCU - boards' 3V regulators may not be able to handle. - - .. csv-table:: - :header: Specification, Value - :widths: 10,5 - - "Emission mode current(peak)", "115 mA" - "Receive Mode current(peak)", "45 mA" - "Power-down mode current", "4.2 µA" -2. Needs sheilding from electromagnetic interference. Sheilding works best when it has a path - to ground (GND pin) - -nRF24L01(+) clones and counterfeits -=================================== - -This library does not directly support clones/counterfeits as there is no way for the library -to differentiate between an actual nRF24L01+ and a clone/counterfeit. To determine if your -purchase is a counterfeit, please contact the retailer you purxhased from (`reading this -article and its links might help -`_). The most notable clone is the `Si24R1 `_. I could not find -the `Si24R1 datasheet `_ in english. Troubleshooting -the SI24R1 may require `replacing the onboard antennae with a wire -`_. Furthermore, the Si24R1 has different power -amplifier options as noted in the `RF_PWR section (bits 0 through 2) of the RF_SETUP register -(hex address 6) of the datasheet `_. -While the options' values differ from those identified by this library's API, the -underlying commands to configure those options are almost identical to the nRF24L01. Other -known clones include the bk242x (AKA RFM7x). - -Future Project Ideas/Additions -============================== - - The following are only ideas; they are not currently supported by this circuitpython library. - - * `There's a few blog posts by Nerd Ralph demonstrating how to use the nRF24L01 via 2 or 3 - pins `_ (uses custom bitbanging SPI functions and an external circuit involving a - resistor and a capacitor) - * network linking layer, maybe something like `TMRh20's RF24Network - `_ - * implement the Gazelle-based protocol used by the BBC micro-bit (`makecode.com's radio - blocks `_). - -Contributing -============ - -Contributions are welcome! Please read our `Code of Conduct -`_ -before contributing to help this project stay welcoming. To contribute, all you need to do is fork `this repository `_, develop your idea(s) and submit a pull request when stable. To initiate a discussion of idea(s), you need only open an issue on the aforementioned repository (doesn't have to be a bug report). - -Sphinx documentation ------------------------ - -Sphinx is used to build the documentation based on rST files and comments in the code. First, -install dependencies (feel free to reuse the virtual environment from `above `_): - -.. code-block:: shell - - python3 -m venv .env - source .env/bin/activate - pip install Sphinx sphinx-rtd-theme - -Now, once you have the virtual environment activated: - -.. code-block:: shell - - cd docs - sphinx-build -E -W -b html . _build - -This will output the documentation to ``docs/_build``. Open the index.html in your browser to -view them. It will also (due to -W) error out on any warning like the Github action, Build CI, -does. This is a good way to locally verify it will pass. +* Authors: Damien P. George, Peter Hinch, Rhys Thomas, Brendan Doherty diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index 43e9721..d09b487 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -74,7 +74,7 @@ class FakeBLE: def __init__(self, spi, csn, ce, spi_frequency=10000000): self._radio = RF24(spi, csn, ce, spi_frequency=spi_frequency) self._chan = 0 - self._to_iphone = 0x40 + self._to_iphone = 0x42 self._show_dbm = False self._ble_name = None self._mac = urandom(6) diff --git a/docs/greetings.rst b/docs/greetings.rst new file mode 100644 index 0000000..b53267f --- /dev/null +++ b/docs/greetings.rst @@ -0,0 +1,267 @@ + +.. .. only:: html + +.. image:: https://readthedocs.org/projects/circuitpython-nrf24l01/badge/?version=stable + :target: https://circuitpython-nrf24l01.readthedocs.io/en/stable/ + :alt: Documentation Status + +.. image:: https://github.com/2bndy5/CircuitPython_nRF24L01/workflows/Build%20CI/badge.svg + :target: https://github.com/2bndy5/CircuitPython_nRF24L01/actions?query=workflow%3A%22Build+CI%22 + :alt: Build Status + +.. image:: https://img.shields.io/pypi/v/circuitpython-nrf24l01.svg + :alt: latest version on PyPI + :target: https://pypi.python.org/pypi/circuitpython-nrf24l01 + +.. image:: https://pepy.tech/badge/circuitpython-nrf24l01?label=pypi%20downloads&logo=python + :alt: Total PyPI downloads + :target: https://pepy.tech/project/circuitpython-nrf24l01 + +About this library +================== + +Circuitpython driver library for the nRF24L01 transceiver + +CircuitPython port of the nRF24L01 library from Micropython. +Original work by Damien P. George & Peter Hinch can be found `here +`_ + +The Micropython source has been rewritten to expose all the nRF24L01's features and for +compatibilty with the Raspberry Pi and other Circuitpython compatible devices. Modified by Brendan Doherty, Rhys Thomas + +* Author(s): Damien P. George, Peter Hinch, Rhys Thomas, Brendan Doherty + +Features currently supported +---------------------------- + +* change the addresses' length (can be 3 to 5 bytes long) +* dynamically sized payloads (max 32 bytes each) or statically sized payloads +* automatic responding acknowledgment (ACK) for verifying transmission success +* custom acknowledgment (ACK) payloads for bi-directional communication +* flag a single payload for no acknowledgment (ACK) from the receiving nRF24L01 +* "re-use the same payload" feature (for manually re-transmitting failed transmissions that + remain in the buffer) +* multiple payload transmissions with one function call (MUST read documentation on the + `send()` function) +* context manager compatible for easily switching between different radio configurations + using "with" statements (not available in ``rf24_lite.py`` variant for M0 based boards) +* configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or + failed transmissions (these 3 flags control the 1 IRQ pin). There's also virtual + representations of these interrupt flags available (see + :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr`, + :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_ds`, & `irq_df` attributes) +* invoke sleep mode (AKA power down mode) for ultra-low current consumption +* cyclic redundancy checking (CRC) up to 2 bytes long +* adjust the nRF24L01's builtin automatic re-transmit feature's parameters (`arc`: number + of attempts, `ard`: delay between attempts) +* adjust the nRF24L01's frequency channel (2.4-2.525 GHz) +* adjust the nRF24L01's power amplifier level (0, -6, -12, or -18 dBm) +* adjust the nRF24L01's RF data rate (250Kbps is buggy due to hardware design, but 1Mbps and 2Mbps are reliable) +* a nRF24L01 driven by this library can communicate with a nRF24L01 on an Arduino driven by the `TMRh20 RF24 library `_. See the nrf24l01_2arduino_handling_data.py code in the `examples folder of this library's repository `_ +* fake BLE module for sending BLE beacon advertisments from the nRF24L01 as outlined by `Dmitry Grinberg in his write-up (including C source code) `_. + +Features currently unsupported +------------------------------ + +* as of yet, no [intended] implementation for Multiceiver mode (up to 6 TX nRF24L01 "talking" to 1 RX nRF24L01 simultaneously). Although this might be acheived easily using the "automatic retry delay" (`ard`) and "automatic retry count" (`arc`) attributes set accordingly (varyingly high -- this has not been tested). + +Dependencies +-------------------------- + +This driver depends on: + +* `Adafruit CircuitPython `_ +* `Bus Device `_ + +Please ensure all dependencies are available on the CircuitPython filesystem. +This is easily achieved by downloading +`the Adafruit library and driver bundle `_. + +.. note:: This library supports Python 3.4 or newer, but Python 3.7 introduced + the function `time.monotonic_ns() `_ which returns an arbitrary time "counter" + as an `int` of nanoseconds. However, this function is not used in the + example scripts for backward compatibility reasons. Instead, we used + :py:func:`~time.monotonic()` which returns an arbitrary time "counter" as + a `float` of seconds. CircuitPython firmware supports both functions as of + v4.0. + +Installing from PyPI +-------------------- + +On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from +PyPI `_. To install for current user: + +.. code-block:: shell + + pip3 install circuitpython-nrf24l01 + +To install system-wide (this may be required in some cases): + +.. code-block:: shell + + sudo pip3 install circuitpython-nrf24l01 + +To install in a virtual environment in your current project: + +.. code-block:: shell + + mkdir project-name && cd project-name + python3 -m venv .env + source .env/bin/activate + pip3 install circuitpython-nrf24l01 + +Pinout +====== +.. image:: https://lastminuteengineers.com/wp-content/uploads/2018/07/Pinout-nRF24L01-Wireless-Transceiver-Module.png + :target: https://lastminuteengineers.com/nrf24l01-arduino-wireless-communication/#nrf24l01-transceiver-module-pinout + +The nRF24L01 is controlled through SPI so there are 3 pins (SCK, MOSI, & MISO) that can only be connected to their counterparts on the MCU (microcontroller unit). The other 2 essential pins (CE & CSN) can be connected to any digital output pins. Lastly, the only optional pin on the nRf24L01 GPIOs is the IRQ (interrupt; a digital output that's active when low) pin and is only connected to the MCU via a digital input pin during the interrupt example. The following pinout is used in the example codes of this library's `example directory `_. + +.. csv-table:: + :header: nRF2401, "Raspberry Pi", "ItsyBitsy M4" + + GND, GND, GND + VCC, 3V, 3.3V + CE, GPIO4, D4 + CSN, GPIO5, D5 + SCK, "GPIO11 (SCK)", SCK + MOSI, "GPIO10 (MOSI)", MOSI + MISO, "GPIO9 (MISO)", MISO + IRQ, GPIO12, D12 + +.. tip:: User reports and personal experiences have improved results if there is a capacitor of 100 mirofarads [+ another optional 0.1 microfarads capacitor for added stability] connected in parrallel to the VCC and GND pins. + +Using The Examples +================== + +See `examples `_ for testing certain features of this the library. The examples were developed and tested on both Raspberry Pi and ItsyBitsy M4. Pins have been hard coded in the examples for the corresponding device, so please adjust these accordingly to your circuitpython device if necessary. + +To run the simple example, navigate to this repository's "examples" folder in the terminal. If you're working with a CircuitPython device (not a Raspberry Pi), copy the file named "nrf24l01_simple_test.py" from this repository's "examples" folder to the root directory of your CircuitPython device's CIRCUITPY drive. Now you're ready to open a python REPR and run the following commands: + +.. code-block:: python + + >>> from nrf24l01_simple_test import * + nRF24L01 Simple test. + Run slave() on receiver + Run master() on transmitter + >>> master() + Sending: 5 as struct: b'\x05\x00\x00\x00' + send() successful + Transmission took 36.0 ms + Sending: 4 as struct: b'\x04\x00\x00\x00' + send() successful + Transmission took 28.0 ms + Sending: 3 as struct: b'\x03\x00\x00\x00' + send() successful + Transmission took 24.0 ms + + +Where do I get 1? +================= + +See the store links on the sidebar or just google "nRF24L01+". It is worth noting that you +generally want to buy more than 1 as you need 2 for testing -- 1 to send & 1 to receive and +vise versa. This library has been tested on a cheaply bought 6 pack from Amazon.com, but don't +take Amazon or eBay for granted! There are other wireless transceivers that are NOT compatible +with this library. For instance, the esp8266-01 (also sold in packs) is NOT compatible with +this library, but looks very similar to the nRF24L01+ and could lead to an accidental purchase. + +About the nRF24L01+ +------------------- + +Stablizing the power input to the VCC and GND using parallel capacitors (100 µF + an optional +0.1µF) provides significant performance increases. This stability is very highly recommended! +More finite details about the nRF24L01 are available from the datasheet (referenced here in +the documentation as the `nRF24L01+ Specification Sheet `_) + +About the nRF24L01+PA+LNA modules +--------------------------------- + +You may find variants of the nRF24L01 transceiver that are marketed as "nRF24L01+PA+LNA". +These modules are distinct in the fact that they come with a detachable (SMA-type) antenna. +They employ seperate RFX24C01 IC with the antenna for enhanced Power Amplification (PA) and +Low Noise Amplification (LNA) features. While they boast greater range with the same +functionality, they are subject to a couple lesser known (and lesser advertised) drawbacks: + +1. Stronger power source. Below is a chart of advertised current requirements that many MCU + boards' 3V regulators may not be able to handle. + + .. csv-table:: + :header: Specification, Value + :widths: 10,5 + + "Emission mode current(peak)", "115 mA" + "Receive Mode current(peak)", "45 mA" + "Power-down mode current", "4.2 µA" +2. Needs sheilding from electromagnetic interference. Sheilding works best when it has a path + to ground (GND pin) + +See also the `Testing nRF24L01+PA+LNA module `_ + +nRF24L01(+) clones and counterfeits +----------------------------------- + +This library does not directly support clones/counterfeits as there is no way for the library +to differentiate between an actual nRF24L01+ and a clone/counterfeit. To determine if your +purchase is a counterfeit, please contact the retailer you purxhased from (`reading this +article and its links might help +`_). The most notable clone is the `Si24R1 `_. I could not find +the `Si24R1 datasheet `_ in english. Troubleshooting +the SI24R1 may require `replacing the onboard antennae with a wire +`_. Furthermore, the Si24R1 has different power +amplifier options as noted in the `RF_PWR section (bits 0 through 2) of the RF_SETUP register +(hex address 6) of the datasheet `_. +While the options' values differ from those identified by this library's API, the +underlying commands to configure those options are almost identical to the nRF24L01. Other +known clones include the bk242x (AKA RFM7x). + +Contributing +============ + +Contributions are welcome! Please read our `Code of Conduct +`_ +before contributing to help this project stay welcoming. To contribute, all you need to do is fork `this repository `_, develop your idea(s) and submit a pull request when stable. To initiate a discussion of idea(s), you need only open an issue on the aforementioned repository (doesn't have to be a bug report). + + +Future Project Ideas/Additions +------------------------------ + + The following are only ideas; they are not currently supported by this circuitpython library. + + * `There's a few blog posts by Nerd Ralph demonstrating how to use the nRF24L01 via 2 or 3 + pins `_ (uses custom bitbanging SPI functions and an external circuit involving a + resistor and a capacitor) + * network linking layer, maybe something like `TMRh20's RF24Network + `_ + * implement the Gazelle-based protocol used by the BBC micro-bit (`makecode.com's radio + blocks `_). + + +Sphinx documentation +----------------------- + +Sphinx is used to build the documentation based on rST files and comments in the code. First, +install dependencies (feel free to reuse the virtual environment from `above `_): + +.. code-block:: shell + + python3 -m venv .env + source .env/bin/activate + pip install Sphinx sphinx-rtd-theme + +Now, once you have the virtual environment activated: + +.. code-block:: shell + + cd docs + sphinx-build -E -W -b html . _build + +This will output the documentation to ``docs/_build``. Open the index.html in your browser to +view them. It will also (due to -W) error out on any warning like the Github action, Build CI, +does. This is a good way to locally verify it will pass. diff --git a/docs/index.rst b/docs/index.rst index af47f39..6b9304a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,13 +1,12 @@ -.. include:: ../README.rst Table of Contents ================= .. toctree:: + :caption: Introduction :maxdepth: 4 - :hidden: - self + greetings .. toctree:: :caption: Examples diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index c624303..33b22df 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -120,3 +120,52 @@ version: * Cannot switch between different radio configurations using context manager (the `with` blocks). It is advised that only one `RF24` object be instantiated when RAM is limited (less than or equal to 32KB). + + +Testing nRF24L01+PA+LNA module +================================= + +The following are semi-successful test results using a nRF24L01+PA+LNA module: + +The Setup +********************************* + + I wrapped the PA/LNA module with electrical tape and then foil around that (for shielding) + while being very careful to not let the foil touch any current carrying parts (like the GPIO pins and the soldier joints for the antenna mount). Then I wired up a PA/LNA module with a 3V + regulator (L4931 with a 2.2 µF capacitor between V\ :sub:`out` & GND) using my ItsyBitsy M4 + 5V (USB) pin going directly to the L4931 V\ :sub:`in` pin. The following are experiences from + running simple, ack, & stream examples with a reliable nRF24L01+ (no PA/LNA) on the other end (driven by a Raspberry Pi 2): + +Results (ordered by :py:attr:`~circuitpython_nrf24l01.rf24.RF24.pa_level` settings) +*********************************************************************************** + + * 0 dBm: ``master()`` worked the first time (during simple example) then continuously failed + (during all examples). ``slave()`` worked on simple & stream examples, but the opposing + ``master()`` node reporting that ACK packets (without payloads) were **not** received from + the PA/LNA module; ``slave()`` failed to send ACK packet payloads during the ack example. + * -6 dBm: ``master()`` worked consistently on simple, ack, & stream example. ``slave()`` worked + reliably on simple & stream examples, but failed to transmit **any** ACK packet payloads in + the ack example. + * -12 dBm: ``master()`` worked consistently on simple, ack, & stream example. ``slave()`` + worked reliably on simple & stream examples, but failed to transmit **some** ACK packet + payloads in the ack example. + * -18 dBm: ``master()`` worked consistently on simple, ack, & stream example. ``slave()`` + worked reliably on simple, ack, & stream examples, meaning **all** ACK packet payloads were + successfully transmit in the ack example. + + I should note that without shielding the PA/LNA module and using the L4931 3V regulator, + no TX transmissions got sent (including ACK packets for the + :py:attr:`~circuitpython_nrf24l01.rf24.RF24.auto-ack` feature). + +Conclusion +********************************* + + The PA/LNA modules seem to require quite a bit more power to transmit. The L4931 regulator + that I used in the tests boasts a 300 mA current limit and a typical current of 250 mA. + While the ItsyBitsy M4 boasts a 500 mA max, it would seem that much of that is consumed + internally. Since playing with the :py:attr:`~circuitpython_nrf24l01.rf24.RF24.pa_level` + is a current saving hack (as noted in the datasheet), I can only imagine that a higher power + 3V regulator may enable sending transmissions (including ACK packets -- with or without ACK + payloads attached) from PA/LNA modules using higher + :py:attr:`~circuitpython_nrf24l01.rf24.RF24.pa_level` settings. More testing is called for, + but I don't have an oscilloscope to measure the peak current draws. diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index a82899f..e71ff50 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -32,7 +32,7 @@ # if broadcasting to an Android, set the to_iphone attribute to False # if broadcasting to an iPhone, set the to_iphone attribute to True -# nrf.to_iphone = False +nrf.to_iphone = True # default value is False # you can optionally set the arbitrary MAC address to be used as the # BLE device's MAC address. Otherwise this is randomly generated upon @@ -40,7 +40,8 @@ # nrf.mac = b"\x19\x12\x14\x26\x09\xE0" # set the Power Amplifier level to -12 dBm since this test example is -# usually run with nRF24L01 transceivers in close proximity +# usually run with nRF24L01 transceiver in close proximity to the +# BLE scanning application nrf.pa_level = -12 @@ -63,9 +64,9 @@ def master(count=50): # using the "with" statement is highly recommended if the nRF24L01 is # to be used for more than a BLE configuration with nrf as ble: - nrf.name = b"nRF24L01" + ble.name = b"nRF24L01" # include the radio's pa_level attribute in the payload - nrf.show_pa_level = True + ble.show_pa_level = True print( "available bytes in next payload:", ble.available(chunk(battery_service.buffer)) @@ -78,6 +79,8 @@ def master(count=50): ble.advertise(battery_service.buffer, data_type=0x16) # channel hoping is recommended per BLE specs ble.hop_channel() + # alternate advertisements to target all devices + ble.to_iphone = not ble.to_iphone time.sleep(0.5) # wait till next broadcast # nrf.show_pa_level & nrf.name both are set to false when # exiting a with statement block @@ -104,6 +107,7 @@ def send_temp(count=50): ble.advertise(temperature_service.buffer, data_type=0x16) # channel hoping is recommended per BLE specs ble.hop_channel() + ble.to_iphone = not ble.to_iphone time.sleep(0.2) @@ -113,7 +117,9 @@ def send_temp(count=50): # the data attribute converts a URL string into a simplified # bytes object using byte codes defined by the Eddystone protocol. url_service.data = "http://www.google.com" - +# Eddystone protocol requires a estimated TX PA level at 1 meter +# lower this estimate siince we lowered the actual `ble.pa_level` +url_service.pa_level_at_1_meter = -45 # defaults to -25 dBm def send_url(count=50): """Sends out a chunk of data twice a second.""" @@ -129,12 +135,13 @@ def send_url(count=50): _prompt(count, i) ble.advertise(url_service.buffer, 0x16) ble.hop_channel() + ble.to_iphone = not ble.to_iphone time.sleep(0.2) print( """\ nRF24L01 fake BLE beacon test.\n\ Run master() to broadcast the device name, pa_level, & battery charge\n\ - Run send_temperature() to broadcast the device name & a temperature\n\ + Run send_temp() to broadcast the device name & a temperature\n\ Run send_url() to broadcast a custom URL link""" ) From 8f9d0712700ae6a67d8db8dbb1587822ac92b1da Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sat, 3 Oct 2020 21:35:42 -0700 Subject: [PATCH 120/127] BLE data_rate is fixed; some docs improvements --- README.rst | 15 ++++++----- circuitpython_nrf24l01/fake_ble.py | 16 ----------- docs/_static/darkness.css | 26 ++++++++++++++++-- docs/ble_api.rst | 43 +++++++++++++----------------- docs/greetings.rst | 37 ++++++++++++++----------- 5 files changed, 72 insertions(+), 65 deletions(-) diff --git a/README.rst b/README.rst index a71c491..ab981b9 100644 --- a/README.rst +++ b/README.rst @@ -18,18 +18,21 @@ Read The Docs ============= -Documentation for this library is hosted at `ReadTheDocs.org `_ +Documentation for this library is hosted at +`ReadTheDocs.org `_ About this Library ================== -Circuitpython driver library for the nRF24L01 transceiver +This is a Circuitpython driver library for the nRF24L01 transceiver. -CircuitPython port of the nRF24L01 library from Micropython. -Original work by Damien P. George & Peter Hinch can be found `here +Originally this code was a Micropython module written by Damien P. George +& Peter Hinch which can still be found `here `_ -The Micropython source has been rewritten to expose all the nRF24L01's features and for -compatibilty with the Raspberry Pi and other Circuitpython compatible devices. Modified by Brendan Doherty, Rhys Thomas +The Micropython source has since been rewritten to expose all the nRF24L01's +features and for Circuitpython compatible devices (including linux-based +SoC computers like the Raspberry Pi). +Modified by Brendan Doherty & Rhys Thomas. * Authors: Damien P. George, Peter Hinch, Rhys Thomas, Brendan Doherty diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index d09b487..70f683b 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -238,22 +238,6 @@ def channel(self, value): ) self._radio.channel = value - @property - def data_rate(self): - """See :py:attr:`~circuitpython_nrf24l01.rf24.RF24.data_rate` for - more details. - - .. warning:: 250 kbps is not a valid data rate for BLE operations.""" - return self._radio.data_rate - - @data_rate.setter - def data_rate(self, value): - if value not in (1, 2): - raise ValueError( - "valid data rates for BLE transmissions are 1 or 2 Mbps." - ) - self._radio.data_rate = value - @property def payload_length(self): """This attribute is best left at 32 bytes for all BLE diff --git a/docs/_static/darkness.css b/docs/_static/darkness.css index ea72432..6b6ab50 100644 --- a/docs/_static/darkness.css +++ b/docs/_static/darkness.css @@ -89,7 +89,16 @@ .highlight .go { color: #1afd00; - } +} + +td.linenos pre { + color: #fff; + background-color: #373737; +} + +.highlight .fm { + color: #f7ef84; +} /* ----------------------table sections---------------------------------------- */ .wy-table thead, @@ -182,7 +191,20 @@ body { } .wy-nav-content-wrap, .wy-nav-content { - background:#242424; + background:#242424; +} +.btn-neutral { + background-color:#275325 !important; + color:#fff !important; +} + +.btn-neutral:hover { + background-color:#006060 !important; + color:#fffafa +} + +.btn-neutral:visited { + color:#fff !important; } /* -------------------------------------sidebar sections------------------------------------------------ */ diff --git a/docs/ble_api.rst b/docs/ble_api.rst index cbe2505..3c08282 100644 --- a/docs/ble_api.rst +++ b/docs/ble_api.rst @@ -3,25 +3,25 @@ BLE Limitations --------------- This module uses the `RF24` class to make the nRF24L01 imitate a -Bluetooth-Low-Emissions (BLE) beacon. A BLE beacon can send (referred to as -advertise) data to any BLE compatible device (ie smart devices with Bluetooth +Bluetooth-Low-Emissions (BLE) beacon. A BLE beacon can send data (referred to as +advertisements) to any BLE compatible device (ie smart devices with Bluetooth 4.0 or later) that is listening. Original research was done by `Dmitry Grinberg and his write-up (including C source code) can be found here `_ As this technique can prove invaluable in certain project designs, the code -here is simply ported to work on CircuitPython. +here has been adapted to work with CircuitPython. .. important:: Because the nRF24L01 wasn't designed for BLE advertising, it has some limitations that helps to be aware of. - 1. the maximum payload length is shortened to **18** bytes (when not + 1. The maximum payload length is shortened to **18** bytes (when not broadcasting a device - :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.name`) nor - the nRF24L01 power amplifier level (using + :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.name` nor + the nRF24L01 :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.show_pa_level`). - This is can be calculated as: + This is calculated as: **32** (nRF24L01 maximum) - **6** (MAC address) - **5** (required flags) - **3** (CRC checksum) = **18** @@ -30,20 +30,21 @@ here is simply ported to work on CircuitPython. :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.available()` to detirmine if your payload can be transmit. 2. the channels that BLE use are limited to the following three: 2.402 - GHz, 2.426 GHz, and 2.480 GHz + GHz, 2.426 GHz, and 2.480 GHz. We have provided a tuple of these + specific channels for convenience (See `BLE_FREQ` and `hop_channel()`). 3. :py:attr:`~circuitpython_nrf24l01.rf24.RF24.crc` is disabled in the - nRF24L01 firmware as BLE requires 3 bytes - (:py:func:`~circuitpython_nrf24l01.fake_ble.crc24_ble()`) and nRF24L01 - only handles a maximum of 2. Thus, we have appended the required 3 - bytes of CRC24 into the payload. + nRF24L01 firmware because BLE specifications require 3 bytes + (:py:func:`~circuitpython_nrf24l01.fake_ble.crc24_ble()`), and the + nRF24L01 firmware can only handle a maximum of 2. + Thus, we have appended the required 3 bytes of CRC24 into the payload. 4. :py:attr:`~circuitpython_nrf24l01.rf24.RF24.address_length` of BLE packet only uses 4 bytes, so we have set that accordingly. 5. The :py:attr:`~circuitpython_nrf24l01.rf24.RF24.auto_ack` (automatic acknowledgment) feature of the nRF24L01 is useless when tranmitting to BLE devices, thus it is disabled as well as automatic re-transmit (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.arc`) and custom ACK - payloads (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.ack`) which both - depend on the automatic acknowledgments feature. + payloads (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.ack`) features + which both depend on the automatic acknowledgments feature. 6. The :py:attr:`~circuitpython_nrf24l01.rf24.RF24.dynamic_payloads` feature of the nRF24L01 isn't compatible with BLE specifications. Thus, we have disabled it. @@ -54,12 +55,9 @@ here is simply ported to work on CircuitPython. (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_ds`) & "on data ready" (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr`) events will have an effect on the interrupt (IRQ) pin. The "on data fail" - (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_df`), is never + (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_df`) is never triggered because - :py:attr:`~circuitpython_nrf24l01.rf24.RF24.auto_ack` feature is - disabled. - -.. currentmodule:: circuitpython_nrf24l01.fake_ble + :py:attr:`~circuitpython_nrf24l01.rf24.RF24.arc` attribute is disabled. helpers ---------------- @@ -268,7 +266,7 @@ advertise() advertisements. .. important:: If the name and/or TX power level of the emulated BLE - device is also to be broadcast, then the 'name' and/or + device is also to be broadcast, then the `name` and/or `show_pa_level` attribute(s) should be set prior to calling `advertise()`. @@ -310,11 +308,6 @@ channel .. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.channel -data_rate -#################### - -.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.data_rate - payload_length #################### diff --git a/docs/greetings.rst b/docs/greetings.rst index b53267f..d12d2fa 100644 --- a/docs/greetings.rst +++ b/docs/greetings.rst @@ -17,19 +17,21 @@ :alt: Total PyPI downloads :target: https://pepy.tech/project/circuitpython-nrf24l01 -About this library +Getting Started ================== -Circuitpython driver library for the nRF24L01 transceiver +This is a Circuitpython driver library for the nRF24L01(+) transceiver. -CircuitPython port of the nRF24L01 library from Micropython. -Original work by Damien P. George & Peter Hinch can be found `here +Originally this code was a Micropython module written by Damien P. George +& Peter Hinch which can still be found `here `_ -The Micropython source has been rewritten to expose all the nRF24L01's features and for -compatibilty with the Raspberry Pi and other Circuitpython compatible devices. Modified by Brendan Doherty, Rhys Thomas +The Micropython source has since been rewritten to expose all the nRF24L01's +features and for Circuitpython compatible devices (including linux-based +SoC computers like the Raspberry Pi). +Modified by Brendan Doherty & Rhys Thomas. -* Author(s): Damien P. George, Peter Hinch, Rhys Thomas, Brendan Doherty +* Authors: Damien P. George, Peter Hinch, Rhys Thomas, Brendan Doherty Features currently supported ---------------------------- @@ -157,7 +159,7 @@ To run the simple example, navigate to this repository's "examples" folder in th Transmission took 24.0 ms -Where do I get 1? +What to purchase ================= See the store links on the sidebar or just google "nRF24L01+". It is worth noting that you @@ -167,13 +169,15 @@ take Amazon or eBay for granted! There are other wireless transceivers that are with this library. For instance, the esp8266-01 (also sold in packs) is NOT compatible with this library, but looks very similar to the nRF24L01+ and could lead to an accidental purchase. -About the nRF24L01+ +Power Stability ------------------- -Stablizing the power input to the VCC and GND using parallel capacitors (100 µF + an optional -0.1µF) provides significant performance increases. This stability is very highly recommended! -More finite details about the nRF24L01 are available from the datasheet (referenced here in -the documentation as the `nRF24L01+ Specification Sheet `_) About the nRF24L01+PA+LNA modules @@ -186,7 +190,8 @@ Low Noise Amplification (LNA) features. While they boast greater range with the functionality, they are subject to a couple lesser known (and lesser advertised) drawbacks: 1. Stronger power source. Below is a chart of advertised current requirements that many MCU - boards' 3V regulators may not be able to handle. + boards' 3V regulators may not be able to provide (after supplying power to internal + components). .. csv-table:: :header: Specification, Value @@ -195,8 +200,8 @@ functionality, they are subject to a couple lesser known (and lesser advertised) "Emission mode current(peak)", "115 mA" "Receive Mode current(peak)", "45 mA" "Power-down mode current", "4.2 µA" -2. Needs sheilding from electromagnetic interference. Sheilding works best when it has a path - to ground (GND pin) +2. Needs shielding from electromagnetic interference. Shielding usually works best when + it has a path to ground (GND pin), but this connection to the GND pin is not required. See also the `Testing nRF24L01+PA+LNA module `_ From e5346dd71d1387eb02b3e01edf8ef7ff96097a55 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Sat, 3 Oct 2020 22:32:02 -0700 Subject: [PATCH 121/127] testing examples on RPi --- examples/nrf24l01_context_test.py | 3 ++- examples/nrf24l01_fake_ble_test.py | 2 +- examples/nrf24l01_interrupt_test.py | 10 +++++++--- examples/nrf24l01_stream_test.py | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/examples/nrf24l01_context_test.py b/examples/nrf24l01_context_test.py index 4ae1b69..c555930 100644 --- a/examples/nrf24l01_context_test.py +++ b/examples/nrf24l01_context_test.py @@ -64,4 +64,5 @@ # Be sure to use 1 "with" block per RF24 object when instantiating multiple # RF24 objects in your program. # NOTE exiting a "with" block will always power down the nRF24L01 -# NOTE this library's RF24 class closes all pipes upon instantiation +# NOTE upon instantiation, this library closes all RX pipes & +# extracts the TX/RX addresses from the nRF24L01 registers diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index e71ff50..1269842 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -118,7 +118,7 @@ def send_temp(count=50): # bytes object using byte codes defined by the Eddystone protocol. url_service.data = "http://www.google.com" # Eddystone protocol requires a estimated TX PA level at 1 meter -# lower this estimate siince we lowered the actual `ble.pa_level` +# lower this estimate since we lowered the actual `ble.pa_level` url_service.pa_level_at_1_meter = -45 # defaults to -25 dBm def send_url(count=50): diff --git a/examples/nrf24l01_interrupt_test.py b/examples/nrf24l01_interrupt_test.py index 6837713..2902815 100644 --- a/examples/nrf24l01_interrupt_test.py +++ b/examples/nrf24l01_interrupt_test.py @@ -41,8 +41,8 @@ def master(timeout=5): # will only wait 5 seconds for slave to respond nrf.listen = 0 # on data sent test - print("Pinging: enslaved nRF24L01 without auto_ack") - nrf.write(b"ping") + print("\nPing slave node without auto_ack") + nrf.write(b"ping", ask_no_ack=True) time.sleep(0.00001) # mandatory 10 microsecond pulse starts transmission nrf.ce_pin.value = 0 # end 10 us pulse; now in active TX while not nrf.irq_ds and not nrf.irq_df: @@ -57,6 +57,7 @@ def master(timeout=5): # will only wait 5 seconds for slave to respond nrf.clear_status_flags() # clear all flags for next test # on data ready test + print("\nwaiting for pong from slave node.") nrf.listen = 1 nrf.open_rx_pipe(0, address) start = time.monotonic() @@ -81,7 +82,9 @@ def master(timeout=5): # will only wait 5 seconds for slave to respond nrf.clear_status_flags() # clear all flags for next test # on data fail test + print("\nsending a ping to a non-responsive slave node.") nrf.listen = False # put the nRF24L01 is in TX mode + nrf.open_tx_pipe(b"\xe7" * 5) nrf.flush_tx() # just in case the previous "on data sent" test failed nrf.write(b"dummy") # slave isn't listening anymore time.sleep(0.00001) # mandatory 10 microsecond pulse starts transmission @@ -115,8 +118,9 @@ def slave(timeout=10): # will listen for 10 seconds before timing out nrf.flush_rx() nrf.listen = 0 nrf.open_tx_pipe(address) + time.sleep(1) # send a payload to complete the on data ready test - nrf.send(b"pong", force_retry=1) + nrf.send(b"pong", ask_no_ack=True) # we're done on this side diff --git a/examples/nrf24l01_stream_test.py b/examples/nrf24l01_stream_test.py index 6e6e1a4..0cac369 100644 --- a/examples/nrf24l01_stream_test.py +++ b/examples/nrf24l01_stream_test.py @@ -61,7 +61,7 @@ def master(count=1): # count = 5 will transmit the list 5 times successful += 1 if r else 0 print( "successfully sent {}% ({}/{})".format( - successful / size * 100 * count, successful, size * count + successful / (size* count) * 100, successful, size * count ) ) From 4508fc6a3ea639df92d08e006216fec4c81cb7e0 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Mon, 5 Oct 2020 00:53:44 -0700 Subject: [PATCH 122/127] IRQ test uses ACK payloads; rf24_lite has fifo() --- circuitpython_nrf24l01/rf24.py | 20 +-- circuitpython_nrf24l01/rf24_lite.py | 8 +- docs/troubleshooting.rst | 1 - examples/nrf24l01_2arduino_handling_data.py | 42 +++-- examples/nrf24l01_interrupt_test.py | 173 ++++++++++---------- 5 files changed, 122 insertions(+), 122 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 4ee6800..62a9459 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -246,7 +246,7 @@ def listen(self, is_rx): self._reg_write(CONFIGURE, self._config) time.sleep(0.00015) # mandatory wait to power up radio self.flush_rx() - self.clear_status_flags(True, False, False) + self.clear_status_flags() self.ce_pin.value = 1 # mandatory pulse is > 130 µs time.sleep(0.00013) else: @@ -735,7 +735,7 @@ def write(self, buf, ask_no_ack=False): if self._features & 1 == 0: self._features = self._features & 0xFE | 1 self._reg_write(TX_FEATURE, self._features) - self._reg_write_bytes(0xA0 | (ask_no_ack << 4), buf) + self._reg_write_bytes(0xA0 | (bool(ask_no_ack) << 4), buf) self.ce_pin.value = 1 def flush_rx(self): @@ -749,18 +749,10 @@ def flush_tx(self): def fifo(self, about_tx=False, check_empty=None): """This provides some precision determining the status of the TX/RX FIFO buffers. (read-only)""" - if isinstance(about_tx, (bool, int)): - if check_empty is None or isinstance(check_empty, (bool, int)): - self._fifo = self._reg_read(0x17) - mask = 4 * about_tx - if check_empty is None: - return (self._fifo & (0x30 if about_tx else 0x03)) >> mask - return bool(self._fifo & ((2 - check_empty) << mask)) - raise ValueError( - "Argument 1 ('about_tx') must always be a bool or " - "int. Argument 2 ('check_empty'), if specified, must" - " be a bool or int" - ) + self._fifo, about_tx = (self._reg_read(0x17), bool(about_tx)) + if check_empty is None: + return (self._fifo & (0x30 if about_tx else 0x03)) >> (4 * about_tx) + return bool(self._fifo & ((2 - bool(check_empty)) << (4 * about_tx))) def address(self, index=-1): """Returns the current address set to a specified data pipe or the TX diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index dfa7763..7069b71 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -118,7 +118,7 @@ def listen(self, is_rx): self._reg_write(0, (self._reg_read(0) & 0xFC) | 3) time.sleep(0.00015) self.flush_rx() - self.clear_status_flags(True, False, False) + self.clear_status_flags() self.ce_pin.value = 1 time.sleep(0.00013) else: @@ -344,3 +344,9 @@ def flush_rx(self): def flush_tx(self): self._reg_write(0xE1) + + def fifo(self, about_tx=False, check_empty=None): + _fifo, about_tx = (self._reg_read(0x17), bool(about_tx)) + if check_empty is None: + return (_fifo & (0x30 if about_tx else 0x03)) >> (4 * about_tx) + return bool(_fifo & ((2 - bool(check_empty)) << (4 * about_tx))) diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index 33b22df..e965e42 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -96,7 +96,6 @@ version: print(hex(i), "=", nrf._reg_read_bytes(i)) elif i not in (0x18, 0x19, 0x1a, 0x1b): print(hex(i), "=", hex(nrf._reg_read(i))) - * `fifo()` removed. * `dynamic_payloads` applies to all pipes, not individual pipes. * :py:attr:`~circuitpython_nrf24l01.rf24.RF24.payload_length` applies to all pipes, not individual pipes. diff --git a/examples/nrf24l01_2arduino_handling_data.py b/examples/nrf24l01_2arduino_handling_data.py index 40131a7..8d64422 100644 --- a/examples/nrf24l01_2arduino_handling_data.py +++ b/examples/nrf24l01_2arduino_handling_data.py @@ -32,39 +32,46 @@ nrf.pa_level = -12 # set address of TX node into a RX pipe -nrf.open_rx_pipe(1, address[0]) +nrf.open_rx_pipe(1, address[1]) # set address of RX node into a TX pipe -nrf.open_tx_pipe(address[1]) +nrf.open_tx_pipe(address[0]) +# pylint: disable=too-few-public-methods +class DataStruct: + """A data structure to hold transmitted values as the + 'HandlingData' part of the TMRh20 library example""" + time = 0 # in milliseconds (used as start of timer) + value = 1.22 # incremented by 0.01 with every transmission +# pylint: enable=too-few-public-methods + +myData = DataStruct() -def master(count=5): # count = 5 will only transmit 5 packets - """Transmits an arbitrary unsigned long value every second. This method - will only try to transmit (count) number of attempts""" - # for the "HandlingData" part of the test from the TMRh20 library example - float_value = 0.01 +def master(count=5): # count = 5 will only transmit 5 packets + """Transmits an arbitrary unsigned long value every second""" while count: nrf.listen = False # ensures the nRF24L01 is in TX mode print("Now Sending") - start_timer = int(time.monotonic() * 1000) # start timer + myData.time = int(time.monotonic() * 1000) # start timer # use struct.pack to packetize your data into a usable payload # '<' means little endian byte order. # 'L' means a single 4 byte unsigned long value. # 'f' means a single 4 byte float value. - buffer = struct.pack(" Date: Mon, 5 Oct 2020 01:50:36 -0700 Subject: [PATCH 123/127] more proofreading --- docs/examples.rst | 28 ++++++------- docs/greetings.rst | 68 ++++++++++++------------------- examples/nrf24l01_context_test.py | 4 +- 3 files changed, 40 insertions(+), 60 deletions(-) diff --git a/docs/examples.rst b/docs/examples.rst index fd325e1..88df83f 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -17,15 +17,6 @@ This is a test to show how to use custom acknowledgment payloads. :caption: examples/nrf24l01_ack_payload_test.py :linenos: -IRQ Pin Example ---------------- - -This is a test to show how to use nRF24L01's interrupt pin. - -.. literalinclude:: ../examples/nrf24l01_interrupt_test.py - :caption: examples/nrf24l01_interrupt_test.py - :linenos: - Stream Example --------------- @@ -35,6 +26,15 @@ This is a test to show how to use the send() to transmit multiple payloads. :caption: examples/nrf24l01_stream_test.py :linenos: +Fake BLE Example +---------------- + +This is a test to show how to use the nRF24L01 as a BLE advertising beacon. + +.. literalinclude:: ../examples/nrf24l01_fake_ble_test.py + :caption: examples/nrf24l01_fake_ble_test.py + :linenos: + Context Example --------------- @@ -53,11 +53,11 @@ This test is meant to prove compatibility with the popular Arduino library for t :caption: examples/nrf24l01_2arduino_handling_data.py :linenos: -Fake BLE Example ----------------- +IRQ Pin Example +--------------- -This is a test to show how to use the nRF24L01 as a BLE advertising beacon. +This is a test to show how to use nRF24L01's interrupt pin. Be aware that `send()` clears all IRQ events on exit, so we use the non-blocking `write()` instead. Also the `ack` attribute is enabled to trigger the :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr` event when the master node receives ACK payloads. Simply put, this example is the most advanced example script, and it runs VERY quickly. -.. literalinclude:: ../examples/nrf24l01_fake_ble_test.py - :caption: examples/nrf24l01_fake_ble_test.py +.. literalinclude:: ../examples/nrf24l01_interrupt_test.py + :caption: examples/nrf24l01_interrupt_test.py :linenos: diff --git a/docs/greetings.rst b/docs/greetings.rst index d12d2fa..09b9308 100644 --- a/docs/greetings.rst +++ b/docs/greetings.rst @@ -1,22 +1,4 @@ -.. .. only:: html - -.. image:: https://readthedocs.org/projects/circuitpython-nrf24l01/badge/?version=stable - :target: https://circuitpython-nrf24l01.readthedocs.io/en/stable/ - :alt: Documentation Status - -.. image:: https://github.com/2bndy5/CircuitPython_nRF24L01/workflows/Build%20CI/badge.svg - :target: https://github.com/2bndy5/CircuitPython_nRF24L01/actions?query=workflow%3A%22Build+CI%22 - :alt: Build Status - -.. image:: https://img.shields.io/pypi/v/circuitpython-nrf24l01.svg - :alt: latest version on PyPI - :target: https://pypi.python.org/pypi/circuitpython-nrf24l01 - -.. image:: https://pepy.tech/badge/circuitpython-nrf24l01?label=pypi%20downloads&logo=python - :alt: Total PyPI downloads - :target: https://pepy.tech/project/circuitpython-nrf24l01 - Getting Started ================== @@ -36,36 +18,36 @@ Modified by Brendan Doherty & Rhys Thomas. Features currently supported ---------------------------- -* change the addresses' length (can be 3 to 5 bytes long) -* dynamically sized payloads (max 32 bytes each) or statically sized payloads -* automatic responding acknowledgment (ACK) for verifying transmission success -* custom acknowledgment (ACK) payloads for bi-directional communication -* flag a single payload for no acknowledgment (ACK) from the receiving nRF24L01 -* "re-use the same payload" feature (for manually re-transmitting failed transmissions that - remain in the buffer) -* multiple payload transmissions with one function call (MUST read documentation on the +* Change the address's length (can be 3 to 5 bytes long) +* Dynamically sized payloads (max 32 bytes each) or statically sized payloads +* Automatic responding acknowledgment (ACK) packets for verifying transmission success +* Append custom payloadsto the acknowledgment (ACK) packets for instant bi-directional communication +* Mark a single payload for no acknowledgment (ACK) from the receiving nRF24L01 (see ``ask_no_ack`` parameter for `send()` and `write()` functions) +* Invoke the "re-use the same payload" feature (for manually re-transmitting failed transmissions that + remain in the TX FIFO buffer) +* Multiple payload transmissions with one function call (MUST read documentation on the `send()` function) -* context manager compatible for easily switching between different radio configurations - using "with" statements (not available in ``rf24_lite.py`` variant for M0 based boards) -* configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or - failed transmissions (these 3 flags control the 1 IRQ pin). There's also virtual - representations of these interrupt flags available (see +* Context manager compatible for easily switching between different radio configurations + using `with` blocks (not available in ``rf24_lite.py`` version) +* Configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or + failed transmissions (these 3 events control 1 IRQ pin). There's also virtual + representations of these interrupt events available (see :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr`, :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_ds`, & `irq_df` attributes) -* invoke sleep mode (AKA power down mode) for ultra-low current consumption +* Invoke sleep mode (AKA power down mode) for ultra-low current consumption * cyclic redundancy checking (CRC) up to 2 bytes long -* adjust the nRF24L01's builtin automatic re-transmit feature's parameters (`arc`: number +* Adjust the nRF24L01's builtin automatic re-transmit feature's parameters (`arc`: number of attempts, `ard`: delay between attempts) -* adjust the nRF24L01's frequency channel (2.4-2.525 GHz) -* adjust the nRF24L01's power amplifier level (0, -6, -12, or -18 dBm) -* adjust the nRF24L01's RF data rate (250Kbps is buggy due to hardware design, but 1Mbps and 2Mbps are reliable) -* a nRF24L01 driven by this library can communicate with a nRF24L01 on an Arduino driven by the `TMRh20 RF24 library `_. See the nrf24l01_2arduino_handling_data.py code in the `examples folder of this library's repository `_ +* Adjust the nRF24L01's frequency channel (2.4-2.525 GHz) +* Adjust the nRF24L01's power amplifier level (0, -6, -12, or -18 dBm) +* Adjust the nRF24L01's RF data rate (250kbps, 1Mbps, or 2Mbps) +* An nRF24L01 driven by this library can communicate with a nRF24L01 on an Arduino driven by the `TMRh20 RF24 library `_. See the `nrf24l01_2arduino_handling_data.py `_ example. * fake BLE module for sending BLE beacon advertisments from the nRF24L01 as outlined by `Dmitry Grinberg in his write-up (including C source code) `_. Features currently unsupported ------------------------------ -* as of yet, no [intended] implementation for Multiceiver mode (up to 6 TX nRF24L01 "talking" to 1 RX nRF24L01 simultaneously). Although this might be acheived easily using the "automatic retry delay" (`ard`) and "automatic retry count" (`arc`) attributes set accordingly (varyingly high -- this has not been tested). +* as of yet, no [intended] implementation for Multiceiver mode (up to 6 TX nRF24L01 "talking" to 1 RX nRF24L01 simultaneously). Although this might be acheived easily using the "automatic retry delay" (`ard`) and "automatic retry count" (`arc`) attributes set accordingly (varyingly high), but this has not been tested. Dependencies -------------------------- @@ -73,7 +55,7 @@ Dependencies This driver depends on: * `Adafruit CircuitPython `_ -* `Bus Device `_ +* `Bus Device `_ (specifically the :py:mod:`~adafruit_bus_device.spi_device`) Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading @@ -210,20 +192,20 @@ nRF24L01(+) clones and counterfeits This library does not directly support clones/counterfeits as there is no way for the library to differentiate between an actual nRF24L01+ and a clone/counterfeit. To determine if your -purchase is a counterfeit, please contact the retailer you purxhased from (`reading this +purchase is a counterfeit, please contact the retailer you purchased from (also `reading this article and its links might help `_). The most notable clone is the `Si24R1 `_. I could not find the `Si24R1 datasheet `_ in english. Troubleshooting -the SI24R1 may require `replacing the onboard antennae with a wire +the SI24R1 may require `replacing the onboard antenna with a wire `_. Furthermore, the Si24R1 has different power amplifier options as noted in the `RF_PWR section (bits 0 through 2) of the RF_SETUP register -(hex address 6) of the datasheet `_. While the options' values differ from those identified by this library's API, the underlying commands to configure those options are almost identical to the nRF24L01. Other -known clones include the bk242x (AKA RFM7x). +known clones include the bk242x (also known as RFM7x). Contributing ============ diff --git a/examples/nrf24l01_context_test.py b/examples/nrf24l01_context_test.py index c555930..302c133 100644 --- a/examples/nrf24l01_context_test.py +++ b/examples/nrf24l01_context_test.py @@ -33,10 +33,8 @@ # the IRQ pin is configured to only go active on "data fail" # NOTE BLE operations prevent the IRQ pin going active on "data fail" events ble.interrupt_config(data_recv=False, data_sent=False) -# using a different channel: 2 (default is 76) +# using a channel 2 ble.channel = 2 -# data rate is set to 2 Mbps -ble.data_rate = 2 # RF power amplifier is set to -12 dbm ble.pa_level = -12 From 4d927cc133e3773cadde5a5e8f5776e21b403f38 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Mon, 5 Oct 2020 03:11:48 -0700 Subject: [PATCH 124/127] don't flush RX FIFO if send_only==True --- circuitpython_nrf24l01/rf24.py | 4 ++-- circuitpython_nrf24l01/rf24_lite.py | 4 ++-- examples/nrf24l01_fake_ble_test.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 62a9459..d36721e 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -283,7 +283,7 @@ def send(self, buf, ask_no_ack=False, force_retry=0, send_only=False): result.append(self.send(b, ask_no_ack, force_retry)) return result self.flush_tx() - if self.pipe is not None: + if not send_only: self.flush_rx() self.write(buf, ask_no_ack) time.sleep(0.00001) @@ -699,7 +699,7 @@ def resend(self, send_only=False): top level (first out) of the TX FIFO buffer.""" result = False if not self.fifo(True, True): - if self.pipe is not None: + if not send_only: self.flush_rx() self.clear_status_flags() self._reg_write(0xE3) diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index 7069b71..90085f9 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -148,7 +148,7 @@ def send(self, buf, ask_no_ack=False, force_retry=0, send_only=False): result.append(self.send(b, ask_no_ack, force_retry)) return result self.flush_tx() - if self.pipe is not None: + if not send_only: self.flush_rx() self.write(buf, ask_no_ack) time.sleep(0.00001) @@ -306,7 +306,7 @@ def update(self): def resend(self, send_only=False): result = False if not self._reg_read(0x17) & 0x10: - if self.pipe is not None: + if not send_only: self.flush_rx() self.clear_status_flags() self._reg_write(0xE3) diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index 1269842..d09757d 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -117,12 +117,12 @@ def send_temp(count=50): # the data attribute converts a URL string into a simplified # bytes object using byte codes defined by the Eddystone protocol. url_service.data = "http://www.google.com" -# Eddystone protocol requires a estimated TX PA level at 1 meter +# Eddystone protocol requires an estimated TX PA level at 1 meter # lower this estimate since we lowered the actual `ble.pa_level` url_service.pa_level_at_1_meter = -45 # defaults to -25 dBm def send_url(count=50): - """Sends out a chunk of data twice a second.""" + """Sends out a URL twice a second.""" with nrf as ble: print( "available bytes in next payload:", From eaf8740a4c991614feb3866003d6a719a9fa1cae Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Mon, 5 Oct 2020 03:26:22 -0700 Subject: [PATCH 125/127] forgot intro prompt in irq test --- examples/nrf24l01_interrupt_test.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/examples/nrf24l01_interrupt_test.py b/examples/nrf24l01_interrupt_test.py index 749a0f2..b59121a 100644 --- a/examples/nrf24l01_interrupt_test.py +++ b/examples/nrf24l01_interrupt_test.py @@ -127,3 +127,12 @@ def slave(timeout=6): # will listen for 6 seconds before timing out # so, fetching 15 bytes from the RX FIFO also flushes RX FIFO print("Complete RX FIFO:", nrf.recv(15)) nrf.flush_tx() # discard any pending ACK payloads + + +print( + """\ + nRF24L01 Interrupt pin test.\n\ + Make sure the IRQ pin is connected to the MCU\n\ + Run slave() on receiver\n\ + Run master() on transmitter""" +) From 7e23ad4b454766843a17c61641590044ca7c6785 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Mon, 5 Oct 2020 04:38:12 -0700 Subject: [PATCH 126/127] use send_only in recursion & during force_retries --- circuitpython_nrf24l01/rf24.py | 18 +++++++----------- circuitpython_nrf24l01/rf24_lite.py | 10 +++++----- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index d36721e..f293a0e 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -280,7 +280,7 @@ def send(self, buf, ask_no_ack=False, force_retry=0, send_only=False): if isinstance(buf, (list, tuple)): result = [] for b in buf: - result.append(self.send(b, ask_no_ack, force_retry)) + result.append(self.send(b, ask_no_ack, force_retry, send_only)) return result self.flush_tx() if not send_only: @@ -288,12 +288,12 @@ def send(self, buf, ask_no_ack=False, force_retry=0, send_only=False): self.write(buf, ask_no_ack) time.sleep(0.00001) self.ce_pin.value = 0 - while not self._status & 0x30: + while not self._status & 0x70: self.update() result = self.irq_ds if self.irq_df: for _ in range(force_retry): - result = self.resend() + result = self.resend(send_only) if result is None or result: break if self._status & 0x60 == 0x60 and not send_only: @@ -349,9 +349,7 @@ def what_happened(self, dump_pipes=False): """This debuggung function aggregates and outputs all status/condition related information from the nRF24L01.""" observer = self._reg_read(8) - print( - "Is a plus variant_________{}".format(repr(self.is_plus_variant)) - ) + print("Is a plus variant_________{}".format(repr(self.is_plus_variant))) print( "Channel___________________{} ~ {} GHz".format( self.channel, (self.channel + 2400) / 1000 @@ -610,9 +608,7 @@ def data_rate(self): @data_rate.setter def data_rate(self, speed): if not speed in (1, 2, 250): - raise ValueError( - "data_rate must be 1 (Mbps), 2 (Mbps), or 250 (kbps)" - ) + raise ValueError("data_rate must be 1 (Mbps), 2 (Mbps), or 250 (kbps)") if self.is_plus_variant and speed == 250: raise NotImplementedError( "250 kbps data rate is not available for the non-plus " @@ -699,15 +695,15 @@ def resend(self, send_only=False): top level (first out) of the TX FIFO buffer.""" result = False if not self.fifo(True, True): + self.ce_pin.value = 0 if not send_only: self.flush_rx() self.clear_status_flags() self._reg_write(0xE3) - self.ce_pin.value = 0 self.ce_pin.value = 1 time.sleep(0.00001) self.ce_pin.value = 0 - while not self._status & 0x30: + while not self._status & 0x70: self.update() result = self.irq_ds if self._status & 0x60 == 0x60 and not send_only: diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index 90085f9..fe91369 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -145,7 +145,7 @@ def send(self, buf, ask_no_ack=False, force_retry=0, send_only=False): if isinstance(buf, (list, tuple)): result = [] for b in buf: - result.append(self.send(b, ask_no_ack, force_retry)) + result.append(self.send(b, ask_no_ack, force_retry, send_only)) return result self.flush_tx() if not send_only: @@ -153,12 +153,12 @@ def send(self, buf, ask_no_ack=False, force_retry=0, send_only=False): self.write(buf, ask_no_ack) time.sleep(0.00001) self.ce_pin.value = 0 - while not self._status & 0x30: + while not self._status & 0x70: self.update() result = self.irq_ds if self.irq_df: for _ in range(force_retry): - result = self.resend() + result = self.resend(send_only) if result is None or result: break if self._status & 0x60 == 0x60 and not send_only: @@ -305,7 +305,7 @@ def update(self): def resend(self, send_only=False): result = False - if not self._reg_read(0x17) & 0x10: + if not self.fifo(True, True): if not send_only: self.flush_rx() self.clear_status_flags() @@ -314,7 +314,7 @@ def resend(self, send_only=False): self.ce_pin.value = 1 time.sleep(0.00001) self.ce_pin.value = 0 - while not self._status & 0x30: + while not self._status & 0x70: self.update() result = self.irq_ds if self._status & 0x60 == 0x60 and not send_only: From 568d39e0bd83ffe7f8c49fa758bf5ebea4e06e38 Mon Sep 17 00:00:00 2001 From: brendan <2bndy5@gmail.com> Date: Mon, 5 Oct 2020 17:06:46 -0700 Subject: [PATCH 127/127] write() has write_only param for ignoring CE pin --- circuitpython_nrf24l01/fake_ble.py | 4 +-- circuitpython_nrf24l01/rf24.py | 5 +-- circuitpython_nrf24l01/rf24_lite.py | 5 +-- docs/advanced_api.rst | 35 ++++++++++++--------- docs/examples.rst | 2 +- examples/nrf24l01_interrupt_test.py | 48 ++++++++++++++++------------- 6 files changed, 56 insertions(+), 43 deletions(-) diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index 70f683b..e7c7e82 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -178,8 +178,8 @@ def _make_payload(self, payload): """Assemble the entire packet to be transmitted as a payload.""" if self.available(payload) < 0: raise ValueError( - "Payload exceeds maximum size. Configuration allows " - "{} bytes".format(self.available()) + "Payload length exceeds maximum buffer size by " + "{} bytes".format(abs(self.available(payload))) ) name_length = (len(self.name) + 2) if self.name is not None else 0 pl_size = 9 + len(payload) + name_length + self._show_dbm * 3 diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index f293a0e..12f48f6 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -711,7 +711,7 @@ def resend(self, send_only=False): self.clear_status_flags(False) return result - def write(self, buf, ask_no_ack=False): + def write(self, buf, ask_no_ack=False, write_only=False): """This non-blocking function (when used as alternative to `send()`) is meant for asynchronous applications and can only handle one payload at a time as it is a helper function to `send()`.""" @@ -732,7 +732,8 @@ def write(self, buf, ask_no_ack=False): self._features = self._features & 0xFE | 1 self._reg_write(TX_FEATURE, self._features) self._reg_write_bytes(0xA0 | (bool(ask_no_ack) << 4), buf) - self.ce_pin.value = 1 + if not write_only: + self.ce_pin.value = 1 def flush_rx(self): """A helper function to flush the nRF24L01's RX FIFO buffer.""" diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py index fe91369..582ab3e 100644 --- a/circuitpython_nrf24l01/rf24_lite.py +++ b/circuitpython_nrf24l01/rf24_lite.py @@ -322,7 +322,7 @@ def resend(self, send_only=False): self.clear_status_flags(False) return result - def write(self, buf, ask_no_ack=False): + def write(self, buf, ask_no_ack=False, write_only=False): if not buf or len(buf) > 32: raise ValueError("buffer length must be in range [1, 32]") self.clear_status_flags() @@ -337,7 +337,8 @@ def write(self, buf, ask_no_ack=False): elif len(buf) > pl_width: buf = buf[:pl_width] self._reg_write_bytes(0xA0 | (ask_no_ack << 4), buf) - self.ce_pin.value = 1 + if not write_only: + self.ce_pin.value = 1 def flush_rx(self): self._reg_write(0xE2) diff --git a/docs/advanced_api.rst b/docs/advanced_api.rst index 4ca4734..e74524d 100644 --- a/docs/advanced_api.rst +++ b/docs/advanced_api.rst @@ -284,6 +284,10 @@ write() .. automethod:: circuitpython_nrf24l01.rf24.RF24.write + This function isn't completely non-blocking as we still need to wait 5 ms (`CSN_DELAY`) + for the CSN pin to settle (allowing an accurate SPI write transaction). Example usage of + this function can be seen in the `IRQ pin example `_ + :param bytearray buf: The payload to transmit. This bytearray must have a length greater than 0 and less than 32 bytes, otherwise a `ValueError` exception is thrown. @@ -308,16 +312,19 @@ write() Specifications Sheet `_ for more details. - - This function isn't completely non-blocking as we still need to wait just under 5 ms for - the CSN pin to settle (allowing a clean SPI transaction). - - .. note:: The nRF24L01 doesn't initiate sending until a mandatory minimum 10 µs pulse on - the CE pin is acheived. That pulse is initiated before this function exits. However, we - have left that 10 µs wait time to be managed by the MCU in cases of asychronous - application, or it is managed by using `send()` instead of this function. According to - the Specification sheet, if the CE pin remains HIGH for longer than 10 µs, then the - nRF24L01 will continue to transmit all payloads found in the TX FIFO buffer. + :param bool write_only: This function will not manipulate the nRF24L01's CE pin if this + parameter is `True`. The default value of `False` will ensure that the CE pin is + HIGH upon exiting this function. This function does not set the CE pin LOW at + any time. Use this parameter as `True` to fill the TX FIFO buffer before beginning + transmissions. + + .. note:: The nRF24L01 doesn't initiate sending until a mandatory minimum 10 µs pulse + on the CE pin is acheived. If the ``write_only`` parameter is `False`, then that + pulse is initiated before this function exits. However, we have left that 10 µs + wait time to be managed by the MCU in cases of asychronous application, or it is + managed by using `send()` instead of this function. According to the + Specification sheet, if the CE pin remains HIGH for longer than 10 µs, then the + nRF24L01 will continue to transmit all payloads found in the TX FIFO buffer. .. warning:: A note paraphrased from the `nRF24L01+ Specifications Sheet @@ -328,11 +335,11 @@ write() If the [`arc` attribute is] enabled, nRF24L01+ is never in TX mode longer than 4 ms. - .. tip:: Use this function at your own risk. Because of the underlying `"Enhanced - ShockBurst Protocol" `_, disobeying the 4 - ms rule is easily avoided if you enable the `auto_ack` attribute. Alternatively, you - MUST use interrupt flags or IRQ pin with user defined timer(s) to AVOID breaking the + ms rule is easily avoided if the `arc` attribute is greater than ``0``. Alternatively, + you MUST use nRF24L01's IRQ pin and/or user-defined timer(s) to AVOID breaking the 4 ms rule. If the `nRF24L01+ Specifications Sheet explicitly states this `_, we have to assume diff --git a/docs/examples.rst b/docs/examples.rst index 88df83f..1475c19 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -56,7 +56,7 @@ This test is meant to prove compatibility with the popular Arduino library for t IRQ Pin Example --------------- -This is a test to show how to use nRF24L01's interrupt pin. Be aware that `send()` clears all IRQ events on exit, so we use the non-blocking `write()` instead. Also the `ack` attribute is enabled to trigger the :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr` event when the master node receives ACK payloads. Simply put, this example is the most advanced example script, and it runs VERY quickly. +This is a test to show how to use nRF24L01's interrupt pin. Be aware that `send()` clears all IRQ events on exit, so we use the non-blocking `write()` instead. Also the `ack` attribute is enabled to trigger the :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr` event when the master node receives ACK payloads. Simply put, this example is the most advanced example script (in this library), and it runs VERY quickly. .. literalinclude:: ../examples/nrf24l01_interrupt_test.py :caption: examples/nrf24l01_interrupt_test.py diff --git a/examples/nrf24l01_interrupt_test.py b/examples/nrf24l01_interrupt_test.py index b59121a..d3136e7 100644 --- a/examples/nrf24l01_interrupt_test.py +++ b/examples/nrf24l01_interrupt_test.py @@ -38,14 +38,15 @@ nrf.pa_level = -12 -def _ping_and_prompt(buf): - """transmit dummy payload, wait till irq_pin goes active, print IRQ status +def _ping_and_prompt(): + """transmit 1 payload, wait till irq_pin goes active, print IRQ status flags.""" - nrf.write(buf) # write payload to TX FIFO + ce.value = 1 # tell the nRF24L01 to prepare sending a single packet time.sleep(0.00001) # mandatory 10 microsecond pulse starts transmission - ce.value = 0 # end 10 us pulse; now in active TX + ce.value = 0 # end 10 us pulse; use only 1 buffer from TX FIFO while irq_pin.value: # IRQ pin is active when LOW pass + print("IRQ pin went active LOW.") nrf.update() # update irq_d? status flags print( "\tirq_ds: {}, irq_dr: {}, irq_df: {}".format( @@ -61,49 +62,52 @@ def master(): # ensures the nRF24L01 is in TX mode nrf.listen = False # NOTE nrf.power is automatically set to True on first call to nrf.write() + # NOTE nrf.write() internally calls nrf.clear_status_flags() first + + # load 2 buffers into the TX FIFO; write_only=True leaves CE pin LOW + nrf.write(b"Ping ", write_only=True) + nrf.write(b"Pong ", write_only=True) # on data ready test print("\nConfiguring IRQ pin to only ignore 'on data sent' event") nrf.interrupt_config(data_sent=False) - print(" Pinging slave node for an ACK payload.") - _ping_and_prompt(b"Ping ") + print(" Pinging slave node for an ACK payload...", end=" ") + _ping_and_prompt() # CE pin is managed by this function if nrf.irq_dr: print("\t'on data ready' event test successful") else: print("\t'on data ready' event test unsucessful") - nrf.clear_status_flags() # clear all irq_d? flags for next test # on data sent test print("\nConfiguring IRQ pin to only ignore 'on data ready' event") nrf.interrupt_config(data_recv=False) - print(" Pinging slave node again.") - _ping_and_prompt(b"Pong ") + print(" Pinging slave node again... ", end=" ") + _ping_and_prompt() # CE pin is managed by this function if nrf.irq_ds: print("\t'on data sent' event test successful") else: print("\t'on data sent' event test unsucessful") - nrf.clear_status_flags() # clear all irq_d? flags for next test + # trigger slave node to exit by filling the slave node's RX FIFO print("\nSending one extra payload to fill RX FIFO on slave node.") - nrf.write(b"Radio") # write payload to TX FIFO - time.sleep(0.00001) # mandatory 10 microsecond pulse starts transmission - ce.value = 0 # end 10 us pulse; now in active TX - nrf.clear_status_flags() # clear all irq_d? flags for next test - print("Slave node should not be listening anymore. Ready to continue.") + if nrf.send(b"Radio", send_only=True): + # when send_only parameter is True, send() ignores RX FIFO usage + print("Slave node should not be listening anymore.") + else: + print("Slave node was unresponsive.") # on data fail test print("\nConfiguring IRQ pin to go active for all events.") nrf.interrupt_config() - print(" Sending a ping to inactive slave node.") - nrf.flush_tx() # just in case the previous "on data sent" test failed - _ping_and_prompt(b"Dummy") + print(" Sending a ping to inactive slave node...", end=" ") + nrf.flush_tx() # just in case any previous tests failed + nrf.write(b"Dummy", write_only=True) # CE pin is left LOW + _ping_and_prompt() # CE pin is managed by this function if nrf.irq_df: print("\t'on data failed' event test successful") else: print("\t'on data failed' event test unsucessful") - nrf.clear_status_flags() # clear all irq_d? flags for next test - # flush TX FIFO from any failed tests - nrf.flush_tx() + nrf.flush_tx() # flush artifact payload in TX FIFO from last test # all 3 ACK payloads received were 4 bytes each, and RX FIFO is full # so, fetching 12 bytes from the RX FIFO also flushes RX FIFO print("\nComplete RX FIFO:", nrf.recv(12)) @@ -119,7 +123,7 @@ def slave(timeout=6): # will listen for 6 seconds before timing out nrf.listen = True # start listening & clear irq_dr flag start_timer = time.monotonic() # start timer now while not nrf.fifo(0, 0) and time.monotonic() - start_timer < timeout: - # if RX FIFO is not full and timeout is not reached; keep going + # if RX FIFO is not full and timeout is not reached, then keep going pass nrf.listen = False # put nRF24L01 in Standby-I mode when idling if not nrf.fifo(False, True): # if RX FIFO is not empty