No description
  • HTML 47.9%
  • Python 28.5%
  • Nix 23.6%
Find a file
2026-06-12 18:28:18 -07:00
apps/rtl-sdr dashboard: declare category + description on service-config 2026-06-12 18:02:18 -07:00
.gitignore Initial homefree-rtl-sdr flake 2026-05-28 23:12:51 -07:00
flake.nix Initial homefree-rtl-sdr flake 2026-05-28 23:12:51 -07:00
README.md Initial homefree-rtl-sdr flake 2026-05-28 23:12:51 -07:00

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 from nixpkgs — no Docker Hub fetch) bundling rtl_433 and 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/sensors returning 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 with lsusb.

  • 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.

  1. 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 run git init && git add -A && git commit inside it.
  2. Open the admin panel and go to Developers → Custom Flakes.
  3. Click Add a custom flake and choose Local repository, then point the file browser at this directory.
  4. Click Register flake, then Apply Changes.
  5. Reboot the machine so the DVB-T module blacklist takes effect.
  6. After the reboot, open Services, enable RTL-SDR Sensors, and Apply Changes once more.
  7. 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 for homefree-config.json and the source the admin Services page reads.
  • homefree.service-options.rtl-sdr — what this app's config block 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.