Skip to content

Commit c9be381

Browse files
committed
ported recent updates to pure python
1 parent ebbdece commit c9be381

10 files changed

Lines changed: 130 additions & 74 deletions

File tree

.cspell.config.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
version: "0.2"
2+
words:
3+
- abled
4+
- adafruit
5+
- addopts
6+
- armv
7+
- baudrate
8+
- blinka
9+
- busdevice
10+
- busio
11+
- capsys
12+
- circuitpython
13+
- digitalio
14+
- Disabl
15+
- Doherty
16+
- elif
17+
- Fals
18+
- hexlified
19+
- HHHBB
20+
- Kbps
21+
- LENG
22+
- levelname
23+
- Mbps
24+
- micropython
25+
- minversion
26+
- MOSI
27+
- multicasted
28+
- multicasting
29+
- multicasts
30+
- mypy
31+
- pytest
32+
- raspberrypi
33+
- setuptools
34+
- spidev
35+
- testpaths

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,22 @@
44

55
repos:
66
- repo: https://github.com/pre-commit/pre-commit-hooks
7-
rev: v4.6.0
7+
rev: v5.0.0
88
hooks:
99
- id: check-yaml
1010
exclude: ^docs/social_cards/layouts
1111
- id: end-of-file-fixer
1212
- id: trailing-whitespace
1313
- repo: https://github.com/astral-sh/ruff-pre-commit
14-
rev: v0.4.8
14+
rev: v0.11.8
1515
hooks:
1616
# Run the linter.
1717
- id: ruff
1818
args: [ --fix ]
1919
# Run the formatter.
2020
- id: ruff-format
2121
- repo: https://github.com/pre-commit/mirrors-mypy
22-
rev: v1.10.0
22+
rev: v1.15.0
2323
hooks:
2424
- id: mypy
2525
name: mypy (library code)

circuitpython_nrf24l01/network/constants.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,11 @@
7373
MSG_FRAG_MORE = const(149)
7474
#: Used to indicate the last frame of a fragmented message.
7575
MSG_FRAG_LAST = const(150)
76+
77+
# error types used in `header.reserved` attribute
78+
NETWORK_OVERRUN = const(160)
79+
"""Used to indicate that the network was overrun and `RF24Network.available()`
80+
had to exit the internal loop when processing the radio's RX FIFO.
81+
"""
82+
#: Used to indicate the radio's RX FIFO had somehow been corrupted.
83+
NETWORK_CORRUPTION = const(161)

circuitpython_nrf24l01/network/mixins.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@
3636
MSG_FRAG_FIRST,
3737
MSG_FRAG_MORE,
3838
MSG_FRAG_LAST,
39+
NETWORK_CORRUPTION,
3940
NETWORK_DEFAULT_ADDR,
4041
MESH_ADDR_RESPONSE,
4142
MESH_ADDR_REQUEST,
4243
NETWORK_ACK,
4344
NETWORK_EXT_DATA,
45+
NETWORK_OVERRUN,
4446
NETWORK_PING,
4547
NETWORK_POLL,
4648
TX_ROUTED,
@@ -339,15 +341,18 @@ def _pipe_address(self, node_addr: int, pipe_number: int) -> bytearray:
339341
def _net_update(self) -> int:
340342
"""keep the network layer current; returns the received message type"""
341343
ret_val = 0 # sentinel indicating there is nothing to report
344+
timeout = time.monotonic_ns() + 100000000
342345
while True:
346+
if time.monotonic_ns() > timeout:
347+
return NETWORK_OVERRUN
343348
temp_buf = self._rf24.read()
344349
if temp_buf is None:
345350
return ret_val
346-
if (
347-
not self.frame_buf.unpack(temp_buf)
348-
or not is_address_valid(self.frame_buf.header.to_node)
349-
or not is_address_valid(self.frame_buf.header.from_node)
350-
):
351+
if not self.frame_buf.unpack(temp_buf):
352+
return NETWORK_CORRUPTION
353+
if not is_address_valid(
354+
self.frame_buf.header.to_node
355+
) or not is_address_valid(self.frame_buf.header.from_node):
351356
# print("discarding frame due to invalid network addresses.")
352357
continue
353358

@@ -483,7 +488,9 @@ def _write(self, write_direct: int, send_type: int) -> bool:
483488
"""entry point for transmitting the current frame_buf"""
484489
is_ack_t = self.frame_buf.is_ack_type()
485490

