/[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 24 - (show annotations)
Sat Mar 28 14:20:27 2009 UTC (15 years ago) by dpavlin
File MIME type: text/plain
File size: 12350 byte(s)
reset tags_data when no tags are visible,
loop forever

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

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26