/[RFID]/3m-810.pl
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Annotation of /3m-810.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 67 - (hide annotations)
Thu Feb 11 14:59:56 2010 UTC (14 years, 2 months ago) by dpavlin
File MIME type: text/plain
File size: 18670 byte(s)
added /secure REST API

1 dpavlin 1 #!/usr/bin/perl
2    
3     use Device::SerialPort qw (:STAT);
4     use strict;
5     use warnings;
6    
7     use Data::Dump qw/dump/;
8 dpavlin 2 use Carp qw/confess/;
9 dpavlin 19 use Getopt::Long;
10 dpavlin 29 use File::Slurp;
11 dpavlin 44 use JSON;
12 dpavlin 59 use POSIX qw(strftime);
13 dpavlin 1
14 dpavlin 23 use IO::Socket::INET;
15    
16 dpavlin 50 my $debug = 0;
17    
18 dpavlin 54 my $tags_data;
19     my $tags_security;
20     my $visible_tags;
21    
22 dpavlin 43 my $listen_port = 9000; # pick something not in use
23 dpavlin 59 my $server_url = "http://localhost:$listen_port";
24    
25 dpavlin 43 sub http_server {
26    
27     my $server = IO::Socket::INET->new(
28     Proto => 'tcp',
29     LocalPort => $listen_port,
30     Listen => SOMAXCONN,
31     Reuse => 1
32     );
33    
34     die "can't setup server" unless $server;
35    
36 dpavlin 59 print "Server $0 ready at $server_url\n";
37 dpavlin 43
38     sub static {
39     my ($client,$path) = @_;
40    
41     $path = "www/$path";
42 dpavlin 56 $path .= 'rfid.html' if $path =~ m{/$};
43 dpavlin 43
44     return unless -e $path;
45    
46     my $type = 'text/plain';
47     $type = 'text/html' if $path =~ m{\.htm};
48     $type = 'application/javascript' if $path =~ m{\.js};
49    
50     print $client "HTTP/1.0 200 OK\r\nContent-Type: $type\r\n\r\n";
51     open(my $html, $path);
52     while(<$html>) {
53     print $client $_;
54     }
55     close($html);
56    
57     return $path;
58     }
59    
60     while (my $client = $server->accept()) {
61     $client->autoflush(1);
62     my $request = <$client>;
63    
64 dpavlin 50 warn "WEB << $request\n" if $debug;
65 dpavlin 43
66     if ($request =~ m{^GET (/.*) HTTP/1.[01]}) {
67     my $method = $1;
68 dpavlin 46 my $param;
69     if ( $method =~ s{\?(.+)}{} ) {
70     foreach my $p ( split(/[&;]/, $1) ) {
71     my ($n,$v) = split(/=/, $p, 2);
72     $param->{$n} = $v;
73     }
74 dpavlin 50 warn "WEB << param: ",dump( $param ) if $debug;
75 dpavlin 46 }
76 dpavlin 43 if ( my $path = static( $client,$1 ) ) {
77 dpavlin 50 warn "WEB >> $path" if $debug;
78 dpavlin 43 } elsif ( $method =~ m{/scan} ) {
79     my $tags = scan_for_tags();
80 dpavlin 52 my $json = { time => time() };
81 dpavlin 44 map {
82     my $d = decode_tag($_);
83     $d->{sid} = $_;
84 dpavlin 54 $d->{security} = $tags_security->{$_};
85 dpavlin 44 push @{ $json->{tags} }, $d;
86     } keys %$tags;
87 dpavlin 46 print $client "HTTP/1.0 200 OK\r\nContent-Type: application/x-javascript\r\n\r\n",
88     $param->{callback}, "(", to_json($json), ")\r\n";
89 dpavlin 59 } elsif ( $method =~ m{/program} ) {
90    
91     my $status = 501; # Not implementd
92    
93     foreach my $p ( keys %$param ) {
94 dpavlin 66 next unless $p =~ m/^(E[0-9A-F]{15})$/;
95 dpavlin 59 my $tag = $1;
96 dpavlin 61 my $content = "\x04\x11\x00\x01" . $param->{$p};
97 dpavlin 63 $content = "\x00" if $param->{$p} eq 'blank';
98 dpavlin 59 $status = 302;
99    
100     warn "PROGRAM $tag $content\n";
101     write_tag( $tag, $content );
102     }
103    
104     print $client "HTTP/1.0 $status $method\r\nLocation: $server_url\r\n\r\n";
105    
106 dpavlin 67 } elsif ( $method =~ m{/secure} ) {
107    
108     my $status = 501; # Not implementd
109    
110     foreach my $p ( keys %$param ) {
111     next unless $p =~ m/^(E[0-9A-F]{15})$/;
112     my $tag = $1;
113     my $data = $param->{$p};
114     $status = 302;
115    
116     warn "SECURE $tag $data\n";
117     secure_tag_with( $tag, $data );
118     }
119    
120     print $client "HTTP/1.0 $status $method\r\nLocation: $server_url\r\n\r\n";
121    
122 dpavlin 43 } else {
123     print $client "HTTP/1.0 404 Unkown method\r\n";
124     }
125     } else {
126     print $client "HTTP/1.0 500 No method\r\n";
127     }
128     close $client;
129     }
130    
131     die "server died";
132     }
133    
134 dpavlin 48
135     my $last_message = {};
136     sub _message {
137     my $type = shift @_;
138     my $text = join(' ',@_);
139     my $last = $last_message->{$type};
140     if ( $text ne $last ) {
141     warn $type eq 'diag' ? '# ' : '', $text, "\n";
142     $last_message->{$type} = $text;
143     }
144     }
145    
146     sub _log { _message('log',@_) };
147     sub diag { _message('diag',@_) };
148    
149 dpavlin 19 my $device = "/dev/ttyUSB0";
150     my $baudrate = "19200";
151     my $databits = "8";
152     my $parity = "none";
153     my $stopbits = "1";
154     my $handshake = "none";
155    
156 dpavlin 29 my $program_path = './program/';
157 dpavlin 34 my $secure_path = './secure/';
158 dpavlin 29
159 dpavlin 43 # http server
160     my $http_server = 1;
161    
162 dpavlin 41 # 3M defaults: 8,4
163     my $max_rfid_block = 16;
164     my $read_blocks = 8;
165    
166 dpavlin 1 my $response = {
167     'd500090400110a0500027250' => 'version?',
168     'd60007fe00000500c97b' => 'no tag in range',
169    
170     'd6000ffe00000501e00401003123aa26941a' => 'tag #1',
171     'd6000ffe00000501e0040100017c0c388e2b' => 'rfid card',
172     'd6000ffe00000501e00401003123aa2875d4' => 'tag red-stripe',
173    
174     'd60017fe00000502e00401003123aa26e0040100017c0c38cadb' => 'tag #1 + card',
175     'd60017fe00000502e00401003123aa26e00401003123aa283124' => 'tag #1 + tag red-stripe',
176     };
177    
178 dpavlin 19 GetOptions(
179 dpavlin 22 'd|debug+' => \$debug,
180 dpavlin 19 'device=s' => \$device,
181     'baudrate=i' => \$baudrate,
182     'databits=i' => \$databits,
183     'parity=s' => \$parity,
184     'stopbits=i' => \$stopbits,
185     'handshake=s' => \$handshake,
186 dpavlin 45 'http-server!' => \$http_server,
187 dpavlin 19 ) or die $!;
188    
189 dpavlin 22 my $verbose = $debug > 0 ? $debug-- : 0;
190    
191 dpavlin 1 =head1 NAME
192    
193     3m-810 - support for 3M 810 RFID reader
194    
195     =head1 SYNOPSIS
196    
197 dpavlin 19 3m-810.pl --device /dev/ttyUSB0
198 dpavlin 1
199     =head1 DESCRIPTION
200    
201     Communicate with 3M 810 RFID reader and document it's protocol
202    
203     =head1 SEE ALSO
204    
205     L<Device::SerialPort(3)>
206    
207     L<perl(1)>
208    
209 dpavlin 15 L<http://stackoverflow.com/questions/149617/how-could-i-guess-a-checksum-algorithm>
210    
211 dpavlin 1 =head1 AUTHOR
212    
213     Dobrica Pavlinusic <dpavlin@rot13.org> L<http://www.rot13.org/~dpavlin/>
214    
215     =head1 COPYRIGHT AND LICENSE
216    
217     This program is free software; you may redistribute it and/or modify
218     it under the same terms ans Perl itself.
219    
220     =cut
221    
222 dpavlin 31 my $item_type = {
223     1 => 'Book',
224     6 => 'CD/CD ROM',
225     2 => 'Magazine',
226     13 => 'Book with Audio Tape',
227     9 => 'Book with CD/CD ROM',
228     0 => 'Other',
229    
230     5 => 'Video',
231     4 => 'Audio Tape',
232     3 => 'Bound Journal',
233     8 => 'Book with Diskette',
234     7 => 'Diskette',
235     };
236    
237     warn "## known item type: ",dump( $item_type ) if $debug;
238    
239 dpavlin 19 my $port=new Device::SerialPort($device) || die "can't open serial port $device: $!\n";
240     warn "using $device $handshake $baudrate $databits $parity $stopbits" if $debug;
241 dpavlin 1 $handshake=$port->handshake($handshake);
242     $baudrate=$port->baudrate($baudrate);
243     $databits=$port->databits($databits);
244     $parity=$port->parity($parity);
245     $stopbits=$port->stopbits($stopbits);
246    
247 dpavlin 48 warn "## using $device $baudrate $databits $parity $stopbits debug: $debug verbose: $verbose\n";
248 dpavlin 1
249     # Just in case: reset our timing and buffers
250     $port->lookclear();
251     $port->read_const_time(100);
252     $port->read_char_time(5);
253    
254     # Turn on parity checking:
255     #$port->stty_inpck(1);
256     #$port->stty_istrip(1);
257    
258 dpavlin 4 # initial hand-shake with device
259    
260 dpavlin 20 cmd( 'D5 00 05 04 00 11 8C66', 'hw version',
261     'D5 00 09 04 00 11 0A 05 00 02 7250', sub {
262 dpavlin 23 my $hw_ver = join('.', unpack('CCCC', skip_assert(3) ));
263     print "hardware version $hw_ver\n";
264 dpavlin 2 });
265 dpavlin 1
266 dpavlin 20 cmd( 'D6 00 0C 13 04 01 00 02 00 03 00 04 00 AAF2','FIXME: stats?',
267     'D6 00 0C 13 00 02 01 01 03 02 02 03 00 E778', sub { assert() } );
268 dpavlin 1
269 dpavlin 43 sub scan_for_tags {
270 dpavlin 1
271 dpavlin 43 my @tags;
272 dpavlin 20
273 dpavlin 48 cmd( 'D6 00 05 FE 00 05 FA40', "scan for tags",
274 dpavlin 43 'D6 00 0F FE 00 00 05 ', sub { # 01 E00401003123AA26 941A # seen, serial length: 8
275     my $rest = shift || die "no rest?";
276     my $nr = ord( substr( $rest, 0, 1 ) );
277 dpavlin 20
278 dpavlin 43 if ( ! $nr ) {
279 dpavlin 48 _log "no tags in range\n";
280 dpavlin 43 update_visible_tags();
281     $tags_data = {};
282     } else {
283 dpavlin 1
284 dpavlin 43 my $tags = substr( $rest, 1 );
285     my $tl = length( $tags );
286     die "wrong length $tl for $nr tags: ",dump( $tags ) if $tl =! $nr * 8;
287 dpavlin 16
288 dpavlin 43 push @tags, uc(unpack('H16', substr($tags, $_ * 8, 8))) foreach ( 0 .. $nr - 1 );
289     warn "## tags ",as_hex($tags), " [$tl] = ",dump( $tags ) if $debug;
290 dpavlin 48 _log "$nr tags in range: ", join(',', @tags ) , "\n";
291 dpavlin 25
292 dpavlin 43 update_visible_tags( @tags );
293     }
294 dpavlin 5 }
295 dpavlin 43 );
296 dpavlin 5
297 dpavlin 48 diag "tags: ",dump( @tags );
298 dpavlin 43 return $tags_data;
299 dpavlin 22
300 dpavlin 43 }
301 dpavlin 22
302 dpavlin 43 # start scanning for tags
303    
304     if ( $http_server ) {
305     http_server;
306     } else {
307 dpavlin 58 while (1) {
308     scan_for_tags;
309     sleep 1;
310     }
311 dpavlin 43 }
312    
313     die "over and out";
314    
315 dpavlin 22 sub update_visible_tags {
316     my @tags = @_;
317    
318     my $last_visible_tags = $visible_tags;
319     $visible_tags = {};
320    
321     foreach my $tag ( @tags ) {
322 dpavlin 51 $visible_tags->{$tag}++;
323 dpavlin 22 if ( ! defined $last_visible_tags->{$tag} ) {
324 dpavlin 25 if ( defined $tags_data->{$tag} ) {
325 dpavlin 64 warn "$tag in range\n";
326 dpavlin 25 } else {
327     read_tag( $tag );
328     }
329 dpavlin 22 } else {
330     warn "## using cached data for $tag" if $debug;
331     }
332     delete $last_visible_tags->{$tag}; # leave just missing tags
333 dpavlin 29
334     if ( -e "$program_path/$tag" ) {
335     write_tag( $tag );
336     }
337 dpavlin 34 if ( -e "$secure_path/$tag" ) {
338     secure_tag( $tag );
339     }
340 dpavlin 22 }
341    
342     foreach my $tag ( keys %$last_visible_tags ) {
343 dpavlin 23 my $data = delete $tags_data->{$tag};
344 dpavlin 64 warn "$tag removed ", dump($data), $/;
345 dpavlin 22 }
346    
347     warn "## update_visible_tags(",dump( @tags ),") = ",dump( $visible_tags )," removed: ",dump( $last_visible_tags ), " data: ",dump( $tags_data ) if $debug;
348     }
349    
350 dpavlin 28 my $tag_data_block;
351 dpavlin 22
352 dpavlin 28 sub read_tag_data {
353     my ($start_block,$rest) = @_;
354     die "no rest?" unless $rest;
355 dpavlin 41
356     my $last_block = 0;
357    
358 dpavlin 28 warn "## DATA [$start_block] ", dump( $rest ) if $debug;
359     my $tag = uc(unpack('H16',substr( $rest, 0, 8 )));
360     my $blocks = ord(substr($rest,8,1));
361     $rest = substr($rest,9); # leave just data blocks
362     foreach my $nr ( 0 .. $blocks - 1 ) {
363     my $block = substr( $rest, $nr * 6, 6 );
364     warn "## block ",as_hex( $block ) if $debug;
365     my $ord = unpack('v',substr( $block, 0, 2 ));
366     my $expected_ord = $nr + $start_block;
367 dpavlin 41 warn "got block $ord, expected block $expected_ord from ",dump( $block ) if $ord != $expected_ord;
368 dpavlin 28 my $data = substr( $block, 2 );
369     die "data payload should be 4 bytes" if length($data) != 4;
370 dpavlin 40 warn sprintf "## tag %9s %02d: %s |%-4s|\n", $tag, $ord, as_hex( $data ), $data;
371 dpavlin 28 $tag_data_block->{$tag}->[ $ord ] = $data;
372 dpavlin 41 $last_block = $ord;
373 dpavlin 28 }
374     $tags_data->{ $tag } = join('', @{ $tag_data_block->{$tag} });
375 dpavlin 31
376     my $item_type_nr = ord(substr( $tags_data->{$tag}, 3, 1 ));
377 dpavlin 42 print "DATA $tag ",dump( $tags_data ), " item type: ", ( $item_type->{ $item_type_nr } || "UNKWOWN '$item_type_nr'" ), "\n";
378 dpavlin 41
379 dpavlin 42 return $last_block + 1;
380 dpavlin 28 }
381    
382 dpavlin 59 my $saved_in_log;
383    
384 dpavlin 43 sub decode_tag {
385     my $tag = shift;
386    
387     my $data = $tags_data->{$tag} || die "no data for $tag";
388    
389     my ( $u1, $set_item, $u2, $type, $content, $br_lib, $custom ) = unpack('C4Z16Nl>',$data);
390     my $hash = {
391     u1 => $u1,
392     u2 => $u2,
393     set => ( $set_item & 0xf0 ) >> 4,
394     total => ( $set_item & 0x0f ),
395    
396     type => $type,
397     content => $content,
398    
399     branch => $br_lib >> 20,
400     library => $br_lib & 0x000fffff,
401    
402     custom => $custom,
403     };
404    
405 dpavlin 59 if ( ! $saved_in_log->{$tag}++ ) {
406     open(my $log, '>>', 'rfid-log.txt');
407     print $log strftime( "%Y-%m-%d %H:%M:%S", localtime ), ",$tag,$content\n";
408     close($log);
409     }
410    
411 dpavlin 43 return $hash;
412     }
413    
414 dpavlin 67 sub forget_tag {
415     my $tag = shift;
416     delete $tags_data->{$tag};
417     delete $visible_tags->{$tag};
418     }
419    
420 dpavlin 16 sub read_tag {
421     my ( $tag ) = @_;
422 dpavlin 1
423 dpavlin 22 confess "no tag?" unless $tag;
424    
425 dpavlin 16 print "read_tag $tag\n";
426 dpavlin 1
427 dpavlin 41 my $start_block = 0;
428 dpavlin 28
429 dpavlin 41 while ( $start_block < $max_rfid_block ) {
430 dpavlin 1
431 dpavlin 41 cmd(
432 dpavlin 65 sprintf( "D6 00 0D 02 $tag %02x %02x BEEF", $start_block, $read_blocks ),
433 dpavlin 41 "read $tag offset: $start_block blocks: $read_blocks",
434     "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";
435     $start_block = read_tag_data( $start_block, @_ );
436     warn "# read tag upto $start_block\n";
437     },
438 dpavlin 65 "D6 00 0F FE 00 00 05 01 $tag BEEF", sub {
439 dpavlin 41 print "FIXME: tag $tag ready? (expected block read instead)\n";
440     },
441     );
442    
443     }
444    
445 dpavlin 33 my $security;
446    
447     cmd(
448 dpavlin 65 "D6 00 0B 0A $tag BEEF", "check security $tag",
449 dpavlin 33 "D6 00 0D 0A 00", sub {
450     my $rest = shift;
451     my $from_tag;
452     ( $from_tag, $security ) = ( substr($rest,0,8), substr($rest,8,1) );
453     die "security from other tag: ",as_hex( $from_tag ) if $from_tag ne str2bytes( $tag );
454     $security = as_hex( $security );
455 dpavlin 54 $tags_security->{$tag} = $security;
456 dpavlin 33 warn "# SECURITY $tag = $security\n";
457     }
458     );
459    
460 dpavlin 43 print "TAG $tag ", dump(decode_tag( $tag ));
461 dpavlin 16 }
462    
463 dpavlin 29 sub write_tag {
464 dpavlin 59 my ($tag,$data) = @_;
465 dpavlin 29
466     my $path = "$program_path/$tag";
467 dpavlin 59 $data = read_file( $path ) if -e $path;
468 dpavlin 29
469 dpavlin 59 die "no data" unless $data;
470    
471 dpavlin 38 my $hex_data;
472 dpavlin 29
473 dpavlin 38 if ( $data =~ s{^hex\s+}{} ) {
474     $hex_data = $data;
475     $hex_data =~ s{\s+}{}g;
476     } else {
477 dpavlin 29
478 dpavlin 38 $data .= "\0" x ( 4 - ( length($data) % 4 ) );
479 dpavlin 30
480 dpavlin 41 my $max_len = $max_rfid_block * 4;
481 dpavlin 30
482 dpavlin 38 if ( length($data) > $max_len ) {
483     $data = substr($data,0,$max_len);
484     warn "strip content to $max_len bytes\n";
485     }
486    
487     $hex_data = unpack('H*', $data);
488     }
489    
490     my $len = length($hex_data) / 2;
491 dpavlin 40 # pad to block size
492     $hex_data .= '00' x ( 4 - $len % 4 );
493     my $blocks = sprintf('%02x', length($hex_data) / 4);
494 dpavlin 38
495     print "write_tag $tag = ",dump( $data ), " [$len/$blocks] == $hex_data\n";
496    
497 dpavlin 29 cmd(
498 dpavlin 65 "d6 00 ff 04 $tag 00 $blocks 00 $hex_data BEEF", "write $tag",
499     "d6 00 0d 04 00 $tag $blocks BEEF", sub { assert() },
500 dpavlin 40 ); # foreach ( 1 .. 3 ); # XXX 3m software does this three times!
501 dpavlin 29
502     my $to = $path;
503     $to .= '.' . time();
504    
505     rename $path, $to;
506     print ">> $to\n";
507    
508 dpavlin 67 forget_tag $tag;
509 dpavlin 29 }
510    
511 dpavlin 67 sub secure_tag_with {
512     my ( $tag, $data ) = @_;
513    
514     cmd(
515     "d6 00 0c 09 $tag $data BEEF", "secure $tag -> $data",
516     "d6 00 0c 09 00 $tag BEEF", sub { assert() },
517     );
518    
519     forget_tag $tag;
520     }
521    
522 dpavlin 34 sub secure_tag {
523     my ($tag) = @_;
524    
525     my $path = "$secure_path/$tag";
526     my $data = substr(read_file( $path ),0,2);
527    
528 dpavlin 67 secure_tag_with( $tag, $data );
529 dpavlin 34
530     my $to = $path;
531     $to .= '.' . time();
532    
533     rename $path, $to;
534     print ">> $to\n";
535     }
536    
537 dpavlin 19 exit;
538    
539 dpavlin 1 for ( 1 .. 3 ) {
540    
541     # ++-->type 00-0a
542     # 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
543     # 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
544     # 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
545    
546     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 $_" );
547     warn "D6 00 0D 04 00 E00401003123AA26 07 CFF1 -- ack 7 block?\n";
548    
549     }
550     warn " D6 00 0F FE 00 00 05 01 E00401003123AA26 941A\n";
551    
552     cmd( 'D6 00 05 FE 00 05 FA 40', "port-write scan $_" ) foreach ( 1 .. 2 );
553    
554     cmd('D6 00 0C 09 E00401003123AA26 D7 3AF0', 'checkin?',
555     'D6 00 0C 09 00 E00401003123AA26 6A44 -- no?' );
556     cmd('D6 00 0C 09 E00401003123AA26 DA EB5D', 'checkout?',
557     'D6 00 0C 09 00 E00401003123AA26 6A44 -- no?' );
558    
559     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',
560     'D6 00 0D 04 00 E00401003123AA26 06 DFD0 -- ack 6 blocks' ) foreach ( 1 .. 3 );
561    
562     undef $port;
563     print "Port closed\n";
564    
565     sub writechunk
566     {
567     my $str=shift;
568     my $count = $port->write($str);
569 dpavlin 38 my $len = length($str);
570     die "wrong write length $count != $len in ",as_hex( $str ) if $count != $len;
571 dpavlin 19 print "#> ", as_hex( $str ), "\t[$count]\n" if $debug;
572 dpavlin 1 }
573    
574     sub as_hex {
575     my @out;
576     foreach my $str ( @_ ) {
577     my $hex = unpack( 'H*', $str );
578 dpavlin 2 $hex =~ s/(..)/$1 /g if length( $str ) > 2;
579 dpavlin 8 $hex =~ s/\s+$//;
580 dpavlin 1 push @out, $hex;
581     }
582 dpavlin 8 return join(' | ', @out);
583 dpavlin 1 }
584    
585     sub read_bytes {
586     my ( $len, $desc ) = @_;
587     my $data = '';
588     while ( length( $data ) < $len ) {
589     my ( $c, $b ) = $port->read(1);
590 dpavlin 28 die "no bytes on port: $!" unless defined $b;
591 dpavlin 1 #warn "## got $c bytes: ", as_hex($b), "\n";
592     $data .= $b;
593     }
594     $desc ||= '?';
595 dpavlin 4 warn "#< ", as_hex($data), "\t$desc\n" if $debug;
596 dpavlin 1 return $data;
597     }
598    
599 dpavlin 5 our $assert;
600 dpavlin 2
601 dpavlin 5 # my $rest = skip_assert( 3 );
602     sub skip_assert {
603     assert( 0, shift );
604     }
605    
606 dpavlin 2 sub assert {
607     my ( $from, $to ) = @_;
608    
609 dpavlin 5 $from ||= 0;
610 dpavlin 4 $to = length( $assert->{expect} ) if ! defined $to;
611    
612 dpavlin 2 my $p = substr( $assert->{payload}, $from, $to );
613     my $e = substr( $assert->{expect}, $from, $to );
614 dpavlin 3 warn "EXPECTED ",as_hex($e), " GOT ", as_hex($p), " [$from-$to] in ",dump( $assert ), "\n" if $e ne $p;
615 dpavlin 5
616     # return the rest
617     return substr( $assert->{payload}, $to );
618 dpavlin 2 }
619    
620 dpavlin 15 use Digest::CRC;
621    
622     sub crcccitt {
623     my $bytes = shift;
624     my $crc = Digest::CRC->new(
625     # midified CCITT to xor with 0xffff instead of 0x0000
626     width => 16, init => 0xffff, xorout => 0xffff, refout => 0, poly => 0x1021, refin => 0,
627     ) or die $!;
628     $crc->add( $bytes );
629     pack('n', $crc->digest);
630     }
631    
632 dpavlin 8 # my $checksum = checksum( $bytes );
633     # my $checksum = checksum( $bytes, $original_checksum );
634     sub checksum {
635     my ( $bytes, $checksum ) = @_;
636    
637 dpavlin 16 my $len = ord(substr($bytes,2,1));
638 dpavlin 17 my $len_real = length($bytes) - 1;
639 dpavlin 16
640 dpavlin 17 if ( $len_real != $len ) {
641     print "length wrong: $len_real != $len\n";
642 dpavlin 38 $bytes = substr($bytes,0,2) . chr($len_real) . substr($bytes,3);
643 dpavlin 17 }
644    
645 dpavlin 38 my $xor = crcccitt( substr($bytes,1) ); # skip D6
646     warn "## checksum ",dump( $bytes, $xor, $checksum ) if $debug;
647    
648 dpavlin 8 if ( defined $checksum && $xor ne $checksum ) {
649 dpavlin 65 warn "checksum error: ", as_hex($xor), " != ", as_hex($checksum), " data: ", as_hex($bytes), "\n" if $checksum ne "\xBE\xEF";
650 dpavlin 16 return $bytes . $xor;
651 dpavlin 8 }
652 dpavlin 16 return $bytes . $checksum;
653 dpavlin 8 }
654    
655 dpavlin 20 our $dispatch;
656    
657 dpavlin 1 sub readchunk {
658 dpavlin 43 # sleep 1; # FIXME remove
659 dpavlin 2
660 dpavlin 1 # read header of packet
661     my $header = read_bytes( 2, 'header' );
662 dpavlin 2 my $length = read_bytes( 1, 'length' );
663     my $len = ord($length);
664 dpavlin 1 my $data = read_bytes( $len, 'data' );
665    
666 dpavlin 2 my $payload = substr( $data, 0, -2 );
667     my $payload_len = length($data);
668     warn "## payload too short $payload_len != $len\n" if $payload_len != $len;
669 dpavlin 8
670 dpavlin 2 my $checksum = substr( $data, -2, 2 );
671 dpavlin 20 checksum( $header . $length . $payload , $checksum );
672 dpavlin 1
673 dpavlin 22 print "<< ",as_hex( $header ), " [$len] ", as_hex( $payload ), " | sum: ",as_hex($checksum),"\n" if $verbose;
674 dpavlin 2
675     $assert->{len} = $len;
676     $assert->{payload} = $payload;
677    
678 dpavlin 20 my $full = $header . $length . $data; # full
679     # find longest match for incomming data
680     my ($to) = grep {
681     my $match = substr($payload,0,length($_));
682     m/^\Q$match\E/
683     } sort { length($a) <=> length($b) } keys %$dispatch;
684     warn "?? payload dispatch to ",dump( $payload, $dispatch, $to ) if $debug;
685 dpavlin 2
686 dpavlin 42 if ( defined $to ) {
687     my $rest = substr( $payload, length($to) ) if length($to) < length($payload);
688 dpavlin 20 warn "## DISPATCH payload to with rest", dump( $payload, $to, $rest ) if $debug;
689     $dispatch->{ $to }->( $rest );
690     } else {
691 dpavlin 64 die "NO DISPATCH for ",as_hex( $full ),"\n";
692 dpavlin 20 }
693    
694 dpavlin 2 return $data;
695 dpavlin 1 }
696    
697 dpavlin 2 sub str2bytes {
698     my $str = shift || confess "no str?";
699 dpavlin 5 my $b = $str;
700 dpavlin 17 $b =~ s/\s+//g;
701     $b =~ s/(..)/\\x$1/g;
702     $b = "\"$b\"";
703 dpavlin 5 my $bytes = eval $b;
704 dpavlin 2 die $@ if $@;
705 dpavlin 5 warn "## str2bytes( $str ) => $b => ",as_hex($bytes) if $debug;
706 dpavlin 2 return $bytes;
707     }
708    
709     sub cmd {
710 dpavlin 20 my $cmd = shift || confess "no cmd?";
711     my $cmd_desc = shift || confess "no description?";
712     my @expect = @_;
713    
714 dpavlin 2 my $bytes = str2bytes( $cmd );
715    
716 dpavlin 16 # fix checksum if needed
717     $bytes = checksum( substr( $bytes, 0, -2 ), substr( $bytes, -2, 2 ) );
718    
719 dpavlin 22 warn ">> ", as_hex( $bytes ), "\t## $cmd_desc\n" if $verbose;
720 dpavlin 2 $assert->{send} = $cmd;
721     writechunk( $bytes );
722    
723 dpavlin 20 while ( @expect ) {
724     my $pattern = str2bytes( shift @expect ) || confess "no pattern?";
725     my $coderef = shift @expect || confess "no coderef?";
726     confess "not coderef" unless ref $coderef eq 'CODE';
727    
728     next if defined $dispatch->{ $pattern };
729    
730     $dispatch->{ substr($pattern,3) } = $coderef;
731     warn "++ dispatch ", as_hex($pattern) ,dump( $dispatch ) if $debug;
732 dpavlin 2 }
733 dpavlin 20
734     readchunk;
735 dpavlin 2 }
736    

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26