1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
From 720ecacd4ea93db3c6792ed10bc42f8ef6ff989f Mon Sep 17 00:00:00 2001
From: "M. Greyson Christoforo" <grey@christoforo.net>
Date: Mon, 18 Aug 2025 11:48:50 -0600
Subject: [PATCH] add logic to decode the total kWh counter reported by my
meter
add option to publish meter readings to homeassistant via MQTT
add connection message
use oneshot publishing
retain mqtt pubs
fix scaling
Revert "fix scaling"
This reverts commit 6e1c2524067f2d46f6dd883e6d7aaa532b89f561.
use timestamp from the meter reading message
fix scaling
add walrus
---
python/packetize.py | 35 ++++++++++++++++++++++++++++++++++-
1 file changed, 34 insertions(+), 1 deletion(-)
diff --git a/python/packetize.py b/python/packetize.py
index 7a7e07a..3f84aaa 100644
--- a/python/packetize.py
+++ b/python/packetize.py
@@ -19,11 +19,22 @@
# Boston, MA 02110-1301, USA.
#
+# to publish meter readings via MQTT (to homeassistant for example),
+# set an environment variable like this:
+# ELSTER_MQTT_BROKER="192.168.1.1:1883"
+# if the broker needs authentication, provide it with an
+# environment variable like this:
+# ELSTER_MQTT_BROKER_LOGIN="user_name/password"
+# then json meter reading messages will be published the the topic
+# elster/${meter_number}
+# formatted like: {"kWh": 451147.14, "timestamp": 1755614264.336097}
+
import datetime
-import os.path
+import os
import struct
import time
import numpy
+import json
from gnuradio import gr
@@ -53,6 +64,8 @@ def __init__(self, num_inputs):
self.packet_type = [0] * num_inputs
self.bits_remaining = [0] * num_inputs
+ self.mqtt_publish("elster/new_client", json.dumps({"pcap_file": filename, "timestamp": time.time()}))
+
def __del__(self):
self.file.close()
@@ -72,6 +85,25 @@ def crc_x25(self, message):
reg ^= 0xffff
return bytes([reg & 0xff, reg >> 8])
+ def mqtt_publish(self, topic, payload, retain=True):
+ try:
+ if 'ELSTER_MQTT_BROKER' in os.environ:
+ import paho.mqtt.publish as publish
+ if ":" in os.environ['ELSTER_MQTT_BROKER']:
+ mqtt_host, mqtt_port_env = os.environ['ELSTER_MQTT_BROKER'].split(":", 1)
+ mqtt_port = int(mqtt_port_env)
+ else:
+ mqtt_host = os.environ['ELSTER_MQTT_BROKER']
+ mqtt_port = 1883
+ if 'ELSTER_MQTT_BROKER_LOGIN' in os.environ:
+ mqtt_user, mqtt_pass = os.environ['ELSTER_MQTT_BROKER_LOGIN'].split("/", 1)
+ auth = {"username": mqtt_user, "password": mqtt_pass}
+ else:
+ auth = None
+ publish.single(topic=topic, payload=payload, retain=retain, hostname=mqtt_host, port=mqtt_port, auth=auth)
+ except Exception as e:
+ print(f"MQTT publish failed: {e}")
+
def process_packet(self, channel, pkt):
if pkt[0] >= 2:
len_bytes = 1
@@ -131,6 +163,7 @@ def process_packet(self, channel, pkt):
YY, MM, DD, hh, mm, ss = struct.unpack("BBBBBB", cmd_payload[8:14])
ts_theirs = datetime.datetime(2000+YY, MM, DD, hh, mm, ss)
print(f" Reading for meter number {src} @ {ts_theirs.isoformat()} = {main_reading} kWh")
+ self.mqtt_publish(f"elster/{src}", json.dumps({"kWh": main_reading, "timestamp": ts_theirs.isoformat()}))
print()
|