/[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

Diff of /3m-810.pl

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 2 by dpavlin, Sun Sep 28 14:05:43 2008 UTC revision 63 by dpavlin, Thu Feb 11 10:52:14 2010 UTC
# Line 6  use warnings; Line 6  use warnings;
6    
7  use Data::Dump qw/dump/;  use Data::Dump qw/dump/;
8  use Carp qw/confess/;  use Carp qw/confess/;
9    use Getopt::Long;
10    use File::Slurp;
11    use JSON;
12    use POSIX qw(strftime);
13    
14    use IO::Socket::INET;
15    
16    my $debug = 0;
17    
18    my $tags_data;
19    my $tags_security;
20    my $visible_tags;
21    
22    my $meteor_server; # = '192.168.1.13:4671';
23    my $meteor_fh;
24    
25    sub meteor {
26            my @a = @_;
27            push @a, scalar localtime() if $a[0] =~ m{^info};
28    
29            if ( ! defined $meteor_fh ) {
30                    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            }
42    
43            if ( $meteor_fh ) {
44                    warn ">> meteor ",dump( @a );
45                    print $meteor_fh "ADDMESSAGE test ",join('|',@a),"\n"
46            }
47    }
48    
49    my $listen_port = 9000;                  # pick something not in use
50    my $server_url  = "http://localhost:$listen_port";
51    
52    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            print "Server $0 ready at $server_url\n";
64    
65            sub static {
66                    my ($client,$path) = @_;
67    
68                    $path = "www/$path";
69                    $path .= 'rfid.html' if $path =~ m{/$};
70    
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                    warn "WEB << $request\n" if $debug;
92    
93                    if ($request =~ m{^GET (/.*) HTTP/1.[01]}) {
94                            my $method = $1;
95                            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                                    warn "WEB << param: ",dump( $param ) if $debug;
102                            }
103                            if ( my $path = static( $client,$1 ) ) {
104                                    warn "WEB >> $path" if $debug;
105                            } elsif ( $method =~ m{/scan} ) {
106                                    my $tags = scan_for_tags();
107                                    my $json = { time => time() };
108                                    map {
109                                            my $d = decode_tag($_);
110                                            $d->{sid} = $_;
111                                            $d->{security} = $tags_security->{$_};
112                                            push @{ $json->{tags} },  $d;
113                                    } keys %$tags;
114                                    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                            } 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                                            my $content = "\x04\x11\x00\x01" . $param->{$p};
124                                            $content = "\x00" if $param->{$p} eq 'blank';
125                                            $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                            } 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    
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    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    my $program_path = './program/';
168    my $secure_path = './secure/';
169    
170    # http server
171    my $http_server = 1;
172    
173    # 3M defaults: 8,4
174    my $max_rfid_block = 16;
175    my $read_blocks = 8;
176    
177  my $response = {  my $response = {
178          'd500090400110a0500027250'                              => 'version?',          'd500090400110a0500027250'                              => 'version?',
# Line 19  my $response = { Line 186  my $response = {
186          'd60017fe00000502e00401003123aa26e00401003123aa283124' => 'tag #1 + tag red-stripe',          'd60017fe00000502e00401003123aa26e00401003123aa283124' => 'tag #1 + tag red-stripe',
187  };  };
188    
189    GetOptions(
190            'd|debug+'    => \$debug,
191            'device=s'    => \$device,
192            'baudrate=i'  => \$baudrate,
193            'databits=i'  => \$databits,
194            'parity=s'    => \$parity,
195            'stopbits=i'  => \$stopbits,
196            'handshake=s' => \$handshake,
197            'meteor=s'    => \$meteor_server,
198            'http-server!' => \$http_server,
199    ) or die $!;
200    
201    my $verbose = $debug > 0 ? $debug-- : 0;
202    
203  =head1 NAME  =head1 NAME
204    
205  3m-810 - support for 3M 810 RFID reader  3m-810 - support for 3M 810 RFID reader
206    
207  =head1 SYNOPSIS  =head1 SYNOPSIS
208    
209  3m-810.pl [DEVICE [BAUD [DATA [PARITY [STOP [FLOW]]]]]]  3m-810.pl --device /dev/ttyUSB0
210    
211  =head1 DESCRIPTION  =head1 DESCRIPTION
212    
# Line 37  L<Device::SerialPort(3)> Line 218  L<Device::SerialPort(3)>
218    
219  L<perl(1)>  L<perl(1)>
220    
221    L<http://stackoverflow.com/questions/149617/how-could-i-guess-a-checksum-algorithm>
222    
223  =head1 AUTHOR  =head1 AUTHOR
224    
225  Dobrica Pavlinusic <dpavlin@rot13.org> L<http://www.rot13.org/~dpavlin/>  Dobrica Pavlinusic <dpavlin@rot13.org> L<http://www.rot13.org/~dpavlin/>
# Line 48  it under the same terms ans Perl itself. Line 231  it under the same terms ans Perl itself.
231    
232  =cut  =cut
233    
234  # your serial port.  my $item_type = {
235  my ($device,$baudrate,$databits,$parity,$stopbits,$handshake)=@ARGV;          1 => 'Book',
236  $device    ||= "/dev/ttyUSB0";          6 => 'CD/CD ROM',
237  $baudrate  ||= "19200";          2 => 'Magazine',
238  $databits  ||= "8";          13 => 'Book with Audio Tape',
239  $parity    ||= "none";          9 => 'Book with CD/CD ROM',
240  $stopbits  ||= "1";          0 => 'Other',
241  $handshake ||= "none";  
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  my $port=new Device::SerialPort($device) || die "new($device): $!\n";  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  $handshake=$port->handshake($handshake);  $handshake=$port->handshake($handshake);
254  $baudrate=$port->baudrate($baudrate);  $baudrate=$port->baudrate($baudrate);
255  $databits=$port->databits($databits);  $databits=$port->databits($databits);
256  $parity=$port->parity($parity);  $parity=$port->parity($parity);
257  $stopbits=$port->stopbits($stopbits);  $stopbits=$port->stopbits($stopbits);
258    
259  print "## using $device $baudrate $databits $parity $stopbits\n";  warn "## using $device $baudrate $databits $parity $stopbits debug: $debug verbose: $verbose\n";
260    
261  # Just in case: reset our timing and buffers  # Just in case: reset our timing and buffers
262  $port->lookclear();  $port->lookclear();
# Line 75  $port->read_char_time(5); Line 267  $port->read_char_time(5);
267  #$port->stty_inpck(1);  #$port->stty_inpck(1);
268  #$port->stty_istrip(1);  #$port->stty_istrip(1);
269    
270  cmd( 'D5 00  05  04   00   11                 8C66', 'hw version?',  # initial hand-shake with device
271       'D5 00  09  04   00   11   0A 05 00 02   7250', 'hw 10.5.0.2', sub {  
272          my ( $len, $payload, $checksum ) = @_;  cmd( 'D5 00  05   04 00 11                 8C66', 'hw version',
273          assert( 0, 3 );       'D5 00  09   04 00 11   0A 05 00 02   7250', sub {
274          print "hardware version ", join('.', unpack('CCCC', substr($payload,3,4))), "\n";          my $hw_ver = join('.', unpack('CCCC', skip_assert(3) ));
275            print "hardware version $hw_ver\n";
276            meteor( 'info', "Found reader hardware $hw_ver" );
277  });  });
278    
279  cmd( 'D6 00  0C  13   04   01 00  02 00  03 00  04 00   AAF2','stats?' );  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       'D6 00  0C   13  00  02 01 01 03 02 02 03  00     E778', sub { assert() }  );
281    
282    sub scan_for_tags {
283    
284            my @tags;
285    
286            cmd( 'D6 00  05   FE     00  05         FA40', "scan for tags",
287                     '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    
291                            if ( ! $nr ) {
292                                    _log "no tags in range\n";
293                                    update_visible_tags();
294                                    meteor( 'info-none-in-range' );
295                                    $tags_data = {};
296                            } else {
297    
298                                    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    
302                                    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                                    _log "$nr tags in range: ", join(',', @tags ) , "\n";
305    
306                                    meteor( 'info-in-range', join(' ',@tags));
307    
308                                    update_visible_tags( @tags );
309                            }
310                    }
311            );
312    
313            diag "tags: ",dump( @tags );
314            return $tags_data;
315    
316    }
317    
318    # start scanning for tags
319    
320    if ( $http_server ) {
321            http_server;
322    } else {
323            while (1) {
324                    scan_for_tags;
325                    sleep 1;
326            }
327    }
328    
329    die "over and out";
330    
331    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                    $visible_tags->{$tag}++;
339                    if ( ! defined $last_visible_tags->{$tag} ) {
340                            if ( defined $tags_data->{$tag} ) {
341    #                               meteor( 'in-range', $tag );
342                            } else {
343                                    meteor( 'read', $tag );
344                                    read_tag( $tag );
345                            }
346                    } else {
347                            warn "## using cached data for $tag" if $debug;
348                    }
349                    delete $last_visible_tags->{$tag}; # leave just missing tags
350    
351                    if ( -e "$program_path/$tag" ) {
352                                    meteor( 'write', $tag );
353                                    write_tag( $tag );
354                    }
355                    if ( -e "$secure_path/$tag" ) {
356                                    meteor( 'secure', $tag );
357                                    secure_tag( $tag );
358                    }
359            }
360    
361            foreach my $tag ( keys %$last_visible_tags ) {
362                    my $data = delete $tags_data->{$tag};
363                    print "removed tag $tag with data ",dump( $data ),"\n";
364                    meteor( 'removed', $tag );
365            }
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    my $tag_data_block;
371    
372    sub read_tag_data {
373            my ($start_block,$rest) = @_;
374            die "no rest?" unless $rest;
375    
376            my $last_block = 0;
377    
378            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                    warn "got block $ord, expected block $expected_ord from ",dump( $block ) if $ord != $expected_ord;
388                    my $data  = substr( $block, 2 );
389                    die "data payload should be 4 bytes" if length($data) != 4;
390                    warn sprintf "## tag %9s %02d: %s |%-4s|\n", $tag, $ord, as_hex( $data ), $data;
391                    $tag_data_block->{$tag}->[ $ord ] = $data;
392                    $last_block = $ord;
393            }
394            $tags_data->{ $tag } = join('', @{ $tag_data_block->{$tag} });
395    
396            my $item_type_nr = ord(substr( $tags_data->{$tag}, 3, 1 ));
397            print "DATA $tag ",dump( $tags_data ), " item type: ", ( $item_type->{ $item_type_nr } || "UNKWOWN '$item_type_nr'" ), "\n";
398    
399            return $last_block + 1;
400    }
401    
402    my $saved_in_log;
403    
404    sub decode_tag {
405            my $tag = shift;
406    
407            my $data = $tags_data->{$tag} || die "no data for $tag";
408    
409  cmd( 'D6 00  05  FE     00  05  FA40', "XXX scan $_",          my ( $u1, $set_item, $u2, $type, $content, $br_lib, $custom ) = unpack('C4Z16Nl>',$data);
410       'D6 00  07  FE  00 00  05  00  C97B -- no tag' ) foreach ( 1 .. 10 );          my $hash = {
411                    u1 => $u1,
412                    u2 => $u2,
413                    set => ( $set_item & 0xf0 ) >> 4,
414                    total => ( $set_item & 0x0f ),
415    
416  #     D6 00  0F  FE  00 00  05  01  E00401003123AA26  941A       # seen                  type => $type,
417                    content => $content,
418    
419  cmd( 'D6 00  0D  02      E00401003123AA26   00   03     1CC4', 'read offset: 0 blocks: 3' );                  branch => $br_lib >> 20,
420                    library => $br_lib & 0x000fffff,
421    
422  #     D6 00  1F  02 00   E00401003123AA26   03   00 00   04 11 00 01   01 00   30 30 30 30   02 00   30 30 30 30    E5F4                  custom => $custom,
423  warn "D6 00  1F  02 00   E00401003123AA26   03   00 00   04 11 00 01   01 00   31 32 33 34   02 00   35 36 37 38    531F\n";          };
424    
425  cmd( 'D6 00  0D  02      E00401003123AA26   03   04     3970', 'read offset: 3 blocks: 4' );          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            return $hash;
432    }
433    
434    sub read_tag {
435            my ( $tag ) = @_;
436    
437            confess "no tag?" unless $tag;
438    
439            print "read_tag $tag\n";
440    
441            my $start_block = 0;
442    
443            while ( $start_block < $max_rfid_block ) {
444    
445                    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            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                            $tags_security->{$tag} = $security;
470                            warn "# SECURITY $tag = $security\n";
471                    }
472            );
473    
474            print "TAG $tag ", dump(decode_tag( $tag ));
475    }
476    
477    sub write_tag {
478            my ($tag,$data) = @_;
479    
480            my $path = "$program_path/$tag";
481            $data = read_file( $path ) if -e $path;
482    
483            die "no data" unless $data;
484    
485            my $hex_data;
486    
487            if ( $data =~ s{^hex\s+}{} ) {
488                    $hex_data = $data;
489                    $hex_data =~ s{\s+}{}g;
490            } else {
491    
492                    $data .= "\0" x ( 4 - ( length($data) % 4 ) );
493    
494                    my $max_len = $max_rfid_block * 4;
495    
496                    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            # pad to block size
506            $hex_data .= '00' x ( 4 - $len % 4 );
507            my $blocks = sprintf('%02x', length($hex_data) / 4);
508    
509            print "write_tag $tag = ",dump( $data ), " [$len/$blocks] == $hex_data\n";
510    
511            cmd(
512                    "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            ); # foreach ( 1 .. 3 ); # XXX 3m software does this three times!
515    
516            my $to = $path;
517            $to .= '.' . time();
518    
519            rename $path, $to;
520            print ">> $to\n";
521    
522            # force re-read of tag
523            delete $tags_data->{$tag};
524            delete $visible_tags->{$tag};
525    }
526    
527    sub secure_tag {
528            my ($tag) = @_;
529    
530  #     D6 00  25  02 00   E00401003123AA26   04                         03 00   30 30 00 00   04 00   00 00 00 00            my $path = "$secure_path/$tag";
531  #                                                                      05 00   00 00 00 00   06 00   00 00 00 00    B9BA          my $data = substr(read_file( $path ),0,2);
532  warn "D6 00  25  02 00   E00401003123AA26   04                         03 00   39 30 31 32   04 00   33 34 35 36  
533                                                                         05 00   00 00 00 00   06 00   00 00 00 00    524B\n";          cmd(
534  warn "D6 00  0F  FE  00 00  05 01   E00401003123AA26  941A ##### ready?\n";                  "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    exit;
546    
547  for ( 1 .. 3 ) {  for ( 1 .. 3 ) {
548    
# Line 133  sub writechunk Line 574  sub writechunk
574  {  {
575          my $str=shift;          my $str=shift;
576          my $count = $port->write($str);          my $count = $port->write($str);
577          print ">> ", as_hex( $str ), "\t[$count]\n";          my $len = length($str);
578            die "wrong write length $count != $len in ",as_hex( $str ) if $count != $len;
579            print "#> ", as_hex( $str ), "\t[$count]\n" if $debug;
580  }  }
581    
582  sub as_hex {  sub as_hex {
# Line 141  sub as_hex { Line 584  sub as_hex {
584          foreach my $str ( @_ ) {          foreach my $str ( @_ ) {
585                  my $hex = unpack( 'H*', $str );                  my $hex = unpack( 'H*', $str );
586                  $hex =~ s/(..)/$1 /g if length( $str ) > 2;                  $hex =~ s/(..)/$1 /g if length( $str ) > 2;
587                    $hex =~ s/\s+$//;
588                  push @out, $hex;                  push @out, $hex;
589          }          }
590          return join('  ', @out);          return join(' | ', @out);
591  }  }
592    
593  sub read_bytes {  sub read_bytes {
# Line 151  sub read_bytes { Line 595  sub read_bytes {
595          my $data = '';          my $data = '';
596          while ( length( $data ) < $len ) {          while ( length( $data ) < $len ) {
597                  my ( $c, $b ) = $port->read(1);                  my ( $c, $b ) = $port->read(1);
598                    die "no bytes on port: $!" unless defined $b;
599                  #warn "## got $c bytes: ", as_hex($b), "\n";                  #warn "## got $c bytes: ", as_hex($b), "\n";
600                  $data .= $b;                  $data .= $b;
601          }          }
602          $desc ||= '?';          $desc ||= '?';
603          warn "#< ", as_hex($data), "\t$desc\n";          warn "#< ", as_hex($data), "\t$desc\n" if $debug;
604          return $data;          return $data;
605  }  }
606    
607  my $assert;  our $assert;
608    
609    # my $rest = skip_assert( 3 );
610    sub skip_assert {
611            assert( 0, shift );
612    }
613    
614  sub assert {  sub assert {
615          my ( $from, $to ) = @_;          my ( $from, $to ) = @_;
616    
617          warn "# assert ", dump( $assert );          $from ||= 0;
618            $to = length( $assert->{expect} ) if ! defined $to;
619    
620          my $p = substr( $assert->{payload}, $from, $to );          my $p = substr( $assert->{payload}, $from, $to );
621          my $e = substr( $assert->{expect},  $from, $to );          my $e = substr( $assert->{expect},  $from, $to );
622          warn "EXPECTED ",as_hex($e), " GOT ", as_hex($p), "\t[$from-$to]\n" if $e ne $p;          warn "EXPECTED ",as_hex($e), " GOT ", as_hex($p), " [$from-$to] in ",dump( $assert ), "\n" if $e ne $p;
623    
624            # return the rest
625            return substr( $assert->{payload}, $to );
626  }  }
627    
628  sub readchunk {  use Digest::CRC;
629          my ( $parser ) = @_;  
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    # my $checksum = checksum( $bytes );
641    # my $checksum = checksum( $bytes, $original_checksum );
642    sub checksum {
643            my ( $bytes, $checksum ) = @_;
644    
645            my $len = ord(substr($bytes,2,1));
646            my $len_real = length($bytes) - 1;
647    
648            if ( $len_real != $len ) {
649                    print "length wrong: $len_real != $len\n";
650                    $bytes = substr($bytes,0,2) . chr($len_real) . substr($bytes,3);
651            }
652    
653          sleep 1;        # FIXME remove          my $xor = crcccitt( substr($bytes,1) ); # skip D6
654            warn "## checksum ",dump( $bytes, $xor, $checksum ) if $debug;
655    
656            if ( defined $checksum && $xor ne $checksum ) {
657                    print "checksum doesn't match: ", as_hex($xor), " != ", as_hex($checksum), " data: ", as_hex($bytes), "\n";
658                    return $bytes . $xor;
659            }
660            return $bytes . $checksum;
661    }
662    
663    our $dispatch;
664    
665    sub readchunk {
666    #       sleep 1;        # FIXME remove
667    
668          # read header of packet          # read header of packet
669          my $header = read_bytes( 2, 'header' );          my $header = read_bytes( 2, 'header' );
670          my $length = read_bytes( 1, 'length' );          my $length = read_bytes( 1, 'length' );
671          my $len = ord($length);          my $len = ord($length);
672          my $data = read_bytes( $len, 'data' );          my $data = read_bytes( $len, 'data' );
         my ( $cmd ) = unpack('C', $data );  
673    
674          my $payload  = substr( $data, 0, -2 );          my $payload  = substr( $data, 0, -2 );
675          my $payload_len = length($data);          my $payload_len = length($data);
676          warn "## payload too short $payload_len != $len\n" if $payload_len != $len;          warn "## payload too short $payload_len != $len\n" if $payload_len != $len;
677    
678          my $checksum = substr( $data, -2, 2 );          my $checksum = substr( $data, -2, 2 );
679          # FIXME check checksum          checksum( $header . $length . $payload , $checksum );
680    
681          print "<< ",as_hex( $header ), " [$len] ", as_hex( $payload ), "checksum: ", as_hex( $checksum ),"\n";          print "<< ",as_hex( $header ), " [$len] ", as_hex( $payload ), " | sum: ",as_hex($checksum),"\n" if $verbose;
682    
683          $assert->{len}      = $len;          $assert->{len}      = $len;
684          $assert->{payload}  = $payload;          $assert->{payload}  = $payload;
         $assert->{checksum} = $checksum;  
685    
686          $parser->( $len, $payload, $checksum ) if $parser && ref($parser) eq 'CODE';          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    
694            if ( defined $to ) {
695                    my $rest = substr( $payload, length($to) ) if length($to) < length($payload);
696                    warn "## DISPATCH payload to with rest", dump( $payload, $to, $rest ) if $debug;
697                    $dispatch->{ $to }->( $rest );
698            } else {
699                    print "NO DISPATCH for ",as_hex( $full ),"\n";
700            }
701    
702          return $data;          return $data;
703  }  }
704    
705  sub str2bytes {  sub str2bytes {
706          my $str = shift || confess "no str?";          my $str = shift || confess "no str?";
707          $str =~ s/\s+(\S\S)(\S\S)+\s*/ $1 $2/;  # fix checksum          my $b = $str;
708          $str =~ s/\s+/\\x/g;          $b =~ s/\s+//g;
709          $str = '"\x' . $str . '"';          $b =~ s/(..)/\\x$1/g;
710          my $bytes = eval $str;          $b = "\"$b\"";
711            my $bytes = eval $b;
712          die $@ if $@;          die $@ if $@;
713            warn "## str2bytes( $str ) => $b => ",as_hex($bytes) if $debug;
714          return $bytes;          return $bytes;
715  }  }
716    
717  sub cmd {  sub cmd {
718          my ( $cmd, $cmd_desc, $expect, $expect_desc, $coderef ) = @_;          my $cmd = shift || confess "no cmd?";
719            my $cmd_desc = shift || confess "no description?";
720            my @expect = @_;
721    
722          my $bytes = str2bytes( $cmd );          my $bytes = str2bytes( $cmd );
723    
724          warn ">> ", as_hex( $bytes ), "\t## $cmd_desc\n";          # fix checksum if needed
725            $bytes = checksum( substr( $bytes, 0, -2 ), substr( $bytes, -2, 2 ) );
726    
727            warn ">> ", as_hex( $bytes ), "\t## $cmd_desc\n" if $verbose;
728          $assert->{send} = $cmd;          $assert->{send} = $cmd;
729          writechunk( $bytes );          writechunk( $bytes );
730    
731          if ( $expect ) {          while ( @expect ) {
732                  warn "?? $expect", $expect_desc ? "\t## $expect_desc" : '', "\n";                  my $pattern = str2bytes( shift @expect ) || confess "no pattern?";
733                  $assert->{expect} = substr(str2bytes($expect), 3, -2); # just expected payload                  my $coderef = shift @expect || confess "no coderef?";
734                  readchunk( $coderef );                  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          }          }
741    
742            readchunk;
743  }  }
744    

Legend:
Removed from v.2  
changed lines
  Added in v.63

  ViewVC Help
Powered by ViewVC 1.1.26