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

Contents of /3m-810.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 22 - (show annotations)
Sat Oct 4 11:55:30 2008 UTC (15 years, 6 months ago) by dpavlin
File MIME type: text/plain
File size: 11752 byte(s)
- fixed visible tag handling
- two distinct debug levels ( -d is like verbose -d -d is full debug)
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 use Carp qw/confess/;
9 use Getopt::Long;
10
11 my $debug = 0;
12
13 my $device = "/dev/ttyUSB0";
14 my $baudrate = "19200";
15 my $databits = "8";
16 my $parity = "none";
17 my $stopbits = "1";
18 my $handshake = "none";
19
20 my $response = {
21 'd500090400110a0500027250' => 'version?',
22 'd60007fe00000500c97b' => 'no tag in range',
23
24 'd6000ffe00000501e00401003123aa26941a' => 'tag #1',
25 'd6000ffe00000501e0040100017c0c388e2b' => 'rfid card',
26 'd6000ffe00000501e00401003123aa2875d4' => 'tag red-stripe',
27
28 'd60017fe00000502e00401003123aa26e0040100017c0c38cadb' => 'tag #1 + card',
29 'd60017fe00000502e00401003123aa26e00401003123aa283124' => 'tag #1 + tag red-stripe',
30 };
31
32 GetOptions(
33 'd|debug+' => \$debug,
34 'device=s' => \$device,
35 'baudrate=i' => \$baudrate,
36 'databits=i' => \$databits,
37 'parity=s' => \$parity,
38 'stopbits=i' => \$stopbits,
39 'handshake=s' => \$handshake,
40 ) or die $!;
41
42 my $verbose = $debug > 0 ? $debug-- : 0;
43
44 =head1 NAME
45
46 3m-810 - support for 3M 810 RFID reader
47
48 =head1 SYNOPSIS
49
50 3m-810.pl --device /dev/ttyUSB0
51
52 =head1 DESCRIPTION
53
54 Communicate with 3M 810 RFID reader and document it's protocol
55
56 =head1 SEE ALSO
57
58 L<Device::SerialPort(3)>
59
60 L<perl(1)>
61
62 L<http://stackoverflow.com/questions/149617/how-could-i-guess-a-checksum-algorithm>
63
64 =head1 AUTHOR
65
66 Dobrica Pavlinusic <dpavlin@rot13.org> L<http://www.rot13.org/~dpavlin/>
67
68 =head1 COPYRIGHT AND LICENSE
69
70 This program is free software; you may redistribute it and/or modify
71 it under the same terms ans Perl itself.
72
73 =cut
74
75 my $port=new Device::SerialPort($device) || die "can't open serial port $device: $!\n";
76 warn "using $device $handshake $baudrate $databits $parity $stopbits" if $debug;
77 $handshake=$port->handshake($handshake);
78 $baudrate=$port->baudrate($baudrate);
79 $databits=$port->databits($databits);
80 $parity=$port->parity($parity);
81 $stopbits=$port->stopbits($stopbits);
82
83 print "## using $device $baudrate $databits $parity $stopbits debug: $debug verbose: $verbose\n";
84
85 # Just in case: reset our timing and buffers
86 $port->lookclear();
87 $port->read_const_time(100);
88 $port->read_char_time(5);
89
90 # Turn on parity checking:
91 #$port->stty_inpck(1);
92 #$port->stty_istrip(1);
93
94 # initial hand-shake with device
95
96 cmd( 'D5 00 05 04 00 11 8C66', 'hw version',
97 'D5 00 09 04 00 11 0A 05 00 02 7250', sub {
98 print "hardware version ", join('.', unpack('CCCC', skip_assert(3) )), "\n";
99 });
100
101 cmd( 'D6 00 0C 13 04 01 00 02 00 03 00 04 00 AAF2','FIXME: stats?',
102 'D6 00 0C 13 00 02 01 01 03 02 02 03 00 E778', sub { assert() } );
103
104 # start scanning for tags
105
106 cmd( 'D6 00 05 FE 00 05 FA40', "scan for tags, retry $_",
107 'D6 00 0F FE 00 00 05 ', sub { # 01 E00401003123AA26 941A # seen, serial length: 8
108 my $rest = shift || die "no rest?";
109 my $nr = ord( substr( $rest, 0, 1 ) );
110
111 if ( ! $nr ) {
112 print "no tags in range\n";
113 update_visible_tags();
114 } else {
115
116 my $tags = substr( $rest, 1 );
117
118 my $tl = length( $tags );
119 die "wrong length $tl for $nr tags: ",dump( $tags ) if $tl =! $nr * 8;
120
121 my @tags;
122 push @tags, uc(unpack('H16', substr($tags, $_ * 8, 8))) foreach ( 0 .. $nr - 1 );
123 warn "## tags ",as_hex($tags), " [$tl] = ",dump( $tags ) if $debug;
124 print "$nr tags in range: ", join(',', @tags ) , "\n";
125
126 update_visible_tags( @tags );
127
128 }
129 }
130 ) foreach ( 1 .. 100 );
131
132
133
134 my $tags_data;
135 my $visible_tags;
136
137 sub update_visible_tags {
138 my @tags = @_;
139
140 my $last_visible_tags = $visible_tags;
141 $visible_tags = {};
142
143 foreach my $tag ( @tags ) {
144 if ( ! defined $last_visible_tags->{$tag} ) {
145 read_tag( $tag );
146 $visible_tags->{$tag}++;
147 } else {
148 warn "## using cached data for $tag" if $debug;
149 }
150 delete $last_visible_tags->{$tag}; # leave just missing tags
151 }
152
153 foreach my $tag ( keys %$last_visible_tags ) {
154 print "removed tag $tag with data ",dump( delete $tags_data->{$tag} ),"\n";
155 }
156
157 warn "## update_visible_tags(",dump( @tags ),") = ",dump( $visible_tags )," removed: ",dump( $last_visible_tags ), " data: ",dump( $tags_data ) if $debug;
158 }
159
160
161 sub read_tag {
162 my ( $tag ) = @_;
163
164 confess "no tag?" unless $tag;
165
166 return if defined $tags_data->{$tag};
167
168 print "read_tag $tag\n";
169
170 cmd(
171 "D6 00 0D 02 $tag 00 03 1CC4", 'read $tag offset: 0 blocks: 3',
172 "D6 00 0F FE 00 00 05 01 $tag 941A", sub {
173 print "FIXME: tag $tag ready?\n";
174 },
175 "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";
176 my $rest = shift || die "no rest?";
177 warn "## DATA ", dump( $rest ) if $debug;
178 my $tag = uc(unpack('H16',substr( $rest, 0, 8 )));
179 my $blocks = ord(substr($rest,8,1));
180 $rest = substr($rest,9); # leave just data blocks
181 my @data;
182 foreach my $nr ( 0 .. $blocks - 1 ) {
183 my $block = substr( $rest, $nr * 6, 6 );
184 warn "## block ",as_hex( $block ) if $debug;
185 my $ord = unpack('v',substr( $block, 0, 2 ));
186 die "got block $ord, expected block $nr from ",dump( $block ) if $ord != $nr;
187 my $data = substr( $block, 2 );
188 die "data payload should be 4 bytes" if length($data) != 4;
189 warn sprintf "## tag %08s %02d %s |%-4s|\n", $tag, $ord, as_hex( $data ), $data;
190 $data[ $ord ] = $data;
191 }
192 $tags_data->{ $tag } = join('', @data);
193 print "DATA $tag ",dump( $tags_data ), "\n";
194 }
195 );
196
197 # D6 00 1F 02 00 $tag 03 00 00 04 11 00 01 01 00 30 30 30 30 02 00 30 30 30 30 E5F4
198 if (0) {
199 cmd( "D6 00 0D 02 $tag 03 04 3970", 'read offset: 3 blocks: 4' );
200
201 # D6 00 25 02 00 $tag 04 03 00 30 30 00 00 04 00 00 00 00 00
202 # $tag 05 00 00 00 00 00 06 00 00 00 00 00 B9BA
203 warn "?? D6 00 25 02 00 $tag 04 03 00 39 30 31 32 04 00 ....\n";
204 }
205 warn "?? D6 00 0F FE 00 00 05 01 $tag 941A ##### ready?\n";
206
207 }
208
209 exit;
210
211 for ( 1 .. 3 ) {
212
213 # ++-->type 00-0a
214 # 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
215 # 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
216 # 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
217
218 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 $_" );
219 warn "D6 00 0D 04 00 E00401003123AA26 07 CFF1 -- ack 7 block?\n";
220
221 }
222 warn " D6 00 0F FE 00 00 05 01 E00401003123AA26 941A\n";
223
224 cmd( 'D6 00 05 FE 00 05 FA 40', "port-write scan $_" ) foreach ( 1 .. 2 );
225
226 cmd('D6 00 0C 09 E00401003123AA26 D7 3AF0', 'checkin?',
227 'D6 00 0C 09 00 E00401003123AA26 6A44 -- no?' );
228 cmd('D6 00 0C 09 E00401003123AA26 DA EB5D', 'checkout?',
229 'D6 00 0C 09 00 E00401003123AA26 6A44 -- no?' );
230
231 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',
232 'D6 00 0D 04 00 E00401003123AA26 06 DFD0 -- ack 6 blocks' ) foreach ( 1 .. 3 );
233
234 undef $port;
235 print "Port closed\n";
236
237 sub writechunk
238 {
239 my $str=shift;
240 my $count = $port->write($str);
241 print "#> ", as_hex( $str ), "\t[$count]\n" if $debug;
242 }
243
244 sub as_hex {
245 my @out;
246 foreach my $str ( @_ ) {
247 my $hex = unpack( 'H*', $str );
248 $hex =~ s/(..)/$1 /g if length( $str ) > 2;
249 $hex =~ s/\s+$//;
250 push @out, $hex;
251 }
252 return join(' | ', @out);
253 }
254
255 sub read_bytes {
256 my ( $len, $desc ) = @_;
257 my $data = '';
258 while ( length( $data ) < $len ) {
259 my ( $c, $b ) = $port->read(1);
260 #warn "## got $c bytes: ", as_hex($b), "\n";
261 $data .= $b;
262 }
263 $desc ||= '?';
264 warn "#< ", as_hex($data), "\t$desc\n" if $debug;
265 return $data;
266 }
267
268 our $assert;
269
270 # my $rest = skip_assert( 3 );
271 sub skip_assert {
272 assert( 0, shift );
273 }
274
275 sub assert {
276 my ( $from, $to ) = @_;
277
278 $from ||= 0;
279 $to = length( $assert->{expect} ) if ! defined $to;
280
281 my $p = substr( $assert->{payload}, $from, $to );
282 my $e = substr( $assert->{expect}, $from, $to );
283 warn "EXPECTED ",as_hex($e), " GOT ", as_hex($p), " [$from-$to] in ",dump( $assert ), "\n" if $e ne $p;
284
285 # return the rest
286 return substr( $assert->{payload}, $to );
287 }
288
289 use Digest::CRC;
290
291 sub crcccitt {
292 my $bytes = shift;
293 my $crc = Digest::CRC->new(
294 # midified CCITT to xor with 0xffff instead of 0x0000
295 width => 16, init => 0xffff, xorout => 0xffff, refout => 0, poly => 0x1021, refin => 0,
296 ) or die $!;
297 $crc->add( $bytes );
298 pack('n', $crc->digest);
299 }
300
301 # my $checksum = checksum( $bytes );
302 # my $checksum = checksum( $bytes, $original_checksum );
303 sub checksum {
304 my ( $bytes, $checksum ) = @_;
305
306 my $xor = crcccitt( substr($bytes,1) ); # skip D6
307 warn "## checksum ",dump( $bytes, $xor, $checksum ) if $debug;
308
309 my $len = ord(substr($bytes,2,1));
310 my $len_real = length($bytes) - 1;
311
312 if ( $len_real != $len ) {
313 print "length wrong: $len_real != $len\n";
314 $bytes = substr($bytes,0,2) . chr($len_real) . substr($bytes,4);
315 }
316
317 if ( defined $checksum && $xor ne $checksum ) {
318 print "checksum doesn't match: ", as_hex($xor), " != ", as_hex($checksum), " data: ", as_hex($bytes), "\n";
319 return $bytes . $xor;
320 }
321 return $bytes . $checksum;
322 }
323
324 our $dispatch;
325
326 sub readchunk {
327 sleep 1; # FIXME remove
328
329 # read header of packet
330 my $header = read_bytes( 2, 'header' );
331 my $length = read_bytes( 1, 'length' );
332 my $len = ord($length);
333 my $data = read_bytes( $len, 'data' );
334
335 my $payload = substr( $data, 0, -2 );
336 my $payload_len = length($data);
337 warn "## payload too short $payload_len != $len\n" if $payload_len != $len;
338
339 my $checksum = substr( $data, -2, 2 );
340 checksum( $header . $length . $payload , $checksum );
341
342 print "<< ",as_hex( $header ), " [$len] ", as_hex( $payload ), " | sum: ",as_hex($checksum),"\n" if $verbose;
343
344 $assert->{len} = $len;
345 $assert->{payload} = $payload;
346
347 my $full = $header . $length . $data; # full
348 # find longest match for incomming data
349 my ($to) = grep {
350 my $match = substr($payload,0,length($_));
351 m/^\Q$match\E/
352 } sort { length($a) <=> length($b) } keys %$dispatch;
353 warn "?? payload dispatch to ",dump( $payload, $dispatch, $to ) if $debug;
354
355 if ( defined $to ) {
356 my $rest = substr( $payload, length($to) );
357 warn "## DISPATCH payload to with rest", dump( $payload, $to, $rest ) if $debug;
358 $dispatch->{ $to }->( $rest );
359 } else {
360 print "NO DISPATCH for ",dump( $full ),"\n";
361 }
362
363 return $data;
364 }
365
366 sub str2bytes {
367 my $str = shift || confess "no str?";
368 my $b = $str;
369 $b =~ s/\s+//g;
370 $b =~ s/(..)/\\x$1/g;
371 $b = "\"$b\"";
372 my $bytes = eval $b;
373 die $@ if $@;
374 warn "## str2bytes( $str ) => $b => ",as_hex($bytes) if $debug;
375 return $bytes;
376 }
377
378 sub cmd {
379 my $cmd = shift || confess "no cmd?";
380 my $cmd_desc = shift || confess "no description?";
381 my @expect = @_;
382
383 my $bytes = str2bytes( $cmd );
384
385 # fix checksum if needed
386 $bytes = checksum( substr( $bytes, 0, -2 ), substr( $bytes, -2, 2 ) );
387
388 warn ">> ", as_hex( $bytes ), "\t## $cmd_desc\n" if $verbose;
389 $assert->{send} = $cmd;
390 writechunk( $bytes );
391
392 while ( @expect ) {
393 my $pattern = str2bytes( shift @expect ) || confess "no pattern?";
394 my $coderef = shift @expect || confess "no coderef?";
395 confess "not coderef" unless ref $coderef eq 'CODE';
396
397 next if defined $dispatch->{ $pattern };
398
399 $dispatch->{ substr($pattern,3) } = $coderef;
400 warn "++ dispatch ", as_hex($pattern) ,dump( $dispatch ) if $debug;
401 }
402
403 readchunk;
404 }
405

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26