Skip to content

Commit 4061ac1

Browse files
committed
Update
1 parent 6a11cc4 commit 4061ac1

File tree

1 file changed

+229
-0
lines changed

1 file changed

+229
-0
lines changed
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
---
2+
layout: post
3+
title: Nooelec SDR with Modular Biped for Real-Time 433 MHz IoT Signal Processing
4+
date: 2024-10-28 11:50
5+
categories: [Guides, Software, SDR]
6+
tags: [guide, SDR, IoT, python]
7+
---
8+
9+
As the IoT landscape expands, more devices communicate on the 433 MHz frequency, a common frequency for home automation systems, remote controls, weather stations, and similar IoT devices. With the Modular Biped project, we can leverage an RTL-SDR USB dongle from Nooelec to scan for and process these 433 MHz transmissions, making data accessible for other modules through Python's PubSub library.
10+
11+
This article introduces a custom Python module to interface with the Nooelec SDR, enabling data collection, processing, and publication over a PubSub bus. We’ll cover the SDR module setup, the configuration file, and example output to show how the module interprets and broadcasts data from nearby IoT devices.
12+
13+
### Module Overview
14+
15+
The `RTLSDR` Python module, created for the Modular Biped project, uses the `rtl_433` software to collect data and re-broadcasts information on detected IoT signals. This module starts by initiating an HTTP stream from `rtl_433` and listens to messages. Once configured, it is straightforward to publish or subscribe to specific topics via PubSub.
16+
17+
Here’s a breakdown of the components:
18+
19+
1. **Configuration File:** Defines the IP and port for the HTTP stream, topics for subscribing and publishing data, and dependencies.
20+
2. **Python Module:** Manages the RTL-SDR's processes and parses incoming data for real-time analysis and publication.
21+
3. **Output Example:** Shows how the module captures data like temperature, humidity, and battery status from various IoT devices.
22+
23+
### Configuration File
24+
25+
The configuration file defines key settings and dependencies for the `RTLSDR` module:
26+
27+
```yaml
28+
rtl_sdr:
29+
enabled: true
30+
path: modules.network.rtlsdr.RTLSDR
31+
config:
32+
udp_host: "127.0.0.1"
33+
udp_port: 8433
34+
timeout: 70
35+
topics:
36+
publish_data: "sdr/data"
37+
subscribe_listen: "sdr/listen"
38+
subscribe_start: "sdr/start"
39+
subscribe_stop: "sdr/stop"
40+
dependencies:
41+
unix:
42+
- "rtl-433"
43+
python:
44+
- "pypubsub"
45+
- "requests"
46+
```
47+
48+
- **Host and Port**: Specifies the IP and port for the `rtl_433` HTTP stream.
49+
- **Timeout**: Sets a response timeout of 70 seconds.
50+
- **Topics**: Defines PubSub topics for starting, stopping, and publishing data.
51+
- **Dependencies**: Lists system and Python dependencies required by the module.
52+
53+
### Python Module Code
54+
55+
The following Python code uses the `rtl_433` tool to listen for 433 MHz signals, convert JSON data from these signals, and publish them over a PubSub network:
56+
57+
```python
58+
#!/usr/bin/env python3
59+
60+
import requests
61+
import json
62+
import subprocess
63+
from time import sleep
64+
from pubsub import pub
65+
66+
class RTLSDR:
67+
def __init__(self, **kwargs):
68+
self.udp_host = kwargs.get('udp_host', "127.0.0.1")
69+
self.udp_port = kwargs.get('udp_port', 8433)
70+
self.timeout = kwargs.get('timeout', 70)
71+
self.topics = kwargs.get('topics')
72+
self.rtl_process = None
73+
pub.subscribe(self.start_rtl_433, self.topics['subscribe_start'])
74+
pub.subscribe(self.listen_once, self.topics['subscribe_listen'])
75+
pub.subscribe(self.stop_rtl_433, self.topics['subscribe_stop'])
76+
77+
def start_rtl_433(self):
78+
"""Starts the rtl_433 process with HTTP (line) streaming enabled."""
79+
if self.rtl_process is None:
80+
try:
81+
self.rtl_process = subprocess.Popen(
82+
["rtl_433", "-F", f"http://{self.udp_host}:{self.udp_port}"],
83+
stdout=subprocess.PIPE,
84+
stderr=subprocess.PIPE
85+
)
86+
print(f"Started rtl_433 on {self.udp_host}:{self.udp_port}")
87+
except FileNotFoundError:
88+
print("rtl_433 command not found. Please ensure rtl_433 is installed.")
89+
else:
90+
print("rtl_433 is already running.")
91+
92+
def stop_rtl_433(self):
93+
"""Stops the rtl_433 process if it is running."""
94+
if self.rtl_process:
95+
self.rtl_process.terminate()
96+
self.rtl_process.wait()
97+
self.rtl_process = None
98+
print("Stopped rtl_433 process.")
99+
else:
100+
print("rtl_433 is not currently running.")
101+
102+
def stream_lines(self):
103+
"""Stream lines from rtl_433's HTTP API."""
104+
url = f'http://{self.udp_host}:{self.udp_port}/stream'
105+
headers = {'Accept': 'application/json'}
106+
try:
107+
response = requests.get(url, headers=headers, timeout=self.timeout, stream=True)
108+
print(f'Connected to {url}')
109+
for chunk in response.iter_lines():
110+
yield chunk
111+
except requests.ConnectionError:
112+
print("Failed to connect to rtl_433 HTTP stream.")
113+
self.stop_rtl_433()
114+
115+
def handle_event(self, line):
116+
"""Process each JSON line from rtl_433."""
117+
try:
118+
data = json.loads(line)
119+
print(data)
120+
pub.sendMessage(self.topics['publish_data'], data=data)
121+
label = data.get("model", "Unknown")
122+
if "channel" in data:
123+
label += ".CH" + str(data["channel"])
124+
elif "id" in data:
125+
label += ".ID" + str(data["id"])
126+
127+
if data.get("battery_ok") == 0:
128+
print(f"{label} Battery empty!")
129+
if "temperature_C" in data:
130+
print(f"{label} Temperature: {data['temperature_C']}°C")
131+
if "humidity" in data:
132+
print(f"{label} Humidity: {data['humidity']}%")
133+
134+
except json.JSONDecodeError:
135+
print("Failed to decode JSON line:", line)
136+
137+
def listen_once(self):
138+
"""Listen to one chunk of the rtl_433 stream."""
139+
for chunk in self.stream_lines():
140+
chunk = chunk.rstrip()
141+
if chunk:
142+
self.handle_event(chunk)
143+
144+
def rtl_433_listen(self):
145+
"""Listen to rtl_433 messages in a loop until stopped."""
146+
self.start_rtl_433()
147+
try:
148+
while True:
149+
try:
150+
self.listen_once()
151+
except requests.ConnectionError:
152+
print("Connection failed, retrying in 5 seconds...")
153+
sleep(5)
154+
finally:
155+
self.stop_rtl_433()
156+
157+
if __name__ == "__main__":
158+
try:
159+
sdr = RTLSDR(topics={
160+
'subscribe_listen': 'sdr/listen',
161+
'publish_data': 'sdr/data',
162+
'subscribe_start': 'sdr/start',
163+
'subscribe_stop': 'sdr/stop'
164+
})
165+
sdr.rtl_433_listen()
166+
except KeyboardInterrupt:
167+
print('\nExiting.')
168+
sdr.stop_rtl_433()
169+
```
170+
171+
### Example Output
172+
173+
Below is an example of how the module interprets and outputs data. This example includes different devices detected by the SDR dongle, showing each device's model, temperature, humidity, and other metadata:
174+
175+
```shell
176+
archie@archie:~/modular-biped $ python modules/network/rtlsdr.py
177+
Started rtl_433 on 127.0.0.1:8433
178+
Connected to http://127.0.0.1:8433/stream
179+
{'time': '2024-10-28 11:39:04', 'model': 'Esperanza-EWS', 'id': 11, 'channel': 2, 'battery_ok': 1, 'temperature_F': 67.6, 'humidity': 0, 'mic': 'CRC'}
180+
Esperanza-EWS.CH2 Humidity: 0%
181+
{'time': '2024-10-28 11:39:57', 'model': 'Esperanza-EWS', 'id': 11, 'channel': 2, 'battery_ok': 1, 'temperature_F': 67.6, 'humidity': 0, 'mic': 'CRC'}
182+
Esperanza-EWS.CH2 Humidity: 0%
183+
{'time': '2024-10-28 11:40:50', 'model': 'Esperanza-EWS', 'id': 11, 'channel': 2, 'battery_ok': 1, 'temperature_F': 67.6, 'humidity': 0, 'mic': 'CRC'}
184+
Esperanza-EWS.CH2 Humidity: 0%
185+
{'time': '2024-10-28 11:41:28', 'model': 'Renault', 'type': 'TPMS', 'id': 'fa6ec6', 'flags': '05', 'pressure_kPa': 192.0, 'temperature_C': -30, 'mic': 'CRC'}
186+
Renault.IDfa6ec6 Temperature: -30°C
187+
{'time': '2024-10-28 11:42:36', 'model': 'Esperanza-EWS', 'id': 11, 'channel': 2, 'battery_ok': 1, 'temperature_F': 67.4, 'humidity': 0, 'mic': 'CRC'}
188+
Esperanza-EWS.CH2 Humidity: 0%
189+
{'time': '2024-10-28 11:43:06', 'model': 'Renault', 'type': 'TPMS', 'id': 'fa6ec6', 'flags': '05', 'pressure_kPa': 192.0, 'temperature_C': -30, 'mic': 'CRC'}
190+
Renault.IDfa6ec6 Temperature: -30°C
191+
{'time': '2024-10-28 11:43:06', 'model': 'Renault', 'type': 'TPMS', 'id': 'fa6ec6', 'flags': '05', 'pressure_kPa': 192.0, 'temperature_C': -30, 'mic': 'CRC'}
192+
Renault.IDfa6ec6 Temperature: -30°C
193+
{'time': '2024-10-28 11:43:07', 'model': 'Renault', 'type': 'TPMS', 'id': 'fa6ec6', 'flags': '05', 'pressure_kPa': 192.0, 'temperature_C': -30, 'mic': 'CRC'}
194+
Renault.IDfa6ec6 Temperature: -30°C
195+
{'time': '2024-10-28 11:43:07', 'model': 'Renault', 'type': 'TPMS', 'id': 'fa6ec6', 'flags': '05', 'pressure_kPa': 192.0, 'temperature_C': -30, 'mic': 'CRC'}
196+
Renault.IDfa6ec6 Temperature: -30°C
197+
{'time': '2024-10-28 11:43:10', 'model': 'Truck', 'type': 'TPMS', 'id': '50000c66', 'wheel': 239, 'pressure_kPa': 0, 'temperature_C': 0, 'state': 1, 'flags': 10, 'mic': 'CHECKSUM'}
198+
Truck.ID50000c66 Temperature: 0°C
199+
{'time': '2024-10-28 11:43:10', 'model': 'Truck', 'type': 'TPMS', 'id': '50000c66', 'wheel': 239, 'pressure_kPa': 0, 'temperature_C': 0, 'state': 1, 'flags': 10, 'mic': 'CHECKSUM'}
200+
Truck.ID50000c66 Temperature: 0°C
201+
{'time': '2024-10-28 11:43:12', 'model': 'Renault', 'type': 'TPMS', 'id': 'fa6ec6', 'flags': '05', 'pressure_kPa': 192.0, 'temperature_C': -30, 'mic': 'CRC'}
202+
Renault.IDfa6ec6 Temperature: -30°C
203+
{'time': '2024-10-28 11:43:12', 'model': 'Renault', 'type': 'TPMS', 'id': 'fa6ec6', 'flags': '05', 'pressure_kPa': 192.0, 'temperature_C': -30, 'mic': 'CRC'}
204+
Renault.IDfa6ec6 Temperature: -30°C
205+
{'time': '2024-10-28 11:43:13', 'model': 'Abarth-124Spider', 'type': 'TPMS', 'id': '150000c6', 'flags': '6e', 'pressure_kPa': 345.0, 'temperature_C': -48, 'status': 64, 'mic': 'CHECKSUM'}
206+
Abarth-124Spider.ID150000c6 Temperature: -48°C
207+
{'time': '2024-10-28 11:43:13', 'model': 'Abarth-124Spider', 'type': 'TPMS', 'id': '150000c6', 'flags': '6e', 'pressure_kPa': 345.0, 'temperature_C': -48, 'status': 64, 'mic': 'CHECKSUM'}
208+
Abarth-124Spider.ID150000c6 Temperature: -48°C
209+
{'time': '2024-10-28 11:43:32', 'model': 'Renault', 'type': 'TPMS', 'id': 'fa6ec6', 'flags': '05', 'pressure_kPa': 192.0, 'temperature_C': -30, 'mic': 'CRC'}
210+
Renault.IDfa6ec6 Temperature: -30°C
211+
{'time': '2024-10-28 11:43:32', 'model': 'Renault', 'type': 'TPMS', 'id': 'fa6ec6', 'flags': '05', 'pressure_kPa': 192.0, 'temperature_C': -30, 'mic': 'CRC'}
212+
Renault.IDfa6ec6 Temperature: -30°C
213+
{'time': '2024-10-28 11:43:35', 'model': 'Truck', 'type': 'TPMS', 'id': '50000c66', 'wheel': 239, 'pressure_kPa': 0, 'temperature_C': 0, 'state': 1, 'flags': 10, 'mic': 'CHECKSUM'}
214+
Truck.ID50000c66 Temperature: 0°C
215+
{'time': '2024-10-28 11:43:35', 'model': 'Truck', 'type': 'TPMS', 'id': '50000c66', 'wheel': 239, 'pressure_kPa': 0, 'temperature_C': 0, 'state': 1, 'flags': 10, 'mic': 'CHECKSUM'}
216+
Truck.ID50000c66 Temperature: 0°C
217+
{'time': '2024-10-28 11:43:36', 'model': 'Truck', 'type': 'TPMS', 'id': '50000c66', 'wheel': 239, 'pressure_kPa': 0, 'temperature_C': 0, 'state': 1, 'flags': 10, 'mic': 'CHECKSUM'}
218+
Truck.ID50000c66 Temperature: 0°C
219+
{'time': '2024-10-28 11:43:36', 'model': 'Truck', 'type': 'TPMS', 'id': '50000c66', 'wheel': 239, 'pressure_kPa': 0, 'temperature_C': 0, 'state': 1, 'flags': 10, 'mic': 'CHECKSUM'}
220+
Truck.ID50000c66 Temperature: 0°C
221+
{'time': '2024-10-28 11:43:36', 'model': 'Truck', 'type': 'TPMS', 'id': '50000c66', 'wheel': 239, 'pressure_kPa': 0, 'temperature_C': 0, 'state': 1, 'flags': 10, 'mic': 'CHECKSUM'}
222+
Truck.ID50000c66 Temperature: 0°C
223+
{'time': '2024-10-28 11:43:36', 'model': 'Truck', 'type': 'TPMS', 'id': '50000c66', 'wheel': 239, 'pressure_kPa': 0, 'temperature_C': 0, 'state': 1, 'flags': 10, 'mic': 'CHECKSUM'}
224+
Truck.ID50000c66 Temperature: 0°C
225+
```
226+
227+
### Conclusion
228+
229+
This `RTLSDR` module is an efficient way to expand the Modular Biped project with IoT connectivity using a Nooelec SDR USB dongle. By harnessing the 433 MHz frequency, this module enables real-time data publication over PubSub, allowing other modules to interact with and respond to nearby IoT devices seamlessly.

0 commit comments

Comments
 (0)