Community
OpenEnergyMonitor

Community

Autometers DDSD285 Smart Meter Modbus RS485 interfacing and monitoring

smartmeter
modbus
rs485
Tags: #<Tag:0x00007f13ed84cda0> #<Tag:0x00007f13ed84ca80> #<Tag:0x00007f13ed84c878>

(Anthony Smith) #41

All,

Off to bed with solid progress. After laboriously testing each register It seems that the only valid registers are in the code below. The Protocol guide is a generic guide covering all their products.

Code:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import minimalmodbus

instrument = minimalmodbus.Instrument("/dev/ttyUSB0", 1)
instrument.serial.baudrate = 9600
instrument.serial.parity = minimalmodbus.serial.PARITY_EVEN
instrument.serial.stopbits = 1
instrument.serial.timeout  = .5
instrument.debug = False

#print instrument

TOTKWH  = round(instrument.read_float((32001-30001),4),4) # Total Energy KWHs

#Address Register  Length Parameter Name Access Data Format   Units
# (hex)  (decimal) (bytes) (string)       (R/W) (Float,16bit) A,V,KW,KWh,Hz 
# 0x0010   30017      4   Voltage L1        R      Float        V
# 0x004E   30079      4   Frequency         R      Float        Hz
VOLTAGE = round(instrument.read_float((30017-30001),4),4) 
FREQUENCY = round(instrument.read_float((30079-30001),4),4) 

#Address Register  Length Parameter Name Access Data Format   Units
# (hex)  (decimal) (bytes) (string)       (R/W) (Float,16bit) A,V,KW,KWh,Hz 
# 0x0058 30089        4   Current Total     R       Float       A
AMPS = round(instrument.read_float((30089-30001),4),4)  

#Address Register  Length Parameter Name Access Data Format   Units
# (hex)  (decimal) (bytes) (string)       (R/W) (Float,16bit) A,V,KW,KWh,Hz
# 0x0090   30145      4   Power L1          R       Float       KW
POWER = (round(instrument.read_float((30145-30001),4),4)*1000) 

#BAUDRATE = instrument.read_register((31318-30001),0,4)
# SETBAUD = instrument.write_register((31318-30001),38400,0,6)
#print BAUDRATE

