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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.