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

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26