print('Volts  Amps Frequency Hz Power Load Watts Total KWHs\n{} {} {}        {}             {} \n'.format(VOLTA$

exit()

The resulting output is:

[email protected]:~/DDSD285 $ python ddsd285-test1.py
Volts    Amps Frequency Hz     Power Load Watts Total KWHs
240.19 0.3     50.09                   47.0                               0.58 

Solid progress. Thank you to all.

Cheers
Tony

Edit - formatted post for readability. BT - Moderator.


(Greebo) #42

As per Paul’s reply, we’re suspicious that the registers claim to be hex but may actually be interpreted as decimal. I think it would be unwise to write to that register to change anything at this stage and risk losing access to it completely :slight_smile:

If you’ve configured the code/interface to talk at 9600, then it should be talking at 9600, and if you’re getting sensible data back, then the other end must also be talking at 9600. If documentation says those two bytes Paul identified in bold in this response

do indeed hold the baud rate, then they MUST actually be decimal digits, not hex digits.

9600 decimal is actually 2580 hex, I don’t think I saw that in any of the responses.

I’m also very confused how you represent 115200 baud in 2 bytes…
I had a quick skim through the protocol PDF from post #3, trying to figure it out (and failing to) but I did find in section 2.1.1 that it also talks about specific timing when the baud rate is above or below 19200.

So maybe there’s more things to worry about if you start playing with the baud rate (another reason to leave well enough alone for now).


(Bill Thomson) #43

From the minimalmodbus docs:


read_float ( registeraddress , functioncode=3 , numberOfRegisters=2 )[source]

Read a floating point number from the slave.

Floats are stored in two or more consecutive 16-bit registers in the slave. The encoding is according to the standard IEEE 754.

There are differences in the byte order used by different manufacturers. A floating point value of 1.0 is encoded (in single precision) as 3f800000 (hex). In this implementation the data will be sent as '\x3f\x80' and '\x00\x00' to two consecutetive registers . Make sure to test that it makes sense for your instrument. It is pretty straight-forward to change this code if some other byte order is required by anyone (see support section).

Args:

  • registeraddress (int): The slave register start address (use decimal numbers, not hex).
  • functioncode (int): Modbus function code. Can be 3 or 4.
  • numberOfRegisters (int): The number of registers allocated for the float. Can be 2 or 4.

All of the minimalmodbus read / write API calls say to use decimal vice hex numbers.


(Paul) #44

Whilst we cannot rule out typo’s and mis-information, the table does state it is a 16bit (integer implied) and the function Tony used was read_register() rather than read_float()

The decimal returned when querying at 9600 baud was 38400(baud), this threw us and then I noticed the x96 x00 in the raw reply, possibly a coincidence, but that’s all we have to go on right now.

the response is id 1, func code 4, size 2, “0x96”, “0x00” and the last 2 bytes (0xD6 and 0x90) are CRC.

MinimalModbus debug mode. Response from instrument: '\x01\x04\x02\x96\x00\xd6\x90’ (01 04 02 96 00 D6 90) (7 bytes), roundtrip time: 128.0 ms. Timeout setting: 300.0 ms.

So unless 9600 baud and 38400 baud are interchangable, we have to assume the hex representation is right rather than the decimal, no matter how odd that seems. I did cross my mind that maybe it’s a partial read of a larger datatype (ie 4byte long/float) but there is no neighboring space unused.

Until we are sure, I think @Greebo’s advice to NOT change the baud is good advice.

The mystery continues . . .


(Bill Thomson) #45

Absolutely! Don’t want to get locked out, that’s for sure.

I was just trying to say that minimalmodbus need decimals (vice hex) in its API calls. :wink:
i.e. the read register call wants decimals vice hex, same as the read float call.

The data returned by MM from my WattsOns as well as a PZEM-016 is hex.


(Paul) #46

It’s a shame if that is all the info that is available, but possibly to be expected for a 20 quid device?

How are you determining which registers are good or bad? Are you getting no comms or zero’s? There is a section on Exception Codes in the pdf (page 45)

Is there a documented list of available values (worth dropping an email to the supplier?) for this model so you have a definitive spec? Since there are many ways you can trip up with Modbus and they all result in “no data” it would be easy to assume something wasn’t available without being 100% sure.

Since there are so few values and it’s the only device on the network, I don’t think there is any need to change the baud, and with the risk of breaking comms altogether as we don’t know for sure how that bit works, it isn’t worth the risk.


(Anthony Smith) #47

I agree its too dangerous to write to that baud register and risk locking myself out. For now I have at least got comms operating and can interrogate the device.

In terms of determining which registers are active for this device I did it long hand and tried to read from each of the potentially relevant registers. Comms errors typically and the Illegal Data Address response such as \x01\x84\x02\xc2\xc1 when trying to read 31537 Total Amps .

I cannot seems to get a specific manual for the device.

Now I have data I want to try and send it to emoncms to display it centrally. We plan to host it on the intranet centrally and have a dashboard showing the various data points from the 3 meters. We can then simply run a very minimal server implementation on each pi Zero to talk to the meters and send the data.

Confused by the structure of emoncms and posting the data. I need to do more reading on the Device,Feed,Input etc. and how to get the data items into the emoncms platform.

Cheers
Tony


(Anthony Smith) #48

OK, Managed to cobble this together to get the data then send it to emoncms using a modified version of Bills script. Really just these 2 lines:

read DATE TIME VOLTAGE AMPS FREQUENCY POWER TOTKWH < /home/pi/DDSD285/meter_data.txt
#
#
/usr/bin/curl -s -m 3 --connect-timeout 2 "http://192.168.0.182/emoncms/input/post?node=TWHGOFFICE&json={POWER:$POWER,TOTKWH:$TOTKWH,VOLTAGE:$VOLTAGE,AMPS:$AMPS}&apikey=5880fef251b2409d66e2a28cf6d30493" > /dev/$

Not sure how to format code blocks in this forum to preserve formatting.

I setup the various Inputs,Feeds and made a basic dashboard.



It is a bit flaky as the request for the data seems to result in no comms intermittently. At first I thought it was a specific register that was the issue but disabling them one at a time resulted in the same behaviour with a different register. However if I enable the debug flag it works perfectly every time. Not sure what that’s about.

Anyway Phase 1 done. Now to automate and industrialise the programs a bit. I have uploaded the program and script as it is now. Had to change the filenames to add .txt so remember to delete that extension.
ddsd285-test1.py.txt (1.9 KB)
send-to-emoncms.sh.txt (1.4 KB)

Cheers
Tony


(Bill Thomson) #49

Like this:

`Lorem ipsum dolem`

which gives you:

Lorem ipsum dolem

IOW, enclose your text in backticks. :wink:

An easy way format a block of text is highlight the text you want to format by dragging your
mouse over it, or hold down the shift key and use the keyboard up or down arrows to select
the text a row at a time.

I fixed it for you. :wink:


(Bill Thomson) #50

Have you tried adding a 120Ω resistor to the end of the chain and enabling the termination
resistor in the meter itself? May not help, but is worth a try.


(Anthony Smith) #51

Bill,

Many thanks for fixing the formatting. Resistors and such things have always been a mystery to me. I tried reading the resistor register but got the comms error illegal data address so this device may not have one.

Why enabling the Debug flag makes it work every time? Maybe the delay in processing the extra debug statements makes the timing work.

Thank you for your help in getting this to this stage.

Cheers
Tony


(Bill Thomson) #52

YW,S!

Happy to help.

Have you tried adding or subtracting one from the register address?
i.e. when it gives you the address error, subtract one from the register address and try agan.
If subtracting one from the address doesn’t work, try adding one.

Depending on the manufacturer, and sometimes even on the instrument, modbus registers are
sometimes referenced with an offset of one.

e.g. here’s an excerpt from the register “map” for my WattsOn power transducer:
image

Notice that the first address in the list is referenced as 0x300 and decimal 40769.
However, to read that register, I have to use a decimal address of 40768


(Paul) #53

That works ok for in-line code snippets, but it doesn’t really work for multiple lines of code. If you use 3 backticks (```) in the line before and again in the line after your block of code it works much better

eg

read DATE TIME VOLTAGE AMPS FREQUENCY POWER TOTKWH < /home/pi/DDSD285/meter_data.txt
#
#
/usr/bin/curl -s -m 3 --connect-timeout 2 "http://192.168.0.182/emoncms/input/post?node=TWHGOFFICE&json={POWER:$POWER,TOTKWH:$TOTKWH,VOLTAGE:$VOLTAGE,AMPS:$AMPS}&apikey=5880fexxxxxxxxxxxxxxxxxxxxxxx0493" > /dev/$

would be displayed as

read DATE TIME VOLTAGE AMPS FREQUENCY POWER TOTKWH < /home/pi/DDSD285/meter_data.txt`
#
#
/usr/bin/curl -s -m 3 --connect-timeout 2 "http://192.168.0.182/emoncms/input/post?node=TWHGOFFICE&json={POWER:$POWER,TOTKWH:$TOTKWH,VOLTAGE:$VOLTAGE,AMPS:$AMPS}&apikey=5880xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0493" > /dev/$

Longer code snippets are also reduced with a vertical scroll bar added, here’s the 2 scripts that were linked for downloading (much easier to view within the thread)

#!/usr/bin/python
# -*- coding: utf-8 -*-

import threading, time, subprocess, logging, minimalmodbus

instrument = minimalmodbus.Instrument("/dev/ttyUSB0", 1)
instrument.serial.baudrate = 9600
instrument.serial.parity = minimalmodbus.serial.PARITY_EVEN
instrument.serial.stopbits = 1
instrument.serial.timeout  = .5
instrument.debug = True

#print instrument

TOTKWH  = round(instrument.read_float((32001-30001),4),4) # Total Energy KWHs

#Address Register  Length Parameter Name Access Data Format   Units
# (hex)  (decimal) (bytes) (string)       (R/W) (Float,16bit) A,V,KW,KWh,Hz
# 0x0010   30017      4   Voltage L1        R      Float        V
# 0x004E   30079      4   Frequency         R      Float        Hz
VOLTAGE = round(instrument.read_float((30017-30001),4),4)
FREQUENCY = round(instrument.read_float((30079-30001),4),4)

#Address Register  Length Parameter Name Access Data Format   Units
# (hex)  (decimal) (bytes) (string)       (R/W) (Float,16bit) A,V,KW,KWh,Hz
# 0x0058 30089        4   Current Total     R       Float       A
AMPS = round(instrument.read_float((30089-30001),4),4) 

#Address Register  Length Parameter Name Access Data Format   Units
# (hex)  (decimal) (bytes) (string)       (R/W) (Float,16bit) A,V,KW,KWh,Hz
# 0x0090   30145      4   Power L1          R       Float       KW
POWER = (round(instrument.read_float((30145-30001),4),4)*1000)

#BAUDRATE = instrument.read_register((31318-30001),0,4)
# SETBAUD = instrument.write_register((31318-30001),38400,0,6)
#print BAUDRATE

print('Volts  Amps Frequency Hz Power Load Watts Total KWHs\n{} {} {}        {}             {} \n'.format(VOLTA$
#print time.strftime('%Y%m%d %X ')
file = open('/home/pi/DDSD285/meter_data.txt', 'w+')
file.write(time.strftime('%Y%m%d %X '))
file.write('{} {} {} {} {}\n'.format(VOLTAGE,AMPS,FREQUENCY,POWER,TOTKWH))
file.close()

subprocess.call('/home/pi/DDSD285/send-to-emoncms.sh') # call the bash file to transmit the data

exit()
#!/bin/bash
#
# I gave the variables (DATE TIME GENW VOLT etc) the same names I used in the Python script.
# It made things easier to keep track of. They can be given any name you like.
#
# Generate a "here doc" that takes its data from the meter_data.txt file written by the Python script.
read DATE TIME VOLTAGE AMPS FREQUENCY POWER TOTKWH < /home/pi/DDSD285/meter_data.txt
#
#
#/usr/bin/curl -s -m 3 --connect-timeout 2 "http://192.168.0.182/emoncms/input/post?node=TWHGOFFICE&csv=$POWER,$TOTKWH&apikey=588xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0493" > /dev/null
/usr/bin/curl -s -m 3 --connect-timeout 2 "http://192.168.0.182/emoncms/input/post?node=TWHGOFFICE&json={POWER:$POWER,TOTKWH:$TOTKWH,VOLTAGE:$VOLTAGE,AMPS:$AMPS}&apikey=5880xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0493" > /dev/$

#
# -s is the curl "silent" switch.
# -m sets the maximun amount of time (in seconds) curl is allowed for the entire send transaction.
# set the node number to the node that has the data you want to transmit.
#
# Variables
# GENW = GENerated power in Watts, from my pv system
# HTOT = House TOTal. total house load, in Watts.
# VOLT = self explanatory.
# CONS = present CONSumption in Watts.
# TEC  = Total Energy Consumed in KWh.
# IMPA = IMPorted energy, Leg A, in KWh.
# IMPB = IMPorted energy, Leg B, in KWh.
# EXPA = EXPorted energy, Leg A, in KWh.
# EXPB = EXPorted energy, Leg B, in KWh.
# NETA = NET energy, Leg A.
# NETB = NET energy, Leg B.
# NETC = pv production in KWh.

It’s much easier to discuss code without needing to download it, Just plant the code between 2 sets of 3 back ticks eg

    ```
    chunk of code
    ```

Edit - munged API key. BT - Moderator.


(Paul) #54

Yes it could well be the timing is being altered by the console print. As I said in the first couple of posts, you should try to have a pause in between reads, most of the time you might get away without it, but I’ve found it more stable to wait ~0.02s or so between reads.

What does the debug prints show you? I know the intermittent issue isn’t occurring when the debug is enabled, but are the times “close” to timing out or being premature?

here’s a debug print from earlier

[email protected]:~/DDSD285 $ python ddsd285-test1.py

MinimalModbus debug mode. Writing to instrument (expecting 9 bytes back): ‘\x01\x04\x07\xd0\x00\x02qF’ (01 04 07 D0 00 02 71 46)
MinimalModbus debug mode. No sleep required before write. Time since previous read: 1549323920849.6 ms, minimum silent period: 4.01 ms.
MinimalModbus debug mode. Response from instrument: ‘\x01\x04\x04?\x07\xae\x14:>’ (01 04 04 3F 07 AE 14 3A 3E) (9 bytes), roundtrip time: 127.0 ms. Timeout setting: 300.0 ms.

MinimalModbus debug mode. Writing to instrument (expecting 7 bytes back): ‘\x01\x04\x05%\x00\x01 \xcd’ (01 04 05 25 00 01 20 CD)
MinimalModbus debug mode. No sleep required before write. Time since previous read: 5.3 ms, minimum silent period: 4.01 ms.
MinimalModbus debug mode. Response from instrument: ‘\x01\x04\x02\x96\x00\xd6\x90’ (01 04 02 96 00 D6 90) (7 bytes), roundtrip time: 128.0 ms. Timeout setting: 300.0 ms.

0.53 KWHs 38400 Baudrate

See the middle line in the second block of 3 lines, it says it was 5.3ms since the previous read and minimum silent period: 4.01 ms so you had 1.2ms wriggle room even with the extra print of the debug slowing it down, it’s really close, perhaps you need to slow down a tad with a delay.


(Bill Thomson) #55

Noted and recorded. :wink:

Thanks for the heads-up, PB! thumbsup


(Greebo) #56

Off-topic, but on subject, you can also force the “type” of formatting within the code block with certain specifiers after the three back-ticks…
So if you don’t want any formatting, use “text”, otherwise discourse tries to guess the language and apply formatting and highlighting based on that :slight_smile:

    ```text
    here's some stuff from a log file that wont get syntax highlighting
    ```

ref: https://meta.discourse.org/t/how-do-i-select-a-language-in-code-blocks/19247/2


(Bill Thomson) #57

Good to know. Tnx Greebo! thumbsup


(Anthony Smith) #58

Paul,

This is what I get from the command line:

python ddsd285-test1.py
Traceback (most recent call last):
  File "ddsd285-test1.py", line 21, in <module>
    VOLTAGE = round(instrument.read_float((30017-30001),4),4)
  File "/usr/local/lib/python2.7/dist-packages/minimalmodbus.py", line 392, in read_float
    return self._genericCommand(functioncode, registeraddress, numberOfRegisters=numberOfRegisters, payloadformat='float')
  File "/usr/local/lib/python2.7/dist-packages/minimalmodbus.py", line 697, in _genericCommand
    payloadFromSlave = self._performCommand(functioncode, payloadToSlave)
  File "/usr/local/lib/python2.7/dist-packages/minimalmodbus.py", line 795, in _performCommand
    response = self._communicate(request, number_of_bytes_to_read)
  File "/usr/local/lib/python2.7/dist-packages/minimalmodbus.py", line 930, in _communicate
    raise IOError('No communication with the instrument (no answer)')
IOError: No communication with the instrument (no answer)

It can report different registers each time. And then it will suddenly work. With Debug True it seems to work each time.

Cheers
Tony


(Paul) #59

I’ve edited your post, the lines before and after code should be 3x backticks ``` (top left of the keyboard) not 3 single quotes '''.

Have you tried a delay as suggested in my last post?


(Anthony Smith) #60

so a sleep .02 between calls?

Cheers
Tony