-
Notifications
You must be signed in to change notification settings - Fork 1
/
rfm69_tx_test.py
executable file
·273 lines (237 loc) · 13.3 KB
/
rfm69_tx_test.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright 2020-2021 by Murray Altheim. All rights reserved. This file is part
# of the Robot Operating System project, released under the MIT License. Please
# see the LICENSE file included as part of this package.
#
# author: Murray Altheim
# created: 2021-10-02
# modified: 2021-10-03
#
# An asynchronous RFM69 transceiver.
#
# See notes at bottom of file.
# pylint: disable=missing-function-docstring,unused-import,redefined-outer-name
import sys, signal, time, traceback, itertools
import asyncio
from RFM69 import Radio, FREQ_868MHZ, FREQ_915MHZ
import RPi.GPIO as GPIO
from colorama import init, Fore, Style
init(autoreset=True)
from core.config_loader import ConfigLoader
from core.message_factory import MessageFactory
from core.logger import Logger, Level
from mock.message_bus import MockMessageBus
# execution handler ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
def signal_handler(signal, frame):
print('\nsignal handler :' + Fore.MAGENTA + Style.BRIGHT + ' INFO : Ctrl-C caught: exiting...')
print(Fore.MAGENTA + 'exit.')
sys.exit(0)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
class Rfm69Radio(object):
'''
'''
def __init__(self, config, message_bus, message_factory, level=Level.INFO):
self._log = Logger('radio', level)
if config is None:
raise ValueError('no configuration provided.')
_cfg = config['kros'].get('hardware').get('rfm69_radio')
self._message_bus = message_bus
self._message_factory = message_factory
self._loop = self._message_bus.loop
# configuration ..............
self._frequency_name = _cfg.get('frequency') # either 'FREQ_868MHZ' or default to FREQ_915MHZ
if self._frequency_name == 'FREQ_868MHZ':
self._log.info('frequency:\t868MHz')
self._frequency = FREQ_868MHZ
else:
self._log.info('frequency:\t915MHz')
self._frequency = FREQ_915MHZ
self._spi_bus = _cfg.get('spi_bus') # 0
self._log.info('SPI bus: \t{:d}'.format(self._spi_bus))
self._spi_device = _cfg.get('spi_device') # 0
self._log.info('SPI device: \t{:d}'.format(self._spi_device))
self._network_id = _cfg.get('network_id') # 100
self._log.info('network ID: \t{:d}'.format(self._network_id))
self._node_id = _cfg.get('node_id') # the node ID of this device
self._log.info('node ID: \t{:d}'.format(self._node_id))
self._rx_id = _cfg.get('recipient_id') # identifier for target of messages
self._log.info('recipient ID: \t{:d}'.format(self._rx_id))
self._int_pin = _cfg.get('interrupt_pin') # BOARD 18/GPIO 24
self._log.info('interrupt pin: \t{:d}'.format(self._int_pin))
self._reset_pin = _cfg.get('reset_pin') # BOARD 29/GPIO 5
self._log.info('reset pin: \t{:d}'.format(self._reset_pin))
self._attempts = _cfg.get('attempts') # 3 attempts default
self._log.info('attempts: \t{:d}'.format(self._attempts))
self._timeout_ms = _cfg.get('timeout_ms') # 100ms default
self._log.info('wait time: \t{:d}ms'.format(self._timeout_ms))
self._prom_mode = _cfg.get('promiscuous_mode') # bool
self._log.info('listen mode: \t{}'.format('promiscuous' if self._prom_mode else 'normal'))
self._tx_enabled = _cfg.get('transmit_enabled')
self._log.info('operation mode: \t{}'.format('transmit/receive' if self._tx_enabled else 'receive only'))
self._auto_acknowledge = False
self._high_power = False
self._sent_counter = itertools.count()
self._ack_counter = itertools.count()
self._no_ack_counter = itertools.count()
self._tx_counter = itertools.count()
self._rx_counter = itertools.count()
self._enabled = False
# configure reset pin for radio (LOW is enabled)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(self._reset_pin, GPIO.OUT)
self._log.info('ready')
# ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
def _receive_function(self, radio, f_is_enabled):
'''
We'll run this function in a separate thread.
'''
self._log.info(Fore.BLUE + "🌎 _receive_function() begin.")
while f_is_enabled():
self._log.info(Fore.BLUE + "🌎 _receive_function() listening in loop...")
# This call will block until a packet is received
_packet = radio.get_packet()
self._log.info(Fore.YELLOW + "Got a packet: ", end="")
# process packet
self._log.info(Fore.WHITE + '{}'.format(_packet))
# self._log.info(Fore.BLUE + 'sending ack')
# radio.send_ack(self._rx_id)
self._log.info(Fore.BLUE + "🌎 _receive_function() end of loop.")
self._log.info("exit Rx loop.")
# ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
def _reset_radio(self, radio):
'''
Perform a hard reset on the RFM69 radio.
'''
if radio:
self._log.info('🤡 executing hard reset on radio...')
try:
radio._reset_radio()
self._log.info('🤡 hard reset complete.')
except Exception as e:
self._log.info('🤡 error performing hard reset: {}'.format(e))
else:
self._log.info('🤡 no radio available.')
# ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
async def _receive(self, radio, f_is_enabled):
self._log.info(Fore.GREEN + 'receive begin.')
while f_is_enabled():
self._log.info(Fore.GREEN + Style.DIM + 'receive loop.')
for _packet in radio.get_packets():
_rx_count = next(self._rx_counter)
self._log.info(Fore.GREEN + '[{:04d}] 🌎 packet received: {}'.format(_rx_count, _packet.to_dict()))
_message = self.message_factory.create_message(Event.RADIO_PACKET, _packet)
await self._message_bus.publish_message(self, _message)
# await call_API("http://httpbin.org/post", packet)
await asyncio.sleep(1.0)
self._log.info(Fore.GREEN + 'receive end.')
# ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
async def _transmit(self, radio, f_is_enabled):
self._log.info(Fore.BLUE + 'transmit begin.')
while f_is_enabled():
_tx_count = next(self._tx_counter)
self._log.info(Fore.BLUE + '[{:04d}] transmitting message...'.format(_tx_count))
await self._send(radio, "ping-{:04d}".format(_tx_count))
await asyncio.sleep(5)
self._log.info(Fore.BLUE + 'transmit end.')
# ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
async def _send(self, radio, message):
_sent_count = next(self._sent_counter)
self._log.info(Fore.MAGENTA + '[{:04d}] sending message: \'{}\' to address {}'.format(
_sent_count, message, self._rx_id))
if radio.send(self._rx_id, message, attempts=self._attempts, waitTime=self._timeout_ms):
_ack_count = next(self._ack_counter)
self._log.info(Fore.MAGENTA + Style.BRIGHT + 'acknowledgement received.'.format(_ack_count))
else:
_no_ack_count = next(self._no_ack_counter)
self._log.info(Fore.MAGENTA + Style.DIM + '[{:04d}] no acknowledgement.'.format(_no_ack_count))
# ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
def enable(self):
if self._enabled:
self._log.warning("already enabled.")
return
self._enabled = True
self._log.info("📡 enabling RFM69 radio...")
GPIO.output(self._reset_pin, GPIO.LOW)
time.sleep(1.0)
_radio = None
try:
self._log.info("📡 establishing link to radio...")
with Radio(self._frequency,
spiBus = self._spi_bus,
spiDevice = self._spi_device,
interruptPin = self._int_pin,
resetPin = self._reset_pin,
nodeID = self._node_id,
networkID = self._network_id,
auto_acknowledge = self._auto_acknowledge,
isHighPower = self._high_power,
verbose = True,
promiscuousMode = self._prom_mode ) as _radio:
self._log.info("📡 established link to radio.")
self._log.info('creating receive task...')
self._loop.create_task(self._receive(_radio, lambda: self._enabled))
self._log.info('creating transmit task...')
self._loop.create_task(self._transmit(_radio, lambda: self._enabled))
self._log.info('starting forever loop:\t' + Fore.YELLOW + 'type Ctrl-C to exit.')
self._loop.run_forever()
self._log.info('after forever.')
except KeyboardInterrupt:
self._log.info(Style.BRIGHT + 'caught Ctrl-C; exiting...')
except Exception:
self._log.error(Fore.RED + Style.BRIGHT + '🤡 error with radio: {}'.format(traceback.format_exc()))
# self._reset_radio(_radio)
finally:
self._log.info('closing...')
self._enabled = False
self._log.info('finally; enabled: {}'.format(self._enabled))
self._loop.close()
time.sleep(2.0)
self._log.info('closed.')
# end class ........................
''' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ NOTES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Uses RFM69 library: https://rpi-rfm69.readthedocs.io/en/latest/
Installed via:
sudo pip3 install rpi-rfm69
Installed at: /usr/local/lib/python3.8/site-packages/RFM69/radio.py
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
Notes on the Raspberry Pi Wiring:
__RFM69_____BOARD pin____
EN (not connected, default high)
G0 28 (ID_SC)
SCK 23 (SCLK, GPIO 11)
MISO 21 (MISO, GPIO 9)
MOSI 19 (MOSI, GPIO 10)
CS 24 (CE0, GPIO 8)
RST 29 (GPIO 5)
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
Notes on the Pimoroni Breakout Garden:
On the single SPI slot version:
The SPI slot on Breakout Garden Mini uses:
* chip select 1 (BCM 7) and
* the GPIO pin (BCM 19) for things like LCD backlights
On the double SPI slot version:
The top/back slot (closest to the Breakout Garden logo) uses:
* chip select 0 (BCM 8) and
* the GPIO pin (BCM 18) for things like LCD backlights
The bottom/front slot uses
* chip select 1 (BCM 7) and
* the GPIO pin (BCM 19) for things like LCD backlights
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ '''
# main ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
def main(argv):
signal.signal(signal.SIGINT, signal_handler)
# read YAML configuration
_level = Level.INFO
_loader = ConfigLoader(_level)
filename = 'config.yaml'
_config = _loader.configure(filename)
_message_bus = MockMessageBus()
_message_factory = MessageFactory(_message_bus, _level)
_radio = Rfm69Radio(_config, _message_bus, _level)
_radio.enable()
# ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
if __name__== "__main__":
main(sys.argv[1:])
#EOF