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

Diff of /cpr-m02.pl

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

revision 1 by dpavlin, Sun Sep 28 12:57:32 2008 UTC revision 31 by dpavlin, Mon Apr 6 15:19:24 2009 UTC
# Line 5  use strict; Line 5  use strict;
5  use warnings;  use warnings;
6    
7  use Data::Dump qw/dump/;  use Data::Dump qw/dump/;
8    use Carp qw/confess/;
9    use Getopt::Long;
10    use File::Slurp;
11    
12    use IO::Socket::INET;
13    
14    my $meteor_server = '192.168.1.13:4671';
15    my $meteor_fh;
16    
17    sub meteor {
18            my @a = @_;
19            push @a, scalar localtime() if $a[0] =~ m{^info};
20    
21            if ( ! defined $meteor_fh ) {
22                    warn "# open connection to $meteor_server";
23                    $meteor_fh = IO::Socket::INET->new(
24                                    PeerAddr => $meteor_server,
25                                    Timeout => 1,
26                    ) || warn "can't connect to meteor $meteor_server: $!"; # FIXME warn => die for production
27                    $meteor_fh = 0; # don't try again
28            }
29    
30            warn ">> meteor ",dump( @a );
31            print $meteor_fh "ADDMESSAGE test ",join('|',@a),"\n" if $meteor_fh;
32    }
33    
34    my $debug = 0;
35    
36    my $device    = "/dev/ttyUSB0";
37    my $baudrate  = "19200";
38    my $databits  = "8";
39    my $parity        = "none";
40    my $stopbits  = "1";
41    my $handshake = "none";
42    
43    my $program_path = './program/';
44    
45  my $response = {  my $response = {
46          'd500090400110a0500027250'                              => 'version?',          'd500090400110a0500027250'                              => 'version?',
# Line 18  my $response = { Line 54  my $response = {
54          'd60017fe00000502e00401003123aa26e00401003123aa283124' => 'tag #1 + tag red-stripe',          'd60017fe00000502e00401003123aa26e00401003123aa283124' => 'tag #1 + tag red-stripe',
55  };  };
56    
57    GetOptions(
58            'd|debug+'    => \$debug,
59            'device=s'    => \$device,
60            'baudrate=i'  => \$baudrate,
61            'databits=i'  => \$databits,
62            'parity=s'    => \$parity,
63            'stopbits=i'  => \$stopbits,
64            'handshake=s' => \$handshake,
65            'meteor=s'    => \$meteor_server,
66    ) or die $!;
67    
68    my $verbose = $debug > 0 ? $debug-- : 0;
69    
70  =head1 NAME  =head1 NAME
71    
72  3m-810 - support for 3M 810 RFID reader  3m-810 - support for 3M 810 RFID reader
73    
74  =head1 SYNOPSIS  =head1 SYNOPSIS
75    
76  3m-810.pl [DEVICE [BAUD [DATA [PARITY [STOP [FLOW]]]]]]  3m-810.pl --device /dev/ttyUSB0
77    
78  =head1 DESCRIPTION  =head1 DESCRIPTION
79    
# Line 36  L<Device::SerialPort(3)> Line 85  L<Device::SerialPort(3)>
85    
86  L<perl(1)>  L<perl(1)>
87    
88    L<http://stackoverflow.com/questions/149617/how-could-i-guess-a-checksum-algorithm>
89    
90  =head1 AUTHOR  =head1 AUTHOR
91    
92  Dobrica Pavlinusic <dpavlin@rot13.org> L<http://www.rot13.org/~dpavlin/>  Dobrica Pavlinusic <dpavlin@rot13.org> L<http://www.rot13.org/~dpavlin/>
# Line 47  it under the same terms ans Perl itself. Line 98  it under the same terms ans Perl itself.
98    
99  =cut  =cut
100    
101  # your serial port.  my $tags_data;
102  my ($device,$baudrate,$databits,$parity,$stopbits,$handshake)=@ARGV;  my $visible_tags;
103  $device    ||= "/dev/ttyUSB0";  
104  $baudrate  ||= "19200";  my $item_type = {
105  $databits  ||= "8";          1 => 'Book',
106  $parity    ||= "none";          6 => 'CD/CD ROM',
107  $stopbits  ||= "1";          2 => 'Magazine',
108  $handshake ||= "none";          13 => 'Book with Audio Tape',
109            9 => 'Book with CD/CD ROM',
110            0 => 'Other',
111    
112            5 => 'Video',
113            4 => 'Audio Tape',
114            3 => 'Bound Journal',
115            8 => 'Book with Diskette',
116            7 => 'Diskette',
117    };
118    
119    warn "## known item type: ",dump( $item_type ) if $debug;
120    
121  my $port=new Device::SerialPort($device) || die "new($device): $!\n";  my $port=new Device::SerialPort($device) || die "can't open serial port $device: $!\n";
122    warn "using $device $handshake $baudrate $databits $parity $stopbits" if $debug;
123  $handshake=$port->handshake($handshake);  $handshake=$port->handshake($handshake);
124  $baudrate=$port->baudrate($baudrate);  $baudrate=$port->baudrate($baudrate);
125  $databits=$port->databits($databits);  $databits=$port->databits($databits);
126  $parity=$port->parity($parity);  $parity=$port->parity($parity);
127  $stopbits=$port->stopbits($stopbits);  $stopbits=$port->stopbits($stopbits);
128    
129  print "## using $device $baudrate $databits $parity $stopbits\n";  print "## using $device $baudrate $databits $parity $stopbits debug: $debug verbose: $verbose\n";
130    
131  # Just in case: reset our timing and buffers  # Just in case: reset our timing and buffers
132  $port->lookclear();  $port->lookclear();
# Line 74  $port->read_char_time(5); Line 137  $port->read_char_time(5);
137  #$port->stty_inpck(1);  #$port->stty_inpck(1);
138  #$port->stty_istrip(1);  #$port->stty_istrip(1);
139    
140  sub cmd {  # initial hand-shake with device
141          my ( $cmd, $desc, $expect ) = @_;  
142          $cmd =~ s/\s+(\S\S)(\S\S)+\s*/ $1 $2/;  # fix checksum  cmd( 'D5 00  05   04 00 11                 8C66', 'hw version',
143          $cmd =~ s/\s+/\\x/g;       'D5 00  09   04 00 11   0A 05 00 02   7250', sub {
144          $cmd = '"\x' . $cmd . '"';          my $hw_ver = join('.', unpack('CCCC', skip_assert(3) ));
145          my $bytes = eval $cmd;          print "hardware version $hw_ver\n";
146          die $@ if $@;          meteor( 'info', "Found reader hardware $hw_ver" );
147          warn ">> ", as_hex( $bytes ), "\t$desc\n";  });
148          writechunk( $bytes );  
149          warn "?? $expect\n" if $expect;  cmd( 'D6 00  0C   13  04  01 00  02 00  03 00  04 00   AAF2','FIXME: stats?',
150          readchunk();       'D6 00  0C   13  00  02 01 01 03 02 02 03  00     E778', sub { assert() }  );
151    
152    # start scanning for tags
153    
154    cmd( 'D6 00  05   FE     00  05         FA40', "scan for tags, retry $_",
155             'D6 00  0F   FE  00 00  05 ', sub { # 01 E00401003123AA26  941A         # seen, serial length: 8
156                    my $rest = shift || die "no rest?";
157                    my $nr = ord( substr( $rest, 0, 1 ) );
158    
159                    if ( ! $nr ) {
160                            print "no tags in range\n";
161                            update_visible_tags();
162                            meteor( 'info-none-in-range' );
163                            $tags_data = {};
164                    } else {
165    
166                            my $tags = substr( $rest, 1 );
167    
168                            my $tl = length( $tags );
169                            die "wrong length $tl for $nr tags: ",dump( $tags ) if $tl =! $nr * 8;
170    
171                            my @tags;
172                            push @tags, uc(unpack('H16', substr($tags, $_ * 8, 8))) foreach ( 0 .. $nr - 1 );
173                            warn "## tags ",as_hex($tags), " [$tl] = ",dump( $tags ) if $debug;
174                            print "$nr tags in range: ", join(',', @tags ) , "\n";
175    
176                            meteor( 'info-in-range', join(' ',@tags));
177    
178                            update_visible_tags( @tags );
179                    }
180            }
181    ) while(1);
182    #) foreach ( 1 .. 100 );
183    
184    
185    
186    sub update_visible_tags {
187            my @tags = @_;
188    
189            my $last_visible_tags = $visible_tags;
190            $visible_tags = {};
191    
192            foreach my $tag ( @tags ) {
193                    if ( ! defined $last_visible_tags->{$tag} ) {
194                            if ( defined $tags_data->{$tag} ) {
195    #                               meteor( 'in-range', $tag );
196                            } else {
197                                    meteor( 'read', $tag );
198                                    read_tag( $tag );
199                            }
200                            $visible_tags->{$tag}++;
201                    } else {
202                            warn "## using cached data for $tag" if $debug;
203                    }
204                    delete $last_visible_tags->{$tag}; # leave just missing tags
205    
206                    if ( -e "$program_path/$tag" ) {
207                                    meteor( 'write', $tag );
208                                    write_tag( $tag );
209                    }
210            }
211    
212            foreach my $tag ( keys %$last_visible_tags ) {
213                    my $data = delete $tags_data->{$tag};
214                    print "removed tag $tag with data ",dump( $data ),"\n";
215                    meteor( 'removed', $tag );
216            }
217    
218            warn "## update_visible_tags(",dump( @tags ),") = ",dump( $visible_tags )," removed: ",dump( $last_visible_tags ), " data: ",dump( $tags_data ) if $debug;
219  }  }
220    
221  cmd( 'D5 00  05  04   00   11                 8C66', 'hw version?',  my $tag_data_block;
222       'D5 00  09  04   00   11   0A 05 00 02   7250 -- hw 10.5.0.2' );  
223    sub read_tag_data {
224            my ($start_block,$rest) = @_;
225            die "no rest?" unless $rest;
226            warn "## DATA [$start_block] ", dump( $rest ) if $debug;
227            my $tag = uc(unpack('H16',substr( $rest, 0, 8 )));
228            my $blocks = ord(substr($rest,8,1));
229            $rest = substr($rest,9); # leave just data blocks
230            foreach my $nr ( 0 .. $blocks - 1 ) {
231                    my $block = substr( $rest, $nr * 6, 6 );
232                    warn "## block ",as_hex( $block ) if $debug;
233                    my $ord   = unpack('v',substr( $block, 0, 2 ));
234                    my $expected_ord = $nr + $start_block;
235                    die "got block $ord, expected block $expected_ord from ",dump( $block ) if $ord != $expected_ord;
236                    my $data  = substr( $block, 2 );
237                    die "data payload should be 4 bytes" if length($data) != 4;
238                    warn sprintf "## tag %9s %02d %s |%-4s|\n", $tag, $ord, as_hex( $data ), $data;
239                    $tag_data_block->{$tag}->[ $ord ] = $data;
240            }
241            $tags_data->{ $tag } = join('', @{ $tag_data_block->{$tag} });
242    
243            my $item_type_nr = ord(substr( $tags_data->{$tag}, 3, 1 ));
244            print "DATA $tag ",dump( $tags_data ), " item type: ", ( $item_type->{ $item_type_nr } || "UNKWOWN '$item_type_nr' in " . dump( $item_type ) ), "\n";
245    }
246    
247    sub read_tag {
248            my ( $tag ) = @_;
249    
250            confess "no tag?" unless $tag;
251    
252            print "read_tag $tag\n";
253    
254            cmd(
255                    "D6 00  0D  02      $tag   00   03     1CC4", "read $tag offset: 0 blocks: 3",
256                    "D6 00  0F  FE  00 00  05 01   $tag    941A", sub {
257                            print "FIXME: tag $tag ready?\n";
258                    },
259                    "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";
260                            read_tag_data( 0, @_ );
261                    },
262            );
263    
264            cmd(
265                    "D6 00  0D  02      $tag   03   04     3970", "read $tag offset: 3 blocks: 4",
266                    "D6 00  25  02 00", sub { # $tag   04                         03 00   30 30 00 00   04 00   00 00 00 00  
267                            read_tag_data( 3, @_ );
268                    }
269            );
270    
271    }
272    
273    sub write_tag {
274            my ($tag) = @_;
275    
276            my $path = "$program_path/$tag";
277    
278  cmd( 'D6 00  0C  13   04   01 00  02 00  03 00  04 00   AAF2','stats?' );          my $data = read_file( $path );
 #     D6 00  0C  13   00   02 01 01 03 02 02 03  00   E778  
279    
280  cmd( 'D6 00  05  FE     00  05  FA40', "XXX scan $_",          $data = substr($data,0,16);
      'D6 00  07  FE  00 00  05  00  C97B -- no tag' ) foreach ( 1 .. 10 );  
281    
282  #     D6 00  0F  FE  00 00  05  01  E00401003123AA26  941A       # seen          my $hex_data = unpack('H*', $data) . ' 00' x ( 16 - length($data) );
283    
284  cmd( 'D6 00  0D  02      E00401003123AA26   00   03     1CC4', 'read offset: 0 blocks: 3' );          print "write_tag $tag = $data ",dump( $hex_data );
285    
286  #     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          cmd(
287  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";                  "D6 00  26  04  $tag  00 06 00  04 11 00 01  $hex_data 00 00 00 00  FD3B", "write $tag",
288                    "D6 00  0D  04 00  $tag  06  AFB1", sub { assert() },
289            ) foreach ( 1 .. 3 ); # XXX 3M software does this three times!
290    
291  cmd( 'D6 00  0D  02      E00401003123AA26   03   04     3970', 'read offset: 3 blocks: 4' );          my $to = $path;
292            $to .= '.' . time();
293    
294  #     D6 00  25  02 00   E00401003123AA26   04                         03 00   30 30 00 00   04 00   00 00 00 00            rename $path, $to;
295  #                                                                      05 00   00 00 00 00   06 00   00 00 00 00    B9BA          print ">> $to\n";
296  warn "D6 00  25  02 00   E00401003123AA26   04                         03 00   39 30 31 32   04 00   33 34 35 36  
297                                                                         05 00   00 00 00 00   06 00   00 00 00 00    524B\n";          delete $tags_data->{$tag};      # force re-read of tag
298  warn "D6 00  0F  FE  00 00  05 01   E00401003123AA26  941A ##### ready?\n";  }
299    
300    exit;
301    
302  for ( 1 .. 3 ) {  for ( 1 .. 3 ) {
303    
# Line 140  print "Port closed\n"; Line 328  print "Port closed\n";
328  sub writechunk  sub writechunk
329  {  {
330          my $str=shift;          my $str=shift;
   
331          my $count = $port->write($str);          my $count = $port->write($str);
332          print ">> ", as_hex( $str ), "\t[$count]\n";          print "#> ", as_hex( $str ), "\t[$count]\n" if $debug;
333  }  }
334    
335  sub as_hex {  sub as_hex {
336          my @out;          my @out;
337          foreach my $str ( @_ ) {          foreach my $str ( @_ ) {
338                  my $hex = unpack( 'H*', $str );                  my $hex = unpack( 'H*', $str );
339                  $hex =~ s/(..)/$1 /g;                  $hex =~ s/(..)/$1 /g if length( $str ) > 2;
340                    $hex =~ s/\s+$//;
341                  push @out, $hex;                  push @out, $hex;
342          }          }
343          return join('  ', @out);          return join(' | ', @out);
344  }  }
345    
346  sub read_bytes {  sub read_bytes {
# Line 160  sub read_bytes { Line 348  sub read_bytes {
348          my $data = '';          my $data = '';
349          while ( length( $data ) < $len ) {          while ( length( $data ) < $len ) {
350                  my ( $c, $b ) = $port->read(1);                  my ( $c, $b ) = $port->read(1);
351                    die "no bytes on port: $!" unless defined $b;
352                  #warn "## got $c bytes: ", as_hex($b), "\n";                  #warn "## got $c bytes: ", as_hex($b), "\n";
353                  $data .= $b;                  $data .= $b;
354          }          }
355          $desc ||= '?';          $desc ||= '?';
356          warn "#< ", as_hex($data), "\t$desc\n";          warn "#< ", as_hex($data), "\t$desc\n" if $debug;
357          return $data;          return $data;
358  }  }
359    
360    our $assert;
361    
362    # my $rest = skip_assert( 3 );
363    sub skip_assert {
364            assert( 0, shift );
365    }
366    
367    sub assert {
368            my ( $from, $to ) = @_;
369    
370            $from ||= 0;
371            $to = length( $assert->{expect} ) if ! defined $to;
372    
373            my $p = substr( $assert->{payload}, $from, $to );
374            my $e = substr( $assert->{expect},  $from, $to );
375            warn "EXPECTED ",as_hex($e), " GOT ", as_hex($p), " [$from-$to] in ",dump( $assert ), "\n" if $e ne $p;
376    
377            # return the rest
378            return substr( $assert->{payload}, $to );
379    }
380    
381    use Digest::CRC;
382    
383    sub crcccitt {
384            my $bytes = shift;
385            my $crc = Digest::CRC->new(
386                    # midified CCITT to xor with 0xffff instead of 0x0000
387                    width => 16, init => 0xffff, xorout => 0xffff, refout => 0, poly => 0x1021, refin => 0,
388            ) or die $!;
389            $crc->add( $bytes );
390            pack('n', $crc->digest);
391    }
392    
393    # my $checksum = checksum( $bytes );
394    # my $checksum = checksum( $bytes, $original_checksum );
395    sub checksum {
396            my ( $bytes, $checksum ) = @_;
397    
398            my $xor = crcccitt( substr($bytes,1) ); # skip D6
399            warn "## checksum ",dump( $bytes, $xor, $checksum ) if $debug;
400    
401            my $len = ord(substr($bytes,2,1));
402            my $len_real = length($bytes) - 1;
403    
404            if ( $len_real != $len ) {
405                    print "length wrong: $len_real != $len\n";
406                    $bytes = substr($bytes,0,2) . chr($len_real) . substr($bytes,4);
407            }
408    
409            if ( defined $checksum && $xor ne $checksum ) {
410                    print "checksum doesn't match: ", as_hex($xor), " != ", as_hex($checksum), " data: ", as_hex($bytes), "\n";
411                    return $bytes . $xor;
412            }
413            return $bytes . $checksum;
414    }
415    
416    our $dispatch;
417    
418  sub readchunk {  sub readchunk {
419            sleep 1;        # FIXME remove
420    
421          # read header of packet          # read header of packet
422          my $header = read_bytes( 2, 'header' );          my $header = read_bytes( 2, 'header' );
423          my $len = ord( read_bytes( 1, 'length' ) );          my $length = read_bytes( 1, 'length' );
424            my $len = ord($length);
425          my $data = read_bytes( $len, 'data' );          my $data = read_bytes( $len, 'data' );
426    
427          warn "<< ",as_hex( $header, ), " [$len] ", as_hex( $data ), "\n";          my $payload  = substr( $data, 0, -2 );
428            my $payload_len = length($data);
429            warn "## payload too short $payload_len != $len\n" if $payload_len != $len;
430    
431            my $checksum = substr( $data, -2, 2 );
432            checksum( $header . $length . $payload , $checksum );
433    
434            print "<< ",as_hex( $header ), " [$len] ", as_hex( $payload ), " | sum: ",as_hex($checksum),"\n" if $verbose;
435    
436            $assert->{len}      = $len;
437            $assert->{payload}  = $payload;
438    
439            my $full = $header . $length . $data; # full
440            # find longest match for incomming data
441            my ($to) = grep {
442                    my $match = substr($payload,0,length($_));
443                    m/^\Q$match\E/
444            } sort { length($a) <=> length($b) } keys %$dispatch;
445            warn "?? payload dispatch to ",dump( $payload, $dispatch, $to ) if $debug;
446    
447            if ( defined $to ) {
448                    my $rest = substr( $payload, length($to) );
449                    warn "## DISPATCH payload to with rest", dump( $payload, $to, $rest ) if $debug;
450                    $dispatch->{ $to }->( $rest );
451            } else {
452                    print "NO DISPATCH for ",dump( $full ),"\n";
453            }
454    
455            return $data;
456    }
457    
458    sub str2bytes {
459            my $str = shift || confess "no str?";
460            my $b = $str;
461            $b =~ s/\s+//g;
462            $b =~ s/(..)/\\x$1/g;
463            $b = "\"$b\"";
464            my $bytes = eval $b;
465            die $@ if $@;
466            warn "## str2bytes( $str ) => $b => ",as_hex($bytes) if $debug;
467            return $bytes;
468    }
469    
470    sub cmd {
471            my $cmd = shift || confess "no cmd?";
472            my $cmd_desc = shift || confess "no description?";
473            my @expect = @_;
474    
475            my $bytes = str2bytes( $cmd );
476    
477            # fix checksum if needed
478            $bytes = checksum( substr( $bytes, 0, -2 ), substr( $bytes, -2, 2 ) );
479    
480            warn ">> ", as_hex( $bytes ), "\t## $cmd_desc\n" if $verbose;
481            $assert->{send} = $cmd;
482            writechunk( $bytes );
483    
484            while ( @expect ) {
485                    my $pattern = str2bytes( shift @expect ) || confess "no pattern?";
486                    my $coderef = shift @expect || confess "no coderef?";
487                    confess "not coderef" unless ref $coderef eq 'CODE';
488    
489                    next if defined $dispatch->{ $pattern };
490    
491                    $dispatch->{ substr($pattern,3) } = $coderef;
492                    warn "++ dispatch ", as_hex($pattern) ,dump( $dispatch ) if $debug;
493            }
494    
495          sleep 1;          readchunk;
496  }  }
497    

Legend:
Removed from v.1  
changed lines
  Added in v.31

  ViewVC Help
Powered by ViewVC 1.1.26