fix formatting, divide by 100 if needed
[zc] / unpack.pl
1 #!/usr/bin/perl
2 use warnings;
3 use strict;
4
5 use Data::Dump qw(dump);
6
7 # Table of CRC values for high order byte
8 use constant CRC_HI => [
9     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
10     0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
11     0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1,
12     0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
13     0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
14     0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
15     0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1,
16     0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
17     0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
18     0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
19     0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
20     0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
21     0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
22     0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
23     0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
24     0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
25     0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
26     0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
27     0x00, 0xC1, 0x81, 0x40
28 ];
29  
30 # Table of CRC values for low order byte
31 use constant CRC_LO => [
32     0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5,
33     0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B,
34     0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE,
35     0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6,
36     0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
37     0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
38     0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8,
39     0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C,
40     0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21,
41     0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
42     0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A,
43     0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
44     0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7,
45     0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51,
46     0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
47     0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98,
48     0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D,
49     0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
50     0x41, 0x81, 0x80, 0x40
51 ];
52
53 sub modbus_crc16 {
54         my ($msg) = @_;
55
56         my $crcl  = 0xFF;
57         my $crch  = 0xFF;
58         my @bytes = split(//, $msg);
59         for (@bytes) {
60                 my $data = unpack('C*', $_);
61                 my $crcIdx = $crcl ^ $data;
62                 $crcl = $crch ^ &CRC_HI->[$crcIdx];
63                 $crch = &CRC_LO->[$crcIdx];
64         }
65  
66         # Pack the trailer - CRC has low byte first
67         my $trlr = pack('CC', $crcl, $crch);
68
69         return $trlr;
70 }
71
72
73 my $protocol;
74 while(<DATA>) {
75         chomp;
76         my @a = split(/\s*\t\s*/, $_,7);
77         my $id = hex( $a[0] );
78         my $pack_fmt = $a[2]; $pack_fmt =~ s/\s+.*$//;
79         $protocol->{$id} = {
80                 description => $a[1],
81                 pack_fmt => $pack_fmt,
82                 r_w => $a[3],
83                 range => $a[4],
84                 default => $a[5],
85                 remark => $a[6],
86                 line => [ @a ],
87         };
88 }
89
90 warn "## protocol = ",dump( $protocol );
91
92
93 my @raw;
94
95 #Publish/at-most-once,retain Inclinometer/ZCT330Ex_SWP_N_YK/869858035167585/up
96 push @raw, qq{
97   5a 0c 08 29 00 01 04 55 79 fb 71 02 01 10 03 04  Z..)...Uy.q.....
98   00 00 df c1 04 04 75 93 6c c1 0c 02 49 0a 0d 02  ......u.l...I...
99   61 01 11 01 01 17 01 12 18 01 01 19 01 03 48 5d  a.............H]
100 };
101
102 #Publish/at-most-once,retain Inclinometer/ZCT330Ex_SWP_N_YK/869858031634109/up
103 push @raw, qq{
104   5a 0c 07 29 00 01 04 00 00 00 00 02 01 10 03 04  Z..)............
105   df 4f ad 3f 04 04 d1 22 4b c0 0c 02 c2 14 0d 02  .O.?..."K.......
106   41 01 11 01 01 17 01 16 18 01 01 19 01 00 a8 b9  A...............
107 };
108 #Publish/at-most-once,retain Inclinometer/ZCT330Ex_SWP_N_YK/864823041482779/up
109 push @raw, qq|
110   5a 0c 08 29 00 01 04 00 00 00 00 02 01 10 03 04  Z..)............
111   db f9 3e 3f 04 04 b2 9d 6f be 0c 02 b7 10 0d 02  ..>?....o.......
112   48 01 11 01 01 17 01 1a 18 01 01 19 01 03 7d 2c  H.............},
113 |;
114 #Publish/at-most-once,retain Inclinometer/ZCT330Ex_SWP_N_YK/864823041482498/up
115 push @raw, qq{
116   5a 0c 08 29 00 01 04 e3 b1 a0 77 02 01 10 03 04  Z..)......w.....
117   02 2b 87 be 04 04 27 31 e8 c1 0c 02 bd 0b 0d 02  .+....'1........
118   45 01 11 01 01 17 01 16 18 01 01 19 01 02 2a 69  E.............*i
119 };
120
121 my $raw_nr = shift @ARGV || 0;
122 my $raw = $raw[$raw_nr];
123 $raw =~ s/  \S{16}\n//gs;
124 $raw =~ s/^\s+//;
125 my $up_down = 'up';
126
127 warn "up/down: $up_down raw = ",dump($raw);
128
129 my $bin = join('', map { chr(hex($_)) } split(/\s+/,$raw));
130
131 warn "bin = ", dump($bin);
132
133 my $cksum = substr($bin, -2, 2);
134
135 if ( my $crc = modbus_crc16( substr($bin,0,-2) ) ) {
136         if ( $crc ne $cksum ) {
137                 warn "CRC error, got ",unpack('H*',$crc), " expected ", unpack('H*',$cksum), "\n";
138         } else {
139                 warn "CRC OK ", unpack('H*',$crc), "\n";
140         }
141 }
142
143 my ( $header, $ver, $function_code, $len ) = unpack("CCCv", $bin);
144
145 my $data = substr($bin,5,-2);
146
147 warn "header = $header ver = $ver function_code = $function_code len = $len == ",length($data), " data = ",dump($data), " cksum = ", unpack('H*',$cksum);
148
149 warn "header corrupt: $header" if $header != 0x5a;
150 warn "invalid length $len got ",length($data) if length($data) != $len;
151
152 my $function_code_upstream = {
153 0x07 => 'Upstream Heartbeat Frame',
154 0x08 => 'Upstream alarm frame',
155 0x03 => 'Upstream Read Parameter Frame',
156 0x06 => 'Upstream return write parameter frame',
157 };
158
159 my $function_code_downstream = {
160 0x03 => 'Downstream read parameter frame',
161 0x06 => 'Downstream write parameter frame',
162 };
163
164 print "Function code: $function_code ", 
165         $up_down eq 'up' ? $function_code_upstream->{ $function_code } : $function_code_downstream->{ $function_code }, "\n";
166
167
168
169
170 while ( $data ) {
171         my $hex = unpack('H*', $data);
172         $hex =~ s/(..)/$1 /g;
173         warn "XXX data = $hex\n";
174
175         my $data_id = unpack( 'C', substr($data,0,1) );
176         my $data_id_desc = $protocol->{$data_id}->{description} || die "can't find description for data_id $data_id";
177         my $pack_fmt = $protocol->{$data_id}->{pack_fmt} || die "can't find pack_fmt for data_id $data_id";
178
179         my $data_range;
180
181         if ( $data_id == 0x00 ) {
182                 warn "FIXME seq number?";
183
184         } elsif ( $up_down eq 'down' && $function_code == 0x03 ) {
185                 # type A
186
187                 $data_range = substr($data,0,1);
188                 $data = substr($data,1);
189
190         } elsif ( $up_down eq 'up'   && ( $function_code == 0x07 || $function_code == 0x08 || $function_code == 0x03 ) ) {
191                 # type B
192
193                 my $data_len = unpack('C', substr($data,1,1));
194                 $data_range = substr($data,2, $data_len);
195
196                 $data = substr($data,2 + $data_len);
197
198         } elsif ( $up_down eq 'down' && $function_code == 0x06 ) {
199                 # type B
200
201                 my $data_len = unpack('C', substr($data,1,1));
202                 $data_range = substr($data,2, $data_len);
203
204                 $data = substr($data,2 + $data_len);
205
206         } elsif ( $up_down eq 'up' && $function_code == 0x06 ) {
207                 # type C
208
209                 # FIXME check first byte?
210                 my $data_range = substr($data,1,1);
211
212                 $data = substr($data,2);
213
214         } else {
215                 warn "ERROR unknown function_code = $function_code";
216         }
217
218         my $v = unpack($pack_fmt, $data_range);
219
220         if ( $data_id == 0x0c ) {
221                 $v = $v / 100;
222         } elsif ( $data_id == 0x0d ) {
223                 $v = $v / 100;
224         }
225
226         my $description = $protocol->{$data_id}->{description} || die "can't find description for data_id $data_id";
227         print "$up_down | $data_id | $description | hex:", unpack('H*', $data_range), " fmt:$pack_fmt [v=$v] range:",$protocol->{$data_id}->{range}, " remark:", $protocol->{$data_id}->{remark},"\n";
228 }
229
230 # 6. Protocol Format 
231 # Valid data ID and parameter range supported by the product 
232 # DATA ID       ID Description  Data Type( Data Length)       R/W     Range   Default         Remark 
233
234 __DATA__
235 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. 
236 0x01    PN                              l DWord(4)      R       /                /      / 
237 0x02    Model                           C Byte(1)       R       32              32      Inner number:32 
238 0x03    X axis angle                    f Float(4)      R        ‐90°~90°   /       X axis angle 
239 0x04    Y axis angle                    f Float(4)      R       ‐90°~90°    /       Y axis angle 
240 0x09    X axis relative angle           f Float(4)      R       ‐90°~90°    0       Return the X angle value according to the set relative zero 
241 0x0A    Y axis relative angle           f Float(4)      R       ‐90°~90°    0       Return the Y angle value according to the set relative zero 
242 0x0C    Sensor temperature              s Word(2)       R       ‐32768~32767  /       signed,sensor temperature =Data/100, unit ℃ 
243 0x0D    Power source voltage            S Word(2)       R       0~65535         /       Voltage= Data/100, unit V 
244 0x11    Arming/disarming                C Byte(1)       R/W     0~255           1       0 means disarming, non‐zero means arming 
245 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. 
246
247 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. 
248 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.
249 0x17    Signal strength                 C Byte(1)       R       10~34           /       A larger value indicates a stronger signal 
250 0x18    Sensor operating mode           C Byte(1)       R/W     0               0       The sensor can only work in absolute measurement mode 
251 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 
252 0x1A    SIM card ID                     Q QWord(8)      R       0~18446744073709551615  /       Take the first 19 digits, the last digit is discarded 
253 0x21    Heartbeat interval              l DWord(4)      R/W     60~131071       86400   Interval at which the device periodically uploads data to the server 
254 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. 
255 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        / 
256 0x24    Backup server enable            C Byte(1)       R/W     0~255   0       0 means off, non‐zero means on 
257 0x33    DNS IP address                  CCCC 4*Byte(1)  R/W     /       208.67.222.222  / 
258 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 
259 0x35    MQTT‐ClientID                         a* 32*Byte(1)   R/W      /      IMEI number of the device       Length <=32, subject to MQTT related specifications 
260 0x36    MQTT‐Username                         a* 32*Byte(1)   R/W     /       empty           Length <=32, subject to MQTT related specifications 
261
262 0x37    MQTT‐Password                         a* 32*Byte(1)   R/W     /       empty           Length <=32, subject to MQTT related specifications 
263 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 
264 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 
265 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. 
266 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. 
267 0x3D    Protocol type                   C Byte(1)       R/W     0~255   CTIOT   0:CTIOT 1:MQTT Other values are invalid. 
268 0x44    Alarm angle                     f Float(4)      R/W     ‐90°~90°    3°     X/Y axis alarm angle is consistent 
269