1 package RFID::Biblio::Reader::3M810;
5 RFID::Biblio::Reader::3M810 - support for 3M 810 RFID reader
9 This module uses L<RFID::Biblio::Reader::Serial> over USB/serial adapter
10 with 3M 810 RFID reader, often used in library applications.
12 This is most mature implementation which supports full API defined
13 in L<RFID::Biblio::Reader::API>. 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>
22 More inforation about process of reverse engeeniring protocol with
23 this reader is available at L<http://blog.rot13.org/rfid/>
30 use base 'RFID::Biblio::Reader::Serial';
33 use Data::Dump qw(dump);
38 sub serial_settings {{
39 device => "/dev/ttyUSB1", # FIXME comment out before shipping
53 $port->read_char_time(0);
54 $port->read_const_time(0);
57 my ( $count, $str ) = $port->read(3);
58 my $data = $port->read( ord(substr($str,2,1)) );
59 warn "drain ",as_hex( $str, $data ),"\n";
61 $port->read_char_time(100); # 0.1 s char timeout
62 $port->read_const_time(500); # 0.5 s read timeout
70 my $crc = Digest::CRC->new(
71 # midified CCITT to xor with 0xffff instead of 0x0000
72 width => 16, init => 0xffff, xorout => 0xffff, refout => 0, poly => 0x1021, refin => 0,
75 pack('n', $crc->digest);
79 Time::HiRes::sleep 0.015;
83 my ( $hex, $description, $coderef ) = @_;
84 my $bytes = hex2bytes($hex);
85 if ( substr($bytes,0,1) !~ /(\xD5|\xD6)/ ) {
86 my $len = pack( 'n', length( $bytes ) + 2 );
87 $bytes = $len . $bytes;
88 my $checksum = checksum($bytes);
89 $bytes = "\xD6" . $bytes . $checksum;
92 warn ">> ", as_hex( $bytes ), "\t\t[$description]\n";
93 $port->write( $bytes );
97 my $r_len = $port->read(3);
99 while ( length($r_len) < 3 ) {
101 $r_len = $port->read( 3 - length($r_len) );
106 my $len = ord( substr($r_len,2,1) );
107 my $data = $port->read( $len );
109 while ( length($data) < $len ) {
110 warn "# short read ", length($data), " < $len\n";
112 $data .= $port->read( $len - length($data) );
115 warn "<< ", as_hex($r_len,$data),
117 substr($data,-2,2) eq checksum(substr($r_len,1).substr($data,0,-2)) ? 'OK' : 'ERROR',
121 $coderef->( $data ) if $coderef;
126 my ( $got, $expected ) = @_;
127 $expected = hex2bytes($expected);
129 my $len = length($got);
130 $len = length($expected) if length $expected < $len;
132 confess "got ", as_hex($got), " expected ", as_hex($expected)
133 unless substr($got,0,$len) eq substr($expected,0,$len);
135 return substr($got,$len);
141 'D5 00 05 04 00 11 8C66', 'hw version', sub {
143 my $rest = assert $data => '04 00 11';
144 my $hw_ver = join('.', unpack('CCCC', $rest));
145 warn "# 3M 810 hardware version $hw_ver\n";
149 '13 04 01 00 02 00 03 00 04 00','FIXME: stats? rf-on?', sub { assert(shift,
150 '13 00 02 01 01 03 02 02 03 00'
158 cmd( 'FE 00 05', 'scan for tags', sub {
160 my $rest = assert $data => 'FE 00 00 05';
161 my $nr = ord( substr( $rest, 0, 1 ) );
164 warn "# no tags in range\n";
166 my $tags = substr( $rest, 1 );
167 my $tl = length( $tags );
168 die "wrong length $tl for $nr tags: ",dump( $tags ) if $tl =! $nr * 8;
170 foreach ( 0 .. $nr - 1 ) {
171 push @tags, hex_tag substr($tags, $_ * 8, 8);
177 warn "# tags ",dump @tags;
183 # cards 16, stickers: 8
184 my $max_rfid_block = 8;
188 my ( $data, $hex ) = @_;
189 my $b = hex2bytes $hex;
191 if ( substr($data,0,$l) eq $b ) {
192 warn "_matched $hex [$l] in ",as_hex($data);
193 return substr($data,$l);
198 my $tag = shift || confess "no tag?";
199 $tag = shift if ref($tag);
204 sprintf( "02 $tag %02x %02x", $start, $blocks ) => "read_blocks $tag $start/$blocks", sub {
206 if ( my $rest = _matched $data => '02 00' ) {
208 my $tag = hex_tag substr($rest,0,8);
209 my $blocks = ord(substr($rest,8,1));
210 warn "# response from $tag $blocks blocks ",as_hex substr($rest,9);
211 foreach ( 1 .. $blocks ) {
212 my $pos = ( $_ - 1 ) * 6 + 9;
213 my $nr = unpack('v', substr($rest,$pos,2));
214 my $payload = substr($rest,$pos+2,4);
215 warn "## pos $pos block $nr ",as_hex($payload), $/;
216 $tag_blocks->{$tag}->[$nr] = $payload;
218 } elsif ( $rest = _matched $data => 'FE 00 00 05 01' ) {
219 warn "FIXME ready? ",as_hex $rest;
220 } elsif ( $rest = _matched $data => '02 06' ) {
221 warn "ERROR ",as_hex($rest);
223 warn "FIXME unsuported ",as_hex($rest);
227 warn "# tag_blocks ",dump($tag_blocks);
233 $tag = shift if ref $tag;
236 $data = join('', @$data) if ref $data eq 'ARRAY';
238 warn "## write_blocks ",dump($tag,$data);
240 if ( length($data) % 4 ) {
241 $data .= '\x00' x ( 4 - length($data) % 4 );
242 warn "# padded data to ",dump($data);
245 my $hex_data = as_hex $data;
246 my $blocks = sprintf('%02x', length($data) / 4 );
249 "04 $tag 00 $blocks 00 $hex_data", "write_blocks $tag [$blocks] $hex_data", sub {
251 if ( my $rest = _matched $data => '04 00' ) {
252 my $tag = substr($rest,0,8);
253 my $blocks = substr($rest,8,1);
254 warn "# WRITE ",as_hex($tag), " [$blocks]\n";
255 } elsif ( $rest = _matched $data => '04 06' ) {
256 warn "ERROR ",as_hex($rest);
267 $tag = shift if ref $tag;
272 "0A $tag", "read_afi $tag", sub {
275 if ( my $rest = _matched $data => '0A 00' ) {
277 my $tag = substr($rest,0,8);
278 $afi = substr($rest,8,1);
280 warn "# SECURITY ", hex_tag($tag), " AFI: ", as_hex($afi);
282 } elsif ( $rest = _matched $data => '0A 06' ) {
283 warn "ERROR reading security from $tag ", as_hex($data);
285 warn "IGNORED ",as_hex($data);
288 warn "## read_afi ",dump($tag, $afi);
294 $tag = shift if ref $tag;
295 my $afi = shift || die "no afi?";
300 "09 $tag $afi", "write_afi $tag $afi", sub {
303 if ( my $rest = _matched $data => '09 00' ) {
304 my $tag_back = hex_tag substr($rest,0,8);
305 die "write_afi got $tag_back expected $tag" if $tag_back ne $tag;
306 warn "# SECURITY ", hex_tag($tag), " AFI: ", as_hex($afi);
307 } elsif ( $rest = _matched $data => '0A 06' ) {
308 warn "ERROR writing AFI to $tag ", as_hex($data);
311 warn "IGNORED ",as_hex($data);
315 warn "## write_afi ", dump( $tag, $afi );
325 L<RFID::Biblio::Reader::API>