Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minimalmodbus add rid175 #179

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions conf/interfacer_examples/MinimalModbus/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

86 changes: 86 additions & 0 deletions conf/interfacer_examples/MinimalModbus/minimalmodbus.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
[[SDM120]]
Type = EmonHubMinimalModbusInterfacer
[[[init_settings]]]
device = /dev/ttyUSB0
baud = 2400
parity = none
[[[runtimesettings]]]
pubchannels = ToEmonCMS,
read_interval = 10
nodename = sdm120
# prefix = sdm_
[[[[meters]]]]
[[[[[sdm120a]]]]]
address = 1
type = sdm120
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
type = sdm120
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


[[SDM630]]
Type = EmonHubMinimalModbusInterfacer
[[[init_settings]]]
device = /dev/ttyUSB1
baud = 9600
[[[runtimesettings]]]
pubchannels = ToEmonCMS,
read_interval = 10
nodename = sdm630
# prefix = sdm_
registers = 0,1,2,3,4,5,6,7,8,26,36,37
names = V1,V2,V3,I1,I2,I3,P1,P2,P3,TotalPower,Import_kWh,Export_kWh
precision = 2,2,2,2,2,2,2,2,2,2,2,2

[[modbusRTU]]
Type = EmonHubMinimalModbusInterfacer
[[[init_settings]]]
device = /dev/ttyUSB0
baud = 9600
parity = none
[[[runtimesettings]]]
pubchannels = ToEmonCMS,
read_interval = 10
nodename = rid175
# prefix = rid_
[[[[meters]]]]
[[[[[rid175]]]]]
address = 1
type = rid175
registers = 0,6,8,10,14,16,18
names = TotalkWh,V,A,Power,KVA,PF,FR
scales = 0.01,0.1,0.1,0.01,0.01,0.001,0.01
[[[[[sdm120]]]]]
address = 2
type = sdm120
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

[[SAMSUNG-ASHP-MIB19N]]
Type = EmonHubMinimalModbusInterfacer
[[[init_settings]]]
device = /dev/ttyUSB0
baud = 9600
parity = even
[[[runtimesettings]]]
pubchannels = ToEmonCMS,
read_interval = 10
nodename = samsung-ashp
# prefix = sdm_
[[[[meters]]]]
[[[[[ashp]]]]]
address = 1
type = mib19n
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


127 changes: 127 additions & 0 deletions conf/interfacer_examples/MinimalModbus/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
## MinimalModbus

The minimalmodbus interfacer supports a number of Modbus-RTU serial connected electricity meters and devices for monitoring the electricity supply of heat pumps,
EV Chargers Solar etc.
One or more USB to RS485 converter is needed to read from the modbus output of the meter such as: https://www.amazon.co.uk/gp/product/B07SD65BVF.

Multiple meters of different types can be mixed on the same serial interface provided the serial parameters of all the meters is the same (Baud rate, parity stop bits)

### Supported Meters include
**SDM120**
A single phase bi-directional electricity meter provides MID certified electricity monitoring up to 45A, ideal for monitoring the electricity supply of heat pumps
EV chargers, Solar etc.
The SDM120 meter comes in a number of different variants, be sure to order the version with a modbus output.

**SDM630**
A single/ three phase bi-directional electricity meter provides MID certified electricity monitoring up to 100A, ideal for monitoring the electricity supply of heat pumps
EV chargers, Solar etc.
The SDM630 meter comes in a number of different variants, be sure to order the version with a modbus output.

**RI-D175**
A low cost single phase unidirectional (Import only) electricity meter provides MID certified electricity monitoring up to 45A, ideal for monitoring the electricity supply of heat pumps
EV chargers, Solar etc.
The RI-D175 meter comes in a number of different variants, be sure to order the version with a modbus output.
https://www.rayleigh.com/media/uploads/RI_Data_Sheet_RI-D175_MID_01_12_20.pdf

