correctly unpack unsupported data_id
[zc] / unpack.pl
1 #!/usr/bin/perl
2 use warnings;
3 use strict;
4
5 use Data::Dump qw(dump);
6
7 my $debug = $ENV{DEBUG};
8
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
30 ];
31  
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
53 ];
54
55 sub modbus_crc16 {
56         my ($msg) = @_;
57
58         my $crcl  = 0xFF;
59         my $crch  = 0xFF;
60         my @bytes = split(//, $msg);
61         for (@bytes) {
62                 my $data = unpack('C*', $_);
63                 my $crcIdx = $crcl ^ $data;
64                 $crcl = $crch ^ &CRC_HI->[$crcIdx];
65                 $crch = &CRC_LO->[$crcIdx];
66         }
67  
68         # Pack the trailer - CRC has low byte first
69         my $trlr = pack('CC', $crcl, $crch);
70
71         return $trlr;
72 }
73
74
75 my $protocol;
76 while(<DATA>) {
77         chomp;
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+.*$//;
82         $protocol->{$id} = {
83                 description => $a[1],
84                 pack_fmt => $pack_fmt,
85                 r_w => $a[3],
86                 range => $a[4],
87                 default => $a[5],
88                 remark => $a[6],
89                 line => [ @a ],
90         };
91 }
92
93 warn "## protocol = ",dump( $protocol ) if $debug;
94
95 my $pkg_nr = 0;
96
97 sub protocol_decode {
98         my ( $up_down, $hex ) = @_;
99
100 warn "up/down: $up_down hex = ",dump($hex);
101
102 my $bin = join('', map { chr(hex($_)) } split(/\s+/,$hex));
103
104 warn "bin = ", dump($bin);
105
106 if ( $debug ) {
107         open(my $dump, '>', "/dev/shm/zc.$pkg_nr.$up_down");
108         print $dump $bin;
109         close($dump);
110         $pkg_nr++;
111 }
112
113 my $cksum = substr($bin, -2, 2);
114
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";
118         } else {
119                 warn "CRC OK ", unpack('H*',$crc), "\n";
120         }
121 }
122
123 my ( $header, $ver, $function_code, $len ) = unpack("CCCv", $bin);
124
125 my $data = substr($bin,5,-2);
126
127 warn "header = $header ver = $ver function_code = $function_code len = $len == ",length($data), " data = ",dump($data), " cksum = ", unpack('H*',$cksum);
128
129 warn "header corrupt: $header" if $header != 0x5a;
130 warn "invalid length $len got ",length($data) if length($data) != $len;
131
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',
137 };
138
139 my $function_code_downstream = {
140 0x03 => 'Downstream read parameter frame',
141 0x06 => 'Downstream write parameter frame',
142 };
143
144 print "Function code: $function_code ", 
145         $up_down eq 'up' ? $function_code_upstream->{ $function_code } : $function_code_downstream->{ $function_code }, "\n";
146
147
148
149
150 while ( $data ) {
151         my $hex = unpack('H*', $data);
152         $hex =~ s/(..)/$1 /g;
153         warn "XXX data = $hex\n";
154
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);
161                 next;
162         }
163         my $pack_fmt = $protocol->{$data_id}->{pack_fmt} || die "can't find pack_fmt for data_id $data_id";
164
165         my $data_range;
166
167         if ( $data_id == 0x00 ) {
168                 # sequence number
169
170                 $data_range = substr($data,2,4);
171                 $data = substr($data,6);
172
173         } elsif ( $up_down eq 'down' && $function_code == 0x03 ) {
174                 # type A
175
176                 $data_range = substr($data,0,1);
177                 $data = substr($data,1);
178
179         } elsif ( $up_down eq 'up'   && ( $function_code == 0x07 || $function_code == 0x08 || $function_code == 0x03 ) ) {
180                 # type B
181
182                 my $data_len = unpack('C', substr($data,1,1));
183                 $data_range = substr($data,2, $data_len);
184
185                 $data = substr($data,2 + $data_len);
186
187         } elsif ( $up_down eq 'down' && $function_code == 0x06 ) {
188                 # type B
189
190                 my $data_len = unpack('C', substr($data,1,1));
191                 $data_range = substr($data,2, $data_len);
192
193                 $data = substr($data,2 + $data_len);
194
195         } elsif ( $up_down eq 'up' && $function_code == 0x06 ) {
196                 # type C
197
198                 # FIXME check first byte?
199                 my $data_range = substr($data,1,1);
200
201                 $data = substr($data,2);
202
203         } else {
204                 warn "ERROR unknown function_code = $function_code";
205         }
206
207         my $v = unpack($pack_fmt, $data_range);
208
209         if ( $data_id == 0x0c ) {
210                 $v = $v / 100;
211         } elsif ( $data_id == 0x0d ) {
212                 $v = $v / 100;
213         }
214
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";
217 }
218
219
220 } # protocol_decode
221
222 my $raw;
223 my ( $timestamp, $sensor, $imei, $up_down );
224 my $stat;
225 while(<>) {
226         chomp;
227
228         if ( m{^(\d\d\d\d-\d\d-\d\d\s\d\d:\d\d:\d\d).*Inclinometer/([^/]+)/([^/]+)/(\w+)} ) {
229                 if ( $raw ) {
230                         $raw =~ s{^\s+}{}; # strip leading spaces
231                         print "## $timestamp $sensor $imei $up_down $raw\n";
232                         protocol_decode( $up_down, $raw );
233                         $raw = '';
234                 }
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
239                 s/^\s+/ /;
240                 $raw .= $_;
241                 warn "++ $_\n";
242         } else {
243                 warn "IGNORE: $_\n";
244         }
245
246 }
247
248 if ( $raw ) {
249         $raw =~ s{^\s+}{}; # strip leading spaces
250         print "## $timestamp $sensor $imei $up_down $raw\n";
251         protocol_decode( $up_down, $raw );
252 }
253
254 print "stat = ",dump($stat), "\n";
255
256 # 6. Protocol Format 
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 
259
260 __DATA__
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. 
272
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 
287
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 
295