Python (Raspberry Pi · Linux · macOS · Windows)¶
With Python you can read the WattWächter TTL across platforms – whether directly on a Raspberry Pi via its GPIO UART, or on Mac, Linux and Windows via a simple USB-to-TTL adapter.
Most modern meters send their data in the SML format (Smart Message Language) at 9600 baud, 8N1. We use pyserial to read the serial interface and smllib to decode the telegrams.
How the communication works
Most meters send their telegram on their own at regular intervals (push) – for those, the receive line (green / RXD) is sufficient for read-out. The yellow TX line is only needed for the remaining meters that have to be requested to send.
Which meters have to be requested to send?
Modern SML meters (e.g. EMH eHZ/eBZD, EasyMeter Q3A/Q3D, eBZ, Holley DTZ541, Iskra MT 681) send without a request – the RX line alone is enough, and the example script above is written for them.
A request over the TX line is mainly required by older meters using the IEC 62056-21 protocol (mode C, "D0" ASCII). They only emit their telegram after the request sequence /?!\r\n is sent. According to our meter scripts this applies to these models:
| Manufacturer | Models |
|---|---|
| Apator | 12EC3 / 12EC3G |
| Baylan | BM, BT |
| Elster / Honeywell | AS1350, AS1440, AS1500, AS2018, AS3500, T510 |
| EMH | ITZ, LZQJ-XC |
| Iskra | AM550, MT 171, MT 174, MT 382 |
| Itron | ACE3000 Typ 260, ACE6000 |
| Kamstrup | 382 |
| Landis + Gyr | E230, E350, E650, ZMB120, ZMD120, T550 |
| Logarex | LK11BL, LK13BD, LK13BO |
| MetCom | MCS301 |
| PAFAL | 20EC3gr |
| Siemens | TD-3511 |
| ZPA | ZE311, ZE314 |
In addition, some M-Bus heat/water meters (Engelmann SensoStar, MetCom Ultramess C3, Sensus Pollucom F) need a wake-up telegram. For all of these meters the pure SML script above is not sufficient – they speak a different protocol and must be polled actively.
Meter prerequisites
For the meter to send the full instantaneous values (power, phases), the PIN usually has to be disabled and the extended info mode (Inf → On) enabled. You obtain the PIN from your grid operator.
Wiring¶
The four wires connect 1:1 (the RX/TX crossover is already done on the reading head's circuit board).
| Wire (TTL) | Function | Raspberry Pi pin |
|---|---|---|
| Brown | VCC | Pin 1 — 3V3 |
| Green | RXD (data) | Pin 10 — GPIO15 / RXD |
| Yellow | TXD | Pin 8 — GPIO14 / TXD — optional |
| White | GND | Pin 6 — GND |
Wire colors: brown → VCC, green → RXD, yellow → TXD (dashed = optional), white → GND.
Enable the UART on the Raspberry Pi
Enable the serial port with sudo raspi-config → Interface Options → Serial Port: disable the login shell over serial, enable the serial hardware. Then reboot. The interface is then available at /dev/serial0.
For a PC, Mac or server, connect the WattWächter TTL via a standard USB-to-TTL adapter (e.g. CP2102, FT232, CH340).
| Wire (TTL) | Function | USB-to-TTL adapter |
|---|---|---|
| Brown | VCC | 3V3 (or 5V) |
| Green | RXD (data) | RXD |
| Yellow | TXD | TXD — optional |
| White | GND | GND |
Wire colors: brown → VCC, green → RXD, yellow → TXD (dashed = optional), white → GND.
Installation¶
pip install pyserial smllib
Example script¶
import serial
from smllib import SmlStreamReader
# --- Serial interface -------------------------------------------------------
# Raspberry Pi (GPIO UART): /dev/serial0
# USB-to-TTL adapter (Linux): /dev/ttyUSB0
# macOS: /dev/tty.usbserial-XXXX
# Windows: COM3
PORT = "/dev/serial0"
# OBIS codes we are interested in (short form -> label)
OBIS = {
"1.8.0": "Import", # Wh, drawn from grid
"2.8.0": "Export", # Wh, fed into grid
"16.7.0": "Power", # W, current active power (signed)
}
ser = serial.Serial(PORT, baudrate=9600, bytesize=8,
parity="N", stopbits=1, timeout=2)
stream = SmlStreamReader()
print(f"Reading WattWächter TTL on {PORT} ...")
while True:
data = ser.read(ser.in_waiting or 1)
if not data:
continue
stream.add(data)
frame = stream.get_frame()
if frame is None:
continue # telegram not complete yet
for entry in frame.get_obis():
name = OBIS.get(entry.obis.obis_short)
if name is None:
continue
value = entry.get_value() # already scaled (value × 10^scaler)
if entry.obis.obis_short in ("1.8.0", "2.8.0"):
print(f"{name:8s}: {value / 1000:.3f} kWh")
else:
print(f"{name:8s}: {value:.0f} W")
print("-" * 30)
Example output:
Reading WattWächter TTL on /dev/serial0 ...
Import : 12345.678 kWh
Export : 4321.000 kWh
Power : 437 W
------------------------------
Reading additional values
Extend the OBIS dictionary with more codes (e.g. 36.7.0, 56.7.0, 76.7.0 for per-phase power, or 32.7.0, 52.7.0, 72.7.0 for per-phase voltage). entry.get_value() returns the value already correctly scaled, entry.unit contains the unit code. An overview of common OBIS codes can be found under Additional OBIS codes.
Troubleshooting¶
| Problem | Possible cause / solution |
|---|---|
Permission denied on /dev/... |
Add your user to the dialout group: sudo usermod -aG dialout $USER, then log in again |
No data / frame stays None |
Reading head not positioned correctly over the IR diode; green wire (RXD) not on the adapter's RX; wrong port |
| Only energy counters, no power/phases | Disable the PIN and set Inf → On on the meter |
| Nothing arrives on the Raspberry Pi | Serial login shell still enabled? Use port /dev/serial0 instead of /dev/ttyAMA0 |
| Values off by a factor of 10/100 | In rare cases replace get_obis() with parse_frame() (see smllib docs) |