/[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 84 - (show annotations)
Mon Jul 12 11:46:21 2010 UTC (13 years, 9 months ago) by dpavlin
File MIME type: text/plain
File size: 20580 byte(s)
added 0.050 s sleeps to give reader a chanance

documentation mentiones much shorter timeout, but
it seems that windows software work with delay of
0.045-0.047 (introduced by stack?) which works

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

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26