Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Hiking DDS238-2 ZN/S energy meter

Hiking DDS238-2 ZN/S energy meter

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

This comment has been minimized.

Copy link

@DonGould DonGould commented May 11, 2020

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

@alphp

This comment has been minimized.

Copy link
Owner Author

@alphp 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

This comment has been minimized.

Copy link

@antoni1970 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

This comment has been minimized.

Copy link

@nikkiman 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

This comment has been minimized.

Copy link

@antoni1970 antoni1970 commented Dec 26, 2020

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

@analystcmyk

This comment has been minimized.

Copy link

@analystcmyk analystcmyk commented Apr 5, 2021

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

This comment has been minimized.

Copy link
Owner Author

@alphp 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

This comment has been minimized.

Copy link

@PepeLuis1959 PepeLuis1959 commented Apr 10, 2021

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

This comment has been minimized.

Copy link

@PepeLuis1959 PepeLuis1959 commented Apr 10, 2021

El meditor es de dds238-2 zn.
Gracias

@alphp

This comment has been minimized.

Copy link
Owner Author

@alphp 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.

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