/[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 38 - (show annotations)
Mon Jun 1 18:36:42 2009 UTC (14 years, 10 months ago) by dpavlin
File MIME type: text/plain
File size: 15467 byte(s)
- support hex 00 04 01 ... notation in program files
- calculate number of blocks to program (supporting variable length payload)
- check length of received content against expected ones protecting us from short writes
- calculate checksum AFTER applying fix to length field

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 warn "DEBUG: ", as_hex($str);
402 my $count = $port->write($str);
403 my $len = length($str);
404 die "wrong write length $count != $len in ",as_hex( $str ) if $count != $len;
405 print "#> ", as_hex( $str ), "\t[$count]\n" if $debug;
406 }
407
408 sub as_hex {
409 my @out;
410 foreach my $str ( @_ ) {
411 my $hex = unpack( 'H*', $str );
412 $hex =~ s/(..)/$1 /g if length( $str ) > 2;
413 $hex =~ s/\s+$//;
414 push @out, $hex;
415 }
416 return join(' | ', @out);
417 }
418
419 sub read_bytes {
420 my ( $len, $desc ) = @_;
421 my $data = '';
422 while ( length( $data ) < $len ) {
423 my ( $c, $b ) = $port->read(1);
424 die "no bytes on port: $!" unless defined $b;
425 #warn "## got $c bytes: ", as_hex($b), "\n";
426 $data .= $b;
427 }
428 $desc ||= '?';
429 warn "#< ", as_hex($data), "\t$desc\n" if $debug;
430 return $data;
431 }
432
433 our $assert;
434
435 # my $rest = skip_assert( 3 );
436 sub skip_assert {
437 assert( 0, shift );
438 }
439
440 sub assert {
441 my ( $from, $to ) = @_;
442
443 $from ||= 0;
444 $to = length( $assert->{expect} ) if ! defined $to;
445
446 my $p = substr( $assert->{payload}, $from, $to );
447 my $e = substr( $assert->{expect}, $from, $to );
448 warn "EXPECTED ",as_hex($e), " GOT ", as_hex($p), " [$from-$to] in ",dump( $assert ), "\n" if $e ne $p;
449
450 # return the rest
451 return substr( $assert->{payload}, $to );
452 }
453
454 use Digest::CRC;
455
456 sub crcccitt {
457 my $bytes = shift;
458 my $crc = Digest::CRC->new(
459 # midified CCITT to xor with 0xffff instead of 0x0000
460 width => 16, init => 0xffff, xorout => 0xffff, refout => 0, poly => 0x1021, refin => 0,
461 ) or die $!;
462 $crc->add( $bytes );
463 pack('n', $crc->digest);
464 }
465
466 # my $checksum = checksum( $bytes );
467 # my $checksum = checksum( $bytes, $original_checksum );
468 sub checksum {
469 my ( $bytes, $checksum ) = @_;
470
471 my $len = ord(substr($bytes,2,1));
472 my $len_real = length($bytes) - 1;
473
474 if ( $len_real != $len ) {
475 print "length wrong: $len_real != $len\n";
476 $bytes = substr($bytes,0,2) . chr($len_real) . substr($bytes,3);
477 }
478
479 my $xor = crcccitt( substr($bytes,1) ); # skip D6
480 warn "## checksum ",dump( $bytes, $xor, $checksum ) if $debug;
481
482 if ( defined $checksum && $xor ne $checksum ) {
483 print "checksum doesn't match: ", as_hex($xor), " != ", as_hex($checksum), " data: ", as_hex($bytes), "\n";
484 return $bytes . $xor;
485 }
486 return $bytes . $checksum;
487 }
488
489 our $dispatch;
490
491 sub readchunk {
492 sleep 1; # FIXME remove
493
494 # read header of packet
495 my $header = read_bytes( 2, 'header' );
496 my $length = read_bytes( 1, 'length' );
497 my $len = ord($length);
498 my $data = read_bytes( $len, 'data' );
499
500 my $payload = substr( $data, 0, -2 );
501 my $payload_len = length($data);
502 warn "## payload too short $payload_len != $len\n" if $payload_len != $len;
503
504 my $checksum = substr( $data, -2, 2 );
505 checksum( $header . $length . $payload , $checksum );
506
507 print "<< ",as_hex( $header ), " [$len] ", as_hex( $payload ), " | sum: ",as_hex($checksum),"\n" if $verbose;
508
509 $assert->{len} = $len;
510 $assert->{payload} = $payload;
511
512 my $full = $header . $length . $data; # full
513 # find longest match for incomming data
514 my ($to) = grep {
515 my $match = substr($payload,0,length($_));
516 m/^\Q$match\E/
517 } sort { length($a) <=> length($b) } keys %$dispatch;
518 warn "?? payload dispatch to ",dump( $payload, $dispatch, $to ) if $debug;
519
520 if ( defined $to ) {
521 my $rest = substr( $payload, length($to) );
522 warn "## DISPATCH payload to with rest", dump( $payload, $to, $rest ) if $debug;
523 $dispatch->{ $to }->( $rest );
524 } else {
525 print "NO DISPATCH for ",dump( $full ),"\n";
526 }
527
528 return $data;
529 }
530
531 sub str2bytes {
532 my $str = shift || confess "no str?";
533 my $b = $str;
534 $b =~ s/\s+//g;
535 $b =~ s/(..)/\\x$1/g;
536 $b = "\"$b\"";
537 my $bytes = eval $b;
538 die $@ if $@;
539 warn "## str2bytes( $str ) => $b => ",as_hex($bytes) if $debug;
540 return $bytes;
541 }
542
543 sub cmd {
544 my $cmd = shift || confess "no cmd?";
545 my $cmd_desc = shift || confess "no description?";
546 my @expect = @_;
547
548 my $bytes = str2bytes( $cmd );
549
550 # fix checksum if needed
551 $bytes = checksum( substr( $bytes, 0, -2 ), substr( $bytes, -2, 2 ) );
552
553 warn ">> ", as_hex( $bytes ), "\t## $cmd_desc\n" if $verbose;
554 $assert->{send} = $cmd;
555 writechunk( $bytes );
556
557 while ( @expect ) {
558 my $pattern = str2bytes( shift @expect ) || confess "no pattern?";
559 my $coderef = shift @expect || confess "no coderef?";
560 confess "not coderef" unless ref $coderef eq 'CODE';
561
562 next if defined $dispatch->{ $pattern };
563
564 $dispatch->{ substr($pattern,3) } = $coderef;
565 warn "++ dispatch ", as_hex($pattern) ,dump( $dispatch ) if $debug;
566 }
567
568 readchunk;
569 }
570

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26