/[RFID]/3m-810.pl
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /3m-810.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 23 - (show annotations)
Sat Mar 28 03:47:10 2009 UTC (15 years ago) by dpavlin
File MIME type: text/plain
File size: 12318 byte(s)
added simple meteor notifications

1 #!/usr/bin/perl
2
3 use Device::SerialPort qw (:STAT);
4 use strict;
5 use warnings;
6
7 use Data::Dump qw/dump/;
8 use Carp qw/confess/;
9 use Getopt::Long;
10
11 use IO::Socket::INET;
12
13 my $meteor = IO::Socket::INET->new( '192.168.1.13:4671' ) || die "can't connect to meteor: $!";
14
15 sub meteor {
16 my ( $item, $html ) = @_;
17 warn ">> meteor $item $html\n";
18 print $meteor "ADDMESSAGE test $item|" . localtime() . "<br>$html\n";
19 }
20
21 my $debug = 0;
22
23 my $device = "/dev/ttyUSB0";
24 my $baudrate = "19200";
25 my $databits = "8";
26 my $parity = "none";
27 my $stopbits = "1";
28 my $handshake = "none";
29
30 my $response = {
31 'd500090400110a0500027250' => 'version?',
32 'd60007fe00000500c97b' => 'no tag in range',
33
34 'd6000ffe00000501e00401003123aa26941a' => 'tag #1',
35 'd6000ffe00000501e0040100017c0c388e2b' => 'rfid card',
36 'd6000ffe00000501e00401003123aa2875d4' => 'tag red-stripe',
37
38 'd60017fe00000502e00401003123aa26e0040100017c0c38cadb' => 'tag #1 + card',
39 'd60017fe00000502e00401003123aa26e00401003123aa283124' => 'tag #1 + tag red-stripe',
40 };
41
42 GetOptions(
43 'd|debug+' => \$debug,
44 'device=s' => \$device,
45 'baudrate=i' => \$baudrate,
46 'databits=i' => \$databits,
47 'parity=s' => \$parity,
48 'stopbits=i' => \$stopbits,
49 'handshake=s' => \$handshake,
50 ) or die $!;
51
52 my $verbose = $debug > 0 ? $debug-- : 0;
53
54 =head1 NAME
55
56 3m-810 - support for 3M 810 RFID reader
57
58 =head1 SYNOPSIS
59
60 3m-810.pl --device /dev/ttyUSB0
61
62 =head1 DESCRIPTION
63
64 Communicate with 3M 810 RFID reader and document it's protocol
65
66 =head1 SEE ALSO
67
68 L<Device::SerialPort(3)>
69
70 L<perl(1)>
71
72 L<http://stackoverflow.com/questions/149617/how-could-i-guess-a-checksum-algorithm>
73
74 =head1 AUTHOR
75
76 Dobrica Pavlinusic <dpavlin@rot13.org> L<http://www.rot13.org/~dpavlin/>
77
78 =head1 COPYRIGHT AND LICENSE
79
80 This program is free software; you may redistribute it and/or modify
81 it under the same terms ans Perl itself.
82
83 =cut
84
85 my $tags_data;
86 my $visible_tags;
87
88 my $port=new Device::SerialPort($device) || die "can't open serial port $device: $!\n";
89 warn "using $device $handshake $baudrate $databits $parity $stopbits" if $debug;
90 $handshake=$port->handshake($handshake);
91 $baudrate=$port->baudrate($baudrate);
92 $databits=$port->databits($databits);
93 $parity=$port->parity($parity);
94 $stopbits=$port->stopbits($stopbits);
95
96 print "## using $device $baudrate $databits $parity $stopbits debug: $debug verbose: $verbose\n";
97
98 # Just in case: reset our timing and buffers
99 $port->lookclear();
100 $port->read_const_time(100);
101 $port->read_char_time(5);
102
103 # Turn on parity checking:
104 #$port->stty_inpck(1);
105 #$port->stty_istrip(1);
106
107 # initial hand-shake with device
108
109 cmd( 'D5 00 05 04 00 11 8C66', 'hw version',
110 'D5 00 09 04 00 11 0A 05 00 02 7250', sub {
111 my $hw_ver = join('.', unpack('CCCC', skip_assert(3) ));
112 print "hardware version $hw_ver\n";
113 meteor( -1, "Found reader $hw_ver" );
114 });
115
116 cmd( 'D6 00 0C 13 04 01 00 02 00 03 00 04 00 AAF2','FIXME: stats?',
117 'D6 00 0C 13 00 02 01 01 03 02 02 03 00 E778', sub { assert() } );
118
119 # start scanning for tags
120
121 cmd( 'D6 00 05 FE 00 05 FA40', "scan for tags, retry $_",
122 'D6 00 0F FE 00 00 05 ', sub { # 01 E00401003123AA26 941A # seen, serial length: 8
123 my $rest = shift || die "no rest?";
124 my $nr = ord( substr( $rest, 0, 1 ) );
125
126 if ( ! $nr ) {
127 print "no tags in range\n";
128 update_visible_tags();
129 meteor( -1, "No tags in range" );
130 } else {
131
132 my $tags = substr( $rest, 1 );
133
134 my $tl = length( $tags );
135 die "wrong length $tl for $nr tags: ",dump( $tags ) if $tl =! $nr * 8;
136
137 my @tags;
138 push @tags, uc(unpack('H16', substr($tags, $_ * 8, 8))) foreach ( 0 .. $nr - 1 );
139 warn "## tags ",as_hex($tags), " [$tl] = ",dump( $tags ) if $debug;
140 print "$nr tags in range: ", join(',', @tags ) , "\n";
141
142 update_visible_tags( @tags );
143
144 my $html = join('', map { "<li><tt>$_</tt>" } @tags);
145 meteor( 0, "Tags:<ul>$html</ul>" );
146 }
147 }
148 ) foreach ( 1 .. 1000 );
149
150
151
152 sub update_visible_tags {
153 my @tags = @_;
154
155 my $last_visible_tags = $visible_tags;
156 $visible_tags = {};
157
158 foreach my $tag ( @tags ) {
159 if ( ! defined $last_visible_tags->{$tag} ) {
160 read_tag( $tag );
161 $visible_tags->{$tag}++;
162 } else {
163 warn "## using cached data for $tag" if $debug;
164 }
165 delete $last_visible_tags->{$tag}; # leave just missing tags
166 }
167
168 foreach my $tag ( keys %$last_visible_tags ) {
169 my $data = delete $tags_data->{$tag};
170 print "removed tag $tag with data ",dump( $data ),"\n";
171 }
172
173 warn "## update_visible_tags(",dump( @tags ),") = ",dump( $visible_tags )," removed: ",dump( $last_visible_tags ), " data: ",dump( $tags_data ) if $debug;
174 }
175
176
177 sub read_tag {
178 my ( $tag ) = @_;
179
180 confess "no tag?" unless $tag;
181
182 return if defined $tags_data->{$tag};
183
184 print "read_tag $tag\n";
185
186 cmd(
187 "D6 00 0D 02 $tag 00 03 1CC4", 'read $tag offset: 0 blocks: 3',
188 "D6 00 0F FE 00 00 05 01 $tag 941A", sub {
189 print "FIXME: tag $tag ready?\n";
190 },
191 "D6 00 1F 02 00", sub { # $tag 03 00 00 04 11 00 01 01 00 31 32 33 34 02 00 35 36 37 38 531F\n";
192 my $rest = shift || die "no rest?";
193 warn "## DATA ", dump( $rest ) if $debug;
194 my $tag = uc(unpack('H16',substr( $rest, 0, 8 )));
195 my $blocks = ord(substr($rest,8,1));
196 $rest = substr($rest,9); # leave just data blocks
197 my @data;
198 foreach my $nr ( 0 .. $blocks - 1 ) {
199 my $block = substr( $rest, $nr * 6, 6 );
200 warn "## block ",as_hex( $block ) if $debug;
201 my $ord = unpack('v',substr( $block, 0, 2 ));
202 die "got block $ord, expected block $nr from ",dump( $block ) if $ord != $nr;
203 my $data = substr( $block, 2 );
204 die "data payload should be 4 bytes" if length($data) != 4;
205 warn sprintf "## tag %08s %02d %s |%-4s|\n", $tag, $ord, as_hex( $data ), $data;
206 $data[ $ord ] = $data;
207 }
208 $tags_data->{ $tag } = join('', @data);
209 print "DATA $tag ",dump( $tags_data ), "\n";
210 }
211 );
212
213 # D6 00 1F 02 00 $tag 03 00 00 04 11 00 01 01 00 30 30 30 30 02 00 30 30 30 30 E5F4
214 if (0) {
215 cmd( "D6 00 0D 02 $tag 03 04 3970", 'read offset: 3 blocks: 4' );
216
217 # D6 00 25 02 00 $tag 04 03 00 30 30 00 00 04 00 00 00 00 00
218 # $tag 05 00 00 00 00 00 06 00 00 00 00 00 B9BA
219 warn "?? D6 00 25 02 00 $tag 04 03 00 39 30 31 32 04 00 ....\n";
220 }
221 warn "?? D6 00 0F FE 00 00 05 01 $tag 941A ##### ready?\n";
222
223 my $item = unpack('H*', substr($tag,-8) ) % 100000;
224 meteor( $item, "Loading $item" );
225
226 }
227
228 exit;
229
230 for ( 1 .. 3 ) {
231
232 # ++-->type 00-0a
233 # D6 00 2A 04 E00401003123AA26 00 07 00 04 11 00 01 31 31 31 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1C D4
234 # D6 00 2A 04 E0 04 01 00 31 23 AA 26 00 07 00 04 11 00 06 32 32 32 32 32 32 32 32 32 32 32 00 00 00 00 00 00 00 00 00 00 00 00 00 32B7
235 # D6 00 2A 04 E0 04 01 00 31 23 AA 26 00 07 00 04 11 00 02 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 00 00 00 00 00 00 00 00 42 1F
236
237 cmd(' D6 00 2A 04 E00401003123AA26 00 07 00 04 11 00 01 30 30 30 30 30 30 30 30 30 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 8843', "write offset 0, block: 7 -- 0000000000 $_" );
238 warn "D6 00 0D 04 00 E00401003123AA26 07 CFF1 -- ack 7 block?\n";
239
240 }
241 warn " D6 00 0F FE 00 00 05 01 E00401003123AA26 941A\n";
242
243 cmd( 'D6 00 05 FE 00 05 FA 40', "port-write scan $_" ) foreach ( 1 .. 2 );
244
245 cmd('D6 00 0C 09 E00401003123AA26 D7 3AF0', 'checkin?',
246 'D6 00 0C 09 00 E00401003123AA26 6A44 -- no?' );
247 cmd('D6 00 0C 09 E00401003123AA26 DA EB5D', 'checkout?',
248 'D6 00 0C 09 00 E00401003123AA26 6A44 -- no?' );
249
250 cmd('D6 00 26 04 E00401003123AA26 00 06 00 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 A98B', 'blank offset: 0 blocks: 6',
251 'D6 00 0D 04 00 E00401003123AA26 06 DFD0 -- ack 6 blocks' ) foreach ( 1 .. 3 );
252
253 undef $port;
254 print "Port closed\n";
255
256 sub writechunk
257 {
258 my $str=shift;
259 my $count = $port->write($str);
260 print "#> ", as_hex( $str ), "\t[$count]\n" if $debug;
261 }
262
263 sub as_hex {
264 my @out;
265 foreach my $str ( @_ ) {
266 my $hex = unpack( 'H*', $str );
267 $hex =~ s/(..)/$1 /g if length( $str ) > 2;
268 $hex =~ s/\s+$//;
269 push @out, $hex;
270 }
271 return join(' | ', @out);
272 }
273
274 sub read_bytes {
275 my ( $len, $desc ) = @_;
276 my $data = '';
277 while ( length( $data ) < $len ) {
278 my ( $c, $b ) = $port->read(1);
279 #warn "## got $c bytes: ", as_hex($b), "\n";
280 $data .= $b;
281 }
282 $desc ||= '?';
283 warn "#< ", as_hex($data), "\t$desc\n" if $debug;
284 return $data;
285 }
286
287 our $assert;
288
289 # my $rest = skip_assert( 3 );
290 sub skip_assert {
291 assert( 0, shift );
292 }
293
294 sub assert {
295 my ( $from, $to ) = @_;
296
297 $from ||= 0;
298 $to = length( $assert->{expect} ) if ! defined $to;
299
300 my $p = substr( $assert->{payload}, $from, $to );
301 my $e = substr( $assert->{expect}, $from, $to );
302 warn "EXPECTED ",as_hex($e), " GOT ", as_hex($p), " [$from-$to] in ",dump( $assert ), "\n" if $e ne $p;
303
304 # return the rest
305 return substr( $assert->{payload}, $to );
306 }
307
308 use Digest::CRC;
309
310 sub crcccitt {
311 my $bytes = shift;
312 my $crc = Digest::CRC->new(
313 # midified CCITT to xor with 0xffff instead of 0x0000
314 width => 16, init => 0xffff, xorout => 0xffff, refout => 0, poly => 0x1021, refin => 0,
315 ) or die $!;
316 $crc->add( $bytes );
317 pack('n', $crc->digest);
318 }
319
320 # my $checksum = checksum( $bytes );
321 # my $checksum = checksum( $bytes, $original_checksum );
322 sub checksum {
323 my ( $bytes, $checksum ) = @_;
324
325 my $xor = crcccitt( substr($bytes,1) ); # skip D6
326 warn "## checksum ",dump( $bytes, $xor, $checksum ) if $debug;
327
328 my $len = ord(substr($bytes,2,1));
329 my $len_real = length($bytes) - 1;
330
331 if ( $len_real != $len ) {
332 print "length wrong: $len_real != $len\n";
333 $bytes = substr($bytes,0,2) . chr($len_real) . substr($bytes,4);
334 }
335
336 if ( defined $checksum && $xor ne $checksum ) {
337 print "checksum doesn't match: ", as_hex($xor), " != ", as_hex($checksum), " data: ", as_hex($bytes), "\n";
338 return $bytes . $xor;
339 }
340 return $bytes . $checksum;
341 }
342
343 our $dispatch;
344
345 sub readchunk {
346 sleep 1; # FIXME remove
347
348 # read header of packet
349 my $header = read_bytes( 2, 'header' );
350 my $length = read_bytes( 1, 'length' );
351 my $len = ord($length);
352 my $data = read_bytes( $len, 'data' );
353
354 my $payload = substr( $data, 0, -2 );
355 my $payload_len = length($data);
356 warn "## payload too short $payload_len != $len\n" if $payload_len != $len;
357
358 my $checksum = substr( $data, -2, 2 );
359 checksum( $header . $length . $payload , $checksum );
360
361 print "<< ",as_hex( $header ), " [$len] ", as_hex( $payload ), " | sum: ",as_hex($checksum),"\n" if $verbose;
362
363 $assert->{len} = $len;
364 $assert->{payload} = $payload;
365
366 my $full = $header . $length . $data; # full
367 # find longest match for incomming data
368 my ($to) = grep {
369 my $match = substr($payload,0,length($_));
370 m/^\Q$match\E/
371 } sort { length($a) <=> length($b) } keys %$dispatch;
372 warn "?? payload dispatch to ",dump( $payload, $dispatch, $to ) if $debug;
373
374 if ( defined $to ) {
375 my $rest = substr( $payload, length($to) );
376 warn "## DISPATCH payload to with rest", dump( $payload, $to, $rest ) if $debug;
377 $dispatch->{ $to }->( $rest );
378 } else {
379 print "NO DISPATCH for ",dump( $full ),"\n";
380 }
381
382 return $data;
383 }
384
385 sub str2bytes {
386 my $str = shift || confess "no str?";
387 my $b = $str;
388 $b =~ s/\s+//g;
389 $b =~ s/(..)/\\x$1/g;
390 $b = "\"$b\"";
391 my $bytes = eval $b;
392 die $@ if $@;
393 warn "## str2bytes( $str ) => $b => ",as_hex($bytes) if $debug;
394 return $bytes;
395 }
396
397 sub cmd {
398 my $cmd = shift || confess "no cmd?";
399 my $cmd_desc = shift || confess "no description?";
400 my @expect = @_;
401
402 my $bytes = str2bytes( $cmd );
403
404 # fix checksum if needed
405 $bytes = checksum( substr( $bytes, 0, -2 ), substr( $bytes, -2, 2 ) );
406
407 warn ">> ", as_hex( $bytes ), "\t## $cmd_desc\n" if $verbose;
408 $assert->{send} = $cmd;
409 writechunk( $bytes );
410
411 while ( @expect ) {
412 my $pattern = str2bytes( shift @expect ) || confess "no pattern?";
413 my $coderef = shift @expect || confess "no coderef?";
414 confess "not coderef" unless ref $coderef eq 'CODE';
415
416 next if defined $dispatch->{ $pattern };
417
418 $dispatch->{ substr($pattern,3) } = $coderef;
419 warn "++ dispatch ", as_hex($pattern) ,dump( $dispatch ) if $debug;
420 }
421
422 readchunk;
423 }
424

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26