/[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 87 - (hide annotations)
Fri Jul 16 13:05:24 2010 UTC (13 years, 9 months ago) by dpavlin
File MIME type: text/plain
File size: 21483 byte(s)
read first blocks from card, cleanup PSST wait

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     # cpr( "FF B0 2B 01 $hex_uid", "Get System Information $hex_uid" );
343     }
344    
345    
346 dpavlin 86 my $inventory;
347 dpavlin 83
348 dpavlin 86 while(1) {
349    
350     cpr( 'FF B0 01 00', 'ISO - Inventory', sub {
351     my $data = shift;
352     my $data_sets = ord(substr($data,3,1));
353     $data = substr($data,4);
354     foreach ( 1 .. $data_sets ) {
355     my $tr_type = substr($data,0,1);
356 dpavlin 87 die "FIXME only TR-TYPE=3 ISO 15693 supported" unless $tr_type eq "\x03";
357 dpavlin 86 my $dsfid = substr($data,1,1);
358     my $uid = substr($data,2,8);
359     $inventory->{$uid}++;
360     $data = substr($data,10);
361     warn "# TAG $_ ",as_hex( $tr_type, $dsfid, $uid ),$/;
362 dpavlin 87
363     cpr_read( $uid );
364 dpavlin 86 }
365     warn "inventory: ",dump($inventory);
366     });
367    
368     }
369    
370 dpavlin 83 #cpr( '', '?' );
371    
372 dpavlin 82 exit;
373 dpavlin 4 # initial hand-shake with device
374    
375 dpavlin 20 cmd( 'D5 00 05 04 00 11 8C66', 'hw version',
376     'D5 00 09 04 00 11 0A 05 00 02 7250', sub {
377 dpavlin 23 my $hw_ver = join('.', unpack('CCCC', skip_assert(3) ));
378     print "hardware version $hw_ver\n";
379 dpavlin 2 });
380 dpavlin 1
381 dpavlin 20 cmd( 'D6 00 0C 13 04 01 00 02 00 03 00 04 00 AAF2','FIXME: stats?',
382     'D6 00 0C 13 00 02 01 01 03 02 02 03 00 E778', sub { assert() } );
383 dpavlin 1
384 dpavlin 43 sub scan_for_tags {
385 dpavlin 1
386 dpavlin 43 my @tags;
387 dpavlin 20
388 dpavlin 48 cmd( 'D6 00 05 FE 00 05 FA40', "scan for tags",
389 dpavlin 43 'D6 00 0F FE 00 00 05 ', sub { # 01 E00401003123AA26 941A # seen, serial length: 8
390     my $rest = shift || die "no rest?";
391     my $nr = ord( substr( $rest, 0, 1 ) );
392 dpavlin 20
393 dpavlin 43 if ( ! $nr ) {
394 dpavlin 48 _log "no tags in range\n";
395 dpavlin 43 update_visible_tags();
396     $tags_data = {};
397     } else {
398 dpavlin 1
399 dpavlin 43 my $tags = substr( $rest, 1 );
400     my $tl = length( $tags );
401     die "wrong length $tl for $nr tags: ",dump( $tags ) if $tl =! $nr * 8;
402 dpavlin 16
403 dpavlin 43 push @tags, uc(unpack('H16', substr($tags, $_ * 8, 8))) foreach ( 0 .. $nr - 1 );
404     warn "## tags ",as_hex($tags), " [$tl] = ",dump( $tags ) if $debug;
405 dpavlin 48 _log "$nr tags in range: ", join(',', @tags ) , "\n";
406 dpavlin 25
407 dpavlin 43 update_visible_tags( @tags );
408     }
409 dpavlin 5 }
410 dpavlin 43 );
411 dpavlin 5
412 dpavlin 48 diag "tags: ",dump( @tags );
413 dpavlin 43 return $tags_data;
414 dpavlin 22
415 dpavlin 43 }
416 dpavlin 22
417 dpavlin 43 # start scanning for tags
418    
419     if ( $http_server ) {
420     http_server;
421     } else {
422 dpavlin 58 while (1) {
423     scan_for_tags;
424     sleep 1;
425     }
426 dpavlin 43 }
427    
428     die "over and out";
429    
430 dpavlin 22 sub update_visible_tags {
431     my @tags = @_;
432    
433     my $last_visible_tags = $visible_tags;
434     $visible_tags = {};
435    
436     foreach my $tag ( @tags ) {
437 dpavlin 51 $visible_tags->{$tag}++;
438 dpavlin 22 if ( ! defined $last_visible_tags->{$tag} ) {
439 dpavlin 25 if ( defined $tags_data->{$tag} ) {
440 dpavlin 64 warn "$tag in range\n";
441 dpavlin 25 } else {
442     read_tag( $tag );
443     }
444 dpavlin 22 } else {
445     warn "## using cached data for $tag" if $debug;
446     }
447     delete $last_visible_tags->{$tag}; # leave just missing tags
448 dpavlin 29
449     if ( -e "$program_path/$tag" ) {
450     write_tag( $tag );
451     }
452 dpavlin 34 if ( -e "$secure_path/$tag" ) {
453     secure_tag( $tag );
454     }
455 dpavlin 22 }
456    
457     foreach my $tag ( keys %$last_visible_tags ) {
458 dpavlin 23 my $data = delete $tags_data->{$tag};
459 dpavlin 64 warn "$tag removed ", dump($data), $/;
460 dpavlin 22 }
461    
462     warn "## update_visible_tags(",dump( @tags ),") = ",dump( $visible_tags )," removed: ",dump( $last_visible_tags ), " data: ",dump( $tags_data ) if $debug;
463     }
464    
465 dpavlin 28 my $tag_data_block;
466 dpavlin 22
467 dpavlin 28 sub read_tag_data {
468     my ($start_block,$rest) = @_;
469     die "no rest?" unless $rest;
470 dpavlin 41
471     my $last_block = 0;
472    
473 dpavlin 28 warn "## DATA [$start_block] ", dump( $rest ) if $debug;
474     my $tag = uc(unpack('H16',substr( $rest, 0, 8 )));
475     my $blocks = ord(substr($rest,8,1));
476     $rest = substr($rest,9); # leave just data blocks
477     foreach my $nr ( 0 .. $blocks - 1 ) {
478     my $block = substr( $rest, $nr * 6, 6 );
479     warn "## block ",as_hex( $block ) if $debug;
480     my $ord = unpack('v',substr( $block, 0, 2 ));
481     my $expected_ord = $nr + $start_block;
482 dpavlin 41 warn "got block $ord, expected block $expected_ord from ",dump( $block ) if $ord != $expected_ord;
483 dpavlin 28 my $data = substr( $block, 2 );
484     die "data payload should be 4 bytes" if length($data) != 4;
485 dpavlin 40 warn sprintf "## tag %9s %02d: %s |%-4s|\n", $tag, $ord, as_hex( $data ), $data;
486 dpavlin 28 $tag_data_block->{$tag}->[ $ord ] = $data;
487 dpavlin 41 $last_block = $ord;
488 dpavlin 28 }
489     $tags_data->{ $tag } = join('', @{ $tag_data_block->{$tag} });
490 dpavlin 31
491     my $item_type_nr = ord(substr( $tags_data->{$tag}, 3, 1 ));
492 dpavlin 42 print "DATA $tag ",dump( $tags_data ), " item type: ", ( $item_type->{ $item_type_nr } || "UNKWOWN '$item_type_nr'" ), "\n";
493 dpavlin 41
494 dpavlin 42 return $last_block + 1;
495 dpavlin 28 }
496    
497 dpavlin 59 my $saved_in_log;
498    
499 dpavlin 43 sub decode_tag {
500     my $tag = shift;
501    
502 dpavlin 78 my $data = $tags_data->{$tag};
503     if ( ! $data ) {
504     warn "no data for $tag\n";
505     return;
506     }
507 dpavlin 43
508     my ( $u1, $set_item, $u2, $type, $content, $br_lib, $custom ) = unpack('C4Z16Nl>',$data);
509     my $hash = {
510     u1 => $u1,
511     u2 => $u2,
512     set => ( $set_item & 0xf0 ) >> 4,
513     total => ( $set_item & 0x0f ),
514    
515     type => $type,
516     content => $content,
517    
518     branch => $br_lib >> 20,
519     library => $br_lib & 0x000fffff,
520    
521     custom => $custom,
522     };
523    
524 dpavlin 59 if ( ! $saved_in_log->{$tag}++ ) {
525     open(my $log, '>>', 'rfid-log.txt');
526     print $log strftime( "%Y-%m-%d %H:%M:%S", localtime ), ",$tag,$content\n";
527     close($log);
528     }
529    
530 dpavlin 43 return $hash;
531     }
532    
533 dpavlin 67 sub forget_tag {
534     my $tag = shift;
535     delete $tags_data->{$tag};
536     delete $visible_tags->{$tag};
537     }
538    
539 dpavlin 16 sub read_tag {
540     my ( $tag ) = @_;
541 dpavlin 1
542 dpavlin 22 confess "no tag?" unless $tag;
543    
544 dpavlin 16 print "read_tag $tag\n";
545 dpavlin 1
546 dpavlin 41 my $start_block = 0;
547 dpavlin 28
548 dpavlin 41 while ( $start_block < $max_rfid_block ) {
549 dpavlin 1
550 dpavlin 41 cmd(
551 dpavlin 65 sprintf( "D6 00 0D 02 $tag %02x %02x BEEF", $start_block, $read_blocks ),
552 dpavlin 41 "read $tag offset: $start_block blocks: $read_blocks",
553     "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";
554     $start_block = read_tag_data( $start_block, @_ );
555     warn "# read tag upto $start_block\n";
556     },
557 dpavlin 65 "D6 00 0F FE 00 00 05 01 $tag BEEF", sub {
558 dpavlin 41 print "FIXME: tag $tag ready? (expected block read instead)\n";
559     },
560 dpavlin 78 "D6 00 0D 02 06 $tag", sub {
561     my $rest = shift;
562     print "ERROR reading $tag ", as_hex($rest), $/;
563     forget_tag $tag;
564     $start_block = $max_rfid_block; # XXX break out of while
565     },
566 dpavlin 41 );
567    
568     }
569    
570 dpavlin 33 my $security;
571    
572     cmd(
573 dpavlin 65 "D6 00 0B 0A $tag BEEF", "check security $tag",
574 dpavlin 33 "D6 00 0D 0A 00", sub {
575     my $rest = shift;
576     my $from_tag;
577     ( $from_tag, $security ) = ( substr($rest,0,8), substr($rest,8,1) );
578     die "security from other tag: ",as_hex( $from_tag ) if $from_tag ne str2bytes( $tag );
579     $security = as_hex( $security );
580 dpavlin 54 $tags_security->{$tag} = $security;
581 dpavlin 33 warn "# SECURITY $tag = $security\n";
582 dpavlin 78 },
583     "D6 00 0C 0A 06", sub {
584     my $rest = shift;
585     warn "ERROR reading security from $rest\n";
586     forget_tag $tag;
587     },
588 dpavlin 33 );
589    
590 dpavlin 43 print "TAG $tag ", dump(decode_tag( $tag ));
591 dpavlin 16 }
592    
593 dpavlin 29 sub write_tag {
594 dpavlin 59 my ($tag,$data) = @_;
595 dpavlin 29
596     my $path = "$program_path/$tag";
597 dpavlin 59 $data = read_file( $path ) if -e $path;
598 dpavlin 29
599 dpavlin 59 die "no data" unless $data;
600    
601 dpavlin 38 my $hex_data;
602 dpavlin 29
603 dpavlin 38 if ( $data =~ s{^hex\s+}{} ) {
604     $hex_data = $data;
605     $hex_data =~ s{\s+}{}g;
606     } else {
607 dpavlin 29
608 dpavlin 38 $data .= "\0" x ( 4 - ( length($data) % 4 ) );
609 dpavlin 30
610 dpavlin 41 my $max_len = $max_rfid_block * 4;
611 dpavlin 30
612 dpavlin 38 if ( length($data) > $max_len ) {
613     $data = substr($data,0,$max_len);
614     warn "strip content to $max_len bytes\n";
615     }
616    
617     $hex_data = unpack('H*', $data);
618     }
619    
620     my $len = length($hex_data) / 2;
621 dpavlin 40 # pad to block size
622     $hex_data .= '00' x ( 4 - $len % 4 );
623     my $blocks = sprintf('%02x', length($hex_data) / 4);
624 dpavlin 38
625     print "write_tag $tag = ",dump( $data ), " [$len/$blocks] == $hex_data\n";
626    
627 dpavlin 29 cmd(
628 dpavlin 65 "d6 00 ff 04 $tag 00 $blocks 00 $hex_data BEEF", "write $tag",
629     "d6 00 0d 04 00 $tag $blocks BEEF", sub { assert() },
630 dpavlin 40 ); # foreach ( 1 .. 3 ); # XXX 3m software does this three times!
631 dpavlin 29
632     my $to = $path;
633     $to .= '.' . time();
634    
635     rename $path, $to;
636     print ">> $to\n";
637    
638 dpavlin 67 forget_tag $tag;
639 dpavlin 29 }
640    
641 dpavlin 67 sub secure_tag_with {
642     my ( $tag, $data ) = @_;
643    
644     cmd(
645     "d6 00 0c 09 $tag $data BEEF", "secure $tag -> $data",
646     "d6 00 0c 09 00 $tag BEEF", sub { assert() },
647     );
648    
649     forget_tag $tag;
650     }
651    
652 dpavlin 34 sub secure_tag {
653     my ($tag) = @_;
654    
655     my $path = "$secure_path/$tag";
656     my $data = substr(read_file( $path ),0,2);
657    
658 dpavlin 67 secure_tag_with( $tag, $data );
659 dpavlin 34
660     my $to = $path;
661     $to .= '.' . time();
662    
663     rename $path, $to;
664     print ">> $to\n";
665     }
666    
667 dpavlin 19 exit;
668    
669 dpavlin 1 for ( 1 .. 3 ) {
670    
671     # ++-->type 00-0a
672     # 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
673     # 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
674     # 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
675    
676     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 $_" );
677     warn "D6 00 0D 04 00 E00401003123AA26 07 CFF1 -- ack 7 block?\n";
678    
679     }
680     warn " D6 00 0F FE 00 00 05 01 E00401003123AA26 941A\n";
681    
682     cmd( 'D6 00 05 FE 00 05 FA 40', "port-write scan $_" ) foreach ( 1 .. 2 );
683    
684     cmd('D6 00 0C 09 E00401003123AA26 D7 3AF0', 'checkin?',
685     'D6 00 0C 09 00 E00401003123AA26 6A44 -- no?' );
686     cmd('D6 00 0C 09 E00401003123AA26 DA EB5D', 'checkout?',
687     'D6 00 0C 09 00 E00401003123AA26 6A44 -- no?' );
688    
689     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',
690     'D6 00 0D 04 00 E00401003123AA26 06 DFD0 -- ack 6 blocks' ) foreach ( 1 .. 3 );
691    
692     undef $port;
693     print "Port closed\n";
694    
695     sub writechunk
696     {
697     my $str=shift;
698     my $count = $port->write($str);
699 dpavlin 38 my $len = length($str);
700     die "wrong write length $count != $len in ",as_hex( $str ) if $count != $len;
701 dpavlin 19 print "#> ", as_hex( $str ), "\t[$count]\n" if $debug;
702 dpavlin 1 }
703    
704     sub as_hex {
705     my @out;
706     foreach my $str ( @_ ) {
707 dpavlin 78 my $hex = uc unpack( 'H*', $str );
708 dpavlin 2 $hex =~ s/(..)/$1 /g if length( $str ) > 2;
709 dpavlin 8 $hex =~ s/\s+$//;
710 dpavlin 1 push @out, $hex;
711     }
712 dpavlin 8 return join(' | ', @out);
713 dpavlin 1 }
714    
715     sub read_bytes {
716     my ( $len, $desc ) = @_;
717     my $data = '';
718     while ( length( $data ) < $len ) {
719     my ( $c, $b ) = $port->read(1);
720 dpavlin 28 die "no bytes on port: $!" unless defined $b;
721 dpavlin 82 warn "## got $c bytes: ", as_hex($b), "\n";
722 dpavlin 83 last if $c == 0;
723 dpavlin 1 $data .= $b;
724     }
725     $desc ||= '?';
726 dpavlin 4 warn "#< ", as_hex($data), "\t$desc\n" if $debug;
727 dpavlin 1 return $data;
728     }
729    
730 dpavlin 5 our $assert;
731 dpavlin 2
732 dpavlin 5 # my $rest = skip_assert( 3 );
733     sub skip_assert {
734     assert( 0, shift );
735     }
736    
737 dpavlin 2 sub assert {
738     my ( $from, $to ) = @_;
739    
740 dpavlin 5 $from ||= 0;
741 dpavlin 4 $to = length( $assert->{expect} ) if ! defined $to;
742    
743 dpavlin 2 my $p = substr( $assert->{payload}, $from, $to );
744     my $e = substr( $assert->{expect}, $from, $to );
745 dpavlin 3 warn "EXPECTED ",as_hex($e), " GOT ", as_hex($p), " [$from-$to] in ",dump( $assert ), "\n" if $e ne $p;
746 dpavlin 5
747     # return the rest
748     return substr( $assert->{payload}, $to );
749 dpavlin 2 }
750    
751 dpavlin 15 use Digest::CRC;
752    
753     sub crcccitt {
754     my $bytes = shift;
755     my $crc = Digest::CRC->new(
756     # midified CCITT to xor with 0xffff instead of 0x0000
757     width => 16, init => 0xffff, xorout => 0xffff, refout => 0, poly => 0x1021, refin => 0,
758     ) or die $!;
759     $crc->add( $bytes );
760     pack('n', $crc->digest);
761     }
762    
763 dpavlin 8 # my $checksum = checksum( $bytes );
764     # my $checksum = checksum( $bytes, $original_checksum );
765     sub checksum {
766     my ( $bytes, $checksum ) = @_;
767    
768 dpavlin 16 my $len = ord(substr($bytes,2,1));
769 dpavlin 17 my $len_real = length($bytes) - 1;
770 dpavlin 16
771 dpavlin 17 if ( $len_real != $len ) {
772     print "length wrong: $len_real != $len\n";
773 dpavlin 38 $bytes = substr($bytes,0,2) . chr($len_real) . substr($bytes,3);
774 dpavlin 17 }
775    
776 dpavlin 38 my $xor = crcccitt( substr($bytes,1) ); # skip D6
777     warn "## checksum ",dump( $bytes, $xor, $checksum ) if $debug;
778    
779 dpavlin 8 if ( defined $checksum && $xor ne $checksum ) {
780 dpavlin 65 warn "checksum error: ", as_hex($xor), " != ", as_hex($checksum), " data: ", as_hex($bytes), "\n" if $checksum ne "\xBE\xEF";
781 dpavlin 16 return $bytes . $xor;
782 dpavlin 8 }
783 dpavlin 16 return $bytes . $checksum;
784 dpavlin 8 }
785    
786 dpavlin 20 our $dispatch;
787    
788 dpavlin 1 sub readchunk {
789 dpavlin 43 # sleep 1; # FIXME remove
790 dpavlin 2
791 dpavlin 1 # read header of packet
792     my $header = read_bytes( 2, 'header' );
793 dpavlin 2 my $length = read_bytes( 1, 'length' );
794     my $len = ord($length);
795 dpavlin 1 my $data = read_bytes( $len, 'data' );
796    
797 dpavlin 2 my $payload = substr( $data, 0, -2 );
798     my $payload_len = length($data);
799     warn "## payload too short $payload_len != $len\n" if $payload_len != $len;
800 dpavlin 8
801 dpavlin 2 my $checksum = substr( $data, -2, 2 );
802 dpavlin 20 checksum( $header . $length . $payload , $checksum );
803 dpavlin 1
804 dpavlin 22 print "<< ",as_hex( $header ), " [$len] ", as_hex( $payload ), " | sum: ",as_hex($checksum),"\n" if $verbose;
805 dpavlin 2
806     $assert->{len} = $len;
807     $assert->{payload} = $payload;
808    
809 dpavlin 20 my $full = $header . $length . $data; # full
810     # find longest match for incomming data
811     my ($to) = grep {
812     my $match = substr($payload,0,length($_));
813     m/^\Q$match\E/
814     } sort { length($a) <=> length($b) } keys %$dispatch;
815     warn "?? payload dispatch to ",dump( $payload, $dispatch, $to ) if $debug;
816 dpavlin 2
817 dpavlin 42 if ( defined $to ) {
818     my $rest = substr( $payload, length($to) ) if length($to) < length($payload);
819 dpavlin 20 warn "## DISPATCH payload to with rest", dump( $payload, $to, $rest ) if $debug;
820     $dispatch->{ $to }->( $rest );
821     } else {
822 dpavlin 64 die "NO DISPATCH for ",as_hex( $full ),"\n";
823 dpavlin 20 }
824    
825 dpavlin 2 return $data;
826 dpavlin 1 }
827    
828 dpavlin 2 sub str2bytes {
829     my $str = shift || confess "no str?";
830 dpavlin 5 my $b = $str;
831 dpavlin 17 $b =~ s/\s+//g;
832     $b =~ s/(..)/\\x$1/g;
833     $b = "\"$b\"";
834 dpavlin 5 my $bytes = eval $b;
835 dpavlin 2 die $@ if $@;
836 dpavlin 5 warn "## str2bytes( $str ) => $b => ",as_hex($bytes) if $debug;
837 dpavlin 2 return $bytes;
838     }
839    
840     sub cmd {
841 dpavlin 20 my $cmd = shift || confess "no cmd?";
842     my $cmd_desc = shift || confess "no description?";
843     my @expect = @_;
844    
845 dpavlin 2 my $bytes = str2bytes( $cmd );
846    
847 dpavlin 16 # fix checksum if needed
848     $bytes = checksum( substr( $bytes, 0, -2 ), substr( $bytes, -2, 2 ) );
849    
850 dpavlin 22 warn ">> ", as_hex( $bytes ), "\t## $cmd_desc\n" if $verbose;
851 dpavlin 2 $assert->{send} = $cmd;
852     writechunk( $bytes );
853    
854 dpavlin 20 while ( @expect ) {
855     my $pattern = str2bytes( shift @expect ) || confess "no pattern?";
856     my $coderef = shift @expect || confess "no coderef?";
857     confess "not coderef" unless ref $coderef eq 'CODE';
858    
859     next if defined $dispatch->{ $pattern };
860    
861     $dispatch->{ substr($pattern,3) } = $coderef;
862     warn "++ dispatch ", as_hex($pattern) ,dump( $dispatch ) if $debug;
863 dpavlin 2 }
864 dpavlin 20
865     readchunk;
866 dpavlin 2 }
867    

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26