From 2d0b259e24e1ce5ebf867d44e06e6780eba2b50b Mon Sep 17 00:00:00 2001 From: TrystanLea Date: Sat, 8 May 2021 10:54:53 +0100 Subject: [PATCH 01/14] set modbus address --- src/interfacers/EmonHubMinimalModbusInterfacer.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/interfacers/EmonHubMinimalModbusInterfacer.py b/src/interfacers/EmonHubMinimalModbusInterfacer.py index d764ffa0..3efb5afd 100644 --- a/src/interfacers/EmonHubMinimalModbusInterfacer.py +++ b/src/interfacers/EmonHubMinimalModbusInterfacer.py @@ -44,6 +44,7 @@ def __init__(self, name, device="/dev/modbus", baud=2400): 'read_interval': 10.0, 'nodename':'sdm120', 'prefix':'', + 'address':1, 'registers': [0,6,12,18,30,70,72,74,76], 'names': ['V','I','P','VA','PF','FR','EI','EE','RI'], 'precision': [2,3,1,1,3,3,3,3,3] @@ -64,8 +65,8 @@ def __init__(self, name, device="/dev/modbus", baud=2400): self._rs485.serial.stopbits = 1 self._rs485.serial.timeout = 1 self._rs485.debug = False - self._rs485.mode = minimalmodbus.MODE_RTU - + self._rs485.mode = minimalmodbus.MODE_RTU + except ModuleNotFoundError as err: self._log.error(err) self._rs485 = False @@ -88,6 +89,9 @@ def read(self): c.nodeid = self._settings['nodename'] if self._rs485: + # Set modbus address + self._rs485.address = self._settings['address'] + for i in range(0,len(self._settings['registers'])): valid = True try: @@ -144,6 +148,10 @@ def set(self, **kwargs): self._log.info("Setting %s prefix: %s", self.name, setting) self._settings[key] = str(setting) continue + elif key == 'address': + self._log.info("Setting %s address: %s", self.name, setting) + self._settings[key] = int(setting) + continue elif key == 'registers': self._log.info("Setting %s datafields: %s", self.name, ",".join(setting)) self._settings[key] = setting From 1e816c4841060ba3b174647fce8499ef7eee606a Mon Sep 17 00:00:00 2001 From: TrystanLea Date: Sat, 8 May 2021 11:00:23 +0100 Subject: [PATCH 02/14] allow reading from multiple meters --- .../EmonHubMinimalModbusInterfacer.py | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/interfacers/EmonHubMinimalModbusInterfacer.py b/src/interfacers/EmonHubMinimalModbusInterfacer.py index 3efb5afd..28c39c5e 100644 --- a/src/interfacers/EmonHubMinimalModbusInterfacer.py +++ b/src/interfacers/EmonHubMinimalModbusInterfacer.py @@ -16,6 +16,7 @@ read_interval = 10 nodename = sdm120 # prefix = sdm_ + addresses = 1, registers = 0,6,12,18,30,70,72,74,76 names = V,I,P,VA,PF,FR,EI,EE,RI precision = 2,3,1,1,3,3,3,3,3 @@ -44,7 +45,7 @@ def __init__(self, name, device="/dev/modbus", baud=2400): 'read_interval': 10.0, 'nodename':'sdm120', 'prefix':'', - 'address':1, + 'addresses':[1], 'registers': [0,6,12,18,30,70,72,74,76], 'names': ['V','I','P','VA','PF','FR','EI','EE','RI'], 'precision': [2,3,1,1,3,3,3,3,3] @@ -89,30 +90,32 @@ def read(self): c.nodeid = self._settings['nodename'] if self._rs485: - # Set modbus address - self._rs485.address = self._settings['address'] - for i in range(0,len(self._settings['registers'])): - valid = True - try: - value = self._rs485.read_float(int(self._settings['registers'][i]), functioncode=4, number_of_registers=2) - except Exception as e: - valid = False - self._log.error("Could not read register @ "+self._settings['registers'][i]+": " + str(e)) + # Set modbus address + for addr in self._settings['addresses']: + self._rs485.address = addr - if valid: - # replace datafield name with custom name - if i0: self._log.debug(c.realdata) @@ -150,7 +153,7 @@ def set(self, **kwargs): continue elif key == 'address': self._log.info("Setting %s address: %s", self.name, setting) - self._settings[key] = int(setting) + self._settings[key] = setting continue elif key == 'registers': self._log.info("Setting %s datafields: %s", self.name, ",".join(setting)) From 3e44c5d883716627d9c7dd5686c36de53e111f0e Mon Sep 17 00:00:00 2001 From: TrystanLea Date: Sat, 8 May 2021 11:05:58 +0100 Subject: [PATCH 03/14] fix addresses setting --- src/interfacers/EmonHubMinimalModbusInterfacer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/interfacers/EmonHubMinimalModbusInterfacer.py b/src/interfacers/EmonHubMinimalModbusInterfacer.py index 28c39c5e..6398b334 100644 --- a/src/interfacers/EmonHubMinimalModbusInterfacer.py +++ b/src/interfacers/EmonHubMinimalModbusInterfacer.py @@ -93,7 +93,7 @@ def read(self): # Set modbus address for addr in self._settings['addresses']: - self._rs485.address = addr + self._rs485.address = int(addr) for i in range(0,len(self._settings['registers'])): valid = True @@ -151,8 +151,8 @@ def set(self, **kwargs): self._log.info("Setting %s prefix: %s", self.name, setting) self._settings[key] = str(setting) continue - elif key == 'address': - self._log.info("Setting %s address: %s", self.name, setting) + elif key == 'addresses': + self._log.info("Setting %s address: %s", self.name, ",".join(setting)) self._settings[key] = setting continue elif key == 'registers': From f2f6abd1cfa712e12609a85a001af05011982ee7 Mon Sep 17 00:00:00 2001 From: TrystanLea Date: Sat, 8 May 2021 11:11:13 +0100 Subject: [PATCH 04/14] default needs to be string --- src/interfacers/EmonHubMinimalModbusInterfacer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfacers/EmonHubMinimalModbusInterfacer.py b/src/interfacers/EmonHubMinimalModbusInterfacer.py index 6398b334..5d0ee26a 100644 --- a/src/interfacers/EmonHubMinimalModbusInterfacer.py +++ b/src/interfacers/EmonHubMinimalModbusInterfacer.py @@ -45,7 +45,7 @@ def __init__(self, name, device="/dev/modbus", baud=2400): 'read_interval': 10.0, 'nodename':'sdm120', 'prefix':'', - 'addresses':[1], + 'addresses':['1'], 'registers': [0,6,12,18,30,70,72,74,76], 'names': ['V','I','P','VA','PF','FR','EI','EE','RI'], 'precision': [2,3,1,1,3,3,3,3,3] From 2fd1b848928c012e81f53306d0441e390a1f7abf Mon Sep 17 00:00:00 2001 From: TrystanLea Date: Tue, 8 Feb 2022 20:13:45 +0000 Subject: [PATCH 05/14] add address in input name --- src/interfacers/EmonHubMinimalModbusInterfacer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfacers/EmonHubMinimalModbusInterfacer.py b/src/interfacers/EmonHubMinimalModbusInterfacer.py index 5d0ee26a..12c4de34 100644 --- a/src/interfacers/EmonHubMinimalModbusInterfacer.py +++ b/src/interfacers/EmonHubMinimalModbusInterfacer.py @@ -113,7 +113,7 @@ def read(self): if i Date: Tue, 22 Feb 2022 16:59:07 +0000 Subject: [PATCH 06/14] fix concat error --- .../EmonHubMinimalModbusInterfacer.py | 97 ++++++++++++------- 1 file changed, 61 insertions(+), 36 deletions(-) diff --git a/src/interfacers/EmonHubMinimalModbusInterfacer.py b/src/interfacers/EmonHubMinimalModbusInterfacer.py index 12c4de34..3686f4d4 100644 --- a/src/interfacers/EmonHubMinimalModbusInterfacer.py +++ b/src/interfacers/EmonHubMinimalModbusInterfacer.py @@ -16,10 +16,17 @@ read_interval = 10 nodename = sdm120 # prefix = sdm_ - addresses = 1, - registers = 0,6,12,18,30,70,72,74,76 - names = V,I,P,VA,PF,FR,EI,EE,RI - precision = 2,3,1,1,3,3,3,3,3 + [[[[meters]]]] + [[[[[sdm120a]]]]] + address = 1 + registers = 0,6,12,18,30,70,72,74,76 + names = V,I,P,VA,PF,FR,EI,EE,RI + precision = 2,3,1,1,3,3,3,3,3 + [[[[[sdm120b]]]]] + address = 2 + registers = 0,6,12,18,30,70,72,74,76 + names = V,I,P,VA,PF,FR,EI,EE,RI + precision = 2,3,1,1,3,3,3,3,3 """ """class EmonHubSDM120Interfacer @@ -45,10 +52,7 @@ def __init__(self, name, device="/dev/modbus", baud=2400): 'read_interval': 10.0, 'nodename':'sdm120', 'prefix':'', - 'addresses':['1'], - 'registers': [0,6,12,18,30,70,72,74,76], - 'names': ['V','I','P','VA','PF','FR','EI','EE','RI'], - 'precision': [2,3,1,1,3,3,3,3,3] + 'meters':[] } self.next_interval = True @@ -91,29 +95,29 @@ def read(self): if self._rs485: - # Set modbus address - for addr in self._settings['addresses']: - self._rs485.address = int(addr) + # Support for multiple MBUS meters on a single bus + for meter in self._settings['meters']: + self._rs485.address = self._settings['meters'][meter]['address'] - for i in range(0,len(self._settings['registers'])): + for i in range(0,len(self._settings['meters'][meter]['registers'])): valid = True try: - value = self._rs485.read_float(int(self._settings['registers'][i]), functioncode=4, number_of_registers=2) + value = self._rs485.read_float(int(self._settings['meters'][meter]['registers'][i]), functioncode=4, number_of_registers=2) except Exception as e: valid = False - self._log.error("Could not read register @ "+self._settings['registers'][i]+": " + str(e)) + self._log.error("Could not read register @ "+str(self._settings['meters'][meter]['registers'][i])+": " + str(e)) if valid: # replace datafield name with custom name - if i Date: Fri, 18 Mar 2022 16:36:50 +0000 Subject: [PATCH 07/14] needs short sleep --- src/interfacers/EmonHubMinimalModbusInterfacer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfacers/EmonHubMinimalModbusInterfacer.py b/src/interfacers/EmonHubMinimalModbusInterfacer.py index 3686f4d4..1dbc849e 100644 --- a/src/interfacers/EmonHubMinimalModbusInterfacer.py +++ b/src/interfacers/EmonHubMinimalModbusInterfacer.py @@ -120,7 +120,7 @@ def read(self): c.names.append(self._settings['prefix']+str(meter)+"_"+name) c.realdata.append(value) # self._log.debug(str(name)+": "+str(value)) - + time.sleep(0.1) if len(c.realdata)>0: self._log.debug(c.realdata) return c From b79d6c9074fc192f2c132e78c72e920e021a8109 Mon Sep 17 00:00:00 2001 From: TrystanLea Date: Wed, 23 Mar 2022 15:42:29 +0000 Subject: [PATCH 08/14] auto reconnect if modbus disconnects --- .../EmonHubMinimalModbusInterfacer.py | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/src/interfacers/EmonHubMinimalModbusInterfacer.py b/src/interfacers/EmonHubMinimalModbusInterfacer.py index 3686f4d4..536b5b6d 100644 --- a/src/interfacers/EmonHubMinimalModbusInterfacer.py +++ b/src/interfacers/EmonHubMinimalModbusInterfacer.py @@ -60,22 +60,31 @@ def __init__(self, name, device="/dev/modbus", baud=2400): # Only load module if it is installed try: import minimalmodbus + self.minimalmodbus = minimalmodbus # import serial - self._log.info("Connecting to Modbus device="+str(device)+" baud="+str(baud)) + except ModuleNotFoundError as err: + self._log.error(err) + self._rs485 = False + + self.device = device + self.baud = baud + self.rs485_connect() + + def rs485_connect(self): + try: + self._log.info("Connecting to Modbus device="+str(self.device)+" baud="+str(self.baud)) - self._rs485 = minimalmodbus.Instrument(device, 1) - self._rs485.serial.baudrate = baud + self._rs485 = self.minimalmodbus.Instrument(self.device, 1) + self._rs485.serial.baudrate = self.baud self._rs485.serial.bytesize = 8 - self._rs485.serial.parity = minimalmodbus.serial.PARITY_NONE + self._rs485.serial.parity = self.minimalmodbus.serial.PARITY_NONE self._rs485.serial.stopbits = 1 self._rs485.serial.timeout = 1 self._rs485.debug = False - self._rs485.mode = minimalmodbus.MODE_RTU - - except ModuleNotFoundError as err: - self._log.error(err) + self._rs485.mode = self.minimalmodbus.MODE_RTU + except Exception: + self._log.error("Could not connect to Modbus device") self._rs485 = False - def read(self): """Read data and process @@ -94,17 +103,21 @@ def read(self): c.nodeid = self._settings['nodename'] if self._rs485: + invalid_count = 0 + register_count = 0 # Support for multiple MBUS meters on a single bus for meter in self._settings['meters']: self._rs485.address = self._settings['meters'][meter]['address'] for i in range(0,len(self._settings['meters'][meter]['registers'])): + register_count += 1 valid = True try: value = self._rs485.read_float(int(self._settings['meters'][meter]['registers'][i]), functioncode=4, number_of_registers=2) except Exception as e: valid = False + invalid_count += 1 self._log.error("Could not read register @ "+str(self._settings['meters'][meter]['registers'][i])+": " + str(e)) if valid: @@ -120,12 +133,19 @@ def read(self): c.names.append(self._settings['prefix']+str(meter)+"_"+name) c.realdata.append(value) # self._log.debug(str(name)+": "+str(value)) - + + time.sleep(0.1) + + if invalid_count==register_count: + self._log.error("Could not read all registers") + self.rs485_connect() + if len(c.realdata)>0: self._log.debug(c.realdata) return c else: self._log.error("Not connected to modbus device") + self.rs485_connect() else: self.next_interval = True From d5cde95a825f85e8e42465f311b8a9e6754a9bbe Mon Sep 17 00:00:00 2001 From: Glyn Hudson Date: Sat, 20 Aug 2022 01:08:59 +0100 Subject: [PATCH 09/14] required changes to support samsung ASHP modbus --- ...onHubSamsungASHPMinimalModbusInterfacer.py | 214 ++++++++++++++++++ src/interfacers/__init__.py | 1 + 2 files changed, 215 insertions(+) create mode 100644 src/interfacers/EmonHubSamsungASHPMinimalModbusInterfacer.py diff --git a/src/interfacers/EmonHubSamsungASHPMinimalModbusInterfacer.py b/src/interfacers/EmonHubSamsungASHPMinimalModbusInterfacer.py new file mode 100644 index 00000000..e1c421e2 --- /dev/null +++ b/src/interfacers/EmonHubSamsungASHPMinimalModbusInterfacer.py @@ -0,0 +1,214 @@ +import time +import json +import Cargo +import os +import glob +from emonhub_interfacer import EmonHubInterfacer + +""" +[[MIMB19N]] + Type = EmonHubSamsungASHPMinimalModbusInterfacer + [[[init_settings]]] + device = /dev/ttyUSB0 + baud = 9600 + [[[runtimesettings]]] + pubchannels = ToEmonCMS, + read_interval = 10 + nodename = samsung-mib19n + # prefix = sdm_ + [[[[meters]]]] + [[[[[ashp]]]]] + address = 1 + registers = 75,74,72,65,66,68,52,59,58,2,79 + names = dhw_temp,dhw_target,dhw_status,return_temp,flow_temp,flow_target,heating_status,indoor_temp,indoor_target, defrost_status, away_status + scales = 0.1,0.1,1,0.1,0.1,0.1,1,0.1,0,1,1,1 +""" + +"""class EmonHubSDM120Interfacer + +SDM120 interfacer for use in development + +""" + +class EmonHubSamsungASHPMinimalModbusInterfacer(EmonHubInterfacer): + + def __init__(self, name, device="/dev/modbus", baud=9600): + """Initialize Interfacer + + """ + # Initialization + super(EmonHubSamsungASHPMinimalModbusInterfacer, self).__init__(name) + + # This line will stop the default values printing to logfile at start-up + # self._settings.update(self._defaults) + + # Interfacer specific settings + self._modbus_settings = { + 'read_interval': 10.0, + 'nodename':'sdm120', + 'prefix':'', + 'meters':[] + } + + self.next_interval = True + + # Only load module if it is installed + try: + import minimalmodbus + self.minimalmodbus = minimalmodbus + # import serial + except ModuleNotFoundError as err: + self._log.error(err) + self._rs485 = False + + self.device = device + self.baud = baud + self.rs485_connect() + + def rs485_connect(self): + try: + self._log.info("Connecting to Modbus device="+str(self.device)+" baud="+str(self.baud)) + + self._rs485 = self.minimalmodbus.Instrument(self.device, 1) + self._rs485.serial.baudrate = self.baud + self._rs485.serial.bytesize = 8 + self._rs485.serial.parity = self.minimalmodbus.serial.PARITY_EVEN + self._rs485.serial.stopbits = 1 + self._rs485.serial.timeout = 1 + self._rs485.debug = False + self._rs485.mode = self.minimalmodbus.MODE_RTU + except Exception: + self._log.error("Could not connect to Modbus device") + self._rs485 = False + + def read(self): + """Read data and process + + Return data as a list: [NodeID, val1, val2] + + """ + + if int(time.time())%self._settings['read_interval']==0: + if self.next_interval: + self.next_interval = False + + c = Cargo.new_cargo() + c.names = [] + c.realdata = [] + c.nodeid = self._settings['nodename'] + + if self._rs485: + invalid_count = 0 + register_count = 0 + + # Support for multiple MBUS meters on a single bus + for meter in self._settings['meters']: + self._rs485.address = self._settings['meters'][meter]['address'] + + for i in range(0,len(self._settings['meters'][meter]['registers'])): + register_count += 1 + valid = True + try: + value = self._rs485.read_register(int(self._settings['meters'][meter]['registers'][i]), functioncode=3) + except Exception as e: + valid = False + invalid_count += 1 + self._log.error("Could not read register @ "+str(self._settings['meters'][meter]['registers'][i])+": " + str(e)) + + if valid: + # replace datafield name with custom name + if i0: + self._log.debug(c.realdata) + return c + else: + self._log.error("Not connected to modbus device") + self.rs485_connect() + + else: + self.next_interval = True + + return False + + + def set(self, **kwargs): + for key, setting in self._modbus_settings.items(): + # Decide which setting value to use + if key in kwargs: + setting = kwargs[key] + else: + setting = self._modbus_settings[key] + + if key in self._settings and self._settings[key] == setting: + continue + elif key == 'read_interval': + self._log.info("Setting %s read_interval: %s", self.name, setting) + self._settings[key] = float(setting) + continue + elif key == 'nodename': + self._log.info("Setting %s nodename: %s", self.name, setting) + self._settings[key] = str(setting) + continue + elif key == 'prefix': + self._log.info("Setting %s prefix: %s", self.name, setting) + self._settings[key] = str(setting) + continue + elif key == 'meters': + self._settings['meters'] = {} + for meter in setting: + # default + address = 1 + registers = [] + names = [] + precision = [] + # address + if 'address' in setting[meter]: + address = int(setting[meter]['address']) + self._log.info("Setting %s meters %s address %s", self.name, meter, address) + + if 'registers' in setting[meter]: + for reg in setting[meter]['registers']: + registers.append(int(reg)) + self._log.info("Setting %s meters %s registers %s", self.name, meter, json.dumps(registers)) + + if 'names' in setting[meter]: + for name in setting[meter]['names']: + names.append(str(name)) + self._log.info("Setting %s meters %s names %s", self.name, meter, json.dumps(names)) + + if 'precision' in setting[meter]: + for dp in setting[meter]['precision']: + precision.append(int(dp)) + self._log.info("Setting %s meters %s precision %s", self.name, meter, json.dumps(precision)) + + #assign + self._settings['meters'][meter] = { + 'address':address, + 'registers':registers, + 'names':names, + 'precision':precision + } + + continue + else: + self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) + + # include kwargs from parent + super().set(**kwargs) diff --git a/src/interfacers/__init__.py b/src/interfacers/__init__.py index 9355d85f..59bf0d5a 100644 --- a/src/interfacers/__init__.py +++ b/src/interfacers/__init__.py @@ -23,6 +23,7 @@ "EmonHubSDM120Interfacer", "EmonHubMBUSInterfacer", "EmonHubMinimalModbusInterfacer", + "EmonHubSamsungASHPMinimalModbusInterfacer", "EmonHubBleInterfacer", "EmonHubGoodWeInterfacer", "EmonHubInfluxInterfacer" From 4b8a4780c3d2a2243354b0cbd4ec7698936e43f5 Mon Sep 17 00:00:00 2001 From: Glyn Hudson Date: Sat, 20 Aug 2022 01:19:42 +0100 Subject: [PATCH 10/14] modifications to EmonHubMinimalModbusInterfacer.py --- .../EmonHubMinimalModbusInterfacer.py | 48 ++-- ...onHubSamsungASHPMinimalModbusInterfacer.py | 214 ------------------ src/interfacers/__init__.py | 1 - 3 files changed, 33 insertions(+), 230 deletions(-) delete mode 100644 src/interfacers/EmonHubSamsungASHPMinimalModbusInterfacer.py diff --git a/src/interfacers/EmonHubMinimalModbusInterfacer.py b/src/interfacers/EmonHubMinimalModbusInterfacer.py index 536b5b6d..adac9871 100644 --- a/src/interfacers/EmonHubMinimalModbusInterfacer.py +++ b/src/interfacers/EmonHubMinimalModbusInterfacer.py @@ -27,6 +27,24 @@ registers = 0,6,12,18,30,70,72,74,76 names = V,I,P,VA,PF,FR,EI,EE,RI precision = 2,3,1,1,3,3,3,3,3 + +[[SAMSUNGASHP]] + Type = EmonHubMinimalModbusInterfacer + [[[init_settings]]] + device = /dev/ttyUSB0 + baud = 9600 + [[[runtimesettings]]] + pubchannels = ToEmonCMS, + read_interval = 10 + nodename = samsung-mib19n + # prefix = sdm_ + [[[[meters]]]] + [[[[[ashp]]]]] + address = 1 + registers = 75,74,72,65,66,68,52,59,58,2,79 + names = dhw_temp,dhw_target,dhw_status,return_temp,flow_temp,flow_target,heating_status,indoor_temp,indoor_target, defrost_status, away_status + scales = 0.1,0.1,1,0.1,0.1,0.1,1,0.1,0,1,1,1 + """ """class EmonHubSDM120Interfacer @@ -57,8 +75,8 @@ def __init__(self, name, device="/dev/modbus", baud=2400): self.next_interval = True - # Only load module if it is installed - try: + # Only load module if it is installed + try: import minimalmodbus self.minimalmodbus = minimalmodbus # import serial @@ -71,13 +89,13 @@ def __init__(self, name, device="/dev/modbus", baud=2400): self.rs485_connect() def rs485_connect(self): - try: + try: self._log.info("Connecting to Modbus device="+str(self.device)+" baud="+str(self.baud)) self._rs485 = self.minimalmodbus.Instrument(self.device, 1) self._rs485.serial.baudrate = self.baud self._rs485.serial.bytesize = 8 - self._rs485.serial.parity = self.minimalmodbus.serial.PARITY_NONE + self._rs485.serial.parity = self.minimalmodbus.serial.PARITY_EVEN self._rs485.serial.stopbits = 1 self._rs485.serial.timeout = 1 self._rs485.debug = False @@ -94,7 +112,7 @@ def read(self): """ if int(time.time())%self._settings['read_interval']==0: - if self.next_interval: + if self.next_interval: self.next_interval = False c = Cargo.new_cargo() @@ -114,11 +132,11 @@ def read(self): register_count += 1 valid = True try: - value = self._rs485.read_float(int(self._settings['meters'][meter]['registers'][i]), functioncode=4, number_of_registers=2) + value = self._rs485.read_register(int(self._settings['meters'][meter]['registers'][i]), functioncode=4) except Exception as e: valid = False invalid_count += 1 - self._log.error("Could not read register @ "+str(self._settings['meters'][meter]['registers'][i])+": " + str(e)) + self._log.error("Could not read register @ "+str(self._settings['meters'][meter]['registers'][i])+": " + str(e)) if valid: # replace datafield name with custom name @@ -134,7 +152,7 @@ def read(self): c.realdata.append(value) # self._log.debug(str(name)+": "+str(value)) - time.sleep(0.1) + time.sleep(0.1) if invalid_count==register_count: self._log.error("Could not read all registers") @@ -181,27 +199,27 @@ def set(self, **kwargs): # default address = 1 registers = [] - names = [] + names = [] precision = [] # address if 'address' in setting[meter]: address = int(setting[meter]['address']) - self._log.info("Setting %s meters %s address %s", self.name, meter, address) + self._log.info("Setting %s meters %s address %s", self.name, meter, address) if 'registers' in setting[meter]: for reg in setting[meter]['registers']: registers.append(int(reg)) - self._log.info("Setting %s meters %s registers %s", self.name, meter, json.dumps(registers)) + self._log.info("Setting %s meters %s registers %s", self.name, meter, json.dumps(registers)) if 'names' in setting[meter]: for name in setting[meter]['names']: - names.append(str(name)) - self._log.info("Setting %s meters %s names %s", self.name, meter, json.dumps(names)) + names.append(str(name)) + self._log.info("Setting %s meters %s names %s", self.name, meter, json.dumps(names)) if 'precision' in setting[meter]: for dp in setting[meter]['precision']: precision.append(int(dp)) - self._log.info("Setting %s meters %s precision %s", self.name, meter, json.dumps(precision)) + self._log.info("Setting %s meters %s precision %s", self.name, meter, json.dumps(precision)) #assign self._settings['meters'][meter] = { @@ -211,7 +229,7 @@ def set(self, **kwargs): 'precision':precision } - continue + continue else: self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) diff --git a/src/interfacers/EmonHubSamsungASHPMinimalModbusInterfacer.py b/src/interfacers/EmonHubSamsungASHPMinimalModbusInterfacer.py deleted file mode 100644 index e1c421e2..00000000 --- a/src/interfacers/EmonHubSamsungASHPMinimalModbusInterfacer.py +++ /dev/null @@ -1,214 +0,0 @@ -import time -import json -import Cargo -import os -import glob -from emonhub_interfacer import EmonHubInterfacer - -""" -[[MIMB19N]] - Type = EmonHubSamsungASHPMinimalModbusInterfacer - [[[init_settings]]] - device = /dev/ttyUSB0 - baud = 9600 - [[[runtimesettings]]] - pubchannels = ToEmonCMS, - read_interval = 10 - nodename = samsung-mib19n - # prefix = sdm_ - [[[[meters]]]] - [[[[[ashp]]]]] - address = 1 - registers = 75,74,72,65,66,68,52,59,58,2,79 - names = dhw_temp,dhw_target,dhw_status,return_temp,flow_temp,flow_target,heating_status,indoor_temp,indoor_target, defrost_status, away_status - scales = 0.1,0.1,1,0.1,0.1,0.1,1,0.1,0,1,1,1 -""" - -"""class EmonHubSDM120Interfacer - -SDM120 interfacer for use in development - -""" - -class EmonHubSamsungASHPMinimalModbusInterfacer(EmonHubInterfacer): - - def __init__(self, name, device="/dev/modbus", baud=9600): - """Initialize Interfacer - - """ - # Initialization - super(EmonHubSamsungASHPMinimalModbusInterfacer, self).__init__(name) - - # This line will stop the default values printing to logfile at start-up - # self._settings.update(self._defaults) - - # Interfacer specific settings - self._modbus_settings = { - 'read_interval': 10.0, - 'nodename':'sdm120', - 'prefix':'', - 'meters':[] - } - - self.next_interval = True - - # Only load module if it is installed - try: - import minimalmodbus - self.minimalmodbus = minimalmodbus - # import serial - except ModuleNotFoundError as err: - self._log.error(err) - self._rs485 = False - - self.device = device - self.baud = baud - self.rs485_connect() - - def rs485_connect(self): - try: - self._log.info("Connecting to Modbus device="+str(self.device)+" baud="+str(self.baud)) - - self._rs485 = self.minimalmodbus.Instrument(self.device, 1) - self._rs485.serial.baudrate = self.baud - self._rs485.serial.bytesize = 8 - self._rs485.serial.parity = self.minimalmodbus.serial.PARITY_EVEN - self._rs485.serial.stopbits = 1 - self._rs485.serial.timeout = 1 - self._rs485.debug = False - self._rs485.mode = self.minimalmodbus.MODE_RTU - except Exception: - self._log.error("Could not connect to Modbus device") - self._rs485 = False - - def read(self): - """Read data and process - - Return data as a list: [NodeID, val1, val2] - - """ - - if int(time.time())%self._settings['read_interval']==0: - if self.next_interval: - self.next_interval = False - - c = Cargo.new_cargo() - c.names = [] - c.realdata = [] - c.nodeid = self._settings['nodename'] - - if self._rs485: - invalid_count = 0 - register_count = 0 - - # Support for multiple MBUS meters on a single bus - for meter in self._settings['meters']: - self._rs485.address = self._settings['meters'][meter]['address'] - - for i in range(0,len(self._settings['meters'][meter]['registers'])): - register_count += 1 - valid = True - try: - value = self._rs485.read_register(int(self._settings['meters'][meter]['registers'][i]), functioncode=3) - except Exception as e: - valid = False - invalid_count += 1 - self._log.error("Could not read register @ "+str(self._settings['meters'][meter]['registers'][i])+": " + str(e)) - - if valid: - # replace datafield name with custom name - if i0: - self._log.debug(c.realdata) - return c - else: - self._log.error("Not connected to modbus device") - self.rs485_connect() - - else: - self.next_interval = True - - return False - - - def set(self, **kwargs): - for key, setting in self._modbus_settings.items(): - # Decide which setting value to use - if key in kwargs: - setting = kwargs[key] - else: - setting = self._modbus_settings[key] - - if key in self._settings and self._settings[key] == setting: - continue - elif key == 'read_interval': - self._log.info("Setting %s read_interval: %s", self.name, setting) - self._settings[key] = float(setting) - continue - elif key == 'nodename': - self._log.info("Setting %s nodename: %s", self.name, setting) - self._settings[key] = str(setting) - continue - elif key == 'prefix': - self._log.info("Setting %s prefix: %s", self.name, setting) - self._settings[key] = str(setting) - continue - elif key == 'meters': - self._settings['meters'] = {} - for meter in setting: - # default - address = 1 - registers = [] - names = [] - precision = [] - # address - if 'address' in setting[meter]: - address = int(setting[meter]['address']) - self._log.info("Setting %s meters %s address %s", self.name, meter, address) - - if 'registers' in setting[meter]: - for reg in setting[meter]['registers']: - registers.append(int(reg)) - self._log.info("Setting %s meters %s registers %s", self.name, meter, json.dumps(registers)) - - if 'names' in setting[meter]: - for name in setting[meter]['names']: - names.append(str(name)) - self._log.info("Setting %s meters %s names %s", self.name, meter, json.dumps(names)) - - if 'precision' in setting[meter]: - for dp in setting[meter]['precision']: - precision.append(int(dp)) - self._log.info("Setting %s meters %s precision %s", self.name, meter, json.dumps(precision)) - - #assign - self._settings['meters'][meter] = { - 'address':address, - 'registers':registers, - 'names':names, - 'precision':precision - } - - continue - else: - self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key) - - # include kwargs from parent - super().set(**kwargs) diff --git a/src/interfacers/__init__.py b/src/interfacers/__init__.py index 59bf0d5a..9355d85f 100644 --- a/src/interfacers/__init__.py +++ b/src/interfacers/__init__.py @@ -23,7 +23,6 @@ "EmonHubSDM120Interfacer", "EmonHubMBUSInterfacer", "EmonHubMinimalModbusInterfacer", - "EmonHubSamsungASHPMinimalModbusInterfacer", "EmonHubBleInterfacer", "EmonHubGoodWeInterfacer", "EmonHubInfluxInterfacer" From 5702a4959d9db4845dc761f6cf4ae45804ca0f75 Mon Sep 17 00:00:00 2001 From: Glyn Hudson Date: Mon, 22 Aug 2022 03:06:34 +0100 Subject: [PATCH 11/14] add support for Samsung ASHP to modbus minimal multiple --- conf/interfacer_examples/SDM120/readme.md | 17 ++++++- .../SDM120/sdm120.emonhub.conf | 18 ++++++- .../samsung-ashp/readme.md | 26 ++++++++++ .../samsung-ashp/samsung-ashp.emonhub.conf | 18 +++++++ .../EmonHubMinimalModbusInterfacer.py | 49 +++++++++++++++---- 5 files changed, 115 insertions(+), 13 deletions(-) create mode 100644 conf/interfacer_examples/samsung-ashp/readme.md create mode 100644 conf/interfacer_examples/samsung-ashp/samsung-ashp.emonhub.conf diff --git a/conf/interfacer_examples/SDM120/readme.md b/conf/interfacer_examples/SDM120/readme.md index d5a16873..397086a3 100644 --- a/conf/interfacer_examples/SDM120/readme.md +++ b/conf/interfacer_examples/SDM120/readme.md @@ -6,13 +6,26 @@ The SDM120-Modbus single phase electricity meter provides MID certified electric ```text [[SDM120]] - Type = EmonHubSDM120Interfacer + Type = EmonHubMinimalModbusInterfacer [[[init_settings]]] device = /dev/ttyUSB0 baud = 2400 + parity = none + datatype = float [[[runtimesettings]]] pubchannels = ToEmonCMS, read_interval = 10 - nodename = SDM120 + nodename = sdm120 # prefix = sdm_ + [[[[meters]]]] + [[[[[sdm120a]]]]] + address = 1 + registers = 0,6,12,18,30,70,72,74,76 + names = V,I,P,VA,PF,FR,EI,EE,RI + precision = 2,3,1,1,3,3,3,3,3 + [[[[[sdm120b]]]]] + address = 2 + registers = 0,6,12,18,30,70,72,74,76 + names = V,I,P,VA,PF,FR,EI,EE,RI + precision = 2,3,1,1,3,3,3,3,3 ``` diff --git a/conf/interfacer_examples/SDM120/sdm120.emonhub.conf b/conf/interfacer_examples/SDM120/sdm120.emonhub.conf index c46424db..1f417083 100644 --- a/conf/interfacer_examples/SDM120/sdm120.emonhub.conf +++ b/conf/interfacer_examples/SDM120/sdm120.emonhub.conf @@ -1,9 +1,23 @@ [[SDM120]] - Type = EmonHubSDM120Interfacer + Type = EmonHubMinimalModbusInterfacer [[[init_settings]]] device = /dev/ttyUSB0 baud = 2400 + parity = none + datatype = float [[[runtimesettings]]] pubchannels = ToEmonCMS, read_interval = 10 - nodename = SDM120 + nodename = sdm120 + # prefix = sdm_ + [[[[meters]]]] + [[[[[sdm120a]]]]] + address = 1 + registers = 0,6,12,18,30,70,72,74,76 + names = V,I,P,VA,PF,FR,EI,EE,RI + precision = 2,3,1,1,3,3,3,3,3 + [[[[[sdm120b]]]]] + address = 2 + registers = 0,6,12,18,30,70,72,74,76 + names = V,I,P,VA,PF,FR,EI,EE,RI + precision = 2,3,1,1,3,3,3,3,3 diff --git a/conf/interfacer_examples/samsung-ashp/readme.md b/conf/interfacer_examples/samsung-ashp/readme.md new file mode 100644 index 00000000..692f00e4 --- /dev/null +++ b/conf/interfacer_examples/samsung-ashp/readme.md @@ -0,0 +1,26 @@ +### SAMSUNG ASHP MIB19N Modbus + +Read data from a Samsung Heat Pump or HVAC unit using the [MIM-B19N Modbus module](https://www.samsung.com/uk/support/model/MIM-B19N/). Tested on AE050RXYDEG-EU Gen6 ASHP. Should work for all Samsung HVAC units. + +**read_interval:** Interval between readings in seconds + +```text +[[SAMSUNG-ASHP-MIB19N]] + Type = EmonHubMinimalModbusInterfacer + [[[init_settings]]] + device = /dev/ttyUSB0 + baud = 9600 + parity = even + datatype = int + [[[runtimesettings]]] + pubchannels = ToEmonCMS, + read_interval = 10 + nodename = samsung-ashp + # prefix = sdm_ + [[[[meters]]]] + [[[[[ashp]]]]] + address = 1 + registers = 75,74,72,65,66,68,52,59,58,2,79 + names = dhw_temp,dhw_target,dhw_status,return_temp,flow_temp,flow_target,heating_status,indoor_temp,indoor_target, defrost_status, away_status + scales = 0.1,0.1,1,0.1,0.1,0.1,1,0.1,0.1,1,1 +``` diff --git a/conf/interfacer_examples/samsung-ashp/samsung-ashp.emonhub.conf b/conf/interfacer_examples/samsung-ashp/samsung-ashp.emonhub.conf new file mode 100644 index 00000000..cfa9899a --- /dev/null +++ b/conf/interfacer_examples/samsung-ashp/samsung-ashp.emonhub.conf @@ -0,0 +1,18 @@ +[[SAMSUNG-ASHP-MIB19N]] + Type = EmonHubMinimalModbusInterfacer + [[[init_settings]]] + device = /dev/ttyUSB0 + baud = 9600 + parity = even + datatype = int + [[[runtimesettings]]] + pubchannels = ToEmonCMS, + read_interval = 10 + nodename = samsung-ashp + # prefix = sdm_ + [[[[meters]]]] + [[[[[ashp]]]]] + address = 1 + registers = 75,74,72,65,66,68,52,59,58,2,79 + names = dhw_temp,dhw_target,dhw_status,return_temp,flow_temp,flow_target,heating_status,indoor_temp,indoor_target, defrost_status, away_status + scales = 0.1,0.1,1,0.1,0.1,0.1,1,0.1,0.1,1,1 diff --git a/src/interfacers/EmonHubMinimalModbusInterfacer.py b/src/interfacers/EmonHubMinimalModbusInterfacer.py index adac9871..6eee685e 100644 --- a/src/interfacers/EmonHubMinimalModbusInterfacer.py +++ b/src/interfacers/EmonHubMinimalModbusInterfacer.py @@ -11,6 +11,8 @@ [[[init_settings]]] device = /dev/ttyUSB0 baud = 2400 + parity = none + datatype = float [[[runtimesettings]]] pubchannels = ToEmonCMS, read_interval = 10 @@ -28,22 +30,24 @@ names = V,I,P,VA,PF,FR,EI,EE,RI precision = 2,3,1,1,3,3,3,3,3 -[[SAMSUNGASHP]] +[[SAMSUNG-ASHP-MIB19N]] Type = EmonHubMinimalModbusInterfacer [[[init_settings]]] device = /dev/ttyUSB0 baud = 9600 + parity = even + datatype = int [[[runtimesettings]]] pubchannels = ToEmonCMS, read_interval = 10 - nodename = samsung-mib19n + nodename = samsung-ashp # prefix = sdm_ [[[[meters]]]] [[[[[ashp]]]]] address = 1 registers = 75,74,72,65,66,68,52,59,58,2,79 names = dhw_temp,dhw_target,dhw_status,return_temp,flow_temp,flow_target,heating_status,indoor_temp,indoor_target, defrost_status, away_status - scales = 0.1,0.1,1,0.1,0.1,0.1,1,0.1,0,1,1,1 + scales = 0.1,0.1,1,0.1,0.1,0.1,1,0.1,0.1,1,1 """ @@ -55,7 +59,7 @@ class EmonHubMinimalModbusInterfacer(EmonHubInterfacer): - def __init__(self, name, device="/dev/modbus", baud=2400): + def __init__(self, name, device="/dev/modbus", baud=2400, parity="none", datatype="float"): """Initialize Interfacer """ @@ -86,16 +90,27 @@ def __init__(self, name, device="/dev/modbus", baud=2400): self.device = device self.baud = baud + self.parity = parity + self.datatype = datatype self.rs485_connect() def rs485_connect(self): try: - self._log.info("Connecting to Modbus device="+str(self.device)+" baud="+str(self.baud)) + self._log.info("Connecting to Modbus device="+str(self.device)+" baud="+str(self.baud)+" parity="+str(self.parity)+" datatype="+str(self.datatype)) self._rs485 = self.minimalmodbus.Instrument(self.device, 1) self._rs485.serial.baudrate = self.baud self._rs485.serial.bytesize = 8 - self._rs485.serial.parity = self.minimalmodbus.serial.PARITY_EVEN + if self.parity == 'even': + self._rs485.serial.parity = self.minimalmodbus.serial.PARITY_EVEN + elif self.parity == 'odd': + self._rs485.serial.parity = self.minimalmodbus.serial.PARITY_ODD + elif self.parity == 'mark': + self._rs485.serial.parity = self.minimalmodbus.serial.PARITY_MARK + elif self.parity == 'space': + self._rs485.serial.parity = self.minimalmodbus.serial.PARITY_SPACE + else: + self._rs485.serial.parity = self.minimalmodbus.serial.PARITY_NONE self._rs485.serial.stopbits = 1 self._rs485.serial.timeout = 1 self._rs485.debug = False @@ -132,7 +147,14 @@ def read(self): register_count += 1 valid = True try: - value = self._rs485.read_register(int(self._settings['meters'][meter]['registers'][i]), functioncode=4) + if self.datatype == 'int': + value = self._rs485.read_register(int(self._settings['meters'][meter]['registers'][i]), functioncode=4) + elif self.datatype == 'float': + value = self._rs485.read_float(int(self._settings['meters'][meter]['registers'][i]), functioncode=4, number_of_registers=2) + else: + value = self._rs485.read_float(int(self._settings['meters'][meter]['registers'][i]), functioncode=4, number_of_registers=2) + + except Exception as e: valid = False invalid_count += 1 @@ -147,7 +169,9 @@ def read(self): # apply rounding if set if i Date: Wed, 24 Aug 2022 02:29:32 +0100 Subject: [PATCH 12/14] set precision --- conf/interfacer_examples/samsung-ashp/samsung-ashp.emonhub.conf | 1 + src/interfacers/EmonHubMinimalModbusInterfacer.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/conf/interfacer_examples/samsung-ashp/samsung-ashp.emonhub.conf b/conf/interfacer_examples/samsung-ashp/samsung-ashp.emonhub.conf index cfa9899a..bd9bbf8e 100644 --- a/conf/interfacer_examples/samsung-ashp/samsung-ashp.emonhub.conf +++ b/conf/interfacer_examples/samsung-ashp/samsung-ashp.emonhub.conf @@ -16,3 +16,4 @@ registers = 75,74,72,65,66,68,52,59,58,2,79 names = dhw_temp,dhw_target,dhw_status,return_temp,flow_temp,flow_target,heating_status,indoor_temp,indoor_target, defrost_status, away_status scales = 0.1,0.1,1,0.1,0.1,0.1,1,0.1,0.1,1,1 + precision = 2,2,1,2,2,2,1,2,2,1,1 diff --git a/src/interfacers/EmonHubMinimalModbusInterfacer.py b/src/interfacers/EmonHubMinimalModbusInterfacer.py index 6eee685e..5ad1ef7a 100644 --- a/src/interfacers/EmonHubMinimalModbusInterfacer.py +++ b/src/interfacers/EmonHubMinimalModbusInterfacer.py @@ -148,7 +148,7 @@ def read(self): valid = True try: if self.datatype == 'int': - value = self._rs485.read_register(int(self._settings['meters'][meter]['registers'][i]), functioncode=4) + value = self._rs485.read_register(int(self._settings['meters'][meter]['registers'][i]), functioncode=3) elif self.datatype == 'float': value = self._rs485.read_float(int(self._settings['meters'][meter]['registers'][i]), functioncode=4, number_of_registers=2) else: From 34590ddfc8bc177ee3e2ca202d4bdb64b0807c12 Mon Sep 17 00:00:00 2001 From: Glyn Hudson Date: Wed, 24 Aug 2022 23:13:31 +0100 Subject: [PATCH 13/14] Update readme.md --- conf/interfacer_examples/SDM120/readme.md | 26 ++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/conf/interfacer_examples/SDM120/readme.md b/conf/interfacer_examples/SDM120/readme.md index 397086a3..f1e6adfc 100644 --- a/conf/interfacer_examples/SDM120/readme.md +++ b/conf/interfacer_examples/SDM120/readme.md @@ -4,14 +4,34 @@ The SDM120-Modbus single phase electricity meter provides MID certified electric **read_interval:** Interval between readings in seconds -```text +## Single SDM120 Meter +``` +[[SDM120]] + Type = EmonHubMinimalModbusInterfacer + [[[init_settings]]] + device = /dev/ttyUSB0 + baud = 2400 + [[[runtimesettings]]] + pubchannels = ToEmonCMS, + read_interval = 10 + nodename = sdm120 + # prefix = sdm_ + [[[[meters]]]] + [[[[[sdm120]]]]] + address = 1 + registers = 0,6,12,18,30,70,72,74,76 + names = V,I,P,VA,PF,FR,EI,EE,RI + precision = 2,3,1,1,3,3,3,3,3 +``` + +## Multiple SDM120 Meters + +``` [[SDM120]] Type = EmonHubMinimalModbusInterfacer [[[init_settings]]] device = /dev/ttyUSB0 baud = 2400 - parity = none - datatype = float [[[runtimesettings]]] pubchannels = ToEmonCMS, read_interval = 10 From 1f7775770b65df08a68e29370fa451d41009a2c2 Mon Sep 17 00:00:00 2001 From: Glyn Hudson Date: Wed, 24 Aug 2022 23:14:56 +0100 Subject: [PATCH 14/14] Update readme.md --- conf/interfacer_examples/samsung-ashp/readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/conf/interfacer_examples/samsung-ashp/readme.md b/conf/interfacer_examples/samsung-ashp/readme.md index 692f00e4..3262cb90 100644 --- a/conf/interfacer_examples/samsung-ashp/readme.md +++ b/conf/interfacer_examples/samsung-ashp/readme.md @@ -23,4 +23,5 @@ Read data from a Samsung Heat Pump or HVAC unit using the [MIM-B19N Modbus modul registers = 75,74,72,65,66,68,52,59,58,2,79 names = dhw_temp,dhw_target,dhw_status,return_temp,flow_temp,flow_target,heating_status,indoor_temp,indoor_target, defrost_status, away_status scales = 0.1,0.1,1,0.1,0.1,0.1,1,0.1,0.1,1,1 + precision = 2,2,1,2,2,2,1,2,2,1,1 ```