Skip to content

Instantly share code, notes, and snippets.

@alphp
Last active March 24, 2024 21:56
Show Gist options
  • Star 28 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save alphp/95e1efe916c0dd6df7156f43dd521d53 to your computer and use it in GitHub Desktop.
Save alphp/95e1efe916c0dd6df7156f43dd521d53 to your computer and use it in GitHub Desktop.
Hiking DDS238-2 ZN/S energy meter

Hiking DDS238-2 ZN/S energy meter

Document included in the project https://github.com/fawno/Modbus

https://github.com/fawno/Modbus/blob/master/DDS238-2%20ZN-S%20Modbus.md

Modbus holding registers:

Register(s) Meaning Scale Unit Data format R/W
0000h-0001h total energy 1/100 kWh unsigned dword
0002h-0003h reserved unsigned dword
0004h-0005h reserved unsigned dword
0006h-0007h reserved unsigned dword
0008h-0009h export energy 1/100 kWh unsigned dword
000Ah-000Bh import energy 1/100 kWh unsigned dword
000Ch voltage 1/10 V unsigned word R
000Dh current 1/100 A unsigned word R
000Eh active power 1 W signed word R
000Fh reactive power 1 VAr unsigned word R
0010h power factor 1/1000 unsigned word R
0011h frequency 1/100 Hz unsigned word R
0012h reserved unsigned word
0013h reserved unsigned word
0014h reserved unsigned word
0015h:high station address 1-247 unsigned char R/W
0015h:low baud rate 1-4² unsigned char R/W
001Ah relay³ unsigned word R/W

Notes:

Note 1:

Total, export and import energy counters can erased writing 0 in total energy registers.

Note 2:

Value mapping, default 1.

Value Baud rate
1 9600 Bd
2 4800 Bd
3 2400 Bd
4 1200 Bd
Note 3:

In DDS238-2 ZN/SR model the relay can be switched by 0x001A register.

Value Relay
0 Off
1 On
Data formats
Data format Lenght Byte order
char 8 bits
word 16 bits Big endian
dword 32 bits Big endian

Writing registers

The meter does not understand the 'write sigle register' function code (06h), only the 'write multiple registers' function code (10h).

