/[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 63 - (hide annotations)
Thu Feb 11 10:52:14 2010 UTC (14 years, 2 months ago) by dpavlin
File MIME type: text/plain
File size: 18964 byte(s)
added tag blanking

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

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26