adafruit-circuitpython-rfm 1.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- adafruit_circuitpython_rfm-1.0.4.dist-info/LICENSE +21 -0
- adafruit_circuitpython_rfm-1.0.4.dist-info/METADATA +128 -0
- adafruit_circuitpython_rfm-1.0.4.dist-info/RECORD +10 -0
- adafruit_circuitpython_rfm-1.0.4.dist-info/WHEEL +5 -0
- adafruit_circuitpython_rfm-1.0.4.dist-info/top_level.txt +1 -0
- adafruit_rfm/__init__.py +0 -0
- adafruit_rfm/rfm69.py +651 -0
- adafruit_rfm/rfm9x.py +534 -0
- adafruit_rfm/rfm9xfsk.py +575 -0
- adafruit_rfm/rfm_common.py +557 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2024 Jerry Needell for Adafruit Industries
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
* Author(s): Jerry Needell
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import random
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
from adafruit_bus_device import spi_device
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from typing import Callable, Optional, Type
|
|
18
|
+
|
|
19
|
+
import busio
|
|
20
|
+
import digitalio
|
|
21
|
+
from circuitpython_typing import ReadableBuffer, WriteableBuffer
|
|
22
|
+
|
|
23
|
+
except ImportError:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
from micropython import const
|
|
27
|
+
|
|
28
|
+
HAS_SUPERVISOR = False
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
import supervisor
|
|
32
|
+
|
|
33
|
+
if hasattr(supervisor, "ticks_ms"):
|
|
34
|
+
HAS_SUPERVISOR = True
|
|
35
|
+
except ImportError:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__version__ = "1.0.4"
|
|
40
|
+
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_RFM.git"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# RadioHead specific compatibility constants.
|
|
44
|
+
_RH_BROADCAST_ADDRESS = const(0xFF)
|
|
45
|
+
|
|
46
|
+
# The acknowledgement bit in the FLAGS
|
|
47
|
+
# The top 4 bits of the flags are reserved for RadioHead. The lower 4 bits are reserved
|
|
48
|
+
# for application layer use.
|
|
49
|
+
_RH_FLAGS_ACK = const(0x80)
|
|
50
|
+
_RH_FLAGS_RETRY = const(0x40)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# supervisor.ticks_ms() contants
|
|
54
|
+
_TICKS_PERIOD = const(1 << 29)
|
|
55
|
+
_TICKS_MAX = const(_TICKS_PERIOD - 1)
|
|
56
|
+
_TICKS_HALFPERIOD = const(_TICKS_PERIOD // 2)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def ticks_diff(ticks1: int, ticks2: int) -> int:
|
|
60
|
+
"""Compute the signed difference between two ticks values
|
|
61
|
+
assuming that they are within 2**28 ticks
|
|
62
|
+
"""
|
|
63
|
+
diff = (ticks1 - ticks2) & _TICKS_MAX
|
|
64
|
+
diff = ((diff + _TICKS_HALFPERIOD) & _TICKS_MAX) - _TICKS_HALFPERIOD
|
|
65
|
+
return diff
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def asyncio_to_blocking(function):
|
|
69
|
+
"""run async function as normal blocking function"""
|
|
70
|
+
|
|
71
|
+
def blocking_function(self, *args, **kwargs):
|
|
72
|
+
return asyncio.run(function(self, *args, **kwargs))
|
|
73
|
+
|
|
74
|
+
return blocking_function
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def asyncio_check_timeout(flag: Callable, limit: float, timeout_poll: float) -> bool:
|
|
78
|
+
"""test for timeout waiting for specified flag"""
|
|
79
|
+
timed_out = False
|
|
80
|
+
if HAS_SUPERVISOR:
|
|
81
|
+
start = supervisor.ticks_ms()
|
|
82
|
+
while not timed_out and not flag():
|
|
83
|
+
if ticks_diff(supervisor.ticks_ms(), start) >= limit * 1000:
|
|
84
|
+
timed_out = True
|
|
85
|
+
await asyncio.sleep(timeout_poll)
|
|
86
|
+
else:
|
|
87
|
+
start = time.monotonic()
|
|
88
|
+
while not timed_out and not flag():
|
|
89
|
+
if time.monotonic() - start >= limit:
|
|
90
|
+
timed_out = True
|
|
91
|
+
await asyncio.sleep(timeout_poll)
|
|
92
|
+
|
|
93
|
+
return timed_out
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# pylint: disable=too-many-instance-attributes
|
|
97
|
+
# pylint: disable=too-many-nested-blocks
|
|
98
|
+
class RFMSPI:
|
|
99
|
+
"""Base class for SPI type devices"""
|
|
100
|
+
|
|
101
|
+
class RegisterBits:
|
|
102
|
+
"""Simplify register access"""
|
|
103
|
+
|
|
104
|
+
# Class to simplify access to the many configuration bits avaialable
|
|
105
|
+
# on the chip's registers. This is a subclass here instead of using
|
|
106
|
+
# a higher level module to increase the efficiency of memory usage
|
|
107
|
+
# (all of the instances of this bit class will share the same buffer
|
|
108
|
+
# used by the parent RFM69 class instance vs. each having their own
|
|
109
|
+
# buffer and taking too much memory).
|
|
110
|
+
|
|
111
|
+
# Quirk of pylint that it requires public methods for a class. This
|
|
112
|
+
# is a decorator class in Python and by design it has no public methods.
|
|
113
|
+
# Instead it uses dunder accessors like get and set below. For some
|
|
114
|
+
# reason pylint can't figure this out so disable the check.
|
|
115
|
+
# pylint: disable=too-few-public-methods
|
|
116
|
+
|
|
117
|
+
# Again pylint fails to see the true intent of this code and warns
|
|
118
|
+
# against private access by calling the write and read functions below.
|
|
119
|
+
# This is by design as this is an internally used class. Disable the
|
|
120
|
+
# check from pylint.
|
|
121
|
+
# pylint: disable=protected-access
|
|
122
|
+
|
|
123
|
+
def __init__(self, address: int, *, offset: int = 0, bits: int = 1) -> None:
|
|
124
|
+
assert 0 <= offset <= 7
|
|
125
|
+
assert 1 <= bits <= 8
|
|
126
|
+
assert (offset + bits) <= 8
|
|
127
|
+
self._address = address
|
|
128
|
+
self._mask = 0
|
|
129
|
+
for _ in range(bits):
|
|
130
|
+
self._mask <<= 1
|
|
131
|
+
self._mask |= 1
|
|
132
|
+
self._mask <<= offset
|
|
133
|
+
self._offset = offset
|
|
134
|
+
|
|
135
|
+
def __get__(self, obj: Optional["RFM"], objtype: Type["RFM"]) -> int:
|
|
136
|
+
reg_value = obj.read_u8(self._address)
|
|
137
|
+
return (reg_value & self._mask) >> self._offset
|
|
138
|
+
|
|
139
|
+
def __set__(self, obj: Optional["RFM"], val: int) -> None:
|
|
140
|
+
reg_value = obj.read_u8(self._address)
|
|
141
|
+
reg_value &= ~self._mask
|
|
142
|
+
reg_value |= (val & 0xFF) << self._offset
|
|
143
|
+
obj.write_u8(self._address, reg_value)
|
|
144
|
+
|
|
145
|
+
# pylint: disable-msg=too-many-arguments
|
|
146
|
+
def __init__( # noqa: PLR0913
|
|
147
|
+
self,
|
|
148
|
+
spi: busio.SPI,
|
|
149
|
+
cs_pin: digitalio.DigitalInOut,
|
|
150
|
+
baudrate: int = 5000000,
|
|
151
|
+
polarity: int = 0,
|
|
152
|
+
phase: int = 0,
|
|
153
|
+
):
|
|
154
|
+
self.spi_device = spi_device.SPIDevice(
|
|
155
|
+
spi, cs_pin, baudrate=baudrate, polarity=polarity, phase=phase
|
|
156
|
+
)
|
|
157
|
+
# initialize last RSSI reading
|
|
158
|
+
self.last_rssi = 0.0
|
|
159
|
+
"""The RSSI of the last received packet. Stored when the packet was received.
|
|
160
|
+
The instantaneous RSSI value may not be accurate once the
|
|
161
|
+
operating mode has been changed.
|
|
162
|
+
"""
|
|
163
|
+
self.last_snr = 0.0
|
|
164
|
+
"""The SNR of the last received packet. Stored when the packet was received.
|
|
165
|
+
The instantaneous SNR value may not be accurate once the
|
|
166
|
+
operating mode has been changed.
|
|
167
|
+
"""
|
|
168
|
+
# initialize timeouts and delays delays
|
|
169
|
+
self.ack_wait = 0.1
|
|
170
|
+
"""The delay time before attempting a retry after not receiving an ACK"""
|
|
171
|
+
self.receive_timeout = 0.5
|
|
172
|
+
"""The amount of time to poll for a received packet.
|
|
173
|
+
If no packet is received, the returned packet will be None
|
|
174
|
+
"""
|
|
175
|
+
self.xmit_timeout = 2.0
|
|
176
|
+
"""The amount of time to wait for the HW to transmit the packet.
|
|
177
|
+
This is mainly used to prevent a hang due to a HW issue
|
|
178
|
+
"""
|
|
179
|
+
self.ack_retries = 5
|
|
180
|
+
"""The number of ACK retries before reporting a failure."""
|
|
181
|
+
self.ack_delay = None
|
|
182
|
+
"""The delay time before attemting to send an ACK.
|
|
183
|
+
If ACKs are being missed try setting this to .1 or .2.
|
|
184
|
+
"""
|
|
185
|
+
# initialize sequence number counter for reliabe datagram mode
|
|
186
|
+
self.sequence_number = 0
|
|
187
|
+
# create seen Ids list
|
|
188
|
+
self.seen_ids = bytearray(256)
|
|
189
|
+
# initialize packet header
|
|
190
|
+
# node address - default is broadcast
|
|
191
|
+
self.node = _RH_BROADCAST_ADDRESS
|
|
192
|
+
"""The default address of this Node. (0-255).
|
|
193
|
+
If not 255 (0xff) then only packets address to this node will be accepted.
|
|
194
|
+
First byte of the RadioHead header.
|
|
195
|
+
"""
|
|
196
|
+
# destination address - default is broadcast
|
|
197
|
+
self.destination = _RH_BROADCAST_ADDRESS
|
|
198
|
+
"""The default destination address for packet transmissions. (0-255).
|
|
199
|
+
If 255 (0xff) then any receiving node should accept the packet.
|
|
200
|
+
Second byte of the RadioHead header.
|
|
201
|
+
"""
|
|
202
|
+
# ID - contains seq count for reliable datagram mode
|
|
203
|
+
self.identifier = 0
|
|
204
|
+
"""Automatically set to the sequence number when send_with_ack() used.
|
|
205
|
+
Third byte of the RadioHead header.
|
|
206
|
+
"""
|
|
207
|
+
# flags - identifies ack/reetry packet for reliable datagram mode
|
|
208
|
+
self.flags = 0
|
|
209
|
+
"""Upper 4 bits reserved for use by Reliable Datagram Mode.
|
|
210
|
+
Lower 4 bits may be used to pass information.
|
|
211
|
+
Fourth byte of the RadioHead header.
|
|
212
|
+
"""
|
|
213
|
+
self.radiohead = True
|
|
214
|
+
"""Enable RadioHead compatibility"""
|
|
215
|
+
|
|
216
|
+
self.crc_error_count = 0
|
|
217
|
+
self.timeout_poll = 0.001
|
|
218
|
+
|
|
219
|
+
# pylint: enable-msg=too-many-arguments
|
|
220
|
+
|
|
221
|
+
# Global buffer for SPI commands
|
|
222
|
+
_BUFFER = bytearray(4)
|
|
223
|
+
|
|
224
|
+
# pylint: disable=no-member
|
|
225
|
+
# Reconsider pylint: disable when this can be tested
|
|
226
|
+
def read_into(self, address: int, buf: WriteableBuffer, length: Optional[int] = None) -> None:
|
|
227
|
+
"""Read a number of bytes from the specified address into the provided
|
|
228
|
+
buffer. If length is not specified (the default) the entire buffer
|
|
229
|
+
will be filled."""
|
|
230
|
+
if length is None:
|
|
231
|
+
length = len(buf)
|
|
232
|
+
with self.spi_device as device:
|
|
233
|
+
self._BUFFER[0] = address & 0x7F # Strip out top bit to set 0
|
|
234
|
+
# value (read).
|
|
235
|
+
device.write(self._BUFFER, end=1)
|
|
236
|
+
device.readinto(buf, end=length)
|
|
237
|
+
|
|
238
|
+
def read_u8(self, address: int) -> int:
|
|
239
|
+
"""Read a single byte from the provided address and return it."""
|
|
240
|
+
self.read_into(address, self._BUFFER, length=1)
|
|
241
|
+
return self._BUFFER[0]
|
|
242
|
+
|
|
243
|
+
def write_from(self, address: int, buf: ReadableBuffer, length: Optional[int] = None) -> None:
|
|
244
|
+
"""Write a number of bytes to the provided address and taken from the
|
|
245
|
+
provided buffer. If no length is specified (the default) the entire
|
|
246
|
+
buffer is written."""
|
|
247
|
+
if length is None:
|
|
248
|
+
length = len(buf)
|
|
249
|
+
with self.spi_device as device:
|
|
250
|
+
self._BUFFER[0] = (address | 0x80) & 0xFF # Set top bit to 1 to
|
|
251
|
+
# indicate a write.
|
|
252
|
+
device.write(self._BUFFER, end=1)
|
|
253
|
+
device.write(buf, end=length)
|
|
254
|
+
|
|
255
|
+
def write_u8(self, address: int, val: int) -> None:
|
|
256
|
+
"""Write a byte register to the chip. Specify the 7-bit address and the
|
|
257
|
+
8-bit value to write to that address."""
|
|
258
|
+
with self.spi_device as device:
|
|
259
|
+
self._BUFFER[0] = (address | 0x80) & 0xFF # Set top bit to 1 to indicate a write.
|
|
260
|
+
self._BUFFER[1] = val & 0xFF
|
|
261
|
+
device.write(self._BUFFER, end=2)
|
|
262
|
+
|
|
263
|
+
# pylint: disable=too-many-branches
|
|
264
|
+
|
|
265
|
+
async def asyncio_send( # noqa: PLR0912 PLR0913
|
|
266
|
+
self,
|
|
267
|
+
data: ReadableBuffer,
|
|
268
|
+
*,
|
|
269
|
+
keep_listening: bool = False,
|
|
270
|
+
destination: Optional[int] = None,
|
|
271
|
+
node: Optional[int] = None,
|
|
272
|
+
identifier: Optional[int] = None,
|
|
273
|
+
flags: Optional[int] = None,
|
|
274
|
+
) -> bool:
|
|
275
|
+
"""Send a string of data using the transmitter.
|
|
276
|
+
You can only send 252 bytes at a time
|
|
277
|
+
(limited by chip's FIFO size and appended headers).
|
|
278
|
+
if the propert radiohead is True then this appends a 4 byte header
|
|
279
|
+
to be compatible with the RadioHead library.
|
|
280
|
+
The header defaults to using the initialized attributes:
|
|
281
|
+
(destination,node,identifier,flags)
|
|
282
|
+
It may be temporarily overidden via the kwargs - destination,node,identifier,flags.
|
|
283
|
+
Values passed via kwargs do not alter the attribute settings.
|
|
284
|
+
The keep_listening argument should be set to True if you want to start listening
|
|
285
|
+
automatically after the packet is sent. The default setting is False.
|
|
286
|
+
|
|
287
|
+
Returns: True if success or False if the send timed out.
|
|
288
|
+
"""
|
|
289
|
+
# Disable pylint warning to not use length as a check for zero.
|
|
290
|
+
# This is a puzzling warning as the below code is clearly the most
|
|
291
|
+
# efficient and proper way to ensure a precondition that the provided
|
|
292
|
+
# buffer be within an expected range of bounds. Disable this check.
|
|
293
|
+
# pylint: disable=len-as-condition
|
|
294
|
+
assert 0 < len(data) <= self.max_packet_length
|
|
295
|
+
# pylint: enable=len-as-condition
|
|
296
|
+
self.idle() # Stop receiving to clear FIFO and keep it clear.
|
|
297
|
+
# Combine header and data to form payload
|
|
298
|
+
if self.radiohead:
|
|
299
|
+
payload = bytearray(4)
|
|
300
|
+
if destination is None: # use attribute
|
|
301
|
+
payload[0] = self.destination
|
|
302
|
+
else: # use kwarg
|
|
303
|
+
payload[0] = destination
|
|
304
|
+
if node is None: # use attribute
|
|
305
|
+
payload[1] = self.node
|
|
306
|
+
else: # use kwarg
|
|
307
|
+
payload[1] = node
|
|
308
|
+
if identifier is None: # use attribute
|
|
309
|
+
payload[2] = self.identifier
|
|
310
|
+
else: # use kwarg
|
|
311
|
+
payload[2] = identifier
|
|
312
|
+
if flags is None: # use attribute
|
|
313
|
+
payload[3] = self.flags
|
|
314
|
+
else: # use kwarg
|
|
315
|
+
payload[3] = flags
|
|
316
|
+
payload = payload + data
|
|
317
|
+
elif destination is not None: # prepend destination for non RH packets
|
|
318
|
+
payload = destination.to_bytes(1, "big") + data
|
|
319
|
+
else:
|
|
320
|
+
payload = data
|
|
321
|
+
self.fill_fifo(payload)
|
|
322
|
+
# Turn on transmit mode to send out the packet.
|
|
323
|
+
self.transmit()
|
|
324
|
+
# Wait for packet_sent interrupt with explicit polling (not ideal but
|
|
325
|
+
# best that can be done right now without interrupts).
|
|
326
|
+
timed_out = await asyncio_check_timeout(
|
|
327
|
+
self.packet_sent, self.xmit_timeout, self.timeout_poll
|
|
328
|
+
)
|
|
329
|
+
# Listen again if necessary and return the result packet.
|
|
330
|
+
if keep_listening:
|
|
331
|
+
self.listen()
|
|
332
|
+
else:
|
|
333
|
+
# Enter idle mode to stop receiving other packets.
|
|
334
|
+
self.idle()
|
|
335
|
+
self.clear_interrupt()
|
|
336
|
+
return not timed_out
|
|
337
|
+
|
|
338
|
+
send = asyncio_to_blocking(asyncio_send)
|
|
339
|
+
"""Non-asyncio wrapper to Send a string of data using the transmitter
|
|
340
|
+
using the same arguments and keywords as asyncio_send()
|
|
341
|
+
"""
|
|
342
|
+
|
|
343
|
+
async def asyncio_send_with_ack(self, data: ReadableBuffer) -> bool:
|
|
344
|
+
"""Reliable Datagram mode:
|
|
345
|
+
Send a packet with data and wait for an ACK response.
|
|
346
|
+
The packet header is automatically generated.
|
|
347
|
+
If enabled, the packet transmission will be retried on failure
|
|
348
|
+
"""
|
|
349
|
+
if not self.radiohead:
|
|
350
|
+
raise RuntimeError("send_with_ack onl suppoted in RadioHead mode")
|
|
351
|
+
if self.ack_retries:
|
|
352
|
+
retries_remaining = self.ack_retries
|
|
353
|
+
else:
|
|
354
|
+
retries_remaining = 1
|
|
355
|
+
got_ack = False
|
|
356
|
+
self.sequence_number = (self.sequence_number + 1) & 0xFF
|
|
357
|
+
while not got_ack and retries_remaining:
|
|
358
|
+
self.identifier = self.sequence_number
|
|
359
|
+
await self.asyncio_send(data, keep_listening=True)
|
|
360
|
+
# Don't look for ACK from Broadcast message
|
|
361
|
+
if self.destination == _RH_BROADCAST_ADDRESS:
|
|
362
|
+
got_ack = True
|
|
363
|
+
else:
|
|
364
|
+
# wait for a packet from our destination
|
|
365
|
+
ack_packet = await self.asyncio_receive(timeout=self.ack_wait, with_header=True)
|
|
366
|
+
if ack_packet is not None:
|
|
367
|
+
if ack_packet[3] & _RH_FLAGS_ACK:
|
|
368
|
+
# check the ID
|
|
369
|
+
if ack_packet[2] == self.identifier:
|
|
370
|
+
got_ack = True
|
|
371
|
+
break
|
|
372
|
+
# pause before next retry -- random delay
|
|
373
|
+
if not got_ack:
|
|
374
|
+
# delay by random amount before next try
|
|
375
|
+
await asyncio.sleep(self.ack_wait + self.ack_wait * random.random())
|
|
376
|
+
retries_remaining = retries_remaining - 1
|
|
377
|
+
# set retry flag in packet header
|
|
378
|
+
self.flags |= _RH_FLAGS_RETRY
|
|
379
|
+
self.flags = 0 # clear flags
|
|
380
|
+
return got_ack
|
|
381
|
+
|
|
382
|
+
send_with_ack = asyncio_to_blocking(asyncio_send_with_ack)
|
|
383
|
+
"""Non-asyncio wrapper to Send a string of data using the transmitter
|
|
384
|
+
using the same arguments and keywords as asyncio_send_with_ack()
|
|
385
|
+
"""
|
|
386
|
+
|
|
387
|
+
async def asyncio_receive( # noqa: PLR0912
|
|
388
|
+
self,
|
|
389
|
+
*,
|
|
390
|
+
keep_listening: bool = True,
|
|
391
|
+
with_header: bool = False,
|
|
392
|
+
timeout: Optional[float] = None,
|
|
393
|
+
) -> Optional[bytearray]:
|
|
394
|
+
"""Wait to receive a packet from the receiver. If a packet is found the payload bytes
|
|
395
|
+
are returned, otherwise None is returned (which indicates the timeout elapsed with no
|
|
396
|
+
reception).
|
|
397
|
+
If keep_listening is True (the default) the chip will immediately enter listening mode
|
|
398
|
+
after reception of a packet, otherwise it will fall back to idle mode and ignore any
|
|
399
|
+
future reception.
|
|
400
|
+
Packets may have a 4-byte header for compatibility with the
|
|
401
|
+
RadioHead library.
|
|
402
|
+
The header consists of 4 bytes (To,From,ID,Flags). The default setting will strip
|
|
403
|
+
the header before returning the packet to the caller.
|
|
404
|
+
If with_header is True then the 4 byte header will be returned with the packet.
|
|
405
|
+
The payload then begins at packet[4].
|
|
406
|
+
"""
|
|
407
|
+
if not self.radiohead and with_header:
|
|
408
|
+
raise RuntimeError("with_header only supported for RadioHead mode")
|
|
409
|
+
timed_out = False
|
|
410
|
+
if timeout is None:
|
|
411
|
+
timeout = self.receive_timeout
|
|
412
|
+
if timeout is not None:
|
|
413
|
+
# Wait for the payloadready signal. This is not ideal and will
|
|
414
|
+
# surely miss or overflow the FIFO when packets aren't read fast
|
|
415
|
+
# enough, however it's the best that can be done from Python without
|
|
416
|
+
# interrupt supports.
|
|
417
|
+
# Make sure we are listening for packets.
|
|
418
|
+
self.listen()
|
|
419
|
+
timed_out = await asyncio_check_timeout(self.payload_ready, timeout, self.timeout_poll)
|
|
420
|
+
# Payload ready is set, a packet is in the FIFO.
|
|
421
|
+
packet = None
|
|
422
|
+
# save last RSSI reading
|
|
423
|
+
self.last_rssi = self.rssi
|
|
424
|
+
self.last_snr = self.snr
|
|
425
|
+
|
|
426
|
+
# Enter idle mode to stop receiving other packets.
|
|
427
|
+
self.idle()
|
|
428
|
+
if not timed_out:
|
|
429
|
+
if self.enable_crc and self.crc_error:
|
|
430
|
+
self.crc_error_count += 1
|
|
431
|
+
else:
|
|
432
|
+
packet = self.read_fifo()
|
|
433
|
+
if (packet is not None) and self.radiohead:
|
|
434
|
+
if len(packet) < 5:
|
|
435
|
+
# reject the packet if it is too small to contain the RAdioHead Header
|
|
436
|
+
packet = None
|
|
437
|
+
if packet is not None:
|
|
438
|
+
if (
|
|
439
|
+
self.node != _RH_BROADCAST_ADDRESS # noqa: PLR1714
|
|
440
|
+
and packet[0] != _RH_BROADCAST_ADDRESS
|
|
441
|
+
and packet[0] != self.node
|
|
442
|
+
):
|
|
443
|
+
packet = None
|
|
444
|
+
if not with_header and packet is not None: # skip the header if not wanted
|
|
445
|
+
packet = packet[4:]
|
|
446
|
+
# Listen again if necessary and return the result packet.
|
|
447
|
+
if keep_listening:
|
|
448
|
+
self.listen()
|
|
449
|
+
else:
|
|
450
|
+
# Enter idle mode to stop receiving other packets.
|
|
451
|
+
self.idle()
|
|
452
|
+
self.clear_interrupt()
|
|
453
|
+
return packet
|
|
454
|
+
|
|
455
|
+
receive = asyncio_to_blocking(asyncio_receive)
|
|
456
|
+
"""Non-asyncio wrapper to Receive a packet
|
|
457
|
+
using the same arguments and keywords as asyncio_receive()
|
|
458
|
+
"""
|
|
459
|
+
|
|
460
|
+
async def asyncio_receive_with_ack( # noqa: PLR0912
|
|
461
|
+
self,
|
|
462
|
+
*,
|
|
463
|
+
keep_listening: bool = True,
|
|
464
|
+
with_header: bool = False,
|
|
465
|
+
timeout: Optional[float] = None,
|
|
466
|
+
) -> Optional[bytearray]:
|
|
467
|
+
"""Wait to receive a RadioHead packet from the receiver then send an ACK packet in response.
|
|
468
|
+
AKA Reliable Datagram mode.
|
|
469
|
+
If a packet is found the payload bytes are returned, otherwise None is returned
|
|
470
|
+
(which indicates the timeout elapsed with no reception).
|
|
471
|
+
If keep_listening is True (the default) the chip will immediately enter listening mode
|
|
472
|
+
after receipt of a packet, otherwise it will fall back to idle mode and ignore
|
|
473
|
+
any incomming packets until it is called again.
|
|
474
|
+
All packets must have a 4-byte header for compatibility with the RadioHead library.
|
|
475
|
+
The header consists of 4 bytes (To,From,ID,Flags). The default setting will strip
|
|
476
|
+
the header before returning the packet to the caller.
|
|
477
|
+
If with_header is True then the 4 byte header will be returned with the packet.
|
|
478
|
+
The payload then begins at packet[4].
|
|
479
|
+
"""
|
|
480
|
+
if not self.radiohead:
|
|
481
|
+
raise RuntimeError("receive_with_ack only supported for RadioHead mode")
|
|
482
|
+
timed_out = False
|
|
483
|
+
if timeout is None:
|
|
484
|
+
timeout = self.receive_timeout
|
|
485
|
+
if timeout is not None:
|
|
486
|
+
# Wait for the payloadready signal. This is not ideal and will
|
|
487
|
+
# surely miss or overflow the FIFO when packets aren't read fast
|
|
488
|
+
# enough, however it's the best that can be done from Python without
|
|
489
|
+
# interrupt supports.
|
|
490
|
+
# Make sure we are listening for packets.
|
|
491
|
+
self.listen()
|
|
492
|
+
timed_out = await asyncio_check_timeout(self.payload_ready, timeout, self.timeout_poll)
|
|
493
|
+
# Payload ready is set, a packet is in the FIFO.
|
|
494
|
+
packet = None
|
|
495
|
+
# save last RSSI reading
|
|
496
|
+
self.last_rssi = self.rssi
|
|
497
|
+
self.last_snr = self.snr
|
|
498
|
+
|
|
499
|
+
# Enter idle mode to stop receiving other packets.
|
|
500
|
+
self.idle()
|
|
501
|
+
if not timed_out:
|
|
502
|
+
if self.enable_crc and self.crc_error:
|
|
503
|
+
self.crc_error_count += 1
|
|
504
|
+
else:
|
|
505
|
+
packet = self.read_fifo()
|
|
506
|
+
if (packet is not None) and self.radiohead:
|
|
507
|
+
if len(packet) < 5:
|
|
508
|
+
# reject the packet if it is too small to contain the RAdioHead Header
|
|
509
|
+
packet = None
|
|
510
|
+
if packet is not None:
|
|
511
|
+
if (
|
|
512
|
+
self.node != _RH_BROADCAST_ADDRESS # noqa: PLR1714
|
|
513
|
+
and packet[0] != _RH_BROADCAST_ADDRESS
|
|
514
|
+
and packet[0] != self.node
|
|
515
|
+
):
|
|
516
|
+
packet = None
|
|
517
|
+
# send ACK unless this was an ACK or a broadcast
|
|
518
|
+
elif ((packet[3] & _RH_FLAGS_ACK) == 0) and (
|
|
519
|
+
packet[0] != _RH_BROADCAST_ADDRESS
|
|
520
|
+
):
|
|
521
|
+
# delay before sending Ack to give receiver a chance to get ready
|
|
522
|
+
if self.ack_delay is not None:
|
|
523
|
+
await asyncio.sleep(self.ack_delay)
|
|
524
|
+
# send ACK packet to sender (data is b'!')
|
|
525
|
+
await self.asyncio_send(
|
|
526
|
+
b"!",
|
|
527
|
+
destination=packet[1],
|
|
528
|
+
node=packet[0],
|
|
529
|
+
identifier=packet[2],
|
|
530
|
+
flags=(packet[3] | _RH_FLAGS_ACK),
|
|
531
|
+
)
|
|
532
|
+
# reject Retries if we have seen this idetifier from this source before
|
|
533
|
+
if (self.seen_ids[packet[1]] == packet[2]) and (
|
|
534
|
+
packet[3] & _RH_FLAGS_RETRY
|
|
535
|
+
):
|
|
536
|
+
packet = None
|
|
537
|
+
else: # save the packet identifier for this source
|
|
538
|
+
self.seen_ids[packet[1]] = packet[2]
|
|
539
|
+
if (
|
|
540
|
+
packet is not None and (packet[3] & _RH_FLAGS_ACK) != 0
|
|
541
|
+
): # Ignore it if it was an ACK packet
|
|
542
|
+
packet = None
|
|
543
|
+
if not with_header and packet is not None: # skip the header if not wanted
|
|
544
|
+
packet = packet[4:]
|
|
545
|
+
# Listen again if necessary and return the result packet.
|
|
546
|
+
if keep_listening:
|
|
547
|
+
self.listen()
|
|
548
|
+
else:
|
|
549
|
+
# Enter idle mode to stop receiving other packets.
|
|
550
|
+
self.idle()
|
|
551
|
+
self.clear_interrupt()
|
|
552
|
+
return packet
|
|
553
|
+
|
|
554
|
+
receive_with_ack = asyncio_to_blocking(asyncio_receive_with_ack)
|
|
555
|
+
"""Non-asyncio wrapper to Receive a packet
|
|
556
|
+
using the same arguments and keywords as asyncio_receive_with_ack()
|
|
557
|
+
"""
|