<?php
function set_win_port_attr ($port_name = 'com1:', $port_attr = ['baud' => 9600, 'data' => 8, 'stop' => 1, 'parity' => 'n']) {
$mode = 'mode ' . $port_name;
foreach ($port_attr as $attr => $value) {
$mode .= ' ' . $attr . '=' . $value;
}
exec($mode, $output, $return);
return $return;
}
function crc16 ($data) {
$crc = 0xFFFF;
foreach (unpack('C*', $data) as $byte) {
$crc ^= $byte;
for ($j = 8; $j; $j--) {
$crc = ($crc >> 1) ^ (($crc & 0x0001) * 0xA001);
}
}
return pack('v1', $crc);
}
$modbus_message_format = [
'address' => 'C1',
'function' => 'C1',
'start' => 'n1',
'quantity' => 'n1',
//'crc' => 'n1',
];
$modbus_message_pack = implode($modbus_message_format);
$modbus_message_unpack = null;
foreach ($modbus_message_format as $name => $format) {
$modbus_message_unpack .= $format . $name . '/';
}
$modbus_response_format = [
'address' => 'C1',
'function' => 'C1',
'count' => 'C1',
'total_energy' => 'N1',
'reserved' => 'N3',
'export_energy' => 'N1',
'import_energy' => 'N1',
'voltage' => 'n1',
'current' => 'n1',
'active_power' => 'n1',
'reactive_power' => 'n1',
'power_factor' => 'n1',
'frecuency' => 'n1',
//'year' => 'C1',
//'month' => 'C1',
//'day' => 'C1',
//'hour' => 'C1',
//'minute' => 'C1',
//'second' => 'C1',
//'comm_addr' => 'C1',
//'comm_baud' => 'C1',
//'trip_energy' => 'N1',
//'trip_time' => 'N1',
//'unknow' => 'n5',
//'month2' => 'C1',
//'day2' => 'C1',
'crc' => 'n1',
];
$modbus_response_pack = implode($modbus_response_format);
$modbus_response_unpack = null;
foreach ($modbus_response_format as $name => $format) {
$modbus_response_unpack .= $format . $name . '/';
}
$port_name = 'COM3:';
$port_attr = ['baud' => 9600, 'data' => 8, 'stop' => 1, 'parity' => 'n', 'xon' => 'off'];
set_win_port_attr($port_name, $port_attr);
$modbus = fopen($port_name, 'rb+');
$message = [0x01, 0x03, 0, 18];
$message = pack($modbus_message_pack, ...$message);
$message .= crc16($message);
fwrite($modbus, $message);
$buffer = null;
do {
usleep($buffer ? 5000 : 100000);
$buffer .= fread($modbus, 1);
} while (!feof($modbus));
if (crc16(substr($buffer, 0, -2)) != substr($buffer, -2)) {
echo __LINE__, PHP_EOL;
echo bin2hex($buffer), PHP_EOL;
echo bin2hex(substr($buffer, 0, -2)), PHP_EOL;
echo bin2hex(substr($buffer, -2)), PHP_EOL;
die();
}
$response = unpack($modbus_response_unpack, $buffer);
//print_r($response);
$data_scale = [
'total_energy' => 1/100, // daWh / 100 => kWh
'export_energy' => 1/100, // daWh / 100 => kWh
'import_energy' => 1/100, // daWh / 100 => kWh
'voltage' => 1/10, // dV / 10 => V
'current' => 1/100, // cA / 100 => A
'active_power' => 1, // W
'reactive_power' => 1, // VA
'power_factor' => 1/1000,
'frecuency' => 1/100, // cHz / 100 => Hz
//'comm_addr' => 1,
//'comm_baud' => 1, // [1 => 9600, 2 => 4800, 3 => 2400, 4 => 1200]
//'trip_energy' => 1/100, // daWh / 100 => kWh
];
$data = [];
foreach ($data_scale as $key => $scale) {
$data[$key] = isset($response[$key]) ? $response[$key] * $scale : null;
}
print_r($data);
echo "\nok";
fclose($modbus);
<?php
function set_win_port_attr ($port_name = 'com1:', $port_attr = ['baud' => 9600, 'data' => 8, 'stop' => 1, 'parity' => 'n']) {
$mode = 'mode ' . $port_name;
foreach ($port_attr as $attr => $value) {
$mode .= ' ' . $attr . '=' . $value;
}
exec($mode, $output, $return);
return $return;
}
function crc16 ($data) {
$crc = 0xFFFF;
foreach (unpack('C*', $data) as $byte) {
$crc ^= $byte;
for ($j = 8; $j; $j--) {
$crc = ($crc >> 1) ^ (($crc & 0x0001) * 0xA001);
}
}
return pack('v1', $crc);
}
$port_name = 'COM3:';
$port_attr = ['baud' => 9600, 'data' => 8, 'stop' => 1, 'parity' => 'n', 'xon' => 'off'];
set_win_port_attr($port_name, $port_attr);
$modbus = fopen($port_name, 'rb+');
//| 0000h-0001h | total energy | 1/100 kWh | unsigned dword | R/W |
$message = [0x01, 0x10, 0x0000, 2, 4, 0];
$message = pack('C1C1n1n1C1N1', ...$message);
$message .= crc16($message);
fwrite($modbus, $message);
fclose($modbus);
@DonGould
Copy link

Have you played with the wifi version of this? I'm keen to know if I can talk to it via wifi?

@alphp
Copy link
Author

alphp commented May 11, 2020

I have only tried the serial version of modbus. I do not know if in the wifi version (modbus over ip) the protocol is the same, although it sounds reasonable that it is so.

@antoni1970
Copy link

antoni1970 commented Dec 21, 2020

Thx, you help me with first picture of Modbus addresses to communicate this energy meter to my scada system.
Edit: I cannot change Modbus ID adress - I tried sw QmodMaster... unsuccessfully, some other idea for another SW ?

@nikkiman
Copy link

nikkiman commented Dec 25, 2020

Thx, you help me with first picture of Modbus addresses to communicate this energy meter to my scada system.
Edit: I cannot change Modbus ID adress - I tried sw QmodMaster... unsuccessfully, some other idea for another SW ?

Try this to change slave ID https://www.domoticz.com/wiki/Python_-_Read-out_of_DDS238_kWh-meter_and_upload_to_Domoticz_and_to_PVOutput#More_than_1_kWh-meter_type_DDS238-1ZN_or_other_kWH-meters_in_the_configuration

@antoni1970
Copy link

Thx, but I have already managed to get the SW with which I have already changed the ID on both electricity meters 👍

@analystcmyk
Copy link

FYI There is a variant DDS238-2 ZN/SR that can switch the output by writing at register 0x001A:

