/[RFID]/cpr-m02.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

Annotation of /cpr-m02.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 89 - (hide annotations)
Fri Jul 16 13:50:52 2010 UTC (13 years, 9 months ago) by dpavlin
File MIME type: text/plain
File size: 21883 byte(s)
Get System Information 0x2B
1 dpavlin 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 dpavlin 2 use Carp qw/confess/;
9 dpavlin 19 use Getopt::Long;
10 dpavlin 29 use File::Slurp;
11 dpavlin 44 use JSON;
12 dpavlin 59 use POSIX qw(strftime);
13 dpavlin 84 use Time::HiRes;
14 dpavlin 1
15 dpavlin 23 use IO::Socket::INET;
16    
17 dpavlin 85 my $debug = 0;
18 dpavlin 50
19 dpavlin 54 my $tags_data;
20     my $tags_security;
21     my $visible_tags;
22    
23 dpavlin 43 my $listen_port = 9000; # pick something not in use
24 dpavlin 59 my $server_url = "http://localhost:$listen_port";
25    
26 dpavlin 43 sub http_server {
27    
28     my $server = IO::Socket::INET->new(
29     Proto => 'tcp',
30     LocalPort => $listen_port,
31     Listen => SOMAXCONN,
32     Reuse => 1
33     );
34    
35 dpavlin 80 die "can't setup server: $!" unless $server;
36 dpavlin 43
37 dpavlin 59 print "Server $0 ready at $server_url\n";
38 dpavlin 43
39     sub static {
40     my ($client,$path) = @_;
41    
42     $path = "www/$path";
43 dpavlin 56 $path .= 'rfid.html' if $path =~ m{/$};
44 dpavlin 43
45     return unless -e $path;
46    
47     my $type = 'text/plain';
48     $type = 'text/html' if $path =~ m{\.htm};
49     $type = 'application/javascript' if $path =~ m{\.js};
50    
51     print $client "HTTP/1.0 200 OK\r\nContent-Type: $type\r\n\r\n";
52     open(my $html, $path);
53     while(<$html>) {
54     print $client $_;
55     }
56     close($html);
57    
58     return $path;
59     }
60    
61     while (my $client = $server->accept()) {
62     $client->autoflush(1);
63     my $request = <$client>;
64    
65 dpavlin 50 warn "WEB << $request\n" if $debug;
66 dpavlin 43
67     if ($request =~ m{^GET (/.*) HTTP/1.[01]}) {
68     my $method = $1;
69 dpavlin 46 my $param;
70     if ( $method =~ s{\?(.+)}{} ) {
71     foreach my $p ( split(/[&;]/, $1) ) {
72     my ($n,$v) = split(/=/, $p, 2);
73     $param->{$n} = $v;
74     }
75 dpavlin 50 warn "WEB << param: ",dump( $param ) if $debug;
76 dpavlin 46 }
77 dpavlin 43 if ( my $path = static( $client,$1 ) ) {
78 dpavlin 50 warn "WEB >> $path" if $debug;
79 dpavlin 43 } elsif ( $method =~ m{/scan} ) {
80     my $tags = scan_for_tags();
81 dpavlin 52 my $json = { time => time() };
82 dpavlin 44 map {
83     my $d = decode_tag($_);
84     $d->{sid} = $_;
85 dpavlin 54 $d->{security} = $tags_security->{$_};
86 dpavlin 44 push @{ $json->{tags} }, $d;
87     } keys %$tags;
88 dpavlin 71 print $client "HTTP/1.0 200 OK\r\nContent-Type: application/json\r\n\r\n",
89 dpavlin 46 $param->{callback}, "(", to_json($json), ")\r\n";
90 dpavlin 59 } elsif ( $method =~ m{/program} ) {
91    
92     my $status = 501; # Not implementd
93    
94     foreach my $p ( keys %$param ) {
95 dpavlin 66 next unless $p =~ m/^(E[0-9A-F]{15})$/;
96 dpavlin 59 my $tag = $1;
97 dpavlin 61 my $content = "\x04\x11\x00\x01" . $param->{$p};
98 dpavlin 63 $content = "\x00" if $param->{$p} eq 'blank';
99 dpavlin 59 $status = 302;
100    
101     warn "PROGRAM $tag $content\n";
102     write_tag( $tag, $content );
103 dpavlin 68 secure_tag_with( $tag, $param->{$p} =~ /^130/ ? 'DA' : 'D7' );
104 dpavlin 59 }
105    
106     print $client "HTTP/1.0 $status $method\r\nLocation: $server_url\r\n\r\n";
107    
108 dpavlin 71 } elsif ( $method =~ m{/secure(.js)} ) {
109 dpavlin 67
110 dpavlin 71 my $json = $1;
111    
112 dpavlin 67 my $status = 501; # Not implementd
113    
114     foreach my $p ( keys %$param ) {
115     next unless $p =~ m/^(E[0-9A-F]{15})$/;
116     my $tag = $1;
117     my $data = $param->{$p};
118     $status = 302;
119    
120     warn "SECURE $tag $data\n";
121     secure_tag_with( $tag, $data );
122     }
123    
124 dpavlin 71 if ( $json ) {
125     print $client "HTTP/1.0 200 OK\r\nContent-Type: application/json\r\n\r\n",
126     $param->{callback}, "({ ok: 1 })\r\n";
127     } else {
128     print $client "HTTP/1.0 $status $method\r\nLocation: $server_url\r\n\r\n";
129     }
130 dpavlin 67
131 dpavlin 43 } else {
132 dpavlin 71 print $client "HTTP/1.0 404 Unkown method\r\n\r\n";
133 dpavlin 43 }
134     } else {
135 dpavlin 71 print $client "HTTP/1.0 500 No method\r\n\r\n";
136 dpavlin 43 }
137     close $client;
138     }
139    
140     die "server died";
141     }
142    
143 dpavlin 48
144     my $last_message = {};
145     sub _message {
146     my $type = shift @_;
147     my $text = join(' ',@_);
148     my $last = $last_message->{$type};
149     if ( $text ne $last ) {
150     warn $type eq 'diag' ? '# ' : '', $text, "\n";
151     $last_message->{$type} = $text;
152     }
153     }
154    
155     sub _log { _message('log',@_) };
156     sub diag { _message('diag',@_) };
157    
158 dpavlin 19 my $device = "/dev/ttyUSB0";
159 dpavlin 82 my $baudrate = "38400";
160 dpavlin 19 my $databits = "8";
161 dpavlin 82 my $parity = "even";
162 dpavlin 19 my $stopbits = "1";
163     my $handshake = "none";
164    
165 dpavlin 29 my $program_path = './program/';
166 dpavlin 34 my $secure_path = './secure/';
167 dpavlin 29
168 dpavlin 43 # http server
169     my $http_server = 1;
170    
171 dpavlin 41 # 3M defaults: 8,4
172 dpavlin 75 # cards 16, stickers: 8
173     my $max_rfid_block = 8;
174 dpavlin 41 my $read_blocks = 8;
175    
176 dpavlin 1 my $response = {
177     'd500090400110a0500027250' => 'version?',
178     'd60007fe00000500c97b' => 'no tag in range',
179    
180     'd6000ffe00000501e00401003123aa26941a' => 'tag #1',
181     'd6000ffe00000501e0040100017c0c388e2b' => 'rfid card',
182     'd6000ffe00000501e00401003123aa2875d4' => 'tag red-stripe',
183    
184     'd60017fe00000502e00401003123aa26e0040100017c0c38cadb' => 'tag #1 + card',
185     'd60017fe00000502e00401003123aa26e00401003123aa283124' => 'tag #1 + tag red-stripe',
186     };
187    
188 dpavlin 19 GetOptions(
189 dpavlin 22 'd|debug+' => \$debug,
190 dpavlin 19 'device=s' => \$device,
191     'baudrate=i' => \$baudrate,
192     'databits=i' => \$databits,
193     'parity=s' => \$parity,
194     'stopbits=i' => \$stopbits,
195     'handshake=s' => \$handshake,
196 dpavlin 45 'http-server!' => \$http_server,
197 dpavlin 19 ) or die $!;
198    
199 dpavlin 22 my $verbose = $debug > 0 ? $debug-- : 0;
200    
201 dpavlin 1 =head1 NAME
202    
203     3m-810 - support for 3M 810 RFID reader
204    
205     =head1 SYNOPSIS
206    
207 dpavlin 19 3m-810.pl --device /dev/ttyUSB0
208 dpavlin 1
209     =head1 DESCRIPTION
210    
211     Communicate with 3M 810 RFID reader and document it's protocol
212    
213     =head1 SEE ALSO
214    
215     L<Device::SerialPort(3)>
216    
217     L<perl(1)>
218    
219 dpavlin 15 L<http://stackoverflow.com/questions/149617/how-could-i-guess-a-checksum-algorithm>
220    
221 dpavlin 1 =head1 AUTHOR
222    
223     Dobrica Pavlinusic <dpavlin@rot13.org> L<http://www.rot13.org/~dpavlin/>
224    
225     =head1 COPYRIGHT AND LICENSE
226    
227     This program is free software; you may redistribute it and/or modify
228     it under the same terms ans Perl itself.
229    
230     =cut
231    
232 dpavlin 31 my $item_type = {
233     1 => 'Book',
234     6 => 'CD/CD ROM',
235     2 => 'Magazine',
236     13 => 'Book with Audio Tape',
237     9 => 'Book with CD/CD ROM',
238     0 => 'Other',
239    
240     5 => 'Video',
241     4 => 'Audio Tape',
242     3 => 'Bound Journal',
243     8 => 'Book with Diskette',
244     7 => 'Diskette',
245     };
246    
247     warn "## known item type: ",dump( $item_type ) if $debug;
248    
249 dpavlin 19 my $port=new Device::SerialPort($device) || die "can't open serial port $device: $!\n";
250     warn "using $device $handshake $baudrate $databits $parity $stopbits" if $debug;
251 dpavlin 1 $handshake=$port->handshake($handshake);
252     $baudrate=$port->baudrate($baudrate);
253     $databits=$port->databits($databits);
254     $parity=$port->parity($parity);
255     $stopbits=$port->stopbits($stopbits);
256    
257 dpavlin 48 warn "## using $device $baudrate $databits $parity $stopbits debug: $debug verbose: $verbose\n";
258 dpavlin 1
259     # Just in case: reset our timing and buffers
260     $port->lookclear();
261     $port->read_const_time(100);
262     $port->read_char_time(5);
263    
264     # Turn on parity checking:
265     #$port->stty_inpck(1);
266     #$port->stty_istrip(1);
267    
268 dpavlin 82 sub cpr_m02_checksum {
269     my $data = shift;
270    
271     my $preset = 0xffff;
272     my $polynom = 0x8408;
273    
274     my $crc = $preset;
275     foreach my $i ( 0 .. length($data) - 1 ) {
276     $crc ^= ord(substr($data,$i,1));
277     for my $j ( 0 .. 7 ) {
278     if ( $crc & 0x0001 ) {
279     $crc = ( $crc >> 1 ) ^ $polynom;
280     } else {
281     $crc = $crc >> 1;
282     }
283     }
284 dpavlin 84 # warn sprintf('%d %04x', $i, $crc & 0xffff);
285 dpavlin 82 }
286    
287     return pack('v', $crc);
288     }
289    
290 dpavlin 87 sub cpr_psst_wait {
291     # Protocol Start Synchronization Time (PSST): 5ms < data timeout 12 ms
292     Time::HiRes::sleep 0.005;
293     }
294    
295 dpavlin 82 sub cpr {
296 dpavlin 86 my ( $hex, $description, $coderef ) = @_;
297 dpavlin 82 my $bytes = str2bytes($hex);
298     my $len = pack( 'c', length( $bytes ) + 3 );
299     my $send = $len . $bytes;
300     my $checksum = cpr_m02_checksum($send);
301     $send .= $checksum;
302    
303 dpavlin 85 warn ">> ", as_hex( $send ), "\t\t[$description]\n";
304 dpavlin 83 $port->write( $send );
305 dpavlin 84
306 dpavlin 87 cpr_psst_wait;
307    
308 dpavlin 83 my $r_len = $port->read(1);
309 dpavlin 84
310     while ( ! $r_len ) {
311 dpavlin 87 warn "# wait for response length 5ms\n";
312     cpr_psst_wait;
313 dpavlin 84 $r_len = $port->read(1);
314     }
315    
316 dpavlin 87 my $data_len = ord($r_len) - 1;
317     my $data = $port->read( $data_len );
318     warn "<< ", as_hex( $r_len . $data ),"\n";
319 dpavlin 83
320 dpavlin 87 cpr_psst_wait;
321    
322 dpavlin 86 $coderef->( $data ) if $coderef;
323    
324 dpavlin 82 }
325    
326 dpavlin 85 # FF = COM-ADDR any
327 dpavlin 82
328 dpavlin 85 cpr( 'FF 52 00', 'Boud Rate Detection' );
329 dpavlin 83
330 dpavlin 85 cpr( 'FF 65', 'Get Software Version' );
331 dpavlin 83
332 dpavlin 85 cpr( 'FF 66 00', 'Get Reader Info - General hard and firware' );
333 dpavlin 83
334 dpavlin 85 cpr( 'FF 69', 'RF Reset' );
335 dpavlin 83
336 dpavlin 87
337     sub cpr_read {
338     my $uid = shift;
339     my $hex_uid = as_hex($uid);
340    
341     cpr( "FF B0 23 01 $hex_uid 00 04", "Read Multiple Blocks $hex_uid" );
342 dpavlin 89 cpr( "FF B0 2B 01 $hex_uid", "Get System Information $hex_uid", sub {
343     my $data = shift;
344    
345     warn "# data ",as_hex($data);
346    
347     my $DSFID = substr($data,5-2,1);
348     my $UID = substr($data,6-2,8);
349     my $AFI = substr($data,14-2,1);
350     my $MEM_SIZE = substr($data,15-2,2);
351     my $IC_REF = substr($data,17-2,1);
352    
353     warn "# split ",as_hex( $DSFID, $UID, $AFI, $MEM_SIZE, $IC_REF );
354    
355     });
356 dpavlin 87 }
357    
358    
359 dpavlin 86 my $inventory;
360 dpavlin 83
361 dpavlin 86 while(1) {
362    
363     cpr( 'FF B0 01 00', 'ISO - Inventory', sub {
364     my $data = shift;
365 dpavlin 88 if (length($data) < 5 + 2 ) {
366     warn "# no tags in range\n";
367     return;
368     }
369 dpavlin 86 my $data_sets = ord(substr($data,3,1));
370     $data = substr($data,4);
371     foreach ( 1 .. $data_sets ) {
372     my $tr_type = substr($data,0,1);
373 dpavlin 87 die "FIXME only TR-TYPE=3 ISO 15693 supported" unless $tr_type eq "\x03";
374 dpavlin 86 my $dsfid = substr($data,1,1);
375     my $uid = substr($data,2,8);
376     $inventory->{$uid}++;
377     $data = substr($data,10);
378     warn "# TAG $_ ",as_hex( $tr_type, $dsfid, $uid ),$/;
379 dpavlin 87
380     cpr_read( $uid );
381 dpavlin 86 }
382     warn "inventory: ",dump($inventory);
383     });
384    
385     }
386    
387 dpavlin 83 #cpr( '', '?' );
388    
389 dpavlin 82 exit;
390 dpavlin 4 # initial hand-shake with device
391    
392 dpavlin 20 cmd( 'D5 00 05 04 00 11 8C66', 'hw version',
393     'D5 00 09 04 00 11 0A 05 00 02 7250', sub {
394 dpavlin 23 my $hw_ver = join('.', unpack('CCCC', skip_assert(3) ));
395     print "hardware version $hw_ver\n";
396 dpavlin 2 });
397 dpavlin 1
398 dpavlin 20 cmd( 'D6 00 0C 13 04 01 00 02 00 03 00 04 00 AAF2','FIXME: stats?',
399     'D6 00 0C 13 00 02 01 01 03 02 02 03 00 E778', sub { assert() } );
400 dpavlin 1
401 dpavlin 43 sub scan_for_tags {
402 dpavlin 1
403 dpavlin 43 my @tags;
404 dpavlin 20
405 dpavlin 48 cmd( 'D6 00 05 FE 00 05 FA40', "scan for tags",
406 dpavlin 43 'D6 00 0F FE 00 00 05 ', sub { # 01 E00401003123AA26 941A # seen, serial length: 8
407     my $rest = shift || die "no rest?";
408     my $nr = ord( substr( $rest, 0, 1 ) );
409 dpavlin 20
410 dpavlin 43 if ( ! $nr ) {
411 dpavlin 48 _log "no tags in range\n";
412 dpavlin 43 update_visible_tags();
413     $tags_data = {};
414     } else {
415 dpavlin 1
416 dpavlin 43 my $tags = substr( $rest, 1 );
417     my $tl = length( $tags );
418     die "wrong length $tl for $nr tags: ",dump( $tags ) if $tl =! $nr * 8;
419 dpavlin 16
420 dpavlin 43 push @tags, uc(unpack('H16', substr($tags, $_ * 8, 8))) foreach ( 0 .. $nr - 1 );
421     warn "## tags ",as_hex($tags), " [$tl] = ",dump( $tags ) if $debug;
422 dpavlin 48 _log "$nr tags in range: ", join(',', @tags ) , "\n";
423 dpavlin 25
424 dpavlin 43 update_visible_tags( @tags );
425     }
426 dpavlin 5 }
427 dpavlin 43 );
428 dpavlin 5
429 dpavlin 48 diag "tags: ",dump( @tags );
430 dpavlin 43 return $tags_data;
431 dpavlin 22
432 dpavlin 43 }
433 dpavlin 22
434 dpavlin 43 # start scanning for tags
435    
436     if ( $http_server ) {
437     http_server;
438     } else {
439 dpavlin 58 while (1) {
440     scan_for_tags;
441     sleep 1;
442     }
443 dpavlin 43 }
444    
445     die "over and out";
446    
447 dpavlin 22 sub update_visible_tags {
448     my @tags = @_;
449    
450     my $last_visible_tags = $visible_tags;
451     $visible_tags = {};
452    
453     foreach my $tag ( @tags ) {
454 dpavlin 51 $visible_tags->{$tag}++;
455 dpavlin 22 if ( ! defined $last_visible_tags->{$tag} ) {
456 dpavlin 25 if ( defined $tags_data->{$tag} ) {
457 dpavlin 64 warn "$tag in range\n";
458 dpavlin 25 } else {
459     read_tag( $tag );
460     }
461 dpavlin 22 } else {
462     warn "## using cached data for $tag" if $debug;
463     }
464     delete $last_visible_tags->{$tag}; # leave just missing tags
465 dpavlin 29
466     if ( -e "$program_path/$tag" ) {
467     write_tag( $tag );
468     }
469 dpavlin 34 if ( -e "$secure_path/$tag" ) {
470     secure_tag( $tag );
471     }
472 dpavlin 22 }
473    
474     foreach my $tag ( keys %$last_visible_tags ) {
475 dpavlin 23 my $data = delete $tags_data->{$tag};
476 dpavlin 64 warn "$tag removed ", dump($data), $/;
477 dpavlin 22 }
478    
479     warn "## update_visible_tags(",dump( @tags ),") = ",dump( $visible_tags )," removed: ",dump( $last_visible_tags ), " data: ",dump( $tags_data ) if $debug;
480     }
481    
482 dpavlin 28 my $tag_data_block;
483 dpavlin 22
484 dpavlin 28 sub read_tag_data {
485     my ($start_block,$rest) = @_;
486     die "no rest?" unless $rest;
487 dpavlin 41
488     my $last_block = 0;
489    
490 dpavlin 28 warn "## DATA [$start_block] ", dump( $rest ) if $debug;
491     my $tag = uc(unpack('H16',substr( $rest, 0, 8 )));
492     my $blocks = ord(substr($rest,8,1));
493     $rest = substr($rest,9); # leave just data blocks
494     foreach my $nr ( 0 .. $blocks - 1 ) {
495     my $block = substr( $rest, $nr * 6, 6 );
496     warn "## block ",as_hex( $block ) if $debug;
497     my $ord = unpack('v',substr( $block, 0, 2 ));
498     my $expected_ord = $nr + $start_block;
499 dpavlin 41 warn "got block $ord, expected block $expected_ord from ",dump( $block ) if $ord != $expected_ord;
500 dpavlin 28 my $data = substr( $block, 2 );
501     die "data payload should be 4 bytes" if length($data) != 4;
502 dpavlin 40 warn sprintf "## tag %9s %02d: %s |%-4s|\n", $tag, $ord, as_hex( $data ), $data;
503 dpavlin 28 $tag_data_block->{$tag}->[ $ord ] = $data;
504 dpavlin 41 $last_block = $ord;
505 dpavlin 28 }
506     $tags_data->{ $tag } = join('', @{ $tag_data_block->{$tag} });
507 dpavlin 31
508     my $item_type_nr = ord(substr( $tags_data->{$tag}, 3, 1 ));
509 dpavlin 42 print "DATA $tag ",dump( $tags_data ), " item type: ", ( $item_type->{ $item_type_nr } || "UNKWOWN '$item_type_nr'" ), "\n";
510 dpavlin 41
511 dpavlin 42 return $last_block + 1;
512 dpavlin 28 }
513    
514 dpavlin 59 my $saved_in_log;
515    
516 dpavlin 43 sub decode_tag {
517     my $tag = shift;
518    
519 dpavlin 78 my $data = $tags_data->{$tag};
520     if ( ! $data ) {
521     warn "no data for $tag\n";
522     return;
523     }
524 dpavlin 43
525     my ( $u1, $set_item, $u2, $type, $content, $br_lib, $custom ) = unpack('C4Z16Nl>',$data);
526     my $hash = {
527     u1 => $u1,
528     u2 => $u2,
529     set => ( $set_item & 0xf0 ) >> 4,
530     total => ( $set_item & 0x0f ),
531    
532     type => $type,
533     content => $content,
534    
535     branch => $br_lib >> 20,
536     library => $br_lib & 0x000fffff,
537    
538     custom => $custom,
539     };
540    
541 dpavlin 59 if ( ! $saved_in_log->{$tag}++ ) {
542     open(my $log, '>>', 'rfid-log.txt');
543     print $log strftime( "%Y-%m-%d %H:%M:%S", localtime ), ",$tag,$content\n";
544     close($log);
545     }
546    
547 dpavlin 43 return $hash;
548     }
549    
550 dpavlin 67 sub forget_tag {
551     my $tag = shift;
552     delete $tags_data->{$tag};
553     delete $visible_tags->{$tag};
554     }
555    
556 dpavlin 16 sub read_tag {
557     my ( $tag ) = @_;
558 dpavlin 1
559 dpavlin 22 confess "no tag?" unless $tag;
560    
561 dpavlin 16 print "read_tag $tag\n";
562 dpavlin 1
563 dpavlin 41 my $start_block = 0;
564 dpavlin 28
565 dpavlin 41 while ( $start_block < $max_rfid_block ) {
566 dpavlin 1
567 dpavlin 41 cmd(
568 dpavlin 65 sprintf( "D6 00 0D 02 $tag %02x %02x BEEF", $start_block, $read_blocks ),
569 dpavlin 41 "read $tag offset: $start_block blocks: $read_blocks",
570     "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";
571     $start_block = read_tag_data( $start_block, @_ );
572     warn "# read tag upto $start_block\n";
573     },
574 dpavlin 65 "D6 00 0F FE 00 00 05 01 $tag BEEF", sub {
575 dpavlin 41 print "FIXME: tag $tag ready? (expected block read instead)\n";
576     },
577 dpavlin 78 "D6 00 0D 02 06 $tag", sub {
578     my $rest = shift;
579     print "ERROR reading $tag ", as_hex($rest), $/;
580     forget_tag $tag;
581     $start_block = $max_rfid_block; # XXX break out of while
582     },
583 dpavlin 41 );
584    
585     }
586    
587 dpavlin 33 my $security;
588    
589     cmd(
590 dpavlin 65 "D6 00 0B 0A $tag BEEF", "check security $tag",
591 dpavlin 33 "D6 00 0D 0A 00", sub {
592     my $rest = shift;
593     my $from_tag;
594     ( $from_tag, $security ) = ( substr($rest,0,8), substr($rest,8,1) );
595     die "security from other tag: ",as_hex( $from_tag ) if $from_tag ne str2bytes( $tag );
596     $security = as_hex( $security );
597 dpavlin 54 $tags_security->{$tag} = $security;
598 dpavlin 33 warn "# SECURITY $tag = $security\n";
599 dpavlin 78 },
600     "D6 00 0C 0A 06", sub {
601     my $rest = shift;
602     warn "ERROR reading security from $rest\n";
603     forget_tag $tag;
604     },
605 dpavlin 33 );
606    
607 dpavlin 43 print "TAG $tag ", dump(decode_tag( $tag ));
608 dpavlin 16 }
609    
610 dpavlin 29 sub write_tag {
611 dpavlin 59 my ($tag,$data) = @_;
612 dpavlin 29
613     my $path = "$program_path/$tag";
614 dpavlin 59 $data = read_file( $path ) if -e $path;
615 dpavlin 29
616 dpavlin 59 die "no data" unless $data;
617    
618 dpavlin 38 my $hex_data;
619 dpavlin 29
620 dpavlin 38 if ( $data =~ s{^hex\s+}{} ) {
621     $hex_data = $data;
622     $hex_data =~ s{\s+}{}g;
623     } else {
624 dpavlin 29
625 dpavlin 38 $data .= "\0" x ( 4 - ( length($data) % 4 ) );
626 dpavlin 30
627 dpavlin 41 my $max_len = $max_rfid_block * 4;
628 dpavlin 30
629 dpavlin 38 if ( length($data) > $max_len ) {
630     $data = substr($data,0,$max_len);
631     warn "strip content to $max_len bytes\n";
632     }
633    
634     $hex_data = unpack('H*', $data);
635     }
636    
637     my $len = length($hex_data) / 2;
638 dpavlin 40 # pad to block size
639     $hex_data .= '00' x ( 4 - $len % 4 );
640     my $blocks = sprintf('%02x', length($hex_data) / 4);
641 dpavlin 38
642     print "write_tag $tag = ",dump( $data ), " [$len/$blocks] == $hex_data\n";
643    
644 dpavlin 29 cmd(
645 dpavlin 65 "d6 00 ff 04 $tag 00 $blocks 00 $hex_data BEEF", "write $tag",
646     "d6 00 0d 04 00 $tag $blocks BEEF", sub { assert() },
647 dpavlin 40 ); # foreach ( 1 .. 3 ); # XXX 3m software does this three times!
648 dpavlin 29
649     my $to = $path;
650     $to .= '.' . time();
651    
652     rename $path, $to;
653     print ">> $to\n";
654    
655 dpavlin 67 forget_tag $tag;
656 dpavlin 29 }
657    
658 dpavlin 67 sub secure_tag_with {
659     my ( $tag, $data ) = @_;
660    
661     cmd(
662     "d6 00 0c 09 $tag $data BEEF", "secure $tag -> $data",
663     "d6 00 0c 09 00 $tag BEEF", sub { assert() },
664     );
665    
666     forget_tag $tag;
667     }
668    
669 dpavlin 34 sub secure_tag {
670     my ($tag) = @_;
671    
672     my $path = "$secure_path/$tag";
673     my $data = substr(read_file( $path ),0,2);
674    
675 dpavlin 67 secure_tag_with( $tag, $data );
676 dpavlin 34
677     my $to = $path;
678     $to .= '.' . time();
679    
680     rename $path, $to;
681     print ">> $to\n";
682     }
683    
684 dpavlin 19 exit;
685    
686 dpavlin 1 for ( 1 .. 3 ) {
687    
688     # ++-->type 00-0a
689     # 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
690     # 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
691     # 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
692    
693     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 $_" );
694     warn "D6 00 0D 04 00 E00401003123AA26 07 CFF1 -- ack 7 block?\n";
695    
696     }
697     warn " D6 00 0F FE 00 00 05 01 E00401003123AA26 941A\n";
698    
699     cmd( 'D6 00 05 FE 00 05 FA 40', "port-write scan $_" ) foreach ( 1 .. 2 );
700    
701     cmd('D6 00 0C 09 E00401003123AA26 D7 3AF0', 'checkin?',
702     'D6 00 0C 09 00 E00401003123AA26 6A44 -- no?' );
703     cmd('D6 00 0C 09 E00401003123AA26 DA EB5D', 'checkout?',
704     'D6 00 0C 09 00 E00401003123AA26 6A44 -- no?' );
705    
706     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',
707     'D6 00 0D 04 00 E00401003123AA26 06 DFD0 -- ack 6 blocks' ) foreach ( 1 .. 3 );
708    
709     undef $port;
710     print "Port closed\n";
711    
712     sub writechunk
713     {
714     my $str=shift;
715     my $count = $port->write($str);
716 dpavlin 38 my $len = length($str);
717     die "wrong write length $count != $len in ",as_hex( $str ) if $count != $len;
718 dpavlin 19 print "#> ", as_hex( $str ), "\t[$count]\n" if $debug;
719 dpavlin 1 }
720    
721     sub as_hex {
722     my @out;
723     foreach my $str ( @_ ) {
724 dpavlin 78 my $hex = uc unpack( 'H*', $str );
725 dpavlin 2 $hex =~ s/(..)/$1 /g if length( $str ) > 2;
726 dpavlin 8 $hex =~ s/\s+$//;
727 dpavlin 1 push @out, $hex;
728     }
729 dpavlin 8 return join(' | ', @out);
730 dpavlin 1 }
731    
732     sub read_bytes {
733     my ( $len, $desc ) = @_;
734     my $data = '';
735     while ( length( $data ) < $len ) {
736     my ( $c, $b ) = $port->read(1);
737 dpavlin 28 die "no bytes on port: $!" unless defined $b;
738 dpavlin 82 warn "## got $c bytes: ", as_hex($b), "\n";
739 dpavlin 83 last if $c == 0;
740 dpavlin 1 $data .= $b;
741     }
742     $desc ||= '?';
743 dpavlin 4 warn "#< ", as_hex($data), "\t$desc\n" if $debug;
744 dpavlin 1 return $data;
745     }
746    
747 dpavlin 5 our $assert;
748 dpavlin 2
749 dpavlin 5 # my $rest = skip_assert( 3 );
750     sub skip_assert {
751     assert( 0, shift );
752     }
753    
754 dpavlin 2 sub assert {
755     my ( $from, $to ) = @_;
756    
757 dpavlin 5 $from ||= 0;
758 dpavlin 4 $to = length( $assert->{expect} ) if ! defined $to;
759    
760 dpavlin 2 my $p = substr( $assert->{payload}, $from, $to );
761     my $e = substr( $assert->{expect}, $from, $to );
762 dpavlin 3 warn "EXPECTED ",as_hex($e), " GOT ", as_hex($p), " [$from-$to] in ",dump( $assert ), "\n" if $e ne $p;
763 dpavlin 5
764     # return the rest
765     return substr( $assert->{payload}, $to );
766 dpavlin 2 }
767    
768 dpavlin 15 use Digest::CRC;
769    
770     sub crcccitt {
771     my $bytes = shift;
772     my $crc = Digest::CRC->new(
773     # midified CCITT to xor with 0xffff instead of 0x0000
774     width => 16, init => 0xffff, xorout => 0xffff, refout => 0, poly => 0x1021, refin => 0,
775     ) or die $!;
776     $crc->add( $bytes );
777     pack('n', $crc->digest);
778     }
779    
780 dpavlin 8 # my $checksum = checksum( $bytes );
781     # my $checksum = checksum( $bytes, $original_checksum );
782     sub checksum {
783     my ( $bytes, $checksum ) = @_;
784    
785 dpavlin 16 my $len = ord(substr($bytes,2,1));
786 dpavlin 17 my $len_real = length($bytes) - 1;
787 dpavlin 16
788 dpavlin 17 if ( $len_real != $len ) {
789     print "length wrong: $len_real != $len\n";
790 dpavlin 38 $bytes = substr($bytes,0,2) . chr($len_real) . substr($bytes,3);
791 dpavlin 17 }
792    
793 dpavlin 38 my $xor = crcccitt( substr($bytes,1) ); # skip D6
794     warn "## checksum ",dump( $bytes, $xor, $checksum ) if $debug;
795    
796 dpavlin 8 if ( defined $checksum && $xor ne $checksum ) {
797 dpavlin 65 warn "checksum error: ", as_hex($xor), " != ", as_hex($checksum), " data: ", as_hex($bytes), "\n" if $checksum ne "\xBE\xEF";
798 dpavlin 16 return $bytes . $xor;
799 dpavlin 8 }
800 dpavlin 16 return $bytes . $checksum;
801 dpavlin 8 }
802    
803 dpavlin 20 our $dispatch;
804    
805 dpavlin 1 sub readchunk {
806 dpavlin 43 # sleep 1; # FIXME remove
807 dpavlin 2
808 dpavlin 1 # read header of packet
809     my $header = read_bytes( 2, 'header' );
810 dpavlin 2 my $length = read_bytes( 1, 'length' );
811     my $len = ord($length);
812 dpavlin 1 my $data = read_bytes( $len, 'data' );
813    
814 dpavlin 2 my $payload = substr( $data, 0, -2 );
815     my $payload_len = length($data);
816     warn "## payload too short $payload_len != $len\n" if $payload_len != $len;
817 dpavlin 8
818 dpavlin 2 my $checksum = substr( $data, -2, 2 );
819 dpavlin 20 checksum( $header . $length . $payload , $checksum );
820 dpavlin 1
821 dpavlin 22 print "<< ",as_hex( $header ), " [$len] ", as_hex( $payload ), " | sum: ",as_hex($checksum),"\n" if $verbose;
822 dpavlin 2
823     $assert->{len} = $len;
824     $assert->{payload} = $payload;
825    
826 dpavlin 20 my $full = $header . $length . $data; # full
827     # find longest match for incomming data
828     my ($to) = grep {
829     my $match = substr($payload,0,length($_));
830     m/^\Q$match\E/
831     } sort { length($a) <=> length($b) } keys %$dispatch;
832     warn "?? payload dispatch to ",dump( $payload, $dispatch, $to ) if $debug;
833 dpavlin 2
834 dpavlin 42 if ( defined $to ) {
835     my $rest = substr( $payload, length($to) ) if length($to) < length($payload);
836 dpavlin 20 warn "## DISPATCH payload to with rest", dump( $payload, $to, $rest ) if $debug;
837     $dispatch->{ $to }->( $rest );
838     } else {
839 dpavlin 64 die "NO DISPATCH for ",as_hex( $full ),"\n";
840 dpavlin 20 }
841    
842 dpavlin 2 return $data;
843 dpavlin 1 }
844    
845 dpavlin 2 sub str2bytes {
846     my $str = shift || confess "no str?";
847 dpavlin 5 my $b = $str;
848 dpavlin 17 $b =~ s/\s+//g;
849     $b =~ s/(..)/\\x$1/g;
850     $b = "\"$b\"";
851 dpavlin 5 my $bytes = eval $b;
852 dpavlin 2 die $@ if $@;
853 dpavlin 5 warn "## str2bytes( $str ) => $b => ",as_hex($bytes) if $debug;
854 dpavlin 2 return $bytes;
855     }
856    
857     sub cmd {
858 dpavlin 20 my $cmd = shift || confess "no cmd?";
859     my $cmd_desc = shift || confess "no description?";
860     my @expect = @_;
861    
862 dpavlin 2 my $bytes = str2bytes( $cmd );
863    
864 dpavlin 16 # fix checksum if needed
865     $bytes = checksum( substr( $bytes, 0, -2 ), substr( $bytes, -2, 2 ) );
866    
867 dpavlin 22 warn ">> ", as_hex( $bytes ), "\t## $cmd_desc\n" if $verbose;
868 dpavlin 2 $assert->{send} = $cmd;
869     writechunk( $bytes );
870    
871 dpavlin 20 while ( @expect ) {
872     my $pattern = str2bytes( shift @expect ) || confess "no pattern?";
873     my $coderef = shift @expect || confess "no coderef?";
874     confess "not coderef" unless ref $coderef eq 'CODE';
875    
876     next if defined $dispatch->{ $pattern };
877    
878     $dispatch->{ substr($pattern,3) } = $coderef;
879     warn "++ dispatch ", as_hex($pattern) ,dump( $dispatch ) if $debug;
880 dpavlin 2 }
881 dpavlin 20
882     readchunk;
883 dpavlin 2 }
884    

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26