Skip to content

Instantly share code, notes, and snippets.

@nikkiman
Forked from alphp/DDS238-2 ZN-S Modbus.md
Created December 25, 2020 21:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nikkiman/6b7b2b616dc0d7a12a912b74c986beba to your computer and use it in GitHub Desktop.
Save nikkiman/6b7b2b616dc0d7a12a912b74c986beba to your computer and use it in GitHub Desktop.
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);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment