simple mqtt client to listen to sensors
[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         my @a = split(/\s*\t\s*/, $_,7);
79         my $id = hex( $a[0] );
80         my $pack_fmt = $a[2]; $pack_fmt =~ s/\s+.*$//;
81         $protocol->{$id} = {
82                 description => $a[1],
83                 pack_fmt => $pack_fmt,
84                 r_w => $a[3],
85                 range => $a[4],
86                 default => $a[5],
87                 remark => $a[6],
88                 line => [ @a ],
89         };
90 }
91
92 warn "## protocol = ",dump( $protocol ) if $debug;
93
94
95 sub protocol_decode {
96         my ( $up_down, $raw ) = @_;
97
98 warn "up/down: $up_down raw = ",dump($raw);
99
100 my $bin = join('', map { chr(hex($_)) } split(/\s+/,$raw));
101
102 warn "bin = ", dump($bin);
103
104 my $cksum = substr($bin, -2, 2);
105
106 if ( my $crc = modbus_crc16( substr($bin,0,-2) ) ) {
107         if ( $crc ne $cksum ) {
108                 warn "CRC error, got ",unpack('H*',$crc), " expected ", unpack('H*',$cksum), "\n";
109         } else {
110                 warn "CRC OK ", unpack('H*',$crc), "\n";
111         }
112 }
113
114 my ( $header, $ver, $function_code, $len ) = unpack("CCCv", $bin);
115
116 my $data = substr($bin,5,-2);
117
118 warn "header = $header ver = $ver function_code = $function_code len = $len == ",length($data), " data = ",dump($data), " cksum = ", unpack('H*',$cksum);
119
120 warn "header corrupt: $header" if $header != 0x5a;
121 warn "invalid length $len got ",length($data) if length($data) != $len;
122
123 my $function_code_upstream = {
124 0x07 => 'Upstream Heartbeat Frame',
125 0x08 => 'Upstream alarm frame',
126 0x03 => 'Upstream Read Parameter Frame',
127 0x06 => 'Upstream return write parameter frame',
128 };
129
130 my $function_code_downstream = {
131 0x03 => 'Downstream read parameter frame',
132 0x06 => 'Downstream write parameter frame',
133 };
134
135 print "Function code: $function_code ", 
136         $up_down eq 'up' ? $function_code_upstream->{ $function_code } : $function_code_downstream->{ $function_code }, "\n";
137
138
139
140
141 while ( $data ) {
142         my $hex = unpack('H*', $data);
143         $hex =~ s/(..)/$1 /g;
144         warn "XXX data = $hex\n";
145
146         my $data_id = unpack( 'C', substr($data,0,1) );
147         my $data_id_desc = $protocol->{$data_id}->{description} || die "can't find description for data_id $data_id";
148         my $pack_fmt = $protocol->{$data_id}->{pack_fmt} || die "can't find pack_fmt for data_id $data_id";
149
150         my $data_range;
151
152         if ( $data_id == 0x00 ) {
153                 warn "FIXME seq number?";
154
155         } elsif ( $up_down eq 'down' && $function_code == 0x03 ) {
156                 # type A
157
158                 $data_range = substr($data,0,1);
159                 $data = substr($data,1);
160
161         } elsif ( $up_down eq 'up'   && ( $function_code == 0x07 || $function_code == 0x08 || $function_code == 0x03 ) ) {
162                 # type B
163
164                 my $data_len = unpack('C', substr($data,1,1));
165                 $data_range = substr($data,2, $data_len);
166
167                 $data = substr($data,2 + $data_len);
168
169         } elsif ( $up_down eq 'down' && $function_code == 0x06 ) {
170                 # type B
171
172                 my $data_len = unpack('C', substr($data,1,1));
173                 $data_range = substr($data,2, $data_len);
174
175                 $data = substr($data,2 + $data_len);
176
177         } elsif ( $up_down eq 'up' && $function_code == 0x06 ) {
178                 # type C
179
180                 # FIXME check first byte?
181                 my $data_range = substr($data,1,1);
182
183                 $data = substr($data,2);
184
185         } else {
186                 warn "ERROR unknown function_code = $function_code";
187         }
188
189         my $v = unpack($pack_fmt, $data_range);
190
191         if ( $data_id == 0x0c ) {
192                 $v = $v / 100;
193         } elsif ( $data_id == 0x0d ) {
194                 $v = $v / 100;
195         }
196
197         my $description = $protocol->{$data_id}->{description} || die "can't find description for data_id $data_id";
198         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";
199 }
200
201
202 } # protocol_decode
203
204 my $raw;
205 my ( $timestamp, $sensor, $imei, $up_down );
206 my $stat;
207 while(<>) {
208         chomp;
209
210         if ( m{^(\d\d\d\d-\d\d-\d\d\s\d\d:\d\d:\d\d).*Inclinometer/([^/]+)/([^/]+)/(\w+)} ) {
211                 if ( $raw ) {
212                         $raw =~ s{^\s+}{}; # strip leading spaces
213                         print "## $timestamp $sensor $imei $up_down $raw\n";
214                         protocol_decode( $up_down, $raw );
215                         $raw = '';
216                 }
217                 ( $timestamp, $sensor, $imei, $up_down ) = ( $1, $2, $3, $4 );
218                 $stat->{$sensor}->{$imei}->{$up_down}++;
219         } elsif ( m{^\s+} ) {
220                 s/\s\s\S\S\S+//g; # remove ascii
221                 s/^\s+/ /;
222                 $raw .= $_;
223                 warn "++ $_\n";
224         } else {
225                 warn "IGNORE: $_\n";
226         }
227
228 }
229
230 print "stat = ",dump($stat), "\n";
231
232 # 6. Protocol Format 
233 # Valid data ID and parameter range supported by the product 
234 # DATA ID       ID Description  Data Type( Data Length)       R/W     Range   Default         Remark 
235
236 __DATA__
237 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. 
238 0x01    PN                              l DWord(4)      R       /                /      / 
239 0x02    Model                           C Byte(1)       R       32              32      Inner number:32 
240 0x03    X axis angle                    f Float(4)      R        ‐90°~90°   /       X axis angle 
241 0x04    Y axis angle                    f Float(4)      R       ‐90°~90°    /       Y axis angle 
242 0x09    X axis relative angle           f Float(4)      R       ‐90°~90°    0       Return the X angle value according to the set relative zero 
243 0x0A    Y axis relative angle           f Float(4)      R       ‐90°~90°    0       Return the Y angle value according to the set relative zero 
244 0x0C    Sensor temperature              s Word(2)       R       ‐32768~32767  /       signed,sensor temperature =Data/100, unit ℃ 
245 0x0D    Power source voltage            S Word(2)       R       0~65535         /       Voltage= Data/100, unit V 
246 0x11    Arming/disarming                C Byte(1)       R/W     0~255           1       0 means disarming, non‐zero means arming 
247 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. 
248
249 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. 
250 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.
251 0x17    Signal strength                 C Byte(1)       R       10~34           /       A larger value indicates a stronger signal 
252 0x18    Sensor operating mode           C Byte(1)       R/W     0               0       The sensor can only work in absolute measurement mode 
253 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 
254 0x1A    SIM card ID                     Q QWord(8)      R       0~18446744073709551615  /       Take the first 19 digits, the last digit is discarded 
255 0x21    Heartbeat interval              l DWord(4)      R/W     60~131071       86400   Interval at which the device periodically uploads data to the server 
256 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. 
257 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        / 
258 0x24    Backup server enable            C Byte(1)       R/W     0~255   0       0 means off, non‐zero means on 
259 0x33    DNS IP address                  CCCC 4*Byte(1)  R/W     /       208.67.222.222  / 
260 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 
261 0x35    MQTT‐ClientID                         a* 32*Byte(1)   R/W      /      IMEI number of the device       Length <=32, subject to MQTT related specifications 
262 0x36    MQTT‐Username                         a* 32*Byte(1)   R/W     /       empty           Length <=32, subject to MQTT related specifications 
263
264 0x37    MQTT‐Password                         a* 32*Byte(1)   R/W     /       empty           Length <=32, subject to MQTT related specifications 
265 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 
266 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 
267 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. 
268 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. 
269 0x3D    Protocol type                   C Byte(1)       R/W     0~255   CTIOT   0:CTIOT 1:MQTT Other values are invalid. 
270 0x44    Alarm angle                     f Float(4)      R/W     ‐90°~90°    3°     X/Y axis alarm angle is consistent 
271