/[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 33 - (show annotations)
Wed Apr 8 14:48:22 2009 UTC (15 years ago) by dpavlin
File MIME type: text/plain
File size: 14550 byte(s)
decode security from tag

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

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26