Emonpi Interfacers SDM120 to SDM220

I have got around to connecting my modbus sdm220 meter to my emonpi, looking online help I was able to paste into emonhub the interface for the sdm120 even though I have a sdm220 meter, all is working well in getting the inputs and feeds up and running, this sdm220 meter has got as far as I know you can output more registers then the 6 that are displayed in the inputs of the emonpi,

How do I go about getting the output registers that are missing, looking at a bit of coding I have found from re3lex/SDM220-Modbus-RTU-ESP8266 these outputs but they are hexadecimal registers

{0x00, “Voltage”, “V”},
{0x06, “Current”, “A”},
{0x0C, “Active Power”, “W”},
{0x12, “Apparent Power”, “VA”},
{0x18, “Reactive Power”, “VAr”},
{0x1e, “Power Factor”, “”},
{0x46, “Frequency”, “Hz”},
{0x48, “Import active energy”, “kwh”},
{0x4a, “Export active energy”, “kwh”},
{0x4c, “Import reactive energy”, “kvarh”},
{0x4e, “Export reactive energy”, “kvarh”},
{0x56, “Total active energy”, “kwh”},
{0x58, “Total reactive energy”, “kvarh”}

I have came across this EASTRON (SDM220) DATA LOGGER. MODBUS REGISTERS MAP. How would I integrate this into my emonpi

MODBUS offset Description Units Data type
6 Current Amps single
12 Active power Watts single
18 Apparent power VoltAmps single
24 Reactive power VAr single
30 Power factor None single
36 Phase angle Degree single
70 Frequency Hz single
72 Import active energy kwh single
74 Export active energy kwh single
76 Import reactive energy kvarh single
78 Export reactive energy kvarh single
342 Total active energy kwh single
344 Total reactive energy kvarh single

Just came across the script that has the EmonHubSDM120Interfacer

Just wondering how I could modify this to suit my SDM220 and some hoe unload it?

Looking at the registers of the SDM120 (EmonHubSDM120Interfacer) in the script, the SDM220 uploads the V,I,P,PF,FR, to my emonpi, there MODBUS offset need to be change to 6,12,18,24,30,36,70,72,74,76,78,342,344 from 0,6,12,18,30,70,72,74,76

Hello @Ross

It might be worth trying the EmonHub Minimal Modbus interfacer. Here are a couple of example configurations with registers specified. I need to update the documentation to use this interfacer as standard and there is another version of the code that supports multiple meters as well that needs to be merged in to the master branch.

EmonHub master branch, single meter support:


[[SDM120]]
    Type = EmonHubMinimalModbusInterfacer
    [[[init_settings]]]
        device = /dev/ttyUSB0
        baud = 2400
    [[[runtimesettings]]]
        pubchannels = ToEmonCMS,
        read_interval = 10
        nodename = sdm120
        # prefix = sdm_
        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

Emonhub minimalmodbus_multiple_meters branch https://github.com/openenergymonitor/emonhub/tree/minimalmodbus_multiple_meters:


[[SDM120]]
    Type = EmonHubMinimalModbusInterfacer
    [[[init_settings]]]
        device = /dev/ttyUSB0
        baud = 2400
    [[[runtimesettings]]]
        pubchannels = ToEmonCMS,
        read_interval = 10
        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

Tried modifying this part of the script to this but didn’t work

from emonhub_interfacer import EmonHubInterfacer

[[SDM120]]
    Type = EmonHubMinimalModbusInterfacer
    [[[init_settings]]]
        device = /dev/ttyUSB0
        baud = 2400
    [[[runtimesettings]]]
        pubchannels = ToEmonCMS,
        read_interval = 10
        nodename = sdm120
        # prefix = sdm_
        registers = 6,12,18,24,30,70,72,74,76,78,342,344
        names = I,P,VA,RP,PF,FR,EI,EE,RI,ER,TA,TR
        precision = 3,1,1,2,3,3,3,3,3,3,3,3
class EmonHubSDM120Interfacer

SDM120 interfacer for use in development



class EmonHubMinimalModbusInterfacer(EmonHubInterfacer):

    def __init__(self, name, device="/dev/modbus", baud=2400):
        Initialize Interfacer

        # Initialization
        super(EmonHubMinimalModbusInterfacer, 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':'',
            'registers': [6,12,18,24,30,70,72,74,76,78,342,344],
            'names': [,'I','P','VA','AP','PF','FR','EI','EE','RI','ER','TA','TR'],
            'precision': [3,1,1,2,3,3,3,3,3,3,3.3]
        }
        
        self.next_interval = True
        
        # Only load module if it is installed         
        try: 
            import minimalmodbus
            # import serial
            self._log.info("Connecting to Modbus device="+str(device)+" baud="+str(baud))

