Sunday, March 29, 2015

SDM220 Power Meter, MODBUS, Python, RS485

The Eastron SDM220 is a very inexpensive one-phase power/energy meter, which can be readout via RS485. I got myself one to play with, and there are a few observations I wanted to write down.

From Chris’ Miscellanea

From Chris’ Miscellanea

From Chris’ Miscellanea

It's actually a pretty nifty meter, measuring power and energy in both directions, voltage and line frequency. Furthermore it outputs power-factor (cosϕ) and reactive or apparent power (which is basically the same thing, just represented differently).

One thing that cost me some time to resolve, especially as I used a selfmade RS485 adapter in the first place about the correctnes of which I was unsure are: The RS485 terminals are wired incorrectly, B and A are swapped, this has been verified with an industrial/commercial interface. Connectors 7, 8 and 9 are GND, A and B. In that order. If the link is idle, B will be positive with respect to A.

There's an existing modbus library for Python (pymodbus) which works perfect, so far. The serial modbus link is instantiated like this (if the modbus runs with parity = None).

    cl = pymodbus.client.sync.ModbusSerialClient('rtu',
        port='/dev/ttyUSB1', baudrate=9600,

The values stored in the device are actually IEEE754 floating point values, which can be converted to python floats very conveniently, they are fetched as two consecutive 16bit register, concatenated to a 32bit field, and then reinterpreted as the 32bit float:

    resp = client.read_input_registers(basereg,2, unit=1)
    return struct.unpack('>f',struct.pack('>HH',*resp.registers))

The documentation for the SDM220 is actually quite comprehensive, but it took me quite a while until I really figured out what they meant... So here are the register numbers I sucessfully used to get date off the device:

        # Symbol    Reg#  Format
        ( 'V',      0x00, '%6.2f' ), # Voltage [V]
        ( 'Curr',   0x06, '%6.2f' ), # Current [A]
        ( 'P[act]', 0x0c, '%6.0f' ), # Active Power ("Wirkleistung") [W]
        ( 'P[app]', 0x12, '%6.0f' ), # Apparent Power ("Scheinl.") [W]
        ( 'P[rea]', 0x18, '%6.0f' ), # Reactive Power ("Blindl.") [W]
        ( 'PF',     0x1e, '%6.3f' ), # Power Factor   [1]
        ( 'Phi',    0x24, '%6.1f' ), # cos(Phi)?      [1]
        ( 'Freq',   0x46, '%6.2f' )  # Line Frequency [Hz]


paolo said...

Interesting, do you have a source example wich reads the data?


Christian Vogel said...
This comment has been removed by the author.
Christian Vogel said...

Here's the full script.

floaty said...

grandé !

fiddled a little bit around, wondering how to read the energy parms:

def main() :
regs = [
# Symbol Reg# Format
( 'V', 0x00, '%6.2f' ), # Voltage [V]
( 'Curr', 0x06, '%6.2f' ), # Current [A]
( 'P[act]', 0x0c, '%6.0f' ), # Active Power ("Wirkleistung") [W]
( 'P[app]', 0x12, '%6.0f' ), # Apparent Power ("Scheinl.") [W]
( 'P[rea]', 0x18, '%6.0f' ), # Reactive Power ("Blindl.") [W]
( 'PF', 0x1e, '%6.3f' ), # Power Factor [1]
( 'Phi', 0x24, '%6.1f' ), # cos(Phi)? [1]
( 'Freq', 0x46, '%6.2f' ), # Line Frequency [Hz]
( 'W[act]', 0x0156, '%6.2f' ), # Energy [kWh]
( 'W[rea]', 0x0158, '%6.2f' ) # Energy react [kvarh]

since the results are the same as shown on sdm220's display, I'm hopefully right

# V Curr P[act] P[app] P[rea] PF Phi Freq W[act] W[rea]
# ------:------:------:------:------:------:------:------:------:------
07:35:41 226.79 0.71 146 162 -70 0.903 -25.5 50.01 1.75 0.51
07:35:43 226.86 0.71 146 162 -70 0.901 -25.7 50.02 1.75 0.51
07:35:45 226.63 0.71 146 161 -70 0.903 -25.5 50.01 1.75 0.51

thanks, for this nice piece of code

Joost van der Linde said...

Grateful to have found thisa piece of code.
Apparently I have something wrong (fiddled with the wires and termination as well).
But your code keeps giving no values back.
# V Curr P[act] P[app] P[rea] PF Phi Freq Imp[act] Exp[act]
# ------:------:------:------:------:------:------:------:------:------
23:29:55 ...... ...... ...... ...... ...... ...... ...... ...... ...... ......
23:29:56 ...... ...... ...... ...... ...... ...... ...... ...... ...... ......
23:29:58 ...... ...... ...... ...... ...... ...... ...... ...... ...... ......

Any clue what this might be??

Christian Vogel said...

Joost, the "...." mean that

resp = client.read_input_registers(basereg,2, unit=1)

in line 9 returned None. So the pymodbus library is not able to get a response from your power meter. Check cabling, baudrate, # of stopbits and parity.

Use "strace" to watch the python process. There will be a *LOT* of output, but somewhere there will be burried a open("/dev/ttyUSB...") = XXX and write(XXX,...) and read(XXXX,...). (with XXX being a number). This allows you to see of something is read back from the serial port/RS485 at all.

Joost van der Linde said...

Thanks for reply.

Baudrate matches meter (used parity None, stopbits 2). Shouldnt I set databits as well?
Cabling should be correct, but will do again this evening.
Did you you use termination resistors on one/both ends (as advised in the documentation)?
Will also check if A and B are still reversed on the meter (as you mentioned it is on yours)

Manas R. Das said...

I am trying to implement Python modbus to read parameters like voltage,current and power from ENERGY METER (EM6400).i am using Pyserial instead pf any python modbus package.But i amn't able to implement it.I need a python code for the same.

Below here is the whole is the whole format of the data, i want to write and the respond i want to read from the meter.


01 03 0F56 0002 270F

01: Slave address

03: Function code for read holding registers

0F56: Data Address of the first requested register (address for voltage phase1 to neutral)

(0F56 hex = 3927, +40001 offset = 43928)

0002: Total number of registers requested

270F: CRC (Cyclic Redundancy Check) for error checking (LSB first)


01 03 04 2921 4373 D2B0

01: Slave address

03: Function code for read holding registers

04: Total number of bytes read

2921: Data in 1st requested register

4373: Data in 2st requested register

D2B0: CRC for error checking (LSB first)

Values in required register are 43732921 in hex (since obtained values are being read in little endian format) which is 243.16 when converted to floating point using IEEE 754 norms. Obtained value is a voltage (phase1 to neutral) which is 243.16 Volts.

Christian Vogel said...

Manas R. Das, have a look at this sample code:

Oli Vier said...

For Debian and Ubuntu -> simpler install

apt-get update

apt-get install python-pymodbus

Oli Vier said...

For simpler install with Debian or Ubuntu

apt-get update

apt-get install python-pymodbus

Joost van der Linde said...

Is there a way to list all units that can be queried?
Can you get somthing like a device-if from a SDM22?

zantafio said...

you really saved my day. Thanks for sharing, I have a SDM630 and the IEEE754 was not an easy thing to tackle with...