File Coverage

blib/lib/Hardware/iButton/Device.pm
Criterion Covered Total %
statement 48 223 21.5
branch 0 34 0.0
condition n/a
subroutine 16 41 39.0
pod 4 17 23.5
total 68 315 21.5


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__