A "drop-in" bridge that turns one or more RTL-SDR dongles into Home Assistant-friendly sensors via MQTT.
Unlike standard rtl_433 scripts, this project captures detailed signal metrics (RSSI, SNR, Noise) for every received packet, allowing you to troubleshoot interference and optimize antenna placement directly from Home Assistant.
It also functions as a System Monitor, reporting the host machine's health (CPU, RAM, Disk, Temp) and the live status of the radio dongle itself, giving you a complete view of your hardware's performance in one place.
See rtl_433 documentation for supported devices: https://github.com/merbanan/rtl_433
-
Auto Multi-Radio (plug-and-go): If you leave
rtl_configempty and the add-on detects multiple RTL-SDR dongles, it will automatically start 1–3rtl_433instances (based on how many dongles you plug in).- Zero-clutter UI: the only auto-mode knob in the add-on UI is the Region / Band Plan dropdown:
rtl_auto_band_plan: auto|us|eu|world. - Sharing SDRs / reserving a dongle: In auto mode, RTL-HAOS will use additional RTL-SDR dongles if they’re plugged in.
If you need to keep a stick free for another add-on/app (ADS-B, another rtl_433 stack, etc.), switch to manual mode by defining
rtl_configso RTL-HAOS only claims the dongle(s) you specify. - Radio #1: default 433.92 MHz (typical sensors)
- Radio #2: region-aware high-band:
- EU/UK/EEA/CH: 868 MHz
- US/CA/AU/NZ (and most others): 915 MHz
- If Home Assistant country is unknown, it will hop 868/915 on Radio #2
- Radio #3 (if present): a regional hopper for “interesting” bands that does not intentionally overlap Radio 1/2.
- Want full control of rates / hop intervals / exact freqs? Define
rtl_config(manual mode) and you are responsible for the complete radio configuration.
- Zero-clutter UI: the only auto-mode knob in the add-on UI is the Region / Band Plan dropdown:
-
Zero-Config Discovery: Sensors appear automatically in Home Assistant (via MQTT Discovery) with correct units, icons, and friendly names.
-
Signal Diagnostics: Reports RSSI, SNR, and Noise Floor for every device, making it easy to identify weak signals, plot coverage ranges, or hunt down interference sources.
-
Smart System Monitor:
- Reports Host CPU, RAM, Disk, and Temperature.
- Live Radio Status: Shows
"Scanning...","Online", or error states like"No Device Found"/"Error: USB Busy"for each radio, grouped with the host device.
-
Native Graphing: Environmental sensors use the correct
measurementstate class, and meter-like fields usetotal_increasing, so graphs and statistics in Home Assistant just work for temperatures, humidity, pressure, light, and utility counters. -
Noise Reduction:
- Data Averaging: Buffers readings (e.g., every 30s) to prevent database bloat from sensors that spam updates every second.
- Filtering: Built-in Whitelist and Blacklist support to ignore your neighbor's tire pressure sensors.
-
Advanced Data:
- Dew Point: Automatically calculated from Temp + Humidity sensors.
- Multi-Radio Support: Run multiple dongles on different frequencies simultaneously.
-
Advanced rtl_433 passthrough: Optionally pass arbitrary
rtl_433flags and/or a fullrtl_433config file (-c) globally or per radio (gain/AGC/ppm/tuner settings/decoder selection). -
Stable version comparisons + richer display: Base add-on version stays
X.Y.Zfor Supervisor, while logs/device info can showvX.Y.Z+<build>. -
New diagnostics: Bridge publishes the running
rtl_433version as a diagnostic sensor (rtl_433 Version).
Note (multi-dongle setups): If you plug in multiple RTL-SDR dongles and leave
rtl_config: [], RTL-HAOS will start multiple radios automatically. To force RTL-HAOS to use only one specific stick (and leave others for other software), definertl_config(manual mode).
- Configuration: docs/CONFIG.md
- MQTT topics & discovery: docs/MQTT.md
- Multi-radio setup: docs/MULTI_RADIO.md
- Development & testing: docs/DEVELOPMENT.md
graph TD
%% RF emissions
subgraph "RF Devices (Airwaves)"
A[Weather Station] -->|433/868/915 MHz| ANT[Antenna]
B[Motion Sensor] -->|315/433 MHz| ANT
C[Utility Meter] -->|868/915 MHz| ANT
end
%% Option A: Local USB SDR
subgraph "Option A: Local USB SDR"
USBSDR[RTL-SDR Dongle]
end
ANT -->|Local USB| USBSDR
%% Option B: Remote SDR over rtl_tcp
subgraph "Option B: Remote SDR Host (rtl_tcp)"
RSDR[RTL-SDR Dongle] -->|IQ samples| RTLTCP[rtl_tcp server]
end
ANT -->|Remote SDR| RSDR
%% RTL-HAOS host runs rtl_433 + the bridge
subgraph "RTL-HAOS Host (HAOS Add-on / Docker / Linux)"
USBSDR -->|USB| RTL433[rtl_433]
RTLTCP -->|TCP port 1234| RTL433
RTL433 -->|Decoded JSON packets| BRIDGE[RTL-HAOS Bridge]
STATS[CPU/RAM/Disk/Temp] --> BRIDGE
end
%% MQTT and Home Assistant
BRIDGE -->|MQTT Discovery + State Topics| MQTT[MQTT Broker]
subgraph "Home Assistant"
MQTT --> ENT[Sensor Entities]
MQTT --> MON[System Monitor + Diagnostics]
end
%% Styling
style BRIDGE fill:#f96,stroke:#333,stroke-width:4px,color:black
style MQTT fill:#bbdefb,stroke:#0d47a1,stroke-width:4px,color:black
style ENT fill:#5fb,stroke:#333,stroke-width:2px,color:black
style MON fill:#cfd8dc,stroke:#333,stroke-width:2px,color:black
| System Monitor | Entity View |
|---|---|
![]() |
![]() |
The signal boost between 8:00 AM and 5:00 PM is due to the AcuRite 5-in-1 internal supercapacitor charging via solar panel.
main.py– Main entrypoint (starts radio manager(s), MQTT publishing, system monitoring).rtl_manager.py– Spawns/monitorsrtl_433process(es) and handles multi-radio orchestration.data_processor.py– Handles buffering, averaging, and throttling.mqtt_handler.py– MQTT connection + Home Assistant MQTT Discovery publishing.system_monitor.py/sensors_system.py– Host stats + radio status publishing.field_meta.py– Field metadata (units, device class, icons, etc.).utils.py– Shared helpers (IDs, formatting, derived fields like dew point).docs/– Deeper documentation (config, MQTT, multi-radio, development/testing).scripts/– Developer helper scripts (test venv, fixture recording).tests/– Unit tests + opt-in integration/hardware tests.docker-compose.yml– Docker/Compose runtime.repository.yaml– Home Assistant add-on repository descriptor.
- Host: Raspberry Pi (3/4/5), Mini PC, Proxmox, or any Linux machine.
- Radio: RTL-SDR USB Dongle (RTL-SDR Blog V3, Nooelec, etc.).
Full configuration reference: docs/CONFIG.md.
Note: If you're using the Home Assistant Add-on, skip this section and go directly to Installation. Add-on configuration is done through the Home Assistant UI.
This section applies to Docker and Native installation methods only.
Upgrading from v1.1.14 (or earlier)?
- Displayed version may include
+<build>(example:v1.2.0-rc.2+046cc83). This is only build metadata for logs/device info; the base add-onversion:inconfig.yamlremains a SemVer base version (X.Y.ZorX.Y.Z-PRERELEASE) for Home Assistant Supervisor comparisons. - New optional rtl_433 passthrough knobs are available (global env vars and per-radio fields) if you need manual gain/AGC/ppm/tuner settings or a full
rtl_433config file. - Add-on now maps
/share, so you can droprtl_433config files into your HAOS share and reference them from RTL-HAOS.
git clone https://github.com/jaronmcd/rtl-haos.git
cd rtl-haos
# Copy and edit the environment file
cp .env.example .env
nano .envAll configuration is done via environment variables in a .env file.
At minimum, you need to configure your MQTT broker connection:
# --- MQTT ---
MQTT_HOST="192.168.1.100"
MQTT_USER=mqtt_user
MQTT_PASS=passwordrtl_433 passthrough (advanced tuning):
Use these when you need manual rtl_433 controls (gain/AGC/ppm/tuner settings) or want to supply a full rtl_433 config file (-c).
# Global overrides applied to every rtl_433 invocation
# Any option set here overrides the same option set per-radio/auto defaults; RTL-HAOS logs a WARNING per radio.
RTL_433_ARGS='-g 40 -p 0'
# Provide a config file passed via -c
# - Docker/Native: relative to your working directory
# - HA add-on: prefer putting files in /share and referencing them (e.g. rtl_433.conf)
RTL_433_CONFIG_PATH='rtl_433.conf'
# Or supply inline config content (RTL-HAOS writes a temp file and passes it via -c)
RTL_433_CONFIG_INLINE=$'-g 25\n-R 104\n'
# Optional override for rtl_433 binary path/name
RTL_433_BIN='rtl_433'Per-radio tuning is also supported via rtl_config (fields like args, device, config_path, config_inline, bin). If an option is present in RTL_433_ARGS, it wins over the same option set per-radio/defaults and RTL-HAOS will log a WARNING indicating the override. See docs/CONFIG.md for examples.
Build metadata (optional):
# Appended to the displayed version as SemVer build metadata: vX.Y.Z+BUILD
# (Does not change Supervisor update comparisons)
RTL_HAOS_BUILD='dev.local'Multi-Radio Setup:
Note: If you only have one RTL-SDR, no other radio configuration is needed. The bridge will automatically try to read the dongle's serial with
rtl_eepromand use that. If it cannot detect a serial, it falls back to device indexid = "0".
For multiple RTL-SDR dongles on different frequencies:
# Multiple USB radios
RTL_CONFIG='[{"name": "Weather Radio", "id": "101", "freq": "433.92M", "rate": "250k"}, {"name": "Utility Meter", "id": "102", "freq": "915M", "rate": "250k"}]'TCP Source Setup (rtl_tcp):
You can also connect to a remote SDR source using rtl_tcp instead of a local USB dongle:
# TCP source (e.g., from Ultrafeeder or another rtl_tcp server)
RTL_CONFIG='[{"name": "Remote SDR", "tcp_host": "192.168.1.10", "tcp_port": 1234, "freq": "433.92M", "rate": "250k"}]'When using TCP mode:
- No USB device is required (
--privilegedand--deviceflags are not needed) - The
tcp_hostandtcp_portparameters specify the remote rtl_tcp server - Default port is 1234 if
tcp_portis not specified - You can mix USB and TCP sources in the same configuration
Device Filtering:
Block or allow specific device patterns:
# Device filtering (block specific devices)
DEVICE_BLACKLIST='["SimpliSafe*", "EezTire*"]'
# Device filtering (allow only specific devices - optional)
DEVICE_WHITELIST='["Acurite-5n1*", "AmbientWeather*"]'Pattern syntax (for DEVICE_WHITELIST / DEVICE_BLACKLIST):
- Patterns use shell-style glob matching (Python
fnmatch):*,?, and[]character classes. - Patterns are matched against the decoded device's ID, model, and type fields.
- Matching is case-insensitive.
- Regular expressions (e.g.,
^...$) are not supported.
Examples:
- Whitelist by exact model:
DEVICE_WHITELIST='["Cotech-367959"]' - Whitelist by prefix:
DEVICE_WHITELIST='["Cotech*"]' - Whitelist by ID:
DEVICE_WHITELIST='["101"]'
Misc Configuration:
# Toggle "Last: HH:MM:SS" vs "Online" in status
RTL_SHOW_TIMESTAMPS=false
# Print rtl_433 JSON output
DEBUG_RAW_JSON=trueSee .env.example for all available configuration options.
The easiest way to use rtl-haos with Home Assistant OS is as an add-on.
In Home Assistant:
- Navigate to Settings → Add-ons → Add-on Store
- Click the ⋮ menu (top right) → Repositories
- Add this repository URL:
https://github.com/jaronmcd/rtl-haos - Click Add
- Find "RTL-HAOS" in the add-on store
- Click on it and select Install
- Wait for the installation to complete
Go to the Configuration tab and set your options:
Basic Configuration:
# MQTT Settings
mqtt_host: "" # Leave blank to auto-use the Home Assistant MQTT service (or set to core-mosquitto / broker IP)
mqtt_user: your_mqtt_user
mqtt_pass: your_mqtt_password
# Bridge identity (keeps your HA device stable)
bridge_id: "42"
bridge_name: "rtl-haos-bridge"Note: If
mqtt_hostis left blank, the add-on will try to use Home Assistant's Mosquitto service.
Advanced Configuration (Optional, default values shown below):
# Publishing Settings
rtl_expire_after: 600 # Seconds before sensor marked unavailable
rtl_throttle_interval: 30 # Seconds to buffer/average data (0 = realtime)
debug_raw_json: false # Print raw rtl_433 JSON for debugging
# Battery alert behavior (battery_ok -> Battery Low binary_sensor)
# 0 clears immediately on next OK
battery_ok_clear_after: 300
# Note: Battery Low uses a long MQTT expire_after (24h+) to avoid going 'unavailable' for devices that report battery infrequently.
# Multi-Radio Configuration (leave empty for auto-detection)
rtl_config:
- name: Weather Radio # Friendly name
id: "101" # RTL-SDR USB serial number
freq: 433.92M # Frequency
rate: 250k # Sample rate (optional)
- name: Utility Meter
id: "102"
freq: 915M
rate: 250k
# rtl_tcp mode (network SDR): run rtl_tcp on another host and point RTL-HAOS at it.
# You can use either `device: "rtl_tcp:HOST:PORT"` or the explicit tcp_host/tcp_port keys.
- name: Remote SDR (rtl_tcp)
freq: 433.92M
rate: 250k
device: "rtl_tcp:192.168.1.10:1234"
# tcp_host: "192.168.1.10"
# tcp_port: 1234
# Device Filtering
device_blacklist: # Block specific device patterns
- "SimpliSafe*"
- "EezTire*"
device_whitelist: [] # If set, only allow these patterns
# Pattern syntax
# - Patterns use shell-style glob matching (fnmatch): *, ?, and [] character classes.
# - Patterns are matched against the decoded device's ID, model, and type fields.
# - Matching is case-insensitive.
# - Regular expressions (e.g., ^...$) are not supported.
# Examples:
# - "Cotech-367959" # exact model
# - "Cotech*" # model prefix
# - "101" # exact IDNote: If rtl_config is empty (rtl_config: []), RTL-HAOS runs in auto mode and will attach to detected RTL-SDR dongles automatically. Define rtl_config to pin specific radios, run multiple radios, or to use rtl_tcp.
- Go to the Info tab
- Enable Start on boot (recommended)
- Click Start
- Check the Log tab to verify it's running
Once you have set up your configuration, you can run rtl-haos with Docker.
docker compose up -dDocker Compose note (USB vs rtl_tcp): The provided
docker-compose.ymlis configured for rtl_tcp / network SDR mode by default and does not mount USB devices. If you are using a locally attached USB RTL-SDR, uncommentprivileged: trueand the/dev/bus/usbdevice mapping indocker-compose.yml.
# Build the image
docker build -t rtl-haos .
# Run the container (USB mode)
docker run -d \
--name rtl-haos \
--restart unless-stopped \
--privileged \
--device /dev/bus/usb:/dev/bus/usb \
--env-file .env \
rtl-haos
# Run the container (TCP mode - no USB device needed)
docker run -d \
--name rtl-haos \
--restart unless-stopped \
--env-file .env \
rtl-haosNote: The
--privilegedand--deviceflags are required for USB access to the RTL-SDR dongle. When using TCP mode (rtl_tcp), these flags are not needed.
docker compose logs -f
# or
docker logs -f rtl-haosInstall Python and the rtl_433 binary.
# Debian / Ubuntu / Raspberry Pi OS
sudo apt update
sudo apt install -y rtl-sdr rtl-433 git python3 python3-pip curlcurl -LsSf https://astral.sh/uv/install.sh | sh# Update your Shell
source ~/.bashrc
# From the rtl-haos directory
uv syncMQTT topic details: docs/MQTT.md.
Once the add-on is running, sensors will automatically appear in Home Assistant via MQTT Discovery. Check the add-on logs for status:
- Navigate to Settings → Add-ons → RTL-HAOS
- Click on the Log tab
docker compose up -d
docker compose logs -fcd rtl-haos
uv run python main.pyExpected Output:
[STARTUP] Connecting to MQTT Broker at 192.168.1.100...
[MQTT] Connected Successfully.
[RTL] Manager started for Weather Radio. Monitoring...
-> TX RaspberryPi (b827eb...) [radio_status_0]: Scanning...
-> TX RaspberryPi (b827eb...) [radio_status_0]: Online
-> TX Acurite-5n1 (1234) [temperature]: 72.3
Note: It may take a few minutes for the system to receive its first radio transmission.
RTL-HAOS exposes two maintenance buttons on the Bridge device to help with stale entities or a stuck radio process.
If you change batteries or remove devices, old entities may linger in Home Assistant. This cleanup tool clears the MQTT Discovery configs RTL-HAOS created.
- Navigate to Settings → Devices
- Open your Bridge device (named like
rtl-haos-bridge (xxxxxxxxxxxx)) - Find the button "Delete Entities (Press 5x)"
- Press it 5 times within 5 seconds
What happens?
- RTL-HAOS scans retained Home Assistant discovery topics (
homeassistant/+/+/config) for devices whosemanufacturercontains"rtl-haos". - It publishes empty retained payloads to remove those discovery configs.
- Home Assistant removes the devices/entities almost immediately.
- Valid devices reappear automatically the next time they transmit data (discovery is republished on the next reading).
manufacturer: rtl-haos, so if you run multiple RTL-HAOS bridges on the same broker, it can remove discovery entries for all of them.
If rtl_433 gets wedged (USB hiccup, driver weirdness, no packets flowing), you can restart the radio processes without rebooting the host/add-on.
- Navigate to Settings → Devices → your Bridge device
- Press "Restart Radios"
What happens?
- RTL-HAOS terminates the running
rtl_433process(es). - The radio manager automatically re-launches them.
- The Radio Status entity will briefly show "Rebooting...", then return to normal scanning/online behavior.
Notes:
- Delete Entities clears discovery/entities; it does not restart anything.
- Restart Radios restarts
rtl_433; it does not restart Home Assistant or the container/add-on.
If rtl_433 reports a battery_ok field for a device, RTL-HAOS publishes a Home Assistant
Battery Low binary_sensor:
battery_ok = 0→ Battery Low = ON (LOW)battery_ok = 1→ Battery Low = OFF (OK)
Battery clear delay: battery_ok_clear_after controls when LOW clears:
0clears LOW immediately on the next OK report>0clears LOW only afterbattery_okhas remained OK for that many seconds (helps prevent flapping)
Note: Many devices report battery infrequently. Battery Low uses a long MQTT
expire_after(24h minimum) so it won’t quickly flip to “unavailable” just because no newbattery_okvalue was included in recent packets.
If you plan to run multiple RTL-SDR dongles (e.g. 433 MHz + 915 MHz), you must give each dongle a unique serial number and then configure rtl_config to pin each radio to its frequency.
See docs/MULTI_RADIO.md for the full, step-by-step EEPROM/serial guide and status_id tips.
To keep the bridge running 24/7 using the native installation method, use systemd.
-
Create the service file:
sudo nano /etc/systemd/system/rtl-bridge.service
-
Paste the configuration (Update paths to match your username!):
[Unit] Description=RTL-HAOS MQTT Bridge # Wait for the network to have a valid IP address before starting Wants=network-online.target After=network-online.target [Service] Type=simple # CHANGE THIS to your username (e.g., pi, user, admin) User=pi # CHANGE THIS to your directory path WorkingDirectory=/home/pi/rtl-haos # Unbuffer output so logs show up immediately Environment=PYTHONUNBUFFERED=1 # Wait 10 seconds before starting to ensure USB device is ready ExecStartPre=/bin/sleep 10 # CHANGE THESE PATHS to match your virtual environment and script location ExecStart=/home/pi/rtl-haos/.venv/bin/python3 /home/pi/rtl-haos/main.py # Restart automatically if it crashes Restart=on-failure RestartSec=5 # Infinite restart attempts (do not stop trying) StartLimitIntervalSec=0 [Install] WantedBy=multi-user.target
-
Enable and Start:
sudo systemctl daemon-reload sudo systemctl enable rtl-bridge.service sudo systemctl start rtl-bridge.service -
Check Status:
systemctl status rtl-bridge.service
-
Docker/Compose build fails with:
exec /bin/ash: exec format error- Cause: CPU architecture mismatch (most commonly an
amd64base image being built/run on Raspberry Piarm64/armv7). This is not anapk adddependency problem. - Quick checks:
uname -m docker info --format 'arch={{.Architecture}} os={{.OperatingSystem}}' env | grep -E '^DOCKER_DEFAULT_PLATFORM=' || true docker compose config | grep -nE '^\s*platform:' || true
- Fix (recommended): do not force
linux/amd64, then rebuild cleanly:unset DOCKER_DEFAULT_PLATFORM || true docker compose build --no-cache --pull docker compose up
- If you must pin the platform explicitly:
- Raspberry Pi OS 64-bit (Pi 3/4/5):
linux/arm64 - Raspberry Pi OS 32-bit:
linux/arm/v7Example:
services: rtl-haos: platform: linux/arm64
- Raspberry Pi OS 64-bit (Pi 3/4/5):
- Note on emulation (QEMU/binfmt): you can run
amd64on ARM with emulation, but it is slower and not recommended for SDR workloads. Prefer native ARM images.
- Cause: CPU architecture mismatch (most commonly an
-
"Service Fails to Start" (Exit Code Error)
- Check your username in the service file. If your terminal says
user@hostname, yourUser=line must beuser, notpi. - Verify paths. Run
ls /home/YOUR_USER/rtl-haosto make sure the folder exists. - Check the logs:
journalctl -u rtl-bridge.service -b
- Check your username in the service file. If your terminal says
-
"No Device Found" in Logs
- The script cannot see your USB stick.
- Run
lsusbto verify it is plugged in. - Ensure you are not running another instance of
rtl_433in the background (USB busy). - If running via
systemd, theExecStartPre=/bin/sleep 10line in the service file usually fixes this by waiting for the USB to wake up on reboot. - If running via Docker/Compose, ensure USB passthrough is enabled:
- Compose:
privileged: trueand/dev/bus/usb:/dev/bus/usb - Or docker CLI:
--privileged --device /dev/bus/usb:/dev/bus/usb
- Compose:
- Hard reset your hardware or virtual machine (USB can wedge).
-
"Kernel driver is active" Error
- Linux loaded the default TV tuner driver. You need to blacklist it:
echo "blacklist dvb_usb_rtl28xxu" | sudo tee /etc/modprobe.d/blacklist-rtl.conf sudo reboot
- Linux loaded the default TV tuner driver. You need to blacklist it:
-
Sensors updates are slow
- Check
RTL_THROTTLE_INTERVAL. Default is 30 seconds. Set to 0 for instant updates (not recommended for noisy environments).
- Check
-
Ghost Devices won't delete
- Use Delete Entities (Press 5x) (see Maintenance: Cleanup & Radio Restart above).
- If that still fails, use an MQTT explorer and delete the retained Home Assistant discovery topics created by RTL-HAOS:
homeassistant/sensor/*/confighomeassistant/button/*/configLook for discovery payloads wheredevice.manufactureris"rtl-haos".





