DDS238-2 Energy meter with modbus interface

Hello list, as I could not find complete guide I would like to share my experience of connecting the DDS238-2 electricity meter via modbus2.x to Openhab.

The electricity meter itself is relatively cheap and can be picked up on aliexpress for 13.5 € + additionally 4 € for RS485 USB-485 adapter. Thus contributing to an affordable monitoring solution.

  1. Install Modbus (2.x) in PaperUI > Add-ons > Bindings

  2. check that openhab user has access to /dev/ttyUSB0
    if necessary add openhab user to dialout group

  3. Don’t forget to enable Java access to the /dev/ttyUSB0 and restart openhab2

/etc/default/openhab2 
 EXTRA_JAVA_OPTS="-Dgnu.io.rxtx.SerialPorts=/dev/ttyUSB0"
  1. Install two python modbus librariers. We wll need them to accessthe meter directly.

pip install pymodbus
pip install minimalmodbus

  1. Now you need to check that the meter is accessible. The simplest way is use this python code from

https://www.domoticz.com/wiki/Python_-_Read-out_of_DDS238_kWh-meter_and_upload_to_Domoticz_and_to_PVOutput

use the first piece of code in the section “Multiple DDS238-1ZN kWh-meters at one RS485-bus”

If successfull you will see something like

python ./modbus-identify.py
Comms_value = 2561
=> DDS238_Status, Address = 10
=> DDS238_Status, Baudrate = 1

  1. By default all meters come with ModBus-RTU ID of 1. So if you plan to use multiple meters you need to change it using

use the second piece of code in the section “Multiple DDS238-1ZN kWh-meters at one RS485-bus” to change the ID

  1. Now let’s check if we can read the values from the meter using this small piece of python code
from pymodbus.client.sync import ModbusSerialClient as ModbusClient