//Type DDS238-2 ZN/SR
//0x001A register = relay  
//0000 = off, 0001 = on
//id func reg  datanr len data crc   
//00 10   001A 0001   02  0000 A9FA = relay on
//00 10   001A 0001   02  0001 683A = relay off

echo "\nswitch off\n";
  $message = [0x00, 0x10, 0x001A, 0x0001, 0x02, 0];
  $message = pack('C1C1n1n1C1n1', ...$message);
  $message .= crc16($message);
  echo bin2hex($message);
  fwrite($modbus, $message);

sleep(3);

echo "\nswitch on\n";
  $message = [0x00, 0x10, 0x001A, 0x0001, 0x02, 1];
  $message = pack('C1C1n1n1C1n1', ...$message);
  $message .= crc16($message);
  echo bin2hex($message);
  fwrite($modbus, $message);

@alphp
Copy link
Author

alphp commented Apr 6, 2021

@analystcmyk

FYI There is a variant DDS238-2 ZN/SR that can switch the output by writing at register 0x001A:

Thanks for the information, updated table of records.

@PepeLuis1959
Copy link

Hola,
Estoy buscando un programa para ejecutarlo en Arduino mega.
Dispongo de un módulo max485.
Mis conocimientos son bastante limitados.
Si me podéis hechar una mano os lo agradecería.
He probado algunos ejemplos sin éxito.

@PepeLuis1959
Copy link

El meditor es de dds238-2 zn.
Gracias

@alphp
Copy link
Author

alphp commented Apr 10, 2021

@PepeLuis1959
En parte mi idea era realizar un dataloger con Arduino y o bien descargar datos mediante eth o bien que se suban los datos cada tanto a una web.
Por cosas de la vida tengo el proyecto aparcado y tal vez cuando lo retome lo haga mediante una Rasberry o similar ya que además de poder reutilizar el código en PHP que tengo tendría el LAMP incluido para la página de análisis de consumo. Por no decir que podría tener mi propia centralita de VoIP.

@naufaaal
Copy link

i want to ask, is it possible to reset the total energy using modbus serial in arduino? thankyou

@alphp
Copy link
Author

alphp commented Jun 25, 2022

https://gist.github.com/alphp/95e1efe916c0dd6df7156f43dd521d53#note-1

Total, export and import energy counters can erased writing 0 in total energy registers.

@naufaaal
Copy link

thankyou, it's worked on my arduino

@florentbr
Copy link

I tested the DDS238-2 ZN/S with the current clamp. The refresh rate is quite slow (1-2 seconds)
and I noticed that the active power is sometimes reported with the wrong sign.
This issue can be reproduced with a resistive load backward though the current clamp:

Read   Active Power (address 0x000Eh)
-----  ------------------------------
1        0    // correct
2        0    // correct
// 40 Watts resistive reversed load is turned on .
3       31    // wrong, should be -31, not 31
4      -40    // correct
5      -40    // correct

This meter is therefore unreliable and cannot be used in a regulated feedback loop.

@alphp
Copy link
Author

alphp commented Jan 30, 2023

Indeed, it is not a reliable instrument for regulation.
But performing a reading/minute, valid values are obtained as a record.

@lagabie
Copy link

lagabie commented Jun 26, 2023

Hello, I am also using the DDS238 ZN/SR. I am using QModMaster to communicate with the meter and a ethernet/modbus convertor DR302.

When I got the meter, ID=1. By using the read holding registers 0x03, I could read all the registers. And writing 0 or 1 to register 0x001A, I could switch the relay.

Then I wrote 0x2001 to register 0x0015, I lost all of the modbus communication. On the display is ID=000. The meter still works, it measures how it should do, but no communication. I have ofcourse also used the ID=0, the broadcast, but still no reply of the meter.

In QModMaster I have to increase the address with 1, so If I want to write to register 0x0015 I have to enter 0x0016. I check in the program the output and then it is correct.

Does anyone has seen this before? Can it be a problem with the QModMaster or does anyone how to set the address back to 1?

At the moment I don't have a RS232/RS485 convertor, it is ordered. Then I can use an Arduino to sent the strings and have every bit under control.

Greetings, Frans

@MadsHHLund
Copy link

Hello -
by accident, I also wrote 0 out in register 0x0015.
Now the gauge ID =0 and I can't fix it to anything else.
Is the recycle bin the only solution?

@lagabie
Copy link

lagabie commented Nov 1, 2023 via email

@MadsHHLund
Copy link

Okay recycle Bin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment