/[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 86 - (show annotations)
Fri Jul 16 09:31:56 2010 UTC (13 years, 10 months ago) by dpavlin
File MIME type: text/plain
File size: 21225 byte(s)
decode tags uid from inventory response

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

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26