486-
to_node, to_pipe, is_multicast = self._logi_2_phys(write_direct, send_type)
491+
to_node, to_pipe, is_multicast = self._logical_2_physical(
492+
write_direct, send_type
493+
)
487494

488495
if send_type == TX_ROUTED and write_direct == to_node and is_ack_t:
489496
time.sleep(0.002)
@@ -501,7 +508,7 @@ def _write(self, write_direct: int, send_type: int) -> bool:
501508
):
502509
self.frame_buf.header.message_type = NETWORK_ACK
503510
self.frame_buf.header.to_node = self.frame_buf.header.from_node
504-
ack_to_node, ack_to_pipe, is_multicast = self._logi_2_phys(
511+
ack_to_node, ack_to_pipe, is_multicast = self._logical_2_physical(
505512
self.frame_buf.header.from_node, TX_ROUTED
506513
)
507514
# ack_ok =
@@ -593,7 +600,7 @@ def _tx_standby(self, delta_time: int) -> bool:
593600
result = self._rf24.resend(send_only=True)
594601
return result
595602

596-
def _logi_2_phys(
603+
def _logical_2_physical(
597604
self, to_node: int, send_type: int, is_multicast: bool = False
598605
) -> Tuple[int, int, bool]:
599606
"""translate msg route into node address, pipe number, & multicast flag."""

circuitpython_nrf24l01/rf24.py

Lines changed: 50 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
try:
2828
from typing import Union, Sequence, Optional, List, Tuple
29-
from typing_extensions import Literal
29+
from typing_extensions import Literal # type: ignore
3030
except ImportError:
3131
pass
3232
from micropython import const
@@ -90,7 +90,11 @@ def __init__(
9090
# non-plus variants
9191
self._open_pipes, self._is_plus_variant = (0, False) # close all RX pipes
9292
self._features = self._reg_read(TX_FEATURE)
93-
self._reg_write(0x50, 0x73) # derelict command toggles TX_FEATURE register
93+
# invoke derelict command toggles TX_FEATURE register (test for plus variant)
94+
self._out[0] = 0x50
95+
self._out[1] = 0x73
96+
with self._spi as _spi:
97+
_spi.write_readinto(self._out, self._in, in_end=2, out_end=2)
9498
after_toggle = self._reg_read(TX_FEATURE)
9599
if self._features == after_toggle:
96100
self._is_plus_variant = True
@@ -102,7 +106,7 @@ def __init__(
102106
self._features = 5
103107
# init shadow copy of last RX_ADDR_P0 written to pipe 0 needed as
104108
# open_tx_pipe() appropriates pipe 0 for ACK packet
105-
self._pipe0_read_addr: Optional[Union[bytes, bytearray]] = None
109+
self._is_p0_rx: bool = False
106110
# shadow copy of the TX_ADDRESS
107111
self._tx_address = self._reg_read_bytes(TX_ADDRESS)
108112
# pre-configure the SETUP_RETR register
@@ -158,13 +162,19 @@ def ce_pin(self) -> bool:
158162
def ce_pin(self, val: bool):
159163
self._ce_pin.value = val
160164

161-
def _reg_read(self, reg: int) -> int:
165+
def _reg_read(self, reg: int, command: bool = False) -> int:
162166
self._out[0] = reg
167+
len = int(not command) + 1
163168
with self._spi as spi:
164169
# time.sleep(0.000005)
165-
spi.write_readinto(self._out, self._in, out_end=2, in_end=2)
166-
# print("SPI read 1 byte from", ("%02X" % reg), ("%02X" % self._in[1]))
167-
return self._in[1]
170+
spi.write_readinto(self._out, self._in, out_end=len, in_end=len)
171+
# if command:
172+
# print("SPI command", ("%02X" % reg))
173+
# else:
174+
# print(
175+
# "SPI read", len, "byte from", ("%02X" % reg), ("%02X" % self._in[1])
176+
# )
177+
return self._in[not command]
168178

169179
def _reg_read_bytes(self, reg: int, buf_len: int = 5) -> bytearray:
170180
self._out[0] = reg
@@ -188,21 +198,15 @@ def _reg_write_bytes(self, reg: int, out_buf: Union[bytes, bytearray]):
188198
# buf_len - 1, ("%02X" % reg), address_repr(self._out[1 : buf_len], 0)
189199
# ))
190200

191-
def _reg_write(self, reg: int, value: Optional[int] = None):
192-
self._out[0] = reg
193-
buf_len = 1
194-
if value is not None:
195-
self._out[0] = (0x20 if reg != 0x50 else 0) | reg
196-
self._out[1] = value
197-
buf_len += 1
201+
def _reg_write(self, reg: int, value: int):
202+
self._out[0] = 0x20 | reg
203+
self._out[1] = value
204+
buf_len = 2
198205
with self._spi as spi:
199206
# time.sleep(0.000005)
200207
spi.write_readinto(self._out, self._in, out_end=buf_len, in_end=buf_len)
201208
# if reg != 0xFF:
202-
# print(
203-
# "SPI write", "command" if value is None else "1 byte to",
204-
# ("%02X" % reg), "" if value is None else ("%02X" % value)
205-
# )
209+
# print("SPI write 1 byte to", ("%02X" % reg), ("%02X" % value))
206210

207211
@property
208212
def address_length(self) -> int:
@@ -217,21 +221,20 @@ def address_length(self, length: int):
217221

218222
def open_tx_pipe(self, address: Union[bytes, bytearray]) -> None:
219223
"""Open a data pipe for TX transmissions."""
220-
if self._pipe0_read_addr != address and self._aa & 1:
221-
for i, val in enumerate(address):
222-
self._pipes[0][i] = val # type: ignore[assignment, index]
223-
self._reg_write_bytes(RX_ADDR_P0, address)
224-
for i, val in enumerate(address):
225-
self._tx_address[i] = val
226-
self._reg_write_bytes(TX_ADDRESS, address)
224+
addr_len = min(len(address), self._addr_len)
225+
addr = address[:addr_len]
226+
self._tx_address[:addr_len] = addr
227+
if self._config & 1 == 0 and self._aa & 1:
228+
self._reg_write_bytes(RX_ADDR_P0, addr)
229+
self._reg_write_bytes(TX_ADDRESS, addr)
227230

228231
def close_rx_pipe(self, pipe_number: int) -> None:
229232
"""Close a specific data pipe from RX transmissions."""
230233
if pipe_number < 0 or pipe_number > 5:
231234
raise IndexError("pipe number must be in range [0, 5]")
232235
self._open_pipes = self._reg_read(OPEN_PIPES) & ~(1 << pipe_number)
233236
if not pipe_number:
234-
self._pipe0_read_addr = None
237+
self._is_p0_rx = False
235238
self._reg_write(OPEN_PIPES, self._open_pipes)
236239

237240
def open_rx_pipe(self, pipe_number: int, address: Union[bytes, bytearray]) -> None:
@@ -240,22 +243,24 @@ def open_rx_pipe(self, pipe_number: int, address: Union[bytes, bytearray]) -> No
240243
raise IndexError("pipe number must be in range [0, 5]")
241244
if not address:
242245
raise ValueError("address length cannot be 0")
246+
addr_len = min(len(address), self._addr_len)
247+
addr = address[:addr_len]
243248
if pipe_number < 2:
244249
if not pipe_number:
245-
self._pipe0_read_addr = address
246-
for i, val in enumerate(address):
247-
self._pipes[pipe_number][i] = val # type: ignore[assignment, index]
248-
self._reg_write_bytes(RX_ADDR_P0 + pipe_number, address)
250+
self._is_p0_rx = True
251+
self._pipes[pipe_number][:addr_len] = addr # type: ignore[assignment, index]
252+
if self._config & 1 or pipe_number != 0:
253+
self._reg_write_bytes(RX_ADDR_P0 + pipe_number, addr)
249254
else:
250-
self._pipes[pipe_number] = address[0]
255+
self._pipes[pipe_number] = addr[0]
251256
self._reg_write(RX_ADDR_P0 + pipe_number, address[0])
252257
self._open_pipes = self._reg_read(OPEN_PIPES) | (1 << pipe_number)
253258
self._reg_write(OPEN_PIPES, self._open_pipes)
254259

255260
@property
256261
def listen(self) -> bool:
257262
"""This attribute is the primary role as a radio."""
258-
return self.power and bool(self._config & 1)
263+
return self._config & 3 == 3
259264

260265
@listen.setter
261266
def listen(self, is_rx: bool):
@@ -265,22 +270,19 @@ def listen(self, is_rx: bool):
265270
start_timer = time.monotonic_ns()
266271
if is_rx:
267272
self._ce_pin.value = True
268-
if (
269-
self._pipe0_read_addr is not None
270-
and self._pipe0_read_addr != self.address(0)
271-
):
272-
for i, val in enumerate(self._pipe0_read_addr):
273-
self._pipes[0][i] = val # type: ignore[index]
274-
self._reg_write_bytes(RX_ADDR_P0, self._pipe0_read_addr)
275-
elif self._pipe0_read_addr is None and self._open_pipes & 1:
273+
if self._is_p0_rx:
274+
self._reg_write_bytes(RX_ADDR_P0, self._pipes[0][: self._addr_len]) # type: ignore[index]
275+
elif self._open_pipes & 1:
276276
self._open_pipes &= 0x3E # close_rx_pipe(0) is slower
277277
self._reg_write(OPEN_PIPES, self._open_pipes)
278278
else:
279-
if self._features & 6 == 6 and ((self._aa & self._dyn_pl) & 1):
279+
if self._features & 6 == 6:
280280
self.flush_tx()
281-
if self._aa & 1 and not self._open_pipes & 1:
282-
self._open_pipes |= 1
283-
self._reg_write(OPEN_PIPES, self._open_pipes)
281+
if self._is_p0_rx and self._aa & 1:
282+
self._reg_write_bytes(RX_ADDR_P0, self._tx_address[: self._addr_len])
283+
if not self._open_pipes & 1:
284+
self._open_pipes |= 1
285+
self._reg_write(OPEN_PIPES, self._open_pipes)
284286
# mandatory wait time is 130 µs
285287
delta_time = time.monotonic_ns() - start_timer
286288
if delta_time < 150000:
@@ -372,7 +374,7 @@ def irq_df(self) -> bool:
372374

373375
def update(self) -> Literal[True]:
374376
"""This function gets an updated status byte over SPI."""
375-
self._reg_write(0xFF)
377+
self._reg_read(0xFF, command=True)
376378
return True
377379

378380
def clear_status_flags(
@@ -800,7 +802,7 @@ def resend(self, send_only: bool = False):
800802
if not send_only and (self._in[0] >> 1) < 6:
801803
self.flush_rx()
802804
self.clear_status_flags()
803-
# self._reg_write(0xE3)
805+
# self._reg_read(0xE3, command=True)
804806
up_cnt = 0
805807
self._ce_pin.value = True
806808
while not self._in[0] & 0x30:
@@ -839,11 +841,11 @@ def write(
839841

840842
def flush_rx(self):
841843
"""Flush all 3 levels of the RX FIFO."""
842-
self._reg_write(0xE2)
844+
self._reg_read(0xE2, command=True)
843845

844846
def flush_tx(self):
845847
"""Flush all 3 levels of the TX FIFO."""
846-
self._reg_write(0xE1)
848+
self._reg_read(0xE1, command=True)
847849

848850
def fifo(self, about_tx: bool = False, check_empty: Optional[bool] = None):
849851
"""This provides the status of the TX/RX FIFO buffers. (read-only)"""

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
# General information about the project.
4545
project = "CircuitPython nRF24L01"
4646
author = "Brendan Doherty"
47-
copyright = f'{time.strftime("%Y", time.localtime())} {author}'
47+
copyright = f"{time.strftime('%Y', time.localtime())} {author}"
4848

4949
# The version info for the project you're documenting, acts as replacement for
5050
# |version| and |release|, also used in various other places throughout the

examples/nrf24l01_interrupt_test.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,7 @@ def set_role():
165165

166166

167167
print(
168-
" nRF24L01 Interrupt pin test\n"
169-
" Make sure the IRQ pin is connected to the MCU"
168+
" nRF24L01 Interrupt pin test\n Make sure the IRQ pin is connected to the MCU"
170169
)
171170

172171
if __name__ == "__main__":

examples/nrf24l01_stream_test.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,7 @@ def master_fifo(count=1, size=32):
126126
if failures > 99 and buf_iter < 7 and cnt < 2:
127127
# we need to prevent an infinite loop
128128
print(
129-
"Make sure slave() node is listening."
130-
" Quiting master_fifo()"
129+
"Make sure slave() node is listening. Quiting master_fifo()"
131130
)
132131
buf_iter = size + 1 # be sure to exit the while loop
133132
nrf.flush_tx() # discard all payloads in TX FIFO

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ requires = [
1212
[project]
1313
name = "circuitpython-nrf24l01"
1414
requires-python = ">=3.7"
15-
description = "Circuitpython driver library for the nRF24L01 transceiver"
15+
description = "CircuitPython driver library for the nRF24L01 transceiver"
1616
readme = "README.rst"
1717
authors = [
1818
{name = "Brendan Doherty", email = "2bndy5@gmail.com"}

0 commit comments

Comments
 (0)