Tools for decoding and live-reading Apator (APA) water meter transmissions over the W-MBus radio protocol (EN 13757-4, Mode T, AES-128-CBC).
| File | Description |
|---|---|
decode_wmbus.py |
Command-line batch decoder (XLS / CSV frames → CSV results) |
wmbus_reader.py |
GUI live reader – receives frames from a USB dongle via COM port |
requirements.txt |
Python dependencies |
example_keys.csv |
Example AES keys file (for decode_wmbus.py) |
example_frames.csv |
Example captured frames file (for decode_wmbus.py) |
example_meters.csv |
Example merged meters file (for wmbus_reader.py) |
pip install -r requirements.txtOn Linux, serial port access requires the dialout group:
sudo usermod -aG dialout $USER
# then open a new terminalpython wmbus_reader.py- Select the COM port of your USB W-MBus dongle (e.g.
COM3on Windows,/dev/ttyUSB0on Linux) - Select baud rate (default 9600)
- Open
meters.csvwith Meters file → … → Load - Click Connect – frames are decoded in real time as meters transmit
The table shows one row per meter. Each row is colour-coded:
| Colour | Status | Meaning |
|---|---|---|
| Grey | ⏳ Pending | Waiting for a frame |
| Green | ✅ OK | Frame received and decoded successfully |
| Yellow | Decoded, but meter reports a fault | |
| Orange | 🔑 No key | Frame received, AES key missing |
| Red | ❌ Failed | Frame received but decryption failed |
| Orange (at load) | 🔑 No key | Meter not yet installed (no radio overlay) |
Right-click a row to manually confirm a reading or reset it to Pending. Use Export CSV to save all results, Reset all to start a new session.
Decode a previously captured XLS or CSV file:
python decode_wmbus.py --keys example_keys.csv --frames example_frames.csv
python decode_wmbus.py --keys example_keys.csv --frames frames.xls --output results.csvKeys file (separator ;):
Radio number;wMBUS key
12345678;000102030405060708090A0B0C0D0E0F
Frames CSV (separator ;):
Detail;Frame_hex
Apartment 101 / 2025-06-15;FF61440106785634121A07...
Frames XLS: multi-column format as exported by the USB dongle software (columns: Detail | Manufacturer | SOF | L | C | M | A | data blocks…).
SOF(FF) + L + C(1B) + M-field(2B) + A-field(6B: ID+SW+HW) + data blocks with CRC
ELL layer: CI=0x8C CC(1B) ELL_ACC(1B)
TPL layer: CI=0x7A TPL_ACC(1B) Status(1B) Config(2B LE)
Encryption: AES-128-CBC, security mode 5
IV: M-field(2B) + A-field(6B) + TPL_ACC × 8
After decryption (OMS DIF/VIF):
2F 2F – OMS verification bytes
04 13 [4B LE] – volume (× 0.001 m³)
04 6D [4B] – timestamp (OMS Type F)
0F [VIF] [4B LE alarm] – manufacturer data (Apator): alarm word + history
- Python 3.10+
pycryptodome– AES-128-CBC decryptionxlrd– reading legacy.xlsframe filespyserial– COM port access (GUI reader only)