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.
-
Install Modbus (2.x) in PaperUI > Add-ons > Bindings
-
check that openhab user has access to /dev/ttyUSB0
if necessary add openhab user to dialout group -
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"
- Install two python modbus librariers. We wll need them to accessthe meter directly.
pip install pymodbus
pip install minimalmodbus
- Now you need to check that the meter is accessible. The simplest way is use this python code from
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
- 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
- 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.
- 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" ]
}
}
- 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)
- 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
}
- 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)
- 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
}
- 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