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

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26