Examples¶
nRF24L01 Features¶
Simple test¶
Changed in version 2.0.0:
Ensure your device works with this simple test.
4import time
5import struct
6import board
7from digitalio import DigitalInOut
8
9# if running this on a ATSAMD21 M0 based board
10# from circuitpython_nrf24l01.rf24_lite import RF24
11from circuitpython_nrf24l01.rf24 import RF24
12
13# invalid default values for scoping
14SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
15
16try: # on Linux
17 import spidev
18
19 SPI_BUS = spidev.SpiDev() # for a faster interface on linux
20 CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
21 CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
22
23except ImportError: # on CircuitPython only
24 # using board.SPI() automatically selects the MCU's
25 # available SPI pins, board.SCK, board.MOSI, board.MISO
26 SPI_BUS = board.SPI() # init spi bus object
27
28 # change these (digital output) pins accordingly
29 CE_PIN = DigitalInOut(board.D4)
30 CSN_PIN = DigitalInOut(board.D5)
31
32
33# initialize the nRF24L01 on the spi bus object
34nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
35# On Linux, csn value is a bit coded
36# 0 = bus 0, CE0 # SPI bus 0 is enabled by default
37# 10 = bus 1, CE0 # enable SPI bus 2 prior to running this
38# 21 = bus 2, CE1 # enable SPI bus 1 prior to running this
39
40# set the Power Amplifier level to -12 dBm since this test example is
41# usually run with nRF24L01 transceivers in close proximity
42nrf.pa_level = -12
43
44# addresses needs to be in a buffer protocol object (bytearray)
45address = [b"1Node", b"2Node"]
46
47# to use different addresses on a pair of radios, we need a variable to
48# uniquely identify which address this radio will use to transmit
49# 0 uses address[0] to transmit, 1 uses address[1] to transmit
50radio_number = bool(
51 int(input("Which radio is this? Enter '0' or '1'. Defaults to '0' ") or 0)
52)
53
54# set TX address of RX node into the TX pipe
55nrf.open_tx_pipe(address[radio_number]) # always uses pipe 0
56
57# set RX address of TX node into an RX pipe
58nrf.open_rx_pipe(1, address[not radio_number]) # using pipe 1
59
60# using the python keyword global is bad practice. Instead we'll use a 1 item
61# list to store our float number for the payloads sent
62payload = [0.0]
63
64# uncomment the following 3 lines for compatibility with TMRh20 library
65# nrf.allow_ask_no_ack = False
66# nrf.dynamic_payloads = False
67# nrf.payload_length = 4
68
69
70def master(count=5): # count = 5 will only transmit 5 packets
71 """Transmits an incrementing integer every second"""
72 nrf.listen = False # ensures the nRF24L01 is in TX mode
73
74 while count:
75 # use struct.pack to packetize your data
76 # into a usable payload
77 buffer = struct.pack("<f", payload[0])
78 # "<f" means a single little endian (4 byte) float value.
79 start_timer = time.monotonic_ns() # start timer
80 result = nrf.send(buffer)
81 end_timer = time.monotonic_ns() # end timer
82 if not result:
83 print("send() failed or timed out")
84 else:
85 print(
86 "Transmission successful! Time to Transmit:",
87 f"{(end_timer - start_timer) / 1000} us. Sent: {payload[0]}"
88 )
89 payload[0] += 0.01
90 time.sleep(1)
91 count -= 1
92
93
94def slave(timeout=6):
95 """Polls the radio and prints the received value. This method expires
96 after 6 seconds of no received transmission"""
97 nrf.listen = True # put radio into RX mode and power up
98
99 start = time.monotonic()
100 while (time.monotonic() - start) < timeout:
101 if nrf.available():
102 # grab information about the received payload
103 payload_size, pipe_number = (nrf.any(), nrf.pipe)
104 # fetch 1 payload from RX FIFO
105 buffer = nrf.read() # also clears nrf.irq_dr status flag
106 # expecting a little endian float, thus the format string "<f"
107 # buffer[:4] truncates padded 0s if dynamic payloads are disabled
108 payload[0] = struct.unpack("<f", buffer[:4])[0]
109 # print details about the received packet
110 print(f"Received {payload_size} bytes on pipe {pipe_number}: {payload[0]}")
111 start = time.monotonic()
112
113 # recommended behavior is to keep in TX mode while idle
114 nrf.listen = False # put the nRF24L01 is in TX mode
115
116
ACK Payloads Example¶
Changed in version 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.
See also
More details are found in the documentation on ack
and load_ack()
.
5import time
6import board
7from digitalio import DigitalInOut
8
9# if running this on a ATSAMD21 M0 based board
10# from circuitpython_nrf24l01.rf24_lite import RF24
11from circuitpython_nrf24l01.rf24 import RF24
12
13# invalid default values for scoping
14SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
15
16try: # on Linux
17 import spidev
18
19 SPI_BUS = spidev.SpiDev() # for a faster interface on linux
20 CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
21 CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
22
23except ImportError: # on CircuitPython only
24 # using board.SPI() automatically selects the MCU's
25 # available SPI pins, board.SCK, board.MOSI, board.MISO
26 SPI_BUS = board.SPI() # init spi bus object
27
28 # change these (digital output) pins accordingly
29 CE_PIN = DigitalInOut(board.D4)
30 CSN_PIN = DigitalInOut(board.D5)
31
32
33# initialize the nRF24L01 on the spi bus object
34nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
35# On Linux, csn value is a bit coded
36# 0 = bus 0, CE0 # SPI bus 0 is enabled by default
37# 10 = bus 1, CE0 # enable SPI bus 2 prior to running this
38# 21 = bus 2, CE1 # enable SPI bus 1 prior to running this
39
40# the custom ACK payload feature is disabled by default
41# NOTE the the custom ACK payload feature will be enabled
42# automatically when you call load_ack() passing:
43# a buffer protocol object (bytearray) of
44# length ranging [1,32]. And pipe number always needs
45# to be an int ranging [0, 5]
46
47# to enable the custom ACK payload feature
48nrf.ack = True # False disables again
49
50# set the Power Amplifier level to -12 dBm since this test example is
51# usually run with nRF24L01 transceivers in close proximity
52nrf.pa_level = -12
53
54# addresses needs to be in a buffer protocol object (bytearray)
55address = [b"1Node", b"2Node"]
56
57# to use different addresses on a pair of radios, we need a variable to
58# uniquely identify which address this radio will use to transmit
59# 0 uses address[0] to transmit, 1 uses address[1] to transmit
60radio_number = bool(
61 int(input("Which radio is this? Enter '0' or '1'. Defaults to '0' ") or 0)
62)
63
64# set TX address of RX node into the TX pipe
65nrf.open_tx_pipe(address[radio_number]) # always uses pipe 0
66
67# set RX address of TX node into an RX pipe
68nrf.open_rx_pipe(1, address[not radio_number]) # using pipe 1
69
70# using the python keyword global is bad practice. Instead we'll use a 1 item
71# list to store our integer number for the payloads' counter
72counter = [0]
73
74
75def master(count=5): # count = 5 will only transmit 5 packets
76 """Transmits a payload every second and prints the ACK payload"""
77 nrf.listen = False # put radio in TX mode
78
79 while count:
80 # construct a payload to send
81 # add b"\0" as a c-string NULL terminating char
82 buffer = b"Hello \0" + bytes([counter[0]])
83 start_timer = time.monotonic_ns() # start timer
84 result = nrf.send(buffer) # save the response (ACK payload)
85 end_timer = time.monotonic_ns() # stop timer
86 if result:
87 # print the received ACK that was automatically
88 # fetched and saved to "result" via send()
89 # print timer results upon transmission success
90 print(
91 "Transmission successful! Time to transmit:",
92 f"{int((end_timer - start_timer) / 1000)} us.",
93 "Sent: {}{}".format(buffer[:6].decode("utf-8"), counter[0]),
94 end=" ",
95 )
96 if isinstance(result, bool):
97 print("Received an empty ACK packet")
98 else:
99 # result[:6] truncates c-string NULL termiating char
100 # received counter is a unsigned byte, thus result[7:8][0]
101 print(
102 "Received: {}{}".format(result[:6].decode("utf-8"), result[7:8][0])
103 )
104 counter[0] += 1 # increment payload counter
105 elif not result:
106 print("send() failed or timed out")
107 time.sleep(1) # let the RX node prepare a new ACK payload
108 count -= 1
109
110
111def slave(timeout=6):
112 """Prints the received value and sends an ACK payload"""
113 nrf.listen = True # put radio into RX mode, power it up
114
115 # setup the first transmission's ACK payload
116 # add b"\0" as a c-string NULL terminating char
117 buffer = b"World \0" + bytes([counter[0]])
118 # we must set the ACK payload data and corresponding
119 # pipe number [0, 5]. We'll be acknowledging pipe 1
120 nrf.load_ack(buffer, 1) # load ACK for first response
121
122 start = time.monotonic() # start timer
123 while (time.monotonic() - start) < timeout:
124 if nrf.available():
125 # grab information about the received payload
126 length, pipe_number = (nrf.any(), nrf.pipe)
127 # retreive the received packet's payload
128 received = nrf.read()
129 # increment counter from received payload
130 # received counter is a unsigned byte, thus result[7:8][0]
131 counter[0] = received[7:8][0] + 1
132 # the [:6] truncates the c-string NULL termiating char
133 print(
134 f"Received {length} bytes on pipe {pipe_number}:",
135 "{}{}".format(received[:6].decode("utf-8"), received[7:8][0]),
136 "Sent: {}{}".format(buffer[:6].decode("utf-8"), buffer[7:8][0]),
137 )
138 start = time.monotonic() # reset timer
139 buffer = b"World \0" + bytes([counter[0]]) # build new ACK
140 nrf.load_ack(buffer, 1) # load ACK for next response
141
142 # recommended behavior is to keep in TX mode while idle
143 nrf.listen = False # put radio in TX mode
144 nrf.flush_tx() # flush any ACK payloads that remain
145
146
Multiceiver Example¶
New in version 1.2.2.
Changed in version 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.
5import time
6import struct
7import board
8from digitalio import DigitalInOut
9
10# if running this on a ATSAMD21 M0 based board
11# from circuitpython_nrf24l01.rf24_lite import RF24
12from circuitpython_nrf24l01.rf24 import RF24
13
14# invalid default values for scoping
15SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
16
17try: # on Linux
18 import spidev
19
20 SPI_BUS = spidev.SpiDev() # for a faster interface on linux
21 CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
22 CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
23
24except ImportError: # on CircuitPython only
25 # using board.SPI() automatically selects the MCU's
26 # available SPI pins, board.SCK, board.MOSI, board.MISO
27 SPI_BUS = board.SPI() # init spi bus object
28
29 # change these (digital output) pins accordingly
30 CE_PIN = DigitalInOut(board.D4)
31 CSN_PIN = DigitalInOut(board.D5)
32
33
34# initialize the nRF24L01 on the spi bus object
35nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
36# On Linux, csn value is a bit coded
37# 0 = bus 0, CE0 # SPI bus 0 is enabled by default
38# 10 = bus 1, CE0 # enable SPI bus 2 prior to running this
39# 21 = bus 2, CE1 # enable SPI bus 1 prior to running this
40
41# set the Power Amplifier level to -12 dBm since this test example is
42# usually run with nRF24L01 transceivers in close proximity
43nrf.pa_level = -12
44
45# setup the addresses for all transmitting nRF24L01 nodes
46addresses = [
47 b"\x78" * 5,
48 b"\xF1\xB6\xB5\xB4\xB3",
49 b"\xCD\xB6\xB5\xB4\xB3",
50 b"\xA3\xB6\xB5\xB4\xB3",
51 b"\x0F\xB6\xB5\xB4\xB3",
52 b"\x05\xB6\xB5\xB4\xB3",
53]
54
55# uncomment the following 3 lines for compatibility with TMRh20 library
56# nrf.allow_ask_no_ack = False
57# nrf.dynamic_payloads = False
58# nrf.payload_length = 8
59
60
61def base(timeout=10):
62 """Use the nRF24L01 as a base station for lisening to all nodes"""
63 # write the addresses to all pipes.
64 for pipe_n, addr in enumerate(addresses):
65 nrf.open_rx_pipe(pipe_n, addr)
66 nrf.listen = True # put base station into RX mode
67 start_timer = time.monotonic() # start timer
68 while time.monotonic() - start_timer < timeout:
69 while not nrf.fifo(False, True): # keep RX FIFO empty for reception
70 # show the pipe number that received the payload
71 # NOTE read() clears the pipe number and payload length data
72 print("Received", nrf.any(), "on pipe", nrf.pipe, end=" ")
73 node_id, payload_id = struct.unpack("<ii", nrf.read())
74 print(f"from node {node_id}. PayloadID: {payload_id}")
75 start_timer = time.monotonic() # reset timer with every payload
76 nrf.listen = False
77
78
79def node(node_number=0, count=6):
80 """start transmitting to the base station.
81
82 :param int node_number: the node's identifying index (from the
83 the `addresses` list)
84 :param int count: the number of times that the node will transmit
85 to the base station.
86 """
87 nrf.listen = False
88 # set the TX address to the address of the base station.
89 nrf.open_tx_pipe(addresses[node_number])
90 counter = 0
91 # use the node_number to identify where the payload came from
92 while counter < count:
93 counter += 1
94 # payloads will include the node_number and a payload ID character
95 payload = struct.pack("<ii", node_number, counter)
96 # show something to see it isn't frozen
97 start_timer = time.monotonic_ns()
98 report = nrf.send(payload)
99 end_timer = time.monotonic_ns()
100 # show something to see it isn't frozen
101 if report:
102 print(
103 f"Transmission of payloadID {counter} as node {node_number} success"
104 f"full! Transmission time: {int((end_timer - start_timer) / 1000)} us"
105 )
106 else:
107 print("Transmission failed or timed out")
108 time.sleep(0.5) # slow down the test for readability
109
110
Scanner Example¶
New in version 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
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).
6import time
7import board
8from digitalio import DigitalInOut
9
10# if running this on a ATSAMD21 M0 based board
11# from circuitpython_nrf24l01.rf24_lite import RF24
12from circuitpython_nrf24l01.rf24 import RF24, address_repr
13
14# invalid default values for scoping
15SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
16
17try: # on Linux
18 import spidev
19
20 SPI_BUS = spidev.SpiDev() # for a faster interface on linux
21 CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
22 CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
23
24except ImportError: # on CircuitPython only
25 # using board.SPI() automatically selects the MCU's
26 # available SPI pins, board.SCK, board.MOSI, board.MISO
27 SPI_BUS = board.SPI() # init spi bus object
28
29 # change these (digital output) pins accordingly
30 CE_PIN = DigitalInOut(board.D4)
31 CSN_PIN = DigitalInOut(board.D5)
32
33
34# initialize the nRF24L01 on the spi bus object
35nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
36# On Linux, csn value is a bit coded
37# 0 = bus 0, CE0 # SPI bus 0 is enabled by default
38# 10 = bus 1, CE0 # enable SPI bus 2 prior to running this
39# 21 = bus 2, CE1 # enable SPI bus 1 prior to running this
40
41# turn off RX features specific to the nRF24L01 module
42nrf.auto_ack = False
43nrf.dynamic_payloads = False
44nrf.crc = 0
45nrf.arc = 0
46nrf.allow_ask_no_ack = False
47
48# use reverse engineering tactics for a better "snapshot"
49nrf.address_length = 2
50nrf.open_rx_pipe(1, b"\0\x55")
51nrf.open_rx_pipe(0, b"\0\xAA")
52
53
54def scan(timeout=30):
55 """Traverse the spectrum of accessible frequencies and print any detection
56 of ambient signals.
57
58 :param int timeout: The number of seconds in which scanning is performed.
59 """
60 # print the vertical header of channel numbers
61 print("0" * 100 + "1" * 26)
62 for i in range(13):
63 print(str(i % 10) * (10 if i < 12 else 6), sep="", end="")
64 print("") # endl
65 for i in range(126):
66 print(str(i % 10), sep="", end="")
67 print("\n" + "~" * 126)
68
69 signals = [0] * 126 # store the signal count for each channel
70 curr_channel = 0
71 start_timer = time.monotonic() # start the timer
72 while time.monotonic() - start_timer < timeout:
73 nrf.channel = curr_channel
74 if nrf.available():
75 nrf.flush_rx() # flush the RX FIFO because it asserts the RPD flag
76 nrf.listen = 1 # start a RX session
77 time.sleep(0.00013) # wait 130 microseconds
78 signals[curr_channel] += nrf.rpd # if interference is present
79 nrf.listen = 0 # end the RX session
80 curr_channel = curr_channel + 1 if curr_channel < 125 else 0
81
82 # ouptut the signal counts per channel
83 sig_cnt = signals[curr_channel]
84 print(
85 ("%X" % min(15, sig_cnt)) if sig_cnt else "-",
86 sep="",
87 end="" if curr_channel < 125 else "\r",
88 )
89 # finish printing results and end with a new line
90 while curr_channel < len(signals) - 1:
91 curr_channel += 1
92 sig_cnt = signals[curr_channel]
93 print(("%X" % min(15, sig_cnt)) if sig_cnt else "-", sep="", end="")
94 print("")
95
96
97def noise(timeout=1, channel=None):
98 """print a stream of detected noise for duration of time.
99
100 :param int timeout: The number of seconds to scan for ambiant noise.
101 :param int channel: The specific channel to focus on. If not provided, then the
102 radio's current setting is used.
103 """
104 if channel is not None:
105 nrf.channel = channel
106 nrf.listen = True
107 timeout += time.monotonic()
108 while time.monotonic() < timeout:
109 signal = nrf.read()
110 if signal:
111 print(address_repr(signal, False, " "))
112 nrf.listen = False
113 while not nrf.fifo(False, True):
114 # dump the left overs in the RX FIFO
115 print(address_repr(nrf.read(), False, " "))
116
117
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
000111789~~~13-
should be interpreted as
1
signal detected on channel017
3
signals detected on channel018
no signal (
-
) detected on channel019
The ~
is just a divider between the vertical header and the signal counts.
IRQ Pin Example¶
Changed in version 1.2.0: uses ACK payloads to trigger all 3 IRQ events.
Changed in version 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.
7import time
8import board
9import digitalio
10
11# if running this on a ATSAMD21 M0 based board
12# from circuitpython_nrf24l01.rf24_lite import RF24
13from circuitpython_nrf24l01.rf24 import RF24
14
15# select your digital input pin that's connected to the IRQ pin on the nRF4L01
16irq_pin = digitalio.DigitalInOut(board.D12)
17irq_pin.switch_to_input() # make sure its an input object
18# change these (digital output) pins accordingly
19CE_PIN = digitalio.DigitalInOut(board.D4)
20CSN_PIN = digitalio.DigitalInOut(board.D5)
21
22# using board.SPI() automatically selects the MCU's
23# available SPI pins, board.SCK, board.MOSI, board.MISO
24SPI_BUS = board.SPI() # init spi bus object
25
26# we'll be using the dynamic payload size feature (enabled by default)
27# initialize the nRF24L01 on the spi bus object
28nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
29
30# this example uses the ACK payload to trigger the IRQ pin active for
31# the "on data received" event
32nrf.ack = True # enable ACK payloads
33
34# set the Power Amplifier level to -12 dBm since this test example is
35# usually run with nRF24L01 transceivers in close proximity
36nrf.pa_level = -12
37
38# address needs to be in a buffer protocol object (bytearray is preferred)
39address = [b"1Node", b"2Node"]
40
41# to use different addresses on a pair of radios, we need a variable to
42# uniquely identify which address this radio will use to transmit
43# 0 uses address[0] to transmit, 1 uses address[1] to transmit
44radio_number = bool(
45 int(input("Which radio is this? Enter '0' or '1'. Defaults to '0' ") or 0)
46)
47
48# set TX address of RX node into the TX pipe
49nrf.open_tx_pipe(address[radio_number]) # always uses pipe 0
50
51# set RX address of TX node into an RX pipe
52nrf.open_rx_pipe(1, address[not radio_number]) # using pipe 1
53
54
55def _ping_and_prompt():
56 """transmit 1 payload, wait till irq_pin goes active, print IRQ status
57 flags."""
58 nrf.ce_pin = 1 # tell the nRF24L01 to prepare sending a single packet
59 time.sleep(0.00001) # mandatory 10 microsecond pulse starts transmission
60 nrf.ce_pin = 0 # end 10 us pulse; use only 1 buffer from TX FIFO
61 while irq_pin.value: # IRQ pin is active when LOW
62 pass
63 print("IRQ pin went active LOW.")
64 nrf.update() # update irq_d? status flags
65 print(f"\tirq_ds: {nrf.irq_ds}, irq_dr: {nrf.irq_dr}, irq_df: {nrf.irq_df}")
66
67
68def master():
69 """Transmits 3 times: successfully receive ACK payload first, successfully
70 transmit on second, and intentionally fail transmit on the third"""
71 nrf.listen = False # ensures the nRF24L01 is in TX mode
72 # NOTE nrf.write() internally calls nrf.clear_status_flags() first
73
74 # load 2 buffers into the TX FIFO; write_only=True leaves CE pin LOW
75 nrf.write(b"Ping ", write_only=True)
76 nrf.write(b"Pong ", write_only=True)
77
78 # on data ready test
79 print("\nConfiguring IRQ pin to only ignore 'on data sent' event")
80 nrf.interrupt_config(data_sent=False)
81 print(" Pinging slave node for an ACK payload...", end=" ")
82 _ping_and_prompt() # CE pin is managed by this function
83 print("\t\"on data ready\" event test {}successful".format("un" * nrf.irq_dr))
84
85 # on data sent test
86 print("\nConfiguring IRQ pin to only ignore 'on data ready' event")
87 nrf.interrupt_config(data_recv=False)
88 print(" Pinging slave node again... ", end=" ")
89 _ping_and_prompt() # CE pin is managed by this function
90 print("\t\"on data sent\" event test {}successful".format("un" * nrf.irq_ds))
91
92 # trigger slave node to exit by filling the slave node's RX FIFO
93 print("\nSending one extra payload to fill RX FIFO on slave node.")
94 if nrf.send(b"Radio", send_only=True):
95 # when send_only parameter is True, send() ignores RX FIFO usage
96 if nrf.fifo(False, False): # is RX FIFO full?
97 print("Slave node should not be listening anymore.")
98 else:
99 print("transmission succeeded, " "but slave node might still be listening")
100 else:
101 print("Slave node was unresponsive.")
102
103 # on data fail test
104 print("\nConfiguring IRQ pin to go active for all events.")
105 nrf.interrupt_config()
106 print(" Sending a ping to inactive slave node...", end=" ")
107 nrf.flush_tx() # just in case any previous tests failed
108 nrf.write(b"Dummy", write_only=True) # CE pin is left LOW
109 _ping_and_prompt() # CE pin is managed by this function
110 print("\t\"on data failed\" event test {}successful".format("un" * nrf.irq_df))
111 nrf.flush_tx() # flush artifact payload in TX FIFO from last test
112 # all 3 ACK payloads received were 4 bytes each, and RX FIFO is full
113 # so, fetching 12 bytes from the RX FIFO also flushes RX FIFO
114 print("\nComplete RX FIFO:", nrf.read(12))
115
116
117def slave(timeout=6): # will listen for 6 seconds before timing out
118 """Only listen for 3 payload from the master node"""
119 # setup radio to recieve pings, fill TX FIFO with ACK payloads
120 nrf.load_ack(b"Yak ", 1)
121 nrf.load_ack(b"Back", 1)
122 nrf.load_ack(b" ACK", 1)
123 nrf.listen = True # start listening & clear irq_dr flag
124 start_timer = time.monotonic() # start timer now
125 while not nrf.fifo(0, 0) and time.monotonic() - start_timer < timeout:
126 # if RX FIFO is not full and timeout is not reached, then keep going
127 pass
128 nrf.listen = False # put nRF24L01 in Standby-I mode when idling
129 if not nrf.fifo(False, True): # if RX FIFO is not empty
130 # all 3 payloads received were 5 bytes each, and RX FIFO is full
131 # so, fetching 15 bytes from the RX FIFO also flushes RX FIFO
132 print("Complete RX FIFO:", nrf.read(15))
133 nrf.flush_tx() # discard any pending ACK payloads
134
135
Library-Specific Features¶
Stream Example¶
Changed in version 1.2.3: added master_fifo()
to demonstrate using full TX FIFO to stream data.
Changed in version 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.
4import time
5import board
6from digitalio import DigitalInOut
7
8# if running this on a ATSAMD21 M0 based board
9# from circuitpython_nrf24l01.rf24_lite import RF24
10from circuitpython_nrf24l01.rf24 import RF24
11
12# invalid default values for scoping
13SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
14
15try: # on Linux
16 import spidev
17
18 SPI_BUS = spidev.SpiDev() # for a faster interface on linux
19 CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
20 CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
21
22except ImportError: # on CircuitPython only
23 # using board.SPI() automatically selects the MCU's
24 # available SPI pins, board.SCK, board.MOSI, board.MISO
25 SPI_BUS = board.SPI() # init spi bus object
26
27 # change these (digital output) pins accordingly
28 CE_PIN = DigitalInOut(board.D4)
29 CSN_PIN = DigitalInOut(board.D5)
30
31
32# initialize the nRF24L01 on the spi bus object
33nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
34# On Linux, csn value is a bit coded
35# 0 = bus 0, CE0 # SPI bus 0 is enabled by default
36# 10 = bus 1, CE0 # enable SPI bus 2 prior to running this
37# 21 = bus 2, CE1 # enable SPI bus 1 prior to running this
38
39# set the Power Amplifier level to -12 dBm since this test example is
40# usually run with nRF24L01 transceivers in close proximity
41nrf.pa_level = -12
42
43# addresses needs to be in a buffer protocol object (bytearray)
44address = [b"1Node", b"2Node"]
45
46# to use different addresses on a pair of radios, we need a variable to
47# uniquely identify which address this radio will use to transmit
48# 0 uses address[0] to transmit, 1 uses address[1] to transmit
49radio_number = bool(
50 int(input("Which radio is this? Enter '0' or '1'. Defaults to '0' ") or 0)
51)
52
53# set TX address of RX node into the TX pipe
54nrf.open_tx_pipe(address[radio_number]) # always uses pipe 0
55
56# set RX address of TX node into an RX pipe
57nrf.open_rx_pipe(1, address[not radio_number]) # using pipe 1
58
59# uncomment the following 2 lines for compatibility with TMRh20 library
60# nrf.allow_ask_no_ack = False
61nrf.dynamic_payloads = False
62
63
64def make_buffers(size=32):
65 """return a list of payloads"""
66 buffers = []
67 # we'll use `size` for the number of payloads in the list and the
68 # payloads' length
69 for i in range(size):
70 # prefix payload with a sequential letter to indicate which
71 # payloads were lost (if any)
72 buff = bytes([i + (65 if 0 <= i < 26 else 71)])
73 for j in range(size - 1):
74 char = j >= (size - 1) / 2 + abs((size - 1) / 2 - i)
75 char |= j < (size - 1) / 2 - abs((size - 1) / 2 - i)
76 buff += bytes([char + 48])
77 buffers.append(buff)
78 del buff
79 return buffers
80
81
82def master(count=1, size=32): # count = 5 will transmit the list 5 times
83 """Transmits multiple payloads using `RF24.send()` and `RF24.resend()`."""
84 buffers = make_buffers(size) # make a list of payloads
85 nrf.listen = False # ensures the nRF24L01 is in TX mode
86 successful = 0 # keep track of success rate
87 for _ in range(count):
88 start_timer = time.monotonic_ns() # start timer
89 # NOTE force_retry=2 internally invokes `RF24.resend()` 2 times at
90 # most for payloads that fail to transmit.
91 result = nrf.send(buffers, force_retry=2) # result is a list
92 end_timer = time.monotonic_ns() # end timer
93 print("Transmission took", (end_timer - start_timer) / 1000, "us")
94 for r in result: # tally the resulting success rate
95 successful += 1 if r else 0
96 print(
97 f"successfully sent {successful / (size * count) * 100}%",
98 f"({successful}/{size * count})"
99 )
100
101
102def master_fifo(count=1, size=32):
103 """Similar to the `master()` above except this function uses the full
104 TX FIFO and `RF24.write()` instead of `RF24.send()`"""
105 buf = make_buffers(size) # make a list of payloads
106 nrf.listen = False # ensures the nRF24L01 is in TX mode
107 for cnt in range(count): # transmit the same payloads this many times
108 nrf.flush_tx() # clear the TX FIFO so we can use all 3 levels
109 # NOTE the write_only parameter does not initiate sending
110 buf_iter = 0 # iterator of payloads for the while loop
111 failures = 0 # keep track of manual retries
112 start_timer = time.monotonic_ns() # start timer
113 while buf_iter < size: # cycle through all the payloads
114 nrf.ce_pin = False
115 while buf_iter < size and nrf.write(buf[buf_iter], write_only=1):
116 # NOTE write() returns False if TX FIFO is already full
117 buf_iter += 1 # increment iterator of payloads
118 nrf.ce_pin = True
119 while not nrf.fifo(True, True): # updates irq_df flag
120 if nrf.irq_df:
121 # reception failed; we need to reset the irq_rf flag
122 nrf.ce_pin = False # fall back to Standby-I mode
123 failures += 1 # increment manual retries
124 nrf.clear_status_flags() # clear the irq_df flag
125 if failures > 99 and buf_iter < 7 and cnt < 2:
126 # we need to prevent an infinite loop
127 print(
128 "Make sure slave() node is listening."
129 " Quiting master_fifo()"
130 )
131 buf_iter = size + 1 # be sure to exit the while loop
132 nrf.flush_tx() # discard all payloads in TX FIFO
133 else:
134 nrf.ce_pin = True # start re-transmitting
135 nrf.ce_pin = False
136 end_timer = time.monotonic_ns() # end timer
137 print(
138 f"Transmission took {(end_timer - start_timer) / 1000} us",
139 f"with {failures} failures detected."
140 )
141
142
143def slave(timeout=5):
144 """Stops listening after a `timeout` with no response"""
145 nrf.listen = True # put radio into RX mode and power up
146 count = 0 # keep track of the number of received payloads
147 start_timer = time.monotonic() # start timer
148 while time.monotonic() < start_timer + timeout:
149 if nrf.available():
150 count += 1
151 # retreive the received packet's payload
152 buffer = nrf.read() # clears flags & empties RX FIFO
153 print(f"Received: {buffer} - {count}")
154 start_timer = time.monotonic() # reset timer on every RX payload
155
156 # recommended behavior is to keep in TX mode while idle
157 nrf.listen = False # put the nRF24L01 is in TX mode
158
159
Context Example¶
Changed in version 1.2.0: demonstrates switching between FakeBLE
object & RF24
object with the same nRF24L01
This is a test to show how to use The with statement
blocks to manage multiple different nRF24L01 configurations on 1 transceiver.
10from circuitpython_nrf24l01.rf24 import RF24
11from circuitpython_nrf24l01.fake_ble import FakeBLE
12
13# invalid default values for scoping
14SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
15
16try: # on Linux
17 import spidev
18
19 SPI_BUS = spidev.SpiDev() # for a faster interface on linux
20 CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
21 CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
22
23except ImportError: # on CircuitPython only
24 # using board.SPI() automatically selects the MCU's
25 # available SPI pins, board.SCK, board.MOSI, board.MISO
26 SPI_BUS = board.SPI() # init spi bus object
27
28 # change these (digital output) pins accordingly
29 CE_PIN = DigitalInOut(board.D4)
30 CSN_PIN = DigitalInOut(board.D5)
31
32
33# initialize the nRF24L01 objects on the spi bus object
34# the first object will have all the features enabled
35nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
36# On Linux, csn value is a bit coded
37# 0 = bus 0, CE0 # SPI bus 0 is enabled by default
38# 10 = bus 1, CE0 # enable SPI bus 2 prior to running this
39# 21 = bus 2, CE1 # enable SPI bus 1 prior to running this
40
41# enable the option to use custom ACK payloads
42nrf.ack = True
43# set the static payload length to 8 bytes
44nrf.payload_length = 8
45# RF power amplifier is set to -18 dbm
46nrf.pa_level = -18
47
48# the second object has most features disabled/altered
49ble = FakeBLE(SPI_BUS, CSN_PIN, CE_PIN)
50# the IRQ pin is configured to only go active on "data fail"
51# NOTE BLE operations prevent the IRQ pin going active on "data fail" events
52ble.interrupt_config(data_recv=False, data_sent=False)
53# using a channel 2
54ble.channel = 2
55# RF power amplifier is set to -12 dbm
56ble.pa_level = -12
57
58print("\nsettings configured by the nrf object")
59with nrf:
60 # only the first character gets written because it is on a pipe_number > 1
61 nrf.open_rx_pipe(5, b"1Node") # NOTE we do this inside the "with" block
62
63 # display current settings of the nrf object
64 nrf.print_details(True) # True dumps pipe info
65
66print("\nsettings configured by the ble object")
67with ble as nerf: # the "as nerf" part is optional
68 nerf.print_details(1)
69
70# if you examine the outputs from print_details() you'll see:
71# pipe 5 is opened using the nrf object, but closed using the ble object.
72# pipe 0 is closed using the nrf object, but opened using the ble object.
73# also notice the different addresses bound to the RX pipes
74# this is because the "with" statements load the existing settings
75# for the RF24 object specified after the word "with".
76
77# NOTE it is not advised to manipulate seperate RF24 objects outside of the
78# "with" block; you will encounter bugs about configurations when doing so.
79# Be sure to use 1 "with" block per RF24 object when instantiating multiple
80# RF24 objects in your program.
81# NOTE exiting a "with" block will always power down the nRF24L01
82# NOTE upon instantiation, this library closes all RX pipes &
83# extracts the TX/RX addresses from the nRF24L01 registers
Manual ACK Example¶
New in version 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.
5import time
6import board
7from digitalio import DigitalInOut
8
9# if running this on a ATSAMD21 M0 based board
10# from circuitpython_nrf24l01.rf24_lite import RF24
11from circuitpython_nrf24l01.rf24 import RF24
12
13# invalid default values for scoping
14SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
15
16try: # on Linux
17 import spidev
18
19 SPI_BUS = spidev.SpiDev() # for a faster interface on linux
20 CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
21 CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
22
23except ImportError: # on CircuitPython only
24 # using board.SPI() automatically selects the MCU's
25 # available SPI pins, board.SCK, board.MOSI, board.MISO
26 SPI_BUS = board.SPI() # init spi bus object
27
28 # change these (digital output) pins accordingly
29 CE_PIN = DigitalInOut(board.D4)
30 CSN_PIN = DigitalInOut(board.D5)
31
32# initialize the nRF24L01 on the spi bus object
33nrf = RF24(SPI_BUS, CSN_PIN, CE_PIN)
34# On Linux, csn value is a bit coded
35# 0 = bus 0, CE0 # SPI bus 0 is enabled by default
36# 10 = bus 1, CE0 # enable SPI bus 2 prior to running this
37# 21 = bus 2, CE1 # enable SPI bus 1 prior to running this
38
39# set the Power Amplifier level to -12 dBm since this test example is
40# usually run with nRF24L01 transceivers in close proximity
41nrf.pa_level = -12
42
43# addresses needs to be in a buffer protocol object (bytearray)
44address = [b"1Node", b"2Node"]
45
46# to use different addresses on a pair of radios, we need a variable to
47# uniquely identify which address this radio will use to transmit
48# 0 uses address[0] to transmit, 1 uses address[1] to transmit
49radio_number = bool(
50 int(input("Which radio is this? Enter '0' or '1'. Defaults to '0' ") or 0)
51)
52
53# set TX address of RX node into the TX pipe
54nrf.open_tx_pipe(address[radio_number]) # always uses pipe 0
55
56# set RX address of TX node into an RX pipe
57nrf.open_rx_pipe(1, address[not radio_number]) # using pipe 1
58# nrf.open_rx_pipe(2, address[radio_number]) # for getting responses on pipe 2
59
60# using the python keyword global is bad practice. Instead we'll use a 1 item
61# list to store our integer number for the payloads' counter
62counter = [0]
63
64# uncomment the following 3 lines for compatibility with TMRh20 library
65# nrf.allow_ask_no_ack = False
66# nrf.dynamic_payloads = False
67# nrf.payload_length = 8
68
69
70def master(count=5): # count = 5 will only transmit 5 packets
71 """Transmits an arbitrary unsigned long value every second"""
72 nrf.listen = False # ensures the nRF24L01 is in TX mode
73 while count:
74 # construct a payload to send
75 # add b"\0" as a c-string NULL terminating char
76 buffer = b"Hello \0" + bytes([counter[0]])
77 start_timer = time.monotonic_ns() # start timer
78 result = nrf.send(buffer) # save the response (ACK payload)
79 if not result:
80 print("send() failed or timed out")
81 else: # sent successful; listen for a response
82 nrf.listen = True # get radio ready to receive a response
83 timeout = time.monotonic_ns() + 200000000 # set sentinal for timeout
84 while not nrf.available() and time.monotonic_ns() < timeout:
85 # this loop hangs for 200 ms or until response is received
86 pass
87 nrf.listen = False # put the radio back in TX mode
88 end_timer = time.monotonic_ns() # stop timer
89 print(
90 "Transmission successful! Time to transmit:",
91 f"{int((end_timer - start_timer) / 1000)} us. Sent:",
92 "{}{}".format(buffer[:6].decode("utf-8"), counter[0]),
93 end=" ",
94 )
95 if nrf.pipe is None: # is there a payload?
96 # nrf.pipe is also updated using `nrf.listen = False`
97 print("Received no response.")
98 else:
99 length = nrf.any() # reset with read()
100 pipe_number = nrf.pipe # reset with read()
101 received = nrf.read() # grab the response
102 # save new counter from response
103 counter[0] = received[7:8][0]
104 print(
105 f"Receieved {length} bytes with pipe {pipe_number}:",
106 "{}{}".format(bytes(received[:6]).decode("utf-8"), counter[0]),
107 )
108 count -= 1
109 # make example readable in REPL by slowing down transmissions
110 time.sleep(1)
111
112
113def slave(timeout=6):
114 """Polls the radio and prints the received value. This method expires
115 after 6 seconds of no received transmission"""
116 nrf.listen = True # put radio into RX mode and power up
117 start_timer = time.monotonic() # used as a timeout
118 while (time.monotonic() - start_timer) < timeout:
119 if nrf.available():
120 length = nrf.any() # grab payload length info
121 pipe = nrf.pipe # grab pipe number info
122 received = nrf.read(length) # clears info from any() and nrf.pipe
123 # increment counter before sending it back in responding payload
124 counter[0] = received[7:8][0] + 1
125 nrf.listen = False # put the radio in TX mode
126 result = False
127 ack_timeout = time.monotonic_ns() + 200000000
128 while not result and time.monotonic_ns() < ack_timeout:
129 # try to send reply for 200 milliseconds (at most)
130 result = nrf.send(b"World \0" + bytes([counter[0]]))
131 nrf.listen = True # put the radio back in RX mode
132 print(
133 f"Received {length} on pipe {pipe}:",
134 "{}{}".format(bytes(received[:6]).decode("utf-8"), received[7:8][0]),
135 end=" Sent: ",
136 )
137 if not result:
138 print("Response failed or timed out")
139 else:
140 print("World", counter[0])
141 start_timer = time.monotonic() # reset timeout
142
143 # recommended behavior is to keep in TX mode when in idle
144 nrf.listen = False # put the nRF24L01 in TX mode + Standby-I power state
145
146
Network Test¶
New in version 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.
4import time
5import struct
6import board
7from digitalio import DigitalInOut
8from circuitpython_nrf24l01.network.constants import MAX_FRAG_SIZE, NETWORK_DEFAULT_ADDR
9
10IS_MESH = (
11 input(
12 " nrf24l01_network_test example\n"
13 "Would you like to run as a mesh network node (y/n)? Defaults to 'Y' "
14 ) or "Y"
15).upper().startswith("Y")
16
17# to use different addresses on a set of radios, we need a variable to
18# uniquely identify which address this radio will use
19THIS_NODE = 0
20print(
21 "Remember, the master node always uses `0` as the node_address and node_id."
22 "\nWhich node is this? Enter",
23 end=" ",
24)
25if IS_MESH:
26 # node_id must be less than 255
27 THIS_NODE = int(input("a unique int. Defaults to '0' ") or "0") & 0xFF
28else:
29 # logical node_address is in octal
30 THIS_NODE = int(input("an octal int. Defaults to '0' ") or "0", 8)
31
32if IS_MESH:
33 if THIS_NODE: # if this is not a mesh network master node
34 from circuitpython_nrf24l01.rf24_mesh import RF24MeshNoMaster as Network
35 else:
36 from circuitpython_nrf24l01.rf24_mesh import RF24Mesh as Network
37 print("Using RF24Mesh{} class".format("" if not THIS_NODE else "NoMaster"))
38else:
39 from circuitpython_nrf24l01.rf24_network import RF24Network as Network
40
41 # we need to construct frame headers for RF24Network.send()
42 from circuitpython_nrf24l01.network.structs import RF24NetworkHeader
43
44 # we need to construct entire frames for RF24Network.write() (not for this example)
45 # from circuitpython_nrf24l01.network.structs import RF24NetworkFrame
46 print("Using RF24Network class")
47
48# invalid default values for scoping
49SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
50
51try: # on Linux
52 import spidev
53
54 SPI_BUS = spidev.SpiDev() # for a faster interface on linux
55 CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
56 CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
57
58except ImportError: # on CircuitPython only
59 # using board.SPI() automatically selects the MCU's
60 # available SPI pins, board.SCK, board.MOSI, board.MISO
61 SPI_BUS = board.SPI() # init spi bus object
62
63 # change these (digital output) pins accordingly
64 CE_PIN = DigitalInOut(board.D4)
65 CSN_PIN = DigitalInOut(board.D5)
66
67
68# initialize this node as the network
69nrf = Network(SPI_BUS, CSN_PIN, CE_PIN, THIS_NODE)
70
71# TMRh20 examples use a channel 97 for RF24Mesh library
72# TMRh20 examples use a channel 90 for RF24Network library
73nrf.channel = 90 + IS_MESH * 7
74
75# set the Power Amplifier level to -12 dBm since this test example is
76# usually run with nRF24L01 transceivers in close proximity
77nrf.pa_level = -12
78
79# using the python keyword global is bad practice. Instead we'll use a 1 item
80# list to store our number of the payloads sent
81packets_sent = [0]
82
83if THIS_NODE: # if this node is not the network master node
84 if IS_MESH: # mesh nodes need to bond with the master node
85 print("Connecting to mesh network...", end=" ")
86
87 # get this node's assigned address and connect to network
88 if nrf.renew_address() is None:
89 print("failed. Please try again manually with `nrf.renew_address()`")
90 else:
91 print("assigned address:", oct(nrf.node_address))
92else:
93 print("Acting as network master node.")
94
95
96def emit(node=not THIS_NODE, frag=False, count=5, interval=1):
97 """Transmits 1 (or 2) integers or a large buffer
98
99 :param int node: The target node for network transmissions.
100 If using RF24Mesh, this is a unique node_id.
101 If using RF24Network, this is the node's logical address.
102 :param bool frag: Only use fragmented messages?
103 :param int count: The max number of messages to transmit.
104 :param int interval: time (in seconds) between transmitting messages.
105 """
106 failures = 0
107 start_timer = time.monotonic()
108 while failures < 6 and count:
109 nrf.update() # keep the network layer current
110 now = time.monotonic()
111 if now >= start_timer + interval: # its time to emmit
112 start_timer = now
113 count -= 1
114 packets_sent[0] += 1
115 #TMRh20's RF24Mesh examples use 1 long int containing a timestamp (in ms)
116 message = struct.pack("<L", int(now * 1000))
117 if frag:
118 message = bytes(
119 range((packets_sent[0] + MAX_FRAG_SIZE) % nrf.max_message_length)
120 )
121 elif not IS_MESH: # if using RF24Network
122 # TMRh20's RF24Network examples use 2 long ints, so add another
123 message += struct.pack("<L", packets_sent[0])
124 result = False
125 start = time.monotonic_ns()
126 # pylint: disable=no-value-for-parameter
127 if IS_MESH: # send() is a little different for RF24Mesh vs RF24Network
128 result = nrf.send(node, "M", message)
129 else:
130 result = nrf.send(RF24NetworkHeader(node, "T"), message)
131 # pylint: enable=no-value-for-parameter
132 end = time.monotonic_ns()
133 failures += not result
134 print(
135 f"Sending {packets_sent[0]} (len {len(message)})...",
136 "ok." if result else "failed.",
137 f"Transmission took {int((end - start) / 1000000)} ms",
138 )
139
140
141def idle(timeout=30):
142 """Listen for any payloads and print the transaction
143
144 :param int timeout: The number of seconds to wait (with no transmission)
145 until exiting function.
146 """
147 print("idling for", timeout, "seconds")
148 start_timer = time.monotonic()
149 while (time.monotonic() - start_timer) < timeout:
150 nrf.update() # keep the network layer current
151 while nrf.available():
152 start_timer = time.monotonic() # reset timer
153 payload = nrf.read()
154 payload_len = len(payload.message)
155 print("Received payload", end=" ")
156 # TMRh20 examples only use 1 or 2 long ints as small messages
157 if payload_len < MAX_FRAG_SIZE and payload_len % 4 == 0:
158 # if not a large fragmented message and multiple of 4 bytes
159 fmt = "<" + "L" * int(payload_len / 4)
160 print(struct.unpack(fmt, bytes(payload.message)), end=" ")
161 print(payload.header.to_string(), "length", payload_len)
162
163
OTA compatibility¶
Fake BLE Example¶
New in version 1.2.0.
Changed in version 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.
7import time
8import board
9from digitalio import DigitalInOut
10from circuitpython_nrf24l01.fake_ble import (
11 chunk,
12 FakeBLE,
13 UrlServiceData,
14 BatteryServiceData,
15 TemperatureServiceData,
16)
17from circuitpython_nrf24l01.rf24 import address_repr
18
19# invalid default values for scoping
20SPI_BUS, CSN_PIN, CE_PIN = (None, None, None)
21
22try: # on Linux
23 import spidev
24
25 SPI_BUS = spidev.SpiDev() # for a faster interface on linux
26 CSN_PIN = 0 # use CE0 on default bus (even faster than using any pin)
27 CE_PIN = DigitalInOut(board.D22) # using pin gpio22 (BCM numbering)
28
29except ImportError: # on CircuitPython only
30 # using board.SPI() automatically selects the MCU's
31 # available SPI pins, board.SCK, board.MOSI, board.MISO
32 SPI_BUS = board.SPI() # init spi bus object
33
34 # change these (digital output) pins accordingly
35 CE_PIN = DigitalInOut(board.D4)
36 CSN_PIN = DigitalInOut(board.D5)
37
38
39# initialize the nRF24L01 on the spi bus object as a BLE compliant radio
40nrf = FakeBLE(SPI_BUS, CSN_PIN, CE_PIN)
41# On Linux, csn value is a bit coded
42# 0 = bus 0, CE0 # SPI bus 0 is enabled by default
43# 10 = bus 1, CE0 # enable SPI bus 2 prior to running this
44# 21 = bus 2, CE1 # enable SPI bus 1 prior to running this
45
46# the name parameter is going to be its broadcasted BLE name
47# this can be changed at any time using the `name` attribute
48# nrf.name = b"foobar"
49
50# you can optionally set the arbitrary MAC address to be used as the
51# BLE device's MAC address. Otherwise this is randomly generated upon
52# instantiation of the FakeBLE object.
53# nrf.mac = b"\x19\x12\x14\x26\x09\xE0"
54
55# set the Power Amplifier level to -12 dBm since this test example is
56# usually run with nRF24L01 transceiver in close proximity to the
57# BLE scanning application
58nrf.pa_level = -12
59
60
61def _prompt(remaining):
62 if remaining % 5 == 0 or remaining < 5:
63 if remaining - 1:
64 print(remaining, "advertisments left to go!")
65 else:
66 print(remaining, "advertisment left to go!")
67
68
69# create an object for manipulating the battery level data
70battery_service = BatteryServiceData()
71# battery level data is 1 unsigned byte representing a percentage
72battery_service.data = 85
73
74
75def master(count=50):
76 """Sends out the device information."""
77 # using the "with" statement is highly recommended if the nRF24L01 is
78 # to be used for more than a BLE configuration
79 with nrf as ble:
80 ble.name = b"nRF24L01"
81 # include the radio's pa_level attribute in the payload
82 ble.show_pa_level = True
83 print(
84 "available bytes in next payload:",
85 ble.len_available(chunk(battery_service.buffer)),
86 ) # using chunk() gives an accurate estimate of available bytes
87 for i in range(count): # advertise data this many times
88 if ble.len_available(chunk(battery_service.buffer)) >= 0:
89 _prompt(count - i) # something to show that it isn't frozen
90 # broadcast the device name, MAC address, &
91 # battery charge info; 0x16 means service data
92 ble.advertise(battery_service.buffer, data_type=0x16)
93 # channel hoping is recommended per BLE specs
94 ble.hop_channel()
95 time.sleep(0.5) # wait till next broadcast
96 # nrf.show_pa_level & nrf.name both are set to false when
97 # exiting a with statement block
98
99
100# create an object for manipulating temperature measurements
101temperature_service = TemperatureServiceData()
102# temperature's float data has up to 2 decimal places of percision
103temperature_service.data = 42.0
104
105
106def send_temp(count=50):
107 """Sends out a fake temperature."""
108 with nrf as ble:
109 ble.name = b"nRF24L01"
110 print(
111 "available bytes in next payload:",
112 ble.len_available(chunk(temperature_service.buffer)),
113 )
114 for i in range(count):
115 if ble.len_available(chunk(temperature_service.buffer)) >= 0:
116 _prompt(count - i)
117 # broadcast a temperature measurement; 0x16 means service data
118 ble.advertise(temperature_service.buffer, data_type=0x16)
119 ble.hop_channel()
120 time.sleep(0.2)
121
122
123# use the Eddystone protocol from Google to broadcast a URL as
124# service data. We'll need an object to manipulate that also
125url_service = UrlServiceData()
126# the data attribute converts a URL string into a simplified
127# bytes object using byte codes defined by the Eddystone protocol.
128url_service.data = "http://www.google.com"
129# Eddystone protocol requires an estimated TX PA level at 1 meter
130# lower this estimate since we lowered the actual `ble.pa_level`
131url_service.pa_level_at_1_meter = -45 # defaults to -25 dBm
132
133
134def send_url(count=50):
135 """Sends out a URL."""
136 with nrf as ble:
137 print(
138 "available bytes in next payload:",
139 ble.len_available(chunk(url_service.buffer)),
140 )
141 # NOTE we did NOT set a device name in this with block
142 for i in range(count):
143 # URLs easily exceed the nRF24L01's max payload length
144 if ble.len_available(chunk(url_service.buffer)) >= 0:
145 _prompt(count - i)
146 ble.advertise(url_service.buffer, 0x16)
147 ble.hop_channel()
148 time.sleep(0.2)
149
150
151def slave(timeout=6):
152 """read and decipher BLE payloads for `timeout` seconds."""
153 nrf.listen = True
154 end_timer = time.monotonic() + timeout
155 while time.monotonic() <= end_timer:
156 if nrf.available():
157 result = nrf.read()
158 print(
159 "recevied payload from MAC address",
160 address_repr(result.mac, delimit=":")
161 )
162 if result.name is not None:
163 print("\tdevice name:", result.name)
164 if result.pa_level is not None:
165 print("\tdevice transmitting PA Level:", result.pa_level, "dbm")
166 for service_data in result.data:
167 if isinstance(service_data, (bytearray, bytes)):
168 print("\traw buffer:", address_repr(service_data, False, " "))
169 else:
170 print("\t" + repr(service_data))
171
172
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:
set
dynamic_payloads
toFalse
.set
allow_ask_no_ack
toFalse
.set
payload_length
to the value that is passed to TMRh20’sRF24::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 configurepayload_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.
circuitpython_nrf24l01 |
TMRh20’s C++ examples |
---|---|
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) |
|
- 1(1,2,3,4)
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:
# 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.