From 2f6e219e5012fbaf5ad57d664758e56f02425962 Mon Sep 17 00:00:00 2001 From: Ard Kuijpers Date: Wed, 3 Jun 2020 13:15:53 +0200 Subject: [PATCH] Initial commit --- .gitignore | 8 ++ __init__.py | 1 + device_dsmr.py | 60 +++++++++++++ dsmr2mqtt.py | 134 ++++++++++++++++++++++++++++++ node/__init__.py | 0 node/node_electricitymeter.py | 85 +++++++++++++++++++ node/node_gasmeter.py | 31 +++++++ node/property/__init__.py | 0 node/property/property_current.py | 37 +++++++++ node/property/property_energy.py | 37 +++++++++ node/property/property_power.py | 37 +++++++++ node/property/property_voltage.py | 37 +++++++++ node/property/property_volume.py | 37 +++++++++ settings.py | 31 +++++++ 14 files changed, 535 insertions(+) create mode 100644 .gitignore create mode 100644 __init__.py create mode 100644 device_dsmr.py create mode 100644 dsmr2mqtt.py create mode 100644 node/__init__.py create mode 100644 node/node_electricitymeter.py create mode 100644 node/node_gasmeter.py create mode 100644 node/property/__init__.py create mode 100644 node/property/property_current.py create mode 100644 node/property/property_energy.py create mode 100644 node/property/property_power.py create mode 100644 node/property/property_voltage.py create mode 100644 node/property/property_volume.py create mode 100644 settings.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bec6235 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +.mypy_cache/ + +# local files +.env +.vscode diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..a68927d --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +__version__ = "0.1.0" \ No newline at end of file diff --git a/device_dsmr.py b/device_dsmr.py new file mode 100644 index 0000000..50275e8 --- /dev/null +++ b/device_dsmr.py @@ -0,0 +1,60 @@ +from homie.device_base import Device_Base +from homie.node.node_base import Node_Base + + + +from settings import Settings + +import logging + +logger = logging.getLogger(__name__) + +SETTINGS = Settings() + +TRANSLATED_MQTT_SETTINGS = { + 'MQTT_BROKER': SETTINGS.mqtt_host, + 'MQTT_PORT': SETTINGS.mqtt_port, + 'MQTT_USERNAME' : SETTINGS.mqtt_username, + 'MQTT_PASSWORD' : SETTINGS.mqtt_password, + 'MQTT_CLIENT_ID' : SETTINGS.hostname, + 'MQTT_SHARE_CLIENT': False, +} + +TRANSLATED_HOMIE_SETTINGS = { + 'topic' : SETTINGS.homie_topic, + 'fw_name' : SETTINGS.homie_fw_name, + 'fw_version' : SETTINGS.homie_fw_version, + 'update_interval' : SETTINGS.homie_update_interval, +} + +class Device_DSMR(Device_Base): + def __init__(self, device_id=None, name=None, homie_settings=TRANSLATED_HOMIE_SETTINSG, mqtt_settings=TRANSLATED_MQTT_SETTINGS): + + super().__init__(device_id, name, homie_settings, mqtt_settings) + + node = Node_Base(self, "gasmeter", "Gasmeter", "status") + self.add_node(node) + + + + + + self.start() + + + def register_status_properties(self, node): + super(Device_Temperature_Humidity_Battery, self).register_status_properties( + node + ) + + self.battery = Property_Battery(node) + node.add_property(self.battery) + + def update_battery(self, battery): + logger.info("Updated Battery {}".format(battery)) + self.battery.value = battery + + + + /Ene5\T210-D ESMR5.0 + diff --git a/dsmr2mqtt.py b/dsmr2mqtt.py new file mode 100644 index 0000000..8d86328 --- /dev/null +++ b/dsmr2mqtt.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +# Python script om P1 telegram weer te geven + +import datetime +import re +import serial +import homie +import paho.mqtt.client as paho + +VERSION = 0.1 + + + +def on_publish(client,userdata,result): #create function for callback + print("data published \n") + pass + +client1=paho.Client("control1") #create client object +client1.on_publish = on_publish #assign function to callback +client1.connect(broker,port) #establish connection + + +class DSMRConnection: + def __init__(self): + """Seriele poort confguratie naar DSMR configuratie""" + cfg = Settings() + self.serial = serial.Serial( + port=cfg.dsmr_port, + baudrate=cfg.dsmr_baudrate, + parity=serial.PARITY_NAMES[cfg.dsmr_parity], + timeout=10, + xonxoff=0 + ) + + self.mqttclient = paho.Client(cfg.) + +kwhtot = 0 +kwhoud = 0 +kwhverschil = 0 + +gastot = 0 +gasoud = 0 +gasverschil = 0 + +# Telegram +# +# /KMP5 KA6U001660740912 +# +# 0-0:96.1.1(204B413655303031363630373430393132) +# 1-0:1.8.1(13629.373*kWh) +# 1-0:1.8.2(14700.866*kWh) +# 1-0:2.8.1(00000.000*kWh) +# 1-0:2.8.2(00000.000*kWh) +# 0-0:96.14.0(0001) +# 1-0:1.7.0(0000.64*kW) +# 1-0:2.7.0(0000.00*kW) +# 0-0:96.13.1() +# 0-0:96.13.0() +# 0-1:24.1.0(3) +# 0-1:96.1.0(3238313031353431303037343732343132) +# 0-1:24.3.0(180428140000)(08)(60)(1)(0-1:24.2.1)(m3) +# (03862.650) +# ! + +while True: + ser.open() + checksum_found = False + gasflag = 0 + + while not checksum_found: + telegram_line = ser.readline() # Lees een seriele lijn in. + telegram_line = telegram_line.decode('ascii').strip() # Strip spaties en blanke regels + + #print (telegram_line) #debug + + if re.match(b'(?=1-0:1.7.0)', telegram_line): #1-0:1.7.0 = Actueel verbruik in kW + # 1-0:1.7.0(0000.54*kW) + kw = telegram_line[10:-4] # Knip het kW gedeelte eruit (0000.54) + watt = float(kw) * 1000 # vermengvuldig met 1000 voor conversie naar Watt (540.0) + watt = int(watt) # rond float af naar heel getal (540) + + if re.match(b'(?=1-0:1.8.1)', telegram_line): #1-0:1.8.1 - Hoog tarief / 1-0:1.8.1(13579.595*kWh) + kwh1 = telegram_line[10:-5] # Knip het kWh gedeelte eruit (13579.595) + + if re.match(b'(?=1-0:1.8.2)', telegram_line): #1-0:1.8.2 - Laag tarief / 1-0:1.8.2(14655.223*kWh) + kwh2 = telegram_line[10:-5] # Knip het kWh gedeelte eruit (14655.223) + + if gasflag == 1: + gas = telegram_line[1:-1] + gasflag = 0 + + if re.match(b'(?=0-1:24.3.0)', telegram_line): #0-1:24.3.0 - Gasverbruik + gasflag = 1 + + # Check wanneer het uitroepteken ontavangen wordt (einde telegram) + if re.match(b'(?=!)', telegram_line): + checksum_found = True + + ser.close() + +####################################### + if kwhoud < 1: #Script eerste keer opgestart, sla waarde op + kwhoud = kwhtot + + kwhtot = float(kwh1) + float(kwh2) + kwhverschil = round(float(kwhtot) - float(kwhoud), 3) + + if gasoud < 1: #Script eerste keer opgestart, sla waarde op + gasoud = gas + gasouduur = gas + + gasverschil = round(float(gas) - float(gasoud), 3) + gasverschiluur = round(float(gas) - float(gasouduur), 3) + +# Reset tellers + + tijd = str(datetime.datetime.now().time())[0:-10] + tijdmin = str(datetime.datetime.now().time())[3:-10] + + if tijd == "00:00": # Reset de counter van de dag + kwhoud = kwhtot + gasoud = gas + + if tijdmin == "00": # Reset de counter van het uur + gasouduur = gas + +###################################### +# MQTT PUBLISH +###################################### + + client1.publish("elektra\w", watt) + client1.publish("elektra\kwh", kwhverschil) + client1.publish("gas", gasverschil) + client1.publish("gas\\uur", gasverschiluur) \ No newline at end of file diff --git a/node/__init__.py b/node/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/node/node_electricitymeter.py b/node/node_electricitymeter.py new file mode 100644 index 0000000..c83f041 --- /dev/null +++ b/node/node_electricitymeter.py @@ -0,0 +1,85 @@ +from homie.node.node_base import Node_Base + +from homie.node.property.property_enum import Property_Enum +from homie.node.property.property_integer import Property_Integer +from dsmr2mqtt.node.property.property_energy import Property_Energy +from dsmr2mqtt.node.property.property_power import Property_Power +from dsmr2mqtt.node.property.property_current import Property_Current +from dsmr2mqtt.node.property.property_voltage import Property_Voltage + + +class Node_ElectricityMeter(Node_Base): + def __init__( + self, + device, + id="electricitymeter", + name="Electricity meter", + type_="state", + retain=True, + qos=1, + state_values=None, + set_state=None, + ): + assert state_values + assert set_state + + super().__init__(device, id, name, type_, retain, qos) + + + self.add_property(Property_Integer(self, "tariff_indicator", "Tariff indicator", data_format="1:2", settable=False)) + + self.add_property(Property_Energy(self, "delivery_tariff1", "Delivery tariff 1")) + self.add_property(Property_Energy(self, "delivery_tariff2", "Delivery tariff 2")) + self.add_property(Property_Power(self, "power", "Power")) + + self.add_property(Property_Voltage(self, "voltage_L1", "Voltage L1")) + self.add_property(Property_Voltage(self, "voltage_L2", "Voltage L2")) + self.add_property(Property_Voltage(self, "voltage_L3", "Voltage L3")) + + self.add_property(Property_Current(self, "current_L1", "Current L1")) + self.add_property(Property_Current(self, "current_L2", "Current L2")) + self.add_property(Property_Current(self, "current_L3", "Current L3")) + + self.add_property(Property_Power(self, "power_L1", "Power L1")) + self.add_property(Property_Power(self, "power_L2", "Power L2")) + self.add_property(Property_Power(self, "power_L3", "Power L3")) + + def update_status(self, telegram: str): + + #Telegram + # 1-3:0.2.8(50) + # 0-0:1.0.0(200603122725S) + # 0-0:96.1.1(4530303438303030303339393038333139) + # 1-0:1.8.1(001807.864*kWh) + # 1-0:1.8.2(001173.872*kWh) + # 1-0:2.8.1(000000.091*kWh) + # 1-0:2.8.2(000000.000*kWh) + # 0-0:96.14.0(0002) + # 1-0:1.7.0(00.909*kW) + # 1-0:2.7.0(00.000*kW) + # 0-0:96.7.21(00016) + # 0-0:96.7.9(00003) + # 1-0:99.97.0(0)(0-0:96.7.19) + # 1-0:32.32.0(00002) + # 1-0:52.32.0(00002) + # 1-0:72.32.0(00002) + # 1-0:32.36.0(00000) + # 1-0:52.36.0(00000) + # 1-0:72.36.0(00000) + # 0-0:96.13.0() + # 1-0:32.7.0(235.0*V) + # 1-0:52.7.0(237.0*V) + # 1-0:72.7.0(236.0*V) + # 1-0:31.7.0(001*A) + # 1-0:51.7.0(001*A) + # 1-0:71.7.0(001*A) + # 1-0:21.7.0(00.290*kW) + # 1-0:41.7.0(00.268*kW) + # 1-0:61.7.0(00.350*kW) + # 1-0:22.7.0(00.000*kW) + # 1-0:42.7.0(00.000*kW) + # 1-0:62.7.0(00.000*kW) + # 0-1:24.1.0(003) + # 0-1:96.1.0(4730303732303033393435373234323139) + # 0-1:24.2.1(200603122500S)(01741.782*m3) + pass diff --git a/node/node_gasmeter.py b/node/node_gasmeter.py new file mode 100644 index 0000000..9346b87 --- /dev/null +++ b/node/node_gasmeter.py @@ -0,0 +1,31 @@ +from homie.node.node_base import Node_Base + +from homie.node.property.property_enum import Property_Enum +from dsmr2mqtt.node.property.property_volume import Property_Volume + + +class Node_Gasmeter(Node_Base): + def __init__( + self, + device, + id="gasmeter", + name="Gas meter", + type_="state", + retain=True, + qos=1, + state_values=None, + set_state=None, + ): + assert state_values + assert set_state + + super().__init__(device, id, name, type_, retain, qos) + + self.add_property( + Property_Volume( + self, "volume", "Volume", data_format=state_values, set_value=set_state + ) + ) + + def update_volume(self, volume): + self.get_property("volume").value = volume diff --git a/node/property/__init__.py b/node/property/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/node/property/property_current.py b/node/property/property_current.py new file mode 100644 index 0000000..121deaf --- /dev/null +++ b/node/property/property_current.py @@ -0,0 +1,37 @@ +from homie.node.property.property_float import Property_Float + + +class Property_Current(Property_Float): + def __init__( + self, + node, + id="current", + name="Current", + settable=False, + retained=True, + qos=1, + unit="A", + data_type=None, + data_format=None, + value=None, + set_value=None, + tags=[], + meta={}, + ): + + super().__init__( + node, + id, + name, + settable, + retained, + qos, + unit, + data_type, + data_format, + value, + set_value, + tags, + meta, + ) + diff --git a/node/property/property_energy.py b/node/property/property_energy.py new file mode 100644 index 0000000..43409de --- /dev/null +++ b/node/property/property_energy.py @@ -0,0 +1,37 @@ +from homie.node.property.property_float import Property_Float + + +class Property_Energy(Property_Float): + def __init__( + self, + node, + id="energy", + name="Energy", + settable=False, + retained=True, + qos=1, + unit="kWh", + data_type=None, + data_format=None, + value=None, + set_value=None, + tags=[], + meta={}, + ): + + super().__init__( + node, + id, + name, + settable, + retained, + qos, + unit, + data_type, + data_format, + value, + set_value, + tags, + meta, + ) + diff --git a/node/property/property_power.py b/node/property/property_power.py new file mode 100644 index 0000000..f2ac040 --- /dev/null +++ b/node/property/property_power.py @@ -0,0 +1,37 @@ +from homie.node.property.property_float import Property_Float + + +class Property_Power(Property_Float): + def __init__( + self, + node, + id="power", + name="Power", + settable=False, + retained=True, + qos=1, + unit="W", + data_type=None, + data_format=None, + value=None, + set_value=None, + tags=[], + meta={}, + ): + + super().__init__( + node, + id, + name, + settable, + retained, + qos, + unit, + data_type, + data_format, + value, + set_value, + tags, + meta, + ) + diff --git a/node/property/property_voltage.py b/node/property/property_voltage.py new file mode 100644 index 0000000..e25fc36 --- /dev/null +++ b/node/property/property_voltage.py @@ -0,0 +1,37 @@ +from homie.node.property.property_float import Property_Float + + +class Property_Voltage(Property_Float): + def __init__( + self, + node, + id="voltage", + name="Voltage", + settable=False, + retained=True, + qos=1, + unit="V", + data_type=None, + data_format=None, + value=None, + set_value=None, + tags=[], + meta={}, + ): + + super().__init__( + node, + id, + name, + settable, + retained, + qos, + unit, + data_type, + data_format, + value, + set_value, + tags, + meta, + ) + diff --git a/node/property/property_volume.py b/node/property/property_volume.py new file mode 100644 index 0000000..76a4d2a --- /dev/null +++ b/node/property/property_volume.py @@ -0,0 +1,37 @@ +from homie.node.property.property_float import Property_Float + + +class Property_Volume(Property_Float): + def __init__( + self, + node, + id="volume", + name="Volume", + settable=False, + retained=True, + qos=1, + unit="m³", + data_type=None, + data_format=None, + value=None, + set_value=None, + tags=[], + meta={}, + ): + + super().__init__( + node, + id, + name, + settable, + retained, + qos, + unit, + data_type, + data_format, + value, + set_value, + tags, + meta, + ) + diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..b8229e1 --- /dev/null +++ b/settings.py @@ -0,0 +1,31 @@ +from pydantic import BaseSettings, Field + +from openhab.__init__ import __version__ + + +class Settings(BaseSettings): + """Application settings for the DSMR MQTT bridge.""" + + loglevel: str = Field('INFO', env='LOGLEVEL') + + mqtt_host: str = Field(None, env='MQTT_HOST') + mqtt_port: int = Field(1883, env='MQTT_PORT') + mqtt_username: str = Field(None, env='MQTT_USERNAME') + mqtt_password: str = Field(None, env='MQTT_PASSWORD') + + dsmr_port: str = Field('/dev/ttyUSB0', env='DSMR_PORT') + dsmr_baudrate: str = Field(115200, env='DSMR_BAUDRATE') + dsmr_bytesize: str = Field('EIGHTBITS', env='DSMR_BITESIZE') + dsmr_parity: str = Field('NONE', env='DSMR_PARITY') + dsmr_stopbits: str = Field('ONE', env='DSMR_STOPBITS') + + homie_update_interval: int = 60 + homie_topic: str = Field('homie', env='HOMIE_TOPIC') + homie_implementation: str \ + = f"DSMR Homie {VERSION} Homie 4 Version {homie.__version__}" + homie_fw_name: str = "DSMR" + homie_fw_version: str = "0.1.0" + + class Config: + """Where to find the environment file containing the settings.""" + env_file = '.env' \ No newline at end of file