From 9ce1c688e9086123b802a4a5eb72185b47ae056d Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Thu, 16 Jun 2022 01:24:05 -0700 Subject: [PATCH] No more upy (#41) * no more f-strings * remove upy wrappers * pre-allocate SPI buffers in rf24.py; fix spelling * update setup.py for macos installs * only require spidev for Linux arm* devices * update network example * Update topology.rst * add as_bin param to save/load_dhcp() * update docs for theme changes * complete type hinting * ble example flush RX FIFO on exit slave() --- .github/workflows/build.yml | 4 +- .github/workflows/release.yml | 13 +- .pylintrc | 42 +--- circuitpython_nrf24l01/__init__.py | 5 + circuitpython_nrf24l01/fake_ble.py | 79 +++--- circuitpython_nrf24l01/network/constants.py | 2 +- circuitpython_nrf24l01/network/mixins.py | 73 ++++-- circuitpython_nrf24l01/network/structs.py | 32 ++- circuitpython_nrf24l01/rf24.py | 238 +++++++++++-------- circuitpython_nrf24l01/rf24_mesh.py | 86 +++++-- circuitpython_nrf24l01/rf24_network.py | 17 +- circuitpython_nrf24l01/wrapper/__init__.py | 8 +- circuitpython_nrf24l01/wrapper/cpy_spidev.py | 10 +- circuitpython_nrf24l01/wrapper/upy_pin.py | 80 ------- circuitpython_nrf24l01/wrapper/upy_spi.py | 80 ------- docs/_static/custom_material.css | 41 ++-- docs/conf.py | 22 +- docs/core_api/advanced_api.rst | 48 ++-- docs/core_api/basic_api.rst | 6 +- docs/core_api/ble_api.rst | 20 +- docs/core_api/configure_api.rst | 6 +- docs/examples.rst | 8 +- docs/index.rst | 30 +-- docs/network_docs/mesh_api.rst | 30 ++- docs/network_docs/network_api.rst | 10 +- docs/network_docs/shared_api.rst | 10 +- docs/network_docs/structs.rst | 2 +- docs/network_docs/topology.rst | 72 +++++- docs/troubleshooting.rst | 11 +- examples/nrf24l01_ack_payload_test.py | 6 +- examples/nrf24l01_context_test.py | 2 +- examples/nrf24l01_fake_ble_test.py | 10 +- examples/nrf24l01_interrupt_test.py | 10 +- examples/nrf24l01_manual_ack_test.py | 10 +- examples/nrf24l01_multiceiver_test.py | 11 +- examples/nrf24l01_network_test.py | 119 +++++----- examples/nrf24l01_scanner_test.py | 4 +- examples/nrf24l01_simple_test.py | 10 +- examples/nrf24l01_stream_test.py | 10 +- requirements.txt | 3 +- setup.py | 10 +- 41 files changed, 666 insertions(+), 624 deletions(-) delete mode 100644 circuitpython_nrf24l01/wrapper/upy_pin.py delete mode 100644 circuitpython_nrf24l01/wrapper/upy_spi.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d3b348..25549d9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,10 +32,10 @@ jobs: tr '-' '_' ) - - name: Set up Python 3.7 + - name: Set up Python uses: actions/setup-python@v1 with: - python-version: 3.7 + python-version: 3.9 - name: Versions run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0919911..a2ebc78 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,10 +28,10 @@ jobs: awk -F '\/' '{ print tolower($2) }' | tr '-' '_' ) - - name: Set up Python 3.7 + - name: Set up Python uses: actions/setup-python@v1 with: - python-version: 3.7 + python-version: 3.9 - name: Versions run: | python3 --version @@ -50,12 +50,9 @@ jobs: - name: Build assets run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . --package_folder_prefix ${{ steps.pkg-name.outputs.pkg-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 + # the 'official' actions version changes the release titlle upon upload. + # @csexton's version is chosen based on because it doesn't + #uses: actions/upload-release-asset@v2 uses: csexton/release-asset-action@master with: pattern: "bundles/*" diff --git a/.pylintrc b/.pylintrc index e579e3a..692887b 100644 --- a/.pylintrc +++ b/.pylintrc @@ -51,11 +51,8 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=print-statement, - duplicate-code, +disable=duplicate-code, import-error, - bad-whitespace, - bad-continuation, consider-using-f-string, too-few-public-methods, too-many-instance-attributes, @@ -230,12 +227,6 @@ max-line-length=88 # Maximum number of lines in a module max-module-lines=1000 -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no @@ -262,38 +253,22 @@ min-similarity-lines=4 [BASIC] -# Naming hint for argument names -argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct argument names argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ -# Naming hint for attribute names -attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct attribute names attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ -# Naming hint for class names -# class-name-hint=[A-Z_][a-zA-Z0-9]+$ -class-name-hint=[A-Z_][a-zA-Z0-9_]+$ - # Regular expression matching correct class names # class-rgx=[A-Z_][a-zA-Z0-9]+$ class-rgx=[A-Z_][a-zA-Z0-9_]+$ -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - # Regular expression matching correct constant names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ @@ -301,9 +276,6 @@ const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # ones are exempt. docstring-min-length=-1 -# Naming hint for function names -function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct function names function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ @@ -314,21 +286,12 @@ good-names=r,g,b,w,i,j,k,n,x,y,z,ex,ok,Run,_ # Include a hint for the correct naming format with invalid-name include-naming-hint=no -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - # Regular expression matching correct inline iteration names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ -# Naming hint for method names -method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct method names method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - # Regular expression matching correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ @@ -344,9 +307,6 @@ no-docstring-rgx=^_ # to this list to register other decorators that produce valid properties. property-classes=abc.abstractproperty -# Naming hint for variable names -variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - # Regular expression matching correct variable names variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ diff --git a/circuitpython_nrf24l01/__init__.py b/circuitpython_nrf24l01/__init__.py index e69de29..ac9f39a 100644 --- a/circuitpython_nrf24l01/__init__.py +++ b/circuitpython_nrf24l01/__init__.py @@ -0,0 +1,5 @@ +"""circuitpython-nrf24l01 package version and repository information. This is meant to +be used with adafruit/circup package manager to manage packages on Circuitpython devices +like pip does for CPython devices.""" +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/nRF24/CircuitPython_nRF24L01.git" diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py index c1ba08a..c881a4f 100644 --- a/circuitpython_nrf24l01/fake_ble.py +++ b/circuitpython_nrf24l01/fake_ble.py @@ -23,11 +23,18 @@ http://dmitry.gr/index.php?r=05.Projects&proj=11.%20Bluetooth%20LE%20fakery""" from os import urandom import struct + +try: + from typing import Union, List, Optional +except ImportError: + pass +import busio +from digitalio import DigitalInOut from micropython import const from .rf24 import RF24, address_repr -def swap_bits(original): +def swap_bits(original: int) -> int: """This function reverses the bit order for a single byte.""" original &= 0xFF reverse = 0 @@ -38,7 +45,7 @@ def swap_bits(original): return reverse -def reverse_bits(original): +def reverse_bits(original: Union[bytes, bytearray]) -> bytearray: """This function reverses the bit order for an entire buffer protocol object.""" ret = bytearray(len(original)) for i, byte in enumerate(original): @@ -46,14 +53,14 @@ def reverse_bits(original): return ret -def chunk(buf, data_type=0x16): +def chunk(buf: Union[bytes, bytearray], data_type: int = 0x16) -> bytearray: """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 whitener(buf, coef): - """Whiten and dewhiten data according to the given coefficient.""" +def whitener(buf: Union[bytes, bytearray], coef: int) -> bytearray: + """Whiten and de-whiten data according to the given coefficient.""" data = bytearray(buf) for i, byte in enumerate(data): res, mask = (0, 1) @@ -67,7 +74,9 @@ def whitener(buf, coef): return data -def crc24_ble(data, deg_poly=0x65B, init_val=0x555555): +def crc24_ble( + data: Union[bytes, bytearray], deg_poly: int = 0x65B, init_val: int = 0x555555 +) -> bytearray: """This function calculates a checksum of various sized buffers.""" crc = init_val for byte in data: @@ -98,7 +107,7 @@ class QueueElement: properties. """ - def __init__(self, buffer): + def __init__(self, buffer: bytearray): #: The transmitting BLE device's MAC address as a `bytes` object. self.mac = bytes(buffer[2:8]) self.name = None @@ -111,7 +120,7 @@ def __init__(self, buffer): .. note:: This value does not represent the received signal strength. The nRF24L01 will receive anything over a -64 dbm threshold.""" - self.data = [] + self.data: List[bytearray, "ServiceData"] = [] """A `list` of the transmitting device's data structures (if any). If an element in this `list` is not an instance (or descendant) of the `ServiceData` class, then it is likely a custom, user-defined, or unsupported @@ -130,7 +139,7 @@ def __init__(self, buffer): self.data.append(buffer[i : i + 1 + size]) i += 1 + size - def _decode_data_struct(self, buf): + def _decode_data_struct(self, buf: bytearray): """Decode a data structure in a received BLE payload.""" # print("decoding", address_repr(buf, 0, " ")) if buf[0] not in (0x16, 0x0A, 0x08, 0x09): @@ -167,7 +176,13 @@ def _decode_data_struct(self, buf): class FakeBLE(RF24): """A class to implement BLE advertisements using the nRF24L01.""" - def __init__(self, spi, csn, ce_pin, spi_frequency=10000000): + def __init__( + self, + spi: busio.SPI, + csn: DigitalInOut, + ce_pin: DigitalInOut, + spi_frequency: int = 10000000, + ): super().__init__(spi, csn, ce_pin, spi_frequency=spi_frequency) self._curr_freq = 2 self._show_dbm = False @@ -192,13 +207,13 @@ def __exit__(self, *exc): return super().__exit__() @property - def mac(self): + def mac(self) -> Union[bytes, bytearray]: """This attribute returns a 6-byte buffer that is used as the arbitrary mac address of the BLE device being emulated.""" return self._mac @mac.setter - def mac(self, address): + def mac(self, address: Optional[Union[bytes, bytearray, int]]): if address is None: self._mac = urandom(6) if isinstance(address, int): @@ -214,7 +229,7 @@ def name(self): return self._ble_name @name.setter - def name(self, _name): + def name(self, _name: Optional[Union[str, bytes, bytearray]]): if _name is not None: if isinstance(_name, str): _name = _name.encode("utf-8") @@ -242,17 +257,21 @@ def hop_channel(self): self._curr_freq += 1 if self._curr_freq < 2 else -2 self.channel = BLE_FREQ[self._curr_freq] - def whiten(self, data) -> bytearray: + def whiten(self, data: Union[bytes, bytearray]) -> bytearray: """Whitening the BLE packet data ensures there's no long repetition of bits.""" coef = (self._curr_freq + 37) | 0x40 # print("buffer: 0x" + address_repr(data, 0)) - # print(f"Whiten Coef: {hex(coef)} on channel {BLE_FREQ[self._curr_freq]}") + # print( + # "Whiten Coef: {} on channel {}".format( + # hex(coef), BLE_FREQ[self._curr_freq] + # ) + # ) data = whitener(data, coef) # print("whitened: 0x" + address_repr(data, 0)) return data - def _make_payload(self, payload) -> bytes: + def _make_payload(self, payload: Union[bytes, bytearray]) -> bytes: """Assemble the entire packet to be transmitted as a payload.""" if self.len_available(payload) < 0: raise ValueError( @@ -270,17 +289,19 @@ def _make_payload(self, payload) -> bytes: if name_length: buf += chunk(self.name, 0x08) buf += payload - # print(f"PL: {address_repr(buf, 0)} CRC: {address_repr(crc24_ble(buf), 0)}") + # print("PL: {} CRC: {}".format( + # address_repr(buf, 0), address_repr(crc24_ble(buf), 0) + # )) buf += crc24_ble(buf) return buf - def len_available(self, hypothetical=b"") -> int: + def len_available(self, hypothetical: Union[bytes, bytearray] = b"") -> int: """This function will calculates how much length (in bytes) is available in the next payload.""" name_length = (len(self._ble_name) + 2) if self._ble_name is not None else 0 return 18 - name_length - self._show_dbm * 3 - len(hypothetical) - def advertise(self, buf=b"", data_type: int = 0xFF): + def advertise(self, buf: Union[bytes, bytearray] = b"", data_type: int = 0xFF): """This blocking function is used to broadcast a payload.""" if not isinstance(buf, (bytearray, bytes, list, tuple)): raise ValueError("buffer is an invalid format") @@ -362,7 +383,7 @@ def open_rx_pipe(self, pipe_number, address): raise NotImplementedError("BLE implementation only uses 1 address on pipe 0") def open_tx_pipe(self, address): - raise NotImplementedError("BLE implentation only uses 1 address") + raise NotImplementedError("BLE implementation only uses 1 address") # pylint: enable=unused-argument @@ -371,7 +392,7 @@ 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, uuid): + def __init__(self, uuid: int): self._type = struct.pack(" bytes: return self._type @property - def data(self) -> bytes: + def data(self) -> Union[bytes, bytearray]: """This attribute is a `bytearray` or `bytes` object.""" return self._data @data.setter - def data(self, value): + def data(self, value: Union[bytes, bytearray]): self._data = value @property @@ -411,7 +432,7 @@ def __repr__(self) -> str: class TemperatureServiceData(ServiceData): - """This derivitive of the `ServiceData` class can be used to represent + """This derivative of the `ServiceData` class can be used to represent temperature data values as a `float` value.""" def __init__(self): @@ -420,7 +441,7 @@ def __init__(self): @property def data(self) -> float: """This attribute is a `float` value.""" - return struct.unpack(" str: - return f"Temperature: {self.data} C" + return "Temperature: {} C".format(self.data) class BatteryServiceData(ServiceData): - """This derivitive of the `ServiceData` class can be used to represent + """This derivative of the `ServiceData` class can be used to represent battery charge percentage as a 1-byte value.""" def __init__(self): @@ -454,11 +475,11 @@ def data(self, value: int): self._data = value def __repr__(self) -> str: - return f"Battery capacity remaining: {self.data}%" + return "Battery capacity remaining: {}%".format(self.data) class UrlServiceData(ServiceData): - """This derivitive of the `ServiceData` class can be used to represent + """This derivative of the `ServiceData` class can be used to represent URL data as a `bytes` value.""" def __init__(self): diff --git a/circuitpython_nrf24l01/network/constants.py b/circuitpython_nrf24l01/network/constants.py index a4497a0..11ae5c7 100644 --- a/circuitpython_nrf24l01/network/constants.py +++ b/circuitpython_nrf24l01/network/constants.py @@ -34,7 +34,7 @@ MESH_WRITE_TIMEOUT = const(115) #: The time (in milliseconds) used to send messages. # sending behavior types -AUTO_ROUTING = const(0o70) #: Send a message with automatic network rounting. +AUTO_ROUTING = const(0o70) #: Send a message with automatic network routing. TX_NORMAL = const(0) #: Send a routed message. TX_ROUTED = const(1) #: Send a routed message. TX_PHYSICAL = const(2) #: Send a message directly to network node. diff --git a/circuitpython_nrf24l01/network/mixins.py b/circuitpython_nrf24l01/network/mixins.py index bab17a9..cb4552f 100644 --- a/circuitpython_nrf24l01/network/mixins.py +++ b/circuitpython_nrf24l01/network/mixins.py @@ -19,9 +19,16 @@ # 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. -"""A module to hold all usuall accesssible RF24 API via the RF24Network API""" +"""A module to hold all usually accessible RF24 API via the RF24Network API""" # pylint: disable=missing-docstring import time + +try: + from typing import Tuple, Union +except ImportError: + pass +import busio +from digitalio import DigitalInOut from ..rf24 import RF24, address_repr from .structs import RF24NetworkFrame, FrameQueue, FrameQueueFrag, is_address_valid from .constants import ( @@ -46,8 +53,14 @@ ) -class RadoMixin: - def __init__(self, spi, csn, ce_pin, spi_frequency=10000000): +class RadioMixin: + def __init__( + self, + spi: busio.SPI, + csn: DigitalInOut, + ce_pin: DigitalInOut, + spi_frequency: int = 10000000, + ): self._rf24 = RF24(spi, csn, ce_pin, spi_frequency=spi_frequency) super().__init__() @@ -86,7 +99,7 @@ def channel(self, val: int): def set_dynamic_payloads(self, enable: bool, pipe: int = None): self._rf24.set_dynamic_payloads(enable, pipe_number=pipe) - def get_dynamic_payloads(self, pipe=None): + def get_dynamic_payloads(self, pipe: int = 0) -> bool: return self._rf24.get_dynamic_payloads(pipe) @property @@ -155,8 +168,14 @@ def _lvl_2_addr(level: int) -> int: return level_addr -class NetworkMixin(RadoMixin): - def __init__(self, spi, csn, ce_pin, spi_frequency=10000000): +class NetworkMixin(RadioMixin): + def __init__( + self, + spi: busio.SPI, + csn: DigitalInOut, + ce_pin: DigitalInOut, + spi_frequency: int = 10000000, + ): super().__init__(spi, csn, ce_pin, spi_frequency=spi_frequency) # setup private members self._net_lvl, self._addr, self._mask, self._mask_inv = (0,) * 4 @@ -171,7 +190,7 @@ def __init__(self, spi, csn, ce_pin, spi_frequency=10000000): self.ret_sys_msg = False #: Force `update()` to return on system message types. self._parenthood = True # can mesh nodes respond to NETWORK_POLL messages? self.max_message_length = 144 #: The maximum length of a frame's message. - #: The queue (FIFO) of recieved frames for this node + #: The queue (FIFO) of received frames for this node self.queue = FrameQueueFrag() #: A buffer containing the last frame handled by the network node self.frame_buf = RF24NetworkFrame() @@ -191,7 +210,7 @@ def _begin(self, n_addr: int): self._rf24.open_rx_pipe(i, self._pipe_address(n_addr, i)) self._rf24.listen = True - # setup address-related instance attibutes + # setup address-related instance attributes self._addr = n_addr self._mask = 0 self._net_lvl = 0 @@ -217,16 +236,18 @@ def print_details(self, dump_pipes: bool = False, network_only: bool = False): if not network_only: self._rf24.print_details(False) print( - f"Network frame_buf contents:\n " - f"Header is {self.frame_buf.header.to_string()}. Message contains:\n\t", + "Network frame_buf contents:\n ", + "Header is {}. Message contains:\n\t".format( + self.frame_buf.header.to_string() + ), "{}".format( "an empty buffer" if not self.frame_buf.message else address_repr(self.frame_buf.message, 0, " ") ), ) - print(f"Return on system messages__{bool(self.ret_sys_msg)}") - print(f"Allow network multicasts___{bool(self.allow_multicast)}") + print("Return on system messages__{}".format(bool(self.ret_sys_msg))) + print("Allow network multicasts___{}".format(bool(self.allow_multicast))) print( "Multicast relay____________{}abled".format( "En" if self._relay_enabled else "Dis" @@ -237,10 +258,10 @@ def print_details(self, dump_pipes: bool = False, network_only: bool = False): "En" if self._frag_enabled else "Dis" ) ) - print(f"Network max message length_{self.max_message_length} bytes") - print(f"Network TX timeout_________{self.tx_timeout} milliseconds") - print(f"Network Rounting timeout___{self.route_timeout} milliseconds") - print(f"Network node address_______{oct(self._addr)}") + print("Network max message length_{} bytes".format(self.max_message_length)) + print("Network TX timeout_________{} milliseconds".format(self.tx_timeout)) + print("Network Routing timeout___{} milliseconds".format(self.route_timeout)) + print("Network node address_______{}".format(oct(self._addr))) if dump_pipes: self._rf24.print_pipes() @@ -317,7 +338,7 @@ def _pipe_address(self, node_addr: int, pipe_number: int) -> bytearray: def _net_update(self) -> int: """keep the network layer current; returns the received message type""" - ret_val = 0 # sentinal indicating there is nothing to report + ret_val = 0 # sentinel indicating there is nothing to report while True: temp_buf = self._rf24.read() if temp_buf is None: @@ -347,7 +368,7 @@ def _net_update(self) -> int: if not keep_updating: return ret_val - def _handle_frame_for_this_node(self, msg_t: int) -> tuple: + def _handle_frame_for_this_node(self, msg_t: int) -> Tuple[bool, int]: """Returns False if the frame is not consumed or True if consumed""" if msg_t == NETWORK_PING: return (True, msg_t) @@ -364,7 +385,10 @@ def _handle_frame_for_this_node(self, msg_t: int) -> tuple: if self.ret_sys_msg and msg_t > MAX_USR_DEF_MSG_TYPE or msg_t == NETWORK_ACK: # print("Received system payload type", msg_t) if msg_t not in ( - MSG_FRAG_FIRST, MSG_FRAG_MORE, MSG_FRAG_LAST, NETWORK_EXT_DATA, + MSG_FRAG_FIRST, + MSG_FRAG_MORE, + MSG_FRAG_LAST, + NETWORK_EXT_DATA, ): return (False, msg_t) @@ -375,7 +399,7 @@ def _handle_frame_for_this_node(self, msg_t: int) -> tuple: return (False, NETWORK_EXT_DATA) return (True, msg_t) - def _handle_frame_for_other_node(self, msg_t: int) -> tuple: + def _handle_frame_for_other_node(self, msg_t: int) -> Tuple[bool, int]: """Returns False if the frame is not consumed or True if consumed""" if self.allow_multicast: if self.frame_buf.header.to_node == NETWORK_MULTICAST_ADDR: @@ -429,7 +453,12 @@ def read(self) -> RF24NetworkFrame: """Get (from `queue`) the next available frame.""" return self.queue.dequeue() - def multicast(self, message, message_type, level: int = None) -> bool: + def multicast( + self, + message: Union[bytes, bytearray], + message_type: Union[str, int], + level: int = None, + ) -> bool: """Broadcast a message to all nodes on a certain network level.""" if not self._validate_msg_len(len(message)): message = message[:MAX_FRAG_SIZE] @@ -567,7 +596,7 @@ def _tx_standby(self, delta_time: int) -> bool: def _logi_2_phys( self, to_node: int, send_type: int, is_multicast: bool = False - ) -> tuple: + ) -> Tuple[int, int, bool]: """translate msg route into node address, pipe number, & multicast flag.""" conv_to_node, conv_to_pipe = (self._parent, self._parent_pipe) if send_type > TX_ROUTED: diff --git a/circuitpython_nrf24l01/network/structs.py b/circuitpython_nrf24l01/network/structs.py index b59f304..d4452ed 100644 --- a/circuitpython_nrf24l01/network/structs.py +++ b/circuitpython_nrf24l01/network/structs.py @@ -21,6 +21,11 @@ # THE SOFTWARE. """This module contains the data structures used foe network packets.""" import struct + +try: + from typing import Union +except ImportError: + pass from .constants import ( NETWORK_EXT_DATA, NETWORK_MULTICAST_ADDR, @@ -29,6 +34,7 @@ MSG_FRAG_LAST, ) + def is_address_valid(address) -> bool: """Test if a given address is a valid :ref:`Logical Address `.""" if address is None: @@ -47,16 +53,14 @@ def is_address_valid(address) -> bool: class RF24NetworkHeader: """The header information used for routing network messages.""" - def __init__(self, to_node: int=None, message_type=None): + def __init__(self, to_node: int = None, message_type: Union[str, int] = None): self.from_node = None #: |uint16_t| self.to_node = (to_node & 0xFFF) if to_node is not None else 0 #: |uint16_t| - #: The type of message. - self.message_type = 0 if message_type is None else message_type if isinstance(message_type, str): # convert the first char to int if `message_type` is a string - self.message_type = ord(message_type[0]) + self.message_type = ord(message_type[0]) #: The type of message. else: - self.message_type &= 0xFF + self.message_type = 0 if message_type is None else message_type & 0xFF self.frame_id = RF24NetworkHeader.__next_id #: |uint16_t| RF24NetworkHeader.__next_id = (RF24NetworkHeader.__next_id + 1) & 0xFFFF self.reserved = 0 #: A single byte reserved for network usage. @@ -87,7 +91,8 @@ def pack(self) -> bytes: self.message_type if not isinstance(self.message_type, str) else (ord(self.message_type[0]) if self.message_type else 0) - ) & 0xFF, + ) + & 0xFF, self.reserved & 0xFF, ) @@ -103,16 +108,23 @@ def to_string(self) -> str: self.message_type if not isinstance(self.message_type, str) else (ord(self.message_type[0]) if self.message_type else 0) - ) & 0xFF, + ) + & 0xFF, self.frame_id, self.reserved, ) + def __repr__(self) -> str: + """Alias of `to_string()`""" + return "" + class RF24NetworkFrame: """Structure of a single frame.""" - def __init__(self, header: RF24NetworkHeader=None, message=None): + def __init__( + self, header: RF24NetworkHeader = None, message: Union[bytes, bytearray] = None + ): if header is not None and not isinstance(header, RF24NetworkHeader): raise TypeError("header must be a RF24NetworkHeader object") if message is not None and not isinstance(message, (bytes, bytearray)): @@ -122,8 +134,7 @@ def __init__(self, header: RF24NetworkHeader=None, message=None): self.message = bytearray(0) if message is None else bytearray(message) """The entire message or a fragment of a message allocated to the frame.""" - - def unpack(self, buffer) -> bool: + def unpack(self, buffer: Union[bytes, bytearray]) -> bool: """Decode the `header` & `message` from a ``buffer``.""" if self.header.unpack(buffer): self.message = buffer[8:] @@ -183,6 +194,7 @@ def __len__(self) -> int: """:Returns: The number of the enqueued frames.""" return len(self._queue) + class FrameQueueFrag(FrameQueue): """A specialized `FrameQueue` with an additional cache for fragmented frames.""" diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py index 7dc836d..e5b5b64 100644 --- a/circuitpython_nrf24l01/rf24.py +++ b/circuitpython_nrf24l01/rf24.py @@ -22,10 +22,17 @@ # THE SOFTWARE. """rf24 module containing the base class RF24""" import time + +try: + from typing import Union, Sequence, Optional, List, Tuple + from typing_extensions import Literal +except ImportError: + pass from micropython import const +from digitalio import DigitalInOut +import busio from .wrapper import SPIDevCtx, SPIDevice - 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 @@ -47,15 +54,23 @@ def address_repr(buf, reverse: bool = True, delimit: str = "") -> str: class RF24: """A driver class for the nRF24L01(+) transceiver radios.""" - def __init__(self, spi, csn, ce_pin, spi_frequency=10000000): + def __init__( + self, + spi: busio.SPI, + csn: DigitalInOut, + ce_pin: DigitalInOut, + spi_frequency=10000000, + ): + self._in = bytearray(97) # for full RX FIFO reads + STATUS byte + self._out = bytearray(33) # for max payload writes + STATUS byte self._ce_pin = ce_pin self._ce_pin.switch_to_output(value=False) # init shadow copy of RX addresses for all pipes for context manager - self._pipes = [] # will be 2 bytearrays and 4 ints - # self._status = status byte returned on all SPI transactions + self._pipes = [bytearray(5)] * 2 + [0] * 4 + # self._in[0] = status byte returned on all SPI transactions # pre-configure the CONFIGURE register: # 0x0E = all IRQs enabled, CRC is 2 bytes, and power up in TX mode - self._status, self._config, self._spi = (0, 0x0E, None) + self._in[0], self._config, self._spi = (0, 0x0E, None) # setup SPI if type(spi).__name__.endswith("SpiDev"): self._spi = SPIDevCtx(spi, csn, spi_frequency=spi_frequency) @@ -66,9 +81,9 @@ def __init__(self, spi, csn, ce_pin, spi_frequency=10000000): raise RuntimeError("radio hardware not responding") for i in range(6): # capture RX addresses from registers if i < 2: - self._pipes.append(self._reg_read_bytes(RX_ADDR_P0 + i)) + self._pipes[i] = self._reg_read_bytes(RX_ADDR_P0 + i) else: - self._pipes.append(self._reg_read(RX_ADDR_P0 + i)) + 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._open_pipes, self._is_plus_variant = (0, False) # close all RX pipes @@ -133,53 +148,54 @@ def __exit__(self, *exc): return False @property - def ce_pin(self): + def ce_pin(self) -> bool: """Control the radio's CE pin (for advanced usage)""" return self._ce_pin.value @ce_pin.setter - def ce_pin(self, val): + def ce_pin(self, val: bool): self._ce_pin.value = val def _reg_read(self, reg: int) -> int: - in_buf = bytearray([0, 0]) + self._out[0] = reg with self._spi as spi: # time.sleep(0.000005) - spi.write_readinto(bytes([reg, 0]), in_buf) - self._status = in_buf[0] - # print("SPI read 1 byte from", ("%02X" % reg), ("%02X" % in_buf[1])) - return in_buf[1] + spi.write_readinto(self._out, self._in, out_end=2, in_end=2) + # print("SPI read 1 byte from", ("%02X" % reg), ("%02X" % self._in[1])) + return self._in[1] def _reg_read_bytes(self, reg: int, buf_len: int = 5) -> bytearray: - in_buf = bytearray(buf_len + 1) + self._out[0] = reg + buf_len += 1 with self._spi as spi: # time.sleep(0.000005) - spi.write_readinto(bytes([reg] + [0] * buf_len), in_buf) - self._status = in_buf[0] + spi.write_readinto(self._out, self._in, out_end=buf_len, in_end=buf_len) # print("SPI read {} bytes from {} {}".format( - # buf_len, ("%02X" % reg), address_repr(in_buf[1:], 0) + # buf_len - 1, ("%02X" % reg), address_repr(self._in[1 : buf_len], 0) # )) - return in_buf[1:] + return self._in[1:buf_len] - def _reg_write_bytes(self, reg: int, out_buf): - in_buf = bytearray(len(out_buf) + 1) + def _reg_write_bytes(self, reg: int, out_buf: Union[bytes, bytearray]): + self._out[0] = 0x20 | reg + buf_len = len(out_buf) + 1 + self._out[1:buf_len] = out_buf with self._spi as spi: # time.sleep(0.000005) - spi.write_readinto(bytes([0x20 | reg]) + out_buf, in_buf) - self._status = in_buf[0] + spi.write_readinto(self._out, self._in, out_end=buf_len, in_end=buf_len) # print("SPI write {} bytes to {} {}".format( - # len(out_buf), ("%02X" % reg), address_repr(out_buf, 0) + # buf_len - 1, ("%02X" % reg), address_repr(self._out[1 : buf_len], 0) # )) def _reg_write(self, reg: int, value: int = None): - out_buf = bytes([reg]) + self._out[0] = reg + buf_len = 1 if value is not None: - out_buf = bytes([(0x20 if reg != 0x50 else 0) | reg, value]) - in_buf = bytearray(len(out_buf)) + self._out[0] = (0x20 if reg != 0x50 else 0) | reg + self._out[1] = value + buf_len += 1 with self._spi as spi: # time.sleep(0.000005) - spi.write_readinto(out_buf, in_buf) - self._status = in_buf[0] + spi.write_readinto(self._out, self._in, out_end=buf_len, in_end=buf_len) # if reg != 0xFF: # print( # "SPI write", "command" if value is None else "1 byte to", @@ -197,7 +213,7 @@ def address_length(self, length: int): self._addr_len = int(length) if 3 <= length <= 5 else 2 self._reg_write(0x03, self._addr_len - 2) - def open_tx_pipe(self, address) -> None: + def open_tx_pipe(self, address: Union[bytes, bytearray]) -> None: """Open a data pipe for TX transmissions.""" if self._pipe0_read_addr != address and self._aa & 1: for i, val in enumerate(address): @@ -216,7 +232,7 @@ def close_rx_pipe(self, pipe_number: int) -> None: self._pipe0_read_addr = None self._reg_write(OPEN_PIPES, self._open_pipes) - def open_rx_pipe(self, pipe_number: int, address) -> None: + def open_rx_pipe(self, pipe_number: int, address: Union[bytes, bytearray]) -> None: """Open a specific data pipe for RX transmissions.""" if not 0 <= pipe_number <= 5: raise IndexError("pipe number must be in range [0, 5]") @@ -270,15 +286,15 @@ def listen(self, is_rx: bool): def available(self) -> bool: """A `bool` describing if there is a payload in the RX FIFO.""" - return self.update() and self._status >> 1 & 7 < 6 + return self.update() and self._in[0] >> 1 & 7 < 6 def any(self) -> int: """This function reports the next available payload's length (in bytes).""" last_dyn_size = self._reg_read(0x60) - if self._status >> 1 & 7 < 6: + if self._in[0] >> 1 & 7 < 6: if self._features & 4: return last_dyn_size - return self._pl_len[(self._status >> 1) & 7] + return self._pl_len[(self._in[0] >> 1) & 7] return 0 def read(self, length: int = None) -> bytearray: @@ -292,11 +308,11 @@ def read(self, length: int = None) -> bytearray: def send( self, - buf, + buf: Union[bytes, bytearray, Sequence[Union[bytes, bytearray]]], ask_no_ack: bool = False, force_retry: int = 0, send_only: bool = False, - ): + ) -> Union[bool, bytearray, List[Union[bool, bytearray]]]: """This blocking function is used to transmit payload(s).""" self._ce_pin.value = False if isinstance(buf, (list, tuple)): @@ -304,20 +320,20 @@ def send( for b in buf: result.append(self.send(b, ask_no_ack, force_retry, send_only)) return result - if self._status & 0x10 or self._status & 1: + if self._in[0] & 0x10 or self._in[0] & 1: self.flush_tx() - if not send_only and self._status >> 1 & 7 < 6: + if not send_only and self._in[0] >> 1 & 7 < 6: self.flush_rx() up_cnt = 0 self.write(buf, ask_no_ack) - while not self._status & 0x30: + while not self._in[0] & 0x30: up_cnt += self.update() - result = bool(self._status & 0x20) - # print("send did {} updates. flags: {}".format(up_cnt, self._status >> 4)) + result = bool(self._in[0] & 0x20) + # print("send did {} updates. flags: {}".format(up_cnt, self._in[0] >> 4)) while force_retry and not result: result = self.resend(send_only) force_retry -= 1 - if self._status & 0x60 == 0x60 and not send_only: + if self._in[0] & 0x60 == 0x60 and not send_only: result = self.read() # self._ce_pin.value = False return result @@ -325,13 +341,13 @@ def send( @property def tx_full(self) -> bool: """An `bool` to represent if the TX FIFO is full. (read-only)""" - return bool(self._status & 1) + return bool(self._in[0] & 1) @property - def pipe(self): + def pipe(self) -> Optional[int]: """The number of the data pipe that received the next available payload in the RX FIFO. (read only)""" - result = self._status >> 1 & 7 + result = self._in[0] >> 1 & 7 if result <= 5 and self._spi is not None: return result return None @@ -339,19 +355,19 @@ def pipe(self): @property def irq_dr(self) -> bool: """A `bool` that represents the "Data Ready" interrupted flag. (read-only)""" - return bool(self._status & 0x40) + return bool(self._in[0] & 0x40) @property def irq_ds(self) -> bool: """A `bool` that represents the "Data Sent" interrupted flag. (read-only)""" - return bool(self._status & 0x20) + return bool(self._in[0] & 0x20) @property def irq_df(self) -> bool: """A `bool` that represents the "Data Failed" interrupted flag. (read-only)""" - return bool(self._status & 0x10) + return bool(self._in[0] & 0x10) - def update(self) -> True: + def update(self) -> Literal[True]: """This function gets an updated status byte over SPI.""" self._reg_write(0xFF) return True @@ -371,8 +387,8 @@ def interrupt_config( self._config |= (not data_fail) << 4 | (not data_sent) << 5 self._reg_write(CONFIGURE, self._config) - def print_details(self, dump_pipes: bool = False): - """This debuggung function outputs all details about the nRF24L01.""" + def print_details(self, dump_pipes: bool = False) -> None: + """This debugging function outputs all details about the nRF24L01.""" observer = self._reg_read(8) _fifo = self._reg_read(0x17) self._config = self._reg_read(CONFIGURE) @@ -406,50 +422,60 @@ def print_details(self, dump_pipes: bool = False): if self._config & 2 else "Off" ) - print(f"Is a plus variant_________{self.is_plus_variant}") + print("Is a plus variant_________{}".format(self.is_plus_variant)) print( - f"Channel___________________{self._channel}", - f"~ {(self._channel + 2400) / 1000} GHz", + "Channel___________________{}".format(self._channel), + "~ {} GHz".format((self._channel + 2400) / 1000), ) print( - f"RF Data Rate______________{d_rate}", "Mbps" if d_rate != 250 else "Kbps" + "RF Data Rate______________{}".format(d_rate), + "Mbps" if d_rate != 250 else "Kbps", ) - print(f"RF Power Amplifier________{_pa_level} dbm") + print("RF Power Amplifier________{} dbm".format(_pa_level)) print( "RF Low Noise Amplifier____{}abled".format( "En" if bool(self._rf_setup & 1) else "Dis" ) ) - print(f"CRC bytes_________________{_crc}") - print(f"Address length____________{self._addr_len} bytes") - print(f"TX Payload lengths________{self._pl_len[0]} bytes") + print("CRC bytes_________________{}".format(_crc)) + print("Address length____________{} bytes".format(self._addr_len)) + print("TX Payload lengths________{} bytes".format(self._pl_len[0])) + print( + "Auto retry delay__________{} microseconds".format( + ((self._retry_setup & 0xF0) >> 4) * 250 + 250 + ) + ) + print("Auto retry attempts_______{} maximum".format(self._retry_setup & 0x0F)) + print("Re-use TX FIFO____________{}".format(bool(_fifo & 64))) + print( + "Packets lost on current channel_____________________{}".format( + observer >> 4 + ) + ) print( - f"Auto retry delay__________" - f"{((self._retry_setup & 0xF0) >> 4) * 250 + 250} microseconds", + "Retry attempts made for last transmission___________{}".format( + observer & 0xF + ) ) - print(f"Auto retry attempts_______{self._retry_setup & 0x0F} maximum") - print(f"Re-use TX FIFO____________{bool(_fifo & 64)}") - print(f"Packets lost on current channel_____________________{observer >> 4}") - print(f"Retry attempts made for last transmission___________{observer & 0xF}") print( "IRQ on Data Ready__{}abled".format("Dis" if self._config & 64 else "_En"), - f" Data Ready___________{self.irq_dr}", + " Data Ready___________{}".format(self.irq_dr), ) print( "IRQ on Data Fail___{}abled".format("Dis" if self._config & 16 else "_En"), - f" Data Failed__________{self.irq_df}", + " Data Failed__________{}".format(self.irq_df), ) print( "IRQ on Data Sent___{}abled".format("Dis" if self._config & 32 else "_En"), - f" Data Sent____________{self.irq_ds}", + " Data Sent____________{}".format(self.irq_ds), ) print( "TX FIFO full__________{}e".format("_Tru" if _fifo & 0x20 else "Fals"), - f" TX FIFO empty________{bool(_fifo & 0x10)}", + " TX FIFO empty________{}".format(bool(_fifo & 0x10)), ) print( "RX FIFO full__________{}e".format("_Tru" if _fifo & 2 else "Fals"), - f" RX FIFO empty________{bool(_fifo & 1)}", + " RX FIFO empty________{}".format(bool(_fifo & 1)), ) print( "Ask no ACK_________{}ed Custom ACK Payload___{}abled".format( @@ -457,15 +483,15 @@ def print_details(self, dump_pipes: bool = False): "En" if self._features & 2 else "Dis", ), ) - print(f"Dynamic Payloads___{dyn_p} Auto Acknowledgment__{auto_a}") + print("Dynamic Payloads___{} Auto Acknowledgment__{}".format(dyn_p, auto_a)) print( "Primary Mode_____________{}X".format("R" if self._config & 1 else "T"), - f" Power Mode___________{pwr}", + " Power Mode___________{}".format(pwr), ) if dump_pipes: self.print_pipes() - def print_pipes(self): + def print_pipes(self) -> None: """Prints all information specific to pipe's addresses, RX state, & expected static payload sizes (if configured to use static payloads).""" self._open_pipes = self._reg_read(OPEN_PIPES) @@ -476,7 +502,7 @@ def print_pipes(self): else: self._pipes[i] = self._reg_read(RX_ADDR_P0 + i) self._pl_len[i] = self._reg_read(RX_PL_LENG + i) - print(f"TX address____________ 0x{address_repr(self.address())}") + print("TX address____________ 0x{}".format(address_repr(self.address()))) for i in range(6): is_open = self._open_pipes & (1 << i) print( @@ -485,11 +511,11 @@ def print_pipes(self): ), ) if is_open and not self._dyn_pl & (1 << i): - print(f"\t\texpecting {self._pl_len[i]} byte static payloads") + print("\t\texpecting {} byte static payloads".format(self._pl_len[i])) @property def is_plus_variant(self) -> bool: - """A `bool` descibing if the nRF24L01 is a plus variant or not (read-only).""" + """A `bool` describing if the nRF24L01 is a plus variant or not (read-only).""" return self._is_plus_variant @property @@ -500,7 +526,7 @@ def dynamic_payloads(self) -> int: return self._dyn_pl @dynamic_payloads.setter - def dynamic_payloads(self, enable): + def dynamic_payloads(self, enable: Union[int, bool, Sequence[bool]]): self._features = self._reg_read(TX_FEATURE) if isinstance(enable, bool): self._dyn_pl = 0x3F if enable else 0 @@ -512,7 +538,7 @@ def dynamic_payloads(self, enable): 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(f"dynamic_payloads: {enable} is an invalid input") + raise ValueError("dynamic_payloads: {} is an invalid input".format(enable)) self._features = (self._features & 3) | (bool(self._dyn_pl) << 2) self._reg_write(TX_FEATURE, self._features) self._reg_write(DYN_PL_LEN, self._dyn_pl) @@ -539,7 +565,7 @@ def payload_length(self) -> int: return self._pl_len[0] @payload_length.setter - def payload_length(self, length): + def payload_length(self, length: Union[int, Sequence[int]]): if isinstance(length, int): length = [max(1, length)] * 6 elif not isinstance(length, (list, tuple)): @@ -601,7 +627,7 @@ def get_auto_retries(self) -> tuple: @property def last_tx_arc(self) -> int: - """Return the number of attempts made for last transission (read-only).""" + """Return the number of attempts made for last transmission (read-only).""" return self._reg_read(8) & 0x0F @property @@ -612,7 +638,7 @@ def auto_ack(self) -> int: return self._aa @auto_ack.setter - def auto_ack(self, enable): + def auto_ack(self, enable: Union[int, bool, Sequence[bool]]): if isinstance(enable, bool): self._aa = 0x3F if enable else 0 elif isinstance(enable, int): @@ -623,7 +649,7 @@ def auto_ack(self, enable): if i < 6 and val >= 0: # skip pipe if val is negative self._aa = (self._aa & ~(1 << i)) | (bool(val) << i) else: - raise ValueError(f"auto_ack: {enable} is not a valid input") + raise ValueError("auto_ack: {} is not a valid input".format(enable)) self._reg_write(AUTO_ACK, self._aa) def set_auto_ack(self, enable: bool, pipe_number: int): @@ -747,7 +773,7 @@ def pa_level(self) -> int: return (3 - ((self._rf_setup & 6) >> 1)) * -6 @pa_level.setter - def pa_level(self, power): + def pa_level(self, power: Union[bool, Tuple[bool, int]]): lna_bit = True if isinstance(power, (list, tuple)) and len(power) > 1: lna_bit, power = bool(power[1]), int(power[0]) @@ -768,43 +794,47 @@ def resend(self, send_only: bool = False): if self.fifo(True, True): return False self._ce_pin.value = False - if not send_only and (self._status >> 1) < 6: + if not send_only and (self._in[0] >> 1) < 6: self.flush_rx() self.clear_status_flags() # self._reg_write(0xE3) up_cnt = 0 self._ce_pin.value = True - while not self._status & 0x30: + while not self._in[0] & 0x30: up_cnt += self.update() # self._ce_pin.value = False - result = bool(self._status & 0x20) - # print("resend did {} updates. flags: {}".format(up_cnt, self._status >> 4)) - if result and self._status & 0x40 and not send_only: + result = bool(self._in[0] & 0x20) + # print("resend did {} updates. flags: {}".format(up_cnt, self._in[0] >> 4)) + if result and self._in[0] & 0x40 and not send_only: return self.read() return result - def write(self, buf, ask_no_ack: bool = False, write_only: bool = False) -> bool: + def write( + self, + buf: Union[bytes, bytearray], + ask_no_ack: bool = False, + write_only: bool = False, + ) -> bool: """This non-blocking and helper function to `send()` can only handle one payload at a time.""" if not buf or len(buf) > 32: raise ValueError("buffer must have a length in range [1, 32]") self.clear_status_flags() - is_power_up = self._config & 2 - if self._config & 3 != 2: # is radio powered up in TX mode? - self._config = (self._config & 0x7C) | 2 - self._reg_write(CONFIGURE, self._config) - if not is_power_up: - time.sleep(0.00015) - if self._status & 1: + # is_power_up = self._config & 2 + # if self._config & 3 != 2: # is radio powered up in TX mode? + # self._config = (self._config & 0x7C) | 2 + # self._reg_write(CONFIGURE, self._config) + # if not is_power_up: + # time.sleep(0.00015) + if self._in[0] & 1: return False - if not bool((self._dyn_pl & 1) and (self._features & 4)): - if len(buf) < self._pl_len[0]: - buf += b"\0" * (self._pl_len[0] - len(buf)) - elif len(buf) > self._pl_len[0]: - buf = buf[: self._pl_len[0]] - if ask_no_ack and self._features & 1 == 0: - self._features = self._features & 0xFE | 1 - self._reg_write(TX_FEATURE, self._features) + if not self._dyn_pl & 1 and not self._features & 4: + buf_len = len(buf) + pl_len = self._pl_len[0] + if buf_len < pl_len: + buf += b"\0" * (pl_len - buf_len) + elif buf_len > pl_len: + buf = buf[:pl_len] self._reg_write_bytes(0xA0 | (bool(ask_no_ack) << 4), buf) if not write_only: self._ce_pin.value = True diff --git a/circuitpython_nrf24l01/rf24_mesh.py b/circuitpython_nrf24l01/rf24_mesh.py index 096cae9..b165e50 100644 --- a/circuitpython_nrf24l01/rf24_mesh.py +++ b/circuitpython_nrf24l01/rf24_mesh.py @@ -26,7 +26,13 @@ try: import json except ImportError: - json = None + json = None # some CircuitPython boards don't have the json module +try: + from typing import Union +except ImportError: + pass +import busio +from digitalio import DigitalInOut from .network.constants import ( MESH_ADDR_REQUEST, MESH_ADDR_RESPONSE, @@ -53,7 +59,14 @@ class RF24MeshNoMaster(NetworkMixin): """A descendant of the same mixin class that `RF24Network` inherits from. This class adds easy Mesh networking capability (non-master nodes only).""" - def __init__(self, spi, csn_pin, ce_pin, node_id, spi_frequency=10000000): + def __init__( + self, + spi: busio.SPI, + csn_pin: DigitalInOut, + ce_pin: DigitalInOut, + node_id: int, + spi_frequency: int = 10000000, + ): super().__init__(spi, csn_pin, ce_pin, spi_frequency) self._id = min(255, node_id) #: This variable can be assigned a function to perform during long operations. @@ -75,8 +88,8 @@ def node_id(self, _id: int): def print_details(self, dump_pipes: bool = False, network_only: bool = False): """See RF24.print_details() and Shared Networking API docs""" super().print_details(False, network_only) - print(f"Network node id____________{self.node_id}") - print(f"Mesh node allows children__{self._parenthood}") + print("Network node id____________{}".format(self.node_id)) + print("Mesh node allows children__{}".format(self._parenthood)) if dump_pipes: self._rf24.print_pipes() @@ -149,7 +162,7 @@ def _lookup_2_master(self, number: int, lookup_type: int) -> int: return self.frame_buf.message[0] def check_connection(self) -> bool: - """Check for network conectivity (not for use on master node).""" + """Check for network connectivity (not for use on master node).""" # do a double check as a manual retry in lack of using auto-ack if self.lookup_address(self._id) < 1: if self.lookup_address(self._id) < 1: @@ -235,7 +248,12 @@ def allow_children(self) -> bool: def allow_children(self, allow: bool): self._parenthood = allow - def send(self, to_node: int, message_type, message) -> bool: + def send( + self, + to_node: int, + message_type: Union[int, str], + message: Union[bytes, bytearray], + ) -> bool: """Send a message to a mesh `node_id`.""" if self._addr == NETWORK_DEFAULT_ADDR: return False @@ -255,7 +273,12 @@ def send(self, to_node: int, message_type, message) -> bool: to_node = self._addr return self.write(to_node, message_type, message) - def write(self, to_node: int, message_type, message) -> bool: + def write( + self, + to_node: int, + message_type: Union[int, str], + message: Union[bytes, bytearray], + ) -> bool: """Send a message to a network `node_address`.""" if not isinstance(message, (bytes, bytearray)): raise TypeError("message must be a `bytes` or `bytearray` object") @@ -273,7 +296,14 @@ class RF24Mesh(RF24MeshNoMaster): """A descendant of the base class `RF24MeshNoMaster` that adds algorithms needed for Mesh network master nodes.""" - def __init__(self, spi, csn_pin, ce_pin, node_id, spi_frequency=10000000): + def __init__( + self, + spi: busio.SPI, + csn_pin: DigitalInOut, + ce_pin: DigitalInOut, + node_id: int, + spi_frequency: int = 10000000, + ): super().__init__(spi, csn_pin, ce_pin, node_id, spi_frequency) self._do_dhcp = False self.dhcp_dict = {} #: A `dict` that enables master nodes to act as a DNS. @@ -359,24 +389,34 @@ def set_address( return self.dhcp_dict[node_id] = node_address - def save_dhcp(self, filename: str = "dhcp.json"): + def save_dhcp(self, filename: str = "dhcplist.json", as_bin: bool = False): """Save the `dhcp_dict` to a JSON file (meant for master nodes only).""" - if json is None: - return # some CircuitPython boards don't have the json module - with open(filename, "w", encoding="utf8") as json_file: + with open(filename, "wb") as json_file: # This throws an OSError if file system is read-only. ALL MCU boards # running CircuitPython firmware (not RPi) have read-only file system. - json.dump(self.dhcp_dict, json_file) - - def load_dhcp(self, filename: str = "dhcp.json"): + if json is not None and not as_bin: + json.dump(self.dhcp_dict, json_file) + elif as_bin: + for _id, _addr in self.dhcp_dict.items(): + json_file.write(bytes([_id, 0])) # pad id w/ 0 for mem alignment + json_file.write(struct.pack(" int: class RF24Network(RF24NetworkRoutingOnly): """The object used to instantiate the nRF24L01 as a network node.""" - def send(self, header: RF24NetworkHeader, message) -> bool: + def send(self, header: RF24NetworkHeader, message: Union[bytes, bytearray]) -> bool: """Deliver a message according to the header information.""" return self.write(RF24NetworkFrame(header, message)) diff --git a/circuitpython_nrf24l01/wrapper/__init__.py b/circuitpython_nrf24l01/wrapper/__init__.py index 315e0e7..d8ef329 100644 --- a/circuitpython_nrf24l01/wrapper/__init__.py +++ b/circuitpython_nrf24l01/wrapper/__init__.py @@ -20,11 +20,5 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """import only accessible API wrappers""" +from adafruit_bus_device.spi_device import SPIDevice from .cpy_spidev import SPIDevCtx # for linux only - -try: # check for MicroPython's machine.Pin - from .upy_pin import DigitalInOut - from .upy_spi import SPIDevice -except ImportError: # must be on linux or CircuitPython compatible - from digitalio import DigitalInOut - from adafruit_bus_device.spi_device import SPIDevice diff --git a/circuitpython_nrf24l01/wrapper/cpy_spidev.py b/circuitpython_nrf24l01/wrapper/cpy_spidev.py index 68b361f..b96e8f5 100644 --- a/circuitpython_nrf24l01/wrapper/cpy_spidev.py +++ b/circuitpython_nrf24l01/wrapper/cpy_spidev.py @@ -30,9 +30,9 @@ class SPIDevCtx: :param int,list,tuple csn: The CE pin number (``0``, ``1``, or ``2``) to use as the SPI device's CSN pin. For advanced users, a `list` or `tuple` can instead be used to specify a bus number and a pin number that isn't - controlled by the SPIDEV kernal. Where index ``0`` is the bus number + controlled by the SPIDEV kernel. Where index ``0`` is the bus number multiplied by 10, and index ``1`` is the pin number. - :param int spi_frequency: the SPI frquency to use for the SPI device. + :param int spi_frequency: the SPI frequency to use for the SPI device. Defaults to 10MHz. """ @@ -64,11 +64,13 @@ def __exit__(self, *excs): self._spi.close() return False - def write_readinto(self, out_buf, in_buf): + def write_readinto(self, out_buf, in_buf, in_end: int = None, out_end: int = None): """wraps ``spidev.SpiDev.xfer2()`` into MicroPython compatible ``spi.write_readinto()`` calls. .. warning:: The ``in_buf`` parameter must be a mutable `bytearray`. The ``out_buf`` can be either a `bytes` or `bytearray` object. """ - in_buf[:] = bytearray(self._spi.xfer2(out_buf, self._baudrate)) + out_end = out_end if out_end is not None else len(out_buf) + in_end = in_end if in_end is not None else len(in_buf) + in_buf[:in_end] = bytearray(self._spi.xfer2(out_buf[:out_end], self._baudrate)) diff --git a/circuitpython_nrf24l01/wrapper/upy_pin.py b/circuitpython_nrf24l01/wrapper/upy_pin.py deleted file mode 100644 index b7be11b..0000000 --- a/circuitpython_nrf24l01/wrapper/upy_pin.py +++ /dev/null @@ -1,80 +0,0 @@ -"""wrappers for MicroPython's machine.Pin as CircuitPython's digitalio API""" -# The MIT License (MIT) -# -# Copyright (c) 2020 Brendan Doherty -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# 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. -from machine import Pin # pylint: disable=import-error - -# pylint: disable=too-few-public-methods,missing-class-docstring - - -class DriveMode: - PUSH_PULL = Pin.PULL_HOLD - OPEN_DRAIN = Pin.OPEN_DRAIN - - -class Pull: - UP = Pin.PULL_UP # pylint: disable=invalid-name - DOWN = Pin.PULL_DOWN - - -# pylint: enable=missing-class-docstring - - -class DigitalInOut: - """A class to control micropython's :py:class:`~machine.Pin` object like - a circuitpython DigitalInOut object. - - :param int pin: The digital pin number to alias. - """ - - def __init__(self, pin_number): - self._pin = Pin(pin_number, Pin.IN) - - def deinit(self): - """deinitialize the GPIO pin""" - # deinit() not implemented in micropython - # avoid raising a NotImplemented Error - pass # pylint: disable=unnecessary-pass - - def switch_to_output(self, pull=None, value=False): - """change pin into output""" - if pull is None: - self._pin.init(Pin.OUT, value=value) - elif pull in (Pull.UP, Pull.DOWN): - self._pin.init(Pin.OUT, pull=pull, value=value) - else: - raise AttributeError("pull parameter is unrecognized") - - def switch_to_input(self, pull=None): # pylint: disable=unused-argument - """change pin into input""" - self._pin.init(Pin.IN) - - @property - def value(self) -> bool: - """the value of the pin""" - return self._pin.value() - - @value.setter - def value(self, val): - self._pin.value(val) - - def __del__(self): - del self._pin diff --git a/circuitpython_nrf24l01/wrapper/upy_spi.py b/circuitpython_nrf24l01/wrapper/upy_spi.py deleted file mode 100644 index c51ebc7..0000000 --- a/circuitpython_nrf24l01/wrapper/upy_spi.py +++ /dev/null @@ -1,80 +0,0 @@ -# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT -""" -This module adds MicroPython support via a wrapper class that adds -context management to a `machine.SPI` object. -""" -from . import DigitalInOut - - -class SPIDevice: - """ - Represents a single SPI device and manages locking the bus and the device - address. - - :param ~machine.SPI spi: The SPI bus the device is on - :param ~machine.Pin chip_select: The chip select pin number. - :param int extra_clocks: The minimum number of clock cycles to cycle the - bus after CS is high. (Used for SD cards.) - - Example: - - .. code-block:: python - - import machine - from circuitpython_nrf24l01.upy_wrapper import SPIDevice, DigitalInOut - - spi_bus = machine.SPI(SCK, MOSI, MISO) - cs = DigitalInOut(10) - device = SPIDevice(spi_bus, cs) - bytes_read = bytearray(4) - # The object assigned to spi in the with statements below - # is the original spi_bus object. We are using the machine.SPI - # operations machine.SPI.readinto() and machine.SPI.write(). - with device as spi: - spi.readinto(bytes_read) - # A second transaction - with device as spi: - spi.write(bytes_read) - """ - - def __init__( - self, - spi, - chip_select=None, - *, - baudrate=100000, - polarity=0, - phase=0, - extra_clocks=0 - ): - self.spi = spi - self.baudrate = baudrate - self.polarity = polarity - self.phase = phase - self.extra_clocks = extra_clocks - self.chip_select = chip_select - if isinstance(chip_select, int): - self.chip_select = DigitalInOut(chip_select) - if self.chip_select: - self.chip_select.switch_to_output(value=True) - - def __enter__(self): - self.spi.init(baudrate=self.baudrate, polarity=self.polarity, phase=self.phase) - if self.chip_select: - self.chip_select.value = False - return self.spi - - def __exit__(self, exc_type, exc_val, exc_tb): - if self.chip_select: - self.chip_select.value = True - if self.extra_clocks > 0: - buf = bytearray([0xFF]) - clocks = self.extra_clocks // 8 - if self.extra_clocks % 8 != 0: - clocks += 1 - for _ in range(clocks): - self.spi.write(buf) - self.spi.deinit() - return False diff --git a/docs/_static/custom_material.css b/docs/_static/custom_material.css index 8fe3d8d..0ede382 100644 --- a/docs/_static/custom_material.css +++ b/docs/_static/custom_material.css @@ -13,12 +13,13 @@ background-color: hsl(301, 100%, 63%); } -.md-typeset .admonition.seealso { - border-left: .2rem solid hsl(301, 100%, 63%); +.md-typeset :is(.admonition):is(.seealso) { + border-color: hsl(301, 100%, 63%); } -.md-typeset .admonition.seealso>.admonition-title { - background-color: hsla(287, 100%, 63%, 0.25); +.md-typeset :is(.seealso)> :is(.admonition-title) { + background-color: hsla(287, 100%, 63%, 0.1); + border-color: hsl(301, 100%, 63%); } .md-typeset .admonition.important>.admonition-title::before { @@ -26,24 +27,26 @@ background-color: hsl(123, 100%, 63%); } -.md-typeset .admonition.important { - border-left: .2rem solid hsl(123, 100%, 63%); +.md-typeset :is(.admonition):is(.important) { + border-color: hsl(123, 100%, 63%); } -.md-typeset .admonition.important>.admonition-title { - background-color: hsla(123, 100%, 63%, 0.25); +.md-typeset :is(.important) > :is(.admonition-title) { + background-color: hsla(123, 100%, 63%, 0.1); + border-color: hsl(123, 100%, 63%); } .md-typeset .admonition.warning>.admonition-title::before { background-color: hsl(0, 100%, 63%); } -.md-typeset .admonition.warning { - border-left: .2rem solid hsl(0, 100%, 63%); +.md-typeset :is(.admonition):is(.warning) { + border-color: hsl(0, 100%, 63%); } -.md-typeset .admonition.warning>.admonition-title { - background-color: hsla(0, 100%, 63%, 0.25); +.md-typeset :is(.warning) > :is(.admonition-title) { + background-color: hsla(0, 100%, 63%, 0.1); + border-color: hsl(0, 100%, 63%); } html .md-nav--primary .md-nav__title--site .md-nav__button { @@ -66,16 +69,18 @@ html .md-nav--primary .md-nav__title--site .md-nav__button { --md-code-bg-color: #e8e7e7; } -.md-nav__title .md-nav__button.md-logo img, .md-nav__title .md-nav__button.md-logo svg { +.md-nav__title .md-nav__button.md-logo img, +.md-nav__title .md-nav__button.md-logo svg { height: 3rem; width: auto; } -.md-header__button.md-logo img, .md-header__button.md-logo svg { +.md-header__button.md-logo img, +.md-header__button.md-logo svg { width: auto; } -.linenos { - background-color: var(--md-default-bg-color--light); - margin-right: 0.5rem; -} +thead { + background-color: var(--md-default-fg-color--light); + color: var(--md-primary-bg-color); +} \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 2eeca13..4bc10fe 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -27,6 +27,7 @@ # autodoc module docs will fail to generate with a warning. autodoc_mock_imports = ["digitalio", "busio", "microcontroller"] autodoc_member_order = "bysource" +autodoc_typehints_format = "short" intersphinx_mapping = { "python": ("https://docs.python.org/3", None), @@ -110,6 +111,7 @@ "navigation.top", # "search.highlight", "search.share", + "navigation.tabs.sticky", ], "palette": [ { @@ -137,13 +139,17 @@ "repo_url": "https://github.com/nRF24/CircuitPython_nRF24L01/", "repo_name": "CircuitPython_nRF24L01", "repo_type": "github", - # Visible levels of the global TOC; -1 means unlimited - "globaltoc_depth": -1, # If False, expand all TOC entries "globaltoc_collapse": False, - # If True, show hidden TOC entries - "globaltoc_includehidden": True, + "toc_title_is_page_title": True, } + +# turn off some features specific to sphinx-immaterial theme +object_description_options = [ + ("py:.*", dict(include_fields_in_toc=False, generate_synopses=None)), + ("py:parameter", dict(include_in_toc=False)), +] + # Set link name generated in the top bar. html_title = "CircuitPython_nRF24L01" @@ -177,16 +183,16 @@ latex_elements = { # # The paper size ('letterpaper' or 'a4paper'). - 'papersize': 'letterpaper', + "papersize": "letterpaper", # # The font size ('10pt', '11pt' or '12pt'). - 'pointsize': '10pt', + "pointsize": "10pt", # # Additional stuff for the LaTeX preamble. - 'preamble': '', + "preamble": "", # # Latex figure (float) alignment - 'figure_align': 'htbp', + "figure_align": "htbp", } # Grouping the document tree into LaTeX files. List of tuples diff --git a/docs/core_api/advanced_api.rst b/docs/core_api/advanced_api.rst index 6d9feac..9d288f7 100644 --- a/docs/core_api/advanced_api.rst +++ b/docs/core_api/advanced_api.rst @@ -81,9 +81,9 @@ Advanced RF24 API 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 + on the CE pin is achieved. 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 + wait time to be managed by the MCU in cases of asynchronous 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. @@ -108,7 +108,7 @@ Advanced RF24 API 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. + calculating an adequate transmission timeout sentinel. .. versionadded:: 1.2.0 ``write_only`` parameter @@ -158,7 +158,7 @@ Advanced RF24 API .. 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. The state of - the CE pin determines which Standby mode is acheived. See `Chapter 6.1.2-7 of the + the CE pin determines which Standby mode is achieved. See `Chapter 6.1.2-7 of the nRF24L01+ Specifications Sheet `_ for more details. @@ -179,7 +179,7 @@ Advanced RF24 API :param int index: the number of the data pipe whose address is to be returned. A valid index ranges [0,5] for RX addresses or any negative number for the TX address. - Otherwise an `IndexError` is thown. This parameter defaults to ``-1``. + Otherwise an `IndexError` is thrown. This parameter defaults to ``-1``. .. versionadded:: 1.2.0 @@ -191,7 +191,7 @@ Advanced RF24 API .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.is_plus_variant - This information is detirmined upon instantiation. + This information is determined upon instantiation. .. versionadded:: 1.2.0 @@ -292,9 +292,13 @@ Debugging Output Defaults to an empty string. :Returns: - A string of hexidecimal characters in big endian form of the + A string of hexadecimal characters in big endian form of the specified ``buf`` parameter. + .. versionchanged:: 2.1.0 + Added parameters ``reverse`` and ``delimit`` as this function proved vital to + debugging and developing `RF24NetworkHeader` & `RF24NetworkFrame`. + Status Byte ****************************** @@ -317,10 +321,10 @@ Status Byte - `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. + usually this means the flag has been reset. .. important:: It is recommended that this flag is only used when the IRQ pin is active. - To detirmine if there is a payload in the RX FIFO, use `fifo()`, `any()`, or `pipe`. + To determine if there is a payload in the RX FIFO, use `fifo()`, `any()`, or `pipe`. Notice that calling `read()` also resets this status flag. Pass ``data_recv`` |irq note| @@ -333,9 +337,9 @@ Status Byte :Returns: - - `True` signifies the nRF24L01 attemped all configured retries + - `True` signifies the nRF24L01 attempted all configured retries - `False` represents anything depending on context (state/condition); usually this - means the flag's been reset. + means the flag has been reset. .. important:: This can only return `True` if `auto_ack` is enabled, otherwise this will always be `False`. @@ -352,7 +356,7 @@ Status Byte - `True` represents a successful transmission - `False` represents anything depending on context (state/condition of FIFO buffers); - usually this means the flag's been reset. + usually this means the flag has been reset. Pass ``data_sent`` |irq note| @@ -364,15 +368,15 @@ Status Byte 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 `available()`, `send()`, and `resend()` - functions. + `tx_full` attributes. Internally this is a helper function to `available()`, `send()`, + and `resend()` functions. :returns: `True` for every call. This value is meant to allow this function to be used in `if` or `while` *in conjunction with* attributes related to the refreshed status byte. .. versionchanged:: 1.2.3 - arbitrarily returns `True` + Arbitrarily returns `True`. .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pipe @@ -394,18 +398,18 @@ Status Byte Internally, this is automatically called by `send()`, `write()`, `read()`. - :param bool data_recv: specifies wheather to clear the "RX Data Ready" + :param bool data_recv: specifies whether to clear the "RX Data Ready" (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr`) flag. - :param bool data_sent: specifies wheather to clear the "TX Data Sent" + :param bool data_sent: specifies whether to clear the "TX Data Sent" (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_ds`) flag. - :param bool data_fail: specifies wheather to clear the "Max Re-transmit reached" + :param bool data_fail: specifies whether to clear the "Max Re-transmit reached" (`irq_df`) 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 whether 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 + the status 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 @@ -480,12 +484,12 @@ Ambiguous Signal Detection 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 on a separate nRF24L01 transceiver: .. code-block:: python # declare objects for SPI bus and CSN pin and CE pin - nrf. = RF24(spi, csn, ce) + 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 diff --git a/docs/core_api/basic_api.rst b/docs/core_api/basic_api.rst index e647145..1ff4b03 100644 --- a/docs/core_api/basic_api.rst +++ b/docs/core_api/basic_api.rst @@ -82,7 +82,7 @@ Basic RF24 API 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 mode (CE pin is LOW meaning low current & no transmissions) which is ideal - for post-reception work. Disabing RX mode doesn't flush the RX FIFO buffers, so + for post-reception work. Disabling RX mode doesn't flush the RX FIFO buffers, so remember to flush your 3-level FIFO buffers when appropriate using `flush_tx()` or `flush_rx()` (see also the `read()` function). @@ -104,7 +104,7 @@ Basic RF24 API .. automethod:: circuitpython_nrf24l01.rf24.RF24.available - This function is provided for convenience and is synonomous with the following statement: + This function is provided for convenience and is synonymous with the following statement: .. code-block:: python @@ -115,7 +115,7 @@ Basic RF24 API .. automethod:: circuitpython_nrf24l01.rf24.RF24.read - The `irq_dr` status flag is reset autmotically. This function can also be used to fetch + The `irq_dr` status flag is reset automatically. 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 diff --git a/docs/core_api/ble_api.rst b/docs/core_api/ble_api.rst index ff5939c..b0388e7 100644 --- a/docs/core_api/ble_api.rst +++ b/docs/core_api/ble_api.rst @@ -33,7 +33,7 @@ here has been adapted to work with CircuitPython. Use the helper function :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.len_available()` to - detirmine if your payload can be transmit. + determine 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. We have provided a tuple of these specific channels for convenience (See `BLE_FREQ` and `hop_channel()`). @@ -45,7 +45,7 @@ here has been adapted to work with CircuitPython. 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 + acknowledgment) feature of the nRF24L01 is useless when transmitting 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`) features @@ -114,10 +114,10 @@ here has been adapted to work with CircuitPython. :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 + represents a degree who's coefficient is 1. BLE specifications 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 + will use while shifting in the buffer data. BLE specifications require ``0x555555`` (default value). :returns: A 24-bit `bytearray` representing the checksum of the data (in proper little endian). @@ -128,7 +128,7 @@ here has been adapted to work with CircuitPython. `FakeBLE` class to allow whitening and dewhitening a BLE payload without the hardcoded coefficient. - :param bytes,bytearray data: The BLE payloads data. This data should include the + :param bytes,bytearray buf: The BLE payloads data. This data should include the CRC24 checksum. :param int coef: The whitening coefficient used to avoid repeating binary patterns. This is the index of `BLE_FREQ` tuple for nRF24L01 channel that the payload transits @@ -180,7 +180,7 @@ FakeBLE class .. seealso:: For all parameters' descriptions, see the - :py:class:`~circuitpython_nrf24l01.rf24.RF24` class' contructor documentation. + :py:class:`~circuitpython_nrf24l01.rf24.RF24` class' constructor documentation. .. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.mac @@ -227,7 +227,7 @@ FakeBLE class This is done according to BLE specifications. :param bytearray,bytes data: The packet to whiten. - :returns: A `bytearray` of the ``data`` with the whitening algorythm + :returns: A `bytearray` of the ``data`` with the whitening algorithm applied. .. note:: `advertise()` and @@ -243,7 +243,7 @@ FakeBLE class .. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.len_available - This is detirmined from the current state of `name` and `show_pa_level` + This is determined from the current state of `name` and `show_pa_level` attributes. :param bytearray,bytes hypothetical: Pass a potential `chunk()` of @@ -403,7 +403,7 @@ Service data UUID numbers ************************* These are the 16-bit UUID numbers used by the -`Derivitive Children of the ServiceData class `_ +`Derivative Children of the ServiceData class `_ .. autodata:: circuitpython_nrf24l01.fake_ble.TEMPERATURE_UUID :annotation: = 0x1809 @@ -412,7 +412,7 @@ These are the 16-bit UUID numbers used by the .. autodata:: circuitpython_nrf24l01.fake_ble.EDDYSTONE_UUID :annotation: = 0xFEAA -Derivitive Children +Derivative Children ******************* .. autoclass:: circuitpython_nrf24l01.fake_ble.TemperatureServiceData diff --git a/docs/core_api/configure_api.rst b/docs/core_api/configure_api.rst index 07e58a9..f5e7af7 100644 --- a/docs/core_api/configure_api.rst +++ b/docs/core_api/configure_api.rst @@ -57,7 +57,7 @@ Configurable RF24 API The procedure for handling :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr` IRQ should be: - 1. retreive the payload from RX FIFO using `read()` + 1. retrieve the payload from RX FIFO using `read()` 2. clear :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr` status flag (taken care of by using `read()` in previous step) 3. read FIFO_STATUS register to check if there are more payloads available in RX FIFO @@ -83,12 +83,12 @@ Configurable RF24 API determine if 250 kbps is supposed to be supported. .. hint:: - You can perform a carrier wave test on 250 kbps to see if you transceiver hardware + You can perform a carrier wave test on 250 kbps to see if your transceiver hardware does support that data rate. See `start_carrier_wave()`, `stop_carrier_wave()`, and `rpd` to execute a hardware test. .. versionchanged:: 2.2.0 - Blindly allow confiuring the radio for 250 kbps as support is marginally dependent + Blindly allow configuring the radio for 250 kbps as support is marginally dependent on the hardware being used. .. autoattribute:: circuitpython_nrf24l01.rf24.RF24.channel diff --git a/docs/examples.rst b/docs/examples.rst index 7599fd7..0814c08 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -52,7 +52,7 @@ This example shows how use a group of 6 nRF24L01 transceivers to transmit to 1 n transceiver. This technique is called `"Multiceiver" in the nRF24L01 Specifications Sheet `_ -.. note:: This example follows the diagram illistrated in +.. note:: This example follows the diagram illustrated in `figure 12 of section 7.7 of the nRF24L01 Specifications Sheet `_ Please note that if `auto_ack` (on the base station) and `arc` (on the @@ -83,7 +83,7 @@ Scanner Example .. versionadded:: 2.0.0 -This example simply scans the entire RF frquency (2.4 GHz to 2.525 GHz) +This example simply scans the entire RF frequency (2.4 GHz to 2.525 GHz) and outputs a vertical graph of how many signals (per :py:attr:`~circuitpython_nrf24l01.rf24.RF24.channel`) were detected. This example can be used to find a frequency with the least ambient interference from other @@ -261,7 +261,7 @@ To make this circuitpython library compatible with This will help you comprehend how to configure :py:attr:`~circuitpython_nrf24l01.rf24.RF24.payload_length`. -For completness, TMRh20's RF24 library uses a default value of 15 for the `ard` attribute, +For completeness, TMRh20's RF24 library uses a default value of 15 for the `ard` attribute, but this Circuitpython library uses a default value of 3. .. csv-table:: Corresponding examples @@ -278,7 +278,7 @@ but this Circuitpython library uses a default value of 3. nrf24l01_fake_ble_test, "feature is available via `floe's BTLE library `_" "nrf24l01_network_test (\ [2]_)", "- all RF24Network examples except Network_Ping & Network_Ping_Sleep - all RF24Mesh examples except RF24Mesh_Example_Node2NodeExtra - (which may still work but the data is not interpretted as a string)" + (which may still work but the data is not interpreted as a string)" .. [1] Some of the Circuitpython examples (that are compatible with TMRh20's examples) contain 2 or 3 lines of code that are commented out for easy modification. These lines diff --git a/docs/index.rst b/docs/index.rst index e9e9806..9064fff 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,4 +1,4 @@ - +:hide-navigation: .. toctree:: :hidden: @@ -44,8 +44,8 @@ .. only:: html - .. image:: https://open.vscode.dev/badges/open-in-vscode.svg - :target: https://open.vscode.dev/2bndy5/CircuitPython_nRF24L01 + .. image:: https://img.shields.io/static/v1?label=Visual%20Studio%20Code&message=Use%20Online%20IDE&color=blue&logo=visualstudiocode&logoColor=3f9ae6 + :target: https://vscode.dev/github/nRF24/CircuitPython_nRF24L01 :alt: Open in Visual Studio Code .. image:: https://img.shields.io/badge/Gitpod-Use%20Online%20IDE-B16C04?logo=gitpod @@ -99,11 +99,11 @@ Features currently supported * 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 `_. -* fake BLE module for sending BLE beacon advertisments from the nRF24L01 as outlined by +* fake BLE module for sending BLE beacon advertisements from the nRF24L01 as outlined by `Dmitry Grinberg in his write-up (including C source code) `_. * Multiceiver\ :sup:`TM` mode (up to 6 TX nRF24L01 "talking" to 1 RX nRF24L01 simultaneously). See the `Multiceiver Example `_ -* Networking capability that allows up to 781 tranceivers to interact with each other. +* Networking capability that allows up to 781 transceivers to interact with each other. * This does not mean the radio can connect to WiFi. The networking implementation is a custom protocol ported from TMRh20's RF24Network & RF24Mesh libraries. @@ -121,7 +121,7 @@ This driver depends on: .. tip:: Use CircuitPython v6.3.0 or newer because faster SPI execution yields faster transmissions. -* The `SpiDev `_ module is a C-extention that executes +* The `SpiDev `_ module is a C-extension that executes SPI transactions faster than Adafruit's PureIO library (a dependency of the `Adafruit_Blinka library `_). @@ -188,8 +188,8 @@ to the MCU via a digital input pin during the interrupt example. .. 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. + 100 microfarads (+ another optional 0.1 microfarads capacitor for added stability) connected + in parallel to the VCC and GND pins. .. important:: The nRF24L01's VCC pin is not 5V compliant. All other nRF24L01 pins *should* be 5V compliant, but it is safer to assume they are not. @@ -238,7 +238,7 @@ For CircuitPython devices For CPython in Linux --------------------------- -1. Clone the library repository, then navigate to the reository's example directory. +1. Clone the library repository, then navigate to the repository's example directory. .. code-block:: shell @@ -256,9 +256,9 @@ What to purchase See the following links to Sparkfun or just google "nRF24L01+". - * `2.4GHz Transceiver IC - nRF24L01+ `_ - * `SparkFun Transceiver Breakout - nRF24L01+ `_ - * `SparkFun Transceiver Breakout - nRF24L01+ (RP-SMA) `_ +* `2.4GHz Transceiver IC - nRF24L01+ `_ +* `SparkFun Transceiver Breakout - nRF24L01+ `_ +* `SparkFun Transceiver Breakout - nRF24L01+ (RP-SMA) `_ 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 @@ -274,8 +274,8 @@ Power Stability ------------------- If you're not using a dedicated 3V regulator to supply power to the nRF24L01, -then adding capcitor(s) (100 µF + an optional 0.1µF) in parrellel (& as close -as possible) to the VCC and GND pins is highly recommended. Stablizing the power +then adding capacitor(s) (100 µF + an optional 0.1µF) in parallel (& as close +as possible) to the VCC and GND pins is highly recommended. Stabilizing the power input provides significant performance increases. More finite details about the nRF24L01 are available from the datasheet (referenced here in the documentation as the `nRF24L01+ Specification Sheet `_. Besure that the ``graphiz/bin`` +`Graphviz downloads page `_. Be sure that the ``graphviz/bin`` directory is in the ``PATH`` environment variable (there's an option in the installer for this). After Graphviz is installed, reboot the PC so the updated ``PATH`` environment variable takes affect. diff --git a/docs/network_docs/mesh_api.rst b/docs/network_docs/mesh_api.rst index 912f965..5fd9a85 100644 --- a/docs/network_docs/mesh_api.rst +++ b/docs/network_docs/mesh_api.rst @@ -22,10 +22,11 @@ RF24MeshNoMaster class It is the python equivalent to TMRh20's ``MESH_NO_MASTER`` macro in the C++ RF24Mesh library. All the API is the same as `RF24Mesh` class. - :param int node_id: The unique identifying `node_id` number for the instantiated mesh node. + :param int node_id: The unique identifying :attr:`~circuitpython_nrf24l01.rf24_mesh.RF24Mesh.node_id` + number for the instantiated mesh node. .. seealso:: For all parameters' descriptions, see the - :py:class:`~circuitpython_nrf24l01.rf24.RF24` class' contructor documentation. + :py:class:`~circuitpython_nrf24l01.rf24.RF24` class' constructor documentation. RF24Mesh class @@ -37,7 +38,7 @@ RF24Mesh class :param int node_id: The unique identifying `node_id` number for the instantiated mesh node. .. seealso:: For all parameters' descriptions, see the - :py:class:`~circuitpython_nrf24l01.rf24.RF24` class' contructor documentation. + :py:class:`~circuitpython_nrf24l01.rf24.RF24` class' constructor documentation. Basic API ********* @@ -54,6 +55,9 @@ Basic API then you can use :meth:`~circuitpython_nrf24l01.rf24_mesh.RF24Mesh.write()` for quicker operation. + :param int to_node: The unique mesh network `node_id` of the frame's destination. + Defaults to ``0`` (which is reserved for the master node). + :param str,int message_type: The `int` that describes the frame header's `message_type`. :param bytes,bytearray message: The frame's `message` to be transmitted. .. note:: @@ -61,9 +65,6 @@ Basic API `fragmentation` is disabled. If `fragmentation` is enabled (it is by default), then the message's size must be less than :attr:`~circuitpython_nrf24l01.rf24_network.RF24Network.max_message_length`. - :param str,int message_type: The `int` that describes the frame header's `message_type`. - :param int to_node_id: The unique mesh network `node_id` of the frame's destination. - Defaults to ``0`` (which is reserved for the master node. :Returns: @@ -143,7 +144,7 @@ Advanced API .. automethod:: circuitpython_nrf24l01.rf24_mesh.RF24Mesh.write - :param int to_node_address: The network node's :ref:`Logical Address `. + :param int to_node: The network node's :ref:`Logical Address `. of the frame's destination. This must be the destination's network `node_address` which is not be confused with a mesh node's `node_id`. :param str,int message_type: The `int` that describes the frame header's `message_type`. @@ -200,14 +201,25 @@ Advanced API Calling this function on a Linux device (like the Raspberry Pi) will save the `dhcp_dict` to a JSON file located in the program's working directory. - :param str filename: The name of the json file to be used. This value should end in a ".json" + :param str filename: The name of the json file to be used. This value should include a file extension + (like ".json" or ".txt"). + :param bool as_bin: Set this parameter to `True` to save the DHCP list to a binary text file. + Defaults to `False` which saves the DHCP list as JSON syntax. + + .. versionchanged:: 2.1.1 + Added ``as_bin`` parameter to make use of binary text files. .. automethod:: circuitpython_nrf24l01.rf24_mesh.RF24Mesh.load_dhcp - :param str filename: The name of the json file to be used. This value should end in a ".json" + :param str filename: The name of the json file to be used. This value should include a file extension + (like ".json" or ".txt"). + :param bool as_bin: Set this parameter to `True` to load the DHCP list from a binary text file. + Defaults to `False` which loads the DHCP list from JSON syntax. .. warning:: This function will raise an `OSError` exception if no file exists. + .. versionchanged:: 2.1.1 + Added ``as_bin`` parameter to make use of binary text files. .. automethod:: circuitpython_nrf24l01.rf24_mesh.RF24Mesh.set_address diff --git a/docs/network_docs/network_api.rst b/docs/network_docs/network_api.rst index 915edfa..d6d8f7f 100644 --- a/docs/network_docs/network_api.rst +++ b/docs/network_docs/network_api.rst @@ -29,7 +29,7 @@ RF24NetworkRoutingOnly class .. seealso:: For all other parameters' descriptions, see the - :py:class:`~circuitpython_nrf24l01.rf24.RF24` class' contructor documentation. + :py:class:`~circuitpython_nrf24l01.rf24.RF24` class' constructor documentation. RF24Network class ***************** @@ -41,7 +41,7 @@ RF24Network class .. seealso:: For all other parameters' descriptions, see the - :py:class:`~circuitpython_nrf24l01.rf24.RF24` class' contructor documentation. + :py:class:`~circuitpython_nrf24l01.rf24.RF24` class' constructor documentation. Basic API ********* @@ -56,7 +56,7 @@ Basic API .. warning:: - 1. If this attribute is set to an invald network + 1. If this attribute is set to an invalid network :ref:`Logical Address `, then nothing is done and the invalid address is ignored. 2. A `RF24Mesh` object cannot set this attribute because the @@ -68,7 +68,7 @@ Basic API .. automethod:: circuitpython_nrf24l01.rf24_network.RF24Network.update .. important:: - It is imperitive that this function be called at least once during the application's main + It is imperative that this function be called at least once during the application's main loop. For applications that perform long operations on each iteration of its main loop, it is encouraged to call this function more than once when possible. @@ -177,7 +177,7 @@ Advanced API .. note:: This function will always return `True` if the ``traffic_direct`` parameter is set to anything other than its default value. Using the ``traffic_direct`` parameter assumes - there is a relaible/open connection to the `node_address` passed to ``traffic_direct``. + there is a reliable/open connection to the `node_address` passed to ``traffic_direct``. .. tip:: |use_msg_t| .. autoattribute:: circuitpython_nrf24l01.rf24_network.RF24Network.parent diff --git a/docs/network_docs/shared_api.rst b/docs/network_docs/shared_api.rst index 8179831..d193bd0 100644 --- a/docs/network_docs/shared_api.rst +++ b/docs/network_docs/shared_api.rst @@ -1,12 +1,12 @@ Shared Networking API ====================== -Order of Inheritence +Order of Inheritance ******************** .. graphviz:: - digraph inheritence { + digraph inheritance { bgcolor="#323232A1" fontcolor="#FEF9A9" fontsize=16 @@ -133,7 +133,7 @@ The following list of `RF24` functions and attributes are exposed in the .. code-block:: python - >>> # following command is the same as `nrf.print_details(0, 1)` + >>> # the following command is the same as `nrf.print_details(0, 1)` >>> nrf.print_details(dump_pipes=False, network_only=True) Network frame_buf contents: Header is from 0o7777 to 0o0 type 0 id 1 reserved 0. Message contains: @@ -144,12 +144,12 @@ The following list of `RF24` functions and attributes are exposed in the Network fragmentation______Enabled Network max message length_144 bytes Network TX timeout_________25 milliseconds - Network Rounting timeout___75 milliseconds + Network Routing timeout___75 milliseconds Network node address_______0o0 .. note:: The address ``0o7777`` (seen in output above) is an invalid address used as a sentinel when - frame is unpopulated with a proper `from_node` address. + the frame is unpopulated with a proper `from_node` address. External Systems API ******************** diff --git a/docs/network_docs/structs.rst b/docs/network_docs/structs.rst index 0d3563a..378c9e8 100644 --- a/docs/network_docs/structs.rst +++ b/docs/network_docs/structs.rst @@ -140,7 +140,7 @@ FrameQueueFrag Logical Address Validation -------------------------- -.. automethod:: circuitpython_nrf24l01.network.structs.is_address_valid +.. autofunction:: circuitpython_nrf24l01.network.structs.is_address_valid :param int address: The :ref:`Logical Address ` to validate. diff --git a/docs/network_docs/topology.rst b/docs/network_docs/topology.rst index 1823d02..29dd67d 100644 --- a/docs/network_docs/topology.rst +++ b/docs/network_docs/topology.rst @@ -201,7 +201,7 @@ Physical addresses vs Logical addresses number ``0`` .. tip:: - Use the `is_address_valid()` function to programatically check a Logical Address for validity. + Use the `is_address_valid()` function to programmatically check a Logical Address for validity. .. note:: Remember that the nRF24L01 only has 6 data pipes for which to receive or transmit. @@ -212,7 +212,7 @@ Physical addresses vs Logical addresses Translating Logical to Physical ------------------------------- -Before translating the Logical address, a single byte is used reptitively as the +Before translating the Logical address, a single byte is used repetitively as the base case for all bytes of any Physical Address. This byte is the `address_prefix` attribute (stored as a mutable `bytearray`) in the `RF24Network` class. By default the `address_prefix` has a single byte value of ``b"\xCC"``. @@ -225,13 +225,13 @@ data pipe number and child node's most significant byte in its Physical Address. For example: The Logical Address of the network's master node is ``0``. The radio's pipes - 1-5 start with the `address_prefix`. To make each pipe's Phsyical address unique + 1-5 start with the `address_prefix`. To make each pipe's Physical address unique to a child node's Physical address, the `address_suffix` is used. The Logical address of the master node: ``0o0`` .. csv-table:: - :header: "pipe", "Phsyical Address (hexadecimal)" + :header: "pipe", "Physical Address (hexadecimal)" 1, ``CC CC CC CC 3C`` 2, ``CC CC CC CC 33`` @@ -242,7 +242,7 @@ For example: The Logical address of the master node's first child: ``0o1`` .. csv-table:: - :header: "pipe", "Phsyical Address (hexadecimal)" + :header: "pipe", "Physical Address (hexadecimal)" 1, ``CC CC CC 3C 3C`` 2, ``CC CC CC 3C 33`` @@ -253,7 +253,7 @@ For example: The Logical address of the master node's second child: ``0o2`` .. csv-table:: - :header: "pipe", "Phsyical Address (hexadecimal)" + :header: "pipe", "Physical Address (hexadecimal)" 1, ``CC CC CC 33 3C`` 2, ``CC CC CC 33 33`` @@ -264,7 +264,7 @@ For example: The Logical address of the master node's third child's second child's first child: ``0o123`` .. csv-table:: - :header: "pipe", "Phsyical Address (hexadecimal)" + :header: "pipe", "Physical Address (hexadecimal)" 1, ``CC 3C 33 CE 3C`` 2, ``CC 3C 33 CE 33`` @@ -362,3 +362,61 @@ snippets to use as a template for such a scenario. with network_b_node as net_b: net_b.update() net_b.send(RF24NetworkHeader(0, "T"), b"data for net B master") + +RF24Mesh connecting process +*************************** + +As noted above, a single network *can* have up to 781 nodes. This number also includes +up to 255 RF24Mesh nodes. The key difference from the user's perspective is that RF24Mesh +API does not use a `Logical Address `. Instead the RF24Mesh API relies on +a `node_id` number to identify a RF24Mesh node that may use a different +`Logical Address ` (which can change based on the node's physical location). + +.. important:: + Any network that will use RF24mesh for a child node needs to have a RF24Mesh + master node. This will not interfere with RF24Network nodes since the RF24Mesh API + is layered on top of the RF24Network API. + +To better explain the difference between a node's `node_address` vs a node's `node_id`, +we will examine the connecting process for a RF24Mesh node. These are the steps performed +when calling `renew_address()`: + +1. Any RF24Mesh node not connected to a network will use the `Logical Address ` + ``0o444`` (that's ``2340`` in decimal). It is up to the network administrator to ensure that + each RF24Mesh node has a unique `node_id` (which is limited to the range [0, 255]). + + .. hint:: + Remember that ``0`` is reserved the master node's `node_id`. +2. To get assigned a `Logical Address `, an unconnected node must poll the + network for a response (using a `NETWORK_POLL` message). Initially this happens on the + network level 0, but consecutive attempts will poll higher network levels (in order of low to + high) if this process fails. +3. When a polling transmission is responded, the connecting mesh node sends an address + request which gets forwarded to the master node when necessary (using a + `MESH_ADDR_REQUEST` message). +4. The master node will process the address request and respond with a `node_address` + (using a `MESH_ADDR_RESPONSE` message). If there is no available occupancy on the + network level from which the address request originated, then the master node will + respond with an invalid `Logical Address `. +5. Once the requesting node receives the address response (and the assigned address is + valid), it assumes that as the `node_address` while maintaining its `node_id`. + + - The connecting node will verify its new address by calling `check_connection`. + - If the assigned address is invalid or `check_connection()` returns `False`, then + the connecting node will re-start the process (step 1) on a different network level. + +Points of failure +----------------- + +This process happens over a span of a few milliseconds. However, + +- If the connecting node is physically moving throughout the network very quickly, + then this process will take longer and is likely to fail. +- If a master node is able to respond faster than the connecting node can prepare itself + to receive, then the process will fail entirely. This failure about faster master + nodes often results in some slower RF24Mesh nodes only being able to connect to the + network through another non-master node. + +If you run into trouble with this connection process, then please +`open an issue on github `_ +and describe the situation with as much detail as possible. diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index f950a2d..96f9982 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -1,3 +1,4 @@ +:hide-navigation: Troubleshooting info ~~~~~~~~~~~~~~~~~~~~ @@ -11,7 +12,7 @@ Attribute dependency The nRF24L01 has 3 key features. 1. `auto_ack` feature provides transmission verification by using the RX nRF24L01 to - automatically and immediatedly send an acknowledgment (ACK) packet in response to + automatically and immediately send an acknowledgment (ACK) packet in response to received payloads. `auto_ack` does not require :attr:`~circuitpython_nrf24l01.rf24.RF24.dynamic_payloads` to be enabled. @@ -38,9 +39,9 @@ The nRF24L01 has 3 key features. FIFO Capacity ************* -Remeber that the nRF24L01's FIFO (First-In, First-Out) buffers have 3 levels. This means that +Remember that the nRF24L01's FIFO (First-In, First-Out) buffers have 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). Notice there are seperate FIFO buffers sending & receiving (respectively mentioned +transmit (TX). Notice there are separate FIFO buffers sending & receiving (respectively mentioned in this documentation as TX FIFO & RX FIFO). Each of the 3 levels in the FIFO buffers can only store a *maximum* of 32 bytes. If you receive 2 payloads with a length of 4 bytes each, then there is only 1 level of the RX FIFO buffers left unoccupied. @@ -70,7 +71,7 @@ the same 6 nRF24L01 radios. Channels -------- -Finnaly, the radio's channel is not be confused with the radio's pipes. Channel selection +Finally, 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 @@ -80,7 +81,7 @@ frequencies. Settings that must Match ************************ -For successful transmissions, most of the endpoint trasceivers' settings/features must +For successful transmissions, most of the endpoint transceivers' settings/features must match. These settings/features include: * The RX pipe's address on the receiving nRF24L01 (passed to `open_rx_pipe()`) MUST match diff --git a/examples/nrf24l01_ack_payload_test.py b/examples/nrf24l01_ack_payload_test.py index 242e430..38aa0e4 100644 --- a/examples/nrf24l01_ack_payload_test.py +++ b/examples/nrf24l01_ack_payload_test.py @@ -89,8 +89,8 @@ def master(count=5): # count = 5 will only transmit 5 packets # print timer results upon transmission success print( "Transmission successful! Time to transmit:", - f"{int((end_timer - start_timer) / 1000)} us.", - "Sent: {}{}".format(buffer[:6].decode("utf-8"), counter[0]), + int((end_timer - start_timer) / 1000), + "us. Sent: {}{}".format(buffer[:6].decode("utf-8"), counter[0]), end=" ", ) if isinstance(result, bool): @@ -131,7 +131,7 @@ def slave(timeout=6): counter[0] = received[7:8][0] + 1 # the [:6] truncates the c-string NULL termiating char print( - f"Received {length} bytes on pipe {pipe_number}:", + "Received {} bytes on pipe {}:".format(length, pipe_number), "{}{}".format(received[:6].decode("utf-8"), received[7:8][0]), "Sent: {}{}".format(buffer[:6].decode("utf-8"), buffer[7:8][0]), ) diff --git a/examples/nrf24l01_context_test.py b/examples/nrf24l01_context_test.py index db47570..a99813f 100644 --- a/examples/nrf24l01_context_test.py +++ b/examples/nrf24l01_context_test.py @@ -74,7 +74,7 @@ # this is because the "with" statements load the existing settings # for the RF24 object specified after the word "with". -# NOTE it is not advised to manipulate seperate RF24 objects outside of the +# NOTE it is not advised to manipulate separate 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. diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py index e63fdd4..19d95bd 100644 --- a/examples/nrf24l01_fake_ble_test.py +++ b/examples/nrf24l01_fake_ble_test.py @@ -61,9 +61,9 @@ def _prompt(remaining): if remaining % 5 == 0 or remaining < 5: if remaining - 1: - print(remaining, "advertisments left to go!") + print(remaining, "advertisements left to go!") else: - print(remaining, "advertisment left to go!") + print(remaining, "advertisement left to go!") # create an object for manipulating the battery level data @@ -99,7 +99,7 @@ def master(count=50): # create an object for manipulating temperature measurements temperature_service = TemperatureServiceData() -# temperature's float data has up to 2 decimal places of percision +# temperature's float data has up to 2 decimal places of precision temperature_service.data = 42.0 @@ -156,7 +156,7 @@ def slave(timeout=6): if nrf.available(): result = nrf.read() print( - "recevied payload from MAC address", + "received payload from MAC address", address_repr(result.mac, delimit=":") ) if result.name is not None: @@ -168,6 +168,8 @@ def slave(timeout=6): print("\traw buffer:", address_repr(service_data, False, " ")) else: print("\t" + repr(service_data)) + nrf.listen = False + nrf.flush_rx() # discard any received raw BLE data def set_role(): diff --git a/examples/nrf24l01_interrupt_test.py b/examples/nrf24l01_interrupt_test.py index fe43f69..9213689 100644 --- a/examples/nrf24l01_interrupt_test.py +++ b/examples/nrf24l01_interrupt_test.py @@ -62,7 +62,11 @@ def _ping_and_prompt(): pass print("IRQ pin went active LOW.") nrf.update() # update irq_d? status flags - print(f"\tirq_ds: {nrf.irq_ds}, irq_dr: {nrf.irq_dr}, irq_df: {nrf.irq_df}") + print( + "\tirq_ds: {}, irq_dr: {}, irq_df: {}".format( + nrf.irq_ds, nrf.irq_dr, nrf.irq_df + ) + ) def master(): @@ -96,7 +100,7 @@ def master(): if nrf.fifo(False, False): # is RX FIFO full? print("Slave node should not be listening anymore.") else: - print("transmission succeeded, " "but slave node might still be listening") + print("transmission succeeded, but slave node might still be listening") else: print("Slave node was unresponsive.") @@ -116,7 +120,7 @@ def master(): def slave(timeout=6): # will listen for 6 seconds before timing out """Only listen for 3 payload from the master node""" - # setup radio to recieve pings, fill TX FIFO with ACK payloads + # setup radio to receive pings, fill TX FIFO with ACK payloads nrf.load_ack(b"Yak ", 1) nrf.load_ack(b"Back", 1) nrf.load_ack(b" ACK", 1) diff --git a/examples/nrf24l01_manual_ack_test.py b/examples/nrf24l01_manual_ack_test.py index 14fb8d8..e816aad 100644 --- a/examples/nrf24l01_manual_ack_test.py +++ b/examples/nrf24l01_manual_ack_test.py @@ -80,7 +80,7 @@ def master(count=5): # count = 5 will only transmit 5 packets print("send() failed or timed out") else: # sent successful; listen for a response nrf.listen = True # get radio ready to receive a response - timeout = time.monotonic_ns() + 200000000 # set sentinal for timeout + timeout = time.monotonic_ns() + 200000000 # set sentinel for timeout while not nrf.available() and time.monotonic_ns() < timeout: # this loop hangs for 200 ms or until response is received pass @@ -88,8 +88,8 @@ def master(count=5): # count = 5 will only transmit 5 packets end_timer = time.monotonic_ns() # stop timer print( "Transmission successful! Time to transmit:", - f"{int((end_timer - start_timer) / 1000)} us. Sent:", - "{}{}".format(buffer[:6].decode("utf-8"), counter[0]), + int((end_timer - start_timer) / 1000), + "us. Sent: {}{}".format(buffer[:6].decode("utf-8"), counter[0]), end=" ", ) if nrf.pipe is None: # is there a payload? @@ -102,7 +102,7 @@ def master(count=5): # count = 5 will only transmit 5 packets # save new counter from response counter[0] = received[7:8][0] print( - f"Receieved {length} bytes with pipe {pipe_number}:", + "Received {} bytes with pipe {}:".format(length, pipe_number), "{}{}".format(bytes(received[:6]).decode("utf-8"), counter[0]), ) count -= 1 @@ -130,7 +130,7 @@ def slave(timeout=6): result = nrf.send(b"World \0" + bytes([counter[0]])) nrf.listen = True # put the radio back in RX mode print( - f"Received {length} on pipe {pipe}:", + "Received {} on pipe {}:".format(length, pipe), "{}{}".format(bytes(received[:6]).decode("utf-8"), received[7:8][0]), end=" Sent: ", ) diff --git a/examples/nrf24l01_multiceiver_test.py b/examples/nrf24l01_multiceiver_test.py index c7f3d11..54b4498 100644 --- a/examples/nrf24l01_multiceiver_test.py +++ b/examples/nrf24l01_multiceiver_test.py @@ -59,7 +59,7 @@ def base(timeout=10): - """Use the nRF24L01 as a base station for lisening to all nodes""" + """Use the nRF24L01 as a base station for listening to all nodes""" # write the addresses to all pipes. for pipe_n, addr in enumerate(addresses): nrf.open_rx_pipe(pipe_n, addr) @@ -71,7 +71,7 @@ def base(timeout=10): # NOTE read() clears the pipe number and payload length data print("Received", nrf.any(), "on pipe", nrf.pipe, end=" ") node_id, payload_id = struct.unpack("= start_timer + interval: # its time to emmit - start_timer = now - count -= 1 - packets_sent[0] += 1 - #TMRh20's RF24Mesh examples use 1 long int containing a timestamp (in ms) - message = struct.pack("' for emitter role.\n" "*** Enter 'E 1' to emit fragmented messages.\n" ) - if IS_MESH : + if IS_MESH and THIS_NODE: if nrf.node_address == NETWORK_DEFAULT_ADDR: prompt += "!!! Mesh node not connected.\n" prompt += "*** Enter 'C' to connect to to mesh master node.\n" - user_input = input(prompt + "*** Enter 'Q' to quit example.\n") or "?" - user_input = user_input.split() + user_input = (input(prompt + "*** Enter 'Q' to quit example.\n") or "?").split() if user_input[0].upper().startswith("C"): print("Connecting to mesh network...", end=" ") result = nrf.renew_address(*[int(x) for x in user_input[1:2]]) is not None print(("assigned address " + oct(nrf.node_address)) if result else "failed.") return True if user_input[0].upper().startswith("I"): - idle(*[int(x) for x in user_input[1:2]]) + idle(*[int(x) for x in user_input[1:3]]) return True if user_input[0].upper().startswith("E"): emit(*[int(x) for x in user_input[1:5]]) diff --git a/examples/nrf24l01_scanner_test.py b/examples/nrf24l01_scanner_test.py index 0bef8c0..ac673ad 100644 --- a/examples/nrf24l01_scanner_test.py +++ b/examples/nrf24l01_scanner_test.py @@ -79,7 +79,7 @@ def scan(timeout=30): nrf.listen = 0 # end the RX session curr_channel = curr_channel + 1 if curr_channel < 125 else 0 - # ouptut the signal counts per channel + # output the signal counts per channel sig_cnt = signals[curr_channel] print( ("%X" % min(15, sig_cnt)) if sig_cnt else "-", @@ -97,7 +97,7 @@ def scan(timeout=30): def noise(timeout=1, channel=None): """print a stream of detected noise for duration of time. - :param int timeout: The number of seconds to scan for ambiant noise. + :param int timeout: The number of seconds to scan for ambient noise. :param int channel: The specific channel to focus on. If not provided, then the radio's current setting is used. """ diff --git a/examples/nrf24l01_simple_test.py b/examples/nrf24l01_simple_test.py index cd2617b..e227a01 100644 --- a/examples/nrf24l01_simple_test.py +++ b/examples/nrf24l01_simple_test.py @@ -72,7 +72,7 @@ def master(count=5): # count = 5 will only transmit 5 packets nrf.listen = False # ensures the nRF24L01 is in TX mode while count: - # use struct.pack to packetize your data + # use struct.pack to structure your data # into a usable payload buffer = struct.pack("