/[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 5 by dpavlin, Sun Sep 28 18:13:21 2008 UTC revision 33 by dpavlin, Wed Apr 8 14:48:22 2009 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    
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;  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?',
47          'd60007fe00000500c97b'                                  => 'no tag in range',          'd60007fe00000500c97b'                                  => 'no tag in range',
# Line 21  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 39  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 50  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 79  $port->read_char_time(5); Line 139  $port->read_char_time(5);
139    
140  # initial hand-shake with device  # initial hand-shake with device
141    
142  cmd( 'D5 00  05   04 00 11                 8C66', 'hw version?',  cmd( 'D5 00  05   04 00 11                 8C66', 'hw version',
143       'D5 00  09   04 00 11   0A 05 00 02   7250', 'hw 10.5.0.2', sub {       'D5 00  09   04 00 11   0A 05 00 02   7250', sub {
144          print "hardware version ", join('.', unpack('CCCC', skip_assert(3) )), "\n";          my $hw_ver = join('.', unpack('CCCC', skip_assert(3) ));
145            print "hardware version $hw_ver\n";
146            meteor( 'info', "Found reader hardware $hw_ver" );
147  });  });
148    
149  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?',
150       'D6 00  0C   13  00  02 01 01 03 02 02 03  00     E778','FIXME: unimplemented', sub { assert() }  );       'D6 00  0C   13  00  02 01 01 03 02 02 03  00     E778', sub { assert() }  );
151    
152  # start scanning for tags  # start scanning for tags
153    
154  cmd( 'D6 00  05   FE     00  05         FA40', "XXX scan $_",  cmd( 'D6 00  05   FE     00  05         FA40', "scan for tags, retry $_",
155       'D6 00  07   FE  00 00  05     00  C97B', 'no tag', sub {           'D6 00  0F   FE  00 00  05 ', sub { # 01 E00401003123AA26  941A         # seen, serial length: 8
156  dispatch(                  my $rest = shift || die "no rest?";
157           'D6 00  0F   FE  00 00  05 ',# 01 E00401003123AA26  941A        # seen, serial length: 8                  my $nr = ord( substr( $rest, 0, 1 ) );
158                  sub {  
159                          my $rest = shift || die "no rest?";                  if ( ! $nr ) {
160                          my $nr = ord( substr( $rest, 0, 1 ) );                          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 );                          my $tags = substr( $rest, 1 );
167    
168                          my $tl = length( $tags );                          my $tl = length( $tags );
169                          die "wrong length $tl for $nr tags: ",dump( $tags ) if $tl =! $nr * 8;                          die "wrong length $tl for $nr tags: ",dump( $tags ) if $tl =! $nr * 8;
170    
171                          my @tags;                          my @tags;
172                          push @tags, substr($tags, $_ * 8, 8) foreach ( 0 .. $nr - 1 );                          push @tags, uc(unpack('H16', substr($tags, $_ * 8, 8))) foreach ( 0 .. $nr - 1 );
173                          warn "## tags ",as_hex($tags), " [$tl] = ",dump( $tags );                          warn "## tags ",as_hex($tags), " [$tl] = ",dump( $tags ) if $debug;
174                          print "seen $nr tags: ", join(',', map { unpack('H16', $_) } @tags ) , "\n";                          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  ) foreach ( 1 .. 100 );          warn "## update_visible_tags(",dump( @tags ),") = ",dump( $visible_tags )," removed: ",dump( $last_visible_tags ), " data: ",dump( $tags_data ) if $debug;
219    }
220    
221  cmd( 'D6 00  0D  02      E00401003123AA26   00   03     1CC4', 'read offset: 0 blocks: 3' );  my $tag_data_block;
222    
223  #     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  sub read_tag_data {
224  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";          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  cmd( 'D6 00  0D  02      E00401003123AA26   03   04     3970', 'read offset: 3 blocks: 4' );  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  #     D6 00  25  02 00   E00401003123AA26   04                         03 00   30 30 00 00   04 00   00 00 00 00            my $security;
272  #                                                                      05 00   00 00 00 00   06 00   00 00 00 00    B9BA  
273  warn "D6 00  25  02 00   E00401003123AA26   04                         03 00   39 30 31 32   04 00   33 34 35 36          cmd(
274                                                                         05 00   00 00 00 00   06 00   00 00 00 00    524B\n";                  "D6 00 0B 0A $tag 1234", "check security $tag",
275  warn "D6 00  0F  FE  00 00  05 01   E00401003123AA26  941A ##### ready?\n";                  "D6 00 0D 0A 00", sub {
276                            my $rest = shift;
277                            my $from_tag;
278                            ( $from_tag, $security ) = ( substr($rest,0,8), substr($rest,8,1) );
279                            die "security from other tag: ",as_hex( $from_tag ) if $from_tag ne str2bytes( $tag );
280                            $security = as_hex( $security );
281                            warn "# SECURITY $tag = $security\n";
282                    }
283            );
284    
285            my $data = $tags_data->{$tag} || die "no data for $tag";
286            my ( $u1, $set_item, $u2, $type, $content, $br_lib, $custom ) = unpack('C4Z16Nl>',$data);
287            my $set   = ( $set_item & 0xf0 ) >> 4;
288            my $total = ( $set_item & 0x0f );
289            my $branch  = $br_lib >> 20;
290            my $library = $br_lib & 0x000fffff;
291            print "TAG $tag [$u1] set: $set/$total [$u2] type: $type '$content' branch: $branch library: $library custom: $custom security: $security\n";
292    
293    }
294    
295    sub write_tag {
296            my ($tag) = @_;
297    
298            my $path = "$program_path/$tag";
299    
300            my $data = read_file( $path );
301    
302            $data = substr($data,0,16);
303    
304            my $hex_data = unpack('H*', $data) . ' 00' x ( 16 - length($data) );
305    
306            print "write_tag $tag = $data ",dump( $hex_data );
307    
308            cmd(
309                    "D6 00  26  04  $tag  00 06 00  04 11 00 01  $hex_data 00 00 00 00  FD3B", "write $tag",
310                    "D6 00  0D  04 00  $tag  06  AFB1", sub { assert() },
311            ) foreach ( 1 .. 3 ); # XXX 3M software does this three times!
312    
313            my $to = $path;
314            $to .= '.' . time();
315    
316            rename $path, $to;
317            print ">> $to\n";
318    
319            delete $tags_data->{$tag};      # force re-read of tag
320    }
321    
322    exit;
323    
324  for ( 1 .. 3 ) {  for ( 1 .. 3 ) {
325    
# Line 153  sub writechunk Line 351  sub writechunk
351  {  {
352          my $str=shift;          my $str=shift;
353          my $count = $port->write($str);          my $count = $port->write($str);
354          print ">> ", as_hex( $str ), "\t[$count]\n";          print "#> ", as_hex( $str ), "\t[$count]\n" if $debug;
355  }  }
356    
357  sub as_hex {  sub as_hex {
# Line 161  sub as_hex { Line 359  sub as_hex {
359          foreach my $str ( @_ ) {          foreach my $str ( @_ ) {
360                  my $hex = unpack( 'H*', $str );                  my $hex = unpack( 'H*', $str );
361                  $hex =~ s/(..)/$1 /g if length( $str ) > 2;                  $hex =~ s/(..)/$1 /g if length( $str ) > 2;
362                    $hex =~ s/\s+$//;
363                  push @out, $hex;                  push @out, $hex;
364          }          }
365          return join('  ', @out);          return join(' | ', @out);
366  }  }
367    
368  sub read_bytes {  sub read_bytes {
# Line 171  sub read_bytes { Line 370  sub read_bytes {
370          my $data = '';          my $data = '';
371          while ( length( $data ) < $len ) {          while ( length( $data ) < $len ) {
372                  my ( $c, $b ) = $port->read(1);                  my ( $c, $b ) = $port->read(1);
373                    die "no bytes on port: $!" unless defined $b;
374                  #warn "## got $c bytes: ", as_hex($b), "\n";                  #warn "## got $c bytes: ", as_hex($b), "\n";
375                  $data .= $b;                  $data .= $b;
376          }          }
# Line 200  sub assert { Line 400  sub assert {
400          return substr( $assert->{payload}, $to );          return substr( $assert->{payload}, $to );
401  }  }
402    
403  our $dispatch;  use Digest::CRC;
404  sub dispatch {  
405          my ( $pattern, $coderef ) = @_;  sub crcccitt {
406          my $patt = substr( str2bytes($pattern), 3 ); # just payload          my $bytes = shift;
407          my $l = length($patt);          my $crc = Digest::CRC->new(
408          my $p = substr( $assert->{payload}, 0, $l );                  # midified CCITT to xor with 0xffff instead of 0x0000
409          warn "## dispatch pattern $pattern [$l] ",dump( $patt, $p );                  width => 16, init => 0xffff, xorout => 0xffff, refout => 0, poly => 0x1021, refin => 0,
410            ) or die $!;
411          if ( $assert->{payload} eq $assert->{expect} ) {          $crc->add( $bytes );
412                  warn "## no dispatch, payload expected\n";          pack('n', $crc->digest);
413          } elsif ( $p eq $patt ) {  }
414                  # if matched call with rest of payload  
415                  $coderef->( substr( $assert->{payload}, $l ) );  # my $checksum = checksum( $bytes );
416          } else {  # my $checksum = checksum( $bytes, $original_checksum );
417                  warn "## dispatch ignored\n";  sub checksum {
418            my ( $bytes, $checksum ) = @_;
419    
420            my $xor = crcccitt( substr($bytes,1) ); # skip D6
421            warn "## checksum ",dump( $bytes, $xor, $checksum ) if $debug;
422    
423            my $len = ord(substr($bytes,2,1));
424            my $len_real = length($bytes) - 1;
425    
426            if ( $len_real != $len ) {
427                    print "length wrong: $len_real != $len\n";
428                    $bytes = substr($bytes,0,2) . chr($len_real) . substr($bytes,4);
429          }          }
430    
431            if ( defined $checksum && $xor ne $checksum ) {
432                    print "checksum doesn't match: ", as_hex($xor), " != ", as_hex($checksum), " data: ", as_hex($bytes), "\n";
433                    return $bytes . $xor;
434            }
435            return $bytes . $checksum;
436  }  }
437    
438  sub readchunk {  our $dispatch;
         my ( $parser ) = @_;  
439    
440    sub readchunk {
441          sleep 1;        # FIXME remove          sleep 1;        # FIXME remove
442    
443          # read header of packet          # read header of packet
# Line 228  sub readchunk { Line 445  sub readchunk {
445          my $length = read_bytes( 1, 'length' );          my $length = read_bytes( 1, 'length' );
446          my $len = ord($length);          my $len = ord($length);
447          my $data = read_bytes( $len, 'data' );          my $data = read_bytes( $len, 'data' );
         my ( $cmd ) = unpack('C', $data );  
448    
449          my $payload  = substr( $data, 0, -2 );          my $payload  = substr( $data, 0, -2 );
450          my $payload_len = length($data);          my $payload_len = length($data);
451          warn "## payload too short $payload_len != $len\n" if $payload_len != $len;          warn "## payload too short $payload_len != $len\n" if $payload_len != $len;
452    
453          my $checksum = substr( $data, -2, 2 );          my $checksum = substr( $data, -2, 2 );
454          # FIXME check checksum          checksum( $header . $length . $payload , $checksum );
455    
456          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;
457    
458          $assert->{len}      = $len;          $assert->{len}      = $len;
459          $assert->{payload}  = $payload;          $assert->{payload}  = $payload;
         $assert->{checksum} = $checksum;  
460    
461          $parser->( $len, $payload, $checksum ) if $parser && ref($parser) eq 'CODE';          my $full = $header . $length . $data; # full
462            # find longest match for incomming data
463            my ($to) = grep {
464                    my $match = substr($payload,0,length($_));
465                    m/^\Q$match\E/
466            } sort { length($a) <=> length($b) } keys %$dispatch;
467            warn "?? payload dispatch to ",dump( $payload, $dispatch, $to ) if $debug;
468    
469            if ( defined $to ) {
470                    my $rest = substr( $payload, length($to) );
471                    warn "## DISPATCH payload to with rest", dump( $payload, $to, $rest ) if $debug;
472                    $dispatch->{ $to }->( $rest );
473            } else {
474                    print "NO DISPATCH for ",dump( $full ),"\n";
475            }
476    
477          return $data;          return $data;
478  }  }
# Line 250  sub readchunk { Line 480  sub readchunk {
480  sub str2bytes {  sub str2bytes {
481          my $str = shift || confess "no str?";          my $str = shift || confess "no str?";
482          my $b = $str;          my $b = $str;
483          $b =~ s/\s+(\S\S)(\S\S)+\s*/ $1 $2/;    # fix checksum          $b =~ s/\s+//g;
484          $b =~ s/\s+$//;          $b =~ s/(..)/\\x$1/g;
485          $b =~ s/\s+/\\x/g;          $b = "\"$b\"";
         $b = '"\x' . $b . '"';  
486          my $bytes = eval $b;          my $bytes = eval $b;
487          die $@ if $@;          die $@ if $@;
488          warn "## str2bytes( $str ) => $b => ",as_hex($bytes) if $debug;          warn "## str2bytes( $str ) => $b => ",as_hex($bytes) if $debug;
# Line 261  sub str2bytes { Line 490  sub str2bytes {
490  }  }
491    
492  sub cmd {  sub cmd {
493          my ( $cmd, $cmd_desc, $expect, $expect_desc, $coderef ) = @_;          my $cmd = shift || confess "no cmd?";
494            my $cmd_desc = shift || confess "no description?";
495            my @expect = @_;
496    
497          my $bytes = str2bytes( $cmd );          my $bytes = str2bytes( $cmd );
498    
499          warn ">> ", as_hex( $bytes ), "\t## $cmd_desc\n";          # fix checksum if needed
500            $bytes = checksum( substr( $bytes, 0, -2 ), substr( $bytes, -2, 2 ) );
501    
502            warn ">> ", as_hex( $bytes ), "\t## $cmd_desc\n" if $verbose;
503          $assert->{send} = $cmd;          $assert->{send} = $cmd;
504          writechunk( $bytes );          writechunk( $bytes );
505    
506          if ( $expect ) {          while ( @expect ) {
507                  warn "?? $expect", $expect_desc ? "\t## $expect_desc" : '', "\n";                  my $pattern = str2bytes( shift @expect ) || confess "no pattern?";
508                  $assert->{expect} = substr(str2bytes($expect), 3, -2); # just expected payload                  my $coderef = shift @expect || confess "no coderef?";
509                  readchunk( $coderef );                  confess "not coderef" unless ref $coderef eq 'CODE';
510    
511                    next if defined $dispatch->{ $pattern };
512    
513                    $dispatch->{ substr($pattern,3) } = $coderef;
514                    warn "++ dispatch ", as_hex($pattern) ,dump( $dispatch ) if $debug;
515          }          }
516    
517            readchunk;
518  }  }
519    

Legend:
Removed from v.5  
changed lines
  Added in v.33

  ViewVC Help
Powered by ViewVC 1.1.26