[formatted by Mod - use 3 backticks (top left of most keyboards) not apostrophe.]

I can’t work out why when I change the script and upload the new modified script nothing changes even after a reboot, I have tried twice with a modified EmonHubSDM120Interfacer.py, the original is

datafields = voltage,power_active,power_factor,frequency,import_energy_active,current
names = V,P,PF,FR,E,I
precision = 2,2,4,4,3,3

the two modified ones I tried are
datafields = voltage,power_active,power_factor,frequency,import_energy_active,import_energy_reactive
names = V,P,PF,FR,E,IM
precision = 2,2,4,4,3,3

and
datafields = voltage,power_active,power_factor,frequency,import_energy_active,export_energy_active
names = V,P,PF,FR,E,IM
precision = 2,2,4,4,3,3

after each of these uploads and reboot I am still getting the “current” which is the one I deleted and changed and the modified ones don’t even appear in the inputs at all, what is happening and why is there no change?

Aha you dont need to modify the script, just the emonhub.conf configuration, with the interfacer configuration details:

[[SDM120]]
    Type = EmonHubMinimalModbusInterfacer
    [[[init_settings]]]
        device = /dev/ttyUSB0
        baud = 2400
    [[[runtimesettings]]]
        pubchannels = ToEmonCMS,
        read_interval = 10
        nodename = sdm120
        # prefix = sdm_
        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

were you able to restore the script to it’s original?

Hi TrystanLea, I uploaded the original script with FileZilla and changed the emonhub.conf configuration to the one you posted above, but I lose all the inputs from my SDM220, when I restore the emonhub.conf configuration back to the one from EmonHub Interfacers guides I get the inputs back, the SDM220 doesn’t like the EmonHubMinimalModbusInterfacer, any suggestions?


You should not need to do that as it is on the EmonPi by default.

However, are you referring to the emonhub scripts or some other script?

I have been changing the script in the Interfacer folder on the emonpi a few times trying to get it to read my SDM220, that is why I uploaded the original script

When you change the script, did you reload the emonhub service?

Yes I reload the emonhub and even rebooted it also just to make sure I didn’t miss anything. As soon as I got back to adding the EmonHubSDM120Interfacer into the emonhub, save and reload, the 6 feeds come back online

Ok, so the [[[runtimesettings]]] overwrite what is in the script as defaults, the SDM120 is (I think) simply a specific version of the minimal modbus interfacer.

You need to work out from the docs, what registers you need and what the precision is for each.

I suggest you just start with a couple of registers and build it up from there.

[edit]
Just looking in detail at some of the bits you posted;

This (GitHub - re3lex/SDM220-Modbus-RTU-ESP8266: Simple ModBus master for SDM220 and HTTP server implementation) seems to be a good source and yes the registers are numbered in Hex not decimal and somewhat match the SDM120.

If by this you mean that using the SDM120 interfacer those Inputs are created on the EmonPi, that supports my theory as those register numbers are the same on both systems.

Convert the Hex to decimal and put into the runtimesettings. Unfortunately, I can’t say what the precision should be. A bit of Trial and Error is required. As I say, just build the registers up slowly.

The other source looks a bit iffy as the registers listed for the SDM120 do not match what are in use, so I’d ignore that.

I have never done coding, I can understand a little of it, and play around with it a little by trial and error but a little knowledge can be dangerous, not that I am building a Nuclear reactor, so I am safe with that, I can understand that the SDM120 and the SDM220 have similar registers

So this isn’t coding, it is configuration!

@TrystanLea example above isn’t quite correct - try this

[[SDM120]]
    Type = EmonHubMinimalModbusInterfacer
    [[[init_settings]]]
        device = /dev/ttyUSB0
        baud = 2400
    [[[runtimesettings]]]
        pubchannels = ToEmonCMS,
        read_interval = 10
        nodename = sdm120
        registers = 0,6,12,18,24,30,70,72,74,76,78,86,88
        names = V,I,P,VA,RP,PF,FR,EI,EE,RI,ER,TA,TR
        precision = 3,1,1,2,2,3,3,3,3,3,3,3,3

Just added some spaces to align (don’t use) note the Hex values match the bit in the previous post…

        registers = 0, 6 ,12,18,24,30,70,72,74,76,78,86,88
        names =     V ,I ,P ,VA,RP,PF,FR,EI,EE,RI,ER,TA,TR
        precision = 3 ,1 ,1 ,2 ,2 ,3 ,3 ,3 ,3 ,3 ,3 ,3 ,3
        #Hex =      00,06,0C,12,18,1E,46,48,4A,4C,4E,56,58

I have guessed the precision!

