rtlamr2mqtt turns a cheap SDR into a Home Assistant energy sensor
Docker container to send rtlamr readings to a mqtt broker
At a glance
- What is it?
- This container decodes utility meter transmissions with a USB RTL-SDR and republishes the readings to MQTT, with Home Assistant auto-discovery creating the entities for you. The current release is a complete rewrite of the internals, which fixes the hangs on a Raspberry Pi and changes the device identifiers, so upgrading means cleaning up old MQTT entities by hand.
- Who is it for?
- Adopt rtlamr2mqtt when your meter is on the rtlamr supported list, you have a USB RTL-SDR, and you want the readings in MQTT and in Home Assistant without wiring the meter into anything. Do not adopt it if your meter already reports over the network, because a radio decode is a far bigger commitment than polling an API.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A radio decoder between the meter and your broker
The description says it in one line: a Docker container to send rtlamr readings to an MQTT broker. In practice the project reads utility meters for water, gas and energy using an inexpensive USB RTL-SDR device and publishes the readings to an MQTT broker, and it integrates with Home Assistant through MQTT auto-discovery, automatically creating sensor entities for each configured meter.
The mechanism has three parts and the README names each one. It runs `rtl_tcp` to interface with the SDR hardware, and `rtlamr` to decode the meter transmissions. The readings are parsed and forwarded to MQTT in real time.
So the data path is a USB dongle, a TCP socket to a local process, a decoder, and a broker publish. The hardware requirement is a USB RTL-SDR device, with a NooElec NESDR Mini given as an example, and the meter has to be one the decoder understands, which the README links to a supported meters list in the rtlamr project.
The container runs on both AMD64 and AARCH64, which matters more than usual here, since the most likely host for this kind of thing is a Raspberry Pi in a cupboard next to the meter. The optional fourth requirement is Home Assistant, which the README calls optional but recommended, and an MQTT broker such as Mosquitto is the only hard service dependency.
The rewrite: three async tasks and an asyncio queue
The current release is billed as a major code rewrite, and the changes list is the most informative part of the README because it names the failure modes it removes.
Blocking `subprocess.Popen` was replaced with `asyncio.create_subprocess_exec`, so the application no longer hangs when rtl_tcp or rtlamr stall, which the README calls a recurring issue on resource-constrained devices like Raspberry Pi. The monolithic main loop was split into independent async tasks: `MeterReader`, which reads and parses rtlamr output, `MQTTPublisher`, which handles all MQTT communication, and `ManagedProcess`, a generic subprocess lifecycle manager. They communicate through an `asyncio.Queue`.
MQTT itself changed. paho-mqtt with threading was replaced by `aiomqtt` for native asyncio MQTT support, including automatic reconnection with backoff. Logging was moved to Python's built-in `logging` module, replacing a custom numeric log level system and the `if LOG_LEVEL >= 3:` checks that were scattered through the code, and `unbuffer` was replaced with `stdbuf -oL` from coreutils, which removed the dependency on the `expect` package while keeping line-buffered subprocess output.
The scale of the change is matched by the testing: 90 unit tests using pytest and pytest-asyncio were added. A rewrite of this size is a risk in any project, and a test suite of that size is the reason to accept it.
Retries with backoff and identifiers that survive a reboot
Two changes are about behaviour under stress, and both address problems that appear on a small device rather than on a laptop.
The first is process retry with backoff. When rtl_tcp or rtlamr fail to start, the application retries up to 5 times with increasing delays of 2, 5, 10, 20 and 30 seconds before giving up. The README frames this as handling the transient USB issues that are common on Raspberry Pi, and the delay sequence is a sensible ramp, since a USB device that has just been plugged in may need a moment, while one that has failed five times over about a minute is not coming back.
The second is device identification, and it is the change that will surprise anyone upgrading. `device_id` now uses an integer index, 0, 1, 2 and so on, matching the order devices are found by librtlsdr, instead of the old `bus:address` format, which changed across reboots and in virtual machines. The practical effect is that every configured meter needs a new identifier, and any automation that referenced the old key has to be updated with it.
There is a third change in the same area, about Home Assistant discovery. Discovery messages are now re-published automatically when Home Assistant restarts, which the README says was previously broken because it was gated behind a log level check. On top of that, discovery payloads are re-sent on a configurable interval, `mqtt.discovery_interval`, default 300 seconds, so entities survive an MQTT broker restart even with `retain=false`.
Listen mode is the answer to the first install problem
Configuring this project needs a meter identifier, and the identifier is not written on the meter. The new listen mode solves that.
Setting `general.listen_mode: true` runs the application without any meter filter and logs every meter it hears, deduplicated per session. No MQTT connection is made. The README describes it as useful for discovering your meter ID before configuring the add-on.
That is a small feature with a large effect on the first run. Without it, the natural loop is to guess a meter ID, run the container, look at the logs, notice nothing arrived, and try another guess. With it, the container tells you what is actually on the air, and the configuration follows from that rather than from a forum post.
The listen mode also shows the project's shape. It is not a consumer of a device API, it is a decoder of radio transmissions, and a decoder that can hear everything is the natural first stage of a tool whose normal mode filters to the meters you care about.
Two ways to install: add-on or plain container
The Home Assistant route is the one the project promotes. You go to Settings, Add-ons, Add-on Store, click the three dots in the top-right corner, then Repositories, add `https://github.com/allangood/rtlamr2mqtt` and click Add. The rtlamr2mqtt add-on then appears in the store, and you click to install and configure it. When running as an add-on, the MQTT broker settings are obtained automatically from the Home Assistant Supervisor API, so the only thing you have to configure is the `meters` section.
The standalone Docker route mounts the config file and, importantly, the USB device:
docker run --name rtlamr2mqtt \
-v /path/to/rtlamr2mqtt.yaml:/etc/rtlamr2mqtt.yaml:ro \
--device /dev/bus/usb:/dev/bus/usb \
--restart unless-stopped \
allangood/rtlamr2mqttAnd the compose equivalent:
services:
rtlamr2mqtt:
image: allangood/rtlamr2mqtt
container_name: rtlamr2mqtt
restart: unless-stopped
devices:
- /dev/bus/usb:/dev/bus/usb
volumes:
- /path/to/rtlamr2mqtt.yaml:/etc/rtlamr2mqtt.yaml:roThe `--device` mapping is the part that matters and the part that is easy to omit. Without it the container has no access to the SDR dongle, and no amount of configuration will fix that.
The Dockerfile is dual purpose, working both as a Home Assistant add-on with `BUILD_FROM` and as a standalone container defaulting to `python:3.13-slim`.
The upgrade cost is a manual cleanup
The README puts a caution box at the top of the changes section, and the wording is unambiguous. This version is a complete rewrite of the application internals, and your old entities should be cleaned manually from your MQTT broker.
That is a real operational step, not a formality. The old entities were created by the previous version with the old `bus:address` identifiers, the new version identifies devices by index, and the discovery messages use new keys. Leaving the old ones in place gives you a Home Assistant full of sensors that stopped updating, and nothing in the application will remove them, because the application no longer knows they exist.
The bug fix list is worth reading for the same reason, since it shows what the old version got wrong. Fixed are a non-deterministic meter ID key lookup, an undefined variable in mock mode, deprecated paho-mqtt API usage, a USB file descriptor leak in reset, and a hardcoded TLS 1.2 protocol.
Two more things about the project as a whole. The releases are named by date, with 2026.10.15 the latest, ahead of 2026.5.9 and 2025.6.6, and the last push was on 2026-10-16, so the project is being worked on. The README also carries an explicit AI disclosure, stating that this version was developed with the assistance of AI, specifically Claude by Anthropic, that the architecture, code, tests and documentation were produced collaboratively between a human developer and an AI assistant, and that the maintainer reviewed, tested and validated the code. Whether that is a comfort or a concern is a question each team answers for itself, but the project states it plainly rather than leaving it to be inferred.
When a radio decode is the wrong approach
The clearest case for not using this is a meter that already reports over the network. If your utility meter or its in-home display exposes readings over a local API or publishes them itself, polling that endpoint and pushing the result into MQTT is a smaller, more predictable system than decoding radio transmissions, and it does not depend on the SDR hardware, the decoder supporting your meter's protocol, or the radio signal reaching the dongle.
The second case is a meter that is not on the supported list. The requirement is a compatible smart meter, checked against the supported meters list in the rtlamr project, and the decode is specific to a family of meters. If your model is absent, the work is writing a decoder, which is a different project.
The third is the operational one. This is a container that holds open a USB device and spawns two subprocesses, and the README's own bug fix list mentions a USB file descriptor leak in reset. On a machine where the SDR is shared with something else, or where the USB bus is flaky, that is a source of noise in an otherwise quiet home.
None of that makes the project wrong. It makes the decision conditional. If the meter is supported, the dongle is cheap, and you want the readings in Home Assistant without touching the meter, this is a short, well-specified path. The listen mode, the retry backoff and the auto-discovery are the parts that make it survive a Raspberry Pi in a cupboard, and those are exactly the parts the rewrite added.
Editorial conclusion
Adopt rtlamr2mqtt when your meter is on the rtlamr supported list, you have a USB RTL-SDR, and you want the readings in MQTT and in Home Assistant without wiring the meter into anything. Do not adopt it if your meter already reports over the network, because a radio decode is a far bigger commitment than polling an API. Verify first that your meter model appears in the supported meters list, that the container can reach the USB device through the `/dev/bus/usb` mapping, and that you are prepared for the rewrite, since the old entities have to be removed from the broker by hand.
Frequently asked questions
What hardware does rtlamr2mqtt need?
A compatible smart meter from the rtlamr supported list, a USB RTL-SDR device such as a NooElec NESDR Mini, and an MQTT broker such as Mosquitto. Home Assistant is optional but recommended, and the container image supports both AMD64 and AARCH64.
How do I find my meter ID before configuring rtlamr2mqtt?
Use the new listen mode. Setting `general.listen_mode: true` runs the application without a meter filter, logs every meter it hears deduplicated per session, and makes no MQTT connection, which is what the README recommends for discovering the meter ID before configuring the add-on.
What changed in the rtlamr2mqtt rewrite?
Blocking subprocess calls were replaced with asyncio.create_subprocess_exec, the main loop was split into MeterReader, MQTTPublisher and ManagedProcess tasks using an asyncio.Queue, paho-mqtt was replaced by aiomqtt, and device_id changed from a bus:address format to an integer index matching the order devices are found by librtlsdr. 90 unit tests were added.
What do I have to do when upgrading rtlamr2mqtt to the rewritten version?
The README says your old entities should be cleaned manually from your MQTT broker, because the rewrite changed the device identifiers and the discovery keys. It also notes that the new version re-publishes discovery messages when Home Assistant restarts and on a configurable `mqtt.discovery_interval`, default 300 seconds, so entities survive broker restarts even with retain=false.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/allangood-rtlamr2mqtt)Was this useful?