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"""
12import sys
13import argparse
14import time
15from RF24 import RF24, RF24_PA_LOW
16
17
18parser = argparse.ArgumentParser(
19 description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
20)
21parser.add_argument(
22 "-n",
23 "--node",
24 type=int,
25 choices=range(2),
26 help="the identifying radio number (or node ID number)",
27)
28parser.add_argument(
29 "-r",
30 "--role",
31 type=int,
32 choices=range(2),
33 help="'1' specifies the TX role. '0' specifies the RX role.",
34)
35
36
43CSN_PIN = 0 # connected to GPIO8
44CE_PIN = 22 # connected to GPIO22
45radio = RF24(CE_PIN, CSN_PIN)
46
51
52# using the python keyword global is bad practice. Instead we'll use a 1 item
53# list to store our integer number for the payloads' counter
54counter = [0]
55
56
57def master():
58 """Transmits a message and an incrementing integer every second, then
59 wait for a response for up to 200 ms.
60 """
61 radio.stopListening() # put radio in TX mode
62 failures = 0
63 while failures < 6:
64 # use bytes() to pack our counter data into the payload
65 # NOTE b"\x00" byte is a c-string's NULL terminating 0
66 buffer = b"Hello \x00" + bytes(counter)
67 start_timer = time.monotonic_ns() # start timer
68 result = radio.write(buffer)
69 if not result:
70 failures += 1
71 print("Transmission failed or timed out")
72 else:
73 radio.startListening() # put radio in RX mode
74 timout = time.monotonic() * 1000 + 200 # use 200 ms timeout
75 # declare a variable to save the incoming response
76 while not radio.available() and time.monotonic() * 1000 < timout:
77 pass # wait for incoming payload or timeout
78 radio.stopListening() # put radio in TX mode
79 end_timer = time.monotonic_ns() # end timer
80 decoded = buffer[:6].decode("utf-8")
81 print(
82 f"Transmission successful. Sent: {decoded}{counter[0]}.",
83 end=" ",
84 )
85 has_payload, pipe_number = radio.available_pipe()
86 if has_payload:
87 # grab the incoming payload
88 received = radio.read(radio.payloadSize)
89 # NOTE received[7:8] discards NULL terminating 0
90 counter[0] = received[7:8][0] # save the counter
91 decoded = bytes(received[:6]).decode("utf-8")
92 print(
93 f"Received {radio.payloadSize} bytes",
94 f"on pipe {pipe_number}: {decoded}{counter[0]}.",
95 f"Round-trip delay: {(end_timer - start_timer) / 1000} us.",
96 )
97 else:
98 print("No response received.")
99 time.sleep(1) # make example readable by slowing down transmissions
100 print(failures, "failures detected. Leaving TX role.")
101
102
103def slave(timeout: int = 6):
104 """Listen for any payloads and print the transaction
105
106 :param int timeout: The number of seconds to wait (with no transmission)
107 until exiting function.
108 """
109 radio.startListening() # put radio in RX mode
110
111 start_timer = time.monotonic() # start a timer to detect timeout
112 while (time.monotonic() - start_timer) < timeout:
113 # receive `count` payloads or wait 6 seconds till timing out
114 has_payload, pipe_number = radio.available_pipe()
115 if has_payload:
116 received = radio.read(radio.payloadSize) # fetch the payload
117 # NOTE received[7:8] discards NULL terminating 0
118 # increment the counter from received payload
119 counter[0] = received[7:8][0] + 1 if received[7:8][0] < 255 else 0
120 # use bytes() to pack our counter data into the payload
121 # NOTE b"\x00" byte is a c-string's NULL terminating 0
122 buffer = b"World \x00" + bytes(counter)
123 radio.stopListening() # put radio in TX mode
124 radio.writeFast(buffer) # load response into TX FIFO
125 # keep retrying to send response for 150 milliseconds
126 result = radio.txStandBy(150) # save response's result
127 # NOTE txStandBy() flushes TX FIFO on transmission failure
128 radio.startListening() # put radio back in RX mode
129 # print the payload received payload
130 decoded = bytes(received[:6]).decode("utf-8")
131 print(
132 f"Received {radio.payloadSize} bytes"
133 f"on pipe {pipe_number}: {decoded}{received[7:8][0]}.",
134 end=" ",
135 )
136 if result: # did response succeed?
137 # print response's payload
138 decoded = buffer[:6].decode("utf-8")
139 print(f"Sent: {decoded}{counter[0]}")
140 else:
141 print("Response failed or timed out")
142 start_timer = time.monotonic() # reset the timeout timer
143
144 print("Nothing received in", timeout, "seconds. Leaving RX role")
145 # recommended behavior is to keep in TX mode while idle
146 radio.stopListening() # put the radio in TX mode
147
148
149def set_role() -> bool:
150 """Set the role using stdin stream. Timeout arg for slave() can be
151 specified using a space delimiter (e.g. 'R 10' calls `slave(10)`)
152
153 :return:
154 - True when role is complete & app should continue running.
155 - False when app should exit
156 """
157 user_input = (
158 input(
159 "*** Enter 'R' for receiver role.\n"
160 "*** Enter 'T' for transmitter role.\n"
161 "*** Enter 'Q' to quit example.\n"
162 )
163 or "?"
164 )
165 user_input = user_input.split()
166 if user_input[0].upper().startswith("R"):
167 if len(user_input) > 1:
168 slave(int(user_input[1]))
169 else:
170 slave()
171 return True
172 if user_input[0].upper().startswith("T"):
173 master()
174 return True
175 if user_input[0].upper().startswith("Q"):
176 radio.powerDown()
177 return False
178 print(user_input[0], "is an unrecognized input. Please try again.")
179 return set_role()
180
181
182if __name__ == "__main__":
183
184 args = parser.parse_args() # parse any CLI args
185
186 # initialize the nRF24L01 on the spi bus
187 if not radio.begin():
188 raise RuntimeError("radio hardware is not responding")
189
190 # For this example, we will use different addresses
191 # An address need to be a buffer protocol object (bytearray)
192 address = [b"1Node", b"2Node"]
193 # It is very helpful to think of an address as a path instead of as
194 # an identifying device destination
195
196 print(sys.argv[0]) # print example name
197
198 # to use different addresses on a pair of radios, we need a variable to
199 # uniquely identify which address this radio will use to transmit
200 # 0 uses address[0] to transmit, 1 uses address[1] to transmit
201 radio_number = args.node # uses default value from `parser`
202 if args.node is None: # if '--node' arg wasn't specified
203 radio_number = bool(
204 int(input("Which radio is this? Enter '0' or '1'. Defaults to '0' ") or 0)
205 )
206
207 # set the Power Amplifier level to -12 dBm since this test example is
208 # usually run with nRF24L01 transceivers in close proximity of each other
209 radio.setPALevel(RF24_PA_LOW) # RF24_PA_MAX is default
210
211 # set the TX address of the RX node into the TX pipe
212 radio.openWritingPipe(address[radio_number]) # always uses pipe 0
213
214 # set the RX address of the TX node into a RX pipe
215 radio.openReadingPipe(1, address[not radio_number]) # using pipe 1
216
217 # To save time during transmission, we'll set the payload size to be only
218 # what we need. For this example, we'll be using a byte for the
219 # payload counter and 7 bytes for the payload message
220 radio.payloadSize = 8
221
222 # for debugging, we have 2 options that print a large block of details
223 # (smaller) function that prints raw register values
224 # radio.printDetails()
225 # (larger) function that prints human readable data
226 # radio.printPrettyDetails()
227
228 try:
229 if args.role is None: # if not specified with CLI arg '-r'
230 while set_role():
231 pass # continue example until 'Q' is entered
232 else: # if role was set using CLI args
233 # run role once and exit
234 if bool(args.role):
235 master()
236 else:
237 slave()
238 except KeyboardInterrupt:
239 print(" Keyboard Interrupt detected. Powering down radio.")
240 radio.powerDown()
Driver class for nRF24L01(+) 2.4GHz Wireless Transceiver.
Definition: RF24.h:116