/[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

Contents of /cpr-m02.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 87 - (show 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 #!/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 use JSON;
12 use POSIX qw(strftime);
13 use Time::HiRes;
14
15 use IO::Socket::INET;
16
17 my $debug = 0;
18
19 my $tags_data;
20 my $tags_security;
21 my $visible_tags;
22
23 my $listen_port = 9000; # pick something not in use
24 my $server_url = "http://localhost:$listen_port";
25
26 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 die "can't setup server: $!" unless $server;
36
37 print "Server $0 ready at $server_url\n";
38
39 sub static {
40 my ($client,$path) = @_;
41
42 $path = "www/$path";
43 $path .= 'rfid.html' if $path =~ m{/$};
44
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 warn "WEB << $request\n" if $debug;
66
67 if ($request =~ m{^GET (/.*) HTTP/1.[01]}) {
68 my $method = $1;
69 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 warn "WEB << param: ",dump( $param ) if $debug;
76 }
77 if ( my $path = static( $client,$1 ) ) {
78 warn "WEB >> $path" if $debug;
79 } elsif ( $method =~ m{/scan} ) {
80 my $tags = scan_for_tags();
81 my $json = { time => time() };
82 map {
83 my $d = decode_tag($_);
84 $d->{sid} = $_;
85 $d->{security} = $tags_security->{$_};
86 push @{ $json->{tags} }, $d;
87 } keys %$tags;
88 print $client "HTTP/1.0 200 OK\r\nContent-Type: application/json\r\n\r\n",
89 $param->{callback}, "(", to_json($json), ")\r\n";
90 } elsif ( $method =~ m{/program} ) {
91
92 my $status = 501; # Not implementd
93
94 foreach my $p ( keys %$param ) {
95 next unless $p =~ m/^(E[0-9A-F]{15})$/;
96 my $tag = $1;
97 my $content = "\x04\x11\x00\x01" . $param->{$p};
98 $content = "\x00" if $param->{$p} eq 'blank';
99 $status = 302;
100
101 warn "PROGRAM $tag $content\n";
102 write_tag( $tag, $content );
103 secure_tag_with( $tag, $param->{$p} =~ /^130/ ? 'DA' : 'D7' );
104 }
105
106 print $client "HTTP/1.0 $status $method\r\nLocation: $server_url\r\n\r\n";
107
108 } elsif ( $method =~ m{/secure(.js)} ) {
109
110 my $json = $1;
111
112 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 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
131 } else {
132 print $client "HTTP/1.0 404 Unkown method\r\n\r\n";
133 }
134 } else {
135 print $client "HTTP/1.0 500 No method\r\n\r\n";
136 }
137 close $client;
138 }
139
140 die "server died";
141 }
142
143
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 my $device = "/dev/ttyUSB0";
159 my $baudrate = "38400";
160 my $databits = "8";
161 my $parity = "even";
162 my $stopbits = "1";
163 my $handshake = "none";
164
165 my $program_path = './program/';
166 my $secure_path = './secure/';
167
168 # http server
169 my $http_server = 1;
170
171 # 3M defaults: 8,4
172 # cards 16, stickers: 8
173 my $max_rfid_block = 8;
174 my $read_blocks = 8;
175
176 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 GetOptions(
189 'd|debug+' => \$debug,
190 'device=s' => \$device,
191 'baudrate=i' => \$baudrate,
192 'databits=i' => \$databits,
193 'parity=s' => \$parity,
194 'stopbits=i' => \$stopbits,
195 'handshake=s' => \$handshake,
196 'http-server!' => \$http_server,
197 ) or die $!;
198
199 my $verbose = $debug > 0 ? $debug-- : 0;
200
201 =head1 NAME
202
203 3m-810 - support for 3M 810 RFID reader
204
205 =head1 SYNOPSIS
206
207 3m-810.pl --device /dev/ttyUSB0
208
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 L<http://stackoverflow.com/questions/149617/how-could-i-guess-a-checksum-algorithm>
220
221 =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 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 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 $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 warn "## using $device $baudrate $databits $parity $stopbits debug: $debug verbose: $verbose\n";
258
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 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 # warn sprintf('%d %04x', $i, $crc & 0xffff);
285 }
286
287 return pack('v', $crc);
288 }
289
290 sub cpr_psst_wait {
291 # Protocol Start Synchronization Time (PSST): 5ms < data timeout 12 ms
292 Time::HiRes::sleep 0.005;
293 }
294
295 sub cpr {
296 my ( $hex, $description, $coderef ) = @_;
297 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 warn ">> ", as_hex( $send ), "\t\t[$description]\n";
304 $port->write( $send );
305
306 cpr_psst_wait;
307
308 my $r_len = $port->read(1);
309
310 while ( ! $r_len ) {
311 warn "# wait for response length 5ms\n";
312 cpr_psst_wait;
313 $r_len = $port->read(1);
314 }
315
316 my $data_len = ord($r_len) - 1;
317 my $data = $port->read( $data_len );
318 warn "<< ", as_hex( $r_len . $data ),"\n";
319
320 cpr_psst_wait;
321
322 $coderef->( $data ) if $coderef;
323
324 }
325
326 # FF = COM-ADDR any
327
328 cpr( 'FF 52 00', 'Boud Rate Detection' );
329
330 cpr( 'FF 65', 'Get Software Version' );
331
332 cpr( 'FF 66 00', 'Get Reader Info - General hard and firware' );
333
334 cpr( 'FF 69', 'RF Reset' );
335
336
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 my $inventory;
347
348 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 die "FIXME only TR-TYPE=3 ISO 15693 supported" unless $tr_type eq "\x03";
357 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
363 cpr_read( $uid );
364 }
365 warn "inventory: ",dump($inventory);
366 });
367
368 }
369
370 #cpr( '', '?' );
371
372 exit;
373 # initial hand-shake with device
374
375 cmd( 'D5 00 05 04 00 11 8C66', 'hw version',
376 'D5 00 09 04 00 11 0A 05 00 02 7250', sub {
377 my $hw_ver = join('.', unpack('CCCC', skip_assert(3) ));
378 print "hardware version $hw_ver\n";
379 });
380
381 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
384 sub scan_for_tags {
385
386 my @tags;
387
388 cmd( 'D6 00 05 FE 00 05 FA40', "scan for tags",
389 '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
393 if ( ! $nr ) {
394 _log "no tags in range\n";
395 update_visible_tags();
396 $tags_data = {};
397 } else {
398
399 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
403 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 _log "$nr tags in range: ", join(',', @tags ) , "\n";
406
407 update_visible_tags( @tags );
408 }
409 }
410 );
411
412 diag "tags: ",dump( @tags );
413 return $tags_data;
414
415 }
416
417 # start scanning for tags
418
419 if ( $http_server ) {
420 http_server;
421 } else {
422 while (1) {
423 scan_for_tags;
424 sleep 1;
425 }
426 }
427
428 die "over and out";
429
430 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 $visible_tags->{$tag}++;
438 if ( ! defined $last_visible_tags->{$tag} ) {
439 if ( defined $tags_data->{$tag} ) {
440 warn "$tag in range\n";
441 } else {
442 read_tag( $tag );
443 }
444 } else {
445 warn "## using cached data for $tag" if $debug;
446 }
447 delete $last_visible_tags->{$tag}; # leave just missing tags
448
449 if ( -e "$program_path/$tag" ) {
450 write_tag( $tag );
451 }
452 if ( -e "$secure_path/$tag" ) {
453 secure_tag( $tag );
454 }
455 }
456
457 foreach my $tag ( keys %$last_visible_tags ) {
458 my $data = delete $tags_data->{$tag};
459 warn "$tag removed ", dump($data), $/;
460 }
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 my $tag_data_block;
466
467 sub read_tag_data {
468 my ($start_block,$rest) = @_;
469 die "no rest?" unless $rest;
470
471 my $last_block = 0;
472
473 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 warn "got block $ord, expected block $expected_ord from ",dump( $block ) if $ord != $expected_ord;
483 my $data = substr( $block, 2 );
484 die "data payload should be 4 bytes" if length($data) != 4;
485 warn sprintf "## tag %9s %02d: %s |%-4s|\n", $tag, $ord, as_hex( $data ), $data;
486 $tag_data_block->{$tag}->[ $ord ] = $data;
487 $last_block = $ord;
488 }
489 $tags_data->{ $tag } = join('', @{ $tag_data_block->{$tag} });
490
491 my $item_type_nr = ord(substr( $tags_data->{$tag}, 3, 1 ));
492 print "DATA $tag ",dump( $tags_data ), " item type: ", ( $item_type->{ $item_type_nr } || "UNKWOWN '$item_type_nr'" ), "\n";
493
494 return $last_block + 1;
495 }
496
497 my $saved_in_log;
498
499 sub decode_tag {
500 my $tag = shift;
501
502 my $data = $tags_data->{$tag};
503 if ( ! $data ) {
504 warn "no data for $tag\n";
505 return;
506 }
507
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 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 return $hash;
531 }
532
533 sub forget_tag {
534 my $tag = shift;
535 delete $tags_data->{$tag};
536 delete $visible_tags->{$tag};
537 }
538
539 sub read_tag {
540 my ( $tag ) = @_;
541
542 confess "no tag?" unless $tag;
543
544 print "read_tag $tag\n";
545
546 my $start_block = 0;
547
548 while ( $start_block < $max_rfid_block ) {
549
550 cmd(
551 sprintf( "D6 00 0D 02 $tag %02x %02x BEEF", $start_block, $read_blocks ),
552 "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 "D6 00 0F FE 00 00 05 01 $tag BEEF", sub {
558 print "FIXME: tag $tag ready? (expected block read instead)\n";
559 },
560 "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 );
567
568 }
569
570 my $security;
571
572 cmd(
573 "D6 00 0B 0A $tag BEEF", "check security $tag",
574 "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 $tags_security->{$tag} = $security;
581 warn "# SECURITY $tag = $security\n";
582 },
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 );
589
590 print "TAG $tag ", dump(decode_tag( $tag ));
591 }
592
593 sub write_tag {
594 my ($tag,$data) = @_;
595
596 my $path = "$program_path/$tag";
597 $data = read_file( $path ) if -e $path;
598
599 die "no data" unless $data;
600
601 my $hex_data;
602
603 if ( $data =~ s{^hex\s+}{} ) {
604 $hex_data = $data;
605 $hex_data =~ s{\s+}{}g;
606 } else {
607
608 $data .= "\0" x ( 4 - ( length($data) % 4 ) );
609
610 my $max_len = $max_rfid_block * 4;
611
612 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 # pad to block size
622 $hex_data .= '00' x ( 4 - $len % 4 );
623 my $blocks = sprintf('%02x', length($hex_data) / 4);
624
625 print "write_tag $tag = ",dump( $data ), " [$len/$blocks] == $hex_data\n";
626
627 cmd(
628 "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 ); # foreach ( 1 .. 3 ); # XXX 3m software does this three times!
631
632 my $to = $path;
633 $to .= '.' . time();
634
635 rename $path, $to;
636 print ">> $to\n";
637
638 forget_tag $tag;
639 }
640
641 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 sub secure_tag {
653 my ($tag) = @_;
654
655 my $path = "$secure_path/$tag";
656 my $data = substr(read_file( $path ),0,2);
657
658 secure_tag_with( $tag, $data );
659
660 my $to = $path;
661 $to .= '.' . time();
662
663 rename $path, $to;
664 print ">> $to\n";
665 }
666
667 exit;
668
669 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 my $len = length($str);
700 die "wrong write length $count != $len in ",as_hex( $str ) if $count != $len;
701 print "#> ", as_hex( $str ), "\t[$count]\n" if $debug;
702 }
703
704 sub as_hex {
705 my @out;
706 foreach my $str ( @_ ) {
707 my $hex = uc unpack( 'H*', $str );
708 $hex =~ s/(..)/$1 /g if length( $str ) > 2;
709 $hex =~ s/\s+$//;
710 push @out, $hex;
711 }
712 return join(' | ', @out);
713 }
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 die "no bytes on port: $!" unless defined $b;
721 warn "## got $c bytes: ", as_hex($b), "\n";
722 last if $c == 0;
723 $data .= $b;
724 }
725 $desc ||= '?';
726 warn "#< ", as_hex($data), "\t$desc\n" if $debug;
727 return $data;
728 }
729
730 our $assert;
731
732 # my $rest = skip_assert( 3 );
733 sub skip_assert {
734 assert( 0, shift );
735 }
736
737 sub assert {
738 my ( $from, $to ) = @_;
739
740 $from ||= 0;
741 $to = length( $assert->{expect} ) if ! defined $to;
742
743 my $p = substr( $assert->{payload}, $from, $to );
744 my $e = substr( $assert->{expect}, $from, $to );
745 warn "EXPECTED ",as_hex($e), " GOT ", as_hex($p), " [$from-$to] in ",dump( $assert ), "\n" if $e ne $p;
746
747 # return the rest
748 return substr( $assert->{payload}, $to );
749 }
750
751 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 # my $checksum = checksum( $bytes );
764 # my $checksum = checksum( $bytes, $original_checksum );
765 sub checksum {
766 my ( $bytes, $checksum ) = @_;
767
768 my $len = ord(substr($bytes,2,1));
769 my $len_real = length($bytes) - 1;
770
771 if ( $len_real != $len ) {
772 print "length wrong: $len_real != $len\n";
773 $bytes = substr($bytes,0,2) . chr($len_real) . substr($bytes,3);
774 }
775
776 my $xor = crcccitt( substr($bytes,1) ); # skip D6
777 warn "## checksum ",dump( $bytes, $xor, $checksum ) if $debug;
778
779 if ( defined $checksum && $xor ne $checksum ) {
780 warn "checksum error: ", as_hex($xor), " != ", as_hex($checksum), " data: ", as_hex($bytes), "\n" if $checksum ne "\xBE\xEF";
781 return $bytes . $xor;
782 }
783 return $bytes . $checksum;
784 }
785
786 our $dispatch;
787
788 sub readchunk {
789 # sleep 1; # FIXME remove
790
791 # read header of packet
792 my $header = read_bytes( 2, 'header' );
793 my $length = read_bytes( 1, 'length' );
794 my $len = ord($length);
795 my $data = read_bytes( $len, 'data' );
796
797 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
801 my $checksum = substr( $data, -2, 2 );
802 checksum( $header . $length . $payload , $checksum );
803
804 print "<< ",as_hex( $header ), " [$len] ", as_hex( $payload ), " | sum: ",as_hex($checksum),"\n" if $verbose;
805
806 $assert->{len} = $len;
807 $assert->{payload} = $payload;
808
809 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
817 if ( defined $to ) {
818 my $rest = substr( $payload, length($to) ) if length($to) < length($payload);
819 warn "## DISPATCH payload to with rest", dump( $payload, $to, $rest ) if $debug;
820 $dispatch->{ $to }->( $rest );
821 } else {
822 die "NO DISPATCH for ",as_hex( $full ),"\n";
823 }
824
825 return $data;
826 }
827
828 sub str2bytes {
829 my $str = shift || confess "no str?";
830 my $b = $str;
831 $b =~ s/\s+//g;
832 $b =~ s/(..)/\\x$1/g;
833 $b = "\"$b\"";
834 my $bytes = eval $b;
835 die $@ if $@;
836 warn "## str2bytes( $str ) => $b => ",as_hex($bytes) if $debug;
837 return $bytes;
838 }
839
840 sub cmd {
841 my $cmd = shift || confess "no cmd?";
842 my $cmd_desc = shift || confess "no description?";
843 my @expect = @_;
844
845 my $bytes = str2bytes( $cmd );
846
847 # fix checksum if needed
848 $bytes = checksum( substr( $bytes, 0, -2 ), substr( $bytes, -2, 2 ) );
849
850 warn ">> ", as_hex( $bytes ), "\t## $cmd_desc\n" if $verbose;
851 $assert->{send} = $cmd;
852 writechunk( $bytes );
853
854 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 }
864
865 readchunk;
866 }
867

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26