From 0e27ff04eb72d03c12660f96df40b4b75f29801d Mon Sep 17 00:00:00 2001 From: Thomas Anglmaier Date: Sat, 19 Feb 2022 17:34:45 +0100 Subject: [PATCH 1/7] MQTT: Make sure to reconnect on dirty session --- kaifareader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kaifareader.py b/kaifareader.py index 0294076..89796f4 100644 --- a/kaifareader.py +++ b/kaifareader.py @@ -388,7 +388,7 @@ def mqtt_on_disconnect(client, userdata, rc): # connect to mqtt broker if g_cfg.get_export_format() == 'MQTT': try: - mqtt_client = mqtt.Client("kaifareader") + mqtt_client = mqtt.Client("kaifareader", clean_session=False) mqtt_client.on_connect = mqtt_on_connect mqtt_client.on_disconnect = mqtt_on_disconnect mqtt_client.username_pw_set(g_cfg.get_export_mqtt_user(), g_cfg.get_export_mqtt_password()) From 31d3b9151003803a2181a2dc9bb24f172cbc2060 Mon Sep 17 00:00:00 2001 From: Thomas Anglmaier Date: Sat, 19 Feb 2022 17:42:30 +0100 Subject: [PATCH 2/7] Make values retrieved from meter selectable Makes the values the meter provides selectable by adding/deleting elements to `wanted_values` list in the config file. The template config shows all available readings. --- README.md | 20 ++++- kaifareader.py | 191 ++++++++++++++++++++++++++++++++++++-------- meter_template.json | 17 +++- 3 files changed, 192 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 93fc6dd..903c320 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,22 @@ A template file `meter_template.json` can be recycled for this. "export_mqtt_port": 1883, "export_mqtt_user": "mymqttuser", "export_mqtt_password": "supersecretmqttpass", - "export_mqtt_basetopic": "kaifareader" + "export_mqtt_basetopic": "kaifareader", + "wanted_values": [ + "VoltageL1", + "VoltageL2", + "VoltageL3", + "CurrentL1", + "CurrentL2", + "CurrentL3", + "RealPowerIn", + "RealPowerOut", + "RealEnergyIn", + "RealEnergyOut", + "ReactiveEnergyIn", + "ReactiveEnergyOut", + "Factor" + ] } ``` @@ -66,6 +81,9 @@ the telegrams differ. This script was tested with suppliers: - TINETZ - EVN +Make sure to only select the values you need from the list in `wanted_values` by deleting the elements you don't want. +The logfile will show the related OBIS-Values when run in `logging.INFO`. + ### Export **Solarview** diff --git a/kaifareader.py b/kaifareader.py index 89796f4..e1950ae 100644 --- a/kaifareader.py +++ b/kaifareader.py @@ -200,25 +200,121 @@ def get_export_mqtt_basetopic(self): else: return self._config["export_mqtt_basetopic"] + def get_wanted_values(self): + if not "wanted_values" in self._config: + return None + else: + return self._config["wanted_values"] + class Obis: def to_bytes(code): return bytes([int(a) for a in code.split(".")]) - VoltageL1 = to_bytes("01.0.32.7.0.255") - VoltageL2 = to_bytes("01.0.52.7.0.255") - VoltageL3 = to_bytes("01.0.72.7.0.255") - CurrentL1 = to_bytes("1.0.31.7.0.255") - CurrentL2 = to_bytes("1.0.51.7.0.255") - CurrentL3 = to_bytes("1.0.71.7.0.255") - RealPowerIn = to_bytes("1.0.1.7.0.255") - RealPowerOut = to_bytes("1.0.2.7.0.255") - RealEnergyIn = to_bytes("1.0.1.8.0.255") - RealEnergyIn_S = '1.8.0' # String of Positive active energy (A+) total [Wh] (needed for export) - RealEnergyOut = to_bytes("1.0.2.8.0.255") - RealEnergyOut_S = '2.8.0' # String of Negative active energy (A-) total [Wh] (needed for export) - ReactiveEnergyIn = to_bytes("1.0.3.8.0.255") - ReactiveEnergyOut = to_bytes("1.0.4.8.0.255") - Factor = to_bytes("01.0.13.7.0.255") + VoltageL1 = { + "pos": "32.7.0", + "byte": to_bytes("01.0.32.7.0.255"), + "desc_name": "Voltage L1", + "unit": "V", + "mod": "round(self.obis[d['byte']],2)", + "mqtt_topic": "VoltageL1_V" + } + VoltageL2 = { + "pos": "52.7.0", + "byte": to_bytes("01.0.52.7.0.255"), + "desc_name": "Voltage L2", + "unit": "V", + "mod": "round(self.obis[d['byte']],2)", + "mqtt_topic": "VoltageL2_V" + } + VoltageL3 = { + "pos": "72.7.0", + "byte": to_bytes("01.0.72.7.0.255"), + "desc_name": "Voltage L3", + "unit": "V", + "mod": "round(self.obis[d['byte']],2)", + "mqtt_topic": "VoltageL3_V" + } + CurrentL1 = { + "pos": "31.7.0", + "byte": to_bytes("1.0.31.7.0.255"), + "desc_name": "Current L1", + "unit": "A", + "mod": "round(self.obis[d['byte']],2)", + "mqtt_topic": "CurrentL1_A" + } + CurrentL2 = { + "pos": "51.7.0", + "byte": to_bytes("1.0.51.7.0.255"), + "desc_name": "Current L2", + "unit": "A", + "mod": "round(self.obis[d['byte']],2)", + "mqtt_topic": "CurrentL2_A" + } + CurrentL3 = { + "pos": "71.7.0", + "byte": to_bytes("1.0.71.7.0.255"), + "desc_name": "Current L3", + "unit": "A", + "mod": "round(self.obis[d['byte']],2)", + "mqtt_topic": "CurrentL3_A" + } + RealPowerIn = { + "pos": "1.7.0", + "byte": to_bytes("1.0.1.7.0.255"), + "desc_name": "InstantaneousPower In", + "unit": "W", + "mod": None, + "mqtt_topic": "InstantaneousPowerIn_W" + } + RealPowerOut = { + "pos": "2.7.0", + "byte": to_bytes("1.0.2.7.0.255"), + "desc_name": "InstantaneousPower Out", + "unit": "W", + "mod": None, + "mqtt_topic": "InstantaneousPowerOut_W" + } + RealEnergyIn = { + "pos": "1.8.0", + "byte": to_bytes("1.0.1.8.0.255"), + "desc_name": "ActiveEnergy In", + "unit": "kWh", + "mod": "self.obis[d['byte']] / 1000", + "mqtt_topic": "ActiveEnergyIn_kWh" + } + RealEnergyOut = { + "pos": "2.8.0", + "byte": to_bytes("1.0.2.8.0.255"), + "desc_name": "ActiveEnergy Out", + "unit": "kWh", + "mod": "self.obis[d['byte']] / 1000", + "mqtt_topic": "ActiveEnergyOut_kWh" + } + ReactiveEnergyIn = { + "pos": "3.8.0", + "byte": to_bytes("1.0.3.8.0.255"), + "desc_name": "ReactiveEnergy In", + "unit": "W", + "mod": None, + "mqtt_topic": "ReactiveEnergyIn_W" + } + ReactiveEnergyOut = { + "pos": "4.8.0", + "byte": to_bytes("1.0.4.8.0.255"), + "desc_name": "ReactiveEnergy Out", + "unit": "W", + "mod": None, + "mqtt_topic": "ReactiveEnergyOut_W" + } + Factor = { + "pos": "13.7.0", + "byte": to_bytes("01.0.13.7.0.255"), + "desc_name": "Factor", + "unit": "", + "mod": "round(self.obis[d['byte']],3)", + "mqtt_topic": "Factor" + } + class Exporter: @@ -227,8 +323,10 @@ def __init__(self, file, exp_format): self._format = exp_format self._export_map = {} - def set_value(self, obis_string, value): - self._export_map[obis_string] = value + def set_value(self, obis_string, value, unit): + self._export_map[obis_string] = {} + self._export_map[obis_string]["value"] = value + self._export_map[obis_string]["unit"] = unit def _write_out_solarview(self, file): file.write("/?!\n") # Start bytes @@ -236,7 +334,7 @@ def _write_out_solarview(self, file): for key in self._export_map.keys(): # e.g. 1.8.0(005305.034*kWh) - file.write("{}({:010.3F}*kWh)\n".format(key, self._export_map[key])) + file.write("{}({:010.3F}*{})\n".format(key,self._export_map[key]['value'], self._export_map[key]['unit'])) file.write("!\n") # End byte @@ -323,18 +421,43 @@ def parse_all(self): self.obis[obis_code] = octet g_log.debug("OCTET: {}, {}".format(octet_len, octet)) - def get_act_energy_pos_kwh(self): - if Obis.RealEnergyIn in self.obis: - return self.obis[Obis.RealEnergyIn] / 1000 + def get_generic_name(self, name): + d = getattr(Obis, name) + if 'desc_name' in d: + return d['desc_name'] + else: + return None + + def get_generic_position(self, name): + d = getattr(Obis, name) + if 'pos' in d: + return d['pos'] + else: + return None + + def get_generic_unit(self, name): + d = getattr(Obis, name) + if 'unit' in d: + return d['unit'] else: return None - def get_act_energy_neg_kwh(self): - if Obis.RealEnergyOut in self.obis: - return self.obis[Obis.RealEnergyOut] / 1000 + def get_generic_value(self, name): + d = getattr(Obis, name) + if d['byte'] in self.obis: + if d['mod'] != None: + return(eval(d['mod'])) + else: + return self.obis[d['byte']] else: return None + def get_generic_mqtttopic(self, name): + d = getattr(Obis, name) + if 'mqtt_topic' in d: + return d['mqtt_topic'] + else: + return None def mqtt_on_connect(client, userdata, flags, rc): if rc == 0: @@ -392,7 +515,7 @@ def mqtt_on_disconnect(client, userdata, rc): mqtt_client.on_connect = mqtt_on_connect mqtt_client.on_disconnect = mqtt_on_disconnect mqtt_client.username_pw_set(g_cfg.get_export_mqtt_user(), g_cfg.get_export_mqtt_password()) - mqtt_client.connect(g_cfg.get_export_mqtt_server(), port=g_cfg.get_export_mqtt_port()) + mqtt_client.connect(g_cfg.get_export_mqtt_server(), port=g_cfg.get_export_mqtt_port(), keepalive=7) mqtt_client.loop_start() except Exception as e: print("Failed to connect: " + str(e)) @@ -425,7 +548,7 @@ def mqtt_on_disconnect(client, userdata, rc): (stream.find(g_supplier.frame2_start_bytes) <= 0) or (stream[-1:] != g_supplier.frame2_end_bytes) or (len(byte_chunk) == serial_read_chunk_size) - ): + ): g_log.debug("pos: {} | {}".format(frame1_start_pos, frame2_start_pos)) g_log.debug("incomplete segment: {} ".format(stream)) g_log.debug("received chunk: {} ".format(byte_chunk)) @@ -470,22 +593,22 @@ def mqtt_on_disconnect(client, userdata, rc): dec = Decrypt(g_supplier, frame1, frame2, g_cfg.get_key_hex_string()) dec.parse_all() - g_log.info("1.8.0: {}".format(str(dec.get_act_energy_pos_kwh()))) - g_log.info("2.8.0: {}".format(str(dec.get_act_energy_neg_kwh()))) + for e in g_cfg.get_wanted_values(): + g_log.info("{0:6}: {1:26}: {2:10}".format(dec.get_generic_position(e),dec.get_generic_name(e)+" ("+dec.get_generic_unit(e)+")",str(dec.get_generic_value(e)))) # export solarview if g_cfg.get_export_format() == 'SOLARVIEW': exp = Exporter(g_cfg.get_export_file_abspath(), g_cfg.get_export_format()) - exp.set_value(Obis.RealEnergyIn_S, dec.get_act_energy_pos_kwh()) - exp.set_value(Obis.RealEnergyOut_S, dec.get_act_energy_neg_kwh()) + for e in g_cfg.get_wanted_values(): + if dec.get_generic_value(e) != None: + exp.set_value(dec.get_generic_position(e), dec.get_generic_value(e), dec.get_generic_unit(e)) if not exp.write_out(): g_log.error("Could not export data") sys.exit(50) # export mqtt if g_cfg.get_export_format() == 'MQTT': - mqtt_pub_ret = mqtt_client.publish("{}/RealEnergyIn_S".format(g_cfg.get_export_mqtt_basetopic()), dec.get_act_energy_pos_kwh()) - g_log.debug("MQTT: Publish message: rc: {} mid: {}".format(mqtt_pub_ret[0], mqtt_pub_ret[1])) - mqtt_pub_ret = mqtt_client.publish("{}/RealEnergyOut_S".format(g_cfg.get_export_mqtt_basetopic()), dec.get_act_energy_neg_kwh()) - g_log.debug("MQTT: Publish message: rc: {} mid: {}".format(mqtt_pub_ret[0], mqtt_pub_ret[1])) + for e in g_cfg.get_wanted_values(): + mqtt_pub_ret = mqtt_client.publish("{}/{}".format(g_cfg.get_export_mqtt_basetopic(),dec.get_generic_mqtttopic(e)), dec.get_generic_value(e)) + g_log.debug("MQTT: Publish message: rc: {} mid: {}".format(mqtt_pub_ret[0], mqtt_pub_ret[1])) diff --git a/meter_template.json b/meter_template.json index a0b7b9f..5af2e19 100644 --- a/meter_template.json +++ b/meter_template.json @@ -15,5 +15,20 @@ "export_mqtt_port": 1883, "export_mqtt_user": "mymqttuser", "export_mqtt_password": "supersecretmqttpass", - "export_mqtt_basetopic": "kaifareader" + "export_mqtt_basetopic": "kaifareader", + "wanted_values": [ + "VoltageL1", + "VoltageL2", + "VoltageL3", + "CurrentL1", + "CurrentL2", + "CurrentL3", + "RealPowerIn", + "RealPowerOut", + "RealEnergyIn", + "RealEnergyOut", + "ReactiveEnergyIn", + "ReactiveEnergyOut", + "Factor" + ] } From f64eb672cd3b84c42d8c2a2fc681386e71da1add Mon Sep 17 00:00:00 2001 From: Thomas Anglmaier Date: Mon, 21 Feb 2022 09:44:08 +0100 Subject: [PATCH 3/7] fix documentation --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 903c320..ea5b5b7 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ A template file `meter_template.json` can be recycled for this. "stopbits": "serial.STOPBITS_ONE", "bytesize": "serial.EIGHTBITS", "key_hex_string": "", - "interval": 15, + "interval": 1, "supplier": "TINETZ", "export_format": "SOLARVIEW", "export_file_abspath": "/var/run/kaifareader/kaifa.txt", @@ -84,6 +84,9 @@ the telegrams differ. This script was tested with suppliers: Make sure to only select the values you need from the list in `wanted_values` by deleting the elements you don't want. The logfile will show the related OBIS-Values when run in `logging.INFO`. +`interval` must be `<5` seconds, `1` seco0nd is recommended tho. + + ### Export **Solarview** From 54b2ec08487b037fb8af8feb8408e673547acec2 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 21 Feb 2022 09:45:54 +0100 Subject: [PATCH 4/7] fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ea5b5b7..8c289c6 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ the telegrams differ. This script was tested with suppliers: Make sure to only select the values you need from the list in `wanted_values` by deleting the elements you don't want. The logfile will show the related OBIS-Values when run in `logging.INFO`. -`interval` must be `<5` seconds, `1` seco0nd is recommended tho. +`interval` must be `<5` seconds, `1` second is recommended tho. ### Export From e7900d4bb7849b3194a69bc7583f715fa3a9f176 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 13 Jun 2022 09:00:31 +0200 Subject: [PATCH 5/7] Make sure we start after networking is online --- systemd/kaifareader.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/systemd/kaifareader.service b/systemd/kaifareader.service index ae3957a..361c5bb 100644 --- a/systemd/kaifareader.service +++ b/systemd/kaifareader.service @@ -1,6 +1,6 @@ [Unit] Description=kaifareader -After=syslog.target network.target ntp.service +After=network-online.target [Service] ExecStartPre=/bin/mkdir -p /var/run/kaifareader From 84a5c874a2d65aeb717e867c7d628055e536f3cc Mon Sep 17 00:00:00 2001 From: Thomas Anglmaier Date: Mon, 13 Jun 2022 21:47:54 +0200 Subject: [PATCH 6/7] Fix systemd --- systemd/kaifareader.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/systemd/kaifareader.service b/systemd/kaifareader.service index 361c5bb..0d4b39e 100644 --- a/systemd/kaifareader.service +++ b/systemd/kaifareader.service @@ -1,6 +1,6 @@ [Unit] Description=kaifareader -After=network-online.target +After=systemd-networkd-wait-online.service [Service] ExecStartPre=/bin/mkdir -p /var/run/kaifareader From f1adcb0a19d95a28c4876e4c0452e51b468adea5 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 17 Jun 2022 12:42:31 +0200 Subject: [PATCH 7/7] Make wait-for-network more versatile --- systemd/kaifareader.service | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/systemd/kaifareader.service b/systemd/kaifareader.service index 0d4b39e..e5623fd 100644 --- a/systemd/kaifareader.service +++ b/systemd/kaifareader.service @@ -1,6 +1,7 @@ [Unit] Description=kaifareader -After=systemd-networkd-wait-online.service +Wants=network-online.target +After=network-online.target [Service] ExecStartPre=/bin/mkdir -p /var/run/kaifareader