I have tried the config without success, I have even deleted all but 2 registers, names and precision without success, just wondering where does the original EmonHubSDM120Interfacer config gets it’s registers from to work in the first place?, I looked at EmonHubSDM120Interfacers and show no registers at all only datafields = voltage,power_active,power_factor,frequency,import_energy_active,current

[[SDM120]]
    Type = EmonHubSDM120Interfacer
    [[[init_settings]]]
        device = /dev/ttyUSB0
        baud = 2400
    [[[runtimesettings]]]
        pubchannels = ToEmonCMS,
        read_interval = 10
        nodename = SDM120

import time
import json
import Cargo
import os
import glob
from emonhub_interfacer import EmonHubInterfacer

[[SDM120]]
    Type = EmonHubSDM120Interfacer
    [[[init_settings]]]
        device = /dev/ttyUSB0
        baud = 2400
    [[[runtimesettings]]]
        pubchannels = ToEmonCMS,
        read_interval = 10
        nodename = sdm120
        # prefix = sdm_
        datafields = voltage,power_active,power_factor,frequency,import_energy_active,current
        names = V,P,PF,FR,E,I
        precision = 2,2,4,4,3,3

 

class EmonHubSDM120Interfacer
SDM120 interfacer for use in development

class EmonHubSDM120Interfacer(EmonHubInterfacer):

    def __init__(self, name, device="/dev/modbus", baud=2400):
        """Initialize Interfacer
        """
        # Initialization
        super(EmonHubSDM120Interfacer, 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._SDM120_settings = {
            'read_interval': 10.0,
            'nodename':'sdm120',
            'prefix':'',
            'datafields': ['voltage','power_active','power_factor','frequency','import_energy_active','current'],
            'names': ['V','P','PF','FR','E','I'],
            'precision': [2,2,4,4,3,3]
        }
        
        self.next_interval = True

        # Only load module if it is installed
        try:
            import sdm_modbus
            self._log.info("Connecting to SDM120 device=" + str(device) + " baud=" + str(baud))
            self._sdm = sdm_modbus.SDM120(device=device, baud=int(baud))
            self._sdm_registers = sdm_modbus.registerType.INPUT
        except ModuleNotFoundError as err:
            self._log.error(err)
            self._sdm = 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._sdm and self._sdm.connected():
                    try:
                        r = self._sdm.read_all(self._sdm_registers)
                    except Exception as e:
                        self._log.error("Could not read from SDM120: " + str(e))
                    # for i in r:
                    #     self._log.debug(i+" "+str(r[i]))
                    
                    # Can r be False in any reasonable situation? Why not just return in the exception handler above? 
                    # Unless read_all can return, e.g., [] or None then this is just overcomplicating things.
                    if r:
                        try:
                            for i in range(len(self._settings['datafields'])):
                                datafield = self._settings['datafields'][i]
                                if datafield in r:
                                    # default name is datafield name
                                    name = datafield 
                                    # datafield value
                                    value = r[datafield]
                                    # replace datafield name with custom name
                                    if i<len(self._settings['names']):
                                        name = self._settings['names'][i]
                                    # apply rounding if set
                                    if i<len(self._settings['precision']):
                                        value = round(value,self._settings['precision'][i])
                                    
                                    c.names.append(self._settings['prefix']+name)
                                    c.realdata.append(value)

                            self._log.debug(c.realdata)
                        except Exception as e:
                            self._log.error("Error parsing data: " + str(e))

                    if len(c.realdata) > 0:
                        return c
                else:
                    self._log.error("Not connected to SDM120")

        else:
            self.next_interval = True

        return False


    def set(self, **kwargs):
        for key, setting in self._SDM120_settings.items():
            # Decide which setting value to use
            if key in kwargs:
                setting = kwargs[key]
            else:
                setting = self._SDM120_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 == 'datafields':
                self._log.info("Setting %s datafields: %s", self.name, ",".join(setting))
                self._settings[key] = setting
                continue
            elif key == 'names':
                self._log.info("Setting %s names: %s", self.name, ",".join(setting))
                self._settings[key] = setting
                continue
            elif key == 'precision':
                self._log.info("Setting %s precision: %s", self.name, ",".join(map(str,setting)))
                self._settings[key] = setting
                continue
            else:
                self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key)

        # include kwargs from parent
        super().set(**kwargs)

[Formatted for presentation. Moderator (RW) ]

I added into the config
address = 1
but made no difference

the Emonhub message log
Could not read all registers, connecting to modbus device=/dev/ttyUSB0 baud=2400

Just a thought would you need to connect to the device first then read the registers, by the log, it trying to read the registers before connecting to device?

No, use the minimalModbus interfacer not the SDM120 one.

Don’t edit the code.