Skip to content

Instantly share code, notes, and snippets.

@alphp
Last active November 12, 2024 23:14
Show Gist options
  • 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);
@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.

@solverar
Copy link

Que tal Fernando, espero que te encuentres muy bien.
Tu post me ha ayudado mucho a identificar los datos en los registros del dispositivo.
Ahora mismo te escribo para probar suerte y preguntar si hay forma de obtener el DL/T645 ID usando MODBUS.
Veo que en el dispositivo este ID se encuentra impreso sobre el medidor y me sería muy útil poder obtenerlo para identificar distintos medidores.
Espero puedas ayudarme :) De antemano gracias

@alphp
Copy link
Author

alphp commented Nov 12, 2024

Tengo bastante aparcado el tema.
Pero si se tiene un volcado en binario de todos los registros y se sabe la información que se espera encontrar es posible identificar el registro si esa información está almacenada en algún registro. Obviamente debería ser un registro o bien marcado como reservado (básicamente es un registro cuya información no he podido identificar) o un registro nuevo (no explorado).
Un saludo

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