Reading my electricity meter over the air using CC1101
Pulling electricity meter readings into HomeAssistant
Reading my electricity meter over the air
My electricity meter talks, but you need to know how to listen: on 868.95 MHz, using Wireless M-Bus. The payload is AES-128 encrypted. I wanted those numbers in Home Assistant because relying on an unreliable utility-company webpage for current readings is ridiculous when the meter is only a few meters from the Home Assistant host.
Think about it: the meter reads energy and power, uploads them over GSM to the utility company's servers, and then lets you check them through a web portal, all while it sits a few meters away. Bonkers.
My new meter can send wM-Bus telegrams, as indicated by the logo on its enclosure. First, I had to contact the utility company so that it could enable the feature remotely and send me a decryption key. I will spare you the long, bumpy process involving official emails and paperwork.
The obvious commercial route is a dedicated wM-Bus USB stick and something like wmbusmeters on a Raspberry Pi. That works. It also means using a whole Raspberry Pi only to decode telegrams, or buying a ready-made wM-Bus dongle — but who would I be if I didn't want to do this myself? So: ESP32-C3 Super Mini, CC1101 breakout, and a lot of reading about modes nobody explains clearly on the first page of Google.
This post is about the receiver that actually landed sensors in Home Assistant: a local ESPHome external component. I am aware of SzczepanLeon's work on GitHub, but unfortunately it couldn't properly decode my telegrams (probably a different layout of DIFs and VIFs). Or maybe it could, but I had already decided to do this as simply as possible and started developing my own. This firmware was created with the help of Cursor IDE and the Composer 2.5 model to speed up the whole process. It uses ESP-IDF underneath, but is built and flashed through ESPHome, so Wi-Fi, OTA, and the Home Assistant API remain ordinary ESPHome configuration.
What wM-Bus is, in one paragraph
Wireless M-Bus is the RF side of the M-Bus utility-meter protocol. Meters broadcast telegrams on a schedule set by the utility company. In Poland, and much of Europe, this is 868 MHz, typically 868.95 MHz.
Frames can be plaintext or encrypted. Mine uses mode 5 AES-CBC with a 16-byte key you get from your distribution operator (DSO). You must request it first; I am not aware of any cases where a decryption key was given to the end user upon a new meter installation. Most clients are not even aware that their meter can do this, or they don't think about it — it's enough that the meter always measures "too much" energy consumption ;) Without the key, you still see the meter ID and RSSI in logs. With the wrong key, this receiver logs Decrypt failed and sadness.
This project is deliberately narrower than a general-purpose wM-Bus gateway. It receives T1-mode telegrams from a CC1101, performs 3-out-of-6 decoding, validates block CRCs, decrypts the payload, and extracts the total energy and current power readings. Unfortunately those values (besides the timestamp and meter serial number) are the only usable ones; the rest is limiter thresholds, etc. Current and voltage values on each phase would be more useful, but we need to work with what we have.
A real telegram
Here is one frame the ESP32 actually caught, CRC already stripped, still encrypted. Manufacturer APA, CI 0x7A, security mode 5, 175 bytes on the air:
AE4401061931621301027AE900A005A9A0BB7A255E870CB74EDD2A1D836BED1B
2A9C8A08B44C8FC26D0172C2A8BA505CFCAB2E9F329355057AC1F7E11496A802
8053FF7FA7CE0F3982560F0D80BDAFA562C8D67C20CC71FACBA712F2D3FA6CDE
1648AF7D551EDE7C957EDC94BA4F857A4E1E0EAD20773A153950A9CE4139565D
EF3A45BB99D3B8EC2215CF22B4C4B273A731840DECB03AA55C08D50743D07BC1
12616AC42A5CE8C79BEBA4585B556E
After AES-CBC the application payload starts with the usual 2F2F padding and the records this firmware cares about: 06 6D (meter clock), 0B 2B (power), 0E 03 (total energy). That packet published 1344.518 kWh and 1.405 kW.
2F2F066D4061155238800C78193162130B2B0514000BAB3C0000000E03184534
0100000E833C0901000000000BABC8FC100000008B40ABC8FC100000008240FD
3A000086406DFFFFFFFFFF80844020000000008B8040ABC8FC10000000828040
FD3A00008680406DFFFFFFFFFF8084804020000000008BC040ABC8FC10000000
82C040FD3A000086C0406DFFFFFFFFFF8084C04020000000002F2F2F2F2F2F2F
The same hex pasted into the wmbusmeters analyzer is a useful sanity check. It agrees on energy and power; the rest of the DIF/VIF soup is limiter thresholds and placeholder FF fields, which is why this receiver does not bother exposing them.

Hardware: ESP32-C3 Super Mini + CC1101
Total parts cost is laughably low if you already have a soldering iron and patience.
| Item | Notes |
|---|---|
| MCU | ESP32-C3 Super Mini (or any ESP32-C3 dev board) |
| Radio | CC1101 module, 868 MHz variant, not 433 MHz |
| Antenna | Proper 868 MHz antenna or λ/4 wire (~8.2 cm) on ANT |
| Decoupling | 100 nF across CC1101 VCC/GND, short wires |
Wiring
| CC1101 | ESP32-C3 | Function |
|---|---|---|
| VCC | 3V3 | 3.3 V only, not 5 V |
| GND | GND | Ground |
| MOSI (SI) | GPIO6 | SPI out |
| MISO (SO) | GPIO5 | SPI in |
| SCK | GPIO4 | SPI clock |
| CSN (CS) | GPIO7 | Chip select |
| GDO0 | GPIO3 | Packet / sync interrupt |
| GDO2 | GPIO2 | FIFO threshold interrupt |
GPIO2 is a strapping pin on ESP32-C3. It must be high or floating during reset. If the board will not boot, check that the CC1101 GDO2 connection is not pulling it low. GPIO8 (LED) and GPIO9 (BOOT) are also strapping pins, so leave them alone.
On boot, a healthy CC1101 reports partnum=0x00 version=0x14 in logs. Anything else usually means swapped MOSI/MISO or bad power.
The ESPHome component
The receiver code exists as an ESPHome external component. ESPHome loads it directly from the checkout:
external_components:
- source: components
The wmbus_native parent component owns the CC1101 and the receive pipeline. Its two sensor platforms subscribe to the decoded total-energy and power readings. The radio pins are intentionally fixed in components/wmbus_native/config.h, which keeps the low-level ESP-IDF driver simple and matches the wiring above.
The component:
- configures the CC1101 for 868.95 MHz T1-mode reception;
- uses GDO0 and GDO2 interrupts to drain the radio FIFO;
- decodes 3-out-of-6 symbols and verifies wM-Bus block CRCs;
- filters on RSSI and optionally on meter ID;
- decrypts AES payloads and publishes energy in kWh plus power in kW;
- restarts the radio if its interrupt stream stalls or it stops receiving good packets.
Use ESP-IDF on the ESP32-C3. The YAML sets board_build.flash_mode: dio, which is useful for C3 boards that are unreliable with the default flash mode.
ESPHome automatically assigns the correct Home Assistant metadata: energy is total_increasing in kWh, and power is a measurement in kW. The device appears through the ESPHome API integration; add the total-energy entity to the Energy dashboard after it has reported a value.




Neat diagrams showing usage and total consumption, last photo shows completed device - slap basic enclosure on it and forget.
Things that actually bit me
GDO2 is required
This receiver uses both CC1101 interrupt lines: GDO0 starts and drains FIFO reads, while GDO2 signals that the packet is complete. Unlike some generic ESPHome CC1101 examples, GDO2 is not an optional YAML setting here; wire it to GPIO2 as shown.
RSSI and placement
Electricity meters are often in metal cupboards. -70 dBm next to the door is fine; -95 dBm through two walls is not. Move the ESP32, add an antenna, and accept that your meter might only be readable from the hallway.
rssi_min: -90 rejects weaker frames before attempting header parsing and decryption. Raise the threshold to reject more marginal packets; lower it only when placement and the antenna cannot be improved.
Radio watchdog messages
The receiver logs a heartbeat every 30 seconds. It restarts the CC1101 after two heartbeats with no interrupts, when both interrupt lines are stuck high, or after five minutes without a good packet following a successful reception. These restarts flush the FIFO and put the radio back into RX mode; they are recovery behavior, not a Home Assistant or Wi-Fi failure.
Home Assistant after it works
- In Settings > Devices & Services > ESPHome, adopt the node.
- Add Total Electricity Consumption to the Energy dashboard.
- Current Power Demand works in any gauge or statistics card.
For a wall display elsewhere in the house, pull entities through the homeassistant platform on another ESPHome device. It follows the same pattern as every other sensor, so no MQTT bridge is required unless you want one.
Where things stand
The reader sits in a cupboard, updates whenever the meter broadcasts, and lets me view energy use in pretty plots. Data, yay!
Honestly, I do not know what more to add. It just works.
Design, build, set up, and forget. I hope it stays that way.