/[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 39 - (show annotations)
Mon Jun 1 21:07:11 2009 UTC (14 years, 10 months ago) by dpavlin
File MIME type: text/plain
File size: 15484 byte(s)
fix few warnings

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 use File::Slurp;
11
12 use IO::Socket::INET;
13
14 my $meteor_server = '192.168.1.13:4671';
15 my $meteor_fh;
16
17 sub meteor {
18 my @a = @_;
19 push @a, scalar localtime() if $a[0] =~ m{^info};
20
21 if ( ! defined $meteor_fh ) {
22 if ( $meteor_fh =
23 IO::Socket::INET->new(
24 PeerAddr => $meteor_server,
25 Timeout => 1,
26 )
27 ) {
28 warn "# meteor connected to $meteor_server";
29 } else {
30 warn "can't connect to meteor $meteor_server: $!";
31 $meteor_fh = 0;
32 }
33 }
34
35 if ( $meteor_fh ) {
36 warn ">> meteor ",dump( @a );
37 print $meteor_fh "ADDMESSAGE test ",join('|',@a),"\n"
38 }
39 }
40
41 my $debug = 0;
42
43 my $device = "/dev/ttyUSB0";
44 my $baudrate = "19200";
45 my $databits = "8";
46 my $parity = "none";
47 my $stopbits = "1";
48 my $handshake = "none";
49
50 my $program_path = './program/';
51 my $secure_path = './secure/';
52
53 my $response = {
54 'd500090400110a0500027250' => 'version?',
55 'd60007fe00000500c97b' => 'no tag in range',
56
57 'd6000ffe00000501e00401003123aa26941a' => 'tag #1',
58 'd6000ffe00000501e0040100017c0c388e2b' => 'rfid card',
59 'd6000ffe00000501e00401003123aa2875d4' => 'tag red-stripe',
60
61 'd60017fe00000502e00401003123aa26e0040100017c0c38cadb' => 'tag #1 + card',
62 'd60017fe00000502e00401003123aa26e00401003123aa283124' => 'tag #1 + tag red-stripe',
63 };
64
65 GetOptions(
66 'd|debug+' => \$debug,
67 'device=s' => \$device,
68 'baudrate=i' => \$baudrate,
69 'databits=i' => \$databits,
70 'parity=s' => \$parity,
71 'stopbits=i' => \$stopbits,
72 'handshake=s' => \$handshake,
73 'meteor=s' => \$meteor_server,
74 ) or die $!;
75
76 my $verbose = $debug > 0 ? $debug-- : 0;
77
78 =head1 NAME
79
80 3m-810 - support for 3M 810 RFID reader
81
82 =head1 SYNOPSIS
83
84 3m-810.pl --device /dev/ttyUSB0
85
86 =head1 DESCRIPTION
87
88 Communicate with 3M 810 RFID reader and document it's protocol
89
90 =head1 SEE ALSO
91
92 L<Device::SerialPort(3)>
93
94 L<perl(1)>
95
96 L<http://stackoverflow.com/questions/149617/how-could-i-guess-a-checksum-algorithm>
97
98 =head1 AUTHOR
99
100 Dobrica Pavlinusic <dpavlin@rot13.org> L<http://www.rot13.org/~dpavlin/>
101
102 =head1 COPYRIGHT AND LICENSE
103
104 This program is free software; you may redistribute it and/or modify
105 it under the same terms ans Perl itself.
106
107 =cut
108
109 my $tags_data;
110 my $visible_tags;
111
112 my $item_type = {
113 1 => 'Book',
114 6 => 'CD/CD ROM',
115 2 => 'Magazine',
116 13 => 'Book with Audio Tape',
117 9 => 'Book with CD/CD ROM',
118 0 => 'Other',
119
120 5 => 'Video',
121 4 => 'Audio Tape',
122 3 => 'Bound Journal',
123 8 => 'Book with Diskette',
124 7 => 'Diskette',
125 };
126
127 warn "## known item type: ",dump( $item_type ) if $debug;
128
129 my $port=new Device::SerialPort($device) || die "can't open serial port $device: $!\n";
130 warn "using $device $handshake $baudrate $databits $parity $stopbits" if $debug;
131 $handshake=$port->handshake($handshake);
132 $baudrate=$port->baudrate($baudrate);
133 $databits=$port->databits($databits);
134 $parity=$port->parity($parity);
135 $stopbits=$port->stopbits($stopbits);
136
137 print "## using $device $baudrate $databits $parity $stopbits debug: $debug verbose: $verbose\n";
138
139 # Just in case: reset our timing and buffers
140 $port->lookclear();
141 $port->read_const_time(100);
142 $port->read_char_time(5);
143
144 # Turn on parity checking:
145 #$port->stty_inpck(1);
146 #$port->stty_istrip(1);
147
148 # initial hand-shake with device
149
150 cmd( 'D5 00 05 04 00 11 8C66', 'hw version',
151 'D5 00 09 04 00 11 0A 05 00 02 7250', sub {
152 my $hw_ver = join('.', unpack('CCCC', skip_assert(3) ));
153 print "hardware version $hw_ver\n";
154 meteor( 'info', "Found reader hardware $hw_ver" );
155 });
156
157 cmd( 'D6 00 0C 13 04 01 00 02 00 03 00 04 00 AAF2','FIXME: stats?',
158 'D6 00 0C 13 00 02 01 01 03 02 02 03 00 E778', sub { assert() } );
159
160 # start scanning for tags
161
162 cmd( 'D6 00 05 FE 00 05 FA40', "scan for tags, retry $_",
163 'D6 00 0F FE 00 00 05 ', sub { # 01 E00401003123AA26 941A # seen, serial length: 8
164 my $rest = shift || die "no rest?";
165 my $nr = ord( substr( $rest, 0, 1 ) );
166
167 if ( ! $nr ) {
168 print "no tags in range\n";
169 update_visible_tags();
170 meteor( 'info-none-in-range' );
171 $tags_data = {};
172 } else {
173
174 my $tags = substr( $rest, 1 );
175
176 my $tl = length( $tags );
177 die "wrong length $tl for $nr tags: ",dump( $tags ) if $tl =! $nr * 8;
178
179 my @tags;
180 push @tags, uc(unpack('H16', substr($tags, $_ * 8, 8))) foreach ( 0 .. $nr - 1 );
181 warn "## tags ",as_hex($tags), " [$tl] = ",dump( $tags ) if $debug;
182 print "$nr tags in range: ", join(',', @tags ) , "\n";
183
184 meteor( 'info-in-range', join(' ',@tags));
185
186 update_visible_tags( @tags );
187 }
188 }
189 ) while(1);
190 #) foreach ( 1 .. 100 );
191
192
193
194 sub update_visible_tags {
195 my @tags = @_;
196
197 my $last_visible_tags = $visible_tags;
198 $visible_tags = {};
199
200 foreach my $tag ( @tags ) {
201 if ( ! defined $last_visible_tags->{$tag} ) {
202 if ( defined $tags_data->{$tag} ) {
203 # meteor( 'in-range', $tag );
204 } else {
205 meteor( 'read', $tag );
206 read_tag( $tag );
207 }
208 $visible_tags->{$tag}++;
209 } else {
210 warn "## using cached data for $tag" if $debug;
211 }
212 delete $last_visible_tags->{$tag}; # leave just missing tags
213
214 if ( -e "$program_path/$tag" ) {
215 meteor( 'write', $tag );
216 write_tag( $tag );
217 }
218 if ( -e "$secure_path/$tag" ) {
219 meteor( 'secure', $tag );
220 secure_tag( $tag );
221 }
222 }
223
224 foreach my $tag ( keys %$last_visible_tags ) {
225 my $data = delete $tags_data->{$tag};
226 print "removed tag $tag with data ",dump( $data ),"\n";
227 meteor( 'removed', $tag );
228 }
229
230 warn "## update_visible_tags(",dump( @tags ),") = ",dump( $visible_tags )," removed: ",dump( $last_visible_tags ), " data: ",dump( $tags_data ) if $debug;
231 }
232
233 my $tag_data_block;
234
235 sub read_tag_data {
236 my ($start_block,$rest) = @_;
237 die "no rest?" unless $rest;
238 warn "## DATA [$start_block] ", dump( $rest ) if $debug;
239 my $tag = uc(unpack('H16',substr( $rest, 0, 8 )));
240 my $blocks = ord(substr($rest,8,1));
241 $rest = substr($rest,9); # leave just data blocks
242 foreach my $nr ( 0 .. $blocks - 1 ) {
243 my $block = substr( $rest, $nr * 6, 6 );
244 warn "## block ",as_hex( $block ) if $debug;
245 my $ord = unpack('v',substr( $block, 0, 2 ));
246 my $expected_ord = $nr + $start_block;
247 die "got block $ord, expected block $expected_ord from ",dump( $block ) if $ord != $expected_ord;
248 my $data = substr( $block, 2 );
249 die "data payload should be 4 bytes" if length($data) != 4;
250 warn sprintf "## tag %9s %02d %s |%-4s|\n", $tag, $ord, as_hex( $data ), $data;
251 $tag_data_block->{$tag}->[ $ord ] = $data;
252 }
253 $tags_data->{ $tag } = join('', @{ $tag_data_block->{$tag} });
254
255 my $item_type_nr = ord(substr( $tags_data->{$tag}, 3, 1 ));
256 print "DATA $tag ",dump( $tags_data ), " item type: ", ( $item_type->{ $item_type_nr } || "UNKWOWN '$item_type_nr' in " . dump( $item_type ) ), "\n";
257 }
258
259 sub read_tag {
260 my ( $tag ) = @_;
261
262 confess "no tag?" unless $tag;
263
264 print "read_tag $tag\n";
265
266 cmd(
267 "D6 00 0D 02 $tag 00 03 1CC4", "read $tag offset: 0 blocks: 3",
268 "D6 00 0F FE 00 00 05 01 $tag 941A", sub {
269 print "FIXME: tag $tag ready?\n";
270 },
271 "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";
272 read_tag_data( 0, @_ );
273 },
274 );
275
276 cmd(
277 "D6 00 0D 02 $tag 03 04 3970", "read $tag offset: 3 blocks: 4",
278 "D6 00 25 02 00", sub { # $tag 04 03 00 30 30 00 00 04 00 00 00 00 00
279 read_tag_data( 3, @_ );
280 }
281 );
282
283 my $security;
284
285 cmd(
286 "D6 00 0B 0A $tag 1234", "check security $tag",
287 "D6 00 0D 0A 00", sub {
288 my $rest = shift;
289 my $from_tag;
290 ( $from_tag, $security ) = ( substr($rest,0,8), substr($rest,8,1) );
291 die "security from other tag: ",as_hex( $from_tag ) if $from_tag ne str2bytes( $tag );
292 $security = as_hex( $security );
293 warn "# SECURITY $tag = $security\n";
294 }
295 );
296
297 my $data = $tags_data->{$tag} || die "no data for $tag";
298 my ( $u1, $set_item, $u2, $type, $content, $br_lib, $custom ) = unpack('C4Z16Nl>',$data);
299 my $set = ( $set_item & 0xf0 ) >> 4;
300 my $total = ( $set_item & 0x0f );
301 my $branch = $br_lib >> 20;
302 my $library = $br_lib & 0x000fffff;
303 print "TAG $tag [$u1] set: $set/$total [$u2] type: $type '$content' library: $library branch: $branch custom: $custom security: $security\n";
304
305 }
306
307 sub write_tag {
308 my ($tag) = @_;
309
310 my $path = "$program_path/$tag";
311
312 my $data = read_file( $path );
313 my $hex_data;
314
315 if ( $data =~ s{^hex\s+}{} ) {
316 $hex_data = $data;
317 $hex_data =~ s{\s+}{}g;
318 } else {
319
320 # pad to block size
321 $data .= "\0" x ( 4 - ( length($data) % 4 ) );
322
323 my $max_len = 7 * 4;
324
325 if ( length($data) > $max_len ) {
326 $data = substr($data,0,$max_len);
327 warn "strip content to $max_len bytes\n";
328 }
329
330 $hex_data = unpack('H*', $data);
331 }
332
333 my $len = length($hex_data) / 2;
334 my $blocks = sprintf('%02x', $len / 4);
335
336 print "write_tag $tag = ",dump( $data ), " [$len/$blocks] == $hex_data\n";
337
338 cmd(
339 "d6 00 ff 04 $tag 00 $blocks 00 $hex_data ffff", "write $tag",
340 "d6 00 0d 04 00 $tag $blocks afb1", sub { assert() },
341 ) foreach ( 1 .. 3 ); # xxx 3m software does this three times!
342
343 my $to = $path;
344 $to .= '.' . time();
345
346 rename $path, $to;
347 print ">> $to\n";
348
349 delete $tags_data->{$tag}; # force re-read of tag
350 }
351
352 sub secure_tag {
353 my ($tag) = @_;
354
355 my $path = "$secure_path/$tag";
356 my $data = substr(read_file( $path ),0,2);
357
358 cmd(
359 "d6 00 0c 09 $tag $data 1234", "secure $tag -> $data",
360 "d6 00 0c 09 00 $tag 1234", sub { assert() },
361 );
362
363 my $to = $path;
364 $to .= '.' . time();
365
366 rename $path, $to;
367 print ">> $to\n";
368 }
369
370 exit;
371
372 for ( 1 .. 3 ) {
373
374 # ++-->type 00-0a
375 # 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
376 # 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
377 # 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
378
379 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 $_" );
380 warn "D6 00 0D 04 00 E00401003123AA26 07 CFF1 -- ack 7 block?\n";
381
382 }
383 warn " D6 00 0F FE 00 00 05 01 E00401003123AA26 941A\n";
384
385 cmd( 'D6 00 05 FE 00 05 FA 40', "port-write scan $_" ) foreach ( 1 .. 2 );
386
387 cmd('D6 00 0C 09 E00401003123AA26 D7 3AF0', 'checkin?',
388 'D6 00 0C 09 00 E00401003123AA26 6A44 -- no?' );
389 cmd('D6 00 0C 09 E00401003123AA26 DA EB5D', 'checkout?',
390 'D6 00 0C 09 00 E00401003123AA26 6A44 -- no?' );
391
392 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',
393 'D6 00 0D 04 00 E00401003123AA26 06 DFD0 -- ack 6 blocks' ) foreach ( 1 .. 3 );
394
395 undef $port;
396 print "Port closed\n";
397
398 sub writechunk
399 {
400 my $str=shift;
401 my $count = $port->write($str);
402 my $len = length($str);
403 die "wrong write length $count != $len in ",as_hex( $str ) if $count != $len;
404 print "#> ", as_hex( $str ), "\t[$count]\n" if $debug;
405 }
406
407 sub as_hex {
408 my @out;
409 foreach my $str ( @_ ) {
410 my $hex = unpack( 'H*', $str );
411 $hex =~ s/(..)/$1 /g if length( $str ) > 2;
412 $hex =~ s/\s+$//;
413 push @out, $hex;
414 }
415 return join(' | ', @out);
416 }
417
418 sub read_bytes {
419 my ( $len, $desc ) = @_;
420 my $data = '';
421 while ( length( $data ) < $len ) {
422 my ( $c, $b ) = $port->read(1);
423 die "no bytes on port: $!" unless defined $b;
424 #warn "## got $c bytes: ", as_hex($b), "\n";
425 $data .= $b;
426 }
427 $desc ||= '?';
428 warn "#< ", as_hex($data), "\t$desc\n" if $debug;
429 return $data;
430 }
431
432 our $assert;
433
434 # my $rest = skip_assert( 3 );
435 sub skip_assert {
436 assert( 0, shift );
437 }
438
439 sub assert {
440 my ( $from, $to ) = @_;
441
442 return unless $assert->{expect};
443
444 $from ||= 0;
445 $to = length( $assert->{expect} ) if ! defined $to;
446
447 my $p = substr( $assert->{payload}, $from, $to );
448 my $e = substr( $assert->{expect}, $from, $to );
449 warn "EXPECTED ",as_hex($e), " GOT ", as_hex($p), " [$from-$to] in ",dump( $assert ), "\n" if $e ne $p;
450
451 # return the rest
452 return substr( $assert->{payload}, $to );
453 }
454
455 use Digest::CRC;
456
457 sub crcccitt {
458 my $bytes = shift;
459 my $crc = Digest::CRC->new(
460 # midified CCITT to xor with 0xffff instead of 0x0000
461 width => 16, init => 0xffff, xorout => 0xffff, refout => 0, poly => 0x1021, refin => 0,
462 ) or die $!;
463 $crc->add( $bytes );
464 pack('n', $crc->digest);
465 }
466
467 # my $checksum = checksum( $bytes );
468 # my $checksum = checksum( $bytes, $original_checksum );
469 sub checksum {
470 my ( $bytes, $checksum ) = @_;
471
472 my $len = ord(substr($bytes,2,1));
473 my $len_real = length($bytes) - 1;
474
475 if ( $len_real != $len ) {
476 print "length wrong: $len_real != $len\n";
477 $bytes = substr($bytes,0,2) . chr($len_real) . substr($bytes,3);
478 }
479
480 my $xor = crcccitt( substr($bytes,1) ); # skip D6
481 warn "## checksum ",dump( $bytes, $xor, $checksum ) if $debug;
482
483 if ( defined $checksum && $xor ne $checksum ) {
484 print "checksum doesn't match: ", as_hex($xor), " != ", as_hex($checksum), " data: ", as_hex($bytes), "\n";
485 return $bytes . $xor;
486 }
487 return $bytes . $checksum;
488 }
489
490 our $dispatch;
491
492 sub readchunk {
493 sleep 1; # FIXME remove
494
495 # read header of packet
496 my $header = read_bytes( 2, 'header' );
497 my $length = read_bytes( 1, 'length' );
498 my $len = ord($length);
499 my $data = read_bytes( $len, 'data' );
500
501 my $payload = substr( $data, 0, -2 );
502 my $payload_len = length($data);
503 warn "## payload too short $payload_len != $len\n" if $payload_len != $len;
504
505 my $checksum = substr( $data, -2, 2 );
506 checksum( $header . $length . $payload , $checksum );
507
508 print "<< ",as_hex( $header ), " [$len] ", as_hex( $payload ), " | sum: ",as_hex($checksum),"\n" if $verbose;
509
510 $assert->{len} = $len;
511 $assert->{payload} = $payload;
512
513 my $full = $header . $length . $data; # full
514 # find longest match for incomming data
515 my ($to) = grep {
516 my $match = substr($payload,0,length($_));
517 m/^\Q$match\E/
518 } sort { length($a) <=> length($b) } keys %$dispatch;
519 warn "?? payload dispatch to ",dump( $payload, $dispatch, $to ) if $debug;
520
521 if ( defined $to && $payload ) {
522 my $rest = substr( $payload, length($to) );
523 warn "## DISPATCH payload to with rest", dump( $payload, $to, $rest ) if $debug;
524 $dispatch->{ $to }->( $rest );
525 } else {
526 print "NO DISPATCH for ",dump( $full ),"\n";
527 }
528
529 return $data;
530 }
531
532 sub str2bytes {
533 my $str = shift || confess "no str?";
534 my $b = $str;
535 $b =~ s/\s+//g;
536 $b =~ s/(..)/\\x$1/g;
537 $b = "\"$b\"";
538 my $bytes = eval $b;
539 die $@ if $@;
540 warn "## str2bytes( $str ) => $b => ",as_hex($bytes) if $debug;
541 return $bytes;
542 }
543
544 sub cmd {
545 my $cmd = shift || confess "no cmd?";
546 my $cmd_desc = shift || confess "no description?";
547 my @expect = @_;
548
549 my $bytes = str2bytes( $cmd );
550
551 # fix checksum if needed
552 $bytes = checksum( substr( $bytes, 0, -2 ), substr( $bytes, -2, 2 ) );
553
554 warn ">> ", as_hex( $bytes ), "\t## $cmd_desc\n" if $verbose;
555 $assert->{send} = $cmd;
556 writechunk( $bytes );
557
558 while ( @expect ) {
559 my $pattern = str2bytes( shift @expect ) || confess "no pattern?";
560 my $coderef = shift @expect || confess "no coderef?";
561 confess "not coderef" unless ref $coderef eq 'CODE';
562
563 next if defined $dispatch->{ $pattern };
564
565 $dispatch->{ substr($pattern,3) } = $coderef;
566 warn "++ dispatch ", as_hex($pattern) ,dump( $dispatch ) if $debug;
567 }
568
569 readchunk;
570 }
571

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26