- HTML 47.9%
- Python 28.5%
- Nix 23.6%
|
|
||
|---|---|---|
| apps/rtl-sdr | ||
| .gitignore | ||
| flake.nix | ||
| README.md | ||
homefree-rtl-sdr
A HomeFree plugin that
turns an RTL-SDR.com USB dongle into a
Honeywell 5800-series sensor enumerator. It runs
rtl_433 tuned to 345 MHz,
restricts decoding to the Honeywell 5800 door/window/motion protocol,
and serves a small web page + JSON API listing every unique sensor the
receiver has seen.
Packaged as a standalone Nix flake so it can be registered through the HomeFree admin panel without forking the HomeFree base repo.
What it adds
- A Podman container (
homefree-rtl-sdr:local, built locally fromnixpkgs— no Docker Hub fetch) bundlingrtl_433and a tiny Flask aggregator. - A reverse-proxied site at
rtl-sdr.<your-domain>showing each seen sensor's ID, model, last state, channel, battery, packet count and first/last-seen timestamps. Web UI gated by HomeFree's Zitadel SSO. GET /api/sensorsreturning the same data as JSON.- A boot-time kernel-module blacklist that stops Linux from grabbing the RTL2832 dongle for DVB-T reception.
- A backup entry for
/var/lib/homefree-rtl-sdr/so first-seen timestamps and packet counts survive restores.
Two admin-panel options:
- enable — turn the service on/off
- public — expose it on the WAN port
The Honeywell decoder is hard-coded to rtl_433 protocol 70
(Honeywell Door/Window Sensor, 2Gig DW10/DW11, RE208 repeater — the
5800-series decoder in src/devices/honeywell.c). To enable additional
decoders or change the frequency, edit apps/rtl-sdr/image.nix.
Hardware prerequisites
-
An RTL-SDR.com dongle (Realtek RTL2832U / RTL2838, USB ID
0bda:2838) plugged in. Verify withlsusb. -
The host must reboot once after enabling so the DVB-T blacklist takes effect. After reboot:
lsmod | grep -E 'dvb_usb_rtl28xxu|rtl2832' # must be empty
How to add it to HomeFree
This plugin is registered through the admin panel — no command line, no
editing /etc/nixos by hand.
- Put this repository on the HomeFree machine (it must be a git
repository — a
git+file://flake requires one). Either clone it there, or copy the directory and rungit init && git add -A && git commitinside it. - Open the admin panel and go to Developers → Custom Flakes.
- Click Add a custom flake and choose Local repository, then point the file browser at this directory.
- Click Register flake, then Apply Changes.
- Reboot the machine so the DVB-T module blacklist takes effect.
- After the reboot, open Services, enable RTL-SDR Sensors, and Apply Changes once more.
- Browse to
https://rtl-sdr.<your-domain>.
To remove: delete the entry on the Custom Flakes page and Apply Changes.
How it works
flake.nix # exposes nixosModules.default
apps/rtl-sdr/
default.nix # NixOS module (options, container, reverse proxy, backup)
image.nix # pkgs.dockerTools.buildLayeredImage definition
aggregator.py # reads rtl_433 JSON, serves / and /api/sensors
index.html # vanilla-JS sensor table — no CDN, no build step
icon.svg # admin-panel tile
Data flow inside the container:
RTL2838 USB ─► rtl_433 -f 345M -R 70 -F json ─stdin─► aggregator.py ─8080─► Caddy ─► browser
│
└─► /data/sensors.json (snapshot every 30 s)
rtl_433's own -F http server is intentionally not used — its
embedded HTML loads JavaScript from triq.org, which would violate
HomeFree's "all assets vendored locally" rule. We expose the same
information through our own static page and a small JSON API.
USB pass-through
RTL-SDR dongles are libusb devices (not serial), so the
/dev/serial/by-id/... pattern used by zwave-js-ui doesn't apply.
Instead default.nix passes the whole USB tree into the container and
adds a cgroup rule for the usbfs major:
extraOptions = [
"--device=/dev/bus/usb:/dev/bus/usb"
"--device-cgroup-rule=c 189:* rwm"
];
Same approach frigate uses for the Coral TPU.
Kernel module blacklist
Linux auto-loads dvb_usb_rtl28xxu, which claims the dongle for DVB-T
and prevents rtl_433 from opening it. The module declares the
standard rtl-sdr blacklist:
boot.blacklistedKernelModules = [
"dvb_usb_rtl28xxu" "rtl2832" "rtl2830" "e4000"
];
One reboot is required after enabling the service.
Option namespaces
HomeFree splits a service's options across two namespaces, and the base
repo's module.nix normally declares both halves per in-tree app:
homefree.services.rtl-sdr— binding target forhomefree-config.jsonand the source the admin Services page reads.homefree.service-options.rtl-sdr— what this app'sconfigblock reads.
A plugin flake cannot edit the base module.nix, so this app declares
both halves itself; the shared user-facing options are defined once
and spliced into each namespace.
Verifying
After the admin panel reports "Applied" and you've rebooted once:
lsmod | grep -E 'dvb_usb_rtl28xxu|rtl2832' # must be empty
lsusb | grep 0bda:2838 # the dongle
systemctl status podman-rtl-sdr
journalctl -u podman-rtl-sdr -f
curl -s http://<lan-address>:8080/api/sensors | jq '.count, .sensors[0]'
Trigger any Honeywell 5800 device (open a contact, walk past a motion
sensor) — the entry should appear within a second or two, and
last_seen should update on subsequent triggers. The dashboard at
https://rtl-sdr.<your-domain> polls /api/sensors every three seconds.
Out of scope (for now)
- MQTT / Home Assistant auto-discovery.
- Decoders other than Honeywell 5800.
- Frequencies other than 345 MHz.
- Renaming or aliasing sensors in the UI.
- Signal-strength graphs.