1 package RFID::Biblio::3M810;
5 RFID::Biblio::3M810 - support for 3M 810 RFID reader
9 This module implement serial protocol (over USB/serial adapter) with 3M 810 RFID
10 reader, often used in library applications.
12 This is most complete implementation which supports full API defined
13 in L<RFID::Biblio>. This include scanning for all tags in reader
14 range, reading and writing of data, and AFI security manipulation.
16 This implementation is developed using Portmon on Windows to capture serial traffic
17 L<http://technet.microsoft.com/en-us/sysinternals/bb896644.aspx>
19 Checksum for this reader is developed using help from C<selwyn>
20 L<http://stackoverflow.com/questions/149617/how-could-i-guess-a-checksum-algorithm>
27 use base 'RFID::Biblio';
30 use Data::Dump qw(dump);
35 sub serial_settings {{
36 device => "/dev/ttyUSB1", # FIXME comment out before shipping
50 my ( $count, $str ) = $port->read(3);
51 my $data = $port->read( ord(substr($str,2,1)) );
52 warn "drain ",as_hex( $str, $data ),"\n";
60 my $crc = Digest::CRC->new(
61 # midified CCITT to xor with 0xffff instead of 0x0000
62 width => 16, init => 0xffff, xorout => 0xffff, refout => 0, poly => 0x1021, refin => 0,
65 pack('n', $crc->digest);
69 Time::HiRes::sleep 0.015;
73 my ( $hex, $description, $coderef ) = @_;
74 my $bytes = hex2bytes($hex);
75 if ( substr($bytes,0,1) !~ /(\xD5|\xD6)/ ) {
76 my $len = pack( 'n', length( $bytes ) + 2 );
77 $bytes = $len . $bytes;
78 my $checksum = checksum($bytes);
79 $bytes = "\xD6" . $bytes . $checksum;
82 warn ">> ", as_hex( $bytes ), "\t\t[$description]\n";
83 $port->write( $bytes );
87 my $r_len = $port->read(3);
89 while ( length($r_len) < 3 ) {
91 $r_len = $port->read( 3 - length($r_len) );
96 my $len = ord( substr($r_len,2,1) );
97 my $data = $port->read( $len );
99 while ( length($data) < $len ) {
100 warn "# short read ", length($data), " < $len\n";
102 $data .= $port->read( $len - length($data) );
105 warn "<< ", as_hex($r_len,$data),
107 substr($data,-2,2) eq checksum(substr($r_len,1).substr($data,0,-2)) ? 'OK' : 'ERROR',
111 $coderef->( $data ) if $coderef;
116 my ( $got, $expected ) = @_;
117 $expected = hex2bytes($expected);
119 my $len = length($got);
120 $len = length($expected) if length $expected < $len;
122 confess "got ", as_hex($got), " expected ", as_hex($expected)
123 unless substr($got,0,$len) eq substr($expected,0,$len);
125 return substr($got,$len);
131 'D5 00 05 04 00 11 8C66', 'hw version', sub {
133 my $rest = assert $data => '04 00 11';
134 my $hw_ver = join('.', unpack('CCCC', $rest));
135 warn "# 3M 810 hardware version $hw_ver\n";
139 '13 04 01 00 02 00 03 00 04 00','FIXME: stats? rf-on?', sub { assert(shift,
140 '13 00 02 01 01 03 02 02 03 00'
146 my @tags = inventory;
154 cmd( 'FE 00 05', 'scan for tags', sub {
156 my $rest = assert $data => 'FE 00 00 05';
157 my $nr = ord( substr( $rest, 0, 1 ) );
160 warn "# no tags in range\n";
162 my $tags = substr( $rest, 1 );
163 my $tl = length( $tags );
164 die "wrong length $tl for $nr tags: ",dump( $tags ) if $tl =! $nr * 8;
166 foreach ( 0 .. $nr - 1 ) {
167 push @tags, hex_tag substr($tags, $_ * 8, 8);
173 warn "# tags ",dump @tags;
179 # cards 16, stickers: 8
180 my $max_rfid_block = 8;
184 my ( $data, $hex ) = @_;
185 my $b = hex2bytes $hex;
187 if ( substr($data,0,$l) eq $b ) {
188 warn "_matched $hex [$l] in ",as_hex($data);
189 return substr($data,$l);
194 my $tag = shift || confess "no tag?";
195 $tag = shift if ref($tag);
200 sprintf( "02 $tag %02x %02x", $start, $blocks ) => "read_blocks $tag $start/$blocks", sub {
202 if ( my $rest = _matched $data => '02 00' ) {
204 my $tag = hex_tag substr($rest,0,8);
205 my $blocks = ord(substr($rest,8,1));
206 warn "# response from $tag $blocks blocks ",as_hex substr($rest,9);
207 foreach ( 1 .. $blocks ) {
208 my $pos = ( $_ - 1 ) * 6 + 9;
209 my $nr = unpack('v', substr($rest,$pos,2));
210 my $payload = substr($rest,$pos+2,4);
211 warn "## pos $pos block $nr ",as_hex($payload), $/;
212 $tag_blocks->{$tag}->[$nr] = $payload;
214 } elsif ( $rest = _matched $data => 'FE 00 00 05 01' ) {
215 warn "FIXME ready? ",as_hex $rest;
216 } elsif ( $rest = _matched $data => '02 06' ) {
217 warn "ERROR ",as_hex($rest);
219 warn "FIXME unsuported ",as_hex($rest);
223 warn "# tag_blocks ",dump($tag_blocks);
229 $tag = shift if ref $tag;
230 my $data = join('', @_);
232 warn "## write_blocks ",dump($tag,$data);
234 if ( length($data) % 4 ) {
235 $data .= '\x00' x ( 4 - length($data) % 4 );
236 warn "# padded data to ",dump($data);
239 my $hex_data = as_hex $data;
240 my $blocks = sprintf('%02x', length($data) / 4 );
243 "04 $tag 00 $blocks 00 $hex_data", "write_blocks $tag [$blocks] $hex_data", sub {
245 if ( my $rest = _matched $data => '04 00' ) {
246 my $tag = substr($rest,0,8);
247 my $blocks = substr($rest,8,1);
248 warn "# WRITE ",as_hex($tag), " [$blocks]\n";
249 } elsif ( $rest = _matched $data => '04 06' ) {
250 warn "ERROR ",as_hex($rest);
261 $tag = shift if ref $tag;
266 "0A $tag", "read_afi $tag", sub {
269 if ( my $rest = _matched $data => '0A 00' ) {
271 my $tag = substr($rest,0,8);
272 $afi = substr($rest,8,1);
274 warn "# SECURITY ", hex_tag($tag), " AFI: ", as_hex($afi);
276 } elsif ( $rest = _matched $data => '0A 06' ) {
277 warn "ERROR reading security from $tag ", as_hex($data);
279 warn "IGNORED ",as_hex($data);
282 warn "## read_afi ",dump($tag, $afi);
288 $tag = shift if ref $tag;
289 my $afi = shift || die "no afi?";
294 "09 $tag $afi", "write_afi $tag $afi", sub {
297 if ( my $rest = _matched $data => '09 00' ) {
299 my $tag = substr($rest,0,8);
300 $afi = substr($rest,8,1);
302 warn "# SECURITY ", hex_tag($tag), " AFI: ", as_hex($afi);
304 } elsif ( $rest = _matched $data => '0A 06' ) {
305 warn "ERROR writing AFI to $tag ", as_hex($data);
308 warn "IGNORED ",as_hex($data);
312 warn "## write_afi ", dump( $tag, $afi );