line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
|
2
|
|
|
|
|
|
|
package Hardware::iButton::Device; |
3
|
|
|
|
|
|
|
|
4
|
1
|
|
|
1
|
|
5
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
36
|
|
5
|
1
|
|
|
1
|
|
4
|
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
75
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
require Exporter; |
8
|
|
|
|
|
|
|
#require AutoLoader; |
9
|
1
|
|
|
1
|
|
6
|
use Time::HiRes qw(usleep); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
5
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
@ISA = qw(Exporter); |
12
|
|
|
|
|
|
|
# Items to export into callers namespace by default. Note: do not export |
13
|
|
|
|
|
|
|
# names by default without a very good reason. Use EXPORT_OK instead. |
14
|
|
|
|
|
|
|
# Do not simply export all your public functions/methods/constants. |
15
|
|
|
|
|
|
|
@EXPORT = qw( |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
); |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
( $VERSION ) = '$Revision: 1.2 $ ' =~ /\$Revision:\s+([^\s]+)/; |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=head1 NAME |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
Hardware::iButton::Device - object to represent iButtons |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=head1 SYNOPSIS |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
use Hardware::iButton::Connection; |
28
|
|
|
|
|
|
|
$c = new Hardware::iButton::Connection "/dev/ttyS0"; |
29
|
|
|
|
|
|
|
@b = $c->scan(); |
30
|
|
|
|
|
|
|
foreach $b (@b) { |
31
|
|
|
|
|
|
|
print "id: ", $b->id(), ", reg0: ",$b->readreg(0),"\n"; |
32
|
|
|
|
|
|
|
} |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
=head1 DESCRIPTION |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
This module talks to iButtons via the "active" serial interface (anything |
37
|
|
|
|
|
|
|
using the DS2480, including the DS1411k and the DS 9097U). It builds up a list |
38
|
|
|
|
|
|
|
of devices available, lets you read and write their registers, etc. |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
The connection object is an Hardware::iButton::Connection. The main |
41
|
|
|
|
|
|
|
user-visible purpose of it is to provide a list of Hardware::iButton::Device |
42
|
|
|
|
|
|
|
objects. These can be subclassed once their family codes are known to provide |
43
|
|
|
|
|
|
|
specialized methods unique to the capabilities of that device. Those devices |
44
|
|
|
|
|
|
|
will then be Hardware::iButton::Device::DS1920, etc. |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
=head1 AUTHOR |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
Brian Warner, warner@lothar.com |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
=head1 SEE ALSO |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
http://www.ibutton.com, http://sof.mit.edu/ibuttonpunks/ |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
=cut |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
# Preloaded methods go here. |
57
|
|
|
|
|
|
|
|
58
|
1
|
|
|
1
|
|
155
|
use vars qw(%models); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1554
|
|
59
|
|
|
|
|
|
|
%models = ( |
60
|
|
|
|
|
|
|
"01" => { |
61
|
|
|
|
|
|
|
'model' => 'DS1990A', |
62
|
|
|
|
|
|
|
'memsize' => 0, |
63
|
|
|
|
|
|
|
'memtype' => "none", |
64
|
|
|
|
|
|
|
'specialfuncs' => "", |
65
|
|
|
|
|
|
|
}, |
66
|
|
|
|
|
|
|
"02" => { |
67
|
|
|
|
|
|
|
'model' => 'DS1991', |
68
|
|
|
|
|
|
|
'memsize' => 512/8, |
69
|
|
|
|
|
|
|
'memtype' => "NVRAM", |
70
|
|
|
|
|
|
|
'specialfuncs' => "protected nvram 3*384bits", |
71
|
|
|
|
|
|
|
}, |
72
|
|
|
|
|
|
|
"08" => { |
73
|
|
|
|
|
|
|
'model' => 'DS1992', |
74
|
|
|
|
|
|
|
'memsize' => 1024/8, |
75
|
|
|
|
|
|
|
'memtype' => "NVRAM", |
76
|
|
|
|
|
|
|
'specialfuncs' => "", |
77
|
|
|
|
|
|
|
}, |
78
|
|
|
|
|
|
|
"06" => { |
79
|
|
|
|
|
|
|
'model' => 'DS1993', |
80
|
|
|
|
|
|
|
'memsize' => 4096/8, |
81
|
|
|
|
|
|
|
'memtype' => "NVRAM", |
82
|
|
|
|
|
|
|
'specialfuncs' => "", |
83
|
|
|
|
|
|
|
}, |
84
|
|
|
|
|
|
|
"05" => { |
85
|
|
|
|
|
|
|
'model' => 'DS2405', |
86
|
|
|
|
|
|
|
'memsize' => 0, |
87
|
|
|
|
|
|
|
'memtype' => "none", |
88
|
|
|
|
|
|
|
'specialfuncs' => "switch", |
89
|
|
|
|
|
|
|
'class' => 'Hardware::iButton::Device::switch', |
90
|
|
|
|
|
|
|
}, |
91
|
|
|
|
|
|
|
"04" => { |
92
|
|
|
|
|
|
|
'model' => 'DS1994', |
93
|
|
|
|
|
|
|
'memsize' => 4096/8, |
94
|
|
|
|
|
|
|
'memtype' => "NVRAM", |
95
|
|
|
|
|
|
|
'specialfuncs' => "clock/counter", |
96
|
|
|
|
|
|
|
}, |
97
|
|
|
|
|
|
|
"0a" => { |
98
|
|
|
|
|
|
|
'model' => 'DS1995', |
99
|
|
|
|
|
|
|
'memsize' => 16*1024/8, |
100
|
|
|
|
|
|
|
'memtype' => "NVRAM", |
101
|
|
|
|
|
|
|
'specialfuncs' => "", |
102
|
|
|
|
|
|
|
}, |
103
|
|
|
|
|
|
|
"0c" => { |
104
|
|
|
|
|
|
|
'model' => 'DS1996', |
105
|
|
|
|
|
|
|
'memsize' => 64*1024/8, |
106
|
|
|
|
|
|
|
'memtype' => "NVRAM", |
107
|
|
|
|
|
|
|
'specialfuncs' => "", |
108
|
|
|
|
|
|
|
}, |
109
|
|
|
|
|
|
|
"09" => { |
110
|
|
|
|
|
|
|
'model' => 'DS1982', |
111
|
|
|
|
|
|
|
'memsize' => 1024/8, |
112
|
|
|
|
|
|
|
'memtype' => "EPROM", |
113
|
|
|
|
|
|
|
'specialfuncs' => "", |
114
|
|
|
|
|
|
|
}, |
115
|
|
|
|
|
|
|
"0b" => { |
116
|
|
|
|
|
|
|
'model' => 'DS1985', |
117
|
|
|
|
|
|
|
'memsize' => 16*1024/8, |
118
|
|
|
|
|
|
|
'memtype' => "EPROM", |
119
|
|
|
|
|
|
|
'specialfuncs' => "", |
120
|
|
|
|
|
|
|
}, |
121
|
|
|
|
|
|
|
"0f" => { |
122
|
|
|
|
|
|
|
'model' => 'DS1986', |
123
|
|
|
|
|
|
|
'memsize' => 64*1024/8, |
124
|
|
|
|
|
|
|
'memtype' => "EPROM", |
125
|
|
|
|
|
|
|
'specialfuncs' => "", |
126
|
|
|
|
|
|
|
}, |
127
|
|
|
|
|
|
|
"10" => { |
128
|
|
|
|
|
|
|
'model' => 'DS1920', |
129
|
|
|
|
|
|
|
'memsize' => 16/8, # yes, really. two bytes. |
130
|
|
|
|
|
|
|
'memtype' => "EEPROM", |
131
|
|
|
|
|
|
|
'specialfuncs' => "thermometer", |
132
|
|
|
|
|
|
|
'class' => 'Hardware::iButton::Device::DS1920', |
133
|
|
|
|
|
|
|
}, |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
"14" => { |
136
|
|
|
|
|
|
|
'model' => 'DS1971', |
137
|
|
|
|
|
|
|
'memsize' => 256/8, |
138
|
|
|
|
|
|
|
'memtype' => "EPROM", |
139
|
|
|
|
|
|
|
'specialfuncs' => "??", |
140
|
|
|
|
|
|
|
}, |
141
|
|
|
|
|
|
|
"16" => { |
142
|
|
|
|
|
|
|
'model' => 'javabutton', |
143
|
|
|
|
|
|
|
'memsize' => 0, |
144
|
|
|
|
|
|
|
'memtype' => "??", |
145
|
|
|
|
|
|
|
'specialfuncs' => "Java processor", |
146
|
|
|
|
|
|
|
'class' => 'Hardware::iButton::Device::JavaButton', |
147
|
|
|
|
|
|
|
}, |
148
|
|
|
|
|
|
|
); |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# new is the constructor, called by Hardware::iButton::Connection::scan() to |
152
|
|
|
|
|
|
|
# create the new Hardware::iButton::Device instance to return to the user |
153
|
|
|
|
|
|
|
sub new { |
154
|
0
|
|
|
0
|
0
|
|
my($class, $connection, $raw_id) = @_; |
155
|
0
|
|
|
|
|
|
my $self = bless {}, $class; |
156
|
|
|
|
|
|
|
# we'll rebless ourselves into a device-specific class once we set up some |
157
|
|
|
|
|
|
|
# basic stuff |
158
|
0
|
|
|
|
|
|
$self->{'connection'} = $connection; |
159
|
0
|
|
|
|
|
|
$self->{'raw_id'} = $raw_id; |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# things the user can query about, all derived from the raw_id |
162
|
0
|
|
|
|
|
|
$self->{'family'} = unpack("H2",pack("b8",substr($raw_id,0,8))); |
163
|
0
|
|
|
|
|
|
$self->{'serial'} = unpack("H12", |
164
|
|
|
|
|
|
|
pack("B48", |
165
|
|
|
|
|
|
|
scalar(reverse(substr($raw_id,8,48))))); |
166
|
0
|
|
|
|
|
|
$self->{'crc'} = unpack("H2",pack("b8",substr($raw_id,56,8))); |
167
|
0
|
|
|
|
|
|
$self->{'id'} = join("", |
168
|
|
|
|
|
|
|
$self->{'family'},$self->{'serial'},$self->{'crc'}); |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# check CRC |
171
|
0
|
|
|
|
|
|
my $crc = Hardware::iButton::Connection::crc(0, split(//, pack("b*", |
172
|
|
|
|
|
|
|
$raw_id))); |
173
|
0
|
0
|
|
|
|
|
if ($crc != 0) { |
174
|
0
|
|
|
|
|
|
warn("crc didn't match"); |
175
|
|
|
|
|
|
|
} |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
# model-specific stuff |
178
|
0
|
0
|
|
|
|
|
if (defined($models{$self->{'family'}})) { |
179
|
0
|
|
|
|
|
|
my $m = $models{$self->{'family'}}; |
180
|
0
|
|
|
|
|
|
foreach (keys(%$m)) { |
181
|
|
|
|
|
|
|
#print " $_ -> ",$m->{$_},"\n"; |
182
|
0
|
|
|
|
|
|
$self->{$_} = $m->{$_}; |
183
|
|
|
|
|
|
|
} |
184
|
0
|
0
|
|
|
|
|
if ($m->{'class'}) { |
185
|
0
|
|
|
|
|
|
bless $self, $m->{'class'}; |
186
|
|
|
|
|
|
|
} |
187
|
|
|
|
|
|
|
} else { |
188
|
0
|
|
|
|
|
|
warn "unknown model, family code $self->{'family'}"; |
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
|
191
|
0
|
|
|
|
|
|
return $self; |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=head2 accessors |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
$family = $b->family(); # "01" for DS1990A/DS2401 "id only" buttons |
197
|
|
|
|
|
|
|
$serial = $b->serial(); # "000001F1F1F3", as stamped on button |
198
|
|
|
|
|
|
|
$crc = $b->crc(); # "E5" error check byte |
199
|
|
|
|
|
|
|
$id = $b->id(); # the previous three joined together: "01000001F1F1F3E5" |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
=cut |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
sub family { |
204
|
0
|
|
|
0
|
0
|
|
return $_[0]->{'family'}; |
205
|
|
|
|
|
|
|
} |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
sub serial { |
208
|
0
|
|
|
0
|
0
|
|
return $_[0]->{'serial'}; |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
sub crc { |
212
|
0
|
|
|
0
|
0
|
|
return $_[0]->{'crc'}; |
213
|
|
|
|
|
|
|
} |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
sub id { |
216
|
0
|
|
|
0
|
0
|
|
return $_[0]->{'id'}; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
=head2 select |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
$b->select(); |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
Activate this button (in Dallas terms, "move it to the Transport Layer"). All |
225
|
|
|
|
|
|
|
other buttons will be idled and will not respond to commands until the bus is |
226
|
|
|
|
|
|
|
reset with C<$c->reset()>. Returns 1 for success, undef if the button is no |
227
|
|
|
|
|
|
|
longer on the bus. |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
=cut |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
sub select { |
232
|
0
|
|
|
0
|
1
|
|
my($self) = @_; |
233
|
0
|
|
|
|
|
|
return $self->{'connection'}->select($self->{'raw_id'}); |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
sub verify { |
237
|
0
|
|
|
0
|
0
|
|
my($self) = @_; |
238
|
0
|
|
|
|
|
|
return $self->{'connection'}->verify($self->{'raw_id'}); |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
sub reset { |
242
|
0
|
|
|
0
|
0
|
|
return $_[0]->{'connection'}->reset(); |
243
|
|
|
|
|
|
|
} |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
=head2 is_present |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
$button->is_present(); |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
Checks to see if the given button is still present, using the Search ROM |
251
|
|
|
|
|
|
|
command. Returns 1 if it is, 0 if not. |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
=cut |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
sub is_present { |
256
|
0
|
|
|
0
|
1
|
|
my($self) = @_; |
257
|
0
|
0
|
|
|
|
|
return 1 |
258
|
|
|
|
|
|
|
# XXX pointless code if return 1 above ? |
259
|
|
|
|
|
|
|
if $self->{'connection'}->scan($self->{'family'}, $self->{'serial'}); |
260
|
0
|
|
|
|
|
|
return 0; |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
=head2 Button Introspection |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
or, how not to get lost in your own navel |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
$model = $b->model(); # "DS1992" |
268
|
|
|
|
|
|
|
$bytes = $b->memsize(); # 128 bytes |
269
|
|
|
|
|
|
|
$type = $b->memtype(); # "NVRAM" |
270
|
|
|
|
|
|
|
$special = $b->specialfuncs(); # "thermometer", "clock", "java", "crypto" |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
=cut |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
sub model { |
275
|
0
|
|
|
0
|
0
|
|
return $_[0]->{'model'}; |
276
|
|
|
|
|
|
|
} |
277
|
|
|
|
|
|
|
sub memsize { |
278
|
0
|
|
|
0
|
0
|
|
return $_[0]->{'memsize'}; |
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
sub memtype { |
281
|
0
|
|
|
0
|
0
|
|
return $_[0]->{'memtype'}; |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
sub specialfuncs { |
284
|
0
|
|
|
0
|
0
|
|
return $_[0]->{'specialfuncs'}; |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
# common actions that all buttons can do |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
=head2 read_memory |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
$data = $b->read_memory($start, $length); |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
Reads memory from the iButton. Acts like C<$data = substr(memory, $start, |
295
|
|
|
|
|
|
|
$length)>. If you read beyond the end of the device, you will get all ones |
296
|
|
|
|
|
|
|
in the unimplemented addresses. |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
=cut |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
sub read_memory { |
301
|
0
|
|
|
0
|
1
|
|
my($self, $addr, $length) = @_; |
302
|
0
|
|
|
|
|
|
my $c = $self->{'connection'}; |
303
|
0
|
|
|
|
|
|
$self->select(); |
304
|
0
|
|
|
|
|
|
my $str = &Hardware::iButton::Connection::READ_MEMORY . pack("v",$addr) |
305
|
|
|
|
|
|
|
. "\xff" x $length; |
306
|
0
|
|
|
|
|
|
$c->send($str); |
307
|
0
|
|
|
|
|
|
$c->read(1+2); |
308
|
0
|
|
|
|
|
|
my $buf; |
309
|
0
|
|
|
|
|
|
$buf = $c->read($length); |
310
|
0
|
|
|
|
|
|
$self->reset(); |
311
|
0
|
|
|
|
|
|
return $buf; |
312
|
|
|
|
|
|
|
} |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
=head2 write_memory |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
$b->write_memory($start, $data); |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
Writes memory to the iButton NVRAM. Acts like C
|
319
|
|
|
|
|
|
|
length($data)) = $data;>. Writes in chunks to separate 32-byte pages, each |
320
|
|
|
|
|
|
|
chunk going to the scratchpad first, verified there, then copied into |
321
|
|
|
|
|
|
|
NVRAM. Returns the number of bytes successfully written. |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
=cut |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
sub write_memory_page { |
326
|
0
|
|
|
0
|
0
|
|
my($self, $pageaddr, $chunk) = @_; |
327
|
|
|
|
|
|
|
# the data does not span a page, |
328
|
|
|
|
|
|
|
# i.e. ($pageaddr % 32) == (($pageaddr+length($chunk)) % 32) |
329
|
|
|
|
|
|
|
# length($chunk) <= 32 |
330
|
|
|
|
|
|
|
|
331
|
0
|
|
|
|
|
|
my $c = $self->{'connection'}; |
332
|
|
|
|
|
|
|
|
333
|
0
|
|
|
|
|
|
$c->reset(); |
334
|
0
|
|
|
|
|
|
$self->select(); |
335
|
0
|
|
|
|
|
|
my $str = &Hardware::iButton::Connection::WRITE_SCRATCHPAD . pack("v",$pageaddr); |
336
|
0
|
|
|
|
|
|
$str .= $chunk; |
337
|
0
|
|
|
|
|
|
$c->send($str); |
338
|
0
|
|
|
|
|
|
$c->read(length($str)); |
339
|
0
|
|
|
|
|
|
$c->reset(); |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
# verify the scratchpad |
342
|
0
|
|
|
|
|
|
$self->select(); |
343
|
0
|
|
|
|
|
|
$str = &Hardware::iButton::Connection::READ_SCRATCHPAD . "\xff" x 3; |
344
|
0
|
|
|
|
|
|
$c->send($str); $c->read(1); |
|
0
|
|
|
|
|
|
|
345
|
0
|
|
|
|
|
|
my $buf; |
346
|
0
|
|
|
|
|
|
$buf = $c->read(3); |
347
|
|
|
|
|
|
|
# check it! |
348
|
|
|
|
|
|
|
# ("right foot red.. yellow foot blue.. left right yellow blue green!") |
349
|
|
|
|
|
|
|
# the first two bytes are the address we wrote. The third is a status byte. |
350
|
0
|
|
|
|
|
|
my $readback_addr = unpack("v", substr($buf, 0, 2)); |
351
|
0
|
0
|
|
|
|
|
if ($readback_addr != $pageaddr) { |
352
|
|
|
|
|
|
|
# address got garbled in transit |
353
|
0
|
|
|
|
|
|
print "address not correct: $readback_addr instead of $pageaddr\n"; |
354
|
0
|
|
|
|
|
|
$c->reset(); |
355
|
0
|
|
|
|
|
|
return 0; # try again |
356
|
|
|
|
|
|
|
} |
357
|
0
|
|
|
|
|
|
my $status = unpack("C", substr($buf, 2, 1)); |
358
|
|
|
|
|
|
|
# $status byte is (AA OF PF E4 E3 E2 E1 E0) |
359
|
|
|
|
|
|
|
# AA: authorization accepted: set once COPY_SCRATCHPAD happens |
360
|
|
|
|
|
|
|
# OF: overflow flag, if data ran beyond a page |
361
|
|
|
|
|
|
|
# PF: partial flag, if we didn't send a full byte |
362
|
|
|
|
|
|
|
# E: end address, should be ($pageaddr+$length-1)%32 |
363
|
0
|
0
|
|
|
|
|
if ($status & 0x80) { |
364
|
|
|
|
|
|
|
# AA flag still set, so the WRITE_SCRATCHPAD hasn't happened since |
365
|
|
|
|
|
|
|
# the last COPY_SCRATCHPAD |
366
|
0
|
|
|
|
|
|
print "AA flag set\n"; |
367
|
0
|
|
|
|
|
|
$c->reset(); |
368
|
0
|
|
|
|
|
|
return 0; |
369
|
|
|
|
|
|
|
} |
370
|
0
|
0
|
|
|
|
|
if ($status & 0x40) { |
371
|
|
|
|
|
|
|
# OF flag set, maybe we sent too many bytes, or the pageaddr got |
372
|
|
|
|
|
|
|
# garbled to make it look closer to the end of the page |
373
|
0
|
|
|
|
|
|
print "OF flag set\n"; |
374
|
0
|
|
|
|
|
|
$c->reset(); |
375
|
0
|
|
|
|
|
|
return 0; |
376
|
|
|
|
|
|
|
} |
377
|
0
|
0
|
|
|
|
|
if ($status & 0x20) { |
378
|
|
|
|
|
|
|
# PF set, some bits got dropped |
379
|
0
|
|
|
|
|
|
print "PF flag set\n"; |
380
|
0
|
|
|
|
|
|
$c->reset(); |
381
|
0
|
|
|
|
|
|
return 0; |
382
|
|
|
|
|
|
|
} |
383
|
0
|
0
|
|
|
|
|
if (($status & 0x1f) != ($pageaddr+length($chunk)-1)%32) { |
384
|
|
|
|
|
|
|
# addr isn't right |
385
|
0
|
|
|
|
|
|
print "addr is ",($status & 0x1f),", should be ", |
386
|
|
|
|
|
|
|
($pageaddr+length($chunk)-1)%32,"\n"; |
387
|
0
|
|
|
|
|
|
$c->reset(); |
388
|
0
|
|
|
|
|
|
return 0; |
389
|
|
|
|
|
|
|
} |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
# read data out and check it |
392
|
0
|
|
|
|
|
|
$c->send("\xff" x length($chunk)); |
393
|
0
|
|
|
|
|
|
$buf = $c->read(length($chunk)); |
394
|
0
|
0
|
|
|
|
|
if ($buf ne $chunk) { |
395
|
|
|
|
|
|
|
# data got corrupted |
396
|
0
|
|
|
|
|
|
print "data readback was wrong\n"; |
397
|
0
|
|
|
|
|
|
$c->reset(); |
398
|
0
|
|
|
|
|
|
return 0; |
399
|
|
|
|
|
|
|
} |
400
|
0
|
|
|
|
|
|
$c->reset(); |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
# looks good |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
# copy from scratchpad to NVRAM |
405
|
0
|
|
|
|
|
|
$self->select(); |
406
|
0
|
|
|
|
|
|
$str = &Hardware::iButton::Connection::COPY_SCRATCHPAD |
407
|
|
|
|
|
|
|
. pack("v",$pageaddr) . pack("C", $status); |
408
|
0
|
|
|
|
|
|
$c->send($str); |
409
|
0
|
|
|
|
|
|
$c->read(1+2+1); |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
# wait for it to program.. data book says 30us typ. |
412
|
|
|
|
|
|
|
# the device will respond with 1's if it's still programming |
413
|
0
|
|
|
|
|
|
usleep(50); |
414
|
0
|
|
|
|
|
|
while(1) { |
415
|
0
|
|
|
|
|
|
$c->send("\xff"); |
416
|
0
|
|
|
|
|
|
$buf = $c->read(1); |
417
|
0
|
0
|
|
|
|
|
last if $buf eq "\x00"; |
418
|
0
|
|
|
|
|
|
usleep(50*1000); # 50ms |
419
|
|
|
|
|
|
|
} |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
# read back and verify |
422
|
|
|
|
|
|
|
|
423
|
0
|
|
|
|
|
|
$c->reset(); |
424
|
|
|
|
|
|
|
} |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
sub write_memory_page_loop { |
427
|
0
|
|
|
0
|
0
|
|
my($self, $pageaddr, $chunk) = @_; |
428
|
|
|
|
|
|
|
# try a couple of times to write |
429
|
0
|
|
|
|
|
|
my $times = 3; |
430
|
0
|
|
|
|
|
|
my $nwritten; |
431
|
0
|
|
|
|
|
|
while ($times) { |
432
|
0
|
|
|
|
|
|
print "write(times=$times,pageaddr=$pageaddr,length=",length($chunk),")\n"; |
433
|
0
|
|
|
|
|
|
$nwritten = $self->write_memory_page($pageaddr, $chunk); |
434
|
0
|
0
|
|
|
|
|
last if $nwritten == length($chunk); |
435
|
0
|
|
|
|
|
|
$times--; |
436
|
|
|
|
|
|
|
} |
437
|
0
|
|
|
|
|
|
return $nwritten; |
438
|
|
|
|
|
|
|
} |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
sub write_memory { |
441
|
0
|
|
|
0
|
1
|
|
my($self, $addr, $data) = @_; |
442
|
0
|
|
|
|
|
|
my $nwritten = 0; |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
# find the first chunk boundaries: the scratchpad is like a direct-mapped |
445
|
|
|
|
|
|
|
# cache, so we can only copy to a single "page" (32-bytes) at a time. |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
# do we need to write a partial chunk first |
448
|
0
|
0
|
|
|
|
|
if ($addr % 32) { |
449
|
|
|
|
|
|
|
# yup |
450
|
0
|
|
|
|
|
|
my $chunklen = 32 - ($addr % 32); |
451
|
0
|
|
|
|
|
|
print "chunklen is $chunklen\n"; |
452
|
0
|
|
|
|
|
|
$nwritten += |
453
|
|
|
|
|
|
|
$self->write_memory_page_loop($addr, substr($data, 0, $chunklen)); |
454
|
0
|
|
|
|
|
|
$addr += $chunklen; |
455
|
0
|
|
|
|
|
|
substr($data, 0, $chunklen) = ''; |
456
|
|
|
|
|
|
|
} |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
# write chunks |
459
|
0
|
|
|
|
|
|
while(length($data)) { |
460
|
0
|
0
|
|
|
|
|
my $chunklen = (length($data) > 32) ? 32 : length($data); # max 32 |
461
|
0
|
|
|
|
|
|
print "chunklen is $chunklen\n"; |
462
|
0
|
|
|
|
|
|
$nwritten += |
463
|
|
|
|
|
|
|
$self->write_memory_page_loop($addr, substr($data, 0, $chunklen)); |
464
|
0
|
|
|
|
|
|
$addr += $chunklen; |
465
|
0
|
|
|
|
|
|
substr($data, 0, $chunklen) = ''; |
466
|
|
|
|
|
|
|
} |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
# done! |
469
|
0
|
|
|
|
|
|
return $nwritten; |
470
|
|
|
|
|
|
|
} |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
package Hardware::iButton::Device::eeprom; |
473
|
|
|
|
|
|
|
# this is a class that implements read-eeprom and write-eeprom commands. |
474
|
|
|
|
|
|
|
# other device classes can inherit from this one |
475
|
1
|
|
|
1
|
|
6
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
32
|
|
476
|
1
|
|
|
1
|
|
4
|
use vars qw(@ISA); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
74
|
|
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
# read one byte |
479
|
|
|
|
|
|
|
sub read_eeprom { |
480
|
0
|
|
|
0
|
|
|
my($self, $addr) = @_; |
481
|
|
|
|
|
|
|
} |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
# write one byte |
484
|
|
|
|
|
|
|
sub write_eeprom { |
485
|
0
|
|
|
0
|
|
|
my($self, $addr, $data) = @_; |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
package Hardware::iButton::Device::switch; |
490
|
1
|
|
|
1
|
|
4
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
34
|
|
491
|
1
|
|
|
1
|
|
5
|
use vars qw(@ISA); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
101
|
|
492
|
|
|
|
|
|
|
@ISA = qw(Hardware::iButton::Device); |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
#sub on { |
495
|
|
|
|
|
|
|
# my ($this) = @_; |
496
|
|
|
|
|
|
|
# print $this->verify() . "\n"; |
497
|
|
|
|
|
|
|
# $this->select(); |
498
|
|
|
|
|
|
|
#} |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
#sub off { |
501
|
|
|
|
|
|
|
# my ($this) = @_; |
502
|
|
|
|
|
|
|
# print $this->verify() . "\n"; |
503
|
|
|
|
|
|
|
# $this->select(); |
504
|
|
|
|
|
|
|
#} |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
#sub is_on { |
507
|
|
|
|
|
|
|
#} |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
sub toggle { |
510
|
0
|
|
|
0
|
|
|
my ($this) = @_; |
511
|
0
|
|
|
|
|
|
$this->select(); |
512
|
|
|
|
|
|
|
} |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
package Hardware::iButton::Device::DS1920; |
516
|
|
|
|
|
|
|
|
517
|
1
|
|
|
1
|
|
5
|
use Hardware::iButton::Connection; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
25
|
|
518
|
1
|
|
|
1
|
|
4
|
use Time::HiRes qw(usleep); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
4
|
|
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
# this is the thermometer button. |
521
|
1
|
|
|
1
|
|
75
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
31
|
|
522
|
1
|
|
|
1
|
|
3
|
use vars qw(@ISA); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
529
|
|
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
@ISA = qw(Hardware::iButton::Device); |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
sub read_temperature_scratchpad { |
527
|
0
|
|
|
0
|
|
|
my($self) = @_; |
528
|
0
|
|
|
|
|
|
my $c = $self->{'connection'}; |
529
|
|
|
|
|
|
|
|
530
|
0
|
|
|
|
|
|
$c->reset(); |
531
|
0
|
|
|
|
|
|
$c->mode(&Hardware::iButton::Connection::SET_COMMAND_MODE); |
532
|
0
|
|
|
|
|
|
$c->write("\x39"); # set a 524ms pullup |
533
|
0
|
|
|
|
|
|
$c->read(1); # response to config command |
534
|
0
|
|
|
|
|
|
$c->reset(); |
535
|
0
|
|
|
|
|
|
$self->select(); |
536
|
0
|
|
|
|
|
|
$c->mode(&Hardware::iButton::Connection::SET_COMMAND_MODE); |
537
|
0
|
|
|
|
|
|
$c->write("\xef"); # arm the pullup |
538
|
0
|
|
|
|
|
|
$c->write("\xf1"); # terminate pulse (??) |
539
|
0
|
|
|
|
|
|
$c->read(1); # response to 0xf1 |
540
|
0
|
|
|
|
|
|
$c->mode(&Hardware::iButton::Connection::SET_DATA_MODE); |
541
|
0
|
|
|
|
|
|
$c->send("\x44"); # start conversion. need to do a 0.5s strong pullup. |
542
|
0
|
|
|
|
|
|
$c->read(1); # read back 0x44 |
543
|
|
|
|
|
|
|
# wait |
544
|
0
|
|
|
|
|
|
usleep(600*1000); # wait .6s |
545
|
0
|
|
|
|
|
|
$c->mode(&Hardware::iButton::Connection::SET_COMMAND_MODE); |
546
|
0
|
|
|
|
|
|
$c->write("\xed"); # disarm pullup |
547
|
0
|
|
|
|
|
|
$c->write("\xf1"); # terminate pulse |
548
|
0
|
|
|
|
|
|
$c->read(1); # response?? |
549
|
|
|
|
|
|
|
|
550
|
0
|
|
|
|
|
|
$c->reset(); |
551
|
0
|
|
|
|
|
|
$self->select(); |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
# read scratchpad, bytes 0 and 1 (LSB and MSB) |
554
|
0
|
|
|
|
|
|
$c->send("\xbe"); $c->read(1); |
|
0
|
|
|
|
|
|
|
555
|
0
|
|
|
|
|
|
$c->send("\xff" x 9); |
556
|
0
|
|
|
|
|
|
my $scratchpad = $c->read(9); |
557
|
0
|
|
|
|
|
|
$c->reset(); |
558
|
|
|
|
|
|
|
# check CRC in last byte. |
559
|
0
|
0
|
|
|
|
|
if (Hardware::iButton::Connection::crc(0, split(//,$scratchpad))) { |
560
|
0
|
|
|
|
|
|
warn("scratchpadcrc was wrong"); |
561
|
|
|
|
|
|
|
} |
562
|
0
|
|
|
|
|
|
return $scratchpad; |
563
|
|
|
|
|
|
|
} |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
=head2 read_temperature |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
$temp = $b->read_temperature(); |
568
|
|
|
|
|
|
|
$temp = $b->read_temperature_hires(); |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
These methods can be used on DS1820/DS1920 Thermometer iButtons. They return |
571
|
|
|
|
|
|
|
a temperature in degrees C. The range is -55C to +100C, the resolution of the |
572
|
|
|
|
|
|
|
first is 0.5C, the resolution of the second is about 0.01C. The accuracy is |
573
|
|
|
|
|
|
|
about +/- 0.5C. |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
Useful conversions: C<$f = $c*9/5 + 32>, C<$c = ($f-32)*5/9> . |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
=cut |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
sub read_temperature { |
581
|
0
|
|
|
0
|
|
|
my($self) = @_; |
582
|
0
|
|
|
|
|
|
my $scratchpad = $self->read_temperature_scratchpad($self); |
583
|
0
|
|
|
|
|
|
my $tempnumber = unpack("v",substr($scratchpad, 0, 2)); |
584
|
|
|
|
|
|
|
# now, that's really supposed to be a signed 16-bit little-endian |
585
|
|
|
|
|
|
|
# quantity, but there isn't a pack() code for such things. |
586
|
|
|
|
|
|
|
#printf("tempnumber as read is 0x%04x\n",$tempnumber); |
587
|
0
|
0
|
|
|
|
|
$tempnumber -= 0x10000 if $tempnumber > 0x8000; |
588
|
0
|
|
|
|
|
|
my $temp = $tempnumber / 2; |
589
|
0
|
|
|
|
|
|
return $temp; |
590
|
|
|
|
|
|
|
} |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
sub read_temperature_hires { |
593
|
0
|
|
|
0
|
|
|
my($self) = @_; |
594
|
0
|
|
|
|
|
|
my $scratchpad = $self->read_temperature_scratchpad($self); |
595
|
0
|
|
|
|
|
|
my $tempnumber = unpack("v",substr($scratchpad, 0, 2)); |
596
|
|
|
|
|
|
|
# now, that's really supposed to be a signed 16-bit little-endian |
597
|
|
|
|
|
|
|
# quantity, but there isn't a pack() code for such things. |
598
|
0
|
|
|
|
|
|
my $count_per_c = ord(substr($scratchpad, 7, 1)); |
599
|
0
|
|
|
|
|
|
my $count_remaining = ord(substr($scratchpad, 6, 1)); |
600
|
|
|
|
|
|
|
#printf("tempnumber as read is 0x%04x\n",$tempnumber); |
601
|
0
|
|
|
|
|
|
$tempnumber &= 0xfffe; # truncate LSB |
602
|
0
|
0
|
|
|
|
|
$tempnumber -= 0x10000 if $tempnumber > 0x8000; |
603
|
0
|
|
|
|
|
|
my $temp = ($tempnumber / 2) - 0.25 + |
604
|
|
|
|
|
|
|
($count_per_c - $count_remaining) / $count_per_c; |
605
|
0
|
|
|
|
|
|
return $temp; |
606
|
|
|
|
|
|
|
} |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
package Hardware::iButton::Device::JavaButton; |
609
|
1
|
|
|
1
|
|
6
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
29
|
|
610
|
1
|
|
|
1
|
|
4
|
use vars qw(@ISA); |
|
1
|
|
|
|
|
6
|
|
|
1
|
|
|
|
|
28
|
|
611
|
|
|
|
|
|
|
|
612
|
1
|
|
|
1
|
|
4
|
use Hardware::iButton::Connection; |
|
1
|
|
|
|
|
5
|
|
|
1
|
|
|
|
|
20
|
|
613
|
1
|
|
|
1
|
|
4
|
use Time::HiRes qw(usleep); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
3
|
|
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
# this is the Java button. |
616
|
|
|
|
|
|
|
@ISA = qw(Hardware::iButton::Device); |
617
|
|
|
|
|
|
|
|
618
|
0
|
|
|
0
|
|
|
sub send_apdu { |
619
|
|
|
|
|
|
|
} |
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
# an APDU is just a specially formatted buffer. a Command APDU is sent to the |
622
|
|
|
|
|
|
|
# button, which responds with a Response APDU. |
623
|
|
|
|
|
|
|
# Command APDU: |
624
|
|
|
|
|
|
|
# byte header[4]; // CLA, INS, P1, P2 |
625
|
|
|
|
|
|
|
# byte Lc; |
626
|
|
|
|
|
|
|
# byte *Data; |
627
|
|
|
|
|
|
|
# byte Le; |
628
|
|
|
|
|
|
|
# Response APDU |
629
|
|
|
|
|
|
|
# word Len; |
630
|
|
|
|
|
|
|
# byte *Data; |
631
|
|
|
|
|
|
|
# word SW; // status word |
632
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
# wrappers for those APDUs |
634
|
|
|
|
|
|
|
# Command Packet |
635
|
|
|
|
|
|
|
# byte len; |
636
|
|
|
|
|
|
|
# byte cmdbyte; |
637
|
|
|
|
|
|
|
# byte groupid; |
638
|
|
|
|
|
|
|
# byte cmddata[max=255] |
639
|
|
|
|
|
|
|
# Return Packet |
640
|
|
|
|
|
|
|
# byte CSB |
641
|
|
|
|
|
|
|
# byte groupid |
642
|
|
|
|
|
|
|
# byte datalen |
643
|
|
|
|
|
|
|
# byte cmddata[max=2048] |
644
|
|
|
|
|
|
|
|
645
|
0
|
|
|
0
|
|
|
sub get_firmware_version_string { |
646
|
|
|
|
|
|
|
# apdu: class 0xd0, instruction 0x95, parm1 0x01, parm2 0x00 |
647
|
|
|
|
|
|
|
# header = "\xd0\x95\x01\x00" |
648
|
|
|
|
|
|
|
# Lc = "\x00", data is uninitialized |
649
|
|
|
|
|
|
|
# Le = "\x00" |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
# cmdbyte = 137, groupid = 0, len = 3+4 (3+apdu header) + 1 + lc + 1 |
652
|
|
|
|
|
|
|
# data = header . lc . [lc bytes of data] . le |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
# SendAPDU() |
655
|
|
|
|
|
|
|
# so arg to sendcibmessage is: |
656
|
|
|
|
|
|
|
# len . cmdbyte(137) . groupid(0) . |
657
|
|
|
|
|
|
|
# [header(4bytes) . lc . data(lc bytes) . le] |
658
|
|
|
|
|
|
|
# sendcibmessage(data, len+1) |
659
|
|
|
|
|
|
|
# recvcibmessage |
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
#an apdu has a 4-byte header: class, instruction, parm1, parm2. then lots |
662
|
|
|
|
|
|
|
#of random data. class >= 0xd0 is for the ring itself, otherwise it is passed |
663
|
|
|
|
|
|
|
#to the applet (which one?) |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
} |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
# Autoload methods go after =cut, and are processed by the autosplit program. |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
1; |
670
|
|
|
|
|
|
|
__END__ |