Skip to content

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 / TXDoptional
White GND Pin 6 — GND

Wiring WattWächter TTL to Raspberry Pi

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-configInterface 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 TXDoptional
White GND GND

Wiring WattWächter TTL to USB-to-TTL adapter

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)