diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..3b23950
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,7 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text=auto
+
+# Explicitly declare text files you want to always be normalized and converted
+# to native line endings on checkout.
+*.py text eol=lf
+*.rst text eol=lf
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a5e4c87..6d3b348 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,68 +1,90 @@
name: Build CI
-on: [pull_request, push]
+on:
+ pull_request:
+ types: [opened, reopened]
+ push:
jobs:
test:
runs-on: ubuntu-latest
steps:
- - name: Dump GitHub context
- env:
- GITHUB_CONTEXT: ${{ toJson(github) }}
- run: echo "$GITHUB_CONTEXT"
- - name: Translate Repo Name For Build Tools filename_prefix
- id: repo-name
- run: |
- echo ::set-output name=repo-name::$(
- echo ${{ github.repository }} |
- awk -F '\/' '{ print tolower($2) }' |
- tr '_' '-'
- )
- - name: Translate Repo Name For Build Tools package_prefix
- id: pkg-name
- run: |
- echo ::set-output name=pkg-name::$(
- echo ${{ github.repository }} |
- awk -F '\/' '{ print tolower($2) }' |
- tr '-' '_'
- )
- - name: Set up Python 3.7
- uses: actions/setup-python@v1
- with:
- python-version: 3.7
- - name: Versions
- run: |
- python3 --version
- - name: Checkout Current Repo
- uses: actions/checkout@v1
- with:
- submodules: true
- - name: Checkout tools repo
- uses: actions/checkout@v2
- with:
- repository: adafruit/actions-ci-circuitpython-libs
- path: actions-ci
- - name: Install deps
- run: |
- source actions-ci/install.sh
-
- - name: Pip install pylint, black, & Sphinx stuff
- run: |
- pip install pylint black
- pip install -r docs/requirements.txt
- - name: Library version
- run: git describe --dirty --always --tags
- - name: PyLint
- run: |
- pylint --disable=too-many-instance-attributes,too-many-public-methods,duplicate-code circuitpython_nrf24l01/*.py
- ([[ ! -d "examples" ]] || pylint --disable=invalid-name,duplicate-code $( find . -path "./examples/*.py" ))
- - name: Build assets
- run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . --package_folder_prefix ${{ steps.pkg-name.outputs.pkg-name }}
- - name: Archive bundles
- uses: actions/upload-artifact@v2
- with:
- name: bundles
- path: ${{ github.workspace }}/bundles/
- - name: Build docs
- working-directory: docs
- run: sphinx-build -E -W -b html . _build/html
+ - name: Dump GitHub context
+ env:
+ GITHUB_CONTEXT: ${{ toJson(github) }}
+ run: echo "$GITHUB_CONTEXT"
+
+ - name: Translate Repo Name For Build Tools filename_prefix
+ id: repo-name
+ run: |
+ echo ::set-output name=repo-name::$(
+ echo ${{ github.repository }} |
+ awk -F '\/' '{ print tolower($2) }' |
+ tr '_' '-'
+ )
+
+ - name: Translate Repo Name For Build Tools package_prefix
+ id: pkg-name
+ run: |
+ echo ::set-output name=pkg-name::$(
+ echo ${{ github.repository }} |
+ awk -F '\/' '{ print tolower($2) }' |
+ tr '-' '_'
+ )
+
+ - name: Set up Python 3.7
+ uses: actions/setup-python@v1
+ with:
+ python-version: 3.7
+
+ - name: Versions
+ run: |
+ python3 --version
+
+ - name: Checkout Current Repo
+ uses: actions/checkout@v1
+ with:
+ submodules: true
+
+ - name: Checkout tools repo
+ uses: actions/checkout@v2
+ with:
+ repository: adafruit/actions-ci-circuitpython-libs
+ path: actions-ci
+
+ - name: Install deps
+ run: |
+ source actions-ci/install.sh
+
+ - name: Pip install pylint & Sphinx stuff (and graphviz)
+ run: |
+ pip install --upgrade setuptools twine wheel pylint
+ pip install -r docs/requirements.txt
+ sudo apt install graphviz
+
+
+ - name: Library version
+ run: git describe --dirty --always --tags
+
+ - name: PyLint
+ run: |
+ pylint circuitpython_nrf24l01/*.py circuitpython_nrf24l01/*/*.py
+ pylint --disable=c-extension-no-member examples/*.py
+
+ - 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: Archive bundles
+ uses: actions/upload-artifact@v2
+ with:
+ name: bundles
+ path: ${{ github.workspace }}/bundles/
+
+ - name: Build docs
+ working-directory: docs
+ run: sphinx-build -E -W -b html . _build/html
+
+ - name: Check package distribution
+ run: |
+ python setup.py sdist
+ twine check dist/*
diff --git a/.pylintrc b/.pylintrc
index a95e6a0..e579e3a 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -51,8 +51,16 @@ 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=import-error,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call
-disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error
+disable=print-statement,
+ duplicate-code,
+ import-error,
+ bad-whitespace,
+ bad-continuation,
+ consider-using-f-string,
+ too-few-public-methods,
+ too-many-instance-attributes,
+ too-many-public-methods,
+ unnecessary-dict-index-lookup
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
@@ -156,7 +164,7 @@ ignored-classes=optparse.Values,thread._local,_thread._local
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
-ignored-modules=board
+ignored-modules=board,micropython
# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
@@ -182,7 +190,7 @@ allow-global-unused-variables=yes
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
-callbacks=cb_,_cb
+callbacks=cb_,_cb,_callback
# A regular expression matching the name of dummy variables (i.e. expectedly
# not used).
@@ -217,7 +225,7 @@ indent-after-paren=4
indent-string=' '
# Maximum number of characters on a single line.
-max-line-length=100
+max-line-length=88
# Maximum number of lines in a module
max-module-lines=1000
@@ -395,7 +403,7 @@ valid-metaclass-classmethod-first-arg=mcs
[DESIGN]
# Maximum number of arguments for function / method
-max-args=5
+max-args=6
# Maximum number of attributes for a class (see R0902).
# max-attributes=7
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 8ebe42a..e85bae7 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -4,4 +4,9 @@ python:
version: 3.7
install:
- requirements: docs/requirements.txt
- - requirements: requirements.txt
\ No newline at end of file
+ - requirements: requirements.txt
+
+# Build PDF & ePub
+formats:
+ - epub
+ - pdf
diff --git a/README.rst b/README.rst
index dfd7262..80c07dd 100644
--- a/README.rst
+++ b/README.rst
@@ -1,38 +1,37 @@
-
-.. image:: https://readthedocs.org/projects/circuitpython-nrf24l01/badge/?version=stable
- :target: https://circuitpython-nrf24l01.readthedocs.io/en/stable/
- :alt: Documentation Status
-
-.. image:: https://github.com/2bndy5/CircuitPython_nRF24L01/workflows/Build%20CI/badge.svg
- :target: https://github.com/2bndy5/CircuitPython_nRF24L01/actions?query=workflow%3A%22Build+CI%22
- :alt: Build Status
-
-.. image:: https://img.shields.io/pypi/v/circuitpython-nrf24l01.svg
- :target: https://pypi.python.org/pypi/circuitpython-nrf24l01
- :alt: latest version on PyPI
-
-.. image:: https://static.pepy.tech/personalized-badge/circuitpython-nrf24l01?period=total&units=international_system&left_color=grey&right_color=blue&left_text=PyPi%20Downloads
- :target: https://pepy.tech/project/circuitpython-nrf24l01
- :alt: Total PyPI downloads
-
-Read The Docs
-=============
-
-Documentation for this library is hosted at
-`ReadTheDocs.org `_
-
-About this Library
-==================
-
-This is a Circuitpython driver library for the nRF24L01 transceiver.
-
-Originally this code was a Micropython module written by Damien P. George
-& Peter Hinch which can still be found `here
-`_
-
-The Micropython source has since been rewritten to expose all the nRF24L01's
-features and for Circuitpython compatible devices (including linux-based
-SoC computers like the Raspberry Pi).
-Modified by Brendan Doherty & Rhys Thomas.
-
-* Authors: Damien P. George, Peter Hinch, Rhys Thomas, Brendan Doherty
+
+.. image:: https://readthedocs.org/projects/circuitpython-nrf24l01/badge/?version=stable
+ :target: https://circuitpython-nrf24l01.readthedocs.io/en/stable/
+ :alt: Documentation Status
+
+.. image:: https://github.com/2bndy5/CircuitPython_nRF24L01/workflows/Build%20CI/badge.svg
+ :target: https://github.com/2bndy5/CircuitPython_nRF24L01/actions?query=workflow%3A%22Build+CI%22
+ :alt: Build Status
+
+.. image:: https://img.shields.io/pypi/v/circuitpython-nrf24l01.svg
+ :target: https://pypi.python.org/pypi/circuitpython-nrf24l01
+ :alt: latest version on PyPI
+
+.. image:: https://static.pepy.tech/personalized-badge/circuitpython-nrf24l01?period=total&units=international_system&left_color=grey&right_color=blue&left_text=PyPi%20Downloads
+ :target: https://pepy.tech/project/circuitpython-nrf24l01
+ :alt: Total PyPI downloads
+
+Read The Docs
+=============
+
+Documentation for this library is hosted at https://circuitpython-nrf24l01.rtfd.io/
+
+About this Library
+==================
+
+This is a Circuitpython driver library for the nRF24L01 transceiver.
+
+Originally this code was a Micropython module written by Damien P. George
+& Peter Hinch which can still be found `here
+`_
+
+The Micropython source has since been rewritten to expose all the nRF24L01's
+features and for Circuitpython compatible devices (including linux-based
+SoC computers like the Raspberry Pi).
+Modified by Brendan Doherty & Rhys Thomas.
+
+* Authors: Damien P. George, Peter Hinch, Rhys Thomas, Brendan Doherty
diff --git a/circuitpython_nrf24l01/fake_ble.py b/circuitpython_nrf24l01/fake_ble.py
index 066cd10..1c50527 100644
--- a/circuitpython_nrf24l01/fake_ble.py
+++ b/circuitpython_nrf24l01/fake_ble.py
@@ -19,11 +19,12 @@
# 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.
-"""Original research was done by `Dmitry Grinberg and his write-up can be
-found here `_"""
+"""Original research was done by Dmitry Grinberg and his write-up can be found at
+http://dmitry.gr/index.php?r=05.Projects&proj=11.%20Bluetooth%20LE%20fakery"""
from os import urandom
import struct
-from .rf24 import RF24
+from micropython import const
+from .rf24 import RF24, address_repr
def swap_bits(original):
@@ -38,8 +39,7 @@ def swap_bits(original):
def reverse_bits(original):
- """This function reverses the bit order for an entire
- buffer protocol object."""
+ """This function reverses the bit order for an entire buffer protocol object."""
ret = bytearray(len(original))
for i, byte in enumerate(original):
ret[i] = swap_bits(byte)
@@ -52,6 +52,21 @@ def chunk(buf, data_type=0x16):
return bytearray([len(buf) + 1, data_type & 0xFF]) + buf
+def whitener(buf, coef):
+ """Whiten and dewhiten data according to the given coefficient."""
+ data = bytearray(buf)
+ for i, byte in enumerate(data):
+ res, mask = (0, 1)
+ for _ in range(8):
+ if coef & 1:
+ coef ^= 0x88
+ byte ^= mask
+ mask <<= 1
+ coef >>= 1
+ data[i] = byte ^ res
+ return data
+
+
def crc24_ble(data, deg_poly=0x65B, init_val=0x555555):
"""This function calculates a checksum of various sized buffers."""
crc = init_val
@@ -66,16 +81,97 @@ def crc24_ble(data, deg_poly=0x65B, init_val=0x555555):
return reverse_bits((crc).to_bytes(3, "big"))
-BLE_FREQ = (2, 26, 80,)
+BLE_FREQ = (2, 26, 80)
"""The BLE channel number is different from the nRF channel number."""
+TEMPERATURE_UUID = const(0x1809) #: The Temperature Service UUID number
+BATTERY_UUID = const(0x180F) #: The Battery Service UUID number
+EDDYSTONE_UUID = const(0xFEAA) #: The Eddystone Service UUID number
+
+
+class QueueElement:
+ """A data structure used for storing received & decoded BLE payloads in
+ the :attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.rx_queue`.
+
+ :param bytes,bytearray buffer: the validated BLE payload (not including
+ the CRC checksum). The buffer passed here is decoded into this class's
+ properties.
+
+ .. versionadded:: v2.1.0
+ """
+
+ def __init__(self, buffer):
+ #: The transmitting BLE device's MAC address as a `bytes` object.
+ self.mac = bytes(buffer[2:8])
+ self.name = None
+ """The transmitting BLE device's name. This will be a `str`, `bytes` object (if
+ a `UnicodeError` was caught), or `None` (if not included in the received
+ payload)."""
+ self.pa_level = None
+ """The transmitting device's PA Level (if included in the received payload)
+ as an `int`.
+
+ .. note:: This value does not represent the received signal strength.
+ The nRF24L01 will receive anything over a -64 dbm threshold."""
+ self.data = []
+ """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
+ specification - in which case it will be a `bytearray` object."""
+
+ end = buffer[1] + 2
+ i = 8
+ while i < end:
+ size = buffer[i]
+ if size + i + 1 > end or i + 1 > end or not size:
+ # data seems malformed. just append the buffer & move on
+ self.data.append(buffer[i:end])
+ break
+ result = self._decode_data_struct(buffer[i + 1 : i + 1 + size])
+ if not result: # decoding failed
+ self.data.append(buffer[i : i + 1 + size])
+ i += 1 + size
+
+ def _decode_data_struct(self, buf):
+ """Decode a data structure in a received BLE payload."""
+ # print("decoding", address_repr(buf, 0, " "))
+ if buf[0] not in (0x16, 0x0A, 0x08, 0x09):
+ return False # unknown/unsupported "chunk" of data
+ if buf[0] == 0x0A and len(buf) == 2: # if data is the device's TX-ing PA Level
+ self.pa_level = struct.unpack("b", buf[1:2])[0]
+ if buf[0] in (0x08, 0x09): # if data is a BLE device name
+ try:
+ self.name = buf[1:].decode()
+ except UnicodeError:
+ self.name = bytes(buf[1:])
+ if buf[0] == 0xFF: # if it is a custom/user-defined data format
+ self.data.append(buf) # return the raw buffer as a value
+ if buf[0] == 0x16: # if it is service data
+ service_data_uuid = struct.unpack(" (18 - self._show_dbm * 3):
+ if len(_name) > (18 - self._show_dbm * 3):
raise ValueError("name length exceeds maximum.")
- self._ble_name = n
+ self._ble_name = _name
@property
- def show_pa_level(self):
+ def show_pa_level(self) -> bool:
"""If this attribute is `True`, the payload will automatically include
- the nRF24L01's `pa_level` in the advertisement."""
+ the nRF24L01's :attr:`~circuitpython_nrf24l01.rf24.RF24.pa_level` in the
+ advertisement."""
return bool(self._show_dbm)
@show_pa_level.setter
- def show_pa_level(self, enable):
+ def show_pa_level(self, enable: bool):
if enable and len(self.name) > 16:
raise ValueError("there is not enough room to show the pa_level.")
self._show_dbm = bool(enable)
@@ -141,22 +244,17 @@ 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):
+ def whiten(self, data) -> bytearray:
"""Whitening the BLE packet data ensures there's no long repetition
of bits."""
- data, coef = (bytearray(data), (self._curr_freq + 37) | 0x40)
- for i, byte in enumerate(data):
- res, mask = (0, 1)
- for _ in range(8):
- if coef & 1:
- coef ^= 0x88
- byte ^= mask
- mask <<= 1
- coef >>= 1
- data[i] = byte ^ res
+ 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]}")
+ data = whitener(data, coef)
+ # print("whitened: 0x" + address_repr(data, 0))
return data
- def _make_payload(self, payload):
+ def _make_payload(self, payload) -> bytes:
"""Assemble the entire packet to be transmitted as a payload."""
if self.len_available(payload) < 0:
raise ValueError(
@@ -174,16 +272,17 @@ def _make_payload(self, payload):
if name_length:
buf += chunk(self.name, 0x08)
buf += payload
+ # print(f"PL: {address_repr(buf, 0)} CRC: {address_repr(crc24_ble(buf), 0)}")
buf += crc24_ble(buf)
return buf
- def len_available(self, hypothetical=b""):
+ def len_available(self, hypothetical=b"") -> int:
"""This function will calculates how much length (in bytes) is
available in the next payload."""
- name_length = (len(self.name) + 2) if self.name is not None else 0
+ 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=0xFF):
+ def advertise(self, buf=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")
@@ -193,56 +292,73 @@ def advertise(self, buf=b"", data_type=0xFF):
payload += b
else:
payload = chunk(buf, data_type) if buf else b""
- payload = self._make_payload(payload)
- self.send(reverse_bits(self.whiten(payload)))
+ payload = self.whiten(self._make_payload(payload))
+ # print("original: 0x{}".format(address_repr(payload)))
+ # print("reversed: 0x{}".format(address_repr(reverse_bits(payload))))
+ self.send(reverse_bits(payload))
- @property
- def channel(self):
- """The only allowed channels are those contained in the `BLE_FREQ`
- tuple."""
- return self._channel
-
- @channel.setter
- def channel(self, value):
- if value not in BLE_FREQ:
- raise ValueError("channel {} is not a valid BLE frequency".format(value))
- self._channel = value
- self._reg_write(0x05, value)
-
- # pylint: disable=missing-function-docstring
- @property
- def dynamic_payloads(self):
- raise NotImplementedError(
- "adjusting dynamic_payloads breaks BLE specifications"
- )
-
- def set_dynamic_payloads(self, enable, pipe_number=None):
- raise NotImplementedError(
- "adjusting dynamic_payloads breaks BLE specifications"
- )
-
- @property
- def data_rate(self):
+ def print_details(self, dump_pipes: bool = False):
+ super().print_details(False)
+ print("BLE device name___________{}".format(str(self.name)))
+ print("Broadcasting PA Level_____{}".format(self.show_pa_level))
+ if dump_pipes:
+ super().print_pipes()
+
+ @RF24.channel.setter
+ def channel(self, value: int):
+ if value in BLE_FREQ:
+ self._channel = value
+ self._reg_write(0x05, value)
+
+ def available(self) -> bool:
+ """A `bool` describing if there is a payload in the `rx_queue`."""
+ if super().available():
+ self.rx_cache = super().read(self.payload_length)
+ self.rx_cache = self.whiten(reverse_bits(self.rx_cache))
+ end = self.rx_cache[1] + 2
+ self.rx_cache = self.rx_cache[: end + 3]
+ if end < 30 and self.rx_cache[end : end + 3] == crc24_ble(
+ self.rx_cache[:end]
+ ):
+ # print("recv'd:", self.rx_cache)
+ # print("crc:", self.rx_cache[end: end + 3])
+ self.rx_queue.append(QueueElement(self.rx_cache))
+ return bool(self.rx_queue)
+
+ # pylint: disable=arguments-differ
+ def read(self) -> QueueElement:
+ """Get the First Out element from the queue."""
+ if self.rx_queue:
+ ret_val = self.rx_queue[0]
+ del self.rx_queue[0]
+ return ret_val
+ return None
+
+ # pylint: enable=arguments-differ
+ # pylint: disable=unused-argument
+ @RF24.dynamic_payloads.setter
+ def dynamic_payloads(self, val):
+ raise NotImplementedError("using dynamic_payloads breaks BLE specifications")
+
+ @RF24.data_rate.setter
+ def data_rate(self, val):
raise NotImplementedError("adjusting data_rate breaks BLE specifications")
- @property
- def address_length(self):
+ @RF24.address_length.setter
+ def address_length(self, val):
raise NotImplementedError("adjusting address_length breaks BLE specifications")
- @property
- def auto_ack(self):
- raise NotImplementedError("adjusting auto_ack breaks BLE specifications")
-
- def set_auto_ack(self, enable, pipe_number=None):
- raise NotImplementedError("adjusting auto_ack breaks BLE specifications")
+ @RF24.auto_ack.setter
+ def auto_ack(self, val):
+ raise NotImplementedError("using auto_ack breaks BLE specifications")
- @property
- def ack(self):
+ @RF24.ack.setter
+ def ack(self, val):
raise NotImplementedError("adjusting ack breaks BLE specifications")
- @property
- def crc(self):
- raise NotImplementedError("adjusting crc breaks BLE specifications")
+ @RF24.crc.setter
+ def crc(self, val):
+ raise NotImplementedError("BLE specifications only use crc24")
def open_rx_pipe(self, pipe_number, address):
raise NotImplementedError("BLE implementation only uses 1 address on pipe 0")
@@ -250,72 +366,7 @@ def open_rx_pipe(self, pipe_number, address):
def open_tx_pipe(self, address):
raise NotImplementedError("BLE implentation only uses 1 address")
- # pylint: enable=missing-function-docstring
- def print_details(self, dump_pipes=False):
- """This debuggung function aggregates and outputs all status/condition
- related information from the nRF24L01."""
- print("Is a plus variant_________{}".format(self.is_plus_variant))
- print("BLE device name___________{}".format(str(self.name)))
- print("Broadcasting PA Level_____{}".format(self.show_pa_level))
- print(
- "Channel___________________{} ~ {} GHz".format(
- self.channel, (self.channel + 2400) / 1000
- )
- )
- print("RF Data Rate______________1 Mbps")
- print("RF Power Amplifier________{} dbm".format(self.pa_level))
- print(
- "RF Low Noise Amplifier____{}".format(
- "Enabled" if self.is_lna_enabled else "Disabled"
- )
- )
- print("CRC bytes_________________3")
- print("Address length____________4 bytes")
- print("TX Payload lengths________{} bytes".format(self.payload_length))
- print("Auto retry delay__________250 microseconds")
- print("Auto retry attempts_______0 maximum")
- print("Re-use TX FIFO____________{}".format(bool(self._reg_read(0x17) & 64)))
- print(
- "IRQ on Data Ready__{} Data Ready___________{}".format(
- "_Enabled" if not self._config & 0x40 else "Disabled", self.irq_dr
- )
- )
- print(
- "IRQ on Data Fail___{} Data Failed__________{}".format(
- "_Enabled" if not self._config & 0x10 else "Disabled", self.irq_df
- )
- )
- print(
- "IRQ on Data Sent___{} Data Sent____________{}".format(
- "_Enabled" if not self._config & 0x20 else "Disabled", self.irq_ds
- )
- )
- print(
- "TX FIFO full__________{} TX FIFO empty________{}".format(
- "_True" if self.tx_full else "False", self.fifo(True, True)
- )
- )
- print(
- "RX FIFO full__________{} RX FIFO empty________{}".format(
- "_True" if self.fifo(False, False) else "False", self.fifo(False, True)
- )
- )
- print(
- "Ask no ACK_________{} Custom ACK Payload___Disabled".format(
- "_Allowed" if self.allow_ask_no_ack else "Disabled",
- )
- )
- print("Dynamic Payloads___Disabled Auto Acknowledgment__Disabled")
- print(
- "Primary Mode_____________{} Power Mode___________{}".format(
- "RX" if self.listen else "TX",
- ("Standby-II" if self.ce_pin.value else "Standby-I")
- if self._config & 2
- else "Off",
- )
- )
- if dump_pipes:
- self._dump_pipes()
+ # pylint: enable=unused-argument
class ServiceData:
@@ -327,16 +378,14 @@ def __init__(self, uuid):
self._data = b""
@property
- def uuid(self):
+ def uuid(self) -> bytes:
"""This returns the 16-bit Service UUID as a `bytearray` in little
endian. (read-only)"""
return self._type
@property
- def data(self):
- """The service's data. This is a `bytearray`, and its format is
- defined by relative Bluetooth Service Specifications (and GATT
- supplemental specifications)."""
+ def data(self) -> bytes:
+ """This attribute is a `bytearray` or `bytes` object."""
return self._data
@data.setter
@@ -344,17 +393,23 @@ def data(self, value):
self._data = value
@property
- def buffer(self):
+ def buffer(self) -> bytes:
"""Get the representation of the instantiated object as an
immutable `bytes` object (read-only)."""
- return bytes(self._type + self.data)
+ return bytes(self._type + self._data)
- def __len__(self):
+ def __len__(self) -> int:
"""For convenience, this class is compatible with python's builtin
:py:func:`len()` method. In this context, this will return the length
of the object (in bytes) as it would appear in the advertisement
payload."""
- return len(self._type) + len(self.data)
+ return len(self._type) + len(self._data)
+
+ def __repr__(self) -> str:
+ """For convenience, this class is compatible with python's builtin
+ :py:func:`repr()` method. In this context, it will return a string of
+ data with applicable suffixed units."""
+ return address_repr(self.buffer, False)
class TemperatureServiceData(ServiceData):
@@ -362,12 +417,23 @@ class TemperatureServiceData(ServiceData):
temperature data values as a `float` value."""
def __init__(self):
- super().__init__(0x1809)
+ super().__init__(TEMPERATURE_UUID)
- @ServiceData.data.setter
- def data(self, value):
- value = struct.pack(" float:
+ """This attribute is a `float` value."""
+ return struct.unpack(" str:
+ return f"Temperature: {self.data} C"
class BatteryServiceData(ServiceData):
@@ -375,11 +441,22 @@ class BatteryServiceData(ServiceData):
battery charge percentage as a 1-byte value."""
def __init__(self):
- super().__init__(0x180F)
+ super().__init__(BATTERY_UUID)
- @ServiceData.data.setter
- def data(self, value):
- self._data = struct.pack(">B", value)
+ @property
+ def data(self) -> int:
+ """The attribute is a 1-byte unsigned `int` value."""
+ return int(self._data[0])
+
+ @data.setter
+ def data(self, value: int):
+ if isinstance(value, int):
+ self._data = struct.pack("B", value)
+ elif isinstance(value, (bytes, bytearray)):
+ self._data = value
+
+ def __repr__(self) -> str:
+ return f"Battery capacity remaining: {self.data}%"
class UrlServiceData(ServiceData):
@@ -387,11 +464,15 @@ class UrlServiceData(ServiceData):
URL data as a `bytes` value."""
def __init__(self):
- super().__init__(0xFEAA)
+ super().__init__(EDDYSTONE_UUID)
self._type += bytes([0x10]) + struct.pack(">b", -25)
+ codex_prefix = ["http://www.", "https://www.", "http://", "https://"]
+ codex_suffix = [".com", ".org", ".edu", ".net", ".info", ".biz", ".gov"]
+ codex_suffix = [suffix + "/" for suffix in codex_suffix] + codex_suffix
+
@property
- def pa_level_at_1_meter(self):
+ def pa_level_at_1_meter(self) -> int:
"""The TX power level (in dBm) at 1 meter from the nRF24L01. This
defaults to -25 (due to testing when broadcasting with 0 dBm) and must
be a 1-byte signed `int`."""
@@ -399,29 +480,35 @@ def pa_level_at_1_meter(self):
@pa_level_at_1_meter.setter
def pa_level_at_1_meter(self, value):
- self._type = self._type[:-1] + struct.pack(">b", int(value))
+ if isinstance(value, int):
+ self._type = self._type[:-1] + struct.pack(">b", int(value))
+ elif isinstance(value, (bytes, bytearray)):
+ self._type = self._type[:-1] + value[:1]
@property
- def uuid(self):
+ def uuid(self) -> bytes:
return self._type[:2]
- @ServiceData.data.setter
- def data(self, value):
- value = value.replace("http://www.", "\x00")
- value = value.replace("https://www.", "\x01")
- value = value.replace("http://", "\x02")
- value = value.replace("https://", "\x03")
- value = value.replace(".com/", "\x00")
- value = value.replace(".org/", "\x01")
- value = value.replace(".edu/", "\x02")
- value = value.replace(".net/", "\x03")
- value = value.replace(".info/", "\x04")
- value = value.replace(".biz/", "\x05")
- value = value.replace(".gov/", "\x06")
- value = value.replace(".com", "\x07")
- value = value.replace(".org", "\x08")
- value = value.replace(".edu", "\x09")
- value = value.replace(".net", "\x0A")
- value = value.replace(".info", "\x0B")
- value = value.replace(".biz", "\x0C")
- self._data = value.replace(".gov", "\x0D").encode("utf-8")
+ @property
+ def data(self) -> str:
+ """This attribute is a `str` of URL data."""
+ value = self._data.decode()
+ for i, b_code in enumerate(UrlServiceData.codex_prefix):
+ value = value.replace(chr(i), b_code, 1)
+ for i, b_code in enumerate(UrlServiceData.codex_suffix):
+ value = value.replace(chr(i), b_code)
+ return value
+
+ @data.setter
+ def data(self, value: str):
+ if isinstance(value, str):
+ for i, b_code in enumerate(UrlServiceData.codex_prefix):
+ value = value.replace(b_code, chr(i), 1)
+ for i, b_code in enumerate(UrlServiceData.codex_suffix):
+ value = value.replace(b_code, chr(i))
+ self._data = value.encode("utf-8")
+ elif isinstance(value, (bytes, bytearray)):
+ self._data = value
+
+ def __repr__(self) -> str:
+ return "Advertised URL: " + self.data
diff --git a/circuitpython_nrf24l01/network/__init__.py b/circuitpython_nrf24l01/network/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/circuitpython_nrf24l01/network/constants.py b/circuitpython_nrf24l01/network/constants.py
new file mode 100644
index 0000000..a4497a0
--- /dev/null
+++ b/circuitpython_nrf24l01/network/constants.py
@@ -0,0 +1,69 @@
+# 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.
+"""All the constants related to managing a network with nRF24L01 radios."""
+from micropython import const
+
+
+# generic (internal) constants
+MAX_USR_DEF_MSG_TYPE = const(127) #: A convenient sentinel value.
+NETWORK_DEFAULT_ADDR = const(0o4444) #: Primarily used by RF24Mesh.
+MAX_FRAG_SIZE = const(24) #: Maximum message size for a single frame's message.
+NETWORK_MULTICAST_ADDR = const(0o100) #: A reserved address for multicast messages.
+MESH_LOOKUP_TIMEOUT = const(135) #: Used for `lookup_address()` & `lookup_node_id()`
+MESH_MAX_POLL = const(4) #: The max number of contacts made during `renew_address()`.
+MESH_MAX_CHILDREN = const(4) #: The max number of children for 1 mesh node.
+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.
+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.
+TX_LOGICAL = const(3) #: Similar to `TX_NORMAL`.
+TX_MULTICAST = const(4) #: Broadcast a message to a network level of nodes.
+
+# constants used to define `RF24NetworkHeader.message_type`
+NETWORK_ACK = const(193) #: Used for network-wide acknowledgements.
+NETWORK_PING = const(130) #: Used for network pings
+NETWORK_POLL = const(194) #: Primarily for RF24Mesh
+MESH_ADDR_REQUEST = const(195) #: Primarily for RF24Mesh
+MESH_ADDR_RESPONSE = const(128) #: Primarily for RF24Mesh
+
+#: Unsupported at this time as this operation requires a new implementation.
+NETWORK_EXT_DATA = const(131)
+
+# No Network ACK message types
+#: The `message_type` when manually expiring a leased address.
+MESH_ADDR_RELEASE = const(197)
+#: The `message_type` to request a mesh node's network address from its unique ID.
+MESH_ADDR_LOOKUP = const(196)
+#: The `message_type` to request a mesh node's unique ID number from its node address.
+MESH_ID_LOOKUP = const(198)
+
+
+# fragmented message types (used in the `header.reserved` attribute)
+#: Used to indicate the first frame of a fragmented message.
+MSG_FRAG_FIRST = const(148)
+#: Used to indicate a middle frame of a fragmented message.
+MSG_FRAG_MORE = const(149)
+#: Used to indicate the last frame of a fragmented message.
+MSG_FRAG_LAST = const(150)
diff --git a/circuitpython_nrf24l01/network/mixins.py b/circuitpython_nrf24l01/network/mixins.py
new file mode 100644
index 0000000..731d7cf
--- /dev/null
+++ b/circuitpython_nrf24l01/network/mixins.py
@@ -0,0 +1,581 @@
+# 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.
+"""A module to hold all usuall accesssible RF24 API via the RF24Network API"""
+# pylint: disable=missing-docstring
+import time
+from ..rf24 import RF24, address_repr
+from .structs import RF24NetworkFrame, FrameQueue, FrameQueueFrag, is_address_valid
+from .constants import (
+ MAX_FRAG_SIZE,
+ MSG_FRAG_FIRST,
+ MSG_FRAG_MORE,
+ MSG_FRAG_LAST,
+ NETWORK_DEFAULT_ADDR,
+ MESH_ADDR_RESPONSE,
+ MESH_ADDR_REQUEST,
+ NETWORK_ACK,
+ NETWORK_EXT_DATA,
+ NETWORK_PING,
+ NETWORK_POLL,
+ TX_ROUTED,
+ NETWORK_MULTICAST_ADDR,
+ TX_NORMAL,
+ TX_PHYSICAL,
+ TX_LOGICAL,
+ TX_MULTICAST,
+ MAX_USR_DEF_MSG_TYPE,
+)
+
+
+class RadoMixin:
+ def __init__(self, spi, csn, ce_pin, spi_frequency=10000000):
+ self._rf24 = RF24(spi, csn, ce_pin, spi_frequency=spi_frequency)
+ super().__init__()
+
+ def __enter__(self):
+ self._rf24.__enter__()
+ return self
+
+ def __exit__(self, *exc):
+ return self._rf24.__exit__()
+
+ def flush_rx(self):
+ self._rf24.flush_rx()
+
+ def flush_tx(self):
+ self._rf24.flush_tx()
+
+ def fifo(self, about_tx: bool, check_empty: bool = None):
+ return self._rf24.fifo(about_tx, check_empty)
+
+ @property
+ def power(self) -> bool:
+ return self._rf24.power
+
+ @power.setter
+ def power(self, val: bool):
+ self._rf24.power = val
+
+ @property
+ def channel(self) -> int:
+ return self._rf24.channel
+
+ @channel.setter
+ def channel(self, val: int):
+ self._rf24.channel = val
+
+ 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):
+ return self._rf24.get_dynamic_payloads(pipe)
+
+ @property
+ def listen(self) -> bool:
+ return self._rf24.listen
+
+ @listen.setter
+ def listen(self, is_rx: bool):
+ self._rf24.listen = is_rx
+
+ @property
+ def pa_level(self) -> int:
+ return self._rf24.pa_level
+
+ @pa_level.setter
+ def pa_level(self, val: int):
+ self._rf24.pa_level = val
+
+ @property
+ def is_lna_enabled(self) -> bool:
+ return self._rf24.is_lna_enabled
+
+ @property
+ def data_rate(self) -> int:
+ return self._rf24.data_rate
+
+ @data_rate.setter
+ def data_rate(self, val: int):
+ self._rf24.data_rate = val
+
+ @property
+ def crc(self) -> int:
+ return self._rf24.crc
+
+ @crc.setter
+ def crc(self, val: int):
+ self._rf24.crc = val
+
+ def get_auto_retries(self) -> tuple:
+ return self._rf24.get_auto_retries()
+
+ def set_auto_retries(self, delay: int, count: int):
+ self._rf24.set_auto_retries(delay, count)
+
+ @property
+ def last_tx_arc(self) -> int:
+ return self._rf24.last_tx_arc
+
+ def address(self, index: int = -1) -> int:
+ return self._rf24.address(index)
+
+ def interrupt_config(
+ self, data_recv: bool = True, data_sent: bool = True, data_fail: bool = True
+ ):
+ self._rf24.interrupt_config(data_recv, data_sent, data_fail)
+
+ def print_pipes(self):
+ self._rf24.print_pipes()
+
+
+def _lvl_2_addr(level: int) -> int:
+ """translate decimal tree ``level`` into an octal node address"""
+ level_addr = 0
+ if level:
+ level_addr = 1 << ((level - 1) * 3)
+ return level_addr
+
+
+class NetworkMixin(RadoMixin):
+ def __init__(self, spi, csn, ce_pin, spi_frequency=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
+ self._relay_enabled, self._frag_enabled = (False, True)
+
+ #: The timeout (in milliseconds) to wait for successful transmission.
+ self.tx_timeout = 25
+ #: The timeout (in milliseconds) to wait for transmission's `NETWORK_ACK`.
+ self.route_timeout = 3 * self.tx_timeout
+ #: enable/disable (`True`/`False`) multicasting
+ self.allow_multicast = True
+ 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
+ self.queue = FrameQueueFrag()
+ #: A buffer containing the last frame received by the network node
+ self.frame_buf = RF24NetworkFrame()
+ self.address_suffix = bytearray([0xC3, 0x3C, 0x33, 0xCE, 0x3E, 0xE3])
+ """Each byte in this `bytearray` corresponds to the unique byte per pipe and
+ child node."""
+ self.address_prefix = bytearray([0xCC])
+ """The base case for all pipes' address' bytes before mutating with
+ `address_suffix`."""
+
+ def _begin(self, n_addr: int):
+ # prep radio
+ self._rf24.listen = False
+ self._rf24.auto_ack = 0x3E
+ self._rf24.set_auto_retries(250 * (((n_addr % 6) + 1) * 2 + 3) + 250, 5)
+ for i in range(6):
+ self._rf24.open_rx_pipe(i, self._pipe_address(n_addr, i))
+ self._rf24.listen = True
+
+ # setup address-related instance attibutes
+ self._addr = n_addr
+ self._mask = 0
+ self._net_lvl = 0
+ # calc inverted address mask
+ mask = 0xFFFF
+ while self._addr & mask:
+ mask = (mask << 3) & 0xFFFF
+ self._net_lvl += 1
+ self._mask_inv = mask
+ # calc address mask
+ while not mask & 7:
+ self._mask = (self._mask << 3) | 7
+ mask >>= 3
+ # calc parent's address & pipe number
+ self._parent = self._addr & (self._mask >> 3)
+ self._parent_pipe = self._addr
+ mask = self._mask >> 3
+ while mask:
+ mask >>= 3
+ self._parent_pipe >>= 3
+
+ 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",
+ "{}".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(
+ "Multicast relay____________{}abled".format(
+ "En" if self._relay_enabled else "Dis"
+ )
+ )
+ print(
+ "Network fragmentation______{}abled".format(
+ "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)}")
+ if dump_pipes:
+ self._rf24.print_pipes()
+
+ @property
+ def node_address(self) -> int:
+ """get/set the node's :ref:`Logical Address ` for the
+ `RF24Network` object."""
+ return self._addr
+
+ @property
+ def fragmentation(self) -> bool:
+ """Enable/disable (`True`/`False`) the message fragmentation feature."""
+ return self._frag_enabled
+
+ @fragmentation.setter
+ def fragmentation(self, enabled: bool):
+ enabled = bool(enabled)
+ if enabled != self._frag_enabled:
+ self.max_message_length = 144 if enabled else MAX_FRAG_SIZE
+ if enabled:
+ self.queue = FrameQueueFrag(self.queue)
+ else:
+ self.queue = FrameQueue(self.queue)
+ self._frag_enabled = enabled
+
+ @property
+ def multicast_relay(self) -> bool:
+ """Enabling this attribute will automatically forward received multicasted
+ frames to the next highest `network level `_."""
+ return self.allow_multicast and self._relay_enabled
+
+ @multicast_relay.setter
+ def multicast_relay(self, enable: bool):
+ self._relay_enabled = enable and self.allow_multicast
+
+ @property
+ def multicast_level(self) -> int:
+ """Override the default multicasting network level which is set by the
+ `node_address` attribute."""
+ return self._net_lvl
+
+ @multicast_level.setter
+ def multicast_level(self, lvl: int):
+ lvl = min(4, max(lvl, 0))
+ self._net_lvl = lvl
+ self._rf24.listen = False
+ self._rf24.open_rx_pipe(0, self._pipe_address(_lvl_2_addr(lvl), 0))
+ self._rf24.listen = True
+
+ @property
+ def parent(self) -> int:
+ """Get address for the parent node"""
+ return self._parent
+
+ def _pipe_address(self, node_addr: int, pipe_number: int) -> bytearray:
+ """translate node address for use on any pipe number"""
+ result, count, dec = (bytearray(self.address_prefix[:] * 5), 1, node_addr)
+ while dec:
+ if not self.allow_multicast or (
+ self.allow_multicast and (pipe_number or not node_addr)
+ ):
+ result[count] = self.address_suffix[dec % 8]
+ dec >>= 3
+ count += 1
+
+ if not self.allow_multicast or (
+ self.allow_multicast and (pipe_number or not node_addr)
+ ):
+ result[0] = self.address_suffix[pipe_number]
+ elif self.allow_multicast and (not pipe_number or node_addr):
+ result[1] = self.address_suffix[count - 1]
+ # print(oct(node_addr), "for pipe", pipe_number, "is", address_repr(result))
+ return result
+
+ 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
+ while True:
+ temp_buf = self._rf24.read()
+ if temp_buf is None:
+ return ret_val
+ if (
+ not self.frame_buf.unpack(temp_buf)
+ or not is_address_valid(self.frame_buf.header.to_node)
+ or not is_address_valid(self.frame_buf.header.from_node)
+ ):
+ # print("discarding frame due to invalid network addresses.")
+ continue
+
+ # print(
+ # "Received frame: " + self.frame_buf.header.to_string(),
+ # "message buffer is empty"
+ # if not self.frame_buf.message
+ # else "\n\t" + address_repr(self.frame_buf.message, 0, " ")
+ # )
+ ret_val = self.frame_buf.header.message_type
+ keep_updating = False
+ if self.frame_buf.header.to_node == self._addr:
+ # frame was directed to this node
+ keep_updating, ret_val = self._handle_frame_for_this_node(ret_val)
+ else: # frame was not directed to this node
+ keep_updating, ret_val = self._handle_frame_for_other_node(ret_val)
+
+ if not keep_updating:
+ return ret_val
+
+ def _handle_frame_for_this_node(self, msg_t: int) -> tuple:
+ """Returns False if the frame is not consumed or True if consumed"""
+ if msg_t == NETWORK_PING:
+ return (True, msg_t)
+
+ if msg_t == MESH_ADDR_RESPONSE and NETWORK_DEFAULT_ADDR != self._addr:
+ self.frame_buf.header.to_node = NETWORK_DEFAULT_ADDR
+ self._write(NETWORK_DEFAULT_ADDR, TX_PHYSICAL)
+ return (True, msg_t)
+ if msg_t == MESH_ADDR_REQUEST and self._addr:
+ self.frame_buf.header.from_node = self._addr
+ self.frame_buf.header.to_node = 0
+ self._write(0, TX_NORMAL)
+ return (True, msg_t)
+ 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,
+ ):
+ return (False, msg_t)
+
+ self.queue.enqueue(self.frame_buf)
+ if self.frame_buf.header.message_type == NETWORK_EXT_DATA:
+ # enqueue() will adjust header's message_type for the last fragment
+ # print("Received external data type")
+ return (False, NETWORK_EXT_DATA)
+ return (True, msg_t)
+
+ def _handle_frame_for_other_node(self, msg_t: int) -> tuple:
+ """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:
+ if msg_t == NETWORK_POLL:
+ if self._addr != NETWORK_DEFAULT_ADDR:
+ if self._parenthood:
+ self.frame_buf.header.to_node = (
+ self.frame_buf.header.from_node
+ )
+ self.frame_buf.header.from_node = self._addr
+ time.sleep(self._parent_pipe / 1000)
+ self._write(self.frame_buf.header.to_node, TX_PHYSICAL)
+ return (True, 0)
+ self.queue.enqueue(self.frame_buf)
+ if self.multicast_relay:
+ print(
+ "Forwarding multicast frame from {} to {}".format(
+ oct(self.frame_buf.header.from_node),
+ oct(self.frame_buf.header.to_node),
+ ),
+ )
+ if not self._addr >> 3:
+ time.sleep(0.0024)
+ time.sleep((self._addr % 4) * 0.0006)
+ self._write(
+ (_lvl_2_addr(self._net_lvl) << 3) & 0xFFFF,
+ TX_MULTICAST,
+ )
+ if self.frame_buf.header.message_type == NETWORK_EXT_DATA:
+ # enqueue() will adjust this for the last fragment
+ return (False, NETWORK_EXT_DATA)
+ elif self._addr != NETWORK_DEFAULT_ADDR:
+ # pass it along
+ self._write(self.frame_buf.header.to_node, TX_ROUTED)
+ return (True, 0)
+ elif self._addr != NETWORK_DEFAULT_ADDR: # multicast not enabled
+ # pass it along
+ self._write(self.frame_buf.header.to_node, TX_ROUTED)
+ msg_t = 0
+ return (True, msg_t)
+
+ def available(self) -> bool:
+ """:Returns: A `bool` describing if there is a frame waiting in the `queue`."""
+ return bool(len(self.queue))
+
+ def peek(self) -> RF24NetworkFrame:
+ """Get (from `queue`) the next available frame."""
+ return self.queue.peek()
+
+ 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:
+ """Broadcast a message to all nodes on a certain network level."""
+ if not self._validate_msg_len(len(message)):
+ message = message[:MAX_FRAG_SIZE]
+ level = self._net_lvl if level is None else min(3, max(level, 0))
+ self.frame_buf.header.to_node = NETWORK_MULTICAST_ADDR
+ self.frame_buf.header.from_node = self._addr
+ message_type = (
+ message_type if not isinstance(message_type, str) else ord(message_type[0])
+ )
+ self.frame_buf.header.message_type = message_type & 0xFF
+ self.frame_buf.message = message
+ return self._write(_lvl_2_addr(level), TX_MULTICAST)
+
+ def _validate_msg_len(self, length: int) -> bool:
+ if length > self.max_message_length:
+ raise ValueError("message's length is too large!")
+ if length > MAX_FRAG_SIZE and not self._frag_enabled:
+ return False
+ return True
+
+ def _write(self, write_direct: int, send_type: int) -> bool:
+ """entry point for transmitting the current frame_buf"""
+ is_ack_t = self.frame_buf.is_ack_type()
+
+ to_node, to_pipe, is_multicast = self._logi_2_phys(write_direct, send_type)
+
+ if send_type == TX_ROUTED and write_direct == to_node and is_ack_t:
+ time.sleep(0.002)
+
+ # send the frame
+ result = self._write_to_pipe(to_node, to_pipe, is_multicast)
+ # print("Failed to send" if not result else "Successfully sent")
+
+ if result and is_ack_t: # does NETWORK_ACK need to be handled?
+ # conditionally send the NETWORK_ACK message
+ if (
+ send_type == TX_ROUTED
+ and to_node == write_direct
+ and self.frame_buf.header.from_node != self._addr
+ ):
+ self.frame_buf.header.message_type = NETWORK_ACK
+ self.frame_buf.header.to_node = self.frame_buf.header.from_node
+ ack_to_node, ack_to_pipe, is_multicast = self._logi_2_phys(
+ self.frame_buf.header.from_node, TX_ROUTED
+ )
+ # ack_ok =
+ self._write_to_pipe(ack_to_node, ack_to_pipe, is_multicast)
+ # print(
+ # "Network ACK {} origin {} on pipe {}".format(
+ # "reached" if ack_ok else "failed to reach",
+ # oct(self.frame_buf.header.from_node),
+ # ack_to_pipe,
+ # ),
+ # )
+
+ # conditionally wait for NETWORK_ACK message
+ elif to_node != write_direct and send_type in (TX_NORMAL, TX_LOGICAL):
+ self._rf24.listen = True
+ self._rf24.auto_ack = 0x3E
+ rx_timeout = self.route_timeout * 1000000 + time.monotonic_ns()
+ while self._net_update() != NETWORK_ACK:
+ if time.monotonic_ns() > rx_timeout:
+ result = False
+ break
+ # print(
+ # "Network ACK {}received from {}".format(
+ # "" if result else "not ", oct(to_node)
+ # ),
+ # )
+ return result
+
+ # ready radio to continue listening
+ self._rf24.listen = True
+ if not is_multicast:
+ self._rf24.auto_ack = 0x3E
+ return result
+
+ def _write_to_pipe(self, to_node: int, to_pipe: int, is_multicast: bool) -> bool:
+ """send prepared frame to a particular node's pipe"""
+ result = False
+ if to_node == self._addr:
+ return self.queue.enqueue(self.frame_buf)
+ self._rf24.auto_ack = 0x3E + (not is_multicast)
+ self.listen = False
+ # print("Sending", self.frame_buf.header.to_string(), "to pipe", to_pipe)
+ self._rf24.open_tx_pipe(self._pipe_address(to_node, to_pipe))
+ if len(self.frame_buf.message) <= MAX_FRAG_SIZE:
+ result = self._rf24.send(self.frame_buf.pack(), send_only=True)
+ if not result:
+ result = self._tx_standby(self.tx_timeout)
+ else:
+ # break message into fragments and send the multiple resulting frames
+ total = bool(len(self.frame_buf.message) % MAX_FRAG_SIZE) + int(
+ len(self.frame_buf.message) / MAX_FRAG_SIZE
+ )
+ msg_t = self.frame_buf.header.message_type
+ for count in range(total):
+ buf_start = count * MAX_FRAG_SIZE
+ buf_end = count * MAX_FRAG_SIZE + MAX_FRAG_SIZE
+ self.frame_buf.header.reserved = total - count
+ if count == total - 1:
+ self.frame_buf.header.message_type = MSG_FRAG_LAST
+ self.frame_buf.header.reserved = msg_t
+ buf_end = len(self.frame_buf.message)
+ elif not count:
+ self.frame_buf.header.message_type = MSG_FRAG_FIRST
+ else:
+ self.frame_buf.header.message_type = MSG_FRAG_MORE
+
+ result = self._rf24.send(
+ self.frame_buf.header.pack()
+ + self.frame_buf.message[buf_start:buf_end],
+ send_only=True,
+ )
+ retries = 3
+ while not result and retries:
+ time.sleep(0.002)
+ result = self._tx_standby(self.tx_timeout)
+ retries -= 1
+ # print(
+ # "Frag", count + 1, "of", total,
+ # "sent successfully" if result else "failed to send. Aborting"
+ # )
+ if not result:
+ break
+ self.frame_buf.header.message_type = msg_t
+ return result
+
+ def _tx_standby(self, delta_time: int) -> bool:
+ result = False
+ timeout = delta_time * 1000000 + time.monotonic_ns()
+ while not result and time.monotonic_ns() < timeout:
+ result = self._rf24.resend(send_only=True)
+ return result
+
+ def _logi_2_phys(
+ self, to_node: int, send_type: int, is_multicast: bool = False
+ ) -> tuple:
+ """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:
+ is_multicast, conv_to_pipe, conv_to_node = (True, 0, to_node)
+ elif to_node & self._mask == self._addr: # to_node is a descendant
+ conv_to_pipe = 5
+ if not to_node & (self._mask_inv << 3):
+ conv_to_node = to_node # to_node is a direct child
+ else: # to_node is a descendant of a descendant
+ conv_to_node = to_node & ((self._mask << 3) | 7)
+ return (conv_to_node, conv_to_pipe, is_multicast)
diff --git a/circuitpython_nrf24l01/network/structs.py b/circuitpython_nrf24l01/network/structs.py
new file mode 100644
index 0000000..b59f304
--- /dev/null
+++ b/circuitpython_nrf24l01/network/structs.py
@@ -0,0 +1,221 @@
+# 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.
+"""This module contains the data structures used foe network packets."""
+import struct
+from .constants import (
+ NETWORK_EXT_DATA,
+ NETWORK_MULTICAST_ADDR,
+ MSG_FRAG_FIRST,
+ MSG_FRAG_MORE,
+ MSG_FRAG_LAST,
+)
+
+def is_address_valid(address) -> bool:
+ """Test if a given address is a valid :ref:`Logical Address `."""
+ if address is None:
+ return False
+ if address == NETWORK_MULTICAST_ADDR:
+ return True
+ byte_count = 0
+ while address:
+ if (not 0 < (address & 7) <= 5) or (byte_count > 5):
+ return False
+ address >>= 3
+ byte_count += 1
+ return True
+
+
+class RF24NetworkHeader:
+ """The header information used for routing network messages."""
+
+ def __init__(self, to_node: int=None, message_type=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])
+ else:
+ self.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.
+
+ __next_id = 0
+
+ def unpack(self, buffer) -> bool:
+ """Decode header data from the first 8 bytes of a frame's buffer."""
+ if len(buffer) < 8:
+ return False
+ (
+ self.from_node,
+ self.to_node,
+ self.frame_id,
+ self.message_type,
+ self.reserved,
+ ) = struct.unpack("HHHBB", buffer[:8])
+ return True
+
+ def pack(self) -> bytes:
+ """This function |internal_use|"""
+ return struct.pack(
+ "HHHBB",
+ 0o7777 if self.from_node is None else self.from_node & 0xFFF,
+ self.to_node & 0xFFF,
+ self.frame_id & 0xFFFF,
+ (
+ self.message_type
+ if not isinstance(self.message_type, str)
+ else (ord(self.message_type[0]) if self.message_type else 0)
+ ) & 0xFF,
+ self.reserved & 0xFF,
+ )
+
+ def __len__(self) -> int:
+ return 8
+
+ def to_string(self) -> str:
+ """:Returns: A `str` describing all of the header's attributes."""
+ return "from {} to {} type {} id {} reserved {}".format(
+ oct(0o7777 if self.from_node is None else self.from_node),
+ oct(self.to_node),
+ (
+ self.message_type
+ if not isinstance(self.message_type, str)
+ else (ord(self.message_type[0]) if self.message_type else 0)
+ ) & 0xFF,
+ self.frame_id,
+ self.reserved,
+ )
+
+
+class RF24NetworkFrame:
+ """Structure of a single frame."""
+
+ def __init__(self, header: RF24NetworkHeader=None, message=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)):
+ raise TypeError("message must be a `bytes` or `bytearray` object")
+ self.header = header if header is not None else RF24NetworkHeader()
+ """The `RF24NetworkHeader` about the frame's `message`."""
+ 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:
+ """Decode the `header` & `message` from a ``buffer``."""
+ if self.header.unpack(buffer):
+ self.message = buffer[8:]
+ return True
+ return False
+
+ def pack(self) -> bytes:
+ """This attribute |internal_use|"""
+ return self.header.pack() + bytes(self.message)
+
+ def __len__(self) -> int:
+ return 8 + len(self.message)
+
+ def is_ack_type(self) -> bool:
+ """Check if the frame is to expect a `NETWORK_ACK` message."""
+ return 64 < self.header.message_type < 192
+
+
+class FrameQueue:
+ """A class that wraps a `list` with RF24Network Queue behavior."""
+
+ def __init__(self, queue=None):
+ #: The maximum number of frames that can be enqueued at once. Defaults to 6.
+ self.max_queue_size = 6
+ self._queue = []
+ if queue is not None:
+ while queue:
+ self._queue.append(queue.dequeue())
+ self.max_queue_size = queue.max_queue_size
+ super().__init__()
+
+ def enqueue(self, frame: RF24NetworkFrame) -> bool:
+ """Add a `RF24NetworkFrame` to the queue."""
+ if self.max_queue_size == len(self._queue):
+ return False
+ for frm in self._queue:
+ if (
+ frm.header.from_node == frame.header.from_node
+ and frm.header.frame_id == frame.header.frame_id
+ and frm.header.message_type == frame.header.message_type
+ ):
+ return False # already enqueued this frame
+ new_frame = RF24NetworkFrame()
+ new_frame.unpack(frame.pack())
+ self._queue.append(new_frame)
+ return True
+
+ def peek(self) -> RF24NetworkFrame:
+ """:Returns: The First Out element without removing it from the queue."""
+ return None if not self._queue else self._queue[0]
+
+ def dequeue(self) -> RF24NetworkFrame:
+ """:Returns: The First Out element and removes it from the queue."""
+ return None if not self._queue else self._queue.pop(0)
+
+ 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."""
+
+ def __init__(self, queue=None):
+ super().__init__(queue)
+ self._frags = RF24NetworkFrame() # initialize cache
+
+ def enqueue(self, frame: RF24NetworkFrame) -> bool:
+ """Add a `RF24NetworkFrame` to the queue."""
+ if frame.header.message_type in (MSG_FRAG_FIRST, MSG_FRAG_MORE, MSG_FRAG_LAST):
+ if frame.header.message_type == MSG_FRAG_FIRST:
+ self._frags.unpack(frame.pack()) # make copy not reference
+ return True
+ if (
+ self._frags.header.from_node is not None # if not just initialized
+ and frame.header.to_node == self._frags.header.to_node
+ and frame.header.frame_id == self._frags.header.frame_id
+ ):
+ if (
+ self._frags.header.reserved - 1 != frame.header.reserved
+ and frame.header.message_type != MSG_FRAG_LAST
+ ):
+ # print("dropping non sequential fragment")
+ return False
+ self._frags.header.unpack(frame.header.pack())
+ self._frags.message += frame.message[:]
+ if frame.header.message_type == MSG_FRAG_LAST:
+ if frame.header.reserved == NETWORK_EXT_DATA:
+ # External data needs to be propagated back to update()
+ frame.header.message_type = NETWORK_EXT_DATA # by reference
+ self._frags.header.message_type = frame.header.reserved
+ return super().enqueue(self._frags)
+ return True
+ # print("dropping fragment due to missing 1st fragment")
+ return False
+ return super().enqueue(frame)
diff --git a/circuitpython_nrf24l01/rf24.py b/circuitpython_nrf24l01/rf24.py
index eb59b81..7dc836d 100644
--- a/circuitpython_nrf24l01/rf24.py
+++ b/circuitpython_nrf24l01/rf24.py
@@ -21,14 +21,10 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""rf24 module containing the base class RF24"""
-__version__ = "0.0.0-auto.0"
-__repo__ = "https://github.com/2bndy5/CircuitPython_nRF24L01.git"
import time
from micropython import const
-try:
- from ubus_device import SPIDevice
-except ImportError:
- from adafruit_bus_device.spi_device import SPIDevice
+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
@@ -42,67 +38,62 @@
TX_FEATURE = const(0x1D) # dynamic TX-payloads, TX-ACK payloads, TX-NO_ACK
-def address_repr(addr):
- """Convert an address into a hexlified string (in big endian)."""
- rev_str = ""
- for char in range(len(addr) - 1, -1, -1):
- rev_str += ("" if addr[char] > 0x0F else "0") + hex(addr[char])[2:]
- return rev_str
+def address_repr(buf, reverse: bool = True, delimit: str = "") -> str:
+ """Convert a buffer into a hexlified string."""
+ order = range(len(buf) - 1, -1, -1) if reverse else range(len(buf))
+ return delimit.join(["%02X" % buf[byte] for byte in order])
class RF24:
"""A driver class for the nRF24L01(+) transceiver radios."""
def __init__(self, spi, csn, ce_pin, spi_frequency=10000000):
- self._spi = SPIDevice(
- spi, chip_select=csn, baudrate=spi_frequency, extra_clocks=8
- )
- self.ce_pin = ce_pin
- self.ce_pin.switch_to_output(value=False) # pre-empt standby-I mode
- self._status = 0 # status byte returned on all SPI transactions
+ 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
# pre-configure the CONFIGURE register:
- # 0x0E = IRQs are all enabled, CRC is enabled with 2 bytes, and
- # power up in TX mode
- self._config = 0x0E
+ # 0x0E = all IRQs enabled, CRC is 2 bytes, and power up in TX mode
+ self._status, self._config, self._spi = (0, 0x0E, None)
+ # setup SPI
+ if type(spi).__name__.endswith("SpiDev"):
+ self._spi = SPIDevCtx(spi, csn, spi_frequency=spi_frequency)
+ else:
+ self._spi = SPIDevice(spi, chip_select=csn, baudrate=spi_frequency)
self._reg_write(CONFIGURE, self._config)
- if self._reg_read(CONFIGURE) & 3 != 2:
- raise RuntimeError("nRF24L01 Hardware not responding")
- # init shadow copy of RX addresses for all pipes for context manager
- self._pipes = [bytearray(5), bytearray(5), 0xC3, 0xC4, 0xC5, 0xC6]
- # _open_pipes attribute reflects only RX state on each pipe
- self._open_pipes = 0 # 0 = all pipes closed
+ if self._reg_read(CONFIGURE) != self._config:
+ raise RuntimeError("radio hardware not responding")
for i in range(6): # capture RX addresses from registers
if i < 2:
- self._pipes[i] = self._reg_read_bytes(RX_ADDR_P0 + i)
+ self._pipes.append(self._reg_read_bytes(RX_ADDR_P0 + i))
else:
- self._pipes[i] = self._reg_read(RX_ADDR_P0 + i)
+ self._pipes.append(self._reg_read(RX_ADDR_P0 + i))
# test is nRF24L01 is a plus variant using a command specific to
# non-plus variants
- self._is_plus_variant = False
- b4_toggle = self._reg_read(TX_FEATURE)
- # derelict ACTIVATE command toggles bits in the TX_FEATURE register
- self._reg_write(0x50, 0x73)
+ self._open_pipes, self._is_plus_variant = (0, False) # close all RX pipes
+ self._features = self._reg_read(TX_FEATURE)
+ self._reg_write(0x50, 0x73) # derelict command toggles TX_FEATURE register
after_toggle = self._reg_read(TX_FEATURE)
- if b4_toggle == after_toggle:
+ if self._features == after_toggle:
self._is_plus_variant = True
- if not after_toggle: # if features are disabled
+ elif not after_toggle: # if features are disabled
self._reg_write(0x50, 0x73) # ensure they're enabled
+ # pre-configure features for TX operations:
+ # 5 = enable dynamic_payloads, disable custom ack payloads, &
+ # allow ask_no_ack command
+ self._features = 5
# init shadow copy of last RX_ADDR_P0 written to pipe 0 needed as
# open_tx_pipe() appropriates pipe 0 for ACK packet
self._pipe0_read_addr = None
# shadow copy of the TX_ADDRESS
self._tx_address = self._reg_read_bytes(TX_ADDRESS)
# pre-configure the SETUP_RETR register
- self._retry_setup = 0x53 # ard = 1500; arc = 3
+ self._retry_setup = 0x5F # ard = 1500; arc = 15
# pre-configure the RF_SETUP register
self._rf_setup = 0x07 # 1 Mbps data_rate, and 0 dbm pa_level
# pre-configure dynamic_payloads & auto_ack
- self._dyn_pl = 0x3F # 0x3F = enable dynamic_payloads on all pipes
- self._aa = 0x3F # 0x3F = enable auto_ack on all pipes
- # pre-configure features for TX operations:
- # 5 = enable dynamic_payloads, disable custom ack payloads, &
- # allow ask_no_ack command
- self._features = 5
+ self._dyn_pl, self._aa = (0x3F,) * 2 # 0x3F = enable feature on all pipes
self._channel = 76 # 2.476 GHz
self._addr_len = 5 # 5-byte long addresses
self._pl_len = [32] * 6 # 32-byte static payloads for all pipes
@@ -113,8 +104,10 @@ def __init__(self, spi, csn, ce_pin, spi_frequency=10000000):
self.clear_status_flags()
def __enter__(self):
- self.ce_pin.value = False
- self._reg_write(CONFIGURE, self._config & 0x7C)
+ self._ce_pin.value = False
+ self._config |= 2
+ self._reg_write(CONFIGURE, self._config)
+ # time.sleep(0.00015) # let the rest of this function be the delay
self._reg_write(RF_PA_RATE, self._rf_setup)
self._reg_write(OPEN_PIPES, self._open_pipes)
self._reg_write(DYN_PL_LEN, self._dyn_pl)
@@ -126,70 +119,87 @@ def __enter__(self):
self._reg_write_bytes(RX_ADDR_P0 + i, addr)
else:
self._reg_write(RX_ADDR_P0 + i, addr)
+ self.set_payload_length(self._pl_len[i], i)
self._reg_write_bytes(TX_ADDRESS, self._tx_address)
self._reg_write(0x05, self._channel)
self._reg_write(0x03, self._addr_len - 2)
- for i, val in enumerate(self._pl_len):
- self.set_payload_length(val, i)
return self
def __exit__(self, *exc):
- self.ce_pin.value = False
- self.power = False
+ self._ce_pin.value = False
+ self._config &= 0x7D # power off radio
+ self._reg_write(CONFIGURE, self._config)
+ time.sleep(0.00015)
return False
- # pylint: disable=no-member
- def _reg_read(self, reg):
- out_buf = bytes([reg, 0])
+ @property
+ def ce_pin(self):
+ """Control the radio's CE pin (for advanced usage)"""
+ return self._ce_pin.value
+
+ @ce_pin.setter
+ def ce_pin(self, val):
+ self._ce_pin.value = val
+
+ def _reg_read(self, reg: int) -> int:
in_buf = bytearray([0, 0])
with self._spi as spi:
- spi.write_readinto(out_buf, in_buf)
+ # 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]
- def _reg_read_bytes(self, reg, buf_len=5):
+ def _reg_read_bytes(self, reg: int, buf_len: int = 5) -> bytearray:
in_buf = bytearray(buf_len + 1)
- out_buf = bytes([reg]) + b"\0" * buf_len
with self._spi as spi:
- spi.write_readinto(out_buf, in_buf)
+ # time.sleep(0.000005)
+ spi.write_readinto(bytes([reg] + [0] * buf_len), in_buf)
self._status = in_buf[0]
+ # print("SPI read {} bytes from {} {}".format(
+ # buf_len, ("%02X" % reg), address_repr(in_buf[1:], 0)
+ # ))
return in_buf[1:]
- def _reg_write_bytes(self, reg, out_buf):
- out_buf = bytes([0x20 | reg]) + out_buf
- in_buf = bytearray(len(out_buf))
+ def _reg_write_bytes(self, reg: int, out_buf):
+ in_buf = bytearray(len(out_buf) + 1)
with self._spi as spi:
- spi.write_readinto(out_buf, in_buf)
+ # time.sleep(0.000005)
+ spi.write_readinto(bytes([0x20 | reg]) + out_buf, in_buf)
self._status = in_buf[0]
+ # print("SPI write {} bytes to {} {}".format(
+ # len(out_buf), ("%02X" % reg), address_repr(out_buf, 0)
+ # ))
- def _reg_write(self, reg, value=None):
+ def _reg_write(self, reg: int, value: int = None):
out_buf = bytes([reg])
if value is not None:
- out_buf = bytes([0x20 | reg, value])
+ out_buf = bytes([(0x20 if reg != 0x50 else 0) | reg, value])
in_buf = bytearray(len(out_buf))
with self._spi as spi:
+ # time.sleep(0.000005)
spi.write_readinto(out_buf, in_buf)
self._status = in_buf[0]
-
- # pylint: enable=no-member
+ # if reg != 0xFF:
+ # print(
+ # "SPI write", "command" if value is None else "1 byte to",
+ # ("%02X" % reg), "" if value is None else ("%02X" % value)
+ # )
@property
- def address_length(self):
- """This `int` attribute specifies the length (in bytes) of addresses
- to be used for RX/TX pipes."""
- return self._reg_read(0x03) + 2
+ def address_length(self) -> int:
+ """This `int` is the length (in bytes) used of RX/TX addresses."""
+ self._addr_len = self._reg_read(0x03) + 2
+ return self._addr_len
@address_length.setter
- def address_length(self, length):
- if not 3 <= length <= 5:
- raise ValueError("address_length can only be set in range [3, 5] bytes")
- self._addr_len = int(length)
- self._reg_write(0x03, length - 2)
-
- def open_tx_pipe(self, address):
- """This function is used to open a data pipe for OTA (over the air)
- TX transmissions."""
- if self._aa & 1:
+ 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:
+ """Open a data pipe for TX transmissions."""
+ if self._pipe0_read_addr != address and self._aa & 1:
for i, val in enumerate(address):
self._pipes[0][i] = val
self._reg_write_bytes(RX_ADDR_P0, address)
@@ -197,21 +207,17 @@ def open_tx_pipe(self, address):
self._tx_address[i] = val
self._reg_write_bytes(TX_ADDRESS, address)
- def close_rx_pipe(self, pipe_number):
- """This function is used to close a specific data pipe from OTA (over
- the air) RX transmissions."""
+ def close_rx_pipe(self, pipe_number: int) -> None:
+ """Close a specific data pipe from RX transmissions."""
if pipe_number < 0 or pipe_number > 5:
raise IndexError("pipe number must be in range [0, 5]")
- self._open_pipes = self._reg_read(OPEN_PIPES)
+ self._open_pipes = self._reg_read(OPEN_PIPES) & ~(1 << pipe_number)
if not pipe_number:
self._pipe0_read_addr = None
- if self._open_pipes & (1 << pipe_number):
- self._open_pipes = self._open_pipes & ~(1 << pipe_number)
- self._reg_write(OPEN_PIPES, self._open_pipes)
+ self._reg_write(OPEN_PIPES, self._open_pipes)
- def open_rx_pipe(self, pipe_number, address):
- """This function is used to open a specific data pipe for OTA (over
- the air) RX transmissions."""
+ def open_rx_pipe(self, pipe_number: int, address) -> 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]")
if not address:
@@ -229,52 +235,54 @@ def open_rx_pipe(self, pipe_number, address):
self._reg_write(OPEN_PIPES, self._open_pipes)
@property
- def listen(self):
- """An attribute to represent the nRF24L01 primary role as a radio."""
+ def listen(self) -> bool:
+ """This attribute is the primary role as a radio."""
return self.power and bool(self._config & 1)
@listen.setter
- def listen(self, is_rx):
- self.ce_pin.value = 0
+ def listen(self, is_rx: bool):
+ self._ce_pin.value = False
+ self._config = self._config & 0xFC | (2 + bool(is_rx))
+ self._reg_write(CONFIGURE, self._config)
+ start_timer = time.monotonic_ns()
if is_rx:
- if self._pipe0_read_addr is not None and self._aa & 1:
+ self._ce_pin.value = True
+ if (
+ self._pipe0_read_addr is not None
+ and self._pipe0_read_addr != self.address(0)
+ ):
for i, val in enumerate(self._pipe0_read_addr):
self._pipes[0][i] = val
self._reg_write_bytes(RX_ADDR_P0, self._pipe0_read_addr)
- elif self._pipe0_read_addr is None:
- self.close_rx_pipe(0)
- self._config = (self._config & 0xFC) | 3
- self._reg_write(CONFIGURE, self._config)
- time.sleep(0.00015) # mandatory wait to power up radio
- self.clear_status_flags()
- self.ce_pin.value = 1 # mandatory pulse is > 130 µs
- time.sleep(0.00013)
+ elif self._pipe0_read_addr is None and self._open_pipes & 1:
+ self._open_pipes &= 0x3E # close_rx_pipe(0) is slower
+ self._reg_write(OPEN_PIPES, self._open_pipes)
else:
if self._features & 6 == 6 and ((self._aa & self._dyn_pl) & 1):
self.flush_tx()
- if self._aa & 1:
+ if self._aa & 1 and not self._open_pipes & 1:
self._open_pipes |= 1
self._reg_write(OPEN_PIPES, self._open_pipes)
- self._config = self._config & 0xFE | 2
- self._reg_write(CONFIGURE, self._config)
- time.sleep(0.00016)
-
- def available(self):
- """Returns a bool describing if there is a payload in the RX FIFO"""
- return self.update() and self.pipe is not None
-
- def any(self):
- """This function checks if the nRF24L01 has received any data at all,
- and then reports the next available payload's length (in bytes)."""
- if self.available():
+ # mandatory wait time is 130 µs
+ delta_time = time.monotonic_ns() - start_timer
+ if delta_time < 150000:
+ time.sleep((150000 - delta_time) / 1000000000)
+
+ 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
+
+ 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._features & 4:
- return self._reg_read(0x60)
- return self._pl_len[self.pipe]
+ return last_dyn_size
+ return self._pl_len[(self._status >> 1) & 7]
return 0
- def read(self, length=None):
- """This function is used to retrieve the next available payload in the
- RX FIFO buffer, then clears the `irq_dr` status flag."""
+ def read(self, length: int = None) -> bytearray:
+ """This function is used to retrieve data from the RX FIFO."""
return_size = length if length is not None else self.any()
if not return_size:
return None
@@ -282,199 +290,212 @@ def read(self, length=None):
self.clear_status_flags(True, False, False)
return result
- def send(self, buf, ask_no_ack=False, force_retry=0, send_only=False):
+ def send(
+ self,
+ buf,
+ ask_no_ack: bool = False,
+ force_retry: int = 0,
+ send_only: bool = False,
+ ):
"""This blocking function is used to transmit payload(s)."""
- self.ce_pin.value = 0
+ self._ce_pin.value = False
if isinstance(buf, (list, tuple)):
result = []
for b in buf:
result.append(self.send(b, ask_no_ack, force_retry, send_only))
return result
- self.flush_tx()
- if not send_only and self.pipe is not None:
+ if self._status & 0x10 or self._status & 1:
+ self.flush_tx()
+ if not send_only and self._status >> 1 & 7 < 6:
self.flush_rx()
+ up_cnt = 0
self.write(buf, ask_no_ack)
while not self._status & 0x30:
- self.update()
- self.ce_pin.value = 0
- result = self.irq_ds
- if self.irq_df:
- for _ in range(force_retry):
- result = self.resend(send_only)
- if result is None or result:
- break
+ up_cnt += self.update()
+ result = bool(self._status & 0x20)
+ # print("send did {} updates. flags: {}".format(up_cnt, self._status >> 4))
+ while force_retry and not result:
+ result = self.resend(send_only)
+ force_retry -= 1
if self._status & 0x60 == 0x60 and not send_only:
result = self.read()
+ # self._ce_pin.value = False
return result
@property
- def tx_full(self):
- """An attribute to represent the nRF24L01's status flag signaling
- that the TX FIFO buffer is full. (read-only)"""
+ def tx_full(self) -> bool:
+ """An `bool` to represent if the TX FIFO is full. (read-only)"""
return bool(self._status & 1)
@property
def pipe(self):
- """The identifying number of the data pipe that received
- the next available payload in the RX FIFO buffer. (read only)"""
- result = (self._status & 0x0E) >> 1
- if result <= 5:
+ """The number of the data pipe that received the next available
+ payload in the RX FIFO. (read only)"""
+ result = self._status >> 1 & 7
+ if result <= 5 and self._spi is not None:
return result
return None
@property
- def irq_dr(self):
- """A `bool` that represents the "Data Ready" interrupted flag.
- (read-only)"""
+ def irq_dr(self) -> bool:
+ """A `bool` that represents the "Data Ready" interrupted flag. (read-only)"""
return bool(self._status & 0x40)
@property
- def irq_ds(self):
- """A `bool` that represents the "Data Sent" interrupted flag.
- (read-only)"""
+ def irq_ds(self) -> bool:
+ """A `bool` that represents the "Data Sent" interrupted flag. (read-only)"""
return bool(self._status & 0x20)
@property
- def irq_df(self):
- """A `bool` that represents the "Data Failed" interrupted flag.
- (read-only)"""
+ def irq_df(self) -> bool:
+ """A `bool` that represents the "Data Failed" interrupted flag. (read-only)"""
return bool(self._status & 0x10)
- def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True):
+ def update(self) -> True:
+ """This function gets an updated status byte over SPI."""
+ self._reg_write(0xFF)
+ return True
+
+ def clear_status_flags(
+ self, data_recv: bool = True, data_sent: bool = True, data_fail: bool = True
+ ):
"""This clears the interrupt flags in the status register."""
- config = bool(data_recv) << 6 | bool(data_sent) << 5 | bool(data_fail) << 4
- self._reg_write(7, config)
+ config = bool(data_recv) << 6 | bool(data_sent) << 5
+ self._reg_write(7, config | bool(data_fail) << 4)
- def interrupt_config(self, data_recv=True, data_sent=True, data_fail=True):
+ def interrupt_config(
+ self, data_recv: bool = True, data_sent: bool = True, data_fail: bool = True
+ ):
"""Sets the configuration of the nRF24L01's IRQ pin. (write-only)"""
self._config = (self._reg_read(CONFIGURE) & 0x0F) | (not data_recv) << 6
self._config |= (not data_fail) << 4 | (not data_sent) << 5
self._reg_write(CONFIGURE, self._config)
- def print_details(self, dump_pipes=False):
- """This debuggung function aggregates and outputs all status/condition
- related information from the nRF24L01."""
+ def print_details(self, dump_pipes: bool = False):
+ """This debuggung function outputs all details about the nRF24L01."""
observer = self._reg_read(8)
- print("Is a plus variant_________{}".format(self.is_plus_variant))
- print(
- "Channel___________________{} ~ {} GHz".format(
- self.channel, (self.channel + 2400) / 1000
- )
+ _fifo = self._reg_read(0x17)
+ self._config = self._reg_read(CONFIGURE)
+ self._rf_setup = self._reg_read(RF_PA_RATE)
+ self._retry_setup = self._reg_read(SETUP_RETR)
+ self._channel = self.channel
+ self._addr_len = self._reg_read(0x03) + 2
+ self._features = self._reg_read(TX_FEATURE)
+ self._aa = self._reg_read(AUTO_ACK)
+ self._dyn_pl = self._reg_read(DYN_PL_LEN)
+ _crc = (
+ (2 if self._config & 4 else 1)
+ if self._aa
+ else max(0, ((self._config & 0x0C) >> 2) - 1)
)
- print(
- "RF Data Rate______________{} {}".format(
- self.data_rate, "Mbps" if self.data_rate != 250 else "Kbps"
- )
+ d_rate = self._rf_setup & 0x28
+ d_rate = (2 if d_rate == 8 else 250) if d_rate else 1
+ _pa_level = (3 - ((self._rf_setup & 6) >> 1)) * -6
+ dyn_p = (
+ ("_Enabled" if self._dyn_pl else "Disabled")
+ if self._dyn_pl == 0x3F or not self._dyn_pl
+ else "0b" + "0" * (8 - len(bin(self._dyn_pl))) + bin(self._dyn_pl)[2:]
+ )
+ auto_a = (
+ ("Enabled" if self._aa else "Disabled")
+ if self._aa == 0x3F or not self._aa
+ else "0b" + "0" * (8 - len(bin(self._aa))) + bin(self._aa)[2:]
+ )
+ pwr = (
+ ("Standby-II" if self._ce_pin.value else "Standby-I")
+ if self._config & 2
+ else "Off"
)
- print("RF Power Amplifier________{} dbm".format(self.pa_level))
+ print(f"Is a plus variant_________{self.is_plus_variant}")
print(
- "RF Low Noise Amplifier____{}".format(
- "Enabled" if self.is_lna_enabled else "Disabled"
- )
+ f"Channel___________________{self._channel}",
+ f"~ {(self._channel + 2400) / 1000} GHz",
)
- print("CRC bytes_________________{}".format(self.crc))
- print("Address length____________{} bytes".format(self.address_length))
- print("TX Payload lengths________{} bytes".format(self.payload_length))
- print("Auto retry delay__________{} microseconds".format(self.ard))
- print("Auto retry attempts_______{} maximum".format(self.arc))
- print("Re-use TX FIFO____________{}".format(bool(self._reg_read(0x17) & 64)))
print(
- "Packets lost on current channel_____________________{}".format(
- (observer & 0xF0) >> 4
- )
+ f"RF Data Rate______________{d_rate}", "Mbps" if d_rate != 250 else "Kbps"
)
+ print(f"RF Power Amplifier________{_pa_level} dbm")
print(
- "Retry attempts made for last transmission___________{}".format(
- observer & 0x0F
+ "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(
- "IRQ on Data Ready__{} Data Ready___________{}".format(
- "_Enabled" if not self._config & 0x40 else "Disabled", self.irq_dr
- )
+ f"Auto retry delay__________"
+ f"{((self._retry_setup & 0xF0) >> 4) * 250 + 250} microseconds",
)
+ 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 Fail___{} Data Failed__________{}".format(
- "_Enabled" if not self._config & 0x10 else "Disabled", self.irq_df
- )
+ "IRQ on Data Ready__{}abled".format("Dis" if self._config & 64 else "_En"),
+ f" Data Ready___________{self.irq_dr}",
)
print(
- "IRQ on Data Sent___{} Data Sent____________{}".format(
- "_Enabled" if not self._config & 0x20 else "Disabled", self.irq_ds
- )
+ "IRQ on Data Fail___{}abled".format("Dis" if self._config & 16 else "_En"),
+ f" Data Failed__________{self.irq_df}",
)
print(
- "TX FIFO full__________{} TX FIFO empty________{}".format(
- "_True" if self.tx_full else "False", self.fifo(True, True)
- )
+ "IRQ on Data Sent___{}abled".format("Dis" if self._config & 32 else "_En"),
+ f" Data Sent____________{self.irq_ds}",
)
print(
- "RX FIFO full__________{} RX FIFO empty________{}".format(
- "_True" if self.fifo(False, False) else "False", self.fifo(False, True)
- )
+ "TX FIFO full__________{}e".format("_Tru" if _fifo & 0x20 else "Fals"),
+ f" TX FIFO empty________{bool(_fifo & 0x10)}",
)
print(
- "Ask no ACK_________{} Custom ACK Payload___{}".format(
- "_Allowed" if self._features & 1 else "Disabled",
- "Enabled" if self.ack else "Disabled",
- )
+ "RX FIFO full__________{}e".format("_Tru" if _fifo & 2 else "Fals"),
+ f" RX FIFO empty________{bool(_fifo & 1)}",
)
print(
- "Dynamic Payloads___{} Auto Acknowledgment__{}".format(
- "_Enabled"
- if self._dyn_pl == 0x3F
- else (
- bin(self._dyn_pl).replace(
- "b", "b" + "0" * (8 - len(bin(self._dyn_pl)))
- )
- if self._dyn_pl
- else "Disabled"
- ),
- "Enabled"
- if self._aa == 0x3F
- else (
- bin(self._aa).replace("b", "b" + "0" * (8 - len(bin(self._aa))))
- if self._aa
- else "Disabled"
- ),
- )
+ "Ask no ACK_________{}ed Custom ACK Payload___{}abled".format(
+ "_Allow" if self._features & 1 else "Disabl",
+ "En" if self._features & 2 else "Dis",
+ ),
)
+ print(f"Dynamic Payloads___{dyn_p} Auto Acknowledgment__{auto_a}")
print(
- "Primary Mode_____________{} Power Mode___________{}".format(
- "RX" if self.listen else "TX",
- ("Standby-II" if self.ce_pin.value else "Standby-I")
- if self._config & 2
- else "Off",
- )
+ "Primary Mode_____________{}X".format("R" if self._config & 1 else "T"),
+ f" Power Mode___________{pwr}",
)
if dump_pipes:
- self._dump_pipes()
+ self.print_pipes()
- def _dump_pipes(self):
- print("TX address____________", "0x" + address_repr(self.address()))
+ def print_pipes(self):
+ """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)
+ self._tx_address = self._reg_read_bytes(TX_ADDRESS)
+ for i in range(6):
+ if i < 2:
+ self._pipes[i] = self._reg_read_bytes(RX_ADDR_P0 + i)
+ else:
+ self._pipes[i] = self._reg_read(RX_ADDR_P0 + i)
+ self._pl_len[i] = self._reg_read(RX_PL_LENG + i)
+ print(f"TX address____________ 0x{address_repr(self.address())}")
for i in range(6):
is_open = self._open_pipes & (1 << i)
print(
- "Pipe {} ({}) bound: {}".format(
- i,
- " open " if is_open else "closed",
- "0x" + address_repr(self.address(i))
- )
+ "Pipe {} ({}) bound: 0x{}".format(
+ i, " open " if is_open else "closed", address_repr(self.address(i))
+ ),
)
- if is_open:
- print("\t\texpecting", self._pl_len[i], "byte static payloads")
+ if is_open and not self._dyn_pl & (1 << i):
+ print(f"\t\texpecting {self._pl_len[i]} byte static payloads")
@property
- def is_plus_variant(self):
- """A `bool` attribute to descibe if the nRF24L01 is a plus variant or
- not (read-only)."""
+ def is_plus_variant(self) -> bool:
+ """A `bool` descibing if the nRF24L01 is a plus variant or not (read-only)."""
return self._is_plus_variant
@property
- def dynamic_payloads(self):
- """This `int` attribute controls the nRF24L01's dynamic payload
- length feature for any or all pipes."""
+ def dynamic_payloads(self) -> int:
+ """This `int` attribute is the dynamic payload length feature for
+ any/all pipes."""
self._dyn_pl = self._reg_read(DYN_PL_LEN)
return self._dyn_pl
@@ -491,13 +512,12 @@ 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("dynamic_payloads: {} is an invalid input" % enable)
- if self._dyn_pl:
- self._features = (self._features & 3) | (bool(self._dyn_pl) << 2)
- self._reg_write(TX_FEATURE, self._features)
+ raise ValueError(f"dynamic_payloads: {enable} is an invalid input")
+ 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)
- def set_dynamic_payloads(self, enable, pipe_number=None):
+ def set_dynamic_payloads(self, enable: bool, pipe_number: int = None):
"""Control the dynamic payload feature for a specific data pipe."""
if pipe_number is None:
self.dynamic_payloads = bool(enable)
@@ -507,17 +527,15 @@ def set_dynamic_payloads(self, enable, pipe_number=None):
else:
raise IndexError("pipe_number must be in range [0, 5]")
- def get_dynamic_payloads(self, pipe_number=0):
- """Returns a `bool` describing the setting of the dynamic payload
- feature about a specific data pipe."""
+ def get_dynamic_payloads(self, pipe_number: int = 0) -> bool:
+ """Returns a `bool` describing the dynamic payload feature about a pipe."""
if 0 <= pipe_number <= 5:
return bool(self.dynamic_payloads & (1 << pipe_number))
raise IndexError("pipe_number must be in range [0, 5]")
@property
- def payload_length(self):
- """This `int` attribute specifies the length (in bytes) of static
- payloads for any or all pipes."""
+ def payload_length(self) -> int:
+ """This `int` attribute is the length of static payloads for any/all pipes."""
return self._pl_len[0]
@payload_length.setter
@@ -531,7 +549,7 @@ def payload_length(self, length):
self._pl_len[i] = min(32, val)
self._reg_write(RX_PL_LENG + i, self._pl_len[i])
- def set_payload_length(self, length, pipe_number=None):
+ def set_payload_length(self, length: int, pipe_number: int = None):
"""Sets the static payload length feature for each/all data pipes."""
if pipe_number is None:
self.payload_length = length
@@ -539,60 +557,57 @@ def set_payload_length(self, length, pipe_number=None):
self._pl_len[pipe_number] = max(1, min(32, length))
self._reg_write(RX_PL_LENG + pipe_number, length)
- def get_payload_length(self, pipe_number=0):
- """Returns an `int` describing the current setting of a specified data
- pipe's expected static payload length."""
+ def get_payload_length(self, pipe_number: int = 0) -> int:
+ """Returns an `int` describing the specified data pipe's static
+ payload length."""
self._pl_len[pipe_number] = self._reg_read(RX_PL_LENG + pipe_number)
return self._pl_len[pipe_number]
@property
- def arc(self):
- """This `int` attribute specifies the nRF24L01's number of attempts
- to re-transmit TX payload when acknowledgment packet is not received.
- """
+ def arc(self) -> int:
+ """This `int` attribute specifies the number of attempts to
+ re-transmit TX payload when ACK packet is not received."""
self._retry_setup = self._reg_read(SETUP_RETR)
return self._retry_setup & 0x0F
@arc.setter
- def arc(self, count):
+ def arc(self, count: int):
count = max(0, min(int(count), 15))
self._retry_setup = (self._retry_setup & 0xF0) | count
self._reg_write(SETUP_RETR, self._retry_setup)
@property
- def ard(self):
- """This `int` attribute specifies the nRF24L01's delay (in
- microseconds) between attempts to automatically re-transmit the
- TX payload when an expected acknowledgement (ACK) packet is not
- received."""
+ def ard(self) -> int:
+ """This `int` attribute specifies the delay (in microseconds) between attempts
+ to automatically re-transmit the TX payload when no ACK packet is received."""
self._retry_setup = self._reg_read(SETUP_RETR)
return ((self._retry_setup & 0xF0) >> 4) * 250 + 250
@ard.setter
- def ard(self, delta):
+ def ard(self, delta: int):
delta = max(250, min(delta, 4000))
self._retry_setup = (self._retry_setup & 15) | int((delta - 250) / 250) << 4
self._reg_write(SETUP_RETR, self._retry_setup)
- def set_auto_retries(self, delay, count):
+ def set_auto_retries(self, delay: int, count: int):
"""set the `ard` & `arc` attributes with 1 function."""
delay = int((max(250, min(delay, 4000)) - 250) / 250) << 4
self._retry_setup = delay | max(0, min(int(count), 15))
self._reg_write(SETUP_RETR, self._retry_setup)
- def get_auto_retries(self):
+ def get_auto_retries(self) -> tuple:
"""get the `ard` & `arc` attributes with 1 function."""
return (self.ard, self._retry_setup & 0x0F)
@property
- def last_tx_arc(self):
+ def last_tx_arc(self) -> int:
"""Return the number of attempts made for last transission (read-only)."""
return self._reg_read(8) & 0x0F
@property
- def auto_ack(self):
- """This `int` attribute controls the nRF24L01's automatic
- acknowledgment feature for any or all pipes."""
+ def auto_ack(self) -> int:
+ """This `int` attribute is the automatic acknowledgment feature for
+ any/all pipes."""
self._aa = self._reg_read(AUTO_ACK)
return self._aa
@@ -603,19 +618,16 @@ def auto_ack(self, enable):
elif isinstance(enable, int):
self._aa = 0x3F & enable
elif isinstance(enable, (list, tuple)):
+ self._aa = self._reg_read(AUTO_ACK)
for i, val in enumerate(enable):
- self._aa = self._reg_read(AUTO_ACK)
if i < 6 and val >= 0: # skip pipe if val is negative
self._aa = (self._aa & ~(1 << i)) | (bool(val) << i)
else:
- raise ValueError("auto_ack: {} is not a valid input" % enable)
- if bool(self._aa & 1) != bool(self._aa & 0x3E) and self._aa & 0x3E:
- self._aa |= 1
+ raise ValueError(f"auto_ack: {enable} is not a valid input")
self._reg_write(AUTO_ACK, self._aa)
- def set_auto_ack(self, enable, pipe_number=None):
- """Control the automatic acknowledgement feature for a specific data
- pipe."""
+ def set_auto_ack(self, enable: bool, pipe_number: int):
+ """Control the `auto_ack` feature for a specific data pipe."""
if pipe_number is None:
self.auto_ack = bool(enable)
elif 0 <= pipe_number <= 5:
@@ -624,26 +636,23 @@ def set_auto_ack(self, enable, pipe_number=None):
else:
raise IndexError("pipe_number must be in range [0, 5]")
- def get_auto_ack(self, pipe_number=0):
- """Returns a `bool` describing the automatic acknowledgement feature
- setting about a specific data pipe."""
+ def get_auto_ack(self, pipe_number: int) -> bool:
+ """Returns a `bool` describing the `auto_ack` feature about a data pipe."""
if 0 <= pipe_number <= 5:
self._aa = self._reg_read(AUTO_ACK)
return bool(self._aa & (1 << pipe_number))
raise IndexError("pipe_number must be in range [0, 5]")
@property
- def ack(self):
- """This `bool` attribute represents the status of the nRF24L01's
- capability to use custom payloads as part of the automatic
- acknowledgment (ACK) packet."""
+ def ack(self) -> bool:
+ """Represents use of custom payloads as part of the ACK packet."""
self._aa = self._reg_read(AUTO_ACK)
self._dyn_pl = self._reg_read(DYN_PL_LEN)
self._features = self._reg_read(TX_FEATURE)
return bool((self._features & 6) == 6 and ((self._aa & self._dyn_pl) & 1))
@ack.setter
- def ack(self, enable):
+ def ack(self, enable: bool):
if bool(enable):
self.set_auto_ack(True, 0)
self._dyn_pl = self._dyn_pl & 0x3E | 1
@@ -652,9 +661,8 @@ def ack(self, enable):
self._features = self._features & 5 | bool(enable) << 1
self._reg_write(TX_FEATURE, self._features)
- def load_ack(self, buf, pipe_number):
- """This allows the MCU to specify a payload to be allocated into the
- TX FIFO buffer for use on a specific data pipe."""
+ def load_ack(self, buf, pipe_number: int) -> bool:
+ """Load a payload into the TX FIFO for use on a specific data pipe."""
if pipe_number < 0 or pipe_number > 5:
raise IndexError("pipe_number must be in range [0, 5]")
if not buf or len(buf) > 32:
@@ -667,55 +675,46 @@ def load_ack(self, buf, pipe_number):
return False
@property
- def allow_ask_no_ack(self):
- """Allow or disallow the use of ``ask_no_ack`` parameter to `send()` &
- `write()`."""
+ def allow_ask_no_ack(self) -> bool:
+ """Allow or disable ``ask_no_ack`` parameter to `send()` & `write()`."""
self._features = self._reg_read(TX_FEATURE)
return bool(self._features & 1)
@allow_ask_no_ack.setter
- def allow_ask_no_ack(self, enable):
- self._features = self._features & 6 | bool(enable)
+ def allow_ask_no_ack(self, enable: bool):
+ self._features = self._reg_read(TX_FEATURE) & 6 | bool(enable)
self._reg_write(TX_FEATURE, self._features)
@property
- def data_rate(self):
- """This `int` attribute specifies the nRF24L01's frequency data rate
- for OTA (over the air) transmissions."""
+ def data_rate(self) -> int:
+ """This `int` attribute specifies the RF data rate."""
self._rf_setup = self._reg_read(RF_PA_RATE)
rf_setup = self._rf_setup & 0x28
return (2 if rf_setup == 8 else 250) if rf_setup else 1
@data_rate.setter
- def data_rate(self, speed):
+ def data_rate(self, speed: int):
if not speed in (1, 2, 250):
raise ValueError("data_rate must be 1 (Mbps), 2 (Mbps), or 250 (kbps)")
- if not self.is_plus_variant and speed == 250:
- raise NotImplementedError(
- "250 kbps data rate is not available for the non-plus "
- "variants of the nRF24L01 transceivers."
- )
- if self.data_rate != speed:
- speed = 0 if speed == 1 else (0x20 if speed != 2 else 8)
- self._rf_setup = self._rf_setup & 0xD7 | speed
- self._reg_write(RF_PA_RATE, self._rf_setup)
+ speed = 0 if speed == 1 else (0x20 if speed != 2 else 8)
+ self._rf_setup = self._reg_read(RF_PA_RATE) & 0xD7 | speed
+ self._reg_write(RF_PA_RATE, self._rf_setup)
@property
- def channel(self):
+ def channel(self) -> int:
"""This `int` attribute specifies the nRF24L01's frequency."""
return self._reg_read(5)
@channel.setter
- def channel(self, channel):
+ def channel(self, channel: int):
if not 0 <= int(channel) <= 125:
raise ValueError("channel can only be set in range [0, 125]")
self._channel = int(channel)
self._reg_write(5, self._channel)
@property
- def crc(self):
- """This `int` attribute specifies the nRF24L01's CRC (cyclic
- redundancy checking) encoding scheme in terms of byte length."""
+ def crc(self) -> int:
+ """This `int` attribute specifies the CRC checksum length in bytes."""
self._config = self._reg_read(CONFIGURE)
self._aa = self._reg_read(AUTO_ACK)
if self._aa:
@@ -723,29 +722,27 @@ def crc(self):
return max(0, ((self._config & 0x0C) >> 2) - 1)
@crc.setter
- def crc(self, length):
+ def crc(self, length: int):
length = min(2, abs(int(length)))
length = (length + 1) << 2 if length else 0
self._config = self._config & 0x73 | length
- self._reg_write(0, self._config)
+ self._reg_write(CONFIGURE, self._config)
@property
- def power(self):
+ def power(self) -> bool:
"""This `bool` attribute controls the power state of the nRF24L01."""
self._config = self._reg_read(CONFIGURE)
return bool(self._config & 2)
@power.setter
- def power(self, is_on):
- self._config = self._reg_read(CONFIGURE)
- if self.power != bool(is_on):
- self._config = self._config & 0x7D | bool(is_on) << 1
- self._reg_write(CONFIGURE, self._config)
- time.sleep(0.00016)
+ def power(self, is_on: bool):
+ self._config = self._reg_read(CONFIGURE) & 0x7D | bool(is_on) << 1
+ self._reg_write(CONFIGURE, self._config)
+ time.sleep(0.00015)
@property
- def pa_level(self):
- """This `int` attribute specifies the nRF24L01's power amplifier level (in dBm)."""
+ def pa_level(self) -> int:
+ """This `int` is the power amplifier level (in dBm)."""
self._rf_setup = self._reg_read(RF_PA_RATE)
return (3 - ((self._rf_setup & 6) >> 1)) * -6
@@ -761,50 +758,45 @@ def pa_level(self, power):
self._reg_write(RF_PA_RATE, self._rf_setup)
@property
- def is_lna_enabled(self):
- """A read-only `bool` attribute about the LNA (Low Noise Amplifier)
- gain feature."""
+ def is_lna_enabled(self) -> bool:
+ """A read-only `bool` attribute about the LNA gain feature."""
self._rf_setup = self._reg_read(RF_PA_RATE)
return bool(self._rf_setup & 1)
- def update(self):
- """This function is only used to get an updated status byte over SPI
- from the nRF24L01."""
- self._reg_write(0xFF)
- return True
-
- def resend(self, send_only=False):
- """Use this function to maunally re-send the previous payload in the
- top level (first out) of the TX FIFO buffer."""
- result = False
- if not self.fifo(True, True):
- self.ce_pin.value = 0
- if not send_only and self.pipe is not None:
- self.flush_rx()
- self.clear_status_flags()
- self._reg_write(0xE3)
- self.ce_pin.value = 1
- while not self._status & 0x70:
- self.update()
- self.ce_pin.value = 0
- result = self.irq_ds
- if self._status & 0x60 == 0x60 and not send_only:
- result = self.read()
+ def resend(self, send_only: bool = False):
+ """Manually re-send the first-out payload from TX FIFO buffers."""
+ if self.fifo(True, True):
+ return False
+ self._ce_pin.value = False
+ if not send_only and (self._status >> 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:
+ 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:
+ return self.read()
return result
- def write(self, buf, ask_no_ack=False, write_only=False):
- """This non-blocking function (when used as alternative to `send()`)
- is meant for asynchronous applications and can only handle one
- payload at a time as it is a helper function to `send()`."""
+ def write(self, buf, 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()
- if self.tx_full:
- return False
+ is_power_up = self._config & 2
if self._config & 3 != 2: # is radio powered up in TX mode?
- self._config = (self._reg_read(CONFIGURE) & 0x7C) | 2
+ self._config = (self._config & 0x7C) | 2
self._reg_write(CONFIGURE, self._config)
- time.sleep(0.00016)
+ if not is_power_up:
+ time.sleep(0.00015)
+ if self._status & 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))
@@ -815,28 +807,26 @@ def write(self, buf, ask_no_ack=False, write_only=False):
self._reg_write(TX_FEATURE, self._features)
self._reg_write_bytes(0xA0 | (bool(ask_no_ack) << 4), buf)
if not write_only:
- self.ce_pin.value = 1
+ self._ce_pin.value = True
return True
def flush_rx(self):
- """A helper function to flush the nRF24L01's RX FIFO buffer."""
+ """Flush all 3 levels of the RX FIFO."""
self._reg_write(0xE2)
def flush_tx(self):
- """A helper function to flush the nRF24L01's TX FIFO buffer."""
+ """Flush all 3 levels of the TX FIFO."""
self._reg_write(0xE1)
- def fifo(self, about_tx=False, check_empty=None):
- """This provides *some* precision determining the status of the TX/RX
- FIFO buffers. (read-only)"""
+ def fifo(self, about_tx: bool = False, check_empty: bool = None):
+ """This provides the status of the TX/RX FIFO buffers. (read-only)"""
_fifo, about_tx = (self._reg_read(0x17), bool(about_tx))
if check_empty is None:
return (_fifo & (0x30 if about_tx else 0x03)) >> (4 * about_tx)
return bool(_fifo & ((2 - bool(check_empty)) << (4 * about_tx)))
- def address(self, index=-1):
- """Returns the current address set to a specified data pipe or the TX
- address. (read-only)"""
+ def address(self, index: int = -1):
+ """Returns the current TX address or optionally RX address. (read-only)"""
if index > 5:
raise IndexError("index {} is out of bounds [0,5]".format(index))
if index < 0:
@@ -846,38 +836,34 @@ def address(self, index=-1):
return bytes([self._pipes[index]]) + self._pipes[1][1:]
@property
- def rpd(self):
- """This read-only attribute returns `True` if RPD (Received Power
- Detector) is triggered or `False` if not triggered."""
+ def rpd(self) -> bool:
+ """Returns `True` if signal was detected or `False` if not. (read-only)"""
return bool(self._reg_read(0x09))
def start_carrier_wave(self):
"""Starts a continuous carrier wave test."""
- self.power = 0
- self.ce_pin.value = 0
- self.power = 1
- self.listen = 0
+ self.power = False
+ self._ce_pin.value = False
+ self.power = True
+ self.listen = False
self._rf_setup |= 0x90
self._reg_write(RF_PA_RATE, self._rf_setup)
if not self.is_plus_variant:
- self.auto_ack = False
- self._retry_setup = 0
- self._reg_write(SETUP_RETR, self._retry_setup)
- self._tx_address = bytearray([0xFF] * 5)
- self._reg_write_bytes(TX_ADDRESS, self._tx_address)
+ self._reg_write(AUTO_ACK, 0)
+ self._reg_write(SETUP_RETR, 0)
+ self._reg_write_bytes(TX_ADDRESS, b"\xFF" * 5)
self._reg_write_bytes(0xA0, b"\xFF" * 32)
- self.crc = 0
- self.ce_pin.value = 1
+ self._reg_write(CONFIGURE, 0x73)
+ self._ce_pin.value = True
time.sleep(0.001)
- self.ce_pin.value = 0
- while self._status & 0x70:
- self.update()
+ self._ce_pin.value = False
+ self.clear_status_flags()
self._reg_write(0x17, 0x40)
- self.ce_pin.value = 1
+ self._ce_pin.value = True
def stop_carrier_wave(self):
"""Stops a continuous carrier wave test."""
- self.ce_pin.value = 0
- self.power = 0
+ self._ce_pin.value = False
+ self.power = False
self._rf_setup &= ~0x90
self._reg_write(RF_PA_RATE, self._rf_setup)
diff --git a/circuitpython_nrf24l01/rf24_lite.py b/circuitpython_nrf24l01/rf24_lite.py
index 7ef211b..9d23726 100644
--- a/circuitpython_nrf24l01/rf24_lite.py
+++ b/circuitpython_nrf24l01/rf24_lite.py
@@ -1,29 +1,31 @@
# see license and copyright information in rf24.py
# pylint: disable=missing-docstring
-__version__ = "0.0.0-auto.0"
-__repo__ = "https://github.com/2bndy5/CircuitPython_nRF24L01.git"
import time
-from adafruit_bus_device.spi_device import SPIDevice
+
+try:
+ from adafruit_bus_device.spi_device import SPIDevice
+except ImportError:
+ from .wrapper.upy_spi import SPIDevice
class RF24:
- def __init__(self, spi, csn, ce, spi_frequency=10000000):
+ def __init__(self, spi, csn, ce_pin, spi_frequency=10000000):
self._spi = SPIDevice(
spi, chip_select=csn, baudrate=spi_frequency, extra_clocks=8
)
- self.ce_pin = ce
- self.ce_pin.switch_to_output(value=False)
self._status = 0
self._reg_write(0, 0x0E)
if self._reg_read(0) & 3 != 2:
raise RuntimeError("nRF24L01 Hardware not responding")
+ self._ce_pin = ce_pin
+ self._ce_pin.switch_to_output(value=False)
self._reg_write(3, 3)
self._reg_write(6, 7)
self._reg_write(2, 0)
self._reg_write(0x1C, 0x3F)
self._reg_write(1, 0x3F)
self._reg_write(0x1D, 5)
- self._reg_write(4, 0x53)
+ self._reg_write(4, 0x5F)
self._pipe0_read_addr = None
self.channel = 76
self.payload_length = 32
@@ -33,47 +35,51 @@ def __init__(self, spi, csn, ce, spi_frequency=10000000):
# pylint: disable=no-member
def _reg_read(self, reg):
- out_buf = bytes([reg, 0])
in_buf = bytearray([0, 0])
with self._spi as spi:
- spi.write_readinto(out_buf, in_buf)
+ spi.write_readinto(bytes([reg, 0]), in_buf)
self._status = in_buf[0]
return in_buf[1]
def _reg_read_bytes(self, reg, buf_len=5):
in_buf = bytearray(buf_len + 1)
- out_buf = bytes([reg]) + b"\0" * buf_len
with self._spi as spi:
- spi.write_readinto(out_buf, in_buf)
+ spi.write_readinto(bytes([reg] + [0] * buf_len), in_buf)
self._status = in_buf[0]
return in_buf[1:]
def _reg_write_bytes(self, reg, out_buf):
- out_buf = bytes([0x20 | reg]) + out_buf
- in_buf = bytearray(len(out_buf))
+ in_buf = bytearray(len(out_buf) + 1)
with self._spi as spi:
- spi.write_readinto(out_buf, in_buf)
+ spi.write_readinto(bytes([0x20 | reg]) + out_buf, in_buf)
self._status = in_buf[0]
- def _reg_write(self, reg, val=None):
+ def _reg_write(self, reg, value=None):
out_buf = bytes([reg])
- if val is not None:
- out_buf = bytes([0x20 | reg, val])
+ if value is not None:
+ out_buf = bytes([(0x20 if reg != 0x50 else 0) | reg, value])
in_buf = bytearray(len(out_buf))
with self._spi as spi:
spi.write_readinto(out_buf, in_buf)
self._status = in_buf[0]
# pylint: enable=no-member
+
+ @property
+ def ce_pin(self):
+ return self._ce_pin.value
+
+ @ce_pin.setter
+ def ce_pin(self, val):
+ self._ce_pin.value = val
+
@property
def address_length(self):
return self._reg_read(0x03) + 2
@address_length.setter
def address_length(self, length):
- if not 3 <= length <= 5:
- raise ValueError("address_length must be in range [3, 5]")
- self._reg_write(0x03, length - 2)
+ self._reg_write(0x03, (length - 2) if 3 <= length <= 5 else 0)
def open_tx_pipe(self, addr):
self._reg_write_bytes(0x0A, addr)
@@ -105,32 +111,28 @@ def listen(self):
@listen.setter
def listen(self, is_rx):
- self.ce_pin.value = 0
+ self.ce_pin = 0
+ self._reg_write(0, (self._reg_read(0) & 0xFC) | (2 + bool(is_rx)))
if is_rx:
+ self.ce_pin = 1
if self._pipe0_read_addr is not None:
self._reg_write_bytes(0x0A, self._pipe0_read_addr)
else:
self.close_rx_pipe(0)
- self._reg_write(0, (self._reg_read(0) & 0xFC) | 3)
- time.sleep(0.00015)
- self.clear_status_flags()
- self.ce_pin.value = 1
- time.sleep(0.00013)
else:
if self._reg_read(0x1D) & 6 == 6:
self.flush_tx()
- self._reg_write(0, self._reg_read(0) & 0xFE | 2)
self._reg_write(2, self._reg_read(2) | 1)
- time.sleep(0.00016)
+ time.sleep(0.0001)
def available(self):
- return self.update() and self.pipe is not None
+ return self.update() and self._status >> 1 & 7 < 6
def any(self):
- if self._reg_read(0x1D) & 4 and self.pipe is not None:
+ if self._reg_read(0x1D) & 4 and self._status >> 1 & 7 < 6:
return self._reg_read(0x60)
- if self.pipe is not None:
- return self._reg_read(0x11 + self.pipe)
+ if self._status >> 1 & 7 < 6:
+ return self._reg_read(0x11 + (self._status >> 1 & 7))
return 0
def read(self, length=None):
@@ -142,25 +144,23 @@ def read(self, length=None):
return result
def send(self, buf, ask_no_ack=False, force_retry=0, send_only=False):
- self.ce_pin.value = 0
+ self.ce_pin = 0
if isinstance(buf, (list, tuple)):
result = []
for b in buf:
result.append(self.send(b, ask_no_ack, force_retry, send_only))
return result
- self.flush_tx()
- if not send_only and self.pipe is not None:
+ if self._status & 0x10 or self._status & 1:
+ self.flush_tx()
+ if not send_only and self._status >> 1 & 7 < 6:
self.flush_rx()
self.write(buf, ask_no_ack)
- while not self._status & 0x70:
+ while not self._status & 0x30:
self.update()
- self.ce_pin.value = 0
- result = self.irq_ds
- if self.irq_df:
- for _ in range(force_retry):
- result = self.resend(send_only)
- if result is None or result:
- break
+ result = bool(self._status & 0x20)
+ while force_retry and not result:
+ result = self.resend(send_only)
+ force_retry -= 1
if self._status & 0x60 == 0x60 and not send_only:
result = self.read()
return result
@@ -171,8 +171,8 @@ def tx_full(self):
@property
def pipe(self):
- result = (self._status & 0x0E) >> 1
- if result <= 5:
+ result = self._status >> 1 & 7
+ if result < 6:
return result
return None
@@ -188,6 +188,10 @@ def irq_ds(self):
def irq_df(self):
return bool(self._status & 0x10)
+ def update(self):
+ self._reg_write(0xFF)
+ return True
+
def clear_status_flags(self, data_recv=True, data_sent=True, data_fail=True):
config = bool(data_recv) << 6 | bool(data_sent) << 5 | bool(data_fail) << 4
self._reg_write(7, config)
@@ -279,10 +283,8 @@ def power(self):
@power.setter
def power(self, is_on):
- config = self._reg_read(0)
- if bool(config & 2) != bool(is_on):
- self._reg_write(0, config & 0x7D | bool(is_on) << 1)
- time.sleep(0.00016)
+ self._reg_write(0, self._reg_read(0) & 0x7D | bool(is_on) << 1)
+ time.sleep(0.00015)
@property
def pa_level(self):
@@ -294,47 +296,41 @@ def pa_level(self, pwr):
raise ValueError("pa_level must be -18, -12, -6, or 0")
self._reg_write(6, self._reg_read(6) & 0xF8 | (3 - int(pwr / -6)) * 2 | 1)
- def update(self):
- self._reg_write(0xFF)
- return True
-
def resend(self, send_only=False):
- result = False
- if not self.fifo(True, True):
- self.ce_pin.value = 0
- if not send_only and self.pipe is not None:
- self.flush_rx()
- self.clear_status_flags()
- self._reg_write(0xE3)
- self.ce_pin.value = 1
- while not self._status & 0x30:
- self.update()
- self.ce_pin.value = 0
- result = self.irq_ds
- if self._status & 0x60 == 0x60 and not send_only:
- result = self.read()
+ if self.fifo(True, True):
+ return False
+ self.ce_pin = 0
+ if not send_only and self._status >> 1 & 7 < 6:
+ self.flush_rx()
+ self.clear_status_flags()
+ self.ce_pin = 1
+ while not self._status & 0x30:
+ self.update()
+ result = bool(self._status & 0x20)
+ if self._status & 0x60 == 0x60 and not send_only:
+ result = self.read()
return result
def write(self, buf, ask_no_ack=False, write_only=False):
if not buf or len(buf) > 32:
raise ValueError("buffer length must be in range [1, 32]")
self.clear_status_flags()
- if self.tx_full:
+ if self._status & 1:
return False
config = self._reg_read(0)
if config & 3 != 2:
self._reg_write(0, (config & 0x7C) | 2)
- time.sleep(0.00016)
+ time.sleep(0.00015)
if not self.dynamic_payloads:
pl_width = self.payload_length
if len(buf) < pl_width:
- buf += b"\0" * pl_width - len(buf)
+ buf += b"\0" * (pl_width - len(buf))
elif len(buf) > pl_width:
buf = buf[:pl_width]
self._reg_write_bytes(0xA0 | (bool(ask_no_ack) << 4), buf)
if not write_only:
- self.ce_pin.value = 1
- return True
+ self.ce_pin = 1
+ return self._status & 0x10 == 0
def flush_rx(self):
self._reg_write(0xE2)
diff --git a/circuitpython_nrf24l01/rf24_mesh.py b/circuitpython_nrf24l01/rf24_mesh.py
new file mode 100644
index 0000000..b6e90da
--- /dev/null
+++ b/circuitpython_nrf24l01/rf24_mesh.py
@@ -0,0 +1,420 @@
+# 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.
+"""rf24_network module containing the base class RF24Network"""
+import time
+import struct
+
+try:
+ import json
+except ImportError:
+ json = None
+from .network.constants import (
+ MESH_ADDR_REQUEST,
+ MESH_ADDR_RESPONSE,
+ NETWORK_DEFAULT_ADDR,
+ NETWORK_MULTICAST_ADDR,
+ NETWORK_POLL,
+ MESH_ADDR_RELEASE,
+ MESH_ADDR_LOOKUP,
+ MESH_ID_LOOKUP,
+ MESH_LOOKUP_TIMEOUT,
+ MESH_WRITE_TIMEOUT,
+ MESH_MAX_POLL,
+ MESH_MAX_CHILDREN,
+ TX_NORMAL,
+ TX_PHYSICAL,
+ TX_MULTICAST,
+ MAX_FRAG_SIZE,
+)
+from .network.structs import RF24NetworkHeader, is_address_valid
+from .network.mixins import NetworkMixin, _lvl_2_addr
+
+
+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):
+ 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.
+ self.block_less_callback = None
+ self.ret_sys_msg = True # force _net_update() to return system message types
+ self._begin(0 if not node_id else NETWORK_DEFAULT_ADDR) # setup radio
+
+ @property
+ def node_id(self) -> int:
+ """The unique ID number (1 byte long) of the mesh network node."""
+ return self._id
+
+ @node_id.setter
+ def node_id(self, _id: int):
+ if self._addr != NETWORK_DEFAULT_ADDR:
+ self.release_address()
+ self._id = _id & 0xFF
+
+ 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}")
+ if dump_pipes:
+ self._rf24.print_pipes()
+
+ def release_address(self) -> bool:
+ """Forces an address lease to expire from the master."""
+ if self._addr != NETWORK_DEFAULT_ADDR:
+ self.frame_buf.header.to_node = 0
+ self.frame_buf.header.from_node = self._addr
+ self.frame_buf.header.message_type = MESH_ADDR_RELEASE
+ self.frame_buf.message = b""
+ if self._write(0, TX_NORMAL):
+ super()._begin(NETWORK_DEFAULT_ADDR)
+ return True
+ return False
+
+ def renew_address(self, timeout: int = 7):
+ """Connect to the mesh network and request a new `node_address`."""
+ if self._rf24.available():
+ self.update()
+
+ if self._addr != NETWORK_DEFAULT_ADDR:
+ super()._begin(NETWORK_DEFAULT_ADDR)
+ total_requests, request_count = (0, 0)
+ end_timer = timeout + time.monotonic()
+ while not self._request_address(request_count):
+ if time.monotonic() > end_timer:
+ return None
+ time.sleep((25 + ((total_requests + 1) * (request_count + 1)) * 2) / 1000)
+ request_count = (request_count + 1) % 4
+ total_requests = (total_requests + 1) % 10
+ return self._addr
+
+ def lookup_address(self, node_id: int = None) -> int:
+ """Convert a node's unique ID number into its corresponding
+ :ref:`Logical Address `."""
+ if not node_id:
+ return 0
+ if self._addr == NETWORK_DEFAULT_ADDR:
+ return -2
+ return self._lookup_2_master(node_id, MESH_ADDR_LOOKUP)
+
+ def lookup_node_id(self, address: int = None) -> int:
+ """Convert a node's :ref:`Logical Address ` into its
+ corresponding unique ID number."""
+ if not address:
+ return self._id if address is None else 0
+ if self._addr == NETWORK_DEFAULT_ADDR:
+ return -2
+ return self._lookup_2_master(address, MESH_ID_LOOKUP)
+
+ def _lookup_2_master(self, number: int, lookup_type: int) -> int:
+ """Returns False if timed out, otherwise lookup result"""
+ self.frame_buf.header.to_node = 0
+ self.frame_buf.header.from_node = self._addr
+ self.frame_buf.header.message_type = lookup_type
+ if lookup_type == MESH_ID_LOOKUP:
+ self.frame_buf.message = struct.pack(" timeout:
+ return -1
+ if lookup_type == MESH_ADDR_LOOKUP:
+ return struct.unpack(" bool:
+ """Check for network conectivity (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:
+ return False
+ return True
+
+ def update(self) -> int:
+ """Checks for incoming network data and returns last message type (if any)"""
+ msg_t = self._net_update()
+ if self._addr == NETWORK_DEFAULT_ADDR:
+ return msg_t
+ return msg_t
+
+ def _request_address(self, level: int) -> bool:
+ """Get a new address assigned from the master node"""
+ contacts = self._make_contact(level)
+ # print("Got", len(contacts), "responses on level",level)
+ if not contacts:
+ return False
+
+ new_addr = None
+ for contact in contacts:
+ # print("Requesting address from", oct(contact))
+ self.frame_buf.header.to_node = contact
+ self.frame_buf.header.from_node = NETWORK_DEFAULT_ADDR
+ self.frame_buf.header.message_type = MESH_ADDR_REQUEST
+ self.frame_buf.header.reserved = self._id
+ self.frame_buf.message = b""
+ self._write(contact, TX_PHYSICAL) # do a no auto-ack write
+ timeout = 225000000 + time.monotonic_ns()
+ while time.monotonic_ns() < timeout: # wait for network ack
+ if (
+ self._net_update() == MESH_ADDR_RESPONSE
+ and self.frame_buf.header.reserved == self.node_id
+ ):
+ new_addr = struct.unpack(" list:
+ """Make a list of connections after multicasting a `NETWORK_POLL` message."""
+ responders = []
+ self.frame_buf.header.to_node = NETWORK_MULTICAST_ADDR
+ self.frame_buf.header.from_node = NETWORK_DEFAULT_ADDR
+ self.frame_buf.header.message_type = NETWORK_POLL
+ self.frame_buf.message = b""
+ # self.multicast() does some extra logic to protect from user misuse.
+ self._write(_lvl_2_addr(lvl), TX_MULTICAST)
+ timeout = 55000000 + time.monotonic_ns()
+ while time.monotonic_ns() < timeout and len(responders) < MESH_MAX_POLL:
+ if self._net_update() == NETWORK_POLL:
+ contacted = self.frame_buf.header.from_node
+ is_duplicate = False
+ for contact in responders:
+ if contacted == contact:
+ is_duplicate = True
+ if not is_duplicate:
+ responders.append(contacted)
+ return responders
+
+ @property
+ def allow_children(self) -> bool:
+ """Allow/disallow child node to connect to this network node."""
+ return self._parenthood
+
+ @allow_children.setter
+ def allow_children(self, allow: bool):
+ self._parenthood = allow
+
+ def send(self, to_node: int, message_type, message) -> bool:
+ """Send a message to a mesh `node_id`."""
+ if self._addr == NETWORK_DEFAULT_ADDR:
+ return False
+ if to_node and to_node != self._id:
+ timeout = MESH_WRITE_TIMEOUT * 1000000 + time.monotonic_ns()
+ retry_delay = 5
+ to_node_addr = -2
+ while to_node_addr < 0:
+ to_node_addr = self.lookup_address(to_node)
+ if time.monotonic_ns() >= timeout:
+ return False
+ if to_node_addr < 0:
+ time.sleep(retry_delay / 1000)
+ retry_delay += 10
+ to_node = to_node_addr
+ if to_node == self._id:
+ to_node = self._addr
+ return self.write(to_node, message_type, message)
+
+ def write(self, to_node: int, message_type, message) -> 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")
+ if not self._validate_msg_len(len(message)):
+ message = message[:MAX_FRAG_SIZE]
+ if self._addr == NETWORK_DEFAULT_ADDR or not is_address_valid(to_node):
+ return False
+ self.frame_buf.header = RF24NetworkHeader(to_node, message_type)
+ self.frame_buf.header.from_node = self._addr
+ self.frame_buf.message = message
+ return self._write(to_node, TX_NORMAL)
+
+
+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):
+ 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.
+
+ def update(self) -> int:
+ """Checks for incoming network data and returns last message type (if any)"""
+ msg_t = super().update()
+ if msg_t == MESH_ADDR_REQUEST and self.frame_buf.header.reserved:
+ self._do_dhcp = True
+ if not self.lookup_node_id(): # if this is the master node
+ if msg_t in (MESH_ADDR_LOOKUP, MESH_ID_LOOKUP):
+ self.frame_buf.header.to_node = self.frame_buf.header.from_node
+
+ ret_val = 0 # will be -2 for requesting un-assigned nodes
+ if msg_t == MESH_ADDR_LOOKUP:
+ ret_val = self.lookup_address(self.frame_buf.message[0])
+ self.frame_buf.message = struct.pack(">= 3
+ shift_val += 3
+ extra_child = self.frame_buf.header.from_node == NETWORK_DEFAULT_ADDR
+
+ for i in range(MESH_MAX_CHILDREN + extra_child, 0, -1):
+ found_addr, new_addr = (False, via_node | (i << shift_val))
+ if new_addr == NETWORK_DEFAULT_ADDR:
+ continue
+ for n_id, addr in self.dhcp_dict.items():
+ # print(i, "(in _addr_dict) ID:", n_id, "ADDR:", oct(addr))
+ if addr == new_addr and n_id != self.frame_buf.header.reserved:
+ found_addr = True
+ break
+ if not found_addr:
+ self.set_address(self.frame_buf.header.reserved, new_addr)
+
+ self.frame_buf.header.message_type = MESH_ADDR_RESPONSE
+ self.frame_buf.header.to_node = self.frame_buf.header.from_node
+ self.frame_buf.message = struct.pack(" int:
+ """Convert a node's unique ID number into its corresponding
+ :ref:`Logical Address `."""
+ if not node_id:
+ return 0
+ if self._addr == NETWORK_DEFAULT_ADDR:
+ return -2
+ if not self._id:
+ return self._get_address(node_id, MESH_ADDR_LOOKUP)
+ return self._lookup_2_master(node_id, MESH_ADDR_LOOKUP)
+
+ def lookup_node_id(self, address: int = None) -> int:
+ """Convert a node's :ref:`Logical Address ` into its
+ corresponding unique ID number."""
+ if not address:
+ return self._id if address is None else 0
+ if self._addr == NETWORK_DEFAULT_ADDR:
+ return -2
+ if not self._addr:
+ return self._get_address(address, MESH_ID_LOOKUP)
+ return self._lookup_2_master(address, MESH_ID_LOOKUP)
+
+ def _get_address(self, number: int, lookup_type: int) -> int:
+ """Helper for get_address() and lookup_node_id()"""
+ for n_id, addr in self.dhcp_dict.items():
+ if lookup_type == MESH_ID_LOOKUP and addr == number:
+ return n_id
+ if lookup_type == MESH_ADDR_LOOKUP and n_id == number:
+ return addr
+ return -2
diff --git a/circuitpython_nrf24l01/rf24_network.py b/circuitpython_nrf24l01/rf24_network.py
new file mode 100644
index 0000000..1f714c2
--- /dev/null
+++ b/circuitpython_nrf24l01/rf24_network.py
@@ -0,0 +1,91 @@
+# 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.
+"""rf24_network module containing the base class RF24Network"""
+from .network.mixins import NetworkMixin
+from .network.structs import RF24NetworkHeader, RF24NetworkFrame, is_address_valid
+from .network.constants import (
+ NETWORK_MULTICAST_ADDR,
+ AUTO_ROUTING,
+ TX_NORMAL,
+ TX_PHYSICAL,
+ TX_LOGICAL,
+ TX_MULTICAST,
+ MAX_FRAG_SIZE,
+)
+
+
+class RF24NetworkRoutingOnly(NetworkMixin):
+ """A minimal Networking implementation for nodes that are meant for strictly
+ routing data amidst a network of nodes."""
+
+ def __init__(self, spi, csn_pin, ce_pin, node_address, spi_frequency=10000000):
+ if not is_address_valid(node_address):
+ raise ValueError("node_address argument is invalid or malformed")
+ super().__init__(spi, csn_pin, ce_pin, spi_frequency)
+ self._begin(node_address) # setup radio
+
+ @NetworkMixin.node_address.setter
+ def node_address(self, val: int):
+ if not is_address_valid(val):
+ return
+ self._begin(val)
+
+ def update(self) -> int:
+ """This function is used to keep the network layer current."""
+ return self._net_update()
+
+
+class RF24Network(RF24NetworkRoutingOnly):
+ """The object used to instantiate the nRF24L01 as a network node."""
+
+ def send(self, header: RF24NetworkHeader, message) -> bool:
+ """Deliver a message according to the header information."""
+ return self.write(RF24NetworkFrame(header, message))
+
+ def write(
+ self, frame: RF24NetworkFrame, traffic_direct: int = AUTO_ROUTING
+ ) -> bool:
+ """Deliver a network frame."""
+ if not isinstance(frame, RF24NetworkFrame):
+ raise TypeError("frame expected object of type RF24NetworkFrame.")
+ if not is_address_valid(frame.header.to_node):
+ raise AttributeError("frame destined for an invalid address")
+ if not self._validate_msg_len(len(frame.message)):
+ frame.message = frame.message[:MAX_FRAG_SIZE]
+ frame.header.from_node = self._addr
+ return self._pre_write(frame, traffic_direct)
+
+ def _pre_write(
+ self, frame: RF24NetworkFrame, traffic_direct: int = AUTO_ROUTING
+ ) -> bool:
+ """Helper to do prep work for _write_to_pipe(); like to TMRh20's _write()"""
+ self.frame_buf = frame
+ if traffic_direct != AUTO_ROUTING:
+ # Payload is multicast to the first node, and routed normally to the next
+ send_type = TX_LOGICAL
+ if self.frame_buf.header.to_node == NETWORK_MULTICAST_ADDR:
+ send_type = TX_MULTICAST
+ if self.frame_buf.header.to_node == traffic_direct:
+ # Payload is multicast to the first node, which is the recipient
+ send_type = TX_PHYSICAL
+ return self._write(traffic_direct, send_type)
+ return self._write(self.frame_buf.header.to_node, TX_NORMAL)
diff --git a/circuitpython_nrf24l01/wrapper/__init__.py b/circuitpython_nrf24l01/wrapper/__init__.py
new file mode 100644
index 0000000..315e0e7
--- /dev/null
+++ b/circuitpython_nrf24l01/wrapper/__init__.py
@@ -0,0 +1,30 @@
+# 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.
+"""import only accessible API wrappers"""
+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
new file mode 100644
index 0000000..68b361f
--- /dev/null
+++ b/circuitpython_nrf24l01/wrapper/cpy_spidev.py
@@ -0,0 +1,74 @@
+# 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.
+"""This module contains a wrapper class for `spidev.SpiDev` in CPython on Linux"""
+
+
+class SPIDevCtx:
+ """A wrapper class to allow using the spidev module on linux and
+ circuitpython's API and context manager.
+
+ :param ~spidev.SpiDev spi: The instance of a ``SpiDev`` object.
+ :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
+ multiplied by 10, and index ``1`` is the pin number.
+ :param int spi_frequency: the SPI frquency to use for the SPI device.
+ Defaults to 10MHz.
+ """
+
+ def __init__(self, spi, csn, spi_frequency=10000000):
+ self._spi = spi
+ self._baudrate = spi_frequency
+ self._no_cs = False
+ self._bus, self._dev = (0, 0)
+ self._csn = csn
+ if isinstance(csn, int):
+ self._bus, self._dev = (int(csn / 10), csn % 10)
+ else:
+ self._no_cs = True
+ if isinstance(csn, (tuple, list)):
+ self._bus, self._dev = (int(csn[0] / 10), csn[0] % 10)
+ self._csn = csn[1]
+ self._csn.switch_to_output()
+
+ def __enter__(self):
+ self._spi.open(self._bus, self._dev)
+ self._spi.no_cs = self._no_cs
+ if self._no_cs:
+ self._csn.value = 0
+ return self
+
+ def __exit__(self, *excs):
+ if self._no_cs:
+ self._csn.value = 1
+ self._spi.close()
+ return False
+
+ def write_readinto(self, out_buf, in_buf):
+ """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))
diff --git a/circuitpython_nrf24l01/wrapper/upy_pin.py b/circuitpython_nrf24l01/wrapper/upy_pin.py
new file mode 100644
index 0000000..b7be11b
--- /dev/null
+++ b/circuitpython_nrf24l01/wrapper/upy_pin.py
@@ -0,0 +1,80 @@
+"""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
new file mode 100644
index 0000000..c51ebc7
--- /dev/null
+++ b/circuitpython_nrf24l01/wrapper/upy_spi.py
@@ -0,0 +1,80 @@
+# 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/Logo.xcf b/docs/_static/Logo.xcf
deleted file mode 100644
index a905343..0000000
Binary files a/docs/_static/Logo.xcf and /dev/null differ
diff --git a/docs/_static/custom_material.css b/docs/_static/custom_material.css
new file mode 100644
index 0000000..8fe3d8d
--- /dev/null
+++ b/docs/_static/custom_material.css
@@ -0,0 +1,81 @@
+.md-typeset .admonition,
+.md-typeset details {
+ font-size: 0.75rem;
+}
+
+.md-typeset .admonition.tip>.admonition-title::before,
+.md-typeset .admonition.hint>.admonition-title::before {
+ mask-image: url('data:image/svg+xml;charset=utf-8,');
+}
+
+.md-typeset .admonition.seealso>.admonition-title::before {
+ mask-image: url('data:image/svg+xml;charset=utf-8,');
+ background-color: hsl(301, 100%, 63%);
+}
+
+.md-typeset .admonition.seealso {
+ border-left: .2rem solid hsl(301, 100%, 63%);
+}
+
+.md-typeset .admonition.seealso>.admonition-title {
+ background-color: hsla(287, 100%, 63%, 0.25);
+}
+
+.md-typeset .admonition.important>.admonition-title::before {
+ mask-image: url('data:image/svg+xml;charset=utf-8,');
+ background-color: hsl(123, 100%, 63%);
+}
+
+.md-typeset .admonition.important {
+ border-left: .2rem solid hsl(123, 100%, 63%);
+}
+
+.md-typeset .admonition.important>.admonition-title {
+ background-color: hsla(123, 100%, 63%, 0.25);
+}
+
+.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 .admonition.warning>.admonition-title {
+ background-color: hsla(0, 100%, 63%, 0.25);
+}
+
+html .md-nav--primary .md-nav__title--site .md-nav__button {
+ top: 0;
+ left: 0;
+ width: inherit;
+ height: auto;
+}
+
+.md-typeset table:not([class]) th {
+ background-color: rgba(23, 35, 83, 0.8);
+}
+
+.md-typeset table:not([class]) tr:hover {
+ background-color: rgba(56, 2, 81, 0.8);
+ box-shadow: inset 0 .05rem 0 #9515ff;
+}
+
+[data-md-color-scheme="default"] {
+ --md-code-bg-color: #e8e7e7;
+}
+
+.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 {
+ width: auto;
+}
+
+.linenos {
+ background-color: var(--md-default-bg-color--light);
+ margin-right: 0.5rem;
+}
diff --git a/docs/_static/dark_material.css b/docs/_static/dark_material.css
deleted file mode 100644
index 55eb288..0000000
--- a/docs/_static/dark_material.css
+++ /dev/null
@@ -1,170 +0,0 @@
-body {
- color: #fcfcfc;
- background-color: #232323;
-}
-
-.sig-name.descname {
- color: #b4e65c;
-}
-
-.sig-prename.descclassname {
- color: #fffefe;
-}
-
-.md-typeset {
- background-color: unset;
-}
-
-.md-typeset .codehilitetable .linenos pre,
-.md-typeset .highlighttable .linenos pre {
- color: #FCFCFC;
-}
-
-.md-typeset .footnote {
- color: rgba(255, 255, 255, 0.83);
- font-size: .64rem
-}
-
-span.highlighted {
- background-color: #794502;
-}
-
-.highlight-shell.notranslate .highlight pre {
- color:#fcfcfc;
-}
-
-.highlight .cpf {
- color: #B88451;
-}
-
-.md-typeset blockquote{
- border-left:.2rem solid rgb(153, 153, 153);
- color:rgb(202, 202, 202);
-}
-
-.md-typeset .admonition.tip .admonition-title::before,
-.md-typeset .admonition.hint .admonition-title::before {
- content: "\E80C";
-}
-
-.md-typeset .admonition.seealso .admonition-title::before {
- content: "\E417";
- color: hsl(301, 100%, 63%);
-}
-
-.md-typeset .admonition.seealso {
- border-left:.2rem solid hsl(301, 100%, 63%);
-}
-
-.md-typeset .admonition.seealso .admonition-title {
- background-color:hsla(287, 100%, 63%, 0.1);
-}
-
-.md-typeset .admonition.important .admonition-title::before {
- content: "\E031";
- color: hsl(123, 100%, 63%);
-}
-
-.md-typeset .admonition.important {
- border-left:.2rem solid hsl(123, 100%, 63%);
-}
-
-.md-typeset .admonition.important .admonition-title {
- background-color:hsla(123, 100%, 63%, 0.1);
-}
-
-.md-typeset .admonition.warning .admonition-title::before {
- color: hsl(0, 100%, 63%);
-}
-
-.md-typeset .admonition.warning {
- border-left:.2rem solid hsl(0, 100%, 63%);
-}
-
-.md-typeset .admonition.warning .admonition-title {
- background-color:hsla(0, 100%, 63%, 0.1);
-}
-
-.sig-name.descname,
-.sig-prename.descclassname {
- background-color: #403f3f;
-}
-
-.docutils.literal.notranslate {
- background-color: hsla(0, 0%, 0%, 0);
- color: #50e60c;
-}
-
-.reference.internal .docutils.literal.notranslate .pre {
- color: #2196f3;
-}
-
-html body .md-typeset .headerlink {
- color: rgb(94, 172, 15);
-}
-
-@media only screen and (max-width: 76.1875em) {
- .md-nav {
- background-color: #393939;
- }
-}
-
-@media only screen and (max-width:76.1875em) {
- html .md-nav .md-nav--secondary .md-nav__title {
- background-color: rgb(0, 107, 9);
- color: rgb(255, 255, 255);
- }
-}
-
-.md-nav__link[data-md-state="blur"] {
- color: rgb(150, 125, 238);
-}
-
-.md-nav {
- font-size: 0.8rem;
-}
-
-.md-typeset h1 {
- color: rgb(255, 255, 255);
-}
-
-html .md-nav--primary .md-nav__title--site .md-nav__button {
- top: 0;
- left: 0;
- width: inherit;
- height: auto;
-}
-
-html .md-nav--primary .md-nav__title~.md-nav__list {
- background-color: #fff0;
-}
-
-.md-nav span.caption {
- background-color: #172353;
-}
-
-[data-md-color-primary=blue] .md-tabs {
- background-color: #006b09;
-}
-
-[data-md-color-primary=blue] .md-header,
-[data-md-color-primary=blue] .md-hero {
- background-color: #006b09;
-}
-
-html [data-md-color-primary=blue] .md-nav--primary .md-nav__title--site {
- background-color: #006B09;
-}
-
-.md-typeset table:not([class]) th {
- background-color:rgba(23, 35, 83, 0.8);
-}
-
-.md-typeset table:not([class]) tr:hover {
- background-color:rgba(56, 2, 81, 0.8);
- box-shadow:inset 0 .05rem 0 #9515ff;
-}
-
-table {
- background-color: hsl(0, 0%, 16%);
-}
\ No newline at end of file
diff --git a/docs/conf.py b/docs/conf.py
index d1e159f..0f6c30c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -2,21 +2,6 @@
"""This file is for `sphinx-build` configuration"""
import os
import sys
-import pygments.styles
-from pygments.style import Style
-from pygments.token import (
- Text,
- Other,
- Comment,
- Keyword,
- Name,
- Literal,
- String,
- Number,
- Operator,
- Generic,
- Punctuation,
-)
sys.path.insert(0, os.path.abspath(".."))
@@ -32,23 +17,24 @@
"sphinx.ext.napoleon",
"sphinx.ext.todo",
"sphinx.ext.viewcode",
- "sphinx_copybutton",
+ "sphinx.ext.graphviz",
+ "sphinx_immaterial"
# "rst2pdf.pdfbuilder", # for local pdf builder support
]
# Uncomment the below if you use native CircuitPython modules such as
# digitalio, micropython and busio. List the modules you use. Without it, the
# autodoc module docs will fail to generate with a warning.
-autodoc_mock_imports = ["digitalio", "busio", "usb_hid", "microcontroller"]
+autodoc_mock_imports = ["digitalio", "busio", "usb_hid", "microcontroller", "logging"]
autodoc_member_order = "bysource"
intersphinx_mapping = {
- "python": ("https://docs.python.org/3.7", None),
- "BusDevice": (
- "https://circuitpython.readthedocs.io/projects/busdevice/en/latest/",
+ "python": ("https://docs.python.org/3", None),
+ "CircuitPython": ("https://circuitpython.readthedocs.io/en/latest/", None),
+ "Adafruit_logging": (
+ "https://circuitpython.readthedocs.io/projects/logging/en/latest/",
None,
),
- "CircuitPython": ("https://circuitpython.readthedocs.io/en/latest/", None),
}
html_baseurl = "https://circuitpython-nrf24l01.readthedocs.io/"
@@ -63,19 +49,19 @@
# General information about the project.
# pylint: disable=redefined-builtin
-copyright = u"2019 Brendan Doherty"
+copyright = "2019 Brendan Doherty"
# pylint: enable=redefined-builtin
-project = u"nRF24L01 Library"
-author = u"Brendan Doherty"
+project = "nRF24L01 Library"
+author = "Brendan Doherty"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
-version = u"2.0.0"
+version = "2.1.0"
# The full version, including alpha/beta/rc tags.
-release = u"2.0.0"
+release = "2.1.0"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -110,137 +96,60 @@
napoleon_numpy_docstring = False
-# pygment custom style
-# --------------------------------------------------
-
-class DarkPlus(Style):
- """A custom pygment highlighting scheme based on
- VSCode's builtin `Dark Plus` theme"""
-
- background_color = "#1E1E1E"
- highlight_color = "#ff0000"
- line_number_color = "#FCFCFC"
- line_number_background_color = "#282828"
-
- default_style = ""
- styles = {
- Text: "#FEFEFE",
- Comment.Single: "#5E9955",
- Comment.Multiline: "#5E9955",
- Comment.Preproc: "#B369BF",
- Other: "#FEFEFE",
- Keyword: "#499CD6",
- Keyword.Declaration: "#C586C0",
- Keyword.Namespace: "#B369BF",
- # Keyword.Pseudo: "#499CD6",
- # Keyword.Reserved: "#499CD6",
- Keyword.Type: "#48C999",
- Name: "#FEFEFE",
- Name.Builtin: "#EAEB82",
- Name.Builtin.Pseudo: "#499DC7",
- Name.Class: "#48C999",
- Name.Decorator: "#EAEB82",
- Name.Exception: "#48C999",
- Name.Attribute: "#569CD6",
- Name.Variable:" #9CDCFE",
- Name.Variable.Magic: "#EAEB82",
- Name.Function: "#EAEB82",
- Name.Function.Magic: "#EAEB82",
- Literal: "#AC4C1E",
- String: "#B88451",
- String.Escape: "#DEA868",
- String.Affix: "#499DC7",
- Number: "#B3D495",
- Operator: "#FEFEFE",
- Operator.Word: "#499DC7",
- Generic.Output: "#F4DA8B",
- Generic.Prompt: "#99FFA2",
- Generic.Traceback: "#FF0909",
- Generic.Error: "#FF0909",
- Punctuation: "#FEFEFE",
- }
-
-
-def pygments_monkeypatch_style(mod_name, cls):
- """ function to inject a custom pygment style """
- cls_name = cls.__name__
- mod = type(__import__("os"))(mod_name)
- setattr(mod, cls_name, cls)
- setattr(pygments.styles, mod_name, mod)
- sys.modules["pygments.styles." + mod_name] = mod
- pygments.styles.STYLE_MAP[mod_name] = mod_name + "::" + cls_name
-
-
-pygments_monkeypatch_style("dark_plus", DarkPlus)
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = "dark_plus"
-
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = "sphinx_material"
+html_theme = "sphinx_immaterial"
# Material theme options
-html_sidebars = {
- "**": ["logo-text.html", "globaltoc.html", "localtoc.html", "searchbox.html"]
-}
html_theme_options = {
- # Set the name of the project to appear in the navigation.
- "nav_title": "CircuitPython-nRF24L01",
- # A list of dictionaries where each has three keys:
- # href: The URL or pagename (str)
- # title: The title to appear (str)
- # internal: Flag indicating to use pathto (bool)
- "nav_links": [
- {
- "href": "examples",
- "title": "Examples",
- "internal": True
- },
- {
- "href": "basic_api",
- "title": "Basic RF24 API",
- "internal": True
- },
- {
- "href": "advanced_api",
- "title": "Advanced RF24 API",
- "internal": True
- },
- {
- "href": "configure_api",
- "title": "Configurable RF24 API",
- "internal": True
- },
+ "features": [
+ # "navigation.expand",
+ "navigation.tabs",
+ # "toc.integrate",
+ "navigation.sections",
+ "navigation.instant",
+ # "header.autohide",
+ "navigation.top",
+ # "search.highlight",
+ "search.share",
+ ],
+ "palette": [
{
- "href": "ble_api",
- "title": "BLE API Reference",
- "internal": True
+ "media": "(prefers-color-scheme: dark)",
+ "scheme": "slate",
+ "primary": "lime",
+ "accent": "light-blue",
+ "toggle": {
+ "icon": "material/lightbulb",
+ "name": "Switch to light mode",
+ },
},
{
- "href": "troubleshooting",
- "title": "Troubleshooting",
- "internal": True
+ "media": "(prefers-color-scheme: light)",
+ "scheme": "default",
+ "primary": "light-blue",
+ "accent": "green",
+ "toggle": {
+ "icon": "material/lightbulb-outline",
+ "name": "Switch to dark mode",
+ },
},
],
- # Set the color and the accent color
- "color_primary": "blue",
- "color_accent": "light-blue",
# Set the repo location to get a badge with stats
- "repo_url": "https://github.com/2bndy5/CircuitPython_nRF24L01/",
+ "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,
+ "globaltoc_depth": -1,
# If False, expand all TOC entries
"globaltoc_collapse": False,
# If True, show hidden TOC entries
"globaltoc_includehidden": True,
}
-
-
# Set link name generated in the top bar.
-html_title = "Introduction"
+html_title = "CircuitPython_nRF24L01"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
@@ -250,7 +159,7 @@ def pygments_monkeypatch_style(mod_name, cls):
# These paths are either relative to html_static_path
# or fully qualified paths (eg. https://...)
html_css_files = [
- "dark_material.css",
+ "custom_material.css",
]
# The name of an image file (relative to this directory) to use as a favicon of
@@ -272,16 +181,16 @@ def pygments_monkeypatch_style(mod_name, cls):
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
@@ -291,7 +200,7 @@ def pygments_monkeypatch_style(mod_name, cls):
(
master_doc,
"nRF24L01Library.tex",
- u"nRF24L01 Library Documentation",
+ "nRF24L01 Library Documentation",
author,
"manual",
),
@@ -302,7 +211,7 @@ def pygments_monkeypatch_style(mod_name, cls):
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
- (master_doc, "nRF24L01library", u"nRF24L01 Library Documentation", [author], 1)
+ (master_doc, "nRF24L01library", "nRF24L01 Library Documentation", [author], 1)
]
# -- Options for Texinfo output -------------------------------------------
@@ -314,7 +223,7 @@ def pygments_monkeypatch_style(mod_name, cls):
(
master_doc,
"nRF24L01Library",
- u" nRF24L01 Library Documentation",
+ " nRF24L01 Library Documentation",
author,
"nRF24L01Library",
"nRF24L01 on CircuitPython devices.",
diff --git a/docs/advanced_api.rst b/docs/core_api/advanced_api.rst
similarity index 92%
rename from docs/advanced_api.rst
rename to docs/core_api/advanced_api.rst
index 9a26a87..38d5edb 100644
--- a/docs/advanced_api.rst
+++ b/docs/core_api/advanced_api.rst
@@ -13,9 +13,6 @@
Advanced RF24 API
-----------------
-resend()
-******************************
-
.. automethod:: circuitpython_nrf24l01.rf24.RF24.resend
This function is meant to be used for payloads that failed to transmit using the
@@ -40,9 +37,6 @@ resend()
over-written by using `send()` or `write()` to load a new payload into the TX FIFO
buffer.
-write()
-******************************
-
.. automethod:: circuitpython_nrf24l01.rf24.RF24.write
This function isn't completely non-blocking as we still need to wait
@@ -119,7 +113,89 @@ write()
.. versionadded:: 1.2.0
``write_only`` parameter
-print_details()
+.. automethod:: circuitpython_nrf24l01.rf24.RF24.load_ack
+
+ This payload will then be appended to the automatic acknowledgment
+ (ACK) packet that is sent when *new* data is received on the specified pipe. See
+ `read()` on how to fetch a received custom ACK payloads.
+
+ :param bytearray,bytes buf: This will be the data attached to an automatic ACK packet on the
+ incoming transmission about the specified ``pipe_number`` parameter. This must have a
+ length in range [1, 32] bytes, otherwise a `ValueError` exception is thrown. Any ACK
+ payloads will remain in the TX FIFO buffer until transmitted successfully or
+ `flush_tx()` is called.
+ :param int pipe_number: This will be the pipe number to use for deciding which
+ transmissions get a response with the specified ``buf`` parameter's data. This number
+ must be in range [0, 5], otherwise a `IndexError` exception is thrown.
+
+ :returns: `True` if payload was successfully loaded onto the TX FIFO buffer. `False` if it
+ wasn't because TX FIFO buffer is full.
+
+ .. note:: this function takes advantage of a special feature on the nRF24L01 and needs to
+ be called for every time a customized ACK payload is to be used (not for every
+ automatic ACK packet -- this just appends a payload to the ACK packet). The `ack`,
+ `auto_ack`, and `dynamic_payloads` attributes are also automatically enabled (with
+ respect to data pipe 0) by this function when necessary.
+
+ .. tip:: The ACK payload must be set prior to receiving a transmission. It is also worth
+ noting that the nRF24L01 can hold up to 3 ACK payloads pending transmission. Using this
+ function does not over-write existing ACK payloads pending; it only adds to the queue
+ (TX FIFO buffer) if it can. Use `flush_tx()` to discard unused ACK payloads when done
+ listening.
+
+.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.power
+
+ This is exposed for convenience.
+
+ - `False` basically puts the nRF24L01 to sleep (AKA power down mode) with ultra-low
+ current consumption. No transmissions are executed when sleeping, but the nRF24L01 can
+ still be accessed through SPI. Upon instantiation, this driver class puts the nRF24L01
+ to sleep until the MCU invokes RX/TX modes. This driver class will only power down
+ the nRF24L01 after exiting a `with` block.
+ - `True` powers up the nRF24L01. This is the first step towards entering RX/TX modes (see
+ also `listen` attribute). Powering up is automatically handled by the `listen` attribute
+ as well as the `send()` and `write()` functions.
+
+ .. note:: This attribute needs to be `True` if you want to put radio on Standby-II (highest
+ current consumption) or Standby-I (moderate current consumption) modes. The state of
+ the CE pin determines which Standby mode is acheived. See `Chapter 6.1.2-7 of the
+ nRF24L01+ Specifications Sheet `_ for more details.
+
+.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.address_length
+
+ A valid input value must be an `int` in range [3, 5]. Default is set to the nRF24L01's maximum of 5.
+ Any invalid input value results in a address length of 2 bytes.
+
+ .. versionchanged:: 2.1.0
+ A `ValueError` exception was thrown when an invalid input value was encountered.
+ This changed to setting the address length to 2 bytes (for possible reverse engineering protocol
+ purposes).
+
+.. automethod:: circuitpython_nrf24l01.rf24.RF24.address
+
+ This function returns the full content of the nRF24L01's registers about RX/TX addresses
+ despite what `address_length` is set to.
+
+ :param int index: the number of the data pipe whose address is to be returned. 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``.
+
+ .. versionadded:: 1.2.0
+
+.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.last_tx_arc
+
+ This attribute resets to 0 at the beginning of every transmission in TX mode.
+ Remember that the number of automatic retry attempts made for each transmission is
+ configured with the `arc` attribute or the `set_auto_retries()` function.
+
+.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.is_plus_variant
+
+ This information is detirmined upon instantiation.
+
+ .. versionadded:: 1.2.0
+
+Debugging Output
******************************
.. automethod:: circuitpython_nrf24l01.rf24.RF24.print_details
@@ -181,10 +257,19 @@ print_details()
payloads`` where ``X`` is the `payload_length` (in bytes) the pipe is setup to
receive when `dynamic_payloads` is disabled for that pipe.
- This parameter's default is `False` and skips this extra information.
+ Set this parameter to `False` (it default value) to skips this extra information.
-address_repr()
-******************************
+ .. versionchanged:: v2.1.0
+ Changed the default value for the ``dump_pipes`` parameter to `True`
+
+.. automethod:: circuitpython_nrf24l01.rf24.RF24.print_pipes
+
+ This method is called from :meth:`~RF24.print_details()` if the ``dump_pipes`` parameter is
+ set to `True`.
+
+ .. versionchanged:: v2.1.0
+ Changed this method's name from the private method ``_dump_pipes()`` to a public method
+ ``print_pipes()``.
.. automethod:: circuitpython_nrf24l01.rf24.address_repr
@@ -197,63 +282,24 @@ address_repr()
>>> address_repr(b"1Node")
'65646f4e31'
- :Return:
- A string of hexidecimal characters in big endian form of the
- specified ``addr`` parameter.
- :param bytes,bytearray addr: The address to convert into a hexlified
- string
-
-is_plus_variant
-******************************
-
-.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.is_plus_variant
-
- This information is detirmined upon instantiation.
-
- .. versionadded:: 1.2.0
+ :param bytes,bytearray buf: The buffer of bytes to convert into a hexlified
+ string.
+ :param bool reverse: A `bool` to control the resulting endianess. `True`
+ outputs the result as big endian. `False` outputs the result as little
+ endian. This parameter defaults to `True` since `bytearray` and `bytes`
+ objects are stored in big endian but written in little endian.
+ :param str delimit: A `chr` or `str` to use as a delimiter between bytes.
+ Defaults to an empty string.
-load_ack()
-******************************
-
-.. automethod:: circuitpython_nrf24l01.rf24.RF24.load_ack
-
- This payload will then be appended to the automatic acknowledgment
- (ACK) packet that is sent when *new* data is received on the specified pipe. See
- `read()` on how to fetch a received custom ACK payloads.
-
- :param bytearray,bytes buf: This will be the data attached to an automatic ACK packet on the
- incoming transmission about the specified ``pipe_number`` parameter. This must have a
- length in range [1, 32] bytes, otherwise a `ValueError` exception is thrown. Any ACK
- payloads will remain in the TX FIFO buffer until transmitted successfully or
- `flush_tx()` is called.
- :param int pipe_number: This will be the pipe number to use for deciding which
- transmissions get a response with the specified ``buf`` parameter's data. This number
- must be in range [0, 5], otherwise a `IndexError` exception is thrown.
-
- :returns: `True` if payload was successfully loaded onto the TX FIFO buffer. `False` if it
- wasn't because TX FIFO buffer is full.
-
- .. note:: this function takes advantage of a special feature on the nRF24L01 and needs to
- be called for every time a customized ACK payload is to be used (not for every
- automatic ACK packet -- this just appends a payload to the ACK packet). The `ack`,
- `auto_ack`, and `dynamic_payloads` attributes are also automatically enabled (with
- respect to data pipe 0) by this function when necessary.
-
- .. tip:: The ACK payload must be set prior to receiving a transmission. It is also worth
- noting that the nRF24L01 can hold up to 3 ACK payloads pending transmission. Using this
- function does not over-write existing ACK payloads pending; it only adds to the queue
- (TX FIFO buffer) if it can. Use `flush_tx()` to discard unused ACK payloads when done
- listening.
+ :Returns:
+ A string of hexidecimal characters in big endian form of the
+ specified ``buf`` parameter.
Status Byte
******************************
-tx_full
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.tx_full
- .
|update manually| (especially after calling
:py:func:`~circuitpython_nrf24l01.rf24.RF24.flush_tx()`).
@@ -264,12 +310,8 @@ tx_full
- `False` for TX FIFO buffer is not full. This doesn't mean the TX FIFO buffer is
empty.
-irq_dr
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_dr
- .
:Returns:
@@ -286,12 +328,8 @@ irq_dr
|update manually| (especially after calling
:py:func:`~circuitpython_nrf24l01.rf24.RF24.clear_status_flags()`).
-irq_df
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_df
- .
:Returns:
@@ -307,12 +345,8 @@ irq_df
|update manually| (especially after calling
:py:func:`~circuitpython_nrf24l01.rf24.RF24.clear_status_flags()`).
-irq_ds
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.irq_ds
- .
:Returns:
@@ -325,9 +359,6 @@ irq_ds
|update manually| (especially after calling
:py:func:`~circuitpython_nrf24l01.rf24.RF24.clear_status_flags()`).
-update()
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
.. automethod:: circuitpython_nrf24l01.rf24.RF24.update
Refreshing the status byte is vital to checking status of the interrupt flags, RX pipe
@@ -343,12 +374,8 @@ update()
.. versionchanged:: 1.2.3
arbitrarily returns `True`
-pipe
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pipe
- .
.. versionchanged:: 1.2.0
In previous versions of this library, this attribute was a read-only function
@@ -363,13 +390,9 @@ pipe
- The `int` identifying pipe number [0,5] that received the next
available payload in the RX FIFO buffer.
-clear_status_flags()
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
.. automethod:: circuitpython_nrf24l01.rf24.RF24.clear_status_flags
- Internally, this is automatically called by `send()`, `write()`, `read()`, and when
- `listen` changes from `False` to `True`.
+ Internally, this is automatically called by `send()`, `write()`, `read()`.
:param bool data_recv: specifies wheather to clear the "RX Data Ready"
(:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr`) flag.
@@ -388,34 +411,10 @@ clear_status_flags()
nRF24L01Pluss_Preliminary_Product_Specification_v1_0.pdf#G1047965>`_ for an outline of
proper behavior.
-power
-******************************
-
-.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.power
-
- This is exposed for convenience.
-
- - `False` basically puts the nRF24L01 to sleep (AKA power down mode) with ultra-low
- current consumption. No transmissions are executed when sleeping, but the nRF24L01 can
- still be accessed through SPI. Upon instantiation, this driver class puts the nRF24L01
- to sleep until the MCU invokes RX/TX modes. This driver class will only power down
- the nRF24L01 after exiting a `with` block.
- - `True` powers up the nRF24L01. This is the first step towards entering RX/TX modes (see
- also `listen` attribute). Powering up is automatically handled by the `listen` attribute
- as well as the `send()` and `write()` functions.
-
- .. note:: This attribute needs to be `True` if you want to put radio on Standby-II (highest
- current consumption) or Standby-I (moderate current consumption) modes. The state of
- the CE pin determines which Standby mode is acheived. See `Chapter 6.1.2-7 of the
- nRF24L01+ Specifications Sheet `_ for more details.
FIFO management
******************************
-flush_rx()
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
.. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_rx
.. note:: The nRF24L01 RX FIFO is 3 level stack that holds payload data. This means that
@@ -423,9 +422,6 @@ flush_rx()
waiting to be read (and removed from the stack) by `read()`. This
function clears all 3 levels.
-flush_tx()
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
.. automethod:: circuitpython_nrf24l01.rf24.RF24.flush_tx
.. note:: The nRF24L01 TX FIFO is 3 level stack that holds payload data. This means that
@@ -435,9 +431,6 @@ flush_tx()
successful transmission (see also `resend()` as the handling of failed transmissions
can be altered).
-fifo()
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
.. automethod:: circuitpython_nrf24l01.rf24.RF24.fifo
:param bool about_tx:
@@ -454,53 +447,18 @@ fifo()
- A `bool` answer to the question:
"Is the [TX/RX](``about_tx``) FIFO buffer [empty/full](``check_empty``)?
- - If the ``check_empty`` parameter is not specified: an `int` in range [0,2] for which:
+ - If the ``check_empty`` parameter is not specified: an `int` in range [0, 2] for which:
- ``1`` means the specified FIFO buffer is empty
- ``2`` means the specified FIFO buffer is full
- ``0`` means the specified FIFO buffer is neither full nor empty
-
-address_length
-******************
-
-.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.address_length
-
- A valid input value must be an `int` in range [3, 5]. Otherwise a `ValueError` exception is
- thrown. Default is set to the nRF24L01's maximum of 5.
-
-address()
-******************************
-
-.. automethod:: circuitpython_nrf24l01.rf24.RF24.address
-
- This function returns the full content of the nRF24L01's registers about RX/TX addresses
- despite what `address_length` is set to.
-
- :param int index: the number of the data pipe whose address is to be returned. 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``.
-
- .. versionadded:: 1.2.0
-
-last_tx_arc
-******************************
-
-.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.last_tx_arc
-
- This attribute resets to 0 at the beginning of every transmission in TX mode.
- Remember that the number of automatic retry attempts made for each transmission is
- configured with the `arc` attribute or the `set_auto_retries()` function.
-
Ambiguous Signal Detection
******************************
-rpd
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.rpd
- The RPD flag is triggered in the following cases:
+ The RPD (Received Power Detector) flag is triggered in the following cases:
1. During RX mode (when `listen` is `True`) and an arbitrary RF transmission with
a gain above -64 dBm threshold is/was present.
@@ -516,9 +474,6 @@ rpd
.. versionadded:: 1.2.0
-start_carrier_wave()
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
.. automethod:: circuitpython_nrf24l01.rf24.RF24.start_carrier_wave
This is a basic test of the nRF24L01's TX output. It is a commonly required
@@ -558,25 +513,20 @@ start_carrier_wave()
.. versionadded:: 1.2.0
-stop_carrier_wave()
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
.. automethod:: circuitpython_nrf24l01.rf24.RF24.stop_carrier_wave
See `start_carrier_wave()` for more details.
.. note::
Calling this function puts the nRF24L01 to sleep (AKA power down mode).
- .. hint:: If the radio is a non-plus variant (`is_plus_variant` returns
- `False`), then use the following code snippet to re-establish the library
- default settings:
+ .. hint::
+ If the radio is a non-plus variant (`is_plus_variant` returns
+ `False`), then use `with` to re-establish the previous settings:
- .. code-block::
+ .. code-block:: python
# let `nrf` be the instantiated RF24 object
- nrf.crc = 2
- nrf.auto_ack = True
- nrf.set_auto_retries(1500, 3)
- nrf.open_tx_pipe(nrf.address())
+ with nrf:
+ pass # settings are now restored
.. versionadded:: 1.2.0
diff --git a/docs/basic_api.rst b/docs/core_api/basic_api.rst
similarity index 91%
rename from docs/basic_api.rst
rename to docs/core_api/basic_api.rst
index 6efb8a2..e647145 100644
--- a/docs/basic_api.rst
+++ b/docs/core_api/basic_api.rst
@@ -1,255 +1,234 @@
-
-Basic RF24 API
---------------
-
-Constructor
-******************
-
-.. autoclass:: circuitpython_nrf24l01.rf24.RF24
- :no-members:
-
- This class aims to be compatible with other devices in the nRF24xxx product line that
- implement the Nordic proprietary Enhanced ShockBurst Protocol (and/or the legacy
- ShockBurst Protocol), but officially only supports (through testing) the nRF24L01 and
- nRF24L01+ devices.
-
- :param ~busio.SPI spi: The object for the SPI bus that the nRF24L01 is connected to.
-
- .. tip:: This object is meant to be shared amongst other driver classes (like
- adafruit_mcp3xxx.mcp3008 for example) that use the same SPI bus. Otherwise, multiple
- devices on the same SPI bus with different spi objects may produce errors or
- undesirable behavior.
- :param ~digitalio.DigitalInOut csn: The digital output pin that is connected to the nRF24L01's
- CSN (Chip Select Not) pin. This is required.
- :param ~digitalio.DigitalInOut ce_pin: The digital output pin that is connected to the nRF24L01's
- CE (Chip Enable) pin. This is required.
- :param int spi_frequency: Specify which SPI frequency (in Hz) to use on the SPI bus. This
- parameter only applies to the instantiated object and is made persistent via
- :py:class:`~adafruit_bus_device.spi_device.SPIDevice`.
-
- .. versionchanged:: 1.2.0
-
- - new ``spi_frequency`` parameter
- - removed all keyword arguments in favor of using the provided corresponding
- attributes.
-
-open_tx_pipe()
-******************
-
-.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_tx_pipe
-
- :param bytearray,bytes address: The virtual address of the receiving nRF24L01. The address
- specified here must match the address set to one of the RX data pipes of the receiving
- nRF24L01. The existing address can be altered by writing a bytearray with a length
- less than 5. The nRF24L01 will use the first `address_length` number of bytes for the
- RX address on the specified data pipe.
-
- .. note:: There is no option to specify which data pipe to use because the nRF24L01 only
- uses data pipe 0 in TX mode. Additionally, the nRF24L01 uses the same data pipe (pipe
- 0) for receiving acknowledgement (ACK) packets in TX mode when the `auto_ack`
- attribute is enabled for data pipe 0. Thus, RX pipe 0 is appropriated with the TX
- address (specified here) when `auto_ack` is enabled for data pipe 0.
-
-close_rx_pipe()
-******************
-
-.. automethod:: circuitpython_nrf24l01.rf24.RF24.close_rx_pipe
-
- :param int pipe_number: The data pipe to use for RX transactions. This must be in range
- [0, 5]. Otherwise a `IndexError` exception is thrown.
-
- .. versionchanged:: 1.2.0
- removed the ``reset`` parameter. Addresses assigned to pipes will persist until
- changed or power to the nRF24L01 is discontinued.
-
-open_rx_pipe()
-******************
-
-.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_rx_pipe
-
- :param int pipe_number: The data pipe to use for RX transactions. This must be in range
- [0, 5]. Otherwise a `IndexError` exception is thrown.
- :param bytearray,bytes address: The virtual address to the receiving nRF24L01. If using a
- ``pipe_number`` greater than 1, then only the MSByte of the address is written, so make
- sure MSByte (first character) is unique among other simultaneously receiving addresses.
- The existing address can be altered by writing a bytearray with a length less than 5.
- The nRF24L01 will use the first `address_length` number of bytes for the RX address on
- the specified data pipe.
-
- .. note:: The nRF24L01 shares the addresses' last 4 LSBytes on data pipes 2 through
- 5. These shared LSBytes are determined by the address set to data pipe 1.
-
-listen
-******************
-
-.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.listen
-
- Setting this attribute incorporates the proper transitioning to/from RX mode as it involves
- playing with the `power` attribute and the nRF24L01's CE pin. This attribute does not power
- down the nRF24L01, but will power it up when needed; use `power` attribute set to `False`
- to put the nRF24L01 to sleep.
-
- A valid input value is a `bool` in which:
-
- - `True` enables RX mode. Additionally, per `Appendix B of the nRF24L01+ Specifications
- Sheet `_, clears the `irq_dr` status flag, and puts nRF24L01 in power up
- mode. Notice the CE pin is be held HIGH during RX mode.
- - `False` disables RX mode. As mentioned in above link, this puts nRF24L01's power in
- Standby-I 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
- remember to flush your 3-level FIFO buffers when appropriate using `flush_tx()` or
- `flush_rx()` (see also the `read()` function).
-
- .. note:: When `ack` payloads are enabled, this attribute flushes the TX FIFO buffers
- upon exiting RX mode. However, this attribute does not flush the TX FIFO buffers
- when entering RX mode. This is done to better manage the ACK payloads loaded into
- the TX FIFO.
-
-any()
-******************
-
-.. automethod:: circuitpython_nrf24l01.rf24.RF24.any
-
- :returns:
- - `int` of the size (in bytes) of an available RX payload (if any).
- - ``0`` if there is no payload in the RX FIFO buffer.
-
-available()
-******************
-
-.. automethod:: circuitpython_nrf24l01.rf24.RF24.available
-
- This function is provided for convenience and is synonomous with the following statement:
-
- .. code-block:: python
-
- # let `nrf` be the instantiated RF24 object
- nrf.update() and nrf.pipe is not None
-
- .. versionadded:: 2.0.0
-
-read()
-******************
-
-.. automethod:: circuitpython_nrf24l01.rf24.RF24.read
-
- This function can also be used to fetch the last ACK packet's payload if `ack` is enabled.
-
- :param int length: An optional parameter to specify how many bytes to read from the RX
- FIFO buffer. This parameter is not constrained in any way.
-
- - If this parameter is less than the length of the first available payload in the
- RX FIFO buffer, then the payload will remain in the RX FIFO buffer until the
- entire payload is fetched by this function.
- - If this parameter is greater than the next available payload's length, then
- additional data from other payload(s) in the RX FIFO buffer are returned.
-
- .. note::
- The nRF24L01 will repeatedly return the last byte fetched from the RX FIFO
- buffer when there is no data to return (even if the RX FIFO is empty). Be
- aware that a payload is only removed from the RX FIFO buffer when the entire
- payload has been fetched by this function. Notice that this function always
- starts reading data from the first byte of the first available payload (if
- any) in the RX FIFO buffer. Remember the RX FIFO buffer can hold up to 3
- payloads at a maximum of 32 bytes each.
- :returns:
- If the ``length`` parameter is not specified, then this function returns a `bytearray`
- of the RX payload data or `None` if there is no payload. This also depends on the
- setting of `dynamic_payloads` & `payload_length` attributes. Consider the following
- two scenarios:
-
- - If the `dynamic_payloads` attribute is disabled, then the returned bytearray's
- length is equal to the user defined `payload_length` attribute for the data pipe
- that received the payload.
- - If the `dynamic_payloads` attribute is enabled, then the returned bytearray's length
- is equal to the payload's length
-
- When the ``length`` parameter is specified, this function strictly returns a `bytearray`
- of that length despite the contents of the RX FIFO.
-
- .. versionadded:: 1.2.0
- ``length`` parameter
-
- ..versionchanged:: 2.0.0
- renamed this method from ``recv()`` to ``read()`` beccause it isn't doing
- any actual receiving. Rather, it is only reading data from the RX FIFO that
- was already received.
-
-send()
-******************
-
-.. automethod:: circuitpython_nrf24l01.rf24.RF24.send
-
- :returns:
- - `list` if a list or tuple of payloads was passed as the ``buf`` parameter. Each item
- in the returned list will contain the returned status for each corresponding payload
- in the list/tuple that was passed. The return statuses will be in one of the
- following forms:
- - `False` if transmission fails. Transmission failure can only be detected if
- `auto_ack` is enabled for data pipe 0.
- - `True` if transmission succeeds.
- - `bytearray` or `True` when the `ack` attribute is `True`. Because the payload
- expects a responding custom ACK payload, the response is returned (upon successful
- transmission) as a `bytearray` (or `True` if ACK payload is empty). Returning the
- ACK payload can be bypassed by setting the ``send_only`` parameter as `True`.
-
- :param bytearray,bytes,list,tuple buf: The payload to transmit. This bytearray must have a
- length in range [1, 32], otherwise a `ValueError` exception is thrown. This can
- also be a list or tuple of payloads (`bytearray`); in which case, all items in the
- list/tuple are processed for consecutive transmissions.
-
- - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this
- bytearray's length is less than the `payload_length` attribute for pipe 0,
- then this bytearray is padded with zeros until its length is equal to the
- `payload_length` attribute for pipe 0.
- - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this
- bytearray's length is greater than `payload_length` attribute for pipe 0,
- then this bytearray's length is truncated to equal the `payload_length`
- attribute for pipe 0.
- :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait
- for an acknowledgment from the receiving nRF24L01. This parameter directly controls a
- ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information
- about the payload). Therefore, it takes advantage of an nRF24L01 feature specific to
- individual payloads, and its value is not saved anywhere. You do not need to specify
- this for every payload if the `auto_ack` attribute is disabled (for data pipe 0),
- however setting this parameter to `True` will work despite the `auto_ack`
- attribute's setting.
-
- .. important:: If the `allow_ask_no_ack` attribute is disabled (set to `False`),
- then this parameter will have no affect at all. By default the
- `allow_ask_no_ack` attribute is enabled.
- .. note:: Each transmission is in the form of a packet. This packet contains sections
- of data around and including the payload. `See Chapter 7.3 in the nRF24L01
- Specifications Sheet `_ for more
- details.
- :param int force_retry: The number of brute-force attempts to `resend()` a failed
- transmission. Default is 0. This parameter has no affect on transmissions if
- `auto_ack` is disabled or if ``ask_no_ack`` parameter is set to `True`. Each
- re-attempt still takes advantage of
- `Auto-Retry feature `_. During multi-payload
- processing, this parameter is meant to slow down CircuitPython devices just enough
- for the Raspberry Pi to catch up (due to the Raspberry Pi's seemingly slower SPI
- speeds).
- :param bool send_only: This parameter only applies when the `ack` attribute is set to
- `True`. Pass this parameter as `True` if the RX FIFO is not to be manipulated. Many
- other libraries' behave as though this parameter is `True`
- (e.g. The popular TMRh20 Arduino RF24 library). This parameter defaults to `False`.
- If this parameter is set to `True`, then use `read()` to get the ACK payload
- (if there is any) from the RX FIFO. Remember that the RX FIFO can only hold
- up to 3 payloads at once.
-
- .. tip:: It is highly recommended that `auto_ack` attribute is enabled
- when sending multiple payloads. Test results with the `auto_ack` attribute
- disabled were rather poor (less than 79% received by a Raspberry Pi). This same
- advice applies to the ``ask_no_ack`` parameter (leave it as `False` for multiple
- payloads).
- .. warning:: The nRF24L01 will block usage of the TX FIFO buffer upon failed
- transmissions. Failed transmission's payloads stay in TX FIFO buffer until the MCU
- calls `flush_tx()` and `clear_status_flags()`. Therefore, this function will discard
- any payloads in the TX FIFO when called, but failed transmissions' payloads will
- remain in the TX FIFO until `send()` or `flush_tx()` is called after failed
- transmissions.
- .. versionadded:: 1.2.0
- ``send_only`` parameter
+
+Basic RF24 API
+--------------
+
+.. autoclass:: circuitpython_nrf24l01.rf24.RF24
+ :no-members:
+
+ This class aims to be compatible with other devices in the nRF24xxx product line that
+ implement the Nordic proprietary Enhanced ShockBurst Protocol (and/or the legacy
+ ShockBurst Protocol), but officially only supports (through testing) the nRF24L01 and
+ nRF24L01+ devices.
+
+ :param ~busio.SPI spi: The object for the SPI bus that the nRF24L01 is connected to.
+
+ .. tip:: This object is meant to be shared amongst other driver classes (like
+ adafruit_mcp3xxx.mcp3008 for example) that use the same SPI bus. Otherwise, multiple
+ devices on the same SPI bus with different spi objects may produce errors or
+ undesirable behavior.
+ :param ~digitalio.DigitalInOut csn: The digital output pin that is connected to the nRF24L01's
+ CSN (Chip Select Not) pin. This is required.
+ :param ~digitalio.DigitalInOut ce_pin: The digital output pin that is connected to the nRF24L01's
+ CE (Chip Enable) pin. This is required.
+ :param int spi_frequency: Specify which SPI frequency (in Hz) to use on the SPI bus. This
+ parameter only applies to the instantiated `RF24` object and is made persistent via
+ :py:class:`~adafruit_bus_device.SPIDevice`.
+
+ .. versionchanged:: 1.2.0
+
+ - new ``spi_frequency`` parameter
+ - removed all keyword arguments in favor of using the provided corresponding
+ attributes.
+
+.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_tx_pipe
+
+ :param bytearray,bytes address: The virtual address of the receiving nRF24L01. The address
+ specified here must match the address set to one of the RX data pipes of the receiving
+ nRF24L01. The existing address can be altered by writing a bytearray with a length
+ less than 5. The nRF24L01 will use the first `address_length` number of bytes for the
+ RX address on the specified data pipe.
+
+ .. note:: There is no option to specify which data pipe to use because the nRF24L01 only
+ uses data pipe 0 in TX mode. Additionally, the nRF24L01 uses the same data pipe (pipe
+ 1) for receiving acknowledgement (ACK) packets in TX mode when the `auto_ack`
+ attribute is enabled for data pipe 0. Thus, RX pipe 0 is appropriated with the TX
+ address (specified here) when `auto_ack` is enabled for data pipe 0.
+
+.. automethod:: circuitpython_nrf24l01.rf24.RF24.close_rx_pipe
+
+ :param int pipe_number: The data pipe to use for RX transactions. This must be in range
+ [0, 5]. Otherwise a `IndexError` exception is thrown.
+
+ .. versionchanged:: 1.2.0
+ removed the ``reset`` parameter. Addresses assigned to pipes will persist until
+ changed or power to the nRF24L01 is discontinued.
+
+.. automethod:: circuitpython_nrf24l01.rf24.RF24.open_rx_pipe
+
+ :param int pipe_number: The data pipe to use for RX transactions. This must be in range
+ [0, 5]. Otherwise a `IndexError` exception is thrown.
+ :param bytearray,bytes address: The virtual address to the receiving nRF24L01. If using a
+ ``pipe_number`` greater than 1, then only the MSByte of the address is written, so make
+ sure MSByte (first character) is unique among other simultaneously receiving addresses.
+ The existing address can be altered by writing a bytearray with a length less than 5.
+ The nRF24L01 will use the first `address_length` number of bytes for the RX address on
+ the specified data pipe.
+
+ .. note:: The nRF24L01 shares the addresses' last 4 LSBytes on data pipes 2 through
+ 5. These shared LSBytes are determined by the address set to data pipe 1.
+
+.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.listen
+
+ Setting this attribute incorporates the proper transitioning to/from RX mode as it involves
+ playing with the `power` attribute and the nRF24L01's CE pin. This attribute does not power
+ down the nRF24L01, but will power it up if needed; use `power` attribute set to `False`
+ to put the nRF24L01 to sleep.
+
+ A valid input value is a `bool` in which:
+
+ - `True` enables RX mode. Additionally, per `Appendix B of the nRF24L01+ Specifications
+ Sheet `_, puts nRF24L01 in
+ power up mode. Notice the CE pin is be held HIGH during RX mode.
+ - `False` disables RX mode. As mentioned in above link, this puts nRF24L01's power in
+ Standby-I 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
+ remember to flush your 3-level FIFO buffers when appropriate using `flush_tx()` or
+ `flush_rx()` (see also the `read()` function).
+
+ .. note:: When `ack` payloads are enabled, this attribute flushes the TX FIFO buffers
+ upon exiting RX mode. However, this attribute does not flush the TX FIFO buffers
+ when entering RX mode. This is done to better manage the ACK payloads loaded into
+ the TX FIFO.
+
+ .. versionchanged:: 2.1.0
+ Prior to v2.1.0 this attribute would clear the status flags when entering RX mode. This
+ was removed to expedite applications that use manually transmitted acknowledgement
+ payloads.
+
+.. automethod:: circuitpython_nrf24l01.rf24.RF24.any
+
+ :returns:
+ - `int` of the size (in bytes) of an available RX payload (if any).
+ - ``0`` if there is no payload in the RX FIFO buffer.
+
+.. automethod:: circuitpython_nrf24l01.rf24.RF24.available
+
+ This function is provided for convenience and is synonomous with the following statement:
+
+ .. code-block:: python
+
+ # let `nrf` be the instantiated RF24 object
+ nrf.update() and nrf.pipe is not None
+
+ .. versionadded:: 2.0.0
+
+.. automethod:: circuitpython_nrf24l01.rf24.RF24.read
+
+ The `irq_dr` status flag is reset autmotically. This function can also be used to fetch
+ the last ACK packet's payload if `ack` is enabled.
+
+ :param int length: An optional parameter to specify how many bytes to read from the RX
+ FIFO buffer. This parameter is not constrained in any way.
+
+ - If this parameter is less than the length of the first available payload in the
+ RX FIFO buffer, then the payload will remain in the RX FIFO buffer until the
+ entire payload is fetched by this function.
+ - If this parameter is greater than the next available payload's length, then
+ additional data from other payload(s) in the RX FIFO buffer are returned.
+
+ .. note::
+ The nRF24L01 will repeatedly return the last byte fetched from the RX FIFO
+ buffer when there is no data to return (even if the RX FIFO is empty). Be
+ aware that a payload is only removed from the RX FIFO buffer when the entire
+ payload has been fetched by this function. Notice that this function always
+ starts reading data from the first byte of the first available payload (if
+ any) in the RX FIFO buffer. Remember the RX FIFO buffer can hold up to 3
+ payloads at a maximum of 32 bytes each.
+ :returns:
+ If the ``length`` parameter is not specified, then this function returns a `bytearray`
+ of the RX payload data or `None` if there is no payload. This also depends on the
+ setting of `dynamic_payloads` & `payload_length` attributes. Consider the following
+ two scenarios:
+
+ - If the `dynamic_payloads` attribute is disabled, then the returned bytearray's
+ length is equal to the user defined `payload_length` attribute for the data pipe
+ that received the payload.
+ - If the `dynamic_payloads` attribute is enabled, then the returned bytearray's length
+ is equal to the payload's length
+
+ When the ``length`` parameter is specified, this function strictly returns a `bytearray`
+ of that length despite the contents of the RX FIFO.
+
+ .. versionadded:: 1.2.0
+ ``length`` parameter
+
+ .. versionchanged:: 2.0.0
+ renamed this method from ``recv()`` to ``read()`` because it isn't doing
+ any actual receiving. Rather, it is only reading data from the RX FIFO that
+ was already received/validated by the radio.
+
+.. automethod:: circuitpython_nrf24l01.rf24.RF24.send
+
+ :returns:
+ - `list` if a list or tuple of payloads was passed as the ``buf`` parameter. Each item
+ in the returned list will contain the returned status for each corresponding payload
+ in the list/tuple that was passed. The return statuses will be in one of the
+ following forms:
+ - `False` if transmission fails. Transmission failure can only be detected if
+ `auto_ack` is enabled for data pipe 0.
+ - `True` if transmission succeeds.
+ - `bytearray` or `True` when the `ack` attribute is `True`. Because the payload
+ expects a responding custom ACK payload, the response is returned (upon successful
+ transmission) as a `bytearray` (or `True` if ACK payload is empty). Returning the
+ ACK payload can be bypassed by setting the ``send_only`` parameter as `True`.
+
+ :param bytearray,bytes,list,tuple buf: The payload to transmit. This bytearray must have a
+ length in range [1, 32], otherwise a `ValueError` exception is thrown. This can
+ also be a list or tuple of payloads (`bytearray`); in which case, all items in the
+ list/tuple are processed for consecutive transmissions.
+
+ - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this
+ bytearray's length is less than the `payload_length` attribute for pipe 0,
+ then this bytearray is padded with zeros until its length is equal to the
+ `payload_length` attribute for pipe 0.
+ - If the `dynamic_payloads` attribute is disabled for data pipe 0 and this
+ bytearray's length is greater than `payload_length` attribute for pipe 0,
+ then this bytearray's length is truncated to equal the `payload_length`
+ attribute for pipe 0.
+ :param bool ask_no_ack: Pass this parameter as `True` to tell the nRF24L01 not to wait
+ for an acknowledgment from the receiving nRF24L01. This parameter directly controls a
+ ``NO_ACK`` flag in the transmission's Packet Control Field (9 bits of information
+ about the payload). Therefore, it takes advantage of an nRF24L01 feature specific to
+ individual payloads, and its value is not saved anywhere. You do not need to specify
+ this for every payload if the `auto_ack` attribute is disabled (for data pipe 0),
+ however setting this parameter to `True` will work despite the `auto_ack`
+ attribute's setting.
+
+ .. important:: If the `allow_ask_no_ack` attribute is disabled (set to `False`),
+ then this parameter will have no affect at all. By default the
+ `allow_ask_no_ack` attribute is enabled.
+ .. note:: Each transmission is in the form of a packet. This packet contains sections
+ of data around and including the payload. `See Chapter 7.3 in the nRF24L01
+ Specifications Sheet `_ for more
+ details.
+ :param int force_retry: The number of brute-force attempts to `resend()` a failed
+ transmission. Default is 0. This parameter has no affect on transmissions if
+ `auto_ack` is disabled or if ``ask_no_ack`` parameter is set to `True`. Each
+ re-attempt still takes advantage of
+ `Auto-Retry feature `_. During multi-payload
+ processing, this parameter is meant to slow down CircuitPython devices just enough
+ for the Raspberry Pi to catch up (due to the Raspberry Pi's seemingly slower SPI
+ speeds).
+ :param bool send_only: This parameter only applies when the `ack` attribute is set to
+ `True`. Pass this parameter as `True` if the RX FIFO is not to be manipulated. Many
+ other libraries' behave as though this parameter is `True`
+ (e.g. The popular TMRh20 Arduino RF24 library). This parameter defaults to `False`.
+ If this parameter is set to `True`, then use `read()` to get the ACK payload
+ (if there is any) from the RX FIFO. Remember that the RX FIFO can only hold
+ up to 3 payloads at once.
+
+ .. tip:: It is highly recommended that `auto_ack` attribute is enabled
+ when sending multiple payloads. Test results with the `auto_ack` attribute
+ disabled were rather poor (less than 79% received by a Raspberry Pi). This same
+ advice applies to the ``ask_no_ack`` parameter (leave it as `False` for multiple
+ payloads).
+ .. warning:: The nRF24L01 will block usage of the TX FIFO buffer upon failed
+ transmissions. Failed transmission's payloads stay in TX FIFO buffer until the MCU
+ calls `flush_tx()` and `clear_status_flags()`. Therefore, this function will discard
+ any payloads in the TX FIFO when called, but failed transmissions' payloads will
+ remain in the TX FIFO until `send()` or `flush_tx()` is called after failed
+ transmissions.
+ .. versionadded:: 1.2.0
+ ``send_only`` parameter
diff --git a/docs/ble_api.rst b/docs/core_api/ble_api.rst
similarity index 66%
rename from docs/ble_api.rst
rename to docs/core_api/ble_api.rst
index 895415c..effe394 100644
--- a/docs/ble_api.rst
+++ b/docs/core_api/ble_api.rst
@@ -1,379 +1,440 @@
-BLE API
-=================
-
-.. versionadded:: 1.2.0
- BLE API added
-
-BLE Limitations
----------------
-
-This module uses the `RF24` class to make the nRF24L01 imitate a
-Bluetooth-Low-Emissions (BLE) beacon. A BLE beacon can send data (referred to as
-advertisements) to any BLE compatible device (ie smart devices with Bluetooth
-4.0 or later) that is listening.
-
-Original research was done by `Dmitry Grinberg and his write-up (including C
-source code) can be found here
-`_
-As this technique can prove invaluable in certain project designs, the code
-here has been adapted to work with CircuitPython.
-
-.. important:: Because the nRF24L01 wasn't designed for BLE advertising, it
- has some limitations that helps to be aware of.
-
- 1. The maximum payload length is shortened to **18** bytes (when not
- broadcasting a device
- :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.name` nor
- the nRF24L01
- :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.show_pa_level`).
- This is calculated as:
-
- **32** (nRF24L01 maximum) - **6** (MAC address) - **5** (required
- flags) - **3** (CRC checksum) = **18**
-
- Use the helper function
- :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.available()` to
- detirmine if your payload can be transmit.
- 2. the channels that BLE use are limited to the following three: 2.402
- GHz, 2.426 GHz, and 2.480 GHz. We have provided a tuple of these
- specific channels for convenience (See `BLE_FREQ` and `hop_channel()`).
- 3. :py:attr:`~circuitpython_nrf24l01.rf24.RF24.crc` is disabled in the
- nRF24L01 firmware because BLE specifications require 3 bytes
- (:py:func:`~circuitpython_nrf24l01.fake_ble.crc24_ble()`), and the
- nRF24L01 firmware can only handle a maximum of 2.
- Thus, we have appended the required 3 bytes of CRC24 into the payload.
- 4. :py:attr:`~circuitpython_nrf24l01.rf24.RF24.address_length` of BLE
- packet only uses 4 bytes, so we have set that accordingly.
- 5. The :py:attr:`~circuitpython_nrf24l01.rf24.RF24.auto_ack` (automatic
- acknowledgment) feature of the nRF24L01 is useless when tranmitting to
- BLE devices, thus it is disabled as well as automatic re-transmit
- (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.arc`) and custom ACK
- payloads (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.ack`) features
- which both depend on the automatic acknowledgments feature.
- 6. The :py:attr:`~circuitpython_nrf24l01.rf24.RF24.dynamic_payloads`
- feature of the nRF24L01 isn't compatible with BLE specifications. Thus,
- we have disabled it.
- 7. BLE specifications only allow using 1 Mbps RF
- :py:attr:`~circuitpython_nrf24l01.rf24.RF24.data_rate`, so that too has
- been hard coded.
- 8. Only the "on data sent"
- (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_ds`) & "on data ready"
- (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr`) events will have
- an effect on the interrupt (IRQ) pin. The "on data fail"
- (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_df`) is never
- triggered because
- :py:attr:`~circuitpython_nrf24l01.rf24.RF24.auto_ack` attribute is disabled.
-
-helpers
-----------------
-
-swap_bits()
-*****************
-
-.. autofunction:: circuitpython_nrf24l01.fake_ble.swap_bits
-
- :returns:
- An `int` containing the byte whose bits are reversed
- compared to the value passed to the ``original`` parameter.
- :param int original: This is truncated to a single unsigned byte,
- meaning this parameter's value can only range from 0 to 255.
-
-reverse_bits()
-*****************
-
-.. autofunction:: circuitpython_nrf24l01.fake_ble.reverse_bits
-
- :returns:
- A `bytearray` whose byte order remains the same, but each
- byte's bit order is reversed.
- :param bytearray,bytes original: The original buffer whose bits are to be
- reversed.
-
-chunk()
-*****************
-
-.. autofunction:: circuitpython_nrf24l01.fake_ble.chunk
-
- :param bytearray,bytes buf: The actual data contained in the block.
- :param int data_type: The type of data contained in the chunk. This is a
- predefined number according to BLE specifications. The default value
- ``0x16`` describes all service data. ``0xFF`` describes manufacturer
- information. Any other values are not applicable to BLE
- advertisements.
-
- .. important:: This function is called internally by
- :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.advertise()`.
- To pack multiple data values into a single payload, use this function
- for each data value and pass a `list` or `tuple` of the returned
- results to
- :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.advertise()`
- (see example code in documentation about
- :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.advertise()`
- for more detail). Remember that broadcasting multiple data values may
- require the :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.name`
- be set to `None` and/or the
- :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.show_pa_level` be
- set to `False` for reasons about the payload size with
- `BLE Limitations`_.
-
-crc24_ble()
-*****************
-
-.. autofunction:: circuitpython_nrf24l01.fake_ble.crc24_ble
-
- This is exposed for convenience and should not be used for other buffer
- protocols that require big endian CRC24 format.
-
- :param bytearray,bytes data: The buffer of data to be uncorrupted.
- :param int deg_poly: A preset "degree polynomial" in which each bit
- represents a degree who's coefficient is 1. BLE specfications require
- ``0x00065b`` (default value).
- :param int init_val: This will be the initial value that the checksum
- will use while shifting in the buffer data. BLE specfications require
- ``0x555555`` (default value).
- :returns: A 24-bit `bytearray` representing the checksum of the data (in
- proper little endian).
-
-BLE_FREQ
-*****************
-
-.. autodata:: circuitpython_nrf24l01.fake_ble.BLE_FREQ
-
- This tuple contains the relative predefined channels used:
-
- .. csv-table::
- :header: "nRF24L01 channel", "BLE channel"
-
- 2, 37
- 26, 38
- 80, 39
-
-FakeBLE class
--------------
-
-.. autoclass:: circuitpython_nrf24l01.fake_ble.FakeBLE
-
- Per the limitations of this technique, only some of underlying
- :py:class:`~circuitpython_nrf24l01.rf24.RF24` functionality is
- available for configuration when implementing BLE transmissions.
- See the `Unavailable RF24 functionality`_ for more details.
-
-
- :param ~busio.SPI spi: The object for the SPI bus that the nRF24L01 is connected to.
-
- .. tip:: This object is meant to be shared amongst other driver classes (like
- adafruit_mcp3xxx.mcp3008 for example) that use the same SPI bus. Otherwise, multiple
- devices on the same SPI bus with different spi objects may produce errors or
- undesirable behavior.
- :param ~digitalio.DigitalInOut csn: The digital output pin that is connected to the nRF24L01's
- CSN (Chip Select Not) pin. This is required.
- :param ~digitalio.DigitalInOut ce_pin: The digital output pin that is connected to the nRF24L01's
- CE (Chip Enable) pin. This is required.
- :param int spi_frequency: Specify which SPI frequency (in Hz) to use on the SPI bus. This
- parameter only applies to the instantiated object and is made persistent via
- :py:class:`~adafruit_bus_device.spi_device.SPIDevice`.
-
-mac
-************
-
-.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.mac
-
- You can set this attribute using a 6-byte `int` or `bytearray`. If this is
- set to `None`, then a random 6-byte address is generated.
-
-name
-************
-
-.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.name
-
- This is not required. In fact setting this attribute will subtract from
- the available payload length (in bytes). Set this attribute to `None` to
- disable advertising the device name.
-
- .. note:: This information occupies (in the TX FIFO) an extra 2 bytes plus
- the length of the name set by this attribute.
-
-show_pa_level
-*************
-
-.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.show_pa_level
-
- The default value of `False` will exclude this optional information.
-
- .. note:: This information occupies (in the TX FIFO) an extra 3 bytes, and is
- really only useful for some applications to calculate proximity to the
- nRF24L01 transceiver.
-
-hop_channel()
-*************
-
-.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.hop_channel
-
-whiten()
-*************
-
-.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.whiten
-
- 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
- applied.
-
- .. warning:: This function uses the currently set BLE channel as a
- base case for the whitening coefficient. Do not call
- `hop_channel()` before using this function to de-whiten received
- payloads (which isn't officially supported yet). Note that
- `advertise()` uses this function internally to prevent such
- improper usage.
-
-len_available()
-******************
-
-.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.len_available
-
- This is detirmined from the current state of `name` and `show_pa_level`
- attributes.
-
- :param bytearray,bytes hypothetical: Pass a potential `chunk()` of
- data to this parameter to calculate the resulting left over length
- in bytes. This parameter is optional.
- :returns: An `int` representing the length of available bytes for
- a single payload.
-
- .. versionchanged:: 2.0.0
- name changed from "available" to "len_available" to avoid confusion with
- :py:func:`circuitpython_nrf24l01.rf24.RF24.available()`. This change also
- allows providing the underlying `RF24` class'
- :py:func:`~circuitpython_nrf24l01.rf24.RF24.available()` method in the
- `FakeBLE` API.
-
-advertise()
-*************
-
-.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.advertise
-
- :returns: Nothing as every transmission will register as a success
- under the required settings for BLE beacons.
-
- :param bytearray buf: The payload to transmit. This bytearray must have
- a length greater than 0 and less than 22 bytes Otherwise a
- `ValueError` exception is thrown whose prompt will tell you the
- maximum length allowed under the current configuration. This can
- also be a list or tuple of payloads (`bytearray`); in which case,
- all items in the list/tuple are processed are packed into 1
- payload for a single transmissions. See example code below about
- passing a `list` or `tuple` to this parameter.
- :param int data_type: This is used to describe the buffer data passed
- to the ``buf`` parameter. ``0x16`` describes all service data. The
- default value ``0xFF`` describes manufacturer information. This
- parameter is ignored when a `tuple` or `list` is passed to the
- ``buf`` parameter. Any other values are not applicable to BLE
- advertisements.
-
- .. important:: If the name and/or TX power level of the emulated BLE
- device is also to be broadcast, then the `name` and/or
- `show_pa_level` attribute(s) should be set prior to calling
- `advertise()`.
-
- To pass multiple data values to the ``buf`` parameter see the
- following code as an example:
-
- .. code-block:: python
-
- # let UUIDs be the 16-bit identifier that corresponds to the
- # BLE service type. The following values are not compatible with
- # BLE advertisements.
- UUID_1 = 0x1805
- UUID_2 = 0x1806
- service1 = ServiceData(UUID_1)
- service2 = ServiceData(UUID_2)
- service1.data = b"some value 1"
- service2.data = b"some value 2"
-
- # make a tuple of the buffers
- buffers = (
- chunk(service1.buffer),
- chunk(service2.buffer)
- )
-
- # let `ble` be the instantiated object of the FakeBLE class
- ble.advertise(buffers)
- ble.hop_channel()
-
-channel
-####################
-
-.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.channel
-
-interrupt_config()
-####################
-
-.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.interrupt_config
-
- .. warning:: The :py:attr:`circuitpython_nrf24l01.rf24.RF24.irq_df`
- attribute is not implemented for BLE operations.
-
- .. seealso:: :py:meth:`~circuitpython_nrf24l01.rf24.RF24.interrupt_config()`
-
-Unavailable RF24 functionality
-******************************
-
-The following `RF24` functionality is not available in `FakeBLE` objects:
-
-- :py:attr:`~circuitpython_nrf24l01.rf24.RF24.dynamic_payloads`
-- :py:meth:`~circuitpython_nrf24l01.rf24.RF24.set_dynamic_payloads()`
-- :py:attr:`~circuitpython_nrf24l01.rf24.RF24.data_rate`
-- :py:attr:`~circuitpython_nrf24l01.rf24.RF24.address_length`
-- :py:attr:`~circuitpython_nrf24l01.rf24.RF24.auto_ack`
-- :py:meth:`~circuitpython_nrf24l01.rf24.RF24.set_auto_ack()`
-- :py:attr:`~circuitpython_nrf24l01.rf24.RF24.ack`
-- :py:attr:`~circuitpython_nrf24l01.rf24.RF24.crc`
-- :py:meth:`~circuitpython_nrf24l01.rf24.RF24.open_rx_pipe()`
-- :py:meth:`~circuitpython_nrf24l01.rf24.RF24.open_tx_pipe()`
-
-
-Service related classes
------------------------
-
-abstract parent
-***************
-
-.. autoclass:: circuitpython_nrf24l01.fake_ble.ServiceData
- :members:
- :special-members: __len__
-
- :param int uuid: The 16-bit UUID `"GATT Service assigned number"
- `_ defined by the
- Bluetooth SIG to describe the service data. This parameter is
- required.
-
-derivitive children
-*******************
-
-.. autoclass:: circuitpython_nrf24l01.fake_ble.TemperatureServiceData
- :show-inheritance:
-
- This class's `data` attribute accepts a `float` value as
- input and returns a `bytes` object that conforms to the Bluetooth
- Health Thermometer Measurement format as defined in the `GATT
- Specifications Supplement. `_
-
-.. autoclass:: circuitpython_nrf24l01.fake_ble.BatteryServiceData
- :show-inheritance:
-
- The class's `data` attribute accepts a 1-byte unsigned `int` value as
- input and returns a `bytes` object that conforms to the Bluetooth
- Battery Level format as defined in the `GATT Specifications
- Supplement. `_
-
-.. autoclass:: circuitpython_nrf24l01.fake_ble.UrlServiceData
- :members: pa_level_at_1_meter
- :show-inheritance:
-
- This class's `data` attribute accepts a `str` of URL data as input, and
- returns the URL as a `bytes` object where some of the URL parts are
- encoded using `Eddystone byte codes as defined by the specifications.
- `_
\ No newline at end of file
+BLE API
+=================
+
+.. versionadded:: 1.2.0
+ BLE API added
+
+BLE Limitations
+---------------
+
+This module uses the `RF24` class to make the nRF24L01 imitate a
+Bluetooth-Low-Emissions (BLE) beacon. A BLE beacon can send data (referred to as
+advertisements) to any BLE compatible device (ie smart devices with Bluetooth
+4.0 or later) that is listening.
+
+Original research was done by `Dmitry Grinberg and his write-up (including C
+source code) can be found here
+`_
+As this technique can prove invaluable in certain project designs, the code
+here has been adapted to work with CircuitPython.
+
+.. important:: Because the nRF24L01 wasn't designed for BLE advertising, it
+ has some limitations that helps to be aware of.
+
+ 1. The maximum payload length is shortened to **18** bytes (when not
+ broadcasting a device
+ :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.name` nor
+ the nRF24L01
+ :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.show_pa_level`).
+ This is calculated as:
+
+ **32** (nRF24L01 maximum) - **6** (MAC address) - **5** (required
+ flags) - **3** (CRC checksum) = **18**
+
+ Use the helper function
+ :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.len_available()` to
+ detirmine if your payload can be transmit.
+ 2. the channels that BLE use are limited to the following three: 2.402
+ GHz, 2.426 GHz, and 2.480 GHz. We have provided a tuple of these
+ specific channels for convenience (See `BLE_FREQ` and `hop_channel()`).
+ 3. :py:attr:`~circuitpython_nrf24l01.rf24.RF24.crc` is disabled in the
+ nRF24L01 firmware because BLE specifications require 3 bytes
+ (:py:func:`~circuitpython_nrf24l01.fake_ble.crc24_ble()`), and the
+ nRF24L01 firmware can only handle a maximum of 2.
+ Thus, we have appended the required 3 bytes of CRC24 into the payload.
+ 4. :py:attr:`~circuitpython_nrf24l01.rf24.RF24.address_length` of BLE
+ packet only uses 4 bytes, so we have set that accordingly.
+ 5. The :py:attr:`~circuitpython_nrf24l01.rf24.RF24.auto_ack` (automatic
+ acknowledgment) feature of the nRF24L01 is useless when tranmitting to
+ BLE devices, thus it is disabled as well as automatic re-transmit
+ (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.arc`) and custom ACK
+ payloads (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.ack`) features
+ which both depend on the automatic acknowledgments feature.
+ 6. The :py:attr:`~circuitpython_nrf24l01.rf24.RF24.dynamic_payloads`
+ feature of the nRF24L01 isn't compatible with BLE specifications. Thus,
+ we have disabled it.
+ 7. BLE specifications only allow using 1 Mbps RF
+ :py:attr:`~circuitpython_nrf24l01.rf24.RF24.data_rate`, so that too has
+ been hard coded.
+ 8. Only the "on data sent"
+ (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_ds`) & "on data ready"
+ (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr`) events will have
+ an effect on the interrupt (IRQ) pin. The "on data fail"
+ (:py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_df`) is never
+ triggered because
+ :py:attr:`~circuitpython_nrf24l01.rf24.RF24.auto_ack` attribute is disabled.
+
+``fake_ble`` module helpers
+---------------------------
+
+.. autofunction:: circuitpython_nrf24l01.fake_ble.swap_bits
+
+ :returns:
+ An `int` containing the byte whose bits are reversed
+ compared to the value passed to the ``original`` parameter.
+ :param int original: This is truncated to a single unsigned byte,
+ meaning this parameter's value can only range from 0 to 255.
+
+.. autofunction:: circuitpython_nrf24l01.fake_ble.reverse_bits
+
+ :returns:
+ A `bytearray` whose byte order remains the same, but each
+ byte's bit order is reversed.
+ :param bytearray,bytes original: The original buffer whose bits are to be
+ reversed.
+
+.. autofunction:: circuitpython_nrf24l01.fake_ble.chunk
+
+ :param bytearray,bytes buf: The actual data contained in the block.
+ :param int data_type: The type of data contained in the chunk. This is a
+ predefined number according to BLE specifications. The default value
+ ``0x16`` describes all service data. ``0xFF`` describes manufacturer
+ information. Any other values are not applicable to BLE
+ advertisements.
+
+ .. important:: This function is called internally by
+ :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.advertise()`.
+ To pack multiple data values into a single payload, use this function
+ for each data value and pass a `list` or `tuple` of the returned
+ results to
+ :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.advertise()`
+ (see example code in documentation about
+ :py:func:`~circuitpython_nrf24l01.fake_ble.FakeBLE.advertise()`
+ for more detail). Remember that broadcasting multiple data values may
+ require the :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.name`
+ be set to `None` and/or the
+ :py:attr:`~circuitpython_nrf24l01.fake_ble.FakeBLE.show_pa_level` be
+ set to `False` for reasons about the payload size with
+ `BLE Limitations`_.
+
+.. autofunction:: circuitpython_nrf24l01.fake_ble.crc24_ble
+
+ This is exposed for convenience and should not be used for other buffer
+ protocols that require big endian CRC24 format.
+
+ :param bytearray,bytes data: The buffer of data to be uncorrupted.
+ :param int deg_poly: A preset "degree polynomial" in which each bit
+ represents a degree who's coefficient is 1. BLE specfications require
+ ``0x00065b`` (default value).
+ :param int init_val: This will be the initial value that the checksum
+ will use while shifting in the buffer data. BLE specfications require
+ ``0x555555`` (default value).
+ :returns: A 24-bit `bytearray` representing the checksum of the data (in
+ proper little endian).
+
+.. autofunction:: circuitpython_nrf24l01.fake_ble.whitener
+
+ This is a helper function to `FakeBLE.whiten()`. It has been broken out of the
+ `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
+ 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
+ (plus 37).
+
+ .. code-block:: python
+
+ coef = None # placeholder for the coefficient
+ rx_channel = nrf.channel
+ for index, chl in enumerate(BLE_FREQ):
+ if chl == rx_channel:
+ coef = index + 37
+ break
+
+ .. note::
+ If currently used nRF24L01 channel is different from the channel in which the
+ payload was received, then set this parameter accordingly.
+
+.. autodata:: circuitpython_nrf24l01.fake_ble.BLE_FREQ
+
+ This tuple contains the relative predefined channels used:
+
+ .. csv-table::
+ :header: "nRF24L01 channel", "BLE channel"
+
+ 2, 37
+ 26, 38
+ 80, 39
+
+QueueElement class
+------------------
+
+.. versionadded:: 2.1.0
+ This class was added when implementing BLE signal sniffing.
+
+.. autoclass:: circuitpython_nrf24l01.fake_ble.QueueElement
+ :members:
+
+FakeBLE class
+-------------
+
+.. autoclass:: circuitpython_nrf24l01.fake_ble.FakeBLE
+ :show-inheritance:
+
+ Per the limitations of this technique, only some of underlying
+ :py:class:`~circuitpython_nrf24l01.rf24.RF24` functionality is
+ available for configuration when implementing BLE transmissions.
+ See the `Unavailable RF24 functionality`_ for more details.
+
+ .. seealso::
+ For all parameters' descriptions, see the
+ :py:class:`~circuitpython_nrf24l01.rf24.RF24` class' contructor documentation.
+
+.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.mac
+
+ You can set this attribute using a 6-byte `int` or `bytearray`. If this is
+ set to `None`, then a random 6-byte address is generated.
+
+.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.name
+
+ This is not required. In fact, setting this attribute will subtract from
+ the available payload length (in bytes). Set this attribute to `None` to
+ disable advertising the device name.
+
+ Valid inputs are `str`, `bytes`, `bytearray`, or `None`. A `str` will be converted to
+ a `bytes` object automatically.
+
+ .. note::
+ This information occupies (in the TX FIFO) an extra 2 bytes plus
+ the length of the name set by this attribute.
+
+ .. versionchanged:: 2.2.0
+ This attribute can also be set with a `str`, but it must be UTF-8 compatible.
+
+.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.show_pa_level
+
+ The default value of `False` will exclude this optional information.
+
+ .. note:: This information occupies (in the TX FIFO) an extra 3 bytes, and is
+ really only useful for some applications to calculate proximity to the
+ nRF24L01 transceiver.
+
+.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.channel
+
+ The only allowed channels are those contained in the `BLE_FREQ` tuple.
+
+ .. versionchanged:: 2.1.0
+ Any invalid input value (that is not found in `BLE_FREQ`) had raised a
+ `ValueError` exception. This behavior changed to ignoring invalid input values,
+ and the exception is no longer raised.
+
+.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.hop_channel
+
+.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.whiten
+
+ 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
+ applied.
+
+ .. note:: `advertise()` and
+ :meth:`~circuitpython_nrf24l01.fake_ble.FakeBLE.available()` uses
+ this function internally to prevent improper usage.
+ .. warning:: This function uses the currently set BLE channel as a
+ base case for the whitening coefficient.
+
+ Do not call `hop_channel()` before calling
+ :meth:`~circuitpython_nrf24l01.fake_ble.FakeBLE.available()`
+ because this function needs to know the correct BLE channel to
+ properly de-whiten received payloads.
+
+.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.len_available
+
+ This is detirmined from the current state of `name` and `show_pa_level`
+ attributes.
+
+ :param bytearray,bytes hypothetical: Pass a potential `chunk()` of
+ data to this parameter to calculate the resulting left over length
+ in bytes. This parameter is optional.
+ :returns: An `int` representing the length of available bytes for
+ a single payload.
+
+ .. versionchanged:: 2.0.0
+ The name of this function changed from "available" to "len_available" to avoid confusion with
+ :py:func:`circuitpython_nrf24l01.rf24.RF24.available()`. This change also
+ allows providing the underlying `RF24` class'
+ :py:func:`~circuitpython_nrf24l01.rf24.RF24.available()` method in the
+ `FakeBLE` API.
+
+.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.advertise
+
+ :returns: Nothing as every transmission will register as a success
+ under the required settings for BLE beacons.
+
+ :param bytearray buf: The payload to transmit. This bytearray must have
+ a length greater than 0 and less than 22 bytes Otherwise a
+ `ValueError` exception is thrown whose prompt will tell you the
+ maximum length allowed under the current configuration. This can
+ also be a list or tuple of payloads (`bytearray`); in which case,
+ all items in the list/tuple are processed are packed into 1
+ payload for a single transmissions. See example code below about
+ passing a `list` or `tuple` to this parameter.
+ :param int data_type: This is used to describe the buffer data passed
+ to the ``buf`` parameter. ``0x16`` describes all service data. The
+ default value ``0xFF`` describes manufacturer information. This
+ parameter is ignored when a `tuple` or `list` is passed to the
+ ``buf`` parameter. Any other values are not applicable to BLE
+ advertisements.
+
+ .. important:: If the name and/or TX power level of the emulated BLE
+ device is also to be broadcast, then the `name` and/or
+ `show_pa_level` attribute(s) should be set prior to calling
+ `advertise()`.
+
+ To pass multiple data values to the ``buf`` parameter see the
+ following code as an example:
+
+ .. code-block:: python
+
+ # let UUIDs be the 16-bit identifier that corresponds to the
+ # BLE service type. The following values are not compatible with
+ # BLE advertisements.
+ UUID_1 = 0x1805
+ UUID_2 = 0x1806
+ service1 = ServiceData(UUID_1)
+ service2 = ServiceData(UUID_2)
+ service1.data = b"some value 1"
+ service2.data = b"some value 2"
+
+ # make a tuple of the buffers
+ buffers = (
+ chunk(service1.buffer),
+ chunk(service2.buffer)
+ )
+
+ # let `ble` be the instantiated object of the FakeBLE class
+ ble.advertise(buffers)
+ ble.hop_channel()
+
+
+.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.available
+
+ This method will take the first available data from the radio's RX FIFO and
+ validate the payload using the 24bit CRC checksum at the end of the payload.
+ If the payload is indeed a valid BLE transmission that fit within the 32 bytes
+ that the nRF24L01 can capture, then this method will decipher the data within
+ the payload and enqueue the resulting `QueueElement` in the `rx_queue`.
+
+ .. tip:: Use :meth:`~circuitpython_nrf24l01.fake_ble.FakeBLE.read()` to fetch the
+ decoded data.
+
+ :Returns:
+ - `True` if payload was received *and* validated
+ - `False` if no payload was received or the received payload could not be
+ deciphered.
+
+ .. versionchanged:: 2.1.0
+ This was an added override to validate & decipher received BLE data.
+
+.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.rx_queue
+
+ Each Element in this queue is a `QueueElement` object whose members are set according to the
+ its internal decoding algorithm. The :meth:`~circuitpython_nrf24l01.fake_ble.FakeBLE.read()`
+ function will remove & return the first element in this queue.
+
+ .. hint::
+ This attribute is exposed for debugging purposes, but it can also be used by applications.
+
+ .. versionadded:: 2.1.0
+
+.. autoattribute:: circuitpython_nrf24l01.fake_ble.FakeBLE.rx_cache
+
+ This attribute is only used by :meth:`~circuitpython_nrf24l01.fake_ble.FakeBLE.available()`
+ to cache the data from the top level of the radio's RX FIFO then validate & decode it.
+
+ .. hint::
+ This attribute is exposed for debugging purposes.
+
+ .. versionadded:: 2.1.0
+
+.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.read
+
+ :Returns:
+ - `None` if nothing is the internal `rx_queue`
+ - A `QueueElement` object from the front of the `rx_queue` (like a FIFO buffer)
+
+ .. versionchanged:: 2.1.0
+ This was an added override to fetch deciphered BLE data from the `rx_queue`.
+
+.. automethod:: circuitpython_nrf24l01.fake_ble.FakeBLE.interrupt_config
+
+ .. warning:: The :py:attr:`circuitpython_nrf24l01.rf24.RF24.irq_df`
+ attribute is not implemented for BLE operations.
+
+ .. seealso::
+ :py:meth:`~circuitpython_nrf24l01.rf24.RF24.interrupt_config()`
+
+Unavailable RF24 functionality
+******************************
+
+The following `RF24` functionality is not available in `FakeBLE` objects:
+
+- :py:attr:`~circuitpython_nrf24l01.rf24.RF24.dynamic_payloads`
+- :py:meth:`~circuitpython_nrf24l01.rf24.RF24.set_dynamic_payloads()`
+- :py:attr:`~circuitpython_nrf24l01.rf24.RF24.data_rate`
+- :py:attr:`~circuitpython_nrf24l01.rf24.RF24.address_length`
+- :py:attr:`~circuitpython_nrf24l01.rf24.RF24.auto_ack`
+- :py:meth:`~circuitpython_nrf24l01.rf24.RF24.set_auto_ack()`
+- :py:attr:`~circuitpython_nrf24l01.rf24.RF24.ack`
+- :py:attr:`~circuitpython_nrf24l01.rf24.RF24.crc`
+- :py:meth:`~circuitpython_nrf24l01.rf24.RF24.open_rx_pipe()`
+- :py:meth:`~circuitpython_nrf24l01.rf24.RF24.open_tx_pipe()`
+
+
+Service related classes
+-----------------------
+
+Abstract Parent
+***************
+
+.. autoclass:: circuitpython_nrf24l01.fake_ble.ServiceData
+ :members:
+ :special-members: __len__,__repr__
+
+ :param int uuid: The 16-bit UUID `"GATT Service assigned number"
+ `_ defined by the
+ Bluetooth SIG to describe the service data. This parameter is
+ required.
+
+Service data UUID numbers
+*************************
+
+These are the 16-bit UUID numbers used by the
+`Derivitive Children of the ServiceData class `_
+
+.. autodata:: circuitpython_nrf24l01.fake_ble.TEMPERATURE_UUID
+ :annotation: = 0x1809
+.. autodata:: circuitpython_nrf24l01.fake_ble.BATTERY_UUID
+ :annotation: = 0x180F
+.. autodata:: circuitpython_nrf24l01.fake_ble.EDDYSTONE_UUID
+ :annotation: = 0xFEAA
+
+Derivitive Children
+*******************
+
+.. autoclass:: circuitpython_nrf24l01.fake_ble.TemperatureServiceData
+ :members: data
+ :show-inheritance:
+
+ .. seealso:: Bluetooth Health Thermometer Measurement format as defined in the
+ `GATT Specifications Supplement.
+ `_
+
+.. autoclass:: circuitpython_nrf24l01.fake_ble.BatteryServiceData
+ :members: data
+ :show-inheritance:
+
+ .. seealso:: The Bluetooth Battery Level format as defined in the
+ `GATT Specifications Supplement.
+ `_
+
+.. autoclass:: circuitpython_nrf24l01.fake_ble.UrlServiceData
+ :members: pa_level_at_1_meter, data
+ :show-inheritance:
+
+ .. seealso::
+ Google's `Eddystone-URL specifications
+ `_.
diff --git a/docs/configure_api.rst b/docs/core_api/configure_api.rst
similarity index 84%
rename from docs/configure_api.rst
rename to docs/core_api/configure_api.rst
index 3252061..07e58a9 100644
--- a/docs/configure_api.rst
+++ b/docs/core_api/configure_api.rst
@@ -9,6 +9,138 @@
Configurable RF24 API
-----------------------
+.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ack
+
+ Use this attribute to set/check if the custom ACK payloads feature is
+ enabled (`True`) or disabled (`False`). Default
+ setting is `False`.
+
+ .. note:: This attribute differs from the `auto_ack` attribute because the
+ `auto_ack` attribute enables or disables the use of automatic ACK *packets*. By default,
+ ACK *packets* have no *payload*. This attribute enables or disables attaching
+ payloads to the ACK packets.
+ .. seealso::
+ Use `load_ack()` attach ACK payloads.
+
+ Use `read()`, `send()`, `resend()` to retrieve ACK payloads.
+ .. important::
+ As `dynamic_payloads` and `auto_ack` attributes are required for this feature to work,
+ they are automatically enabled (on data pipe 0) as needed. However, it is required to
+ enable the `auto_ack` and `dynamic_payloads` features on all applicable pipes.
+ Disabling this feature does not disable the `auto_ack` and `dynamic_payloads`
+ attributes for any data pipe; they work just fine without this feature.
+
+.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.allow_ask_no_ack
+
+ This attribute is enabled by default, and it only exists to provide support for the
+ Si24R1. The designers of the Si24R1 (a cheap chinese clone of the nRF24L01) happened to
+ clone a typo from the first version of the nRF24L01 specification sheet. Disable this attribute for the Si24R1.
+
+.. automethod:: circuitpython_nrf24l01.rf24.RF24.interrupt_config
+
+ The digital signal from the nRF24L01's IRQ (Interrupt ReQuest) pin is active LOW.
+
+ :param bool data_recv: If this is `True`, then IRQ pin goes active when new data is put
+ into the RX FIFO buffer. Default setting is `True`
+ :param bool data_sent: If this is `True`, then IRQ pin goes active when a payload from TX
+ buffer is successfully transmit. Default setting is `True`
+ :param bool data_fail: If this is `True`, then IRQ pin goes active when the maximum
+ number of attempts to re-transmit the packet have been reached. If `auto_ack`
+ attribute is disabled for pipe 0, then this IRQ event is not used. Default setting
+ is `True`
+
+ .. note:: To fetch the status (not configuration) of these IRQ flags, use the `irq_df`,
+ `irq_ds`, `irq_dr` attributes respectively.
+
+ .. tip:: Paraphrased from nRF24L01+ Specification Sheet:
+
+ The procedure for handling :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr` IRQ
+ should be:
+
+ 1. retreive 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
+ buffer. A call to `pipe` (may require `update()` to be called beforehand), `any()`
+ or even ``(False, True)`` as parameters to `fifo()` will get this result.
+ 4. if there is more data in RX FIFO, repeat from step 1
+
+.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.data_rate
+
+ A valid input value is:
+
+ - ``1`` sets the frequency data rate to 1 Mbps
+ - ``2`` sets the frequency data rate to 2 Mbps
+ - ``250`` sets the frequency data rate to 250 kbps (see warning below)
+
+ Any invalid input throws a `ValueError` exception. Default is 1 Mbps.
+
+ .. warning::
+ 250 kbps is not available for all variants of transceivers based on the
+ nRF24L01. This library will assume that the transceiver being used does
+ support 250 kbps, but there is no way to determine (via software) if that
+ is actually the case. Please refer to your transceiver's manufacturer information to
+ 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
+ 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
+ on the hardware being used.
+
+.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.channel
+
+ A valid input value must be in range [0, 125] (that means [2.4, 2.525] GHz). Otherwise a
+ `ValueError` exception is thrown. Default is ``76`` (2.476 GHz).
+
+.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.crc
+
+ CRC (cyclic redundancy checking) is a way of making sure that the transmission didn't get
+ corrupted over the air.
+
+ A valid input value must be:
+
+ - ``0`` disables CRC (no anti-corruption of data)
+ - ``1`` enables CRC encoding scheme using 1 byte (weak anti-corruption of data)
+ - ``2`` enables CRC encoding scheme using 2 bytes (better anti-corruption of data)
+
+ Any invalid input will be clamped to range [0, 2]. Default is enabled using 2 bytes.
+
+ .. note:: The nRF24L01 automatically enables CRC if automatic acknowledgment feature is
+ enabled (see `auto_ack` attribute) for any data pipe.
+ .. versionchanged:: 2.0.0
+ Invalid input values are clamped to proper range instead of throwing a `ValueError`
+ exception.
+
+.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pa_level
+
+ Higher levels mean the transmission will cover a longer distance. Use this attribute to
+ tweak the nRF24L01 current consumption on projects that don't span large areas.
+
+ A valid input value is:
+
+ - ``-18`` sets the nRF24L01's power amplifier to -18 dBm (lowest)
+ - ``-12`` sets the nRF24L01's power amplifier to -12 dBm
+ - ``-6`` sets the nRF24L01's power amplifier to -6 dBm
+ - ``0`` sets the nRF24L01's power amplifier to 0 dBm (highest)
+
+ If this attribute is set to a `list` or `tuple`, then the list/tuple must contain the
+ desired power amplifier level (from list above) at index 0 and a `bool` to control
+ the Low Noise Amplifier (LNA) feature at index 1. All other indices will be discarded.
+
+ .. note:: The LNA feature setting only applies to the nRF24L01 (non-plus variant).
+
+ Any invalid input will invoke the default of 0 dBm with LNA enabled.
+
+.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.is_lna_enabled
+
+ LNA stands for Low Noise Amplifier. See `pa_level` attribute about how to set this. Default
+ is always enabled, but this feature is specific to non-plus variants of nRF24L01 transceivers.
+ If `is_plus_variant` attribute is `True`, then setting feature in any way has no affect.
+
dynamic_payloads
******************************
@@ -38,16 +170,13 @@ dynamic_payloads
payload length feature per pipe.
.. versionchanged:: 1.2.0
- accepts a list or tuple for control of the dynamic payload length feature per pipe.
+ Accepts a list or tuple for control of the dynamic payload length feature per pipe.
.. versionchanged:: 2.0.0
- - returns a integer instead of a boolean
- - accepts an integer for binary control of the dynamic payload length
+ - Returns a integer instead of a boolean
+ - Accepts an integer for binary control of the dynamic payload length
feature per pipe
-set_dynamic_payloads()
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
.. automethod:: circuitpython_nrf24l01.rf24.RF24.set_dynamic_payloads
:param bool enable: The state of the dynamic payload feature about a specified
@@ -59,9 +188,6 @@ set_dynamic_payloads()
.. versionadded:: 2.0.0
-get_dynamic_payloads()
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
.. automethod:: circuitpython_nrf24l01.rf24.RF24.get_dynamic_payloads
:param int pipe_number: The specific data pipe number in range [0, 5] concerning the
@@ -94,7 +220,7 @@ payload_length
The current setting of the expected static payload length feature for pipe 0 only.
.. versionchanged:: 1.2.0
- return a list of all payload length settings for all pipes. This implementation
+ Return a list of all payload length settings for all pipes. This implementation
introduced a couple bugs:
1. The settings could be changed improperly in a way that was not written to the
@@ -104,13 +230,10 @@ payload_length
the length of payloads.
.. versionchanged:: 2.0.0
- this attribute returns the configuration about static payload length for data pipe 0
+ This attribute returns the configuration about static payload length for data pipe 0
only. Use `get_payload_length()` to fetch the configuration of the static payload
length feature for any data pipe.
-set_payload_length()
-^^^^^^^^^^^^^^^^^^^^^^^^^^
-
.. automethod:: circuitpython_nrf24l01.rf24.RF24.set_payload_length
This function only affects data pipes for which the `dynamic_payloads` attribute is
@@ -126,9 +249,6 @@ set_payload_length()
.. versionadded:: 2.0.0
-get_payload_length()
-^^^^^^^^^^^^^^^^^^^^^^^^^^
-
.. automethod:: circuitpython_nrf24l01.rf24.RF24.get_payload_length
The data returned by this function is only relevant for data pipes in which the
@@ -144,12 +264,18 @@ get_payload_length()
auto_ack
******************************
-.. note::
- |mostly_rx_but_tx0| This attribute will intuitively:
- - enable the automatic acknowledgement feature for pipe 0 if any other data pipe
- is configured to use the automatic acknowledgement feature.
- - disable the acknowledgement payload feature (`ack` attribute) when the
- automatic acknowledgement feature is disabled for data pipe 0.
+.. important::
+ |mostly_rx_but_tx0|
+
+ - This attribute will intuitively disable the acknowledgement payload
+ feature (`ack` attribute) when the automatic acknowledgement feature is disabled for
+ data pipe 0.
+ - When entering in TX mode, the `listen` attribute will ensure data pipe 0 is open to
+ receive automatic acknowledgments for outgoing transmissions.
+ - Be sure to configure this attribute for data pipe 0 before calling `open_tx_pipe()`
+ because the RX address for pipe 0 needs to be overwritten for automatic acknowledgments
+ to be received in TX mode. The `listen` attribute will re-write the RX address for data
+ pipe 0 when entering RX mode if needed.
.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.auto_ack
@@ -173,16 +299,13 @@ auto_ack
acknowledgement feature per pipe.
.. versionchanged:: 1.2.0
- accepts a list or tuple for control of the automatic acknowledgement feature per pipe.
+ Accepts a list or tuple for control of the automatic acknowledgement feature per pipe.
.. versionchanged:: 2.0.0
- - returns a integer instead of a boolean
- - accepts an integer for binary control of the automatic acknowledgement feature
+ - Returns an integer instead of a boolean
+ - Accepts an integer for binary control of the automatic acknowledgement feature
per pipe
-set_auto_ack()
-^^^^^^^^^^^^^^^^^^^^^^^
-
.. automethod:: circuitpython_nrf24l01.rf24.RF24.set_auto_ack
:param bool enable: The state of the automatic acknowledgement feature about a specified
@@ -194,9 +317,6 @@ set_auto_ack()
.. versionadded:: 2.0.0
-get_auto_ack()
-^^^^^^^^^^^^^^^^^^^^^^^
-
.. automethod:: circuitpython_nrf24l01.rf24.RF24.get_auto_ack
:param int pipe_number: The specific data pipe number in range [0, 5] concerning the
@@ -209,9 +329,6 @@ get_auto_ack()
Auto-Retry feature
******************************
-arc
-^^^^^^^^^^^^^^^^^^^^^^^
-
.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.arc
The `auto_ack` attribute must be enabled on the receiving nRF24L01's pipe 0 & the
@@ -219,17 +336,17 @@ arc
attribute. If `auto_ack` is disabled on the transmitting nRF24L01's pipe 0, then this
attribute is ignored when calling `send()`.
- A valid input value will be clamped to range [0, 15]. Default is set to 3. A value of
+ A valid input value will be clamped to range [0, 15]. Default is set to 15. A value of
``0`` disables the automatic re-transmit feature, but the sending nRF24L01 will still
wait the number of microseconds specified by `ard` for an Acknowledgement (ACK) packet
response (assuming `auto_ack` is enabled).
.. versionchanged:: 2.0.0
- invalid input values are clamped to proper range instead of throwing a `ValueError`
+ Invalid input values are clamped to proper range instead of throwing a `ValueError`
exception.
-
-ard
-^^^^^^^^^^^^^^^^^^^^^^^
+ .. versionchanged:: 2.2.0
+ Default value changed from 3 to the maximum 15. This only affects performance in
+ scenarios that experience unreliable reception.
.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ard
@@ -250,165 +367,16 @@ ard
See `data_rate` attribute on how to set the data rate of the nRF24L01's transmissions.
.. versionchanged:: 2.0.0
- invalid input values are clamped to proper range instead of throwing a `ValueError`
+ Invalid input values are clamped to proper range instead of throwing a `ValueError`
exception.
-set_auto_retries()
-^^^^^^^^^^^^^^^^^^^^^^^
-
.. automethod:: circuitpython_nrf24l01.rf24.RF24.set_auto_retries
:param int delay: accepts the same input as the `ard` attribute.
:param int count: accepts the same input as the `arc` attribute.
-get_auto_retries()
-^^^^^^^^^^^^^^^^^^^^^^^
-
.. automethod:: circuitpython_nrf24l01.rf24.RF24.get_auto_retries
:Return:
A tuple containing 2 items; index 0 will be the `ard` attribute,
and index 1 will be the `arc` attribute.
-
-ack
-******************************
-
-.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.ack
-
- Use this attribute to set/check if the custom ACK payloads feature is
- enabled (`True`) or disabled (`False`). Default
- setting is `False`.
-
- .. note:: This attribute differs from the `auto_ack` attribute because the
- `auto_ack` attribute enables or disables the use of automatic ACK *packets*. By default,
- ACK *packets* have no *payload*. This attribute enables or disables attaching
- payloads to the ACK packets.
- .. seealso::
- Use `load_ack()` attach ACK payloads.
-
- Use `read()`, `send()`, `resend()` to retrieve ACK payloads.
- .. important::
- As `dynamic_payloads` and `auto_ack` attributes are required for this feature to work,
- they are automatically enabled (on data pipe 0) as needed. However, it is required to
- enable the `auto_ack` and `dynamic_payloads` features on all applicable pipes.
- Disabling this feature does not disable the `auto_ack` and `dynamic_payloads`
- attributes for any data pipe; they work just fine without this feature.
-
-allow_ask_no_ack
-******************************
-
-.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.allow_ask_no_ack
-
- This attribute is enabled by default, and it only exists to provide support for the
- Si24R1. The designers of the Si24R1 (a cheap chinese clone of the nRF24L01) happened to
- clone a typo from the first version of the nRF24L01 specification sheet. Disable this attribute for the Si24R1.
-
-interrupt_config()
-******************************
-
-.. automethod:: circuitpython_nrf24l01.rf24.RF24.interrupt_config
-
- The digital signal from the nRF24L01's IRQ (Interrupt ReQuest) pin is active LOW.
-
- :param bool data_recv: If this is `True`, then IRQ pin goes active when new data is put
- into the RX FIFO buffer. Default setting is `True`
- :param bool data_sent: If this is `True`, then IRQ pin goes active when a payload from TX
- buffer is successfully transmit. Default setting is `True`
- :param bool data_fail: If this is `True`, then IRQ pin goes active when the maximum
- number of attempts to re-transmit the packet have been reached. If `auto_ack`
- attribute is disabled for pipe 0, then this IRQ event is not used. Default setting
- is `True`
-
- .. note:: To fetch the status (not configuration) of these IRQ flags, use the `irq_df`,
- `irq_ds`, `irq_dr` attributes respectively.
-
- .. tip:: Paraphrased from nRF24L01+ Specification Sheet:
-
- The procedure for handling :py:attr:`~circuitpython_nrf24l01.rf24.RF24.irq_dr` IRQ
- should be:
-
- 1. retreive 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
- buffer. A call to `pipe` (may require `update()` to be called beforehand), `any()`
- or even ``(False, True)`` as parameters to `fifo()` will get this result.
- 4. if there is more data in RX FIFO, repeat from step 1
-
-data_rate
-******************************
-
-.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.data_rate
-
- A valid input value is:
-
- - ``1`` sets the frequency data rate to 1 Mbps
- - ``2`` sets the frequency data rate to 2 Mbps
- - ``250`` sets the frequency data rate to 250 Kbps (see warning below)
-
- Any invalid input throws a `ValueError` exception. Default is 1 Mbps.
-
- .. warning:: 250 Kbps is not available for the non-plus variants of the
- nRF24L01 transceivers. Trying to set the data rate to 250 kpbs when
- `is_plus_variant` is `True` will throw a `NotImplementedError`.
-
-channel
-******************************
-
-.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.channel
-
- A valid input value must be in range [0, 125] (that means [2.4, 2.525] GHz). Otherwise a
- `ValueError` exception is thrown. Default is ``76`` (2.476 GHz).
-
-crc
-******************************
-
-.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.crc
-
- CRC is a way of making sure that the transmission didn't get corrupted over the air.
-
- A valid input value must be:
-
- - ``0`` disables CRC (no anti-corruption of data)
- - ``1`` enables CRC encoding scheme using 1 byte (weak anti-corruption of data)
- - ``2`` enables CRC encoding scheme using 2 bytes (better anti-corruption of data)
-
- Any invalid input will be clamped to range [0, 2]. Default is enabled using 2 bytes.
-
- .. note:: The nRF24L01 automatically enables CRC if automatic acknowledgment feature is
- enabled (see `auto_ack` attribute) for any data pipe.
- .. versionchanged:: 2.0.0
- invalid input values are clamped to proper range instead of throwing a `ValueError`
- exception.
-
-pa_level
-******************************
-
-.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.pa_level
-
- Higher levels mean the transmission will cover a longer distance. Use this attribute to
- tweak the nRF24L01 current consumption on projects that don't span large areas.
-
- A valid input value is:
-
- - ``-18`` sets the nRF24L01's power amplifier to -18 dBm (lowest)
- - ``-12`` sets the nRF24L01's power amplifier to -12 dBm
- - ``-6`` sets the nRF24L01's power amplifier to -6 dBm
- - ``0`` sets the nRF24L01's power amplifier to 0 dBm (highest)
-
- If this attribute is set to a `list` or `tuple`, then the list/tuple must contain the
- desired power amplifier level (from list above) at index 0 and a `bool` to control
- the Low Noise Amplifier (LNA) feature at index 1. All other indices will be discarded.
-
- .. note:: The LNA feature setting only applies to the nRF24L01 (non-plus variant).
-
- Any invalid input will invoke the default of 0 dBm with LNA enabled.
-
-is_lna_enabled
-******************************
-
-.. autoattribute:: circuitpython_nrf24l01.rf24.RF24.is_lna_enabled
-
- See `pa_level` attribute about how to set this. Default is always enabled, but this
- feature is specific to non-plus variants of nRF24L01 transceivers. If
- `is_plus_variant` attribute is `True`, then setting feature in any way has no affect.
diff --git a/docs/examples.rst b/docs/examples.rst
index 711e4bd..7599fd7 100644
--- a/docs/examples.rst
+++ b/docs/examples.rst
@@ -1,248 +1,295 @@
-nRF24L01 Features
-=================
-
-Simple test
-------------
-
-.. versionchanged:: 2.0.0
-
- - uses 2 addresses on pipes 1 & 0 to demonstrate proper addressing convention.
- - transmits an incrementing `float` instead of an `int`
-
-Ensure your device works with this simple test.
-
-.. literalinclude:: ../examples/nrf24l01_simple_test.py
- :caption: examples/nrf24l01_simple_test.py
- :start-at: import time
- :end-before: def set_role():
-
-ACK Payloads Example
---------------------
-
-.. versionchanged:: 2.0.0
-
- - uses 2 addresses on pipes 1 & 0 to demonstrate proper addressing convention.
- - changed payloads to show use of c-strings' NULL terminating character.
-
-This is a test to show how to use custom acknowledgment payloads.
-
-.. seealso:: More details are found in the documentation on `ack` and `load_ack()`.
-
-.. literalinclude:: ../examples/nrf24l01_ack_payload_test.py
- :caption: examples/nrf24l01_ack_payload_test.py
- :start-at: import time
- :end-before: def set_role():
-
-Multiceiver Example
---------------------
-
-.. versionadded:: 1.2.2
-
-.. versionchanged:: 2.0.0
- no longer uses ACK payloads for responding to node 1.
-
-This example shows how use a group of 6 nRF24L01 transceivers to transmit to 1 nRF24L01
-transceiver. This technique is called `"Multiceiver" in the nRF24L01 Specifications Sheet
-`_
-
-.. note:: This example follows the diagram illistrated 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
- transmitting nodes) are disabled, then
- `figure 10 of section 7.7 of the nRF24L01 Specifications Sheet
- `_
- would be a better illustration.
-
-.. hint:: A paraphrased note from the the nRF24L01 Specifications Sheet:
-
- *Only when a data pipe receives a complete packet can other data pipes begin
- to receive data. When multiple [nRF24L01]s are transmitting to [one nRF24L01],
- the* `ard` *can be used to skew the auto retransmission so that they only block
- each other once.*
-
- This basically means that it might help packets get received if the `ard` attribute
- is set to various values among multiple transmitting nRF24L01 transceivers.
-
-.. literalinclude:: ../examples/nrf24l01_multiceiver_test.py
- :caption: examples/nrf24l01_multiceiver_test.py
- :start-at: import time
- :end-before: def set_role():
-
-Scanner Example
----------------
-
-.. versionadded:: 2.0.0
-
-This example simply scans the entire RF frquency (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
-radio-emitting sources (i.e. WiFi, Bluetooth, or etc).
-
-.. literalinclude:: ../examples/nrf24l01_scanner_test.py
- :caption: examples/nrf24l01_scanner_test.py
- :start-at: import time
- :end-before: def set_role():
-
-Reading the scanner output
-**************************
-
-.. hint:: Make sure the terminal window used to run the scanner example is expanded
- to fit 125 characters. Otherwise the output will look weird.
-
-The output of the scanner example is supposed to be read vertically (as columns).
-So, the following
-
- | 000
- | 111
- | 789
- | ~~~
- | 13-
-
-should be interpreted as
-
-- ``1`` signal detected on channel ``017``
-- ``3`` signals detected on channel ``018``
-- no signal (``-``) detected on channel ``019``
-
-The ``~`` is just a divider between the vertical header and the signal counts.
-
-IRQ Pin Example
----------------
-
-.. versionchanged:: 1.2.0
- uses ACK payloads to trigger all 3 IRQ events.
-.. versionchanged:: 2.0.0
- uses 2 addresses on pipes 1 & 0 to demonstrate proper addressing convention.
-
-This is a test to show how to use nRF24L01's interrupt pin using the non-blocking
-`write()`. Also the `ack` attribute is enabled to trigger the `irq_dr` event when
-the master node receives ACK payloads. Simply put, this example is the most advanced
-example script (in this library), and it runs **very** quickly.
-
-.. literalinclude:: ../examples/nrf24l01_interrupt_test.py
- :caption: examples/nrf24l01_interrupt_test.py
- :start-at: import time
- :end-before: def set_role():
-
-Library-Specific Features
-=========================
-
-Stream Example
----------------
-
-.. versionchanged:: 1.2.3
- added ``master_fifo()`` to demonstrate using full TX FIFO to stream data.
-.. versionchanged:: 2.0.0
- uses 2 addresses on pipes 1 & 0 to demonstrate proper addressing convention.
-
-This is a test to show how to stream data. The ``master()`` uses the `send()`
-function to transmit multiple payloads with 1 function call. However
-``master()`` only uses 1 level of the nRF24L01's TX FIFO. An alternate function,
-called ``master_fifo()`` uses all 3 levels of the nRF24L01's TX FIFO to stream
-data, but it uses the `write()` function to do so.
-
-.. literalinclude:: ../examples/nrf24l01_stream_test.py
- :caption: examples/nrf24l01_stream_test.py
- :start-at: import time
- :end-before: def set_role():
-
-Context Example
----------------
-
-.. versionchanged:: 1.2.0
- demonstrates switching between `FakeBLE` object & `RF24` object with the same nRF24L01
-
-This is a test to show how to use `with` blocks to manage multiple different nRF24L01 configurations on 1 transceiver.
-
-.. literalinclude:: ../examples/nrf24l01_context_test.py
- :caption: examples/nrf24l01_context_test.py
- :start-at: import board
-
-Manual ACK Example
-------------------
-
-.. versionadded:: 2.0.0
- Previously, this example was strictly made for TMRh20's RF24 library example
- titled "GettingStarted_HandlingData.ino". With the latest addition of new
- examples to the TMRh20 RF24 library, this example was renamed from
- "nrf24l01_2arduino_handling_data.py" and adapted for both this library and
- TMRh20's RF24 library.
-
-This is a test to show how to use the library for acknowledgement (ACK) responses
-without using the automatic ACK packets (like the `ACK Payloads Example`_ does).
-Beware, that this technique is not faster and can be more prone to communication
-failure. However, This technique has the advantage of using more updated information
-in the responding payload as information in ACK payloads are always outdated by 1
-transmission.
-
-.. literalinclude:: ../examples/nrf24l01_manual_ack_test.py
- :caption: examples/nrf24l01_manual_ack_test.py
- :start-at: import time
- :end-before: def set_role():
-
-OTA compatibility
-=================
-
-Fake BLE Example
-----------------
-
-.. versionadded:: 1.2.0
-
-This is a test to show how to use the nRF24L01 as a BLE advertising beacon using the
-`FakeBLE` class.
-
-.. literalinclude:: ../examples/nrf24l01_fake_ble_test.py
- :caption: examples/nrf24l01_fake_ble_test.py
- :start-at: import time
- :end-before: def set_role():
-
-TMRh20's Arduino library
-------------------------
-
-All examples are designed to work with TMRh20's RF24 library examples.
-This Circuitpython library uses dynamic payloads enabled by default.
-TMRh20's library uses static payload lengths by default.
-
-To make this circuitpython library compatible with
-`TMRh20's RF24 library `_:
-
-1. set `dynamic_payloads` to `False`.
-2. set `allow_ask_no_ack` to `False`.
-3. set :py:attr:`~circuitpython_nrf24l01.rf24.RF24.payload_length` to the value that
- is passed to TMRh20's ``RF24::setPayloadSize()``. 32 is the default (& maximum)
- payload length/size for both libraries.
-
- .. warning:: Certain C++ datatypes allocate a different amount of memory depending on
- the board being used in the Arduino IDE. For example, ``uint8_t`` isn't always
- allocated to 1 byte of memory for certain boards.
- Make sure you understand the amount of memory that different datatypes occupy in C++.
- 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,
-but this Circuitpython library uses a default value of 3.
-
-.. csv-table:: Corresponding examples
- :header: circuitpython_nrf24l01, TMRh20 RF24
-
- "nrf24l01_simple_test\ [1]_ ", gettingStarted
- nrf24l01_ack_payload_test, acknowledgementPayloads
- "nrf24l01_manual_ack_test\ [1]_ ", manualAcknowledgements
- "nrf24l01_multiceiver_test\ [1]_ ", multiceiverDemo
- "nrf24l01_stream_test\ [1]_ ", streamingData
- nrf24l01_interrupt_test, interruptConfigure
- nrf24l01_context_test, feature is not available
- nrf24l01_fake_ble_test, feature is available via `floe's BTLE library `_
-
-.. [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
- look like this in the examples' source code:
-
- .. code-block:: python
-
- # uncomment the following 3 lines for compatibility with TMRh20 library
- # nrf.allow_ask_no_ack = False
- # nrf.dynamic_payloads = False
- # nrf.payload_length = 4
+Examples
+~~~~~~~~~~~~
+
+nRF24L01 Features
+=================
+
+Simple test
+------------
+
+.. versionchanged:: 2.0.0
+
+ - uses 2 addresses on pipes 1 & 0 to demonstrate proper addressing convention.
+ - transmits an incrementing `float` instead of an `int`
+
+Ensure your device works with this simple test.
+
+.. literalinclude:: ../examples/nrf24l01_simple_test.py
+ :caption: examples/nrf24l01_simple_test.py
+ :start-at: import time
+ :end-before: def set_role():
+ :linenos:
+ :lineno-match:
+
+ACK Payloads Example
+--------------------
+
+.. versionchanged:: 2.0.0
+
+ - uses 2 addresses on pipes 1 & 0 to demonstrate proper addressing convention.
+ - changed payloads to show use of c-strings' NULL terminating character.
+
+This is a test to show how to use custom acknowledgment payloads.
+
+.. seealso:: More details are found in the documentation on `ack` and `load_ack()`.
+
+.. literalinclude:: ../examples/nrf24l01_ack_payload_test.py
+ :caption: examples/nrf24l01_ack_payload_test.py
+ :start-at: import time
+ :end-before: def set_role():
+ :linenos:
+ :lineno-match:
+
+Multiceiver Example
+--------------------
+
+.. versionadded:: 1.2.2
+
+.. versionchanged:: 2.0.0
+ no longer uses ACK payloads for responding to node 1.
+
+This example shows how use a group of 6 nRF24L01 transceivers to transmit to 1 nRF24L01
+transceiver. This technique is called `"Multiceiver" in the nRF24L01 Specifications Sheet
+`_
+
+.. note:: This example follows the diagram illistrated 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
+ transmitting nodes) are disabled, then
+ `figure 10 of section 7.7 of the nRF24L01 Specifications Sheet
+ `_
+ would be a better illustration.
+
+.. hint:: A paraphrased note from the the nRF24L01 Specifications Sheet:
+
+ *Only when a data pipe receives a complete packet can other data pipes begin
+ to receive data. When multiple [nRF24L01]s are transmitting to [one nRF24L01],
+ the* `ard` *can be used to skew the auto retransmission so that they only block
+ each other once.*
+
+ This basically means that it might help packets get received if the `ard` attribute
+ is set to various values among multiple transmitting nRF24L01 transceivers.
+
+.. literalinclude:: ../examples/nrf24l01_multiceiver_test.py
+ :caption: examples/nrf24l01_multiceiver_test.py
+ :start-at: import time
+ :end-before: def set_role():
+ :linenos:
+ :lineno-match:
+
+Scanner Example
+---------------
+
+.. versionadded:: 2.0.0
+
+This example simply scans the entire RF frquency (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
+radio-emitting sources (i.e. WiFi, Bluetooth, or etc).
+
+.. literalinclude:: ../examples/nrf24l01_scanner_test.py
+ :caption: examples/nrf24l01_scanner_test.py
+ :start-at: import time
+ :end-before: def set_role():
+ :linenos:
+ :lineno-match:
+
+Reading the scanner output
+**************************
+
+.. hint:: Make sure the terminal window used to run the scanner example is expanded
+ to fit 125 characters. Otherwise the output will look weird.
+
+The output of the scanner example is supposed to be read vertically (as columns).
+So, the following
+
+ | 000
+ | 111
+ | 789
+ | ~~~
+ | 13-
+
+should be interpreted as
+
+- ``1`` signal detected on channel ``017``
+- ``3`` signals detected on channel ``018``
+- no signal (``-``) detected on channel ``019``
+
+The ``~`` is just a divider between the vertical header and the signal counts.
+
+IRQ Pin Example
+---------------
+
+.. versionchanged:: 1.2.0
+ uses ACK payloads to trigger all 3 IRQ events.
+.. versionchanged:: 2.0.0
+ uses 2 addresses on pipes 1 & 0 to demonstrate proper addressing convention.
+
+This is a test to show how to use nRF24L01's interrupt pin using the non-blocking
+:meth:`~circuitpython_nrf24l01.rf24.RF24.write()`. Also the `ack` attribute is enabled to trigger the `irq_dr` event when
+the master node receives ACK payloads. Simply put, this example is the most advanced
+example script (in this library), and it runs **very** quickly.
+
+.. literalinclude:: ../examples/nrf24l01_interrupt_test.py
+ :caption: examples/nrf24l01_interrupt_test.py
+ :start-at: import time
+ :end-before: def set_role():
+ :linenos:
+ :lineno-match:
+
+Library-Specific Features
+=========================
+
+Stream Example
+---------------
+
+.. versionchanged:: 1.2.3
+ added ``master_fifo()`` to demonstrate using full TX FIFO to stream data.
+.. versionchanged:: 2.0.0
+ uses 2 addresses on pipes 1 & 0 to demonstrate proper addressing convention.
+
+This is a test to show how to stream data. The ``master()`` uses the :meth:`~circuitpython_nrf24l01.rf24.RF24.send()`
+function to transmit multiple payloads with 1 function call. However
+``master()`` only uses 1 level of the nRF24L01's TX FIFO. An alternate function,
+called ``master_fifo()`` uses all 3 levels of the nRF24L01's TX FIFO to stream
+data, but it uses the :meth:`~circuitpython_nrf24l01.rf24.RF24.write()` function to do so.
+
+.. literalinclude:: ../examples/nrf24l01_stream_test.py
+ :caption: examples/nrf24l01_stream_test.py
+ :start-at: import time
+ :end-before: def set_role():
+ :linenos:
+ :lineno-match:
+
+Context Example
+---------------
+
+.. versionchanged:: 1.2.0
+ demonstrates switching between `FakeBLE` object & `RF24` object with the same nRF24L01
+
+This is a test to show how to use `with` blocks to manage multiple different nRF24L01 configurations on 1 transceiver.
+
+.. literalinclude:: ../examples/nrf24l01_context_test.py
+ :caption: examples/nrf24l01_context_test.py
+ :start-at: from circuitpython_nrf24l01.rf24 import RF24
+ :linenos:
+ :lineno-match:
+
+Manual ACK Example
+------------------
+
+.. versionadded:: 2.0.0
+ Previously, this example was strictly made for TMRh20's RF24 library example
+ titled "GettingStarted_HandlingData.ino". With the latest addition of new
+ examples to the TMRh20 RF24 library, this example was renamed from
+ "nrf24l01_2arduino_handling_data.py" and adapted for both this library and
+ TMRh20's RF24 library.
+
+This is a test to show how to use the library for acknowledgement (ACK) responses
+without using the automatic ACK packets (like the `ACK Payloads Example`_ does).
+Beware, that this technique is not faster and can be more prone to communication
+failure. However, This technique has the advantage of using more updated information
+in the responding payload as information in ACK payloads are always outdated by 1
+transmission.
+
+.. literalinclude:: ../examples/nrf24l01_manual_ack_test.py
+ :caption: examples/nrf24l01_manual_ack_test.py
+ :start-at: import time
+ :end-before: def set_role():
+ :linenos:
+ :lineno-match:
+
+Network Test
+-------------------
+
+.. versionadded:: 2.1.0
+
+The following network example is designed to be compatible with most of TMRh20's C++
+examples for the RF24Mesh and RF24Network libraries. However, due to some slight differences
+this example prompts for user input which can cover a broader spectrum of usage scenarios.
+
+.. literalinclude:: ../examples/nrf24l01_network_test.py
+ :caption: examples/nrf24l01_network_test.py
+ :start-at: import time
+ :end-before: def set_role():
+ :linenos:
+ :lineno-match:
+
+
+OTA compatibility
+=================
+
+Fake BLE Example
+----------------
+
+.. versionadded:: 1.2.0
+.. versionchanged:: 2.1.0
+ A new ``slave()`` function was added to demonstrate receiving BLE data.
+
+This is a test to show how to use the nRF24L01 as a BLE advertising beacon using the
+`FakeBLE` class.
+
+.. literalinclude:: ../examples/nrf24l01_fake_ble_test.py
+ :caption: examples/nrf24l01_fake_ble_test.py
+ :start-at: import time
+ :end-before: def set_role():
+ :linenos:
+ :lineno-match:
+
+TMRh20's C++ libraries
+------------------------
+
+All examples are designed to work with TMRh20's RF24, RF24Network, and RF24Mesh libraries' examples.
+This Circuitpython library uses dynamic payloads enabled by default.
+TMRh20's RF24 library uses static payload lengths by default.
+
+To make this circuitpython library compatible with
+`TMRh20's RF24 library `_:
+
+1. set :attr:`~circuitpython_nrf24l01.rf24.RF24.dynamic_payloads` to `False`.
+2. set `allow_ask_no_ack` to `False`.
+3. set :attr:`~circuitpython_nrf24l01.rf24.RF24.payload_length` to the value that
+ is passed to TMRh20's ``RF24::setPayloadSize()``. 32 is the default (& maximum)
+ payload length/size for both libraries.
+
+ .. warning:: Certain C++ datatypes allocate a different amount of memory depending on
+ the board being used in the Arduino IDE. For example, ``uint8_t`` isn't always
+ allocated to 1 byte of memory for certain boards.
+ Make sure you understand the amount of memory that different datatypes occupy in C++.
+ 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,
+but this Circuitpython library uses a default value of 3.
+
+.. csv-table:: Corresponding examples
+ :header: circuitpython_nrf24l01, "TMRh20's C++ examples"
+ :widths: 10, 20
+
+ "nrf24l01_simple_test (\ [1]_)", "RF24 gettingStarted"
+ nrf24l01_ack_payload_test, "RF24 acknowledgementPayloads"
+ "nrf24l01_manual_ack_test (\ [1]_)", "RF24 manualAcknowledgements"
+ "nrf24l01_multiceiver_test (\ [1]_)", "RF24 multiceiverDemo"
+ "nrf24l01_stream_test (\ [1]_)", "RF24 streamingData"
+ nrf24l01_interrupt_test, "RF24 interruptConfigure"
+ nrf24l01_context_test, "feature is not available in C++"
+ 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)"
+
+.. [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
+ look like this in the examples' source code:
+
+ .. code-block:: python
+
+ # uncomment the following 3 lines for compatibility with TMRh20 library
+ # nrf.allow_ask_no_ack = False
+ # nrf.dynamic_payloads = False
+ # nrf.payload_length = 4
+.. [2] When running the network examples, it is important to understand the typical
+ `network topology `_. Otherwise, entering incorrect answers to the
+ example's user prompts may result in seemingly bad connections.
diff --git a/docs/greetings.rst b/docs/greetings.rst
deleted file mode 100644
index 86873c5..0000000
--- a/docs/greetings.rst
+++ /dev/null
@@ -1,294 +0,0 @@
-
-Getting Started
-==================
-
-This is a Circuitpython driver library for the nRF24L01(+) transceiver.
-
-Originally this code was a Micropython module written by Damien P. George
-& Peter Hinch which can still be found `here
-`_
-
-The Micropython source has since been rewritten to expose all the nRF24L01's
-features and for Circuitpython compatible devices (including linux-based
-SoC computers like the Raspberry Pi).
-Modified by Brendan Doherty & Rhys Thomas.
-
-* Authors: Damien P. George, Peter Hinch, Rhys Thomas, Brendan Doherty
-
-Features currently supported
-----------------------------
-
-* Change the address's length (can be 3 to 5 bytes long)
-* Dynamically sized payloads (max 32 bytes each) or statically sized payloads
-* Automatic responding acknowledgment (ACK) packets for verifying transmission success
-* Append custom payloadsto the acknowledgment (ACK) packets for instant bi-directional communication
-* Mark a single payload for no acknowledgment (ACK) from the receiving nRF24L01 (see ``ask_no_ack``
- parameter for :py:meth:`~circuitpython_nrf24l01.rf24.RF24.send()` and `write()` functions)
-* Invoke the "re-use the same payload" feature (for manually re-transmitting failed transmissions that
- remain in the TX FIFO buffer)
-* Multiple payload transmissions with one function call (see documentation on the
- :py:meth:`~circuitpython_nrf24l01.rf24.RF24.send()` function and try out the
- `Stream example `_)
-* Context manager compatible for easily switching between different radio configurations
- using `with` blocks (not available in ``rf24_lite.py`` version)
-* Configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or
- failed transmissions (these 3 events control 1 IRQ pin). There's also virtual
- representations of these interrupt events available (see `irq_dr`, `irq_ds`, & `irq_df` attributes)
-* Invoke sleep mode (AKA power down mode) for ultra-low current consumption
-* cyclic redundancy checking (CRC) up to 2 bytes long
-* Adjust the nRF24L01's builtin automatic re-transmit feature's parameters (`arc`: number
- of attempts, `ard`: delay between attempts)
-* Adjust the nRF24L01's frequency channel (2.4-2.525 GHz)
-* Adjust the nRF24L01's power amplifier level (0, -6, -12, or -18 dBm)
-* Adjust the nRF24L01's RF data rate (250kbps, 1Mbps, or 2Mbps)
-* An nRF24L01 driven by this library can communicate with a nRF24L01 on an Arduino driven by the `TMRh20 RF24 library `_. See the `nrf24l01_2arduino_handling_data.py `_ example.
-* fake BLE module for sending BLE beacon advertisments from the nRF24L01 as outlined by `Dmitry Grinberg in his write-up (including C source code) `_.
-* Multiceiver\ :sup:`TM` mode (up to 6 TX nRF24L01 "talking" to 1 RX nRF24L01 simultaneously). See the `Multiceiver Example `_
-
-Dependencies
---------------------------
-
-This driver depends on:
-
-* `Adafruit CircuitPython Firmware `_ or the
- `Adafruit_Blinka library `_ for Linux
- SoC boards like Raspberry Pi
-* `Adafruit_CircuitPython_BusDevice
- `_ (specifically the
- :py:mod:`~adafruit_bus_device.spi_device`)
-
-Please ensure all dependencies are available on the CircuitPython filesystem.
-This is easily achieved by downloading
-`the Adafruit library and driver bundle `_.
-
-.. note:: This library supports Python 3.7 or newer because the examples use
- the function `time.monotonic_ns() `_ which returns an arbitrary time "counter"
- as an `int` of nanoseconds. CircuitPython firmware also supports
- :py:func:`time.monotonic_ns()`.
-
-Installing from PyPI
---------------------
-
-On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from
-PyPI `_. To install for current user:
-
-.. code-block:: shell
-
- pip3 install circuitpython-nrf24l01
-
-To install system-wide (this may be required in some cases):
-
-.. code-block:: shell
-
- sudo pip3 install circuitpython-nrf24l01
-
-To install in a virtual environment in your current project:
-
-.. code-block:: shell
-
- mkdir project-name && cd project-name
- python3 -m venv .env
- source .env/bin/activate
- pip3 install circuitpython-nrf24l01
-
-Pinout
-======
-
-.. image:: https://lastminuteengineers.com/wp-content/uploads/2018/07/Pinout-nRF24L01-Wireless-Transceiver-Module.png
- :target: https://lastminuteengineers.com/nrf24l01-arduino-wireless-communication/#nrf24l01-transceiver-module-pinout
-
-The nRF24L01 is controlled through SPI so there are 3 pins (SCK, MOSI, & MISO) that can only be connected to their counterparts on the MCU (microcontroller unit). The other 2 essential pins (CE & CSN) can be connected to any digital output pins. Lastly, the only optional pin on the nRf24L01 GPIOs is the IRQ (interrupt; a digital output that's active when low) pin and is only connected to the MCU via a digital input pin during the interrupt example. The following pinout is used in the example codes of this library's `examples `_.
-
-.. csv-table::
- :header: nRF2401, "Raspberry Pi", "ItsyBitsy M4"
-
- GND, GND, GND
- VCC, 3V, 3.3V
- CE, GPIO4, D4
- CSN, GPIO5, D5
- SCK, "GPIO11 (SCK)", SCK
- MOSI, "GPIO10 (MOSI)", MOSI
- MISO, "GPIO9 (MISO)", MISO
- IRQ, GPIO12, D12
-
-.. tip:: User reports and personal experiences have improved results if there is a capacitor of 100 mirofarads [+ another optional 0.1 microfarads capacitor for added stability] connected in parrallel to the VCC and GND pins.
-
-Using The Examples
-==================
-
-See `examples `_ for testing certain features of this the library. The examples were developed and tested on both Raspberry Pi and ItsyBitsy M4. Pins have been hard coded in the examples for the corresponding device, so please adjust these accordingly to your circuitpython device if necessary.
-
-For an interactive REPL
----------------------------
-
-All examples can be imported from within an interactive python REPL.
-
-1. Make sure the examples are located in the current working directory.
- On CircuitPython devices, this will be the root directory of the CIRCUITPY drive.
-2. Import everything from desired the example. The following code snippet demonstrates running the `Simple Test example `_
-
- .. code-block:: python
-
- >>> from nrf24l01_simple_test import *
- Which radio is this? Enter '0' or '1'. Defaults to '0'
- nRF24L01 Simple test.
- Run slave() on receiver
- Run master() on transmitter
- >>> master()
- Transmission successful! Time to Transmit: 6993.972 us. Sent: 0.0
- Transmission successful! Time to Transmit: 6563.277 us. Sent: 0.01
- Transmission successful! Time to Transmit: 6453.385 us. Sent: 0.02
- Transmission successful! Time to Transmit: 6338.29 us. Sent: 0.03
- Transmission successful! Time to Transmit: 6440.163 us. Sent: 0.04
-
-For CircuitPython devices
----------------------------
-
-1. Copy the examples to the root directory of the CIRCUITPY device.
-2. Rename of the example file to ``main.py``.
-3. If the REPL is not already running, then the example should start automatically.
- If the REPL is already running in interactive mode, then press ``ctrl+d`` to do a
- soft reset, and the example should start automatically.
-
-For CPython in Linux
----------------------------
-
-1. Clone the library repository, then navigate to the reository's example directory.
-
- .. code-block:: shell
-
- git clone https://github.com/2bndy5/CircuitPython_nRF24L01.git
- cd CircuitPython_nRF24L01/examples
-
-2. Run the example as a normal python program
-
- .. code-block:: shell
-
- python3 nrf24l01_simple_test.py
-
-
-What to purchase
-=================
-
-See the store links on the navigation sidebar or just google "nRF24L01+". It is worth noting that you
-generally want to buy more than 1 as you need 2 for testing -- 1 to send & 1 to receive and
-vise versa. This library has been tested on a cheaply bought 6 pack from Amazon.com, but don't
-take Amazon or eBay for granted! There are other wireless transceivers that are NOT compatible
-with this library. For instance, the esp8266-01 (also sold in packs) is NOT compatible with
-this library, but looks very similar to the nRF24L01+ and could lead to an accidental purchase.
-
-.. seealso::
- Beware, there are also `nrf24l01(+) clones and counterfeits`_ that may not work the same.
-
-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
-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 `_)
-
-About the nRF24L01+PA+LNA modules
----------------------------------
-
-You may find variants of the nRF24L01 transceiver that are marketed as "nRF24L01+PA+LNA".
-These modules are distinct in the fact that they come with a detachable (SMA-type) antenna.
-They employ seperate RFX24C01 IC with the antenna for enhanced Power Amplification (PA) and
-Low Noise Amplification (LNA) features. While they boast greater range with the same
-functionality, they are subject to a couple lesser known (and lesser advertised) drawbacks:
-
-1. Stronger power source. Below is a chart of advertised current requirements that many MCU
- boards' 3V regulators may not be able to provide (after supplying power to internal
- components).
-
- .. csv-table::
- :header: Specification, Value
- :widths: 10,5
-
- "Emission mode current(peak)", "115 mA"
- "Receive Mode current(peak)", "45 mA"
- "Power-down mode current", "4.2 µA"
-
-2. Needs shielding from electromagnetic interference. Shielding usually works best when
- it has a path to ground (GND pin), but this connection to the GND pin is not required.
-
-.. seealso::
- I have documented `Testing nRF24L01+PA+LNA module `_
-
-nRF24L01(+) clones and counterfeits
------------------------------------
-
-This library does not directly support clones/counterfeits as there is no way for the library
-to differentiate between an actual nRF24L01+ and a clone/counterfeit. To determine if your
-purchase is a counterfeit, please contact the retailer you purchased from (also `reading this
-article and its links might help
-`_). The most notable clone is the `Si24R1 `_. I could not find
-the `Si24R1 datasheet `_ in english. Troubleshooting
-the SI24R1 may require `replacing the onboard antenna with a wire
-`_. Furthermore, the Si24R1 has different power
-amplifier options as noted in the `RF_PWR section (bits 0 through 2) of the RF_SETUP register
-(address 0x06) of the datasheet `_.
-While the options' values differ from those identified by this library's API, the
-underlying commands to configure those options are almost identical to the nRF24L01.
-The Si24R1 is also famous for not supporting :py:attr:`~circuitpython_nrf24l01.rf24.RF24.auto_ack`
-correctly because the designers "cloned" a typo from the 1\ :sup:`st` version of the nRF24L01
-(non-plus) datasheet into the Si24R1 firmware. Other known clones include the bk242x (also known as
-RFM7x).
-
-.. seealso::
- `Read this article
- `_
- about using clones with missing capacitors (includes pictures).
-
-Contributing
-============
-
-Contributions are welcome! Please read our `Code of Conduct
-`_
-before contributing to help this project stay welcoming. To contribute, all you need to do is fork `this repository `_, develop your idea(s) and submit a pull request when stable. To initiate a discussion of idea(s), you need only open an issue on the aforementioned repository (doesn't have to be a bug report).
-
-
-Future Project Ideas/Additions
-------------------------------
-
-The following are only ideas; they are not currently supported by this circuitpython library.
-
-* `There's a few blog posts by Nerd Ralph demonstrating how to use the nRF24L01 via 2 or 3
- pins `_ (uses custom bitbanging SPI functions and an external circuit involving a
- resistor and a capacitor)
-* network linking layer, maybe something like `TMRh20's RF24Network
- `_
-* implement the Gazelle-based protocol used by the BBC micro-bit (`makecode.com's radio
- blocks `_).
-
-
-Sphinx documentation
------------------------
-
-Sphinx is used to build the documentation based on rST files and comments in the code. First,
-install dependencies (feel free to reuse the virtual environment from `above `_):
-
-.. code-block:: shell
-
- python3 -m venv .env
- source .env/bin/activate
- pip install Sphinx sphinx-rtd-theme
-
-Now, once you have the virtual environment activated:
-
-.. code-block:: shell
-
- cd docs
- sphinx-build -E -W -b html . _build
-
-This will output the documentation to ``docs/_build``. Open the index.html in your browser to
-view them. It will also (due to -W) error out on any warning like the Github action, Build CI,
-does. This is a good way to locally verify it will pass.
diff --git a/docs/index.rst b/docs/index.rst
index e3db2c8..4ab5da3 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,56 +1,414 @@
-.. include:: greetings.rst
-
-.. toctree::
- :caption: Introduction
- :hidden:
-
- greetings
-
-.. toctree::
- :caption: Examples
- :hidden:
-
- examples
-
-.. toctree::
- :caption: API Reference
- :hidden:
-
- basic_api
- advanced_api
- configure_api
- ble_api
-
-.. toctree::
- :caption: Troubleshooting
- :hidden:
-
- troubleshooting
-
-
-.. toctree::
- :caption: Store Links
- :hidden:
-
- 2.4GHz Transceiver IC - nRF24L01+
- SparkFun Transceiver Breakout - nRF24L01+
- SparkFun Transceiver Breakout - nRF24L01+ (RP-SMA)
-
-.. toctree::
- :caption: Other Links
- :hidden:
-
- Download
- CircuitPython Reference Documentation
- CircuitPython Support Forum
- Discord Chat
- Adafruit Learning System
- Adafruit Blog
- Adafruit Store
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
+
+
+.. toctree::
+ :hidden:
+
+ examples
+
+.. toctree::
+ :caption: Core API Reference
+ :hidden:
+
+ core_api/basic_api
+ core_api/advanced_api
+ core_api/configure_api
+ core_api/ble_api
+
+.. toctree::
+ :caption: Network API Reference
+ :hidden:
+
+ network_docs/topology
+ network_docs/structs
+ network_docs/shared_api
+ network_docs/network_api
+ network_docs/mesh_api
+ network_docs/constants
+
+.. toctree::
+ :hidden:
+
+ troubleshooting
+
+.. toctree::
+ :caption: Other Links
+ :hidden:
+
+ Download
+ CircuitPython Reference Documentation
+ CircuitPython Support Forum
+ Discord Chat
+ Adafruit Learning System
+ Adafruit Blog
+ Adafruit Store
+
+.. only:: html
+
+ .. image:: https://open.vscode.dev/badges/open-in-vscode.svg
+ :target: https://open.vscode.dev/2bndy5/CircuitPython_nRF24L01
+ :alt: Open in Visual Studio Code
+
+ .. image:: https://img.shields.io/badge/Gitpod-Use%20Online%20IDE-B16C04?logo=gitpod
+ :target: https://gitpod.io/#https://github.com/2bndy5/CircuitPython_nRF24L01
+ :alt: Open in Gitpod
+
+Getting Started
+~~~~~~~~~~~~~~~
+
+Introduction
+===============
+
+This is a Circuitpython driver library for the nRF24L01(+) transceiver.
+
+Originally this code was a Micropython module written by Damien P. George
+& Peter Hinch which can still be found `here
+`_
+
+The Micropython source has since been rewritten to expose all the nRF24L01's
+features and for Circuitpython compatible devices (including linux-based
+SoC computers like the Raspberry Pi).
+Modified by Brendan Doherty & Rhys Thomas.
+
+* Authors: Damien P. George, Peter Hinch, Rhys Thomas, Brendan Doherty
+
+Features currently supported
+----------------------------
+
+* Change the address's length (can be 3 to 5 bytes long)
+* Dynamically sized payloads (max 32 bytes each) or statically sized payloads
+* Automatic responding acknowledgment (ACK) packets for verifying transmission success
+* Append custom payloads to the acknowledgment (ACK) packets for instant bi-directional communication
+* Mark a single payload for no acknowledgment (ACK) from the receiving nRF24L01 (see ``ask_no_ack``
+ parameter for :meth:`~circuitpython_nrf24l01.rf24.RF24.send()` and :meth:`~circuitpython_nrf24l01.rf24.RF24.write()` functions)
+* Invoke the "re-use the same payload" feature (for manually re-transmitting failed transmissions that
+ remain in the TX FIFO buffer)
+* Multiple payload transmissions with one function call (see documentation on the
+ :py:meth:`~circuitpython_nrf24l01.rf24.RF24.send()` function and try out the
+ `Stream example `_)
+* Context manager compatible for easily switching between different radio configurations
+ using `with` blocks (not available in ``rf24_lite.py`` version)
+* Configure the interrupt (IRQ) pin to trigger (active low) on received, sent, and/or
+ failed transmissions (these 3 events control 1 IRQ pin). There's also virtual
+ representations of these interrupt events available (see `irq_dr`, `irq_ds`, & `irq_df` attributes)
+* Invoke sleep mode (AKA power down mode) for ultra-low current consumption
+* cyclic redundancy checking (CRC) up to 2 bytes long
+* Adjust the nRF24L01's builtin automatic re-transmit feature's parameters (`arc`: number
+ of attempts, `ard`: delay between attempts)
+* Adjust the nRF24L01's frequency channel (2.4 - 2.525 GHz)
+* Adjust the nRF24L01's power amplifier level (0, -6, -12, or -18 dBm)
+* Adjust the nRF24L01's RF data rate (250kbps, 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
+ `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.
+
+ * This does not mean the radio can connect to WiFi. The networking implementation is a
+ custom protocol ported from TMRh20's RF24Network & RF24Mesh libraries.
+
+
+Dependencies
+--------------------------
+
+This driver depends on:
+
+* `Adafruit CircuitPython Firmware `_ or the
+ `Adafruit_Blinka library `_ for Linux
+ SoC boards like Raspberry Pi
+* `adafruit_bus_device` (specifically the :py:class:`~adafruit_bus_device.SPIDevice` class)
+
+ .. tip:: Use CircuitPython v6.3.0 or newer because faster SPI execution yields
+ faster transmissions.
+* The `SpiDev `_ module is a C-extention that executes
+ SPI transactions faster than Adafruit's PureIO library (a dependency of the
+ `Adafruit_Blinka library `_).
+
+The `adafruit_bus_device`, `Adafruit_Blinka library `_,
+and `SpiDev `_ libraries
+are installed automatically on Linux when installing this library.
+
+.. versionadded:: 2.1.0 Added support for the
+ `SpiDev `_ module
+
+.. important:: This library supports Python 3.7 or newer because it use
+ the function :py:func:`time.monotonic_ns()` which returns an arbitrary time "counter"
+ as an `int` of nanoseconds. CircuitPython firmware also supports :py:func:`time.monotonic_ns()`.
+
+
+Installing from PyPI
+--------------------
+
+On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from
+PyPI `_. To install for current user:
+
+.. code-block:: shell
+
+ pip3 install circuitpython-nrf24l01
+
+To install in a virtual environment in your current project:
+
+.. code-block:: shell
+
+ mkdir project-name && cd project-name
+ python3 -m venv .env
+ source .env/bin/activate
+ pip3 install circuitpython-nrf24l01
+
+Pinout
+======
+
+.. image:: https://lastminuteengineers.com/wp-content/uploads/2018/07/Pinout-nRF24L01-Wireless-Transceiver-Module.png
+ :target: https://lastminuteengineers.com/nrf24l01-arduino-wireless-communication/#nrf24l01-transceiver-module-pinout
+
+The nRF24L01 is controlled through SPI so there are 3 pins (SCK, MOSI, & MISO) that can only be
+connected to their counterparts on the MCU (microcontroller unit). The other 2 essential pins
+(CE & CSN) can be connected to any digital output pins. Lastly, the only optional GPIO pin on the
+nRF24L01 is the IRQ (interrupt; a digital output that's active when low) pin and is only connected
+to the MCU via a digital input pin during the interrupt example.
+
+
+.. csv-table:: The pins used in `this library's examples `_.
+ :header: nRF24L01, "ItsyBitsy M4", "Raspberry Pi"
+ :widths: 2, 6, 22
+
+
+ GND, GND, GND
+ VCC, 3.3V, 3V
+ CE, D4, "- GPIO4 if using CircuitPython's :py:class:`~adafruit_bus_device.SPIDevice`
+ - GPIO22 if using the `SpiDev `_ module"
+ CSN, D5, "- GPIO5 if using CircuitPython's :py:class:`~adafruit_bus_device.SPIDevice`
+ - GPIO8 (CE0) if using the `SpiDev `_ module"
+ SCK, SCK, "GPIO11 (SCK)"
+ MOSI, MOSI, "GPIO10 (MOSI)"
+ MISO, MISO, "GPIO9 (MISO)"
+ IRQ, D12, GPIO12
+
+.. 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.
+.. 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.
+
+Using The Examples
+==================
+
+See `examples `_ for testing certain features of this the library. The examples were developed and tested on both Raspberry Pi and ItsyBitsy M4. Pins have been hard coded in the examples for the corresponding device, so please adjust these accordingly to your circuitpython device if necessary.
+
+For an interactive REPL
+---------------------------
+
+All examples can be imported from within an interactive python REPL.
+
+1. Make sure the examples are located in the current working directory.
+ On CircuitPython devices, this will be the root directory of the CIRCUITPY drive.
+2. Import everything from desired the example. The following code snippet demonstrates running the `Simple Test example `_
+
+ .. code-block:: python
+
+ >>> from nrf24l01_simple_test import *
+ Which radio is this? Enter '0' or '1'. Defaults to '0'
+ nRF24L01 Simple test.
+ Run slave() on receiver
+ Run master() on transmitter
+ >>> master()
+ Transmission successful! Time to Transmit: 3906.25 us. Sent: 0.0
+ Transmission successful! Time to Transmit: 2929.69 us. Sent: 0.01
+ Transmission successful! Time to Transmit: 2929.69 us. Sent: 0.02
+ Transmission successful! Time to Transmit: 3906.25 us. Sent: 0.03
+ Transmission successful! Time to Transmit: 4882.81 us. Sent: 0.04
+
+For CircuitPython devices
+---------------------------
+
+1. Copy the examples to the root directory of the CIRCUITPY device.
+2. Rename the desired example file to ``main.py``.
+3. If the REPL is not already running, then the example should start automatically.
+ If the REPL is already running in interactive mode, then press ``ctrl+d`` to do a
+ soft reset, and the example should start automatically.
+
+For CPython in Linux
+---------------------------
+
+1. Clone the library repository, then navigate to the reository's example directory.
+
+ .. code-block:: shell
+
+ git clone https://github.com/2bndy5/CircuitPython_nRF24L01.git
+ cd CircuitPython_nRF24L01/examples
+
+2. Run the example as a normal python program
+
+ .. code-block:: shell
+
+ python3 nrf24l01_simple_test.py
+
+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) `_
+
+It is worth noting that you
+generally want to buy more than 1 as you need 2 for testing -- 1 to send & 1 to receive and
+vise versa. This library has been tested on a cheaply bought 6 pack from Amazon.com, but don't
+take Amazon or eBay for granted! There are other wireless transceivers that are NOT compatible
+with this library. For instance, the esp8266-01 (also sold in packs) is NOT compatible with
+this library, but looks very similar to the nRF24L01+ and could lead to an accidental purchase.
+
+.. seealso::
+ Beware, there are also `nrf24l01(+) clones and counterfeits`_ that may not work the same.
+
+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
+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 `_)
+
+About the nRF24L01+PA+LNA modules
+---------------------------------
+
+You may find variants of the nRF24L01 transceiver that are marketed as "nRF24L01+PA+LNA".
+These modules are distinct in the fact that they come with a detachable (SMA-type) antenna.
+They employ additional circuitry with the antenna for enhanced Power Amplification (PA) and
+Low Noise Amplification (LNA) features. While they boast greater range with the same
+functionality, they are subject to a couple lesser known (and lesser advertised) drawbacks:
+
+Additional requirements for the PA/LNA modules
+**********************************************
+
+These requirements are dependent on what manufacturer produced the radio module.
+
+1. Needs a stronger power source. Below is a chart of advertised current requirements that many MCU
+ boards' 3V regulators may not be able to provide (after supplying power to internal
+ components).
+
+ .. csv-table::
+ :header: Specification, Value
+ :widths: 10,5
+
+ "Emission mode current(peak)", "115 mA"
+ "Receive Mode current(peak)", "45 mA"
+ "Power-down mode current", "4.2 µA"
+
+ .. important:: These values may be different depending on what manufacturer produced the radio module.
+ Please consult the manufacturer's specifications or datasheet.
+
+2. Needs shielding from electromagnetic interference. Shielding usually works best when
+ it has a path to ground (GND pin), but this connection to the GND pin is not required.
+
+.. seealso::
+ I have documented `Testing nRF24L01+PA+LNA module `_
+
+nRF24L01(+) clones and counterfeits
+-----------------------------------
+
+This library does not directly support clones/counterfeits as there is no way for the library
+to differentiate between an actual nRF24L01+ and a clone/counterfeit. To determine if your
+purchase is a counterfeit, please contact the retailer you purchased from (also `reading this
+article and its links might help
+`_). The most notable clone is the `Si24R1 `_. I could not find
+the `Si24R1 datasheet `_ in english. Troubleshooting
+the SI24R1 may require `replacing the onboard antenna with a wire
+`_. Furthermore, the Si24R1 has different power
+amplifier options as noted in the `RF_PWR section (bits 0 through 2) of the RF_SETUP register
+(address 0x06) of the datasheet `_.
+While the options' values differ from those identified by this library's API, the
+underlying commands to configure those options are almost identical to the nRF24L01.
+The Si24R1 is also famous for not supporting :py:attr:`~circuitpython_nrf24l01.rf24.RF24.auto_ack`
+correctly because the designers "cloned" a typo from the 1\ :sup:`st` version of the nRF24L01
+(non-plus) datasheet into the Si24R1 firmware. Other known clones include the bk242x (also known as
+RFM7x).
+
+.. seealso::
+ `Read this article
+ `_
+ about using clones with missing capacitors (includes pictures).
+
+Contributing
+============
+
+Contributions are welcome! Please read our `Code of Conduct
+`_
+before contributing to help this project stay welcoming. To contribute, all you need to do is fork `this repository `_, develop your idea(s) and submit a pull request when stable. To initiate a discussion of idea(s), you need only open an issue on the aforementioned repository (doesn't have to be a bug report).
+
+
+Future Project Ideas/Additions
+------------------------------
+
+The following are only ideas; they are not currently supported by this circuitpython library.
+
+* `There's a few blog posts by Nerd Ralph demonstrating how to use the nRF24L01 via 2 or 3
+ pins `_ (uses custom bitbanging SPI functions and an external circuit involving a
+ resistor and a capacitor)
+* TCI/IP OSI layer, maybe something like `TMRh20's RF24Ethernet
+ `_
+* implement the Gazelle-based protocol used by the BBC micro-bit (`makecode.com's radio
+ blocks `_) Additional resources can be found at
+ `the MicroPython firmware source code `_
+ and `its related documentation `_.
+
+
+Sphinx documentation
+-----------------------
+
+Sphinx and Graphviz are used to build the documentation based on rST files and comments in the code.
+
+Install Graphviz
+****************
+On Windows, installing Graphviz library is done differently. Check out the
+`Graphviz downloads page `_. Besure that the ``graphiz/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.
+
+On Linux, just run:
+
+.. code-block:: shell
+
+ sudo apt-get install graphviz
+
+Installing Sphinx necessities
+*****************************
+
+First, install dependencies (feel free to reuse the virtual environment from
+`above `_):
+
+.. code-block:: shell
+
+ python3 -m venv .env
+ source .env/bin/activate
+ pip install Sphinx sphinx-material sphinx-copybutton
+
+Building the documentation
+**************************
+
+Now, once you have the virtual environment activated:
+
+.. code-block:: shell
+
+ cd docs
+ sphinx-build -E -W -b html . _build
+
+This will output the documentation to ``docs/_build`` directory. Open the *index.html* in your
+browser to view them. It will also (due to -W) error out on any warning like the Github action,
+Build CI, does. This is a good way to locally verify it will pass.
+
+Site Index
+==========
+
+:ref:`genindex`
diff --git a/docs/network_docs/constants.rst b/docs/network_docs/constants.rst
new file mode 100644
index 0000000..101f416
--- /dev/null
+++ b/docs/network_docs/constants.rst
@@ -0,0 +1,152 @@
+Network Constants
+========================
+
+.. versionadded:: 2.1.0
+
+Sending Behavior Types
+----------------------
+
+.. autodata:: circuitpython_nrf24l01.network.constants.AUTO_ROUTING
+.. autodata:: circuitpython_nrf24l01.network.constants.TX_NORMAL
+
+ This is used for most outgoing message types.
+
+.. autodata:: circuitpython_nrf24l01.network.constants.TX_ROUTED
+
+ This is internally used for `NETWORK_ACK` message routing.
+
+.. autodata:: circuitpython_nrf24l01.network.constants.TX_PHYSICAL
+
+ These usually take 1 transmission, so they don't get a network ACK because the
+ radio's `auto_ack` will serve the ACK.
+
+.. autodata:: circuitpython_nrf24l01.network.constants.TX_LOGICAL
+
+ This allows the user to define the routed transmission's first path (these can still get a
+ `NETWORK_ACK`).
+
+.. autodata:: circuitpython_nrf24l01.network.constants.TX_MULTICAST
+
+ .. seealso::
+
+ - `Network Levels `_
+ - `multicast_relay`
+ - `multicast()`
+ - `multicast_level`
+
+
+Reserved Network Message Types
+------------------------------
+
+.. autodata:: circuitpython_nrf24l01.network.constants.MESH_ADDR_RESPONSE
+
+ This `message_type` is used to in the final step of `renew_address()` route a messages
+ containing a newly allocated `node_address`. The header's `reserved` attribute for this
+ `message_type` will store the requesting mesh node's `node_id` related to the newly assigned
+ `node_address`. Any non-requesting network node receiving this `message_type` will forward it
+ to the requesting node using normal network routing.
+
+.. autodata:: circuitpython_nrf24l01.network.constants.NETWORK_PING
+
+ This `message_type` is automatically discarded because the radio's `auto_ack` feature will serve
+ up the response.
+
+.. autodata:: circuitpython_nrf24l01.network.constants.NETWORK_EXT_DATA
+
+ Used for bridging different network protocols between an RF24Network and LAN/WLAN networks.
+
+.. autodata:: circuitpython_nrf24l01.network.constants.NETWORK_ACK
+
+ The message type used when forwarding acknowledgements directed to the
+ instigating message's origin. This is not be confused with the radio's `auto_ack`
+ attribute. In fact, all messages (except multicasted ones) take advantage of the
+ radio's `auto_ack` feature when transmitting between directly related nodes (ie
+ between a transmitting node's parent or child node).
+
+ .. important::
+ NETWORK_ACK messages are only sent by the last node in the route to a
+ destination. For example: Node ``0o0`` sends an instigating message to node
+ ``0o11``. The NETWORK_ACK message is sent from node ``0o1`` when it confirms node
+ ``0o11`` received the instigating message.
+ .. hint::
+ This feature is not flawless because it assumes a reliable connection
+ between all necessary network nodes.
+
+.. autodata:: circuitpython_nrf24l01.network.constants.NETWORK_POLL
+
+ This `message_type` is used with `NETWORK_MULTICAST_ADDR`
+ to find active/available nodes. Any node receiving a `NETWORK_POLL` sent to a
+ `NETWORK_MULTICAST_ADDR` will respond directly to the sender with a blank message,
+ indicating the address of the available node via the header's `from_node` attribute.
+
+.. autodata:: circuitpython_nrf24l01.network.constants.MESH_ADDR_REQUEST
+
+ This `message_type` is used for requesting :ref:`Logical Address ` data from
+ the mesh network's master node. Any non-master node receiving this `message_type` will manually
+ forward it to the master node using normal network routing.
+
+.. autodata:: circuitpython_nrf24l01.network.constants.MESH_ADDR_LOOKUP
+.. autodata:: circuitpython_nrf24l01.network.constants.MESH_ADDR_RELEASE
+.. autodata:: circuitpython_nrf24l01.network.constants.MESH_ID_LOOKUP
+
+Generic Network constants
+----------------------------
+
+.. autodata:: circuitpython_nrf24l01.network.constants.MAX_USR_DEF_MSG_TYPE
+
+ Any message type above 127 (but cannot exceed 255) are reserved for internal
+ network usage.
+
+.. autodata:: circuitpython_nrf24l01.network.constants.NETWORK_DEFAULT_ADDR
+
+ Any mesh node that disconnects or is trying to connect to a mesh network will use this value
+ until it is assigned a :ref:`Logical Address ` from the master node.
+
+.. autodata:: circuitpython_nrf24l01.network.constants.NETWORK_MULTICAST_ADDR
+.. autodata:: circuitpython_nrf24l01.network.constants.MAX_FRAG_SIZE
+
+ This does not including header's byte length (which is always 8 bytes).
+
+ .. warning::
+ Do not increase this value in the source code. Adjust
+ :attr:`~circuitpython_nrf24l01.rf24_network.RF24Network.max_message_length`
+ instead.
+
+Message Fragment Types
+----------------------
+
+Message fragments will use these values in the
+:attr:`~circuitpython_nrf24l01.network.structs.RF24NetworkHeader.message_type` attribute.
+The sequential fragment id number will be stored in the
+:attr:`~circuitpython_nrf24l01.network.structs.RF24NetworkHeader.reserved` attribute,
+but the actual message type is transmitted in the
+:attr:`~circuitpython_nrf24l01.network.structs.RF24NetworkHeader.reserved` attribute
+of the last fragment.
+
+.. autodata:: circuitpython_nrf24l01.network.constants.MSG_FRAG_FIRST
+.. autodata:: circuitpython_nrf24l01.network.constants.MSG_FRAG_MORE
+.. autodata:: circuitpython_nrf24l01.network.constants.MSG_FRAG_LAST
+
+RF24Mesh specific constants
+---------------------------
+
+.. autodata:: circuitpython_nrf24l01.network.constants.MESH_LOOKUP_TIMEOUT
+
+ The time (in milliseconds) that a non-master mesh node will wait for a response when
+ requesting a node's relative :ref:`Logical Address ` or unique ID number
+ from the master node.
+
+.. autodata:: circuitpython_nrf24l01.network.constants.MESH_MAX_POLL
+
+ A mesh node polls the first 4 network levels (0-3) looking for a response.
+ This value is used to used when aggregating a list of responding nodes (per level).
+
+.. autodata:: circuitpython_nrf24l01.network.constants.MESH_MAX_CHILDREN
+
+ This information is only used by mesh network master nodes when allocating a possible
+ :ref:`Logical Address ` for the requesting node.
+
+.. autodata:: circuitpython_nrf24l01.network.constants.MESH_WRITE_TIMEOUT
+
+ When `RF24Mesh.send()` is called, This value is only used when getting the `node_address`
+ assigned to a `node_id` from the mesh network's master node.
diff --git a/docs/network_docs/mesh_api.rst b/docs/network_docs/mesh_api.rst
new file mode 100644
index 0000000..7798aae
--- /dev/null
+++ b/docs/network_docs/mesh_api.rst
@@ -0,0 +1,218 @@
+.. |use_msg_t| replace:: To ensure a message has been delivered to its target destination, set the
+ ``message_type`` parameter to an `int` in range [65, 127]. This will invoke
+ a `NETWORK_ACK` response message.
+
+RF24Mesh API
+============
+
+.. versionadded:: 2.1.0
+
+.. seealso:: Documentation for:
+
+ 1. `Shared Networking API `_ (API common to `RF24Mesh` and `RF24Network`)
+ 2. `RF24Network API `_ (`RF24Mesh` inherits from the same mixin class
+ that `RF24Network` inherits from)
+
+RF24MeshNoMaster class
+**********************
+
+.. autoclass:: circuitpython_nrf24l01.rf24_mesh.RF24MeshNoMaster
+
+ This class exists to save memory for nodes that don't behave like mesh network master nodes.
+ 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.
+
+ .. seealso:: For all parameters' descriptions, see the
+ :py:class:`~circuitpython_nrf24l01.rf24.RF24` class' contructor documentation.
+
+
+RF24Mesh class
+**************
+
+.. autoclass:: circuitpython_nrf24l01.rf24_mesh.RF24Mesh
+ :show-inheritance:
+
+ :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.
+
+Basic API
+*********
+
+
+.. automethod:: circuitpython_nrf24l01.rf24_mesh.RF24Mesh.send
+
+ This function will use `lookup_address()` to fetch the necessary
+ :ref:`Logical Address ` to set the frame's header's `to_node`
+ attribute.
+
+ .. hint::
+ If you already know the destination node's :ref:`Logical Address `,
+ then you can use :meth:`~circuitpython_nrf24l01.rf24_mesh.RF24Mesh.write()`
+ for quicker operation.
+
+ :param bytes,bytearray message: The frame's `message` to be transmitted.
+ :param str,int message_type: The `int` that describes the frame header's `message_type`.
+
+ .. note:: Be mindful of the message's size as this cannot exceed
+ `MAX_FRAG_SIZE` (24 bytes) if `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 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:
+
+ * `True` if the ``frame`` has been transmitted. This does not necessarily
+ describe if the message has been received at its target destination.
+ * `False` if the ``frame`` has not been transmitted.
+
+ .. tip:: |use_msg_t|
+
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_mesh.RF24Mesh.node_id
+
+ This is not to be confused with the network node's `node_address`. This attribute is meant to
+ distinguish different mesh network nodes that may, at separate instances, use the same
+ `node_address`. It is up to the developer to make sure each mesh network node uses a different
+ ID number.
+
+ .. warning::
+ Changing this attributes value after instantiation will automatically call
+ `release_address()` which disconnects the node from the mesh network. Notice the
+ `node_address` is set to `NETWORK_DEFAULT_ADDR` when consciously not connected to the
+ mesh network.
+ .. tip::
+ When a mesh node becomes disconnected from the mesh network, use `renew_address()`
+ to fetch (from the master node) an assigned logical address to be used as the mesh node's
+ `node_address`.
+
+.. automethod:: circuitpython_nrf24l01.rf24_mesh.RF24Mesh.renew_address
+
+ :param float,int timeout: The amount of time (in seconds) to continue trying to connect
+ and get an assigned :ref:`Logical Address `. Defaults to 7.5 seconds.
+
+ .. note:: This function automatically sets the `node_address` accordingly.
+
+ :Returns:
+ * If successful: The `node_address` that was set to the newly assigned
+ :ref:`Logical Address `.
+ * If unsuccessful: `None`, and the `node_address` attribute will be set to
+ `NETWORK_DEFAULT_ADDR` (``0o4444`` in octal or ``2340`` in decimal).
+
+
+Advanced API
+************
+
+.. automethod:: circuitpython_nrf24l01.rf24_mesh.RF24Mesh.lookup_node_id
+
+ :param int address: The :ref:`Logical Address ` for which
+ a unique `node_id` is assigned from network master node.
+
+ :Returns:
+ - The unique `node_id` assigned to the specified ``address``.
+ - Error codes include
+
+ - ``-2`` means the specified ``address`` has not been assigned a
+ unique `node_id` from the master node or the requesting
+ network node's `node_address` is equal to `NETWORK_DEFAULT_ADDR`.
+ - ``-1`` means the address lookup operation failed due to no network connection
+ or the master node has not assigned a unique `node_id`
+ for the specified ``address``.
+
+.. automethod:: circuitpython_nrf24l01.rf24_mesh.RF24Mesh.lookup_address
+
+ :param int node_id: The unique `node_id` for which a
+ :ref:`Logical Address ` is assigned from network master node.
+
+ :Returns:
+ - The :ref:`Logical Address ` assigned to the specified ``node_id``.
+ - Error codes include
+
+ - ``-2`` means the specified ``node_id`` has not been assigned a
+ :ref:`Logical Address ` from the master node or the requesting
+ network node's `node_address` is equal to `NETWORK_DEFAULT_ADDR`.
+ - ``-1`` means the address lookup operation failed due to no network connection
+ or the master node has not assigned a :ref:`Logical Address `
+ for the specified ``node_id``.
+
+
+.. automethod:: circuitpython_nrf24l01.rf24_mesh.RF24Mesh.write
+
+ :param int to_node_address: 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`.
+
+ .. note:: Be mindful of the message's size as this cannot exceed
+ `MAX_FRAG_SIZE` (24 bytes) if `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 bytes,bytearray message: The frame's `message` to be transmitted.
+
+ :Returns:
+
+ * `True` if the ``frame`` has been transmitted. This does not necessarily
+ describe if the message has been received at its target destination.
+ * `False` if the ``frame`` has not been transmitted.
+
+ .. tip:: |use_msg_t|
+
+.. automethod:: circuitpython_nrf24l01.rf24_mesh.RF24Mesh.check_connection
+
+.. automethod:: circuitpython_nrf24l01.rf24_mesh.RF24Mesh.release_address
+
+ .. hint::
+ This should be called from a mesh network node that is disconnecting from the network.
+ This is also recommended for mesh network nodes that are entering a powered down (or
+ sleep) mode.
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_mesh.RF24Mesh.allow_children
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_mesh.RF24Mesh.block_less_callback
+
+ .. note::
+ Requesting a new address (via `renew_address()`) can take a while since it sequentially
+ attempts to get re-assigned to the first available :ref:`Logical Address `
+ on the highest possible `network level `_.
+
+ The assigned function will be called during `renew_address()`, `lookup_address()` and
+ `lookup_node_id()`.
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_mesh.RF24Mesh.dhcp_dict
+
+ This `dict` stores the assigned :ref:`Logical Addresses ` to the connected
+ mesh node's `node_id`.
+
+ - The keys in this `dict` are the unique `node_id` of a mesh network node.
+ - The values in this `dict` (corresponding to each key) are the `node_address` assigned to the `node_id`.
+
+.. automethod:: circuitpython_nrf24l01.rf24_mesh.RF24Mesh.save_dhcp
+
+ .. warning::
+ This function will likely throw a `OSError` on boards running CircuitPython firmware
+ because the file system is by default read-only.
+
+ 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"
+
+.. 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"
+
+ .. warning::
+ This function will raise an `OSError` exception if no file exists.
+
+.. automethod:: circuitpython_nrf24l01.rf24_mesh.RF24Mesh.set_address
+
+ This function is only meant to be called on the mesh network's master node.
+ Use this function to manually assign a `node_id` to a `RF24Network.node_address`.
+
+ :param int node_id: A unique identifying number ranging [1, 255].
+ :param int node_address: A :ref:`Logical Address `
+ :param bool search_by_address: A flag to traverse the `dhcp_dict` by value instead of by key.
diff --git a/docs/network_docs/network_api.rst b/docs/network_docs/network_api.rst
new file mode 100644
index 0000000..17b754a
--- /dev/null
+++ b/docs/network_docs/network_api.rst
@@ -0,0 +1,238 @@
+.. |if_nothing_in_queue| replace:: If there is nothing in the `queue`, this method will return
+.. |use_msg_t| replace:: To ensure a message has been delivered to its target destination, set the
+ frame's header's `message_type` to an `int` in range [65, 127]. This will invoke
+ a `NETWORK_ACK` response message.
+
+RF24Network API
+===============
+
+.. versionadded:: 2.1.0
+
+.. seealso:: Documentation for:
+
+ 1. `Network Topology `_
+ 2. `Shared Networking API `_
+ 3. `Network Data Structures `_
+ 4. `Network Constants `_
+
+RF24NetworkRoutingOnly class
+****************************
+
+.. autoclass:: circuitpython_nrf24l01.rf24_network.RF24NetworkRoutingOnly
+
+ This class is a minimal variant of the `RF24Network` class. The API is almost identical to
+ `RF24Network` except that it has no `RF24Network.write()` or `RF24Network.send()` functions.
+ This is meant to be the python equivalent to TMRh20's ``DISABLE_USER_PAYLOADS`` macro in the
+ C++ RF24Network library.
+
+ :param int node_address: The octal `int` for this node's :ref:`Logical Address `
+
+ .. seealso::
+ For all other parameters' descriptions, see the
+ :py:class:`~circuitpython_nrf24l01.rf24.RF24` class' contructor documentation.
+
+RF24Network class
+*****************
+
+.. autoclass:: circuitpython_nrf24l01.rf24_network.RF24Network
+ :show-inheritance:
+
+ :param int node_address: The octal `int` for this node's :ref:`Logical Address `
+
+ .. seealso::
+ For all other parameters' descriptions, see the
+ :py:class:`~circuitpython_nrf24l01.rf24.RF24` class' contructor documentation.
+
+Basic API
+*********
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_network.RF24Network.node_address
+
+ Setting this attribute will alter
+
+ 1. The :ref:`Physical Addresses ` used on the radio's data pipes
+ 2. The `parent` attribute
+ 3. The `multicast_level` attribute's default value.
+
+ .. warning::
+
+ 1. If this attribute is set to an invald network
+ :ref:`Logical Address `, then nothing is done and the invalid address
+ is ignored.
+ 2. A `RF24Mesh` object cannot set this attribute because the
+ :ref:`Logical Address ` is assigned by the mesh network's master node.
+ Therefore, this attribute is read-only for `RF24Mesh` objects.
+
+ .. seealso:: Please review the tip documented in `RF24Mesh.node_id` for more details.
+
+.. automethod:: circuitpython_nrf24l01.rf24_network.RF24Network.update
+
+ .. important::
+ It is imperitive 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.
+
+ :Returns:
+ The latest received message's `message_type`. The returned value is not gotten
+ from frame's in the `queue`, but rather it is only gotten from the messages handled
+ during the function's operation.
+
+.. automethod:: circuitpython_nrf24l01.rf24_network.RF24Network.available
+
+.. automethod:: circuitpython_nrf24l01.rf24_network.RF24Network.peek
+
+ :Returns: A `RF24NetworkFrame` object. However, the data returned is not removed
+ from the `queue`. |if_nothing_in_queue| `None`.
+
+.. automethod:: circuitpython_nrf24l01.rf24_network.RF24Network.read
+
+ This function differs from `peek()` because this function also removes the header & message
+ from the `queue`.
+
+ :Returns:
+ A `RF24NetworkFrame` object. |if_nothing_in_queue| `None`.
+
+.. automethod:: circuitpython_nrf24l01.rf24_network.RF24Network.send
+
+ :param RF24NetworkHeader header: The outgoing frame's `header`. It is important to
+ have the header's `to_node` attribute set to the target network node's
+ :ref:`Logical Address `.
+ :param bytes,bytearray message: The outgoing frame's `message`.
+
+ .. note:: Be mindful of the message's size as this cannot exceed `MAX_FRAG_SIZE`
+ (24 bytes) if `fragmentation` is disabled. If `fragmentation` is enabled (it
+ is by default), then the message's size must be less than `max_message_length`
+
+ :Returns:
+ A `bool` describing if the message has been transmitted. This does not necessarily
+ describe if the message has been received at its target destination.
+
+ .. note::
+ This function will always return `True` if a message is directed to a node's pipe
+ that does not have `auto_ack` enabled (which will likely be pipe 0 in most network
+ contexts).
+ .. tip:: |use_msg_t|
+
+Advanced API
+************
+
+.. automethod:: circuitpython_nrf24l01.rf24_network.RF24Network.multicast
+
+ :param bytes,bytearray message: The outgoing frame's `message`.
+ :param str,int message_type: The outgoing frame's `message_type`.
+ :param int level: The `network level `_ of nodes to broadcast to.
+ If this optional parameter is not specified, then the node's `multicast_level` is used.
+
+ .. seealso:: `multicast_level`, `multicast_relay`, and `allow_multicast`
+
+ :Returns:
+ A `bool` describing if the message has been transmitted. This does not necessarily
+ describe if the message has been received at its target destination.
+
+ .. note::
+ This function will always return if a message is directed to a node's pipe
+ that does not have `auto_ack` enabled (which will likely be pipe 0 in most network
+ contexts).
+ .. tip:: To ensure a message has been delivered to its target destination, set the
+ header's `message_type` to an `int` in range [65, 127]. This will invoke a
+ `NETWORK_ACK` response message.
+
+.. automethod:: circuitpython_nrf24l01.rf24_network.RF24Network.write
+
+ .. hint::
+ This function can be used to transmit entire frames accumulated in a
+ user-defined `FrameQueue` object.
+
+ .. code-block:: python
+
+ from circuitpython_nrf24l01.network.structs import (
+ FrameQueue, RF24NetworkFrame, RF24NetworkHeader
+ )
+
+ my_q = FrameQueue()
+ for i in range(my_q.max_queue_size):
+ my_q.enqueue(
+ RF24NetworkFrame(
+ RF24NetworkHeader(0, "1"), bytes(range(i + 5))
+ )
+ )
+
+ # when it's time to send the queue
+ while len(my_q):
+ # let `nrf` be the instantiated RF24Network object
+ nrf.write(my_q.dequeue())
+
+ :param RF24NetworkFrame frame: The complete frame to send. It is important to
+ have the header's `to_node` attribute set to the target network node's address.
+ :param int traffic_direct: The specified direction of the frame. By default, this
+ will invoke the automatic routing mechanisms. However, this parameter
+ can be set to a network node's :ref:`Logical Address ` for direct
+ transmission to the specified node - meaning the transmission's automatic routing
+ will begin at the network node that is specified with this parameter instead of being
+ automatically routed from the actual origin of the transmission.
+
+ :Returns:
+
+ * `True` if the ``frame`` has been transmitted. This does not necessarily
+ describe if the message has been received at its target destination.
+ * `False` if the ``frame`` has failed to transmit.
+
+ .. 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``.
+ .. tip:: |use_msg_t|
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_network.RF24Network.parent
+
+ Returns ``0`` if called on the network's master node.
+
+Configuration API
+*****************
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_network.RF24Network.max_message_length
+
+ By default this is set to ``144``. If a network node is driven by the TMRh20
+ RF24Network library on a ATTiny-based board, set this to ``72`` (as per TMRh20's
+ RF24Network library default behavior).
+
+ Configuring the `fragmentation` attribute will automatically change the value that
+ `max_message_length` attribute is set to.
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_network.RF24Network.fragmentation
+
+ Changing this attribute's state will also appropriately changes the type of `FrameQueue`
+ (or `FrameQueueFrag`) object used for storing incoming network packets. Disabling
+ fragmentation can save some memory (not as much as TMRh20's RF24Network library's
+ ``DISABLE_FRAGMENTATION`` macro), but `max_message_length` will be limited to ``24`` bytes
+ (`MAX_FRAG_SIZE`) maximum. Enabling this attribute will set `max_message_length` attribute
+ to ``144`` bytes.
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_network.RF24Network.multicast_relay
+
+ Forwarded frames will also be enqueued on the forwarding node as a received frame.
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_network.RF24Network.multicast_level
+
+ Setting this attribute will also change the :ref:`physical address `
+ on the radio's RX data pipe 0.
+
+ .. seealso::
+ The `network levels `_ are explained in more detail on
+ the `topology `_ document.
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_network.RF24Network.allow_multicast
+
+ This attribute affects
+
+ - the :ref:`Physical Address ` translation (for data pipe 0) when setting the
+ `node_address`
+ - all incoming multicasted frames (including `multicast_relay` behavior).
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_network.RF24Network.tx_timeout
+
+ Defaults to 25.
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_network.RF24Network.route_timeout
+
+ Defaults to 75.
diff --git a/docs/network_docs/shared_api.rst b/docs/network_docs/shared_api.rst
new file mode 100644
index 0000000..f256735
--- /dev/null
+++ b/docs/network_docs/shared_api.rst
@@ -0,0 +1,181 @@
+Shared Networking API
+======================
+
+Order of Inheritence
+********************
+
+.. graphviz::
+
+ digraph inheritence {
+ bgcolor="#323232A1"
+ fontcolor="#FEF9A9"
+ fontsize=16
+ fontname="Roboto"
+ style="rounded,bold"
+ color="#FFFFFF00"
+ newrank=true
+ node [
+ style="filled"
+ fillcolor="#0E6902"
+ color="#FEFEFE"
+ fontcolor="#FEFEFE"
+ fontsize=16
+ fontname="Roboto"
+ ]
+ edge [
+ color="white"
+ penwidth=1.5
+ ]
+
+ subgraph cluster_rf24 {
+ bgcolor="#404040"
+ tooltip="circuitpython_nrf24l01.rf24 module"
+ label=" circuitpython_nrf24l01.rf24 ";
+ RF24 [
+ URL="../core_api/basic_api.html#basic-rf24-api"
+ tooltip="RF24 class"
+ ]
+ }
+
+ subgraph cluster_network_mixins{
+ bgcolor="#404040"
+ label=" circuitpython_nrf24l01.network.mixins "
+ tooltip="circuitpython_nrf24l01.network.mixins module"
+ node [
+ fillcolor="#014B80"
+ ]
+ rank="same"
+ RadioMixin [tooltip="RadioMixin class"]
+ NetworkMixin [tooltip="NetworkMixin class"]
+ RadioMixin -> NetworkMixin
+ }
+
+ subgraph cluster_rf24_network {
+ bgcolor="#404040"
+ labelloc="b"
+ label=" circuitpython_nrf24l01.rf24_network "
+ tooltip="circuitpython_nrf24l01.rf24_network module"
+ RF24NetworkRoutingOnly [
+ URL="network_api.html#rf24networkroutingonly-class"
+ tooltip="RF24NetworkRoutingOnly class"
+ ]
+ RF24Network [
+ URL="network_api.html#rf24network-class"
+ tooltip="RF24Network class"
+ ]
+ RF24NetworkRoutingOnly -> RF24Network
+ }
+
+ subgraph cluster_rf24_mesh {
+ bgcolor="#404040"
+ labelloc="b"
+ label=" circuitpython_nrf24l01.rf24_mesh "
+ tooltip="circuitpython_nrf24l01.rf24_mesh module"
+ RF24MeshNoMaster [
+ URL="mesh_api.html#rf24meshnomaster-class"
+ tooltip="RF24MeshNoMaster class"
+ ]
+ RF24Mesh [
+ URL="mesh_api.html#rf24mesh-class"
+ tooltip="RF24Mesh class"
+ ]
+ RF24MeshNoMaster -> RF24Mesh
+ }
+ RF24 -> RadioMixin
+ NetworkMixin -> RF24NetworkRoutingOnly
+ NetworkMixin -> RF24MeshNoMaster
+ }
+
+The ``RadioMixin`` and ``NetworkMixin`` classes are not documented directly. Instead, this
+documentation follows the OSI (Open Systems Interconnection) model. This is done to mimmic how the
+TMRh20 C++ libraries and documentation are structured.
+
+Consequentially, all functions and members inherited from the ``NetworkMixin`` class are
+documented here as part of the `RF24Network` class. Note that the `RF24MeshNoMaster`, `RF24Mesh`,
+and `RF24NetworkRoutingOnly` classes all share the same API inherited from the ``NetworkMixin``
+class.
+
+Accessible RF24 API
+*******************
+
+The purpose of the ``RadioMixin`` class is
+
+1. to provide a networking layer its own instantiated `RF24` object
+2. to prevent applications from changing the radio's configuration in a way that breaks the
+ networking layer's behavior
+
+The following list of `RF24` functions and attributes are exposed in the
+`RF24Network API `_ and `RF24Mesh API `_.
+
+* :py:attr:`~circuitpython_nrf24l01.rf24.RF24.channel`
+* :py:meth:`~circuitpython_nrf24l01.rf24.RF24.flush_rx`
+* :py:meth:`~circuitpython_nrf24l01.rf24.RF24.flush_tx`
+* :py:meth:`~circuitpython_nrf24l01.rf24.RF24.fifo`
+* :py:attr:`~circuitpython_nrf24l01.rf24.RF24.power`
+* :py:meth:`~circuitpython_nrf24l01.rf24.RF24.set_dynamic_payloads`
+* :py:meth:`~circuitpython_nrf24l01.rf24.RF24.get_dynamic_payloads`
+* :py:attr:`~circuitpython_nrf24l01.rf24.RF24.listen`
+* :py:attr:`~circuitpython_nrf24l01.rf24.RF24.pa_level`
+* :py:attr:`~circuitpython_nrf24l01.rf24.RF24.is_lna_enabled`
+* :py:attr:`~circuitpython_nrf24l01.rf24.RF24.data_rate`
+* :py:attr:`~circuitpython_nrf24l01.rf24.RF24.crc`
+* :py:meth:`~circuitpython_nrf24l01.rf24.RF24.set_auto_retries`
+* :py:meth:`~circuitpython_nrf24l01.rf24.RF24.get_auto_retries`
+* :py:attr:`~circuitpython_nrf24l01.rf24.RF24.last_tx_arc`
+* :py:meth:`~circuitpython_nrf24l01.rf24.RF24.address`
+* :py:meth:`~circuitpython_nrf24l01.rf24.RF24.interrupt_config`
+* :py:meth:`~circuitpython_nrf24l01.rf24.RF24.print_pipes`
+* :py:meth:`~circuitpython_nrf24l01.rf24.RF24.print_details`
+
+ For the ``print_details()`` function, an additional keyword parameter named ``network_only``
+ can be used to filter out all the core details from the `RF24` object. The ``dump_pipes``
+ parameter still exists and defaults to `False`. Usage is as follows:
+
+ .. code-block:: python
+
+ >>> # 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:
+ an empty buffer
+ Return on system messages__False
+ Allow network multicasts___True
+ Multicast relay____________Disabled
+ Network fragmentation______Enabled
+ Network max message length_144 bytes
+ Network TX timeout_________25 milliseconds
+ Network Rounting 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.
+
+External Systems API
+********************
+
+The following attributes are exposed in the `RF24Network` and `RF24Mesh` API for
+extensibility via external applications or systems.
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_network.RF24Network.address_prefix
+ :annotation: = b"\xCC"
+
+ .. seealso::
+ The usage of this attribute is more explained in the `Topology page `_
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_network.RF24Network.address_suffix
+ :annotation: = b"\xC3\x3C\x33\xCE\x3E\xE3"
+
+ .. seealso::
+ The usage of this attribute is more explained in the `Topology page `_
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_network.RF24Network.frame_buf
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_network.RF24Network.queue
+
+ This attribute will be an instantiated `FrameQueue` or `FrameQueueFrag` object depending on the state
+ of the `fragmentation` attribute.
+
+.. autoattribute:: circuitpython_nrf24l01.rf24_network.RF24Network.ret_sys_msg
+
+ This `bool` attribute is asserted on mesh network nodes.
diff --git a/docs/network_docs/structs.rst b/docs/network_docs/structs.rst
new file mode 100644
index 0000000..0d3563a
--- /dev/null
+++ b/docs/network_docs/structs.rst
@@ -0,0 +1,154 @@
+.. |internal_use| replace:: is meant for library internal usage.
+.. |uint16_t| replace:: This value is truncated to a 2-byte unsigned `int`.
+.. |can_be_blank| replace:: These parameters can be left unspecified to create a blank
+ object that can be augmented after instantiation.
+.. |unpacked_buf| replace:: The buffer to unpack. All resulting data is stored in the
+ objects attributes accordingly.
+
+Network Data Structures
+=======================
+
+.. versionadded:: 2.1.0
+
+These classes are used to structure the payload data for wireless network transactions.
+
+Header
+-----------------
+
+.. autoclass:: circuitpython_nrf24l01.network.structs.RF24NetworkHeader
+
+ :param int to_node: The :ref:`Logical Address ` designating the
+ message's destination.
+ :param int,str message_type: A 1-byte `int` representing the `message_type`. If a
+ `str` is passed, then the first character's numeric ASCII representation is
+ used.
+
+ .. note:: |can_be_blank|
+
+.. autoattribute:: circuitpython_nrf24l01.network.structs.RF24NetworkHeader.to_node
+
+ Describes the message destination using a :ref:`Logical Address `.
+
+.. autoattribute:: circuitpython_nrf24l01.network.structs.RF24NetworkHeader.from_node
+
+ Describes the message origin using a :ref:`Logical Address `.
+
+.. autoattribute:: circuitpython_nrf24l01.network.structs.RF24NetworkHeader.message_type
+
+ This `int` must be less than 256. When set using a `str`, this attribute's `int` value is
+ derived from the ASCII number of the string's first character (see :py:func:`ord()`).
+ Non-ASCII characters' values are truncated to 1 byte (see :py:meth:`str.isascii()`). A blank
+ `str` sets this attribute's value to ``0``.
+
+ .. hint::
+ Users are encouraged to specify a number in range [0, 127] (basically less
+ than or equal to `MAX_USR_DEF_MSG_TYPE`) as there are
+ `Reserved Message Types `_.
+
+.. autoattribute:: circuitpython_nrf24l01.network.structs.RF24NetworkHeader.frame_id
+
+ The sequential identifying number for the frame (relative to the originating
+ network node). Each sequential frame's ID is incremented, but frames containing
+ fragmented messages have the same ID number.
+
+.. autoattribute:: circuitpython_nrf24l01.network.structs.RF24NetworkHeader.reserved
+
+ This will be the sequential ID number for fragmented messages, but on the last message
+ fragment, this will be the `message_type`. `RF24Mesh` will also use this attribute to
+ hold a newly assigned network :ref:`Logical Address ` for
+ `MESH_ADDR_RESPONSE` messages.
+
+.. automethod:: circuitpython_nrf24l01.network.structs.RF24NetworkHeader.unpack
+
+ This function |internal_use|
+
+ :param bytes,bytearray buffer: |unpacked_buf|
+ :Returns: `True` if successful; otherwise `False`.
+
+.. automethod:: circuitpython_nrf24l01.network.structs.RF24NetworkHeader.pack
+
+ :Returns: The entire header as a `bytes` object.
+
+.. automethod:: circuitpython_nrf24l01.network.structs.RF24NetworkHeader.to_string
+
+Frame
+-----------------
+
+.. autoclass:: circuitpython_nrf24l01.network.structs.RF24NetworkFrame
+
+ This is used for either a single fragment of an individually large message (greater than 24
+ bytes) or a single message that is less than 25 bytes.
+
+ :param RF24NetworkHeader header: The header describing the frame's `message`.
+ :param bytes,bytearray message: The actual `message` containing the payload
+ or a fragment of a payload.
+
+ .. note:: |can_be_blank|
+
+.. autoattribute:: circuitpython_nrf24l01.network.structs.RF24NetworkFrame.header
+.. autoattribute:: circuitpython_nrf24l01.network.structs.RF24NetworkFrame.message
+
+ This attribute is typically a `bytearray` or `bytes` object.
+
+.. automethod:: circuitpython_nrf24l01.network.structs.RF24NetworkFrame.unpack
+
+ This function |internal_use|
+
+ :param bytes,bytearray buffer: |unpacked_buf|
+ :Returns: `True` if successful; otherwise `False`.
+
+.. automethod:: circuitpython_nrf24l01.network.structs.RF24NetworkFrame.pack
+
+ :Returns: The entire object as a `bytes` object.
+
+.. automethod:: circuitpython_nrf24l01.network.structs.RF24NetworkFrame.is_ack_type
+
+ This function |internal_use|
+
+FrameQueue
+-----------------
+
+.. autoclass:: circuitpython_nrf24l01.network.structs.FrameQueue
+
+ :param FrameQueue,FrameQueueFrag queue: To move (not copy) the contents of another
+ `FrameQueue` based object, you can pass the object to this parameter. Doing so
+ will also copy the object's `max_queue_size` attribute.
+
+.. autoattribute:: circuitpython_nrf24l01.network.structs.FrameQueue.max_queue_size
+.. automethod:: circuitpython_nrf24l01.network.structs.FrameQueue.enqueue
+
+ :Returns: `True` if the frame was added to the queue, or `False` if it was not.
+
+.. automethod:: circuitpython_nrf24l01.network.structs.FrameQueue.dequeue
+.. automethod:: circuitpython_nrf24l01.network.structs.FrameQueue.peek
+.. automethod:: circuitpython_nrf24l01.network.structs.FrameQueue.__len__
+
+ For use with Python's builtin :func:`len()`.
+
+FrameQueueFrag
+-----------------
+
+.. autoclass:: circuitpython_nrf24l01.network.structs.FrameQueueFrag
+ :show-inheritance:
+
+ .. note:: This class will only cache 1 fragmented message at a time. If parts of
+ the fragmented message are missing (or duplicate fragments are received), then
+ the fragment is discarded. If a new fragmented message is received (before a
+ previous fragmented message is completed and reassembled), then the cache
+ is reused for the new fragmented message to avoid memory leaks.
+
+Logical Address Validation
+--------------------------
+
+.. automethod:: circuitpython_nrf24l01.network.structs.is_address_valid
+
+ :param int address: The :ref:`Logical Address ` to validate.
+
+ :Returns:
+ `True` if the given address can be used as a `node_address` or `to_node`
+ destination. Otherwise, this function returns `False`.
+
+ .. warning::
+ Please note that this function also allows the value ``0o100`` to validate
+ because it is used as the `NETWORK_MULTICAST_ADDR` for multicasted messages.
+ Technically, ``0o100`` is an invalid address.
diff --git a/docs/network_docs/topology.rst b/docs/network_docs/topology.rst
new file mode 100644
index 0000000..da07e83
--- /dev/null
+++ b/docs/network_docs/topology.rst
@@ -0,0 +1,372 @@
+Network Topology
+================
+
+Network Levels
+****************
+
+Because of the hardware limitation's of the nRF24L01 transceiver, each network
+is arranged in a levels where a parent can have up to 5 children. And each child can also have
+up to 5 other children. This is not limitless because this network is designed for low-memory
+devices. Consequently, all node's :ref:`Logical Address ` are limited to 12-bit
+integers and use an octal counting scheme.
+
+- The master node (designated with the :ref:`Logical Address ` ``0o0``)
+ is always the only node in the lowest level (denoted as level 0).
+- Child nodes are designated by the most significant octal digit in their
+ :ref:`Logical Address `. A child node address' least significant digits are
+ the inherited address of it's parent node. Nodes on level 1 only have 1 digit because they are
+ children of the master node.
+
+.. graphviz::
+
+ graph network_hierarchy {
+ bgcolor="#323232A1"
+ newrank=true
+ // ratio="0.65"
+ node [
+ fontcolor="#FEFEFE"
+ fontsize=14
+ fontname=Arial
+ ]
+ pad="0"
+ margin="0"
+ subgraph cluster_hierarchy {
+ bgcolor="#24242400"
+ color="#24242400"
+ node [
+ style=filled
+ color="#FEFEFE7f"
+ ]
+ edge [color="#FEFEFE" style="setlinewidth(2)"]
+ subgraph lvl_0 {
+ "0o0" [
+ shape="circle"
+ style="radial"
+ fillcolor="0.85:#018268;0:#000"
+ ]
+ }
+ subgraph lvl_1 {
+ node [fillcolor="#3E0180"]
+ "0o1" "0o2" "0o3" "0o4" "0o5"
+ }
+ subgraph lvl_2 {
+ node [fillcolor="#014B80"]
+ "0o14" "0o24" "0o34" "0o44" "0o54"
+ }
+ subgraph lvl_3 {
+ node [fillcolor="#0E6902"]
+ "0o124" "0o224" "0o324" "0o424" "0o524"
+ }
+ subgraph lvl_4 {
+ node [fillcolor="#80010B"]
+ "0o1324" "0o2324" "0o3324" "0o4324" "0o5324"
+ }
+ "0o0" -- "0o4" -- "0o24" -- "0o324" -- "0o1324"
+ "0o0" -- "0o1"; "0o0" -- "0o2"; "0o0" -- "0o3"; "0o0" -- "0o5"
+ "0o4" -- "0o14"; "0o4" -- "0o34"; "0o4" -- "0o44"; "0o4" -- "0o54"
+ "0o24" -- "0o124"; "0o24" -- "0o224"; "0o24" -- "0o424"; "0o24" -- "0o524"
+ "0o324" -- "0o2324"; "0o324" -- "0o3324"; "0o324" -- "0o4324"; "0o324" -- "0o5324"
+ }
+ subgraph cluster_legend {
+ bgcolor="#242424"
+ color="#24242400"
+ "Legend" [
+ color="#FEF9A9"
+ shape=plain
+ margin=0
+ label=<
+
+
+
Legend
+
+
+
Network Level 0
+
+
+
+
Network Level 1
+
+
+
+
Network Level 2
+
+
+
+
Network Level 3
+
+
+
+
Network Level 4
+
+
+
+
Nodes are labeled in octal numbers
+
+
+ >
+ ]
+ }
+ }
+
+Hopefully, you should see the pattern. There can be up to a maximum of 5 network levels (that's
+0-4 ordered from lowest to highest).
+
+For a message to travel from node ``0o124`` to node ``0o3``, it must be passed through any applicable
+network levels. So, the message flows ``0o124`` -> ``0o24`` -> ``0o4`` -> ``0o0`` -> ``0o3``.
+
+A single network can potentially have a maximum of 781 nodes (all operating on the same
+:attr:`~circuitpython_nrf24l01.rf24.RF24.channel`), but for readability reasons, the following
+graph only demonstrates
+
+- the master node (level 0) and it's 5 children (level 1)
+- level 2 only shows the 1\ :sup:`st` and 2\ :sup:`nd` children of parents on level 1
+- level 3 only shows the 3\ :sup:`rd` and 4\ :sup:`th` children of parents on level 2
+- level 4 only shows the 5\ :sup:`th` children of parents on level 3
+
+
+.. graphviz::
+
+ graph network_levels {
+ layout=twopi
+ bgcolor="#323232A1"
+ ratio="0.825"
+ node [
+ style=filled
+ fontcolor="#FEFEFE"
+ color="#FEFEFE7f"
+ fontsize=14
+ fontname=Arial
+ ]
+ edge [color="#FEFEFE" style="setlinewidth(2)"]
+ ranksep="0.85:0.9:0.95:1.1"
+ subgraph lvl_0 {
+ "0o0" [
+ root=true
+ shape="circle"
+ style="radial"
+ fillcolor="0.9:#018268;0:#000"
+ ]
+ }
+ subgraph lvl_1 {
+ node [fillcolor="#3E0180"]
+ "0o1" "0o2" "0o3" "0o4" "0o5"
+ }
+ subgraph lvl_2 {
+ node [fillcolor="#014B80"]
+ "0o11" "0o21" "0o12" "0o22" "0o13" "0o23" "0o14" "0o24" "0o15" "0o25"
+ }
+ subgraph lvl_3 {
+ node [fillcolor="#0E6902"]
+ "0o311" "0o411" "0o321" "0o421" "0o312" "0o412" "0o322" "0o422" "0o313" "0o413"
+ "0o323" "0o423" "0o314" "0o414" "0o324" "0o424" "0o315" "0o415" "0o325" "0o425"
+ }
+ subgraph lvl_4 {
+ node [fillcolor="#80010B"]
+ "0o5311" "0o5411" "0o5321" "0o5312" "0o5421" "0o5313" "0o5314" "0o5315" "0o5322"
+ "0o5323" "0o5324" "0o5325" "0o5412" "0o5423" "0o5422" "0o5413" "0o5414" "0o5424"
+ "0o5415" "0o5425"
+ }
+ "0o0" -- "0o1" -- "0o11" -- "0o311" -- "0o5311"
+ "0o0" -- "0o2" -- "0o12" -- "0o312" -- "0o5312"
+ "0o0" -- "0o3" -- "0o13" -- "0o313" -- "0o5313"
+ "0o0" -- "0o4" -- "0o14" -- "0o314" -- "0o5314"
+ "0o0" -- "0o5" -- "0o15" -- "0o315" -- "0o5315"
+ "0o1" -- "0o21" -- "0o321" -- "0o5321"
+ "0o2" -- "0o22" -- "0o322" -- "0o5322"
+ "0o3" -- "0o23" -- "0o323" -- "0o5323"
+ "0o4" -- "0o24" -- "0o324" -- "0o5324"
+ "0o5" -- "0o25" -- "0o325" -- "0o5325"
+ "0o11" -- "0o411" -- "0o5411"
+ "0o21" -- "0o421" -- "0o5421"
+ "0o12" -- "0o412" -- "0o5412"
+ "0o22" -- "0o422" -- "0o5422"
+ "0o13" -- "0o413" -- "0o5413"
+ "0o23" -- "0o423" -- "0o5423"
+ "0o14" -- "0o414" -- "0o5414"
+ "0o24" -- "0o424" -- "0o5424"
+ "0o15" -- "0o415" -- "0o5415"
+ "0o25" -- "0o425" -- "0o5425"
+ }
+
+.. _Physical Address:
+.. _Logical Address:
+
+Physical addresses vs Logical addresses
+***************************************
+
+- The Physical address is the 5-byte address assigned to the radio's data pipes.
+- The Logical address is the 12-bit integer representing a network node.
+ The Logical address uses an octal counting scheme. A valid Logical Address must only
+ contain octal digits in range [1, 5]. The master node is the exception for it uses the
+ number ``0``
+
+ .. tip::
+ Use the `is_address_valid()` function to programatically check a Logical Address for validity.
+
+.. note::
+ Remember that the nRF24L01 only has 6 data pipes for which to receive or transmit.
+ Since only data pipe 0 can be used to transmit, the other other data pipes 1-5 are
+ devoted to receiving transmissions from other network nodes; data pipe 0 also receives
+ multicasted messages about the node's network level).
+
+Translating Logical to Physical
+-------------------------------
+
+Before translating the Logical address, a single byte is used reptitively 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"``.
+
+The `RF24Network` class also has a predefined list of bytes used for translating
+unique Logical addresses into unique Physical addresses. This list is called
+`address_suffix` (also stored as a mutable `bytearray`). By default the `address_suffix`
+has 6-byte value of ``b"\xC3\x3C\x33\xCE\x3E\xE3"`` where the order of bytes pertains to the
+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
+ 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)"
+ :width: 10
+ :widths: 1, 9
+
+ 1, ``CC CC CC CC 3C``
+ 2, ``CC CC CC CC 33``
+ 3, ``CC CC CC CC CE``
+ 4, ``CC CC CC CC 3E``
+ 5, ``CC CC CC CC E3``
+
+ The Logical address of the master node's first child: ``0o1``
+
+ .. csv-table::
+ :header: "pipe", "Phsyical Address (hexadecimal)"
+ :width: 10
+ :widths: 1, 9
+
+ 1, ``CC CC CC 3C 3C``
+ 2, ``CC CC CC 3C 33``
+ 3, ``CC CC CC 3C CE``
+ 4, ``CC CC CC 3C 3E``
+ 5, ``CC CC CC 3C E3``
+
+ The Logical address of the master node's second child: ``0o2``
+
+ .. csv-table::
+ :header: "pipe", "Phsyical Address (hexadecimal)"
+ :width: 10
+ :widths: 1, 9
+
+ 1, ``CC CC CC 33 3C``
+ 2, ``CC CC CC 33 33``
+ 3, ``CC CC CC 33 CE``
+ 4, ``CC CC CC 33 3E``
+ 5, ``CC CC CC 33 E3``
+
+ The Logical address of the master node's third child's second child's first child: ``0o123``
+
+ .. csv-table::
+ :header: "pipe", "Phsyical Address (hexadecimal)"
+ :width: 10
+ :widths: 1, 9
+
+ 1, ``CC 3C 33 CE 3C``
+ 2, ``CC 3C 33 CE 33``
+ 3, ``CC 3C 33 CE CE``
+ 4, ``CC 3C 33 CE 3E``
+ 5, ``CC 3C 33 CE E3``
+
+Two networks coexisting on the same channel
+-------------------------------------------
+
+.. warning::
+ The following section is an advanced tutorial. The default values for `address_prefix`
+ and `address_suffix` were carefully chosen by TMRh20 to demonstrate best practices in
+ terms of choosing a data pipe's address for transmissions. Bad practices can be avoided
+ by heeding ManiacBug's advice in his
+ `detailed blog post `_
+ about the topic.
+
+In theory, the `address_prefix` and `address_suffix` attributes could be changed to
+allow 2 separate networks to coexist on the same
+:attr:`~circuitpython_nrf24l01.rf24.RF24.channel`. The following are example code
+snippets to use as a template for such a scenario.
+
+.. code-block:: python
+ :caption: Master node for ``network_a``
+
+ from circuitpython_nrf24l01.rf24_network import RF24Network
+
+ # ... declare SPI_BUS, CE_PIN, and CSN_PIN objects
+ network_a_master = RF24Network(SPI_BUS, CSN_PIN, CE_PIN, 0)
+
+ # let network_a use the default values for address_prefix and address_suffix
+
+ while True:
+ network_a_master.update()
+ if network_a_master.available():
+ recv_frame = network_a_master.read()
+ print(
+ "received {}: {}".format(
+ recv_frame.header.to_string(), recv_frame.message.decode()
+ )
+ )
+ # emit frames as needed
+
+.. code-block:: python
+ :caption: Master node for ``network_b``
+
+ from circuitpython_nrf24l01.rf24_network import RF24Network
+
+ # ... declare SPI_BUS, CE_PIN, and CSN_PIN objects
+ network_b_master = RF24Network(SPI_BUS, CSN_PIN, CE_PIN, 0)
+
+ # let network_b use different values for address_prefix and address_suffix
+ network_b_master.address_prefix = bytearray([0xDB])
+ network_b_master.address_suffix = bytearray([0xDD, 0x99, 0xB6, 0xD9, 0x9D, 0x66])
+
+ # re-assign the node_address for the different physical addresses to be used
+ network_b_master.node_address = 0
+
+ while True:
+ network_b_master.update()
+ if network_b_master.available():
+ recv_frame = network_b_master.read()
+ print(
+ "received {}: {}".format(
+ recv_frame.header.to_string(), recv_frame.message.decode()
+ )
+ )
+ # emit frames as needed
+
+.. code-block:: python
+ :caption: A single network node for hoping between ``network_a`` & ``network_b``
+
+ from circuitpython_nrf24l01.rf24_network import RF24Network
+
+ # ... declare SPI_BUS, CE_PIN, and CSN_PIN objects
+ network_b_node = RF24Network(SPI_BUS, CSN_PIN, CE_PIN, 5)
+ network_a_node = RF24Network(SPI_BUS, CSN_PIN, CE_PIN, 1)
+
+ # let network_b use different values for address_prefix and address_suffix
+ with network_b_node as net_b:
+ net_b.address_prefix = bytearray([0xDB])
+ net_b.address_suffix = bytearray([0xDD, 0x99, 0xB6, 0xD9, 0x9D, 0x66])
+
+ # re-assign the node_address for the different physical addresses to be used
+ net_b.node_address = 5
+
+ while True:
+ # do something with network_a
+ with network_a_node as net_a:
+ net_a.update()
+ net_a.send(RF24NetworkHeader(0, "T"), b"data for net A master")
+
+ # do something with network_b
+ with network_b_node as net_b:
+ net_b.update()
+ net_b.send(RF24NetworkHeader(0, "T"), b"data for net B master")
diff --git a/docs/requirements.txt b/docs/requirements.txt
index b51f761..434057c 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,3 +1 @@
-sphinx-material
-sphinx-copybutton
-sphinx-sitemap
\ No newline at end of file
+sphinx-immaterial
diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst
index a964138..f950a2d 100644
--- a/docs/troubleshooting.rst
+++ b/docs/troubleshooting.rst
@@ -1,203 +1,214 @@
-
-Troubleshooting info
-====================
-
-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
- received payloads. `auto_ack` does not require `dynamic_payloads` to be enabled.
-
- .. note:: With the `auto_ack` feature enabled, you get:
-
- * cyclic redundancy checking (`crc`) automatically enabled
- * to change amount of automatic re-transmit attempts and the delay time between
- them. See the `arc` and `ard` attributes.
-2. `dynamic_payloads` feature allows either TX/RX nRF24L01 to be able to send/receive
- payloads with their size written into the payloads' packet. With this disabled, both
- RX/TX nRF24L01 must use matching `payload_length` attributes. `dynamic_payloads`
- does not require `auto_ack` to be enabled.
-3. `ack` feature allows the MCU to append a payload to the ACK packet, thus instant
- bi-directional communication. A transmitting ACK payload must be loaded into the
- nRF24L01's TX FIFO buffer (done using `load_ack()`) BEFORE receiving the payload that
- is to be acknowledged. Once transmitted, the payload is released from the TX FIFO
- buffer.
-
- .. important:: This `ack` feature requires the `auto_ack` and `dynamic_payloads`
- features enabled.
-
-FIFO Capacity
-*************
-
-Remeber 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
-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.
-
-Pipes vs Addresses vs Channels
-******************************
-
-.. hint:: Please review the `Multiceiver example `_ as a
- demonstration of proper addressing using all pipes (on the same channel).
-
-Pipes
------
-
-You should think of the data pipes as a "parking spot" for your payload. There are only six
-data pipes on the nRF24L01, thus it can simultaneously "listen" to a maximum of 6 other
-nRF24L01 radios. However, it can only "talk" to 1 other nRF24L01 at a time.
-
-Addresses
----------
-
-The specified address is not the address of an nRF24L01 radio, rather it is more like a
-path that connects the endpoints. When assigning addresses to a data pipe, you can use any
-5 byte long address you can think of (as long as the first byte of the `bytearray` is unique
-among simultaneously broadcasting addresses), so you're not limited to communicating with only
-the same 6 nRF24L01 radios.
-
-Channels
---------
-
-Finnaly, the radio's channel is not be confused with the radio's pipes. Channel selection
-is a way of specifying a certain radio frequency (frequency = [2400 + channel] MHz).
-Channel defaults to 76 (like the arduino library), but options range from 0 to 125 --
-that's 2.4 GHz to 2.525 GHz. The channel can be tweaked to find a less occupied frequency
-amongst Bluetooth, WiFi, or other ambient signals that use the same spectrum of
-frequencies.
-
-Settings that must Match
-************************
-
-For successful transmissions, most of the endpoint trasceivers' settings/features must
-match. These settings/features include:
-
-* The RX pipe's address on the receiving nRF24L01 (passed to `open_rx_pipe()`) MUST match
- the TX pipe's address on the transmitting nRF24L01 (passed to `open_tx_pipe()`)
-* `address_length`
-* :py:attr:`~circuitpython_nrf24l01.rf24.RF24.channel`
-* `data_rate`
-* `dynamic_payloads`
-* `payload_length` only when `dynamic_payloads` is disabled
-* `auto_ack`
-* custom `ack` payloads
-* `crc`
-
-Settings that do not need to Match
-----------------------------------
-
-In fact the only attributes that aren't required to match on both endpoint transceivers
-would be
-
-* the identifying data pipe number passed to `open_rx_pipe()` or `load_ack()` (as long as the
- corresponding addresses match)
-* `pa_level`
-* `arc`
-* `ard`
-
-The ``ask_no_ack`` feature can be used despite the
-settings/features configuration (see :py:meth:`~circuitpython_nrf24l01.rf24.RF24.send()` &
-`write()` function parameters for more details).
-
-About the lite version
-======================
-
-.. versionadded:: 1.2.0
-
-This library contains a "lite" version of ``rf24.py`` titled ``rf24_lite.py``. It has been
-developed to save space on microcontrollers with limited amount of RAM and/or storage (like
-boards using the ATSAMD21 M0). The following functionality has been removed from the lite
-version:
-
-* The `FakeBLE` class is not compatible with the ``rf24_lite.py`` module.
-* `is_plus_variant` is removed, meaning the
- lite version is not compatibility with the older non-plus variants of the nRF24L01.
-* `address()` removed.
-* `print_details()` removed. However you can use the following function to dump all available
- registers' values (for advanced users):
-
- .. code-block:: python
-
- # let `nrf` be the instantiated RF24 object
- def dump_registers(end=0x1e):
- for i in range(end):
- if i in (0xA, 0xB, 0x10):
- print(hex(i), "=", nrf._reg_read_bytes(i))
- elif i not in (0x18, 0x19, 0x1a, 0x1b):
- print(hex(i), "=", hex(nrf._reg_read(i)))
-* `dynamic_payloads` applies to all pipes, not individual pipes. This attribute will return
- a `bool` instead of an `int`. `set_dynamic_payloads()` and `get_dynamic_payloads()` have
- been removed.
-* `payload_length` applies to all pipes, not individual pipes. `set_payload_length()` and
- `get_payload_length()` have been removed.
-* `load_ack()` is available, but it will not throw exceptions for malformed ``buf`` or
- invalid ``pipe_number`` parameters. Rather any call to `load_ack()` with invalid
- parameters will have no affect on the TX FIFO.
-* `crc` removed. 2-bytes encoding scheme (CRC16) is always enabled.
-* `auto_ack` removed. This is always enabled for all pipes. Pass ``ask_no_ack`` parameter
- as `True` to :py:meth:`~circuitpython_nrf24l01.rf24.RF24.send()` or `write()` to disable
- automatic acknowledgement for TX operations.
-* `is_lna_enabled` removed as it only affects non-plus variants of the nRF24L01.
-* `pa_level` is available, but it will not accept a `list` or `tuple`.
-* `start_carrier_wave()`, & `stop_carrier_wave()` removed. These only perform a
- test of the nRF24L01's hardware. `rpd` is still available.
-* All comments and docstrings removed, meaning ``help()`` will not provide any specific
- information. Exception prompts have also been reduced and adjusted accordingly.
-* Cannot switch between different radio configurations using context manager (the `with`
- blocks). It is advised that only one `RF24` object be instantiated when RAM is limited
- (less than or equal to 32KB).
-* `last_tx_arc` attribute removed because it is only meant for troubleshooting.
-* `allow_ask_no_ack` attribute removed because it is only provided for the Si24R1
- chinese clone.
-* `set_auto_retries()` & `get_auto_retries()` removed. Use `ard` & `arc` attributes instead.
-
-Testing nRF24L01+PA+LNA module
-=================================
-
-The following are semi-successful test results using a nRF24L01+PA+LNA module:
-
-The Setup
-*********************************
-
-I wrapped the PA/LNA module with electrical tape and then foil around that (for shielding)
-while being very careful to not let the foil touch any current carrying parts (like the GPIO pins and the soldier joints for the antenna mount). Then I wired up a PA/LNA module with a 3V
-regulator (L4931 with a 2.2 µF capacitor between V\ :sub:`out` & GND) using my ItsyBitsy M4
-5V (USB) pin going directly to the L4931 V\ :sub:`in` pin. The following are experiences from
-running simple, ack, & stream examples with a reliable nRF24L01+ (no PA/LNA) on the other end (driven by a Raspberry Pi 2):
-
-Results (ordered by :py:attr:`~circuitpython_nrf24l01.rf24.RF24.pa_level` settings)
-***********************************************************************************
-
-* 0 dBm: ``master()`` worked the first time (during simple example) then continuously failed
- (during all examples). ``slave()`` worked on simple & stream examples, but the opposing
- ``master()`` node reporting that ACK packets (without payloads) were **not** received from
- the PA/LNA module; ``slave()`` failed to send ACK packet payloads during the ack example.
-* -6 dBm: ``master()`` worked consistently on simple, ack, & stream example. ``slave()`` worked
- reliably on simple & stream examples, but failed to transmit **any** ACK packet payloads in
- the ack example.
-* -12 dBm: ``master()`` worked consistently on simple, ack, & stream example. ``slave()``
- worked reliably on simple & stream examples, but failed to transmit **some** ACK packet
- payloads in the ack example.
-* -18 dBm: ``master()`` worked consistently on simple, ack, & stream example. ``slave()``
- worked reliably on simple, ack, & stream examples, meaning **all** ACK packet payloads were
- successfully transmit in the ack example.
-
-I should note that without shielding the PA/LNA module and using the L4931 3V regulator,
-no TX transmissions got sent (including ACK packets for the `auto_ack` feature).
-
-Conclusion
-*********************************
-
-The PA/LNA modules seem to require quite a bit more power to transmit. The L4931 regulator
-that I used in the tests boasts a 300 mA current limit and a typical current of 250 mA.
-While the ItsyBitsy M4 boasts a 500 mA max, it would seem that much of that is consumed
-internally. Since playing with the `pa_level` is a current saving hack (as noted in the
-datasheet), I can only imagine that a higher power 3V regulator may enable sending
-transmissions (including ACK packets -- with or without ACK payloads attached) from PA/LNA
-modules using higher `pa_level` settings. More testing is called for, but I don't have an
-oscilloscope to measure the peak current draws.
+
+Troubleshooting info
+~~~~~~~~~~~~~~~~~~~~
+
+Common Problems
+===============
+
+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
+ received payloads. `auto_ack` does not require
+ :attr:`~circuitpython_nrf24l01.rf24.RF24.dynamic_payloads` to be enabled.
+
+ .. note:: With the `auto_ack` feature enabled, you get:
+
+ * cyclic redundancy checking (`crc`) automatically enabled
+ * to change amount of automatic re-transmit attempts and the delay time between
+ them. See the `arc` and `ard` attributes.
+2. :attr:`~circuitpython_nrf24l01.rf24.RF24.dynamic_payloads` feature allows either TX/RX nRF24L01 to be able to send/receive
+ payloads with their size written into the payloads' packet. With this disabled, both
+ RX/TX nRF24L01 must use matching `payload_length` attributes.
+ :attr:`~circuitpython_nrf24l01.rf24.RF24.dynamic_payloads`
+ does not require `auto_ack` to be enabled.
+3. `ack` feature allows the MCU to append a payload to the ACK packet, thus instant
+ bi-directional communication. A transmitting ACK payload must be loaded into the
+ nRF24L01's TX FIFO buffer (done using `load_ack()`) BEFORE receiving the payload that
+ is to be acknowledged. Once transmitted, the payload is released from the TX FIFO
+ buffer.
+
+ .. important:: This `ack` feature requires the `auto_ack` and
+ :attr:`~circuitpython_nrf24l01.rf24.RF24.dynamic_payloads`
+ features enabled.
+
+FIFO Capacity
+*************
+
+Remeber 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
+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.
+
+Pipes vs Addresses vs Channels
+******************************
+
+.. hint:: Please review the `Multiceiver example `_ as a
+ demonstration of proper addressing using all pipes (on the same channel).
+
+Pipes
+-----
+
+You should think of the data pipes as a "parking spot" for your payload. There are only six
+data pipes on the nRF24L01, thus it can simultaneously "listen" to a maximum of 6 other
+nRF24L01 radios. However, it can only "talk" to 1 other nRF24L01 at a time.
+
+Addresses
+---------
+
+The specified address is not the address of an nRF24L01 radio, rather it is more like a
+path that connects the endpoints. When assigning addresses to a data pipe, you can use any
+5 byte long address you can think of (as long as the first byte of the `bytearray` is unique
+among simultaneously broadcasting addresses), so you're not limited to communicating with only
+the same 6 nRF24L01 radios.
+
+Channels
+--------
+
+Finnaly, the radio's channel is not be confused with the radio's pipes. Channel selection
+is a way of specifying a certain radio frequency (frequency = [2400 + channel] MHz).
+Channel defaults to 76 (like the arduino library), but options range from 0 to 125 --
+that's 2.4 GHz to 2.525 GHz. The channel can be tweaked to find a less occupied frequency
+amongst Bluetooth, WiFi, or other ambient signals that use the same spectrum of
+frequencies.
+
+Settings that must Match
+************************
+
+For successful transmissions, most of the endpoint trasceivers' settings/features must
+match. These settings/features include:
+
+* The RX pipe's address on the receiving nRF24L01 (passed to `open_rx_pipe()`) MUST match
+ the TX pipe's address on the transmitting nRF24L01 (passed to `open_tx_pipe()`)
+* `address_length`
+* :attr:`~circuitpython_nrf24l01.rf24.RF24.channel`
+* `data_rate`
+* :attr:`~circuitpython_nrf24l01.rf24.RF24.dynamic_payloads`
+* `payload_length` only when
+ :attr:`~circuitpython_nrf24l01.rf24.RF24.dynamic_payloads` is disabled
+* `auto_ack`
+* custom `ack` payloads
+* `crc`
+
+Settings that do not need to Match
+----------------------------------
+
+In fact the only attributes that aren't required to match on both endpoint transceivers
+would be
+
+* the identifying data pipe number passed to `open_rx_pipe()` or `load_ack()` (as long as the
+ corresponding addresses match)
+* :attr:`~circuitpython_nrf24l01.rf24.RF24.pa_level`
+* `arc`
+* `ard`
+
+The ``ask_no_ack`` feature can be used despite the
+settings/features configuration (see :meth:`~circuitpython_nrf24l01.rf24.RF24.send()` &
+:meth:`~circuitpython_nrf24l01.rf24.RF24.write()` function parameters for more details).
+
+About the lite version
+======================
+
+.. versionadded:: 1.2.0
+
+This library contains a "lite" version of ``rf24.py`` titled ``rf24_lite.py``. It has been
+developed to save space on microcontrollers with limited amount of RAM and/or storage (like
+boards using the ATSAMD21). The following functionality has been removed from the lite
+version:
+
+* The `FakeBLE`, `RF24Network`, and `RF24Mesh` classes are not compatible with the ``rf24_lite.py`` module.
+* `is_plus_variant` is removed, meaning the
+ lite version is not compatibility with the older non-plus variants of the nRF24L01.
+* `address()` removed.
+* :py:meth:`~circuitpython_nrf24l01.rf24.RF24.print_details()` removed. However you can use the following function to dump all available
+ registers' values (for advanced users):
+
+ .. code-block:: python
+
+ # let `nrf` be the instantiated RF24 object
+ def dump_registers(end=0x1e):
+ for i in range(end):
+ if i in (0xA, 0xB, 0x10):
+ print(hex(i), "=", nrf._reg_read_bytes(i))
+ elif i not in (0x18, 0x19, 0x1a, 0x1b):
+ print(hex(i), "=", hex(nrf._reg_read(i)))
+* :attr:`~circuitpython_nrf24l01.rf24.RF24.dynamic_payloads` applies to all
+ pipes, not individual pipes. This attribute will return
+ a `bool` instead of an `int`.
+ :attr:`~circuitpython_nrf24l01.rf24.RF24.set_dynamic_payloads()` and
+ :attr:`~circuitpython_nrf24l01.rf24.RF24.get_dynamic_payloads()` have
+ been removed.
+* `payload_length` applies to all pipes, not individual pipes. `set_payload_length()` and
+ `get_payload_length()` have been removed.
+* `load_ack()` is available, but it will not throw exceptions for malformed ``buf`` or
+ invalid ``pipe_number`` parameters. Rather any call to `load_ack()` with invalid
+ parameters will have no affect on the TX FIFO.
+* `crc` removed. 2-bytes encoding scheme (CRC16) is always enabled.
+* `auto_ack` removed. This is always enabled for all pipes. Pass ``ask_no_ack`` parameter
+ as `True` to :meth:`~circuitpython_nrf24l01.rf24.RF24.send()` or :meth:`~circuitpython_nrf24l01.rf24.RF24.write()` to disable
+ automatic acknowledgement for TX operations.
+* `is_lna_enabled` removed as it only affects non-plus variants of the nRF24L01.
+* :attr:`~circuitpython_nrf24l01.rf24.RF24.pa_level` is available, but it will not accept a `list` or `tuple`.
+* `start_carrier_wave()`, & `stop_carrier_wave()` removed. These only perform a
+ test of the nRF24L01's hardware. `rpd` is still available.
+* All comments and docstrings removed, meaning ``help()`` will not provide any specific
+ information. Exception prompts have also been reduced and adjusted accordingly.
+* Cannot switch between different radio configurations using context manager (the `with`
+ blocks). It is advised that only one `RF24` object be instantiated when RAM is limited
+ (less than or equal to 32KB).
+* `last_tx_arc` attribute removed because it is only meant for troubleshooting.
+* `allow_ask_no_ack` attribute removed because it is only provided for the Si24R1
+ chinese clone.
+* `set_auto_retries()` & `get_auto_retries()` removed. Use `ard` & `arc` attributes instead.
+
+Testing nRF24L01+PA+LNA module
+=================================
+
+The following are semi-successful test results using a nRF24L01+PA+LNA module:
+
+The Setup
+*********************************
+
+I wrapped the PA/LNA module with electrical tape and then foil around that (for shielding)
+while being very careful to not let the foil touch any current carrying parts (like the GPIO pins and the soldier joints for the antenna mount). Then I wired up a PA/LNA module with a 3V
+regulator (L4931 with a 2.2 µF capacitor between V\ :sub:`out` & GND) using my ItsyBitsy M4
+5V (USB) pin going directly to the L4931 V\ :sub:`in` pin. The following are experiences from
+running simple, ack, & stream examples with a reliable nRF24L01+ (no PA/LNA) on the other end (driven by a Raspberry Pi 2):
+
+Results (ordered by :py:attr:`~circuitpython_nrf24l01.rf24.RF24.pa_level` settings)
+***********************************************************************************
+
+* 0 dBm: ``master()`` worked the first time (during simple example) then continuously failed
+ (during all examples). ``slave()`` worked on simple & stream examples, but the opposing
+ ``master()`` node reporting that ACK packets (without payloads) were **not** received from
+ the PA/LNA module; ``slave()`` failed to send ACK packet payloads during the ack example.
+* -6 dBm: ``master()`` worked consistently on simple, ack, & stream example. ``slave()`` worked
+ reliably on simple & stream examples, but failed to transmit **any** ACK packet payloads in
+ the ack example.
+* -12 dBm: ``master()`` worked consistently on simple, ack, & stream example. ``slave()``
+ worked reliably on simple & stream examples, but failed to transmit **some** ACK packet
+ payloads in the ack example.
+* -18 dBm: ``master()`` worked consistently on simple, ack, & stream example. ``slave()``
+ worked reliably on simple, ack, & stream examples, meaning **all** ACK packet payloads were
+ successfully transmit in the ack example.
+
+I should note that without shielding the PA/LNA module and using the L4931 3V regulator,
+no TX transmissions got sent (including ACK packets for the `auto_ack` feature).
+
+Conclusion
+*********************************
+
+The PA/LNA modules seem to require quite a bit more power to transmit. The L4931 regulator
+that I used in the tests boasts a 300 mA current limit and a typical current of 250 mA.
+While the ItsyBitsy M4 boasts a 500 mA max, it would seem that much of that is consumed
+internally. Since playing with the :attr:`~circuitpython_nrf24l01.rf24.RF24.pa_level` is a
+current saving hack (as noted in the datasheet), I can only imagine that a higher power
+3V regulator may enable sending transmissions (including ACK packets -- with or without
+ACK payloads attached) from PA/LNA modules using higher
+:attr:`~circuitpython_nrf24l01.rf24.RF24.pa_level` settings. More testing is called for,
+but I don't have an oscilloscope to measure the peak current draws.
diff --git a/examples/nrf24l01_ack_payload_test.py b/examples/nrf24l01_ack_payload_test.py
index 02e83df..242e430 100644
--- a/examples/nrf24l01_ack_payload_test.py
+++ b/examples/nrf24l01_ack_payload_test.py
@@ -4,27 +4,40 @@
"""
import time
import board
-import digitalio
+from digitalio import DigitalInOut
# if running this on a ATSAMD21 M0 based board
# from circuitpython_nrf24l01.rf24_lite import RF24
from circuitpython_nrf24l01.rf24 import RF24
-# change these (digital output) pins accordingly
-ce = digitalio.DigitalInOut(board.D4)
-csn = digitalio.DigitalInOut(board.D5)
+# invalid default values for scoping
+SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
-# using board.SPI() automatically selects the MCU's
-# available SPI pins, board.SCK, board.MOSI, board.MISO
-spi = board.SPI() # init spi bus object
+try: # on Linux
+ import spidev
+
+ SPI_BUS = spidev.SpiDev() # for a faster interface on linux
+ CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
+ CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
+
+except ImportError: # on CircuitPython only
+ # using board.SPI() automatically selects the MCU's
+ # available SPI pins, board.SCK, board.MOSI, board.MISO
+ SPI_BUS = board.SPI() # init spi bus object
+
+ # change these (digital output) pins accordingly
+ CE_PIN = DigitalInOut(board.D4)
+ CSN_PIN = DigitalInOut(board.D5)
-# we'll be using the dynamic payload size feature (enabled by default)
-# the custom ACK payload feature is disabled by default
-# the custom ACK payload feature should not be enabled
-# during instantiation due to its singular use nature
-# meaning 1 ACK payload per 1 RX'd payload
-nrf = RF24(spi, csn, ce)
+# initialize the nRF24L01 on the spi bus object
+nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
+# On Linux, csn value is a bit coded
+# 0 = bus 0, CE0 # SPI bus 0 is enabled by default
+# 10 = bus 1, CE0 # enable SPI bus 2 prior to running this
+# 21 = bus 2, CE1 # enable SPI bus 1 prior to running this
+
+# the custom ACK payload feature is disabled by default
# NOTE the the custom ACK payload feature will be enabled
# automatically when you call load_ack() passing:
# a buffer protocol object (bytearray) of
@@ -75,23 +88,18 @@ def master(count=5): # count = 5 will only transmit 5 packets
# fetched and saved to "result" via send()
# print timer results upon transmission success
print(
- "Transmission successful! Time to transmit: "
- "{} us. Sent: {}{}".format(
- int((end_timer - start_timer) / 1000),
- buffer[:6].decode("utf-8"),
- counter[0],
- ),
+ "Transmission successful! Time to transmit:",
+ f"{int((end_timer - start_timer) / 1000)} us.",
+ "Sent: {}{}".format(buffer[:6].decode("utf-8"), counter[0]),
end=" ",
)
if isinstance(result, bool):
- print(" Received an empty ACK packet")
+ print("Received an empty ACK packet")
else:
# result[:6] truncates c-string NULL termiating char
# received counter is a unsigned byte, thus result[7:8][0]
print(
- " Received: {}{}".format(
- bytes(result[:6]).decode("utf-8"), result[7:8][0]
- )
+ "Received: {}{}".format(result[:6].decode("utf-8"), result[7:8][0])
)
counter[0] += 1 # increment payload counter
elif not result:
@@ -123,14 +131,9 @@ def slave(timeout=6):
counter[0] = received[7:8][0] + 1
# the [:6] truncates the c-string NULL termiating char
print(
- "Received {} bytes on pipe {}: {}{} Sent: {}{}".format(
- length,
- pipe_number,
- bytes(received[:6]).decode("utf-8"),
- received[7:8][0],
- bytes(buffer[:6]).decode("utf-8"),
- buffer[7:8][0],
- )
+ f"Received {length} bytes on pipe {pipe_number}:",
+ "{}{}".format(received[:6].decode("utf-8"), received[7:8][0]),
+ "Sent: {}{}".format(buffer[:6].decode("utf-8"), buffer[7:8][0]),
)
start = time.monotonic() # reset timer
buffer = b"World \0" + bytes([counter[0]]) # build new ACK
@@ -144,10 +147,6 @@ def slave(timeout=6):
def set_role():
"""Set the role using stdin stream. Timeout arg for slave() can be
specified using a space delimiter (e.g. 'R 10' calls `slave(10)`)
-
- :return:
- - True when role is complete & app should continue running.
- - False when app should exit
"""
user_input = (
input(
@@ -159,16 +158,10 @@ def set_role():
)
user_input = user_input.split()
if user_input[0].upper().startswith("R"):
- if len(user_input) > 1:
- slave(int(user_input[1]))
- else:
- slave()
+ slave(*[int(x) for x in user_input[1:2]])
return True
if user_input[0].upper().startswith("T"):
- if len(user_input) > 1:
- master(int(user_input[1]))
- else:
- master()
+ master(*[int(x) for x in user_input[1:2]])
return True
if user_input[0].upper().startswith("Q"):
nrf.power = False
diff --git a/examples/nrf24l01_context_test.py b/examples/nrf24l01_context_test.py
index 61414f2..db47570 100644
--- a/examples/nrf24l01_context_test.py
+++ b/examples/nrf24l01_context_test.py
@@ -6,21 +6,38 @@
.. warning:: This script is not compatible with the rf24_lite module
"""
import board
-import digitalio
+from digitalio import DigitalInOut
from circuitpython_nrf24l01.rf24 import RF24
from circuitpython_nrf24l01.fake_ble import FakeBLE
-# change these (digital output) pins accordingly
-ce = digitalio.DigitalInOut(board.D4)
-csn = digitalio.DigitalInOut(board.D5)
+# invalid default values for scoping
+SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
+
+try: # on Linux
+ import spidev
+
+ SPI_BUS = spidev.SpiDev() # for a faster interface on linux
+ CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
+ CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
+
+except ImportError: # on CircuitPython only
+ # using board.SPI() automatically selects the MCU's
+ # available SPI pins, board.SCK, board.MOSI, board.MISO
+ SPI_BUS = board.SPI() # init spi bus object
+
+ # change these (digital output) pins accordingly
+ CE_PIN = DigitalInOut(board.D4)
+ CSN_PIN = DigitalInOut(board.D5)
-# using board.SPI() automatically selects the MCU's
-# available SPI pins, board.SCK, board.MOSI, board.MISO
-spi = board.SPI() # init spi bus object
# initialize the nRF24L01 objects on the spi bus object
# the first object will have all the features enabled
-nrf = RF24(spi, csn, ce)
+nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
+# On Linux, csn value is a bit coded
+# 0 = bus 0, CE0 # SPI bus 0 is enabled by default
+# 10 = bus 1, CE0 # enable SPI bus 2 prior to running this
+# 21 = bus 2, CE1 # enable SPI bus 1 prior to running this
+
# enable the option to use custom ACK payloads
nrf.ack = True
# set the static payload length to 8 bytes
@@ -29,7 +46,7 @@
nrf.pa_level = -18
# the second object has most features disabled/altered
-ble = FakeBLE(spi, csn, ce)
+ble = FakeBLE(SPI_BUS, CSN_PIN, CE_PIN)
# the IRQ pin is configured to only go active on "data fail"
# NOTE BLE operations prevent the IRQ pin going active on "data fail" events
ble.interrupt_config(data_recv=False, data_sent=False)
diff --git a/examples/nrf24l01_fake_ble_test.py b/examples/nrf24l01_fake_ble_test.py
index 04c04ea..e63fdd4 100644
--- a/examples/nrf24l01_fake_ble_test.py
+++ b/examples/nrf24l01_fake_ble_test.py
@@ -6,7 +6,7 @@
"""
import time
import board
-import digitalio
+from digitalio import DigitalInOut
from circuitpython_nrf24l01.fake_ble import (
chunk,
FakeBLE,
@@ -14,17 +14,34 @@
BatteryServiceData,
TemperatureServiceData,
)
+from circuitpython_nrf24l01.rf24 import address_repr
-# change these (digital output) pins accordingly
-ce = digitalio.DigitalInOut(board.D4)
-csn = digitalio.DigitalInOut(board.D5)
+# invalid default values for scoping
+SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
+
+try: # on Linux
+ import spidev
+
+ SPI_BUS = spidev.SpiDev() # for a faster interface on linux
+ CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
+ CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
+
+except ImportError: # on CircuitPython only
+ # using board.SPI() automatically selects the MCU's
+ # available SPI pins, board.SCK, board.MOSI, board.MISO
+ SPI_BUS = board.SPI() # init spi bus object
+
+ # change these (digital output) pins accordingly
+ CE_PIN = DigitalInOut(board.D4)
+ CSN_PIN = DigitalInOut(board.D5)
-# using board.SPI() automatically selects the MCU's
-# available SPI pins, board.SCK, board.MOSI, board.MISO
-spi = board.SPI() # init spi bus object
# initialize the nRF24L01 on the spi bus object as a BLE compliant radio
-nrf = FakeBLE(spi, csn, ce)
+nrf = FakeBLE(SPI_BUS, CSN_PIN, CE_PIN)
+# On Linux, csn value is a bit coded
+# 0 = bus 0, CE0 # SPI bus 0 is enabled by default
+# 10 = bus 1, CE0 # enable SPI bus 2 prior to running this
+# 21 = bus 2, CE1 # enable SPI bus 1 prior to running this
# the name parameter is going to be its broadcasted BLE name
# this can be changed at any time using the `name` attribute
@@ -131,13 +148,31 @@ def send_url(count=50):
time.sleep(0.2)
+def slave(timeout=6):
+ """read and decipher BLE payloads for `timeout` seconds."""
+ nrf.listen = True
+ end_timer = time.monotonic() + timeout
+ while time.monotonic() <= end_timer:
+ if nrf.available():
+ result = nrf.read()
+ print(
+ "recevied payload from MAC address",
+ address_repr(result.mac, delimit=":")
+ )
+ if result.name is not None:
+ print("\tdevice name:", result.name)
+ if result.pa_level is not None:
+ print("\tdevice transmitting PA Level:", result.pa_level, "dbm")
+ for service_data in result.data:
+ if isinstance(service_data, (bytearray, bytes)):
+ print("\traw buffer:", address_repr(service_data, False, " "))
+ else:
+ print("\t" + repr(service_data))
+
+
def set_role():
"""Set the role using stdin stream. Count arg for all functions can be
specified using a space delimiter (e.g. 'T 10' calls `send_temp(10)`)
-
- :return:
- - True when role is complete & app should continue running.
- - False when app should exit
"""
user_input = (
input(
@@ -145,28 +180,23 @@ def set_role():
" charge.\n"
"*** Enter 'T' to broadcast the device name & a temperature\n"
"*** Enter 'U' to broadcast a custom URL link\n"
+ "*** Enter 'R' to receive BLE payloads\n"
"*** Enter 'Q' to quit example.\n"
)
or "?"
)
user_input = user_input.split()
if user_input[0].upper().startswith("M"):
- if len(user_input) > 1:
- master(int(user_input[1]))
- else:
- master()
+ master(*[int(x) for x in user_input[1:]])
return True
if user_input[0].upper().startswith("T"):
- if len(user_input) > 1:
- send_temp(int(user_input[1]))
- else:
- send_temp()
+ send_temp(*[int(x) for x in user_input[1:2]])
return True
if user_input[0].upper().startswith("U"):
- if len(user_input) > 1:
- send_url(int(user_input[1]))
- else:
- send_url()
+ send_url(*[int(x) for x in user_input[1:2]])
+ return True
+ if user_input[0].upper().startswith("R"):
+ slave(*[int(x) for x in user_input[1:2]])
return True
if user_input[0].upper().startswith("Q"):
nrf.power = False
@@ -185,8 +215,7 @@ def set_role():
print(" Keyboard Interrupt detected. Powering down radio...")
nrf.power = False
else:
- print(
- " Run master() to broadcast the device name, pa_level, & battery "
- "charge\n Run send_temp() to broadcast the device name & a "
- "temperature\n Run send_url() to broadcast a custom URL link"
- )
+ print(" Run master() to broadcast the device name, pa_level, & battery charge")
+ print(" Run send_temp() to broadcast the device name & a temperature")
+ print(" Run send_url() to broadcast a custom URL link")
+ print(" Run slave() to receive BLE payloads.")
diff --git a/examples/nrf24l01_interrupt_test.py b/examples/nrf24l01_interrupt_test.py
index 31ca2e2..fe43f69 100644
--- a/examples/nrf24l01_interrupt_test.py
+++ b/examples/nrf24l01_interrupt_test.py
@@ -16,16 +16,16 @@
irq_pin = digitalio.DigitalInOut(board.D12)
irq_pin.switch_to_input() # make sure its an input object
# change these (digital output) pins accordingly
-ce = digitalio.DigitalInOut(board.D4)
-csn = digitalio.DigitalInOut(board.D5)
+CE_PIN = digitalio.DigitalInOut(board.D4)
+CSN_PIN = digitalio.DigitalInOut(board.D5)
# using board.SPI() automatically selects the MCU's
# available SPI pins, board.SCK, board.MOSI, board.MISO
-spi = board.SPI() # init spi bus object
+SPI_BUS = board.SPI() # init spi bus object
# we'll be using the dynamic payload size feature (enabled by default)
# initialize the nRF24L01 on the spi bus object
-nrf = RF24(spi, csn, ce)
+nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
# this example uses the ACK payload to trigger the IRQ pin active for
# the "on data received" event
@@ -55,18 +55,14 @@
def _ping_and_prompt():
"""transmit 1 payload, wait till irq_pin goes active, print IRQ status
flags."""
- ce.value = 1 # tell the nRF24L01 to prepare sending a single packet
+ nrf.ce_pin = 1 # tell the nRF24L01 to prepare sending a single packet
time.sleep(0.00001) # mandatory 10 microsecond pulse starts transmission
- ce.value = 0 # end 10 us pulse; use only 1 buffer from TX FIFO
+ nrf.ce_pin = 0 # end 10 us pulse; use only 1 buffer from TX FIFO
while irq_pin.value: # IRQ pin is active when LOW
pass
print("IRQ pin went active LOW.")
nrf.update() # update irq_d? status flags
- print(
- "\tirq_ds: {}, irq_dr: {}, irq_df: {}".format(
- nrf.irq_ds, nrf.irq_dr, nrf.irq_df
- )
- )
+ print(f"\tirq_ds: {nrf.irq_ds}, irq_dr: {nrf.irq_dr}, irq_df: {nrf.irq_df}")
def master():
@@ -84,22 +80,14 @@ def master():
nrf.interrupt_config(data_sent=False)
print(" Pinging slave node for an ACK payload...", end=" ")
_ping_and_prompt() # CE pin is managed by this function
- print(
- "\t'on data ready' event test{}successful".format(
- " " if nrf.irq_dr else " un"
- )
- )
+ print("\t\"on data ready\" event test {}successful".format("un" * nrf.irq_dr))
# on data sent test
print("\nConfiguring IRQ pin to only ignore 'on data ready' event")
nrf.interrupt_config(data_recv=False)
print(" Pinging slave node again... ", end=" ")
_ping_and_prompt() # CE pin is managed by this function
- print(
- "\t'on data sent' event test{}successful".format(
- " " if nrf.irq_ds else " un"
- )
- )
+ print("\t\"on data sent\" event test {}successful".format("un" * nrf.irq_ds))
# trigger slave node to exit by filling the slave node's RX FIFO
print("\nSending one extra payload to fill RX FIFO on slave node.")
@@ -108,10 +96,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.")
@@ -122,11 +107,7 @@ def master():
nrf.flush_tx() # just in case any previous tests failed
nrf.write(b"Dummy", write_only=True) # CE pin is left LOW
_ping_and_prompt() # CE pin is managed by this function
- print(
- "\t'on data failed' event test{}successful".format(
- " " if nrf.irq_df else " un"
- )
- )
+ print("\t\"on data failed\" event test {}successful".format("un" * nrf.irq_df))
nrf.flush_tx() # flush artifact payload in TX FIFO from last test
# all 3 ACK payloads received were 4 bytes each, and RX FIFO is full
# so, fetching 12 bytes from the RX FIFO also flushes RX FIFO
@@ -155,10 +136,6 @@ def slave(timeout=6): # will listen for 6 seconds before timing out
def set_role():
"""Set the role using stdin stream. Timeout arg for slave() can be
specified using a space delimiter (e.g. 'R 10' calls `slave(10)`)
-
- :return:
- - True when role is complete & app should continue running.
- - False when app should exit
"""
user_input = (
input(
@@ -170,10 +147,7 @@ def set_role():
)
user_input = user_input.split()
if user_input[0].upper().startswith("R"):
- if len(user_input) > 1:
- slave(int(user_input[1]))
- else:
- slave()
+ slave(*[int(x) for x in user_input[1:2]])
return True
if user_input[0].upper().startswith("T"):
master()
diff --git a/examples/nrf24l01_manual_ack_test.py b/examples/nrf24l01_manual_ack_test.py
index e61035d..14fb8d8 100644
--- a/examples/nrf24l01_manual_ack_test.py
+++ b/examples/nrf24l01_manual_ack_test.py
@@ -4,22 +4,37 @@
"""
import time
import board
-import digitalio
+from digitalio import DigitalInOut
# if running this on a ATSAMD21 M0 based board
# from circuitpython_nrf24l01.rf24_lite import RF24
from circuitpython_nrf24l01.rf24 import RF24
-# change these (digital output) pins accordingly
-ce = digitalio.DigitalInOut(board.D4)
-csn = digitalio.DigitalInOut(board.D5)
+# invalid default values for scoping
+SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
-# using board.SPI() automatically selects the MCU's
-# available SPI pins, board.SCK, board.MOSI, board.MISO
-spi = board.SPI() # init spi bus object
+try: # on Linux
+ import spidev
+
+ SPI_BUS = spidev.SpiDev() # for a faster interface on linux
+ CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
+ CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
+
+except ImportError: # on CircuitPython only
+ # using board.SPI() automatically selects the MCU's
+ # available SPI pins, board.SCK, board.MOSI, board.MISO
+ SPI_BUS = board.SPI() # init spi bus object
+
+ # change these (digital output) pins accordingly
+ CE_PIN = DigitalInOut(board.D4)
+ CSN_PIN = DigitalInOut(board.D5)
# initialize the nRF24L01 on the spi bus object
-nrf = RF24(spi, csn, ce)
+nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
+# On Linux, csn value is a bit coded
+# 0 = bus 0, CE0 # SPI bus 0 is enabled by default
+# 10 = bus 1, CE0 # enable SPI bus 2 prior to running this
+# 21 = bus 2, CE1 # enable SPI bus 1 prior to running this
# set the Power Amplifier level to -12 dBm since this test example is
# usually run with nRF24L01 transceivers in close proximity
@@ -72,12 +87,9 @@ def master(count=5): # count = 5 will only transmit 5 packets
nrf.listen = False # put the radio back in TX mode
end_timer = time.monotonic_ns() # stop timer
print(
- "Transmission successful! Time to transmit: "
- "{} us. Sent: {}{}".format(
- int((end_timer - start_timer) / 1000),
- buffer[:6].decode("utf-8"),
- counter[0],
- ),
+ "Transmission successful! Time to transmit:",
+ f"{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?
@@ -90,12 +102,8 @@ def master(count=5): # count = 5 will only transmit 5 packets
# save new counter from response
counter[0] = received[7:8][0]
print(
- "Receieved {} bytes with pipe {}: {}{}".format(
- length,
- pipe_number,
- bytes(received[:6]).decode("utf-8"), # convert to str
- counter[0],
- )
+ f"Receieved {length} bytes with pipe {pipe_number}:",
+ "{}{}".format(bytes(received[:6]).decode("utf-8"), counter[0]),
)
count -= 1
# make example readable in REPL by slowing down transmissions
@@ -122,13 +130,9 @@ def slave(timeout=6):
result = nrf.send(b"World \0" + bytes([counter[0]]))
nrf.listen = True # put the radio back in RX mode
print(
- "Received {} on pipe {}: {}{} Sent:".format(
- length,
- pipe,
- bytes(received[:6]).decode("utf-8"), # convert to str
- received[7:8][0],
- ),
- end=" ",
+ f"Received {length} on pipe {pipe}:",
+ "{}{}".format(bytes(received[:6]).decode("utf-8"), received[7:8][0]),
+ end=" Sent: ",
)
if not result:
print("Response failed or timed out")
@@ -143,10 +147,6 @@ def slave(timeout=6):
def set_role():
"""Set the role using stdin stream. Timeout arg for slave() can be
specified using a space delimiter (e.g. 'R 10' calls `slave(10)`)
-
- :return:
- - True when role is complete & app should continue running.
- - False when app should exit
"""
user_input = (
input(
@@ -158,16 +158,10 @@ def set_role():
)
user_input = user_input.split()
if user_input[0].upper().startswith("R"):
- if len(user_input) > 1:
- slave(int(user_input[1]))
- else:
- slave()
+ slave(*[int(x) for x in user_input[1:2]])
return True
if user_input[0].upper().startswith("T"):
- if len(user_input) > 1:
- master(int(user_input[1]))
- else:
- master()
+ master(*[int(x) for x in user_input[1:2]])
return True
if user_input[0].upper().startswith("Q"):
nrf.power = False
diff --git a/examples/nrf24l01_multiceiver_test.py b/examples/nrf24l01_multiceiver_test.py
index ef53305..c7f3d11 100644
--- a/examples/nrf24l01_multiceiver_test.py
+++ b/examples/nrf24l01_multiceiver_test.py
@@ -5,23 +5,38 @@
import time
import struct
import board
-import digitalio
+from digitalio import DigitalInOut
# if running this on a ATSAMD21 M0 based board
# from circuitpython_nrf24l01.rf24_lite import RF24
from circuitpython_nrf24l01.rf24 import RF24
-# change these (digital output) pins accordingly
-ce = digitalio.DigitalInOut(board.D4)
-csn = digitalio.DigitalInOut(board.D5)
+# invalid default values for scoping
+SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
+
+try: # on Linux
+ import spidev
+
+ SPI_BUS = spidev.SpiDev() # for a faster interface on linux
+ CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
+ CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
+
+except ImportError: # on CircuitPython only
+ # using board.SPI() automatically selects the MCU's
+ # available SPI pins, board.SCK, board.MOSI, board.MISO
+ SPI_BUS = board.SPI() # init spi bus object
+
+ # change these (digital output) pins accordingly
+ CE_PIN = DigitalInOut(board.D4)
+ CSN_PIN = DigitalInOut(board.D5)
-# using board.SPI() automatically selects the MCU's
-# available SPI pins, board.SCK, board.MOSI, board.MISO
-spi = board.SPI() # init spi bus object
-# we'll be using the dynamic payload size feature (enabled by default)
# initialize the nRF24L01 on the spi bus object
-nrf = RF24(spi, csn, ce)
+nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
+# On Linux, csn value is a bit coded
+# 0 = bus 0, CE0 # SPI bus 0 is enabled by default
+# 10 = bus 1, CE0 # enable SPI bus 2 prior to running this
+# 21 = bus 2, CE1 # enable SPI bus 1 prior to running this
# set the Power Amplifier level to -12 dBm since this test example is
# usually run with nRF24L01 transceivers in close proximity
@@ -56,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(" 1:
- base(int(user_input[1]))
- else:
- base()
+ base(*[int(x) for x in user_input[1:2]])
return True
if user_input[0].upper().startswith("T"):
- if len(user_input) > 2:
- node(int(user_input[1]), int(user_input[2]))
- elif len(user_input) > 1:
- node(int(user_input[1]))
- else:
- node()
+ node(*[int(x) for x in user_input[1:3]])
return True
if user_input[0].upper().startswith("Q"):
nrf.power = False
@@ -146,5 +147,6 @@ def set_role():
print(
" Run base() on the receiver\n "
"Run node(node_number) on a transmitter\n "
- "node()'s parameter, `node_number`, must be in range [0, 5]"""
+ "node()'s parameter, `node_number`, must be in range [0, 5]"
+ ""
)
diff --git a/examples/nrf24l01_network_test.py b/examples/nrf24l01_network_test.py
new file mode 100644
index 0000000..d6bc372
--- /dev/null
+++ b/examples/nrf24l01_network_test.py
@@ -0,0 +1,208 @@
+"""
+An all-purpose example of using the nRF24L01 transceiver in a network of nodes.
+"""
+import time
+import struct
+import board
+from digitalio import DigitalInOut
+from circuitpython_nrf24l01.network.constants import MAX_FRAG_SIZE, NETWORK_DEFAULT_ADDR
+
+IS_MESH = (
+ input(
+ " nrf24l01_network_test example\n"
+ "Would you like to run as a mesh network node (y/n)? Defaults to 'Y' "
+ ) or "Y"
+).upper().startswith("Y")
+
+# to use different addresses on a set of radios, we need a variable to
+# uniquely identify which address this radio will use
+THIS_NODE = 0
+print(
+ "Remember, the master node always uses `0` as the node_address and node_id."
+ "\nWhich node is this? Enter",
+ end=" ",
+)
+if IS_MESH:
+ # node_id must be less than 255
+ THIS_NODE = int(input("a unique int. Defaults to '0' ") or "0") & 0xFF
+else:
+ # logical node_address is in octal
+ THIS_NODE = int(input("an octal int. Defaults to '0' ") or "0", 8)
+
+if IS_MESH:
+ if THIS_NODE: # if this is not a mesh network master node
+ from circuitpython_nrf24l01.rf24_mesh import RF24MeshNoMaster as Network
+ else:
+ from circuitpython_nrf24l01.rf24_mesh import RF24Mesh as Network
+ print("Using RF24Mesh{} class".format("" if not THIS_NODE else "NoMaster"))
+else:
+ from circuitpython_nrf24l01.rf24_network import RF24Network as Network
+
+ # we need to construct frame headers for RF24Network.send()
+ from circuitpython_nrf24l01.network.structs import RF24NetworkHeader
+
+ # we need to construct entire frames for RF24Network.write() (not for this example)
+ # from circuitpython_nrf24l01.network.structs import RF24NetworkFrame
+ print("Using RF24Network class")
+
+# invalid default values for scoping
+SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
+
+try: # on Linux
+ import spidev
+
+ SPI_BUS = spidev.SpiDev() # for a faster interface on linux
+ CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
+ CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
+
+except ImportError: # on CircuitPython only
+ # using board.SPI() automatically selects the MCU's
+ # available SPI pins, board.SCK, board.MOSI, board.MISO
+ SPI_BUS = board.SPI() # init spi bus object
+
+ # change these (digital output) pins accordingly
+ CE_PIN = DigitalInOut(board.D4)
+ CSN_PIN = DigitalInOut(board.D5)
+
+
+# initialize this node as the network
+nrf = Network(SPI_BUS, CSN_PIN, CE_PIN, THIS_NODE)
+
+# TMRh20 examples use a channel 97 for RF24Mesh library
+# TMRh20 examples use a channel 90 for RF24Network library
+nrf.channel = 90 + IS_MESH * 7
+
+# set the Power Amplifier level to -12 dBm since this test example is
+# usually run with nRF24L01 transceivers in close proximity
+nrf.pa_level = -12
+
+# using the python keyword global is bad practice. Instead we'll use a 1 item
+# list to store our number of the payloads sent
+packets_sent = [0]
+
+if THIS_NODE: # if this node is not the network master node
+ if IS_MESH: # mesh nodes need to bond with the master node
+ print("Connecting to mesh network...", end=" ")
+
+ # get this node's assigned address and connect to network
+ if nrf.renew_address() is None:
+ print("failed. Please try again manually with `nrf.renew_address()`")
+ else:
+ print("assigned address:", oct(nrf.node_address))
+else:
+ print("Acting as network master node.")
+
+
+def emit(node=not THIS_NODE, frag=False, count=5, interval=1):
+ """Transmits 1 (or 2) integers or a large buffer
+
+ :param int node: The target node for network transmissions.
+ If using RF24Mesh, this is a unique node_id.
+ If using RF24Network, this is the node's logical address.
+ :param bool frag: Only use fragmented messages?
+ :param int count: The max number of messages to transmit.
+ :param int interval: time (in seconds) between transmitting messages.
+ """
+ failures = 0
+ start_timer = time.monotonic()
+ while failures < 6 and count:
+ nrf.update() # keep the network layer current
+ now = time.monotonic()
+ if now >= 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 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()
+ 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]])
+ return True
+ if user_input[0].upper().startswith("E"):
+ emit(*[int(x) for x in user_input[1:5]])
+ return True
+ if user_input[0].upper().startswith("Q"):
+ nrf.power = 0
+ return False
+ print(user_input[0], "is an unrecognized input. Please try again.")
+ return set_role()
+
+
+if __name__ == "__main__":
+
+ try:
+ while set_role():
+ pass # continue example until 'Q' is entered
+ except KeyboardInterrupt:
+ print(" Keyboard Interrupt detected. Powering down radio...")
+ nrf.power = 0
+elif nrf.node_address != NETWORK_DEFAULT_ADDR:
+ print(" Run emit() to transmit.")
+ print(" Run idle() to receive or forward messages in the network.")
+ print(" Pass keyword arg `frag=True` to emit() fragmented messages.")
diff --git a/examples/nrf24l01_scanner_test.py b/examples/nrf24l01_scanner_test.py
index 1e95ffd..0bef8c0 100644
--- a/examples/nrf24l01_scanner_test.py
+++ b/examples/nrf24l01_scanner_test.py
@@ -5,27 +5,50 @@
"""
import time
import board
-import digitalio
+from digitalio import DigitalInOut
# if running this on a ATSAMD21 M0 based board
# from circuitpython_nrf24l01.rf24_lite import RF24
-from circuitpython_nrf24l01.rf24 import RF24
+from circuitpython_nrf24l01.rf24 import RF24, address_repr
-# change these (digital output) pins accordingly
-ce = digitalio.DigitalInOut(board.D4)
-csn = digitalio.DigitalInOut(board.D5)
+# invalid default values for scoping
+SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
+
+try: # on Linux
+ import spidev
+
+ SPI_BUS = spidev.SpiDev() # for a faster interface on linux
+ CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
+ CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
+
+except ImportError: # on CircuitPython only
+ # using board.SPI() automatically selects the MCU's
+ # available SPI pins, board.SCK, board.MOSI, board.MISO
+ SPI_BUS = board.SPI() # init spi bus object
+
+ # change these (digital output) pins accordingly
+ CE_PIN = DigitalInOut(board.D4)
+ CSN_PIN = DigitalInOut(board.D5)
-# using board.SPI() automatically selects the MCU's
-# available SPI pins, board.SCK, board.MOSI, board.MISO
-spi = board.SPI() # init spi bus object
-# we'll be using the dynamic payload size feature (enabled by default)
# initialize the nRF24L01 on the spi bus object
-nrf = RF24(spi, csn, ce)
+nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
+# On Linux, csn value is a bit coded
+# 0 = bus 0, CE0 # SPI bus 0 is enabled by default
+# 10 = bus 1, CE0 # enable SPI bus 2 prior to running this
+# 21 = bus 2, CE1 # enable SPI bus 1 prior to running this
# turn off RX features specific to the nRF24L01 module
-nrf.auto_ack = 0
-nrf.dynamic_payloads = 0
+nrf.auto_ack = False
+nrf.dynamic_payloads = False
+nrf.crc = 0
+nrf.arc = 0
+nrf.allow_ask_no_ack = False
+
+# use reverse engineering tactics for a better "snapshot"
+nrf.address_length = 2
+nrf.open_rx_pipe(1, b"\0\x55")
+nrf.open_rx_pipe(0, b"\0\xAA")
def scan(timeout=30):
@@ -44,51 +67,71 @@ def scan(timeout=30):
print("\n" + "~" * 126)
signals = [0] * 126 # store the signal count for each channel
+ curr_channel = 0
start_timer = time.monotonic() # start the timer
while time.monotonic() - start_timer < timeout:
- for curr_channel in range(126): # for each channel
- nrf.channel = curr_channel
- time.sleep(0.00013) # let radio modulate to new channel
- nrf.listen = 1 # start a RX session
- time.sleep(0.00013) # wait 130 microseconds
- signals[curr_channel] += nrf.rpd # if interference is present
- nrf.listen = 0 # end the RX session
-
- # ouptut the signal counts per channel
- print(
- hex(min(0x0F, signals[curr_channel]))[2:]
- if signals[curr_channel]
- else "-",
- sep="",
- end="" if curr_channel < 125 else "\r",
- )
- # print results 1 last time to end with a new line
- for sig in signals:
- print(hex(min(0x0F, sig))[2:] if sig else "-", sep="", end="")
+ nrf.channel = curr_channel
+ if nrf.available():
+ nrf.flush_rx() # flush the RX FIFO because it asserts the RPD flag
+ nrf.listen = 1 # start a RX session
+ time.sleep(0.00013) # wait 130 microseconds
+ signals[curr_channel] += nrf.rpd # if interference is present
+ nrf.listen = 0 # end the RX session
+ curr_channel = curr_channel + 1 if curr_channel < 125 else 0
+
+ # ouptut the signal counts per channel
+ sig_cnt = signals[curr_channel]
+ print(
+ ("%X" % min(15, sig_cnt)) if sig_cnt else "-",
+ sep="",
+ end="" if curr_channel < 125 else "\r",
+ )
+ # finish printing results and end with a new line
+ while curr_channel < len(signals) - 1:
+ curr_channel += 1
+ sig_cnt = signals[curr_channel]
+ print(("%X" % min(15, sig_cnt)) if sig_cnt else "-", sep="", end="")
print("")
+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 channel: The specific channel to focus on. If not provided, then the
+ radio's current setting is used.
+ """
+ if channel is not None:
+ nrf.channel = channel
+ nrf.listen = True
+ timeout += time.monotonic()
+ while time.monotonic() < timeout:
+ signal = nrf.read()
+ if signal:
+ print(address_repr(signal, False, " "))
+ nrf.listen = False
+ while not nrf.fifo(False, True):
+ # dump the left overs in the RX FIFO
+ print(address_repr(nrf.read(), False, " "))
+
+
def set_role():
"""Set the role using stdin stream. Timeout arg for scan() can be
specified using a space delimiter (e.g. 'S 10' calls `scan(10)`)
-
- :return:
- - True when role is complete & app should continue running.
- - False when app should exit
"""
user_input = (
input(
"*** Enter 'S' to perform scan.\n"
+ "*** Enter 'N' to display noise.\n"
"*** Enter 'Q' to quit example.\n"
- )
- or "?"
+ ) or "?"
)
user_input = user_input.split()
if user_input[0].upper().startswith("S"):
- if len(user_input) > 1:
- scan(int(user_input[1]))
- else:
- scan()
+ scan(*[int(x) for x in user_input[1:2]])
+ return True
+ if user_input[0].upper().startswith("N"):
+ noise(*[int(x) for x in user_input[1:3]])
return True
if user_input[0].upper().startswith("Q"):
nrf.power = False
@@ -98,6 +141,10 @@ def set_role():
print(" nRF24L01 scanner test")
+print(
+ "!!!Make sure the terminal is wide enough for 126 characters on 1 line."
+ " If this line is wrapped, then the output will look bad!"
+)
if __name__ == "__main__":
try:
@@ -108,3 +155,4 @@ def set_role():
nrf.power = False
else:
print(" Run scan() to initiate scan for ambient signals.")
+ print(" Run noise() to display ambient signals' data (AKA noise).")
diff --git a/examples/nrf24l01_simple_test.py b/examples/nrf24l01_simple_test.py
index 152c14b..cd2617b 100644
--- a/examples/nrf24l01_simple_test.py
+++ b/examples/nrf24l01_simple_test.py
@@ -4,23 +4,38 @@
import time
import struct
import board
-import digitalio
+from digitalio import DigitalInOut
# if running this on a ATSAMD21 M0 based board
# from circuitpython_nrf24l01.rf24_lite import RF24
from circuitpython_nrf24l01.rf24 import RF24
-# change these (digital output) pins accordingly
-ce = digitalio.DigitalInOut(board.D4)
-csn = digitalio.DigitalInOut(board.D5)
+# invalid default values for scoping
+SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
+
+try: # on Linux
+ import spidev
+
+ SPI_BUS = spidev.SpiDev() # for a faster interface on linux
+ CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
+ CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
+
+except ImportError: # on CircuitPython only
+ # using board.SPI() automatically selects the MCU's
+ # available SPI pins, board.SCK, board.MOSI, board.MISO
+ SPI_BUS = board.SPI() # init spi bus object
+
+ # change these (digital output) pins accordingly
+ CE_PIN = DigitalInOut(board.D4)
+ CSN_PIN = DigitalInOut(board.D5)
-# using board.SPI() automatically selects the MCU's
-# available SPI pins, board.SCK, board.MOSI, board.MISO
-spi = board.SPI() # init spi bus object
-# we'll be using the dynamic payload size feature (enabled by default)
# initialize the nRF24L01 on the spi bus object
-nrf = RF24(spi, csn, ce)
+nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
+# On Linux, csn value is a bit coded
+# 0 = bus 0, CE0 # SPI bus 0 is enabled by default
+# 10 = bus 1, CE0 # enable SPI bus 2 prior to running this
+# 21 = bus 2, CE1 # enable SPI bus 1 prior to running this
# set the Power Amplifier level to -12 dBm since this test example is
# usually run with nRF24L01 transceivers in close proximity
@@ -68,10 +83,8 @@ def master(count=5): # count = 5 will only transmit 5 packets
print("send() failed or timed out")
else:
print(
- "Transmission successful! Time to Transmit: "
- "{} us. Sent: {}".format(
- (end_timer - start_timer) / 1000, payload[0]
- )
+ "Transmission successful! Time to Transmit:",
+ f"{(end_timer - start_timer) / 1000} us. Sent: {payload[0]}"
)
payload[0] += 0.01
time.sleep(1)
@@ -94,11 +107,7 @@ def slave(timeout=6):
# buffer[:4] truncates padded 0s if dynamic payloads are disabled
payload[0] = struct.unpack(" 1:
- slave(int(user_input[1]))
- else:
- slave()
+ slave(*[int(x) for x in user_input[1:2]])
return True
if user_input[0].upper().startswith("T"):
- if len(user_input) > 1:
- master(int(user_input[1]))
- else:
- master()
+ master(*[int(x) for x in user_input[1:2]])
return True
if user_input[0].upper().startswith("Q"):
nrf.power = False
diff --git a/examples/nrf24l01_stream_test.py b/examples/nrf24l01_stream_test.py
index c47cbf0..12854a0 100644
--- a/examples/nrf24l01_stream_test.py
+++ b/examples/nrf24l01_stream_test.py
@@ -3,23 +3,38 @@
"""
import time
import board
-import digitalio
+from digitalio import DigitalInOut
# if running this on a ATSAMD21 M0 based board
# from circuitpython_nrf24l01.rf24_lite import RF24
from circuitpython_nrf24l01.rf24 import RF24
-# change these (digital output) pins accordingly
-ce = digitalio.DigitalInOut(board.D4)
-csn = digitalio.DigitalInOut(board.D5)
+# invalid default values for scoping
+SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
+
+try: # on Linux
+ import spidev
+
+ SPI_BUS = spidev.SpiDev() # for a faster interface on linux
+ CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
+ CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
+
+except ImportError: # on CircuitPython only
+ # using board.SPI() automatically selects the MCU's
+ # available SPI pins, board.SCK, board.MOSI, board.MISO
+ SPI_BUS = board.SPI() # init spi bus object
+
+ # change these (digital output) pins accordingly
+ CE_PIN = DigitalInOut(board.D4)
+ CSN_PIN = DigitalInOut(board.D5)
-# using board.SPI() automatically selects the MCU's
-# available SPI pins, board.SCK, board.MOSI, board.MISO
-spi = board.SPI() # init spi bus object
-# we'll be using the dynamic payload size feature (enabled by default)
# initialize the nRF24L01 on the spi bus object
-nrf = RF24(spi, csn, ce)
+nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
+# On Linux, csn value is a bit coded
+# 0 = bus 0, CE0 # SPI bus 0 is enabled by default
+# 10 = bus 1, CE0 # enable SPI bus 2 prior to running this
+# 21 = bus 2, CE1 # enable SPI bus 1 prior to running this
# set the Power Amplifier level to -12 dBm since this test example is
# usually run with nRF24L01 transceivers in close proximity
@@ -43,7 +58,7 @@
# uncomment the following 2 lines for compatibility with TMRh20 library
# nrf.allow_ask_no_ack = False
-# nrf.dynamic_payloads = False
+nrf.dynamic_payloads = False
def make_buffers(size=32):
@@ -79,37 +94,35 @@ def master(count=1, size=32): # count = 5 will transmit the list 5 times
for r in result: # tally the resulting success rate
successful += 1 if r else 0
print(
- "successfully sent {}% ({}/{})".format(
- successful / (size * count) * 100, successful, size * count
- )
+ f"successfully sent {successful / (size * count) * 100}%",
+ f"({successful}/{size * count})"
)
def master_fifo(count=1, size=32):
"""Similar to the `master()` above except this function uses the full
TX FIFO and `RF24.write()` instead of `RF24.send()`"""
- if size < 6:
- print("setting size to 6;", size, "is not allowed for this test.")
- size = 6
buf = make_buffers(size) # make a list of payloads
nrf.listen = False # ensures the nRF24L01 is in TX mode
- for c in range(count): # transmit the same payloads this many times
+ for cnt in range(count): # transmit the same payloads this many times
nrf.flush_tx() # clear the TX FIFO so we can use all 3 levels
# NOTE the write_only parameter does not initiate sending
buf_iter = 0 # iterator of payloads for the while loop
failures = 0 # keep track of manual retries
start_timer = time.monotonic_ns() # start timer
while buf_iter < size: # cycle through all the payloads
+ nrf.ce_pin = False
while buf_iter < size and nrf.write(buf[buf_iter], write_only=1):
# NOTE write() returns False if TX FIFO is already full
buf_iter += 1 # increment iterator of payloads
- ce.value = True # start tranmission (after 10 microseconds)
+ nrf.ce_pin = True
while not nrf.fifo(True, True): # updates irq_df flag
if nrf.irq_df:
# reception failed; we need to reset the irq_rf flag
- ce.value = False # fall back to Standby-I mode
+ nrf.ce_pin = False # fall back to Standby-I mode
failures += 1 # increment manual retries
- if failures > 99 and buf_iter < 7 and c < 2:
+ nrf.clear_status_flags() # clear the irq_df flag
+ if failures > 99 and buf_iter < 7 and cnt < 2:
# we need to prevent an infinite loop
print(
"Make sure slave() node is listening."
@@ -117,26 +130,14 @@ def master_fifo(count=1, size=32):
)
buf_iter = size + 1 # be sure to exit the while loop
nrf.flush_tx() # discard all payloads in TX FIFO
- break
- nrf.clear_status_flags() # clear the irq_df flag
- ce.value = True # start re-transmitting
- ce.value = False
+ else:
+ nrf.ce_pin = True # start re-transmitting
+ nrf.ce_pin = False
end_timer = time.monotonic_ns() # end timer
print(
- "Transmission took {} us with {} failures detected.".format(
- (end_timer - start_timer) / 1000, failures
- ),
- end=" " if failures < 100 else "\n",
+ f"Transmission took {(end_timer - start_timer) / 1000} us",
+ f"with {failures} failures detected."
)
- if 1 <= failures < 100:
- print(
- "\n\nHINT: Try playing with the 'ard' and 'arc' attributes to"
- " reduce the number of\nfailures detected. Tests were better"
- " with these attributes at higher values, but\nnotice the "
- "transmission time differences."
- )
- elif not failures:
- print("You Win!")
def slave(timeout=5):
@@ -149,7 +150,7 @@ def slave(timeout=5):
count += 1
# retreive the received packet's payload
buffer = nrf.read() # clears flags & empties RX FIFO
- print("Received: {} - {}".format(buffer, count))
+ print(f"Received: {buffer} - {count}")
start_timer = time.monotonic() # reset timer on every RX payload
# recommended behavior is to keep in TX mode while idle
@@ -159,44 +160,25 @@ def slave(timeout=5):
def set_role():
"""Set the role using stdin stream. Timeout arg for slave() can be
specified using a space delimiter (e.g. 'R 10' calls `slave(10)`)
-
- :return:
- - True when role is complete & app should continue running.
- - False when app should exit
"""
user_input = (
input(
"*** Enter 'R' for receiver role.\n"
- "*** Enter 'T' for transmitter role (using 1 level"
- " of the TX FIFO).\n"
- "*** Enter 'F' for transmitter role (using all 3 levels"
- " of the TX FIFO).\n"
+ "*** Enter 'T' for transmitter role (using 1 level of the TX FIFO).\n"
+ "*** Enter 'F' for transmitter role (using all 3 levels of the TX FIFO).\n"
"*** Enter 'Q' to quit example.\n"
)
or "?"
)
user_input = user_input.split()
if user_input[0].upper().startswith("R"):
- if len(user_input) > 1:
- slave(int(user_input[1]))
- else:
- slave()
+ slave(*[int(x) for x in user_input[1:2]])
return True
if user_input[0].upper().startswith("T"):
- if len(user_input) > 2:
- master(int(user_input[1]), int(user_input[2]))
- elif len(user_input) > 1:
- master(int(user_input[1]))
- else:
- master()
+ master(*[int(x) for x in user_input[1:3]])
return True
if user_input[0].upper().startswith("F"):
- if len(user_input) > 2:
- master_fifo(int(user_input[1]), int(user_input[2]))
- elif len(user_input) > 1:
- master_fifo(int(user_input[1]))
- else:
- master_fifo()
+ master_fifo(*[int(x) for x in user_input[1:3]])
return True
if user_input[0].upper().startswith("Q"):
nrf.power = False
@@ -215,8 +197,6 @@ def set_role():
print(" Keyboard Interrupt detected. Powering down radio...")
nrf.power = False
else:
- print(
- " Run slave() on receiver\n Run master() on transmitter to use"
- " 1 level of the TX FIFO\n Run master_fifo() on transmitter to use"
- " all 3 levels of the TX FIFO"
- )
+ print(" Run slave() on receiver")
+ print(" Run master() on transmitter to use 1 level of the TX FIFO")
+ print(" Run master_fifo() on transmitter to use all 3 levels of the TX FIFO")
diff --git a/requirements.txt b/requirements.txt
index 869ca63..6e7dd3f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,3 @@
Adafruit-Blinka
adafruit-circuitpython-busdevice
+spidev; sys_platform == 'linux'
diff --git a/setup.py b/setup.py
index aa2fd04..8875db3 100644
--- a/setup.py
+++ b/setup.py
@@ -4,29 +4,32 @@
https://packaging.python.org/en/latest/distributing.html
https://github.com/pypa/sampleproject
"""
-from os import path
-from codecs import open # To use a consistent encoding
-from setuptools import setup
+import os
+from codecs import open as open_codec # To use a consistent encoding
+from setuptools import setup, find_packages
-ROOT_DIR = path.abspath(path.dirname(__file__))
-REPO = "https://github.com/2bndy5/CircuitPython_nRF24L01"
+ROOT_DIR = os.path.abspath(os.path.dirname(__file__))
+REPO = "https://github.com/nRF24/CircuitPython_nRF24L01"
+DEPS = ["adafruit-blinka", "adafruit-circuitpython-busdevice"]
+if os.name == "posix":
+ DEPS.append("spidev")
# Get the long description from the README file
-with open(path.join(ROOT_DIR, "README.rst"), encoding="utf-8") as f:
+with open_codec(os.path.join(ROOT_DIR, "README.rst"), encoding="utf-8") as f:
long_description = f.read()
setup(
name="circuitpython-nrf24l01",
use_scm_version=True,
setup_requires=["setuptools_scm"],
- python_requires='>=3.7',
+ python_requires=">=3.7",
description="Circuitpython driver library for the nRF24L01 transceiver",
long_description=long_description,
long_description_content_type="text/x-rst",
author="Brendan Doherty",
author_email="2bndy5@gmail.com",
- install_requires=["Adafruit-Blinka", "adafruit-circuitpython-busdevice"],
+ install_requires=DEPS,
license="MIT",
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[
@@ -46,7 +49,7 @@
# simple. Or you can use find_packages().
# TODO: IF LIBRARY FILES ARE A PACKAGE FOLDER,
# CHANGE `py_modules=['...']` TO `packages=['...']`
- packages=["circuitpython_nrf24l01"],
+ packages=find_packages(),
# Specifiy your homepage URL for your project here
url=REPO,
# Extra links for the sidebar on pypi