5 use Data::Dump qw(dump);
7 my $debug = $ENV{DEBUG};
9 # Table of CRC values for high order byte
10 use constant CRC_HI => [
11 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
12 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
13 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1,
14 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
15 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
16 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
17 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1,
18 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
19 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
20 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
21 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
22 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
23 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
24 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
25 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
26 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
27 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
28 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
29 0x00, 0xC1, 0x81, 0x40
32 # Table of CRC values for low order byte
33 use constant CRC_LO => [
34 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5,
35 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B,
36 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE,
37 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6,
38 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
39 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
40 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8,
41 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C,
42 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21,
43 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
44 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A,
45 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
46 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7,
47 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51,
48 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
49 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98,
50 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D,
51 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
52 0x41, 0x81, 0x80, 0x40
60 my @bytes = split(//, $msg);
62 my $data = unpack('C*', $_);
63 my $crcIdx = $crcl ^ $data;
64 $crcl = $crch ^ &CRC_HI->[$crcIdx];
65 $crch = &CRC_LO->[$crcIdx];
68 # Pack the trailer - CRC has low byte first
69 my $trlr = pack('CC', $crcl, $crch);
78 next if m{^\s*$}; # skip empty lines
79 my @a = split(/\s*\t\s*/, $_,7);
80 my $id = hex( $a[0] );
81 my $pack_fmt = $a[2]; $pack_fmt =~ s/\s+.*$//;
84 pack_fmt => $pack_fmt,
93 warn "## protocol = ",dump( $protocol ) if $debug;
98 my ( $up_down, $hex ) = @_;
100 warn "up/down: $up_down hex = ",dump($hex);
102 my $bin = join('', map { chr(hex($_)) } split(/\s+/,$hex));
104 warn "bin = ", dump($bin);
107 open(my $dump, '>', "/dev/shm/zc.$pkg_nr.$up_down");
113 my $cksum = substr($bin, -2, 2);
115 if ( my $crc = modbus_crc16( substr($bin,0,-2) ) ) {
116 if ( $crc ne $cksum ) {
117 warn "CRC error, got ",unpack('H*',$crc), " expected ", unpack('H*',$cksum), "\n";
119 warn "CRC OK ", unpack('H*',$crc), "\n";
123 my ( $header, $ver, $function_code, $len ) = unpack("CCCv", $bin);
125 my $data = substr($bin,5,-2);
127 warn "header = $header ver = $ver function_code = $function_code len = $len == ",length($data), " data = ",dump($data), " cksum = ", unpack('H*',$cksum);
129 warn "header corrupt: $header" if $header != 0x5a;
130 warn "invalid length $len got ",length($data) if length($data) != $len;
132 my $function_code_upstream = {
133 0x07 => 'Upstream Heartbeat Frame',
134 0x08 => 'Upstream alarm frame',
135 0x03 => 'Upstream Read Parameter Frame',
136 0x06 => 'Upstream return write parameter frame',
139 my $function_code_downstream = {
140 0x03 => 'Downstream read parameter frame',
141 0x06 => 'Downstream write parameter frame',
144 print "Function code: $function_code ",
145 $up_down eq 'up' ? $function_code_upstream->{ $function_code } : $function_code_downstream->{ $function_code }, "\n";
151 my $hex = unpack('H*', $data);
152 $hex =~ s/(..)/$1 /g;
153 warn "XXX data = $hex\n";
155 my $data_id = unpack( 'C', substr($data,0,1) );
156 my $data_id_desc = $protocol->{$data_id}->{description};
157 if ( ! $data_id_desc ) {
158 my $len = unpack('C', substr($data,1,1));
159 printf "ERROR: no description for data_id %d 0x%2x len %d SKIPPING!\n", $data_id, $data_id, $len;
160 $data = substr($data,2 + $len);
163 my $pack_fmt = $protocol->{$data_id}->{pack_fmt} || die "can't find pack_fmt for data_id $data_id";
167 if ( $data_id == 0x00 ) {
170 $data_range = substr($data,2,4);
171 $data = substr($data,6);
173 } elsif ( $up_down eq 'down' && $function_code == 0x03 ) {
176 $data_range = substr($data,0,1);
177 $data = substr($data,1);
179 } elsif ( $up_down eq 'up' && ( $function_code == 0x07 || $function_code == 0x08 || $function_code == 0x03 ) ) {
182 my $data_len = unpack('C', substr($data,1,1));
183 $data_range = substr($data,2, $data_len);
185 $data = substr($data,2 + $data_len);
187 } elsif ( $up_down eq 'down' && $function_code == 0x06 ) {
190 my $data_len = unpack('C', substr($data,1,1));
191 $data_range = substr($data,2, $data_len);
193 $data = substr($data,2 + $data_len);
195 } elsif ( $up_down eq 'up' && $function_code == 0x06 ) {
198 # FIXME check first byte?
199 my $data_range = substr($data,1,1);
201 $data = substr($data,2);
204 warn "ERROR unknown function_code = $function_code";
207 my $v = unpack($pack_fmt, $data_range);
209 if ( $data_id == 0x0c ) {
211 } elsif ( $data_id == 0x0d ) {
215 my $description = $protocol->{$data_id}->{description} || die "can't find description for data_id $data_id";
216 print "$up_down | $data_id | $description | v=$v | hex:", unpack('H*', $data_range), " fmt:$pack_fmt | range:",$protocol->{$data_id}->{range}, " | remark:", $protocol->{$data_id}->{remark},"\n";
223 my ( $timestamp, $sensor, $imei, $up_down );
228 if ( m{^(\d\d\d\d-\d\d-\d\d\s\d\d:\d\d:\d\d).*Inclinometer/([^/]+)/([^/]+)/(\w+)} ) {
230 $raw =~ s{^\s+}{}; # strip leading spaces
231 print "## $timestamp $sensor $imei $up_down $raw\n";
232 protocol_decode( $up_down, $raw );
235 ( $timestamp, $sensor, $imei, $up_down ) = ( $1, $2, $3, $4 );
236 $stat->{$sensor}->{$imei}->{$up_down}++;
237 } elsif ( m{^\s+} ) {
238 s/\s\s\S\S\S+//g; # remove ascii
249 $raw =~ s{^\s+}{}; # strip leading spaces
250 print "## $timestamp $sensor $imei $up_down $raw\n";
251 protocol_decode( $up_down, $raw );
254 print "stat = ",dump($stat), "\n";
257 # Valid data ID and parameter range supported by the product
258 # DATA ID ID Description Data Type( Data Length) R/W Range Default Remark
261 0x00 Seq # l DWord(4) R / 0 The platform can carry the ID when the platform downstream reads and sets the device parameters, and the device returns the same data. Please refer to the example for use.Each seq # refer to one command and its response.
262 0x01 PN l DWord(4) R / / /
263 0x02 Model C Byte(1) R 32 32 Inner number:32
264 0x03 X axis angle f Float(4) R ‐90°~90° / X axis angle
265 0x04 Y axis angle f Float(4) R ‐90°~90° / Y axis angle
266 0x09 X axis relative angle f Float(4) R ‐90°~90° 0 Return the X angle value according to the set relative zero
267 0x0A Y axis relative angle f Float(4) R ‐90°~90° 0 Return the Y angle value according to the set relative zero
268 0x0C Sensor temperature s Word(2) R ‐32768~32767 / signed,sensor temperature =Data/100, unit ℃
269 0x0D Power source voltage S Word(2) R 0~65535 / Voltage= Data/100, unit V
270 0x11 Arming/disarming C Byte(1) R/W 0~255 1 0 means disarming, non‐zero means arming
271 0x12 Alarm delay time C Byte(1) R/W 3~255 20 The unit is 0.1 second, which means that the product responds to the alarm only after the alarm has exceeded the alarm angle for a certain period of time.
273 0x13 Restore factory setting C Byte(1) R/W 0~255 0 0: Do nothing Non‐zero: Restore the non‐network‐related parameters of the sensor.
274 0x14 Server IP&port CCCCs 4*Byte(1)+Word(2) R/W / CTIOT:117.60.157.137,5683 MQTT:0.0.0.0,0 The server address should be IP, not the domain name; using the domain name may cause the connection server to be unstable and cause data loss.
275 0x17 Signal strength C Byte(1) R 10~34 / A larger value indicates a stronger signal
276 0x18 Sensor operating mode C Byte(1) R/W 0 0 The sensor can only work in absolute measurement mode
277 0x19 Alarm axis C Byte(1) R 0~3 / 0: no alarm; 1: X‐axis alarm 2: Y axis alarm; 3: X/Y axis alarm at the same time
278 0x1A SIM card ID Q QWord(8) R 0~18446744073709551615 / Take the first 19 digits, the last digit is discarded
279 0x21 Heartbeat interval l DWord(4) R/W 60~131071 86400 Interval at which the device periodically uploads data to the server
280 0x22 IMEI number of the device Q QWord(8) R 0~18446744073709551615 / Refers to the IMEI of the NB‐IOT network module in the product.
281 0x23 Backup server IP&port CCCCs 4*Byte(1)+Word(2) R/W / CTIOT:117.60.157.137,5683 MQTT:0.0.0.0,0 /
282 0x24 Backup server enable C Byte(1) R/W 0~255 0 0 means off, non‐zero means on
283 0x33 DNS IP address CCCC 4*Byte(1) R/W / 208.67.222.222 /
284 0x34 Domain name and port a* 64*Byte(1) R/W / mqtt.zc‐sensor.com,1883 Supports CTIOT and MQTT protocols. Priority IP in the case of IP (ID number 0x14); domain name and port should be separated with comma, length <=64
285 0x35 MQTT‐ClientID a* 32*Byte(1) R/W / IMEI number of the device Length <=32, subject to MQTT related specifications
286 0x36 MQTT‐Username a* 32*Byte(1) R/W / empty Length <=32, subject to MQTT related specifications
288 0x37 MQTT‐Password a* 32*Byte(1) R/W / empty Length <=32, subject to MQTT related specifications
289 0x38 MQTT‐published topic name a* 128*Byte(1) R/W / Inclinometer/ZCT330Mx_SWP_N_YK/IMEI/up Length <=128, subject to MQTT related specifications
290 0x39 MQTT‐subscribed topic name a* 128*Byte(1) R/W / Inclinometer/ZCT330Mx_SWP_N_YK/IMEI/down Length <=128, subject to MQTT related specifications
291 0x3A Set relative zero command C Byte(1) R/W 0~255 0 0: absolute angle mode 1: Set the current position to zero, relative angle mode (0x09, 0x0A content will be set to the current angle), Other values are invalid.
292 0x3B Backup server domain name and port a* 64*Byte(1) R/W / mqtt.zc‐sensor.com,1883 Supports CTIOT and MQTT protocols. In the case of backup IP, the IP is preferentially backed up; the domain name and port are distinguished by commas, and the length is <=64.
293 0x3D Protocol type C Byte(1) R/W 0~255 CTIOT 0:CTIOT 1:MQTT Other values are invalid.
294 0x44 Alarm angle f Float(4) R/W ‐90°~90° 3° X/Y axis alarm angle is consistent