Optimized high speed nRF24L01+ driver class documentation v1.4.8
TMRh20 2020 - Optimized fork of the nRF24L01+ driver
Loading...
Searching...
No Matches
examples_linux/manual_acknowledgements.py

Written by 2bndy5 in 2020

This is a simple example of using the RF24 class on a Raspberry Pi to transmit and respond with acknowledgment (ACK) transmissions. Notice that the auto-ack feature is enabled, but this example doesn't use automatic ACK payloads because automatic ACK payloads' data will always be outdated by 1 transmission. Instead, this example uses a call and response paradigm.

Remember to install the Python wrapper, then navigate to the "RF24/examples_linux" folder.
To run this example, enter

python3 manual_acknowledgements.py

and follow the prompts.

Note
this example requires python v3.7 or newer because it measures transmission time with time.monotonic_ns().
1"""
2A simple example of sending data from 1 nRF24L01 transceiver to another
3with manually transmitted (non-automatic) Acknowledgement (ACK) payloads.
4This example still uses ACK packets, but they have no payloads. Instead the
5acknowledging response is sent with `write()`. This tactic allows for more
6updated acknowledgement payload data, where actual ACK payloads' data are
7outdated by 1 transmission because they have to loaded before receiving a
8transmission.
9
10This example was written to be used on 2 devices acting as 'nodes'.
11
12See documentation at https://nRF24.github.io/RF24
13"""
14
15import time
16from RF24 import RF24, RF24_PA_LOW, RF24_DRIVER
17
18
19print(__file__) # print example name
20
21
28CSN_PIN = 0 # GPIO8 aka CE0 on SPI bus 0: /dev/spidev0.0
29if RF24_DRIVER == "MRAA":
30 CE_PIN = 15 # for GPIO22
31elif RF24_DRIVER == "wiringPi":
32 CE_PIN = 3 # for GPIO22
33else:
34 CE_PIN = 22
35radio = RF24(CE_PIN, CSN_PIN)
36
37# initialize the nRF24L01 on the spi bus
38if not radio.begin():
39 raise RuntimeError("radio hardware is not responding")
40
41# For this example, we will use different addresses
42# An address need to be a buffer protocol object (bytearray)
43address = [b"1Node", b"2Node"]
44# It is very helpful to think of an address as a path instead of as
45# an identifying device destination
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 the Power Amplifier level to -12 dBm since this test example is
55# usually run with nRF24L01 transceivers in close proximity of each other
56radio.setPALevel(RF24_PA_LOW) # RF24_PA_MAX is default
57
58# set the TX address of the RX node into the TX pipe
59radio.openWritingPipe(address[radio_number]) # always uses pipe 0
60
61# set the RX address of the TX node into a RX pipe
62radio.openReadingPipe(1, address[not radio_number]) # using pipe 1
63
64# To save time during transmission, we'll set the payload size to be only
65# what we need. For this example, we'll be using a byte for the
66# payload counter and 7 bytes for the payload message
67radio.payloadSize = 8
68
69# for debugging, we have 2 options that print a large block of details
70# (smaller) function that prints raw register values
71# radio.printDetails()
72# (larger) function that prints human readable data
73# radio.printPrettyDetails()
74
75# using the python keyword global is bad practice. Instead we'll use a 1 item
76# list to store our integer number for the payloads' counter
77counter = [0]
78
79
80def master():
81 """Transmits a message and an incrementing integer every second, then
82 wait for a response for up to 200 ms.
83 """
84 radio.stopListening() # put radio in TX mode
85 failures = 0
86 while failures < 6:
87 # use bytes() to pack our counter data into the payload
88 # NOTE b"\x00" byte is a c-string's NULL terminating 0
89 buffer = b"Hello \x00" + bytes(counter)
90 start_timer = time.monotonic_ns() # start timer
91 result = radio.write(buffer)
92 if not result:
93 failures += 1
94 print("Transmission failed or timed out")
95 else:
96 radio.startListening() # put radio in RX mode
97 timout = time.monotonic() * 1000 + 200 # use 200 ms timeout
98 # declare a variable to save the incoming response
99 while not radio.available() and time.monotonic() * 1000 < timout:
100 pass # wait for incoming payload or timeout
101 radio.stopListening() # put radio in TX mode
102 end_timer = time.monotonic_ns() # end timer
103 decoded = buffer[:6].decode("utf-8")
104 print(
105 f"Transmission successful. Sent: {decoded}{counter[0]}.",
106 end=" ",
107 )
108 has_payload, pipe_number = radio.available_pipe()
109 if has_payload:
110 # grab the incoming payload
111 received = radio.read(radio.payloadSize)
112 # NOTE received[7:8] discards NULL terminating 0
113 counter[0] = received[7:8][0] # save the counter
114 decoded = bytes(received[:6]).decode("utf-8")
115 print(
116 f"Received {radio.payloadSize} bytes",
117 f"on pipe {pipe_number}: {decoded}{counter[0]}.",
118 f"Round-trip delay: {(end_timer - start_timer) / 1000} us.",
119 )
120 else:
121 print("No response received.")
122 time.sleep(1) # make example readable by slowing down transmissions
123 print(failures, "failures detected. Leaving TX role.")
124
125
126def slave(timeout: int = 6):
127 """Listen for any payloads and print the transaction
128
129 :param int timeout: The number of seconds to wait (with no transmission)
130 until exiting function.
131 """
132 radio.startListening() # put radio in RX mode
133
134 start_timer = time.monotonic() # start a timer to detect timeout
135 while (time.monotonic() - start_timer) < timeout:
136 # receive `count` payloads or wait 6 seconds till timing out
137 has_payload, pipe_number = radio.available_pipe()
138 if has_payload:
139 received = radio.read(radio.payloadSize) # fetch the payload
140 # NOTE received[7:8] discards NULL terminating 0
141 # increment the counter from received payload
142 counter[0] = received[7:8][0] + 1 if received[7:8][0] < 255 else 0
143 # use bytes() to pack our counter data into the payload
144 # NOTE b"\x00" byte is a c-string's NULL terminating 0
145 buffer = b"World \x00" + bytes(counter)
146 radio.stopListening() # put radio in TX mode
147 radio.writeFast(buffer) # load response into TX FIFO
148 # keep retrying to send response for 150 milliseconds
149 result = radio.txStandBy(150) # save response's result
150 # NOTE txStandBy() flushes TX FIFO on transmission failure
151 radio.startListening() # put radio back in RX mode
152 # print the payload received payload
153 decoded = bytes(received[:6]).decode("utf-8")
154 print(
155 f"Received {radio.payloadSize} bytes"
156 f"on pipe {pipe_number}: {decoded}{received[7:8][0]}.",
157 end=" ",
158 )
159 if result: # did response succeed?
160 # print response's payload
161 decoded = buffer[:6].decode("utf-8")
162 print(f"Sent: {decoded}{counter[0]}")
163 else:
164 print("Response failed or timed out")
165 start_timer = time.monotonic() # reset the timeout timer
166
167 print("Nothing received in", timeout, "seconds. Leaving RX role")
168 # recommended behavior is to keep in TX mode while idle
169 radio.stopListening() # put the radio in TX mode
170
171
172def set_role() -> bool:
173 """Set the role using stdin stream. Timeout arg for slave() can be
174 specified using a space delimiter (e.g. 'R 10' calls `slave(10)`)
175
176 :return:
177 - True when role is complete & app should continue running.
178 - False when app should exit
179 """
180 user_input = (
181 input(
182 "*** Enter 'R' for receiver role.\n"
183 "*** Enter 'T' for transmitter role.\n"
184 "*** Enter 'Q' to quit example.\n"
185 )
186 or "?"
187 )
188 user_input = user_input.split()
189 if user_input[0].upper().startswith("R"):
190 if len(user_input) > 1:
191 slave(int(user_input[1]))
192 else:
193 slave()
194 return True
195 if user_input[0].upper().startswith("T"):
196 master()
197 return True
198 if user_input[0].upper().startswith("Q"):
199 radio.powerDown()
200 return False
201 print(user_input[0], "is an unrecognized input. Please try again.")
202 return set_role()
203
204
205if __name__ == "__main__":
206 try:
207 while set_role():
208 pass # continue example until 'Q' is entered
209 except KeyboardInterrupt:
210 print(" Keyboard Interrupt detected. Powering down radio.")
211 radio.powerDown()
212else:
213 print(" Run slave() on receiver\n Run master() on transmitter")
Driver class for nRF24L01(+) 2.4GHz Wireless Transceiver.
Definition RF24.h:116