client = ModbusClient(method='rtu', port='/dev/ttyUSB0', timeout=1, stopbits = 1, bytesize = 8,  parity='N', baudrate= 9600)
client.connect()
request = client.read_holding_registers(0x00,0x2c,unit=10)
print request.registers
# python modbus-read.py                                                                                                                                                 
[0, 86, 0, 0, 0, 0, 0, 0, 0, 0, 0, 86, 2257, 42, 12, 94, 134, 4997, 0, 0, 0, 2561, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

If you have managed to succesfully obtain the readings you can proceed with meter configuration and its integration to the OpenHAB universe.

  1. add the following to the modbus.things
Bridge modbus:serial:usbstick10 [port="/dev/ttyUSB0", id=10, baud=9600, stopBits="1.0", parity="none", dataBits=8, encoding="rtu"] {
         Bridge poller zone10   [ start=0, length=43, refresh=5000, type="holding" ] {
            Thing data reg00    [ readStart="0",    readValueType="uint16" ]
            Thing data reg01    [ readStart="1",    readValueType="uint16", readTransform="JS(divide100.js)"  ]
            Thing data reg11    [ readStart="11",   readValueType="uint16", readTransform="JS(divide100.js)"]
            Thing data reg12    [ readStart="12",   readValueType="uint16", readTransform="JS(divide10.js)" ]
            Thing data reg13    [ readStart="13",   readValueType="uint16", readTransform="JS(divide100.js)" ]
            Thing data reg14    [ readStart="14",   readValueType="uint16", readTransform="JS(divide1000.js)"]
            Thing data reg15    [ readStart="15",   readValueType="uint16", readTransform="JS(divide1000.js)" ]
            Thing data reg16    [ readStart="16",   readValueType="uint16", readTransform="JS(divide1000.js)" ]
            Thing data reg17    [ readStart="17",   readValueType="uint16", readTransform="JS(divide100.js)" ]
            Thing data reg21H   [ readStart="21.1", readValueType="uint8" ]
            Thing data reg21L   [ readStart="21.0", readValueType="uint8" ]
        }
}
  1. add the following to the modbus.items
Number z10Reg99   "Total Energy [%.2f kWh]"             <energy>        (gPers_Change_Hour, gReg99)
Number z10Reg00   "Total Energy High"                   <energy>        (gz10)                          { channel="modbus:data:usbstick10:zone01:reg00:number" }
Number z10Reg01   "Total Energy Low"                    <energy>        (gz10)                          { channel="modbus:data:usbstick10:zone01:reg01:number" }
Number z10Reg12   "Voltage [%.1f V]"                    <energy>        (gPers_Change)  { channel="modbus:data:usbstick10:zone10:reg12:number" }
Number z10Reg13   "Current [%.2f A]"                    <energy>        (gPers_Change)  { channel="modbus:data:usbstick10:zone10:reg13:number" }
Number z10Reg14   "Active Power [%.3f kW]"              <energy>        (gPers_Change)  { channel="modbus:data:usbstick10:zone10:reg14:number" }
Number z10Reg15   "Reactive Power [%.3f kVar]"          <energy>        (gPers_Change)  { channel="modbus:data:usbstick10:zone10:reg15:number" }
Number z10Reg16   "PowerFactor [%.3f]"                  <energy>        (gPers_Change)  { channel="modbus:data:usbstick10:zone10:reg16:number" }
Number z10Reg17   "Frequency [%.2f Hz]"                 <energy>        (gPers_Change)  { channel="modbus:data:usbstick10:zone10:reg17:number" }
Number z10Reg21H  "ModBus Id [%d]"                      <energy>                        { channel="modbus:data:usbstick10:zone10:reg21H:number" }
Number z10Reg21L  "Baudrate [MAP(modbusbaud.map):%s]"   <energy>                        { channel="modbus:data:usbstick10:zone10:reg21L:number" }
Number z10HourCounter "Last Hour [%.2f kWh]"    <energy>        (gPers_Change_Hour)
Number z10DayCounter  "Last Day [%.2f kWh]"     <energy>        (gPers_Change_Day)
Number z10WeekCounter "Last Week [%.2f kWh]"    <energy>        (gPers_Change_Week)
Number z10MonthCounter "Last Month [%.2f kWh]"  <energy>        (gPers_Change_Month)

  1. simple sitemap to check the status of registers.
  Frame label="ModB10" {
        Text    item=z10Reg99
        Text    item=z10Reg00
        Text    item=z10Reg01
        Text    item=z10Reg12
        Text    item=z10Reg13
        Text    item=z10Reg14
        Text    item=z10Reg15
        Text    item=z10Reg16
        Text    item=z10Reg17
        Text    item=z10Reg21H
        Text    item=z10Reg21L
    }
  1. transformation map for baudrate
1=9600 bps
2=4800 bps
3=2400 bps
4=1200 bps

12.transformation map for divide100.js (you will need also to create divide10.js and divide1000.js)

// Wrap everything in a function (no global variable pollution)
// variable "input" contains data passed by openhab
(function(inputData) {
    // on read: the polled number as string
    // on write: openHAB command as string
    var DIVIDE_BY = 100;
    return parseFloat(inputData) / DIVIDE_BY;
})(input)
  1. Persistence needs to be enabled in order to calculate the energy rules. I use mysql and record every state change.
Strategies {
        // if no strategy is specified for an item entry below, the default list will be used
        everyMinute     : "0 * * * * ?"
        every5Minutes   : "0 */5 * * * ?"
        everyHour       : "0 0 * * * ?"
        everyDay        : "0 0 0 * * ?"
        default = everyChange
}

Items {
    // persist all items once a day and on every change and restore them from the db at startup
    gPers_Change* : strategy = everyChange, everyHour, everyDay
    gPers_5Min*   : strategy = every5Minutes, everyHour
    gPers_1Min*   : strategy = everyMinute, everyHour
}
  1. It is usefull to know the total amount of energy consumed, so here we will rely on the internal mechnism of the meter. Rules to calculate hourly and daily energy consumption.
rule "Energy by hour"
when
        Time cron "0 0 * * * ?"
then
        var hour = z10Reg01.state as DecimalType - z10Reg01.historicState(now.minusHours(1), "mysql").state as DecimalType
        logInfo("ENERGY","z10HourCounter = " + hour)
        postUpdate(z10HourCounter, hour)
end

rule "Energy by Day"
when
        Time cron "0 0 0 * * ?"
then
        var Day = z10Reg01.state as DecimalType - z10Reg01.historicState(now.minusHours(24), "mysql").state as DecimalType
        logInfo("ENERGY","z10DayCounter = " + Day)
        postUpdate(z10DayCounter, Day)
end

When you have more then one device you might consider automating the process as following

// energy.rules
import org.eclipse.smarthome.model.script.ScriptServiceUtil

rule "Energy Consumed by hour"
when
        Time cron "0 0 * * * ?"
then
     gReg99.members.forEach[member |
        var triggeringzonehourcounter = ScriptServiceUtil.getItemRegistry.getItem(member.name.toString.split("Reg99").get(0) + "HourCounter")
        var Number triggeringzonehourconsumption = member.state as DecimalType - member.historicState(now.minusHours(1), "mysql").state as DecimalType
        logInfo("ENERGY_HOUR", triggeringzonehourcounter.name + " = " + triggeringzonehourconsumption)
        postUpdate(triggeringzonehourcounter, triggeringzonehourconsumption)
     ]
end

Thats pretty much it. I suspect there is an extra register at z10Reg10 that shows Import Energy, but not in my setup. I also think that z10Reg11 is pretty useless, since in the normal situation it just mimics the z10Reg01, but for smeone with PV it might come usefull.

UPDATE: Changed the type of variable from int8 to uint16.
One of my zones had passed the 65535 max value supported by uint16 register so following a little investigation, the total energy consumed by the zone has to be manually calculated as following

rule "total energy update z10"
when
        Member of gz10 changed
then
        var  z10CombinedPower = z10Reg00.state as DecimalType * 655.35 + z10Reg01.state as DecimalType
        logInfo("ENERGY","z10Reg99: " + z10CombinedPower )
        postUpdate(z10Reg99, z10CombinedPower)
end
3 Likes