**SAMSUNG-ASHP-MIB19N**
Direct connection to a Samsung ASHP MIB19N

**read_interval:** Interval between readings in seconds

**type:** Meter Type

## SDM120 only Meters
```
[[SDM120]]
Type = EmonHubMinimalModbusInterfacer
[[[init_settings]]]
device = /dev/ttyUSB0
baud = 2400
parity = none
[[[runtimesettings]]]
pubchannels = ToEmonCMS,
read_interval = 10
nodename = sdm120
# prefix = sdm_
[[[[meters]]]]
[[[[[sdm120a]]]]]
address = 1
type = sdm120
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
type = sdm120
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

```
## Single SDM630 Meter

```
[[SDM630]]
Type = EmonHubMinimalModbusInterfacer
[[[init_settings]]]
device = /dev/ttyUSB1
baud = 9600
[[[runtimesettings]]]
pubchannels = ToEmonCMS,
read_interval = 10
nodename = sdm630
# prefix = sdm_
registers = 0,1,2,3,4,5,6,7,8,26,36,37
names = V1,V2,V3,I1,I2,I3,P1,P2,P3,TotalPower,Import_kWh,Export_kWh
precision = 2,2,2,2,2,2,2,2,2,2,2,2
```
## RI-D175 and SDM120 on same interface
```
[[modbusRTU]]
Type = EmonHubMinimalModbusInterfacer
[[[init_settings]]]
device = /dev/ttyUSB0
baud = 9600
parity = none
[[[runtimesettings]]]
pubchannels = ToEmonCMS,
read_interval = 10
nodename = rid175
# prefix = rid_
[[[[meters]]]]
[[[[[rid175]]]]]
address = 1
type = rid175
registers = 0,6,8,10,14,16,18
names = TotalkWh,V,A,Power,KVA,PF,FR
scales = 0.01,0.1,0.1,0.01,0.01,0.001,0.01
[[[[[sdm120]]]]]
address = 2
type = sdm120
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
```
## Samsung ASHP
```
[[SAMSUNG-ASHP-MIB19N]]
Type = EmonHubMinimalModbusInterfacer
[[[init_settings]]]
device = /dev/ttyUSB0
baud = 9600
parity = even
[[[runtimesettings]]]
pubchannels = ToEmonCMS,
read_interval = 10
nodename = samsung-ashp
# prefix = sdm_
[[[[meters]]]]
[[[[[ashp]]]]]
address = 1
type = mib19n
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

'''
68 changes: 63 additions & 5 deletions src/interfacers/EmonHubMinimalModbusInterfacer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
device = /dev/ttyUSB0
baud = 2400
parity = none
datatype = float
[[[runtimesettings]]]
pubchannels = ToEmonCMS,
read_interval = 10
Expand All @@ -21,11 +20,13 @@
[[[[meters]]]]
[[[[[sdm120a]]]]]
address = 1
type = sdm120
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
type = sdm120
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
Expand All @@ -36,7 +37,6 @@
device = /dev/ttyUSB0
baud = 9600
parity = even
datatype = int
[[[runtimesettings]]]
pubchannels = ToEmonCMS,
read_interval = 10
Expand All @@ -45,6 +45,7 @@
[[[[meters]]]]
[[[[[ashp]]]]]
address = 1
type = mib19n
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
Expand All @@ -63,12 +64,39 @@
registers = 0,1,2,3,4,5,6,7,8,26,36,37
names = V1,V2,V3,I1,I2,I3,P1,P2,P3,TotalPower,Import_kWh,Export_kWh
precision = 2,2,2,2,2,2,2,2,2,2,2,2

# different meter types can share the same serial port provided the serial settings are the same
[[modbusRTU]]
Type = EmonHubMinimalModbusInterfacer
[[[init_settings]]]
device = /dev/ttyUSB0
baud = 9600
parity = none
[[[runtimesettings]]]
pubchannels = ToEmonCMS,
read_interval = 10
nodename = rid175
# prefix = rid_
[[[[meters]]]]
[[[[[rid175]]]]]
address = 1
type = rid175
registers = 0,6,8,10,14,16,18
names = TotalkWh,V,A,Power,KVA,PF,FR
scales = 0.01,0.1,0.1,0.01,0.01,0.001,0.01
[[[[[sdm120]]]]]
address = 2
type = sdm120
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

SDM120 interfacer for use in development
MinimalModbus interfacer for use in development

"""

Expand Down Expand Up @@ -134,6 +162,23 @@ def rs485_connect(self):
self._log.error("Could not connect to Modbus device")
self._rs485 = False

def bcd_decode(self, bcd_value):
"""
function to decode bcd coded data in long
e.g. Rayleigh Instruments RI-D175m
"""
result=0

for n in range(7,-1,-1):
divisor = 2**(n*4)
if (divisor <= bcd_value):
# result is decimal digit
result = result + bcd_value//divisor * 10**n
# set value to remainder
bcd_value = bcd_value%divisor

return result

def read(self):
"""Read data and process

Expand All @@ -157,12 +202,21 @@ def read(self):
# Support for multiple MBUS meters on a single bus
for meter in self._settings['meters']:
self._rs485.address = self._settings['meters'][meter]['address']
meter_type = self._settings['meters'][meter]['type']

for i in range(0,len(self._settings['meters'][meter]['registers'])):
register_count += 1
valid = True
try:
if self.datatype == 'int':
if meter_type == "sdm120":
value = self._rs485.read_float(int(self._settings['meters'][meter]['registers'][i]), functioncode=4, number_of_registers=2)
elif meter_type == "sdm630":
value = self._rs485.read_float(int(self._settings['meters'][meter]['registers'][i]), functioncode=4, number_of_registers=2)
elif meter_type == "mib19n":
value = self._rs485.read_register(int(self._settings['meters'][meter]['registers'][i]), functioncode=3)
elif meter_type == 'rid175':
value = self.bcd_decode(self._rs485.read_long(int(self._settings['meters'][meter]['registers'][i]), functioncode=4, signed = False, byteorder = 0))
elif self.datatype == 'int':
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)
Expand Down Expand Up @@ -209,7 +263,6 @@ def read(self):

return False


def set(self, **kwargs):
for key, setting in self._modbus_settings.items():
# Decide which setting value to use
Expand Down Expand Up @@ -237,6 +290,7 @@ def set(self, **kwargs):
for meter in setting:
# default
address = 1
meter_type = ""
registers = []
names = []
precision = []
Expand All @@ -245,6 +299,9 @@ def set(self, **kwargs):
if 'address' in setting[meter]:
address = int(setting[meter]['address'])
self._log.info("Setting %s meters %s address %s", self.name, meter, address)

if 'type' in setting[meter]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this backward compatible?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Brian,
yes I think so, if no type is defined then it uses the previous method of working out which meter to use.
I have just tested on my install which uses the new version and put the following config in and it picks up the SDM120 and reads the values
[[SDM120]]
Type = EmonHubMinimalModbusInterfacer
[[[init_settings]]]
device = /dev/ttyUSB0
baud = 9600
[[[runtimesettings]]]
pubchannels = ToEmonCMS,
read_interval = 10
nodename = sdm120
# prefix = sdm_
[[[[meters]]]]
[[[[[sdm120]]]]]
address = 3
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

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heres the elif statements after the type lookups
'''
elif self.datatype == 'int':
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)
'''

meter_type = str(setting[meter]['type'])

if 'registers' in setting[meter]:
for reg in setting[meter]['registers']:
Expand All @@ -269,6 +326,7 @@ def set(self, **kwargs):
#assign
self._settings['meters'][meter] = {
'address':address,
'type':meter_type,
'registers':registers,
'names':names,
'precision':precision,
Expand Down