1 |
dpavlin |
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 |
dpavlin |
2 |
use Carp qw/confess/; |
9 |
dpavlin |
1 |
|
10 |
dpavlin |
4 |
my $debug = 0; |
11 |
|
|
|
12 |
dpavlin |
1 |
my $response = { |
13 |
|
|
'd500090400110a0500027250' => 'version?', |
14 |
|
|
'd60007fe00000500c97b' => 'no tag in range', |
15 |
|
|
|
16 |
|
|
'd6000ffe00000501e00401003123aa26941a' => 'tag #1', |
17 |
|
|
'd6000ffe00000501e0040100017c0c388e2b' => 'rfid card', |
18 |
|
|
'd6000ffe00000501e00401003123aa2875d4' => 'tag red-stripe', |
19 |
|
|
|
20 |
|
|
'd60017fe00000502e00401003123aa26e0040100017c0c38cadb' => 'tag #1 + card', |
21 |
|
|
'd60017fe00000502e00401003123aa26e00401003123aa283124' => 'tag #1 + tag red-stripe', |
22 |
|
|
}; |
23 |
|
|
|
24 |
|
|
=head1 NAME |
25 |
|
|
|
26 |
|
|
3m-810 - support for 3M 810 RFID reader |
27 |
|
|
|
28 |
|
|
=head1 SYNOPSIS |
29 |
|
|
|
30 |
|
|
3m-810.pl [DEVICE [BAUD [DATA [PARITY [STOP [FLOW]]]]]] |
31 |
|
|
|
32 |
|
|
=head1 DESCRIPTION |
33 |
|
|
|
34 |
|
|
Communicate with 3M 810 RFID reader and document it's protocol |
35 |
|
|
|
36 |
|
|
=head1 SEE ALSO |
37 |
|
|
|
38 |
|
|
L<Device::SerialPort(3)> |
39 |
|
|
|
40 |
|
|
L<perl(1)> |
41 |
|
|
|
42 |
|
|
=head1 AUTHOR |
43 |
|
|
|
44 |
|
|
Dobrica Pavlinusic <dpavlin@rot13.org> L<http://www.rot13.org/~dpavlin/> |
45 |
|
|
|
46 |
|
|
=head1 COPYRIGHT AND LICENSE |
47 |
|
|
|
48 |
|
|
This program is free software; you may redistribute it and/or modify |
49 |
|
|
it under the same terms ans Perl itself. |
50 |
|
|
|
51 |
|
|
=cut |
52 |
|
|
|
53 |
|
|
# your serial port. |
54 |
|
|
my ($device,$baudrate,$databits,$parity,$stopbits,$handshake)=@ARGV; |
55 |
|
|
$device ||= "/dev/ttyUSB0"; |
56 |
|
|
$baudrate ||= "19200"; |
57 |
|
|
$databits ||= "8"; |
58 |
|
|
$parity ||= "none"; |
59 |
|
|
$stopbits ||= "1"; |
60 |
|
|
$handshake ||= "none"; |
61 |
|
|
|
62 |
|
|
my $port=new Device::SerialPort($device) || die "new($device): $!\n"; |
63 |
|
|
$handshake=$port->handshake($handshake); |
64 |
|
|
$baudrate=$port->baudrate($baudrate); |
65 |
|
|
$databits=$port->databits($databits); |
66 |
|
|
$parity=$port->parity($parity); |
67 |
|
|
$stopbits=$port->stopbits($stopbits); |
68 |
|
|
|
69 |
|
|
print "## using $device $baudrate $databits $parity $stopbits\n"; |
70 |
|
|
|
71 |
|
|
# Just in case: reset our timing and buffers |
72 |
|
|
$port->lookclear(); |
73 |
|
|
$port->read_const_time(100); |
74 |
|
|
$port->read_char_time(5); |
75 |
|
|
|
76 |
|
|
# Turn on parity checking: |
77 |
|
|
#$port->stty_inpck(1); |
78 |
|
|
#$port->stty_istrip(1); |
79 |
|
|
|
80 |
dpavlin |
4 |
# initial hand-shake with device |
81 |
|
|
|
82 |
|
|
cmd( 'D5 00 05 04 00 11 8C66', 'hw version?', |
83 |
|
|
'D5 00 09 04 00 11 0A 05 00 02 7250', 'hw 10.5.0.2', sub { |
84 |
dpavlin |
2 |
my ( $len, $payload, $checksum ) = @_; |
85 |
|
|
assert( 0, 3 ); |
86 |
|
|
print "hardware version ", join('.', unpack('CCCC', substr($payload,3,4))), "\n"; |
87 |
|
|
}); |
88 |
dpavlin |
1 |
|
89 |
dpavlin |
4 |
cmd( 'D6 00 0C 13 04 01 00 02 00 03 00 04 00 AAF2','stats?', |
90 |
|
|
'D6 00 0C 13 00 02 01 01 03 02 02 03 00 E778','FIXME: unimplemented', sub { assert( 0 ) } ); |
91 |
dpavlin |
1 |
|
92 |
dpavlin |
4 |
# start scanning for tags |
93 |
dpavlin |
1 |
|
94 |
dpavlin |
4 |
cmd( 'D6 00 05 FE 00 05 FA40', "XXX scan $_", |
95 |
|
|
'D6 00 07 FE 00 00 05 00 C97B', 'no tag' ) foreach ( 1 .. 10 ); |
96 |
dpavlin |
1 |
# D6 00 0F FE 00 00 05 01 E00401003123AA26 941A # seen |
97 |
|
|
|
98 |
|
|
cmd( 'D6 00 0D 02 E00401003123AA26 00 03 1CC4', 'read offset: 0 blocks: 3' ); |
99 |
|
|
|
100 |
|
|
# 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 |
101 |
|
|
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"; |
102 |
|
|
|
103 |
|
|
cmd( 'D6 00 0D 02 E00401003123AA26 03 04 3970', 'read offset: 3 blocks: 4' ); |
104 |
|
|
|
105 |
|
|
# D6 00 25 02 00 E00401003123AA26 04 03 00 30 30 00 00 04 00 00 00 00 00 |
106 |
|
|
# 05 00 00 00 00 00 06 00 00 00 00 00 B9BA |
107 |
|
|
warn "D6 00 25 02 00 E00401003123AA26 04 03 00 39 30 31 32 04 00 33 34 35 36 |
108 |
|
|
05 00 00 00 00 00 06 00 00 00 00 00 524B\n"; |
109 |
|
|
warn "D6 00 0F FE 00 00 05 01 E00401003123AA26 941A ##### ready?\n"; |
110 |
|
|
|
111 |
|
|
for ( 1 .. 3 ) { |
112 |
|
|
|
113 |
|
|
# ++-->type 00-0a |
114 |
|
|
# 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 |
115 |
|
|
# 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 |
116 |
|
|
# 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 |
117 |
|
|
|
118 |
|
|
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 $_" ); |
119 |
|
|
warn "D6 00 0D 04 00 E00401003123AA26 07 CFF1 -- ack 7 block?\n"; |
120 |
|
|
|
121 |
|
|
} |
122 |
|
|
warn " D6 00 0F FE 00 00 05 01 E00401003123AA26 941A\n"; |
123 |
|
|
|
124 |
|
|
cmd( 'D6 00 05 FE 00 05 FA 40', "port-write scan $_" ) foreach ( 1 .. 2 ); |
125 |
|
|
|
126 |
|
|
cmd('D6 00 0C 09 E00401003123AA26 D7 3AF0', 'checkin?', |
127 |
|
|
'D6 00 0C 09 00 E00401003123AA26 6A44 -- no?' ); |
128 |
|
|
cmd('D6 00 0C 09 E00401003123AA26 DA EB5D', 'checkout?', |
129 |
|
|
'D6 00 0C 09 00 E00401003123AA26 6A44 -- no?' ); |
130 |
|
|
|
131 |
|
|
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', |
132 |
|
|
'D6 00 0D 04 00 E00401003123AA26 06 DFD0 -- ack 6 blocks' ) foreach ( 1 .. 3 ); |
133 |
|
|
|
134 |
|
|
undef $port; |
135 |
|
|
print "Port closed\n"; |
136 |
|
|
|
137 |
|
|
sub writechunk |
138 |
|
|
{ |
139 |
|
|
my $str=shift; |
140 |
|
|
my $count = $port->write($str); |
141 |
|
|
print ">> ", as_hex( $str ), "\t[$count]\n"; |
142 |
|
|
} |
143 |
|
|
|
144 |
|
|
sub as_hex { |
145 |
|
|
my @out; |
146 |
|
|
foreach my $str ( @_ ) { |
147 |
|
|
my $hex = unpack( 'H*', $str ); |
148 |
dpavlin |
2 |
$hex =~ s/(..)/$1 /g if length( $str ) > 2; |
149 |
dpavlin |
1 |
push @out, $hex; |
150 |
|
|
} |
151 |
|
|
return join(' ', @out); |
152 |
|
|
} |
153 |
|
|
|
154 |
|
|
sub read_bytes { |
155 |
|
|
my ( $len, $desc ) = @_; |
156 |
|
|
my $data = ''; |
157 |
|
|
while ( length( $data ) < $len ) { |
158 |
|
|
my ( $c, $b ) = $port->read(1); |
159 |
|
|
#warn "## got $c bytes: ", as_hex($b), "\n"; |
160 |
|
|
$data .= $b; |
161 |
|
|
} |
162 |
|
|
$desc ||= '?'; |
163 |
dpavlin |
4 |
warn "#< ", as_hex($data), "\t$desc\n" if $debug; |
164 |
dpavlin |
1 |
return $data; |
165 |
|
|
} |
166 |
|
|
|
167 |
dpavlin |
2 |
my $assert; |
168 |
|
|
|
169 |
|
|
sub assert { |
170 |
|
|
my ( $from, $to ) = @_; |
171 |
|
|
|
172 |
dpavlin |
4 |
$to = length( $assert->{expect} ) if ! defined $to; |
173 |
|
|
|
174 |
dpavlin |
2 |
my $p = substr( $assert->{payload}, $from, $to ); |
175 |
|
|
my $e = substr( $assert->{expect}, $from, $to ); |
176 |
dpavlin |
3 |
warn "EXPECTED ",as_hex($e), " GOT ", as_hex($p), " [$from-$to] in ",dump( $assert ), "\n" if $e ne $p; |
177 |
dpavlin |
2 |
} |
178 |
|
|
|
179 |
dpavlin |
1 |
sub readchunk { |
180 |
dpavlin |
2 |
my ( $parser ) = @_; |
181 |
dpavlin |
1 |
|
182 |
dpavlin |
2 |
sleep 1; # FIXME remove |
183 |
|
|
|
184 |
dpavlin |
1 |
# read header of packet |
185 |
|
|
my $header = read_bytes( 2, 'header' ); |
186 |
dpavlin |
2 |
my $length = read_bytes( 1, 'length' ); |
187 |
|
|
my $len = ord($length); |
188 |
dpavlin |
1 |
my $data = read_bytes( $len, 'data' ); |
189 |
dpavlin |
2 |
my ( $cmd ) = unpack('C', $data ); |
190 |
dpavlin |
1 |
|
191 |
dpavlin |
2 |
my $payload = substr( $data, 0, -2 ); |
192 |
|
|
my $payload_len = length($data); |
193 |
|
|
warn "## payload too short $payload_len != $len\n" if $payload_len != $len; |
194 |
|
|
my $checksum = substr( $data, -2, 2 ); |
195 |
|
|
# FIXME check checksum |
196 |
dpavlin |
1 |
|
197 |
dpavlin |
2 |
print "<< ",as_hex( $header ), " [$len] ", as_hex( $payload ), "checksum: ", as_hex( $checksum ),"\n"; |
198 |
|
|
|
199 |
|
|
$assert->{len} = $len; |
200 |
|
|
$assert->{payload} = $payload; |
201 |
|
|
$assert->{checksum} = $checksum; |
202 |
|
|
|
203 |
|
|
$parser->( $len, $payload, $checksum ) if $parser && ref($parser) eq 'CODE'; |
204 |
|
|
|
205 |
|
|
return $data; |
206 |
dpavlin |
1 |
} |
207 |
|
|
|
208 |
dpavlin |
2 |
sub str2bytes { |
209 |
|
|
my $str = shift || confess "no str?"; |
210 |
|
|
$str =~ s/\s+(\S\S)(\S\S)+\s*/ $1 $2/; # fix checksum |
211 |
|
|
$str =~ s/\s+/\\x/g; |
212 |
|
|
$str = '"\x' . $str . '"'; |
213 |
|
|
my $bytes = eval $str; |
214 |
|
|
die $@ if $@; |
215 |
|
|
return $bytes; |
216 |
|
|
} |
217 |
|
|
|
218 |
|
|
sub cmd { |
219 |
|
|
my ( $cmd, $cmd_desc, $expect, $expect_desc, $coderef ) = @_; |
220 |
|
|
my $bytes = str2bytes( $cmd ); |
221 |
|
|
|
222 |
|
|
warn ">> ", as_hex( $bytes ), "\t## $cmd_desc\n"; |
223 |
|
|
$assert->{send} = $cmd; |
224 |
|
|
writechunk( $bytes ); |
225 |
|
|
|
226 |
|
|
if ( $expect ) { |
227 |
|
|
warn "?? $expect", $expect_desc ? "\t## $expect_desc" : '', "\n"; |
228 |
|
|
$assert->{expect} = substr(str2bytes($expect), 3, -2); # just expected payload |
229 |
|
|
readchunk( $coderef ); |
230 |
|
|
} |
231 |
|
|
} |
232 |
|
|
|