| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Device::VantagePro; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 1 |  |  | 1 |  | 48077 | use 5.008008; | 
|  | 1 |  |  |  |  | 5 |  | 
|  | 1 |  |  |  |  | 42 |  | 
| 4 | 1 |  |  | 1 |  | 7 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 37 |  | 
| 5 | 1 |  |  | 1 |  | 6 | use warnings; | 
|  | 1 |  |  |  |  | 7 |  | 
|  | 1 |  |  |  |  | 98 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | require Exporter; | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | our @ISA = qw(Exporter); | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | our $VERSION = '0.25'; | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | #-#use Win32::SerialPort qw(:STAT 0.19 ); | 
| 14 | 1 |  |  | 1 |  | 1392 | use Device::SerialPort qw(:STAT 0.19 ); | 
|  | 1 |  |  |  |  | 48357 |  | 
|  | 1 |  |  |  |  | 285 |  | 
| 15 |  |  |  |  |  |  |  | 
| 16 | 1 |  |  | 1 |  | 1076 | use Time::HiRes qw(usleep gettimeofday time); | 
|  | 1 |  |  |  |  | 1823 |  | 
|  | 1 |  |  |  |  | 5 |  | 
| 17 | 1 |  |  | 1 |  | 1382 | use Data::Dumper; | 
|  | 1 |  |  |  |  | 28938 |  | 
|  | 1 |  |  |  |  | 79 |  | 
| 18 |  |  |  |  |  |  |  | 
| 19 | 1 |  |  | 1 |  | 9 | use POSIX qw(:errno_h :fcntl_h strftime); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 10 |  | 
| 20 |  |  |  |  |  |  |  | 
| 21 | 1 |  |  | 1 |  | 1743 | use Time::Local; | 
|  | 1 |  |  |  |  | 1930 |  | 
|  | 1 |  |  |  |  | 4061 |  | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | our %EXPORT_TAGS = ( 'all' => [ qw( ) ] ); | 
| 24 |  |  |  |  |  |  | our @EXPORT_OK = qw(); | 
| 25 |  |  |  |  |  |  | our @EXPORT = qw(); | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | our $Verbose = 0; | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - | 
| 30 |  |  |  |  |  |  | sub new | 
| 31 |  |  |  |  |  |  | { | 
| 32 | 0 |  |  | 0 | 1 |  | my $caller = shift @_; | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | # In case someone wants to sub-class | 
| 35 | 0 |  |  |  |  |  | my $caller_is_obj  = ref($caller); | 
| 36 | 0 |  | 0 |  |  |  | my $class = $caller_is_obj || $caller; | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | # Passing reference or hash | 
| 39 | 0 |  |  |  |  |  | my %arg_hsh; | 
| 40 | 0 | 0 |  |  |  |  | if ( ref($_[0]) eq "HASH" ) { %arg_hsh = %{ shift @_ } } | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 41 | 0 |  |  |  |  |  | else                        { %arg_hsh = @_ } | 
| 42 |  |  |  |  |  |  |  | 
| 43 | 0 |  | 0 |  |  |  | my $port = $arg_hsh{'port'} || "/dev/ttyS0"; | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | #my $conf = $arg_hsh{'conf'} || 'Conf.ini'; | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | #my $port_obj = new Win32::SerialPort ($port) || die "Can't open $port: $^E\n"; | 
| 48 | 0 |  | 0 |  |  |  | my $port_obj = new Device::SerialPort ($port) || die "Can't open $port: $^E\n"; | 
| 49 |  |  |  |  |  |  |  | 
| 50 | 0 |  | 0 |  |  |  | my $baudrate = $arg_hsh{baudrate} || 19200; | 
| 51 | 0 |  | 0 |  |  |  | my $parity   = $arg_hsh{parity}   || "none"; | 
| 52 | 0 |  | 0 |  |  |  | my $databits = $arg_hsh{databits} || 8; | 
| 53 | 0 |  | 0 |  |  |  | my $stopbits = $arg_hsh{stopbits} || 1; | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | # After new, must check for failure | 
| 56 | 0 |  |  |  |  |  | $port_obj->baudrate($baudrate); | 
| 57 | 0 |  |  |  |  |  | $port_obj->parity($parity); | 
| 58 | 0 |  |  |  |  |  | $port_obj->databits($databits); | 
| 59 | 0 |  |  |  |  |  | $port_obj->stopbits($stopbits); | 
| 60 |  |  |  |  |  |  | #-# $port_obj->read_interval(1);    # max time between read char (milliseconds) Not in Device::SerialPort | 
| 61 |  |  |  |  |  |  |  | 
| 62 | 0 |  |  |  |  |  | $port_obj->read_const_time(10000);  # total = (avg * bytes) + const | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | #$port_obj->handshake("rts"); | 
| 65 |  |  |  |  |  |  | #$port_obj->buffers(4096, 4096); | 
| 66 |  |  |  |  |  |  |  | 
| 67 | 0 | 0 |  |  |  |  | $port_obj->write_settings || warn 'Write Settings Failed'; | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | #$port_obj->save($conf); | 
| 70 |  |  |  |  |  |  |  | 
| 71 | 0 | 0 |  |  |  |  | unless ($port_obj) { die "Can't change Device_Control_Block: $^E\n"; } | 
|  | 0 |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  |  | 
| 73 | 0 |  | 0 |  |  |  | my ($BlockingFlags, $InBytes, $OutBytes, $LatchErrorFlags) = $port_obj->status | 
| 74 |  |  |  |  |  |  | || warn "could not get port status\n"; | 
| 75 |  |  |  |  |  |  |  | 
| 76 | 0 | 0 |  |  |  |  | if ($BlockingFlags) | 
| 77 |  |  |  |  |  |  | { | 
| 78 |  |  |  |  |  |  | #warn "Port is blocked $BlockingFlags, $InBytes, $OutBytes, $LatchErrorFlags\n"; | 
| 79 |  |  |  |  |  |  | } | 
| 80 |  |  |  |  |  |  |  | 
| 81 | 0 |  |  |  |  |  | $port_obj->purge_all();  # these don't seem to work but try anyway. | 
| 82 | 0 |  |  |  |  |  | $port_obj->purge_rx(); | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | # The object data structure | 
| 85 | 0 |  |  |  |  |  | my $self = bless { | 
| 86 |  |  |  |  |  |  | 'arg_hsh'         => { %arg_hsh }, | 
| 87 |  |  |  |  |  |  | 'port_obj'        => $port_obj, | 
| 88 |  |  |  |  |  |  | 'loop_cnt'        => 0, | 
| 89 |  |  |  |  |  |  | }, $class; | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | # if ( $self->wake_up() ) { print "Station found ready for communications\n" } | 
| 92 |  |  |  |  |  |  |  | 
| 93 | 0 |  |  |  |  |  | return $self; | 
| 94 |  |  |  |  |  |  | } | 
| 95 |  |  |  |  |  |  |  | 
| 96 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | 
| 97 |  |  |  |  |  |  | sub wake_up | 
| 98 |  |  |  |  |  |  | { | 
| 99 | 0 |  |  | 0 | 1 |  | my $self = shift @_; | 
| 100 |  |  |  |  |  |  |  | 
| 101 | 0 |  |  |  |  |  | foreach (1..3) | 
| 102 |  |  |  |  |  |  | { | 
| 103 | 0 |  |  |  |  |  | my $cnt_out = $self->{'port_obj'}->write("\n"); | 
| 104 | 0 | 0 |  |  |  |  | unless ($cnt_out) { warn "write failed\n" }; | 
|  | 0 |  |  |  |  |  |  | 
| 105 | 0 |  |  |  |  |  | my ($cnt_in, $str) = $self->read(2); | 
| 106 |  |  |  |  |  |  |  | 
| 107 | 0 | 0 |  |  |  |  | if ($str eq "\n\r" ) | 
| 108 |  |  |  |  |  |  | { | 
| 109 | 0 | 0 |  |  |  |  | print "Success on Wakeup $_\n" if $Verbose; | 
| 110 | 0 |  |  |  |  |  | return 1; | 
| 111 |  |  |  |  |  |  | } | 
| 112 |  |  |  |  |  |  |  | 
| 113 | 0 |  |  |  |  |  | warn "Not responding to Wakeup\n"; | 
| 114 |  |  |  |  |  |  |  | 
| 115 | 0 |  |  |  |  |  | usleep 1200000; # As per page 5 of VantagePro Doc | 
| 116 |  |  |  |  |  |  | } | 
| 117 |  |  |  |  |  |  |  | 
| 118 | 0 |  |  |  |  |  | warn("Could not unit wake up"); | 
| 119 | 0 |  |  |  |  |  | return -1; # fail | 
| 120 |  |  |  |  |  |  | } | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - - - - - - - - | 
| 123 |  |  |  |  |  |  | sub plug_test | 
| 124 |  |  |  |  |  |  | { | 
| 125 | 0 |  |  | 0 | 0 |  | my $self = shift @_; | 
| 126 |  |  |  |  |  |  |  | 
| 127 | 0 |  |  |  |  |  | my $port_obj = $self->{'port_obj'}; | 
| 128 |  |  |  |  |  |  |  | 
| 129 | 0 |  |  |  |  |  | my $str = "TEST\n"; | 
| 130 |  |  |  |  |  |  |  | 
| 131 | 0 |  |  |  |  |  | print "Sending $str"; | 
| 132 | 0 |  |  |  |  |  | my $cnt_out = $port_obj->write($str); | 
| 133 | 0 | 0 |  |  |  |  | unless ($cnt_out) { warn "write failed\n" }; | 
|  | 0 |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  |  | 
| 135 | 0 |  |  |  |  |  | my ($cnt_in, $str_in) = $port_obj->read(8); | 
| 136 |  |  |  |  |  |  |  | 
| 137 | 0 |  |  |  |  |  | print "returned: $cnt_in, $str_in"; | 
| 138 |  |  |  |  |  |  |  | 
| 139 | 0 |  |  |  |  |  | return $str; | 
| 140 |  |  |  |  |  |  | } | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - - - - - - - - | 
| 143 |  |  |  |  |  |  | sub do_dmpaft | 
| 144 |  |  |  |  |  |  | { | 
| 145 | 0 |  |  | 0 | 1 |  | my $self = shift @_; | 
| 146 |  |  |  |  |  |  |  | 
| 147 | 0 |  |  |  |  |  | my $vDateStamp = shift @_; | 
| 148 | 0 |  |  |  |  |  | my $vTimeStamp = shift @_; | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | # If not date/time stamp then assume 0 which will down load the entire archive | 
| 151 | 0 | 0 |  |  |  |  | unless ( $vDateStamp ) { $vDateStamp = 0 } | 
|  | 0 |  |  |  |  |  |  | 
| 152 | 0 | 0 |  |  |  |  | unless ( $vTimeStamp ) { $vTimeStamp = 0 } | 
|  | 0 |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  |  | 
| 154 | 0 |  |  |  |  |  | my $port_obj = $self->{'port_obj'}; | 
| 155 |  |  |  |  |  |  |  | 
| 156 | 0 |  |  |  |  |  | my $datetime = pack("ss",$vDateStamp, $vTimeStamp); | 
| 157 |  |  |  |  |  |  |  | 
| 158 | 0 |  |  |  |  |  | my $crc = CRC_CCITT($datetime); | 
| 159 | 0 |  |  |  |  |  | my $cmd = pack("ssn",$vDateStamp,$vTimeStamp,$crc); | 
| 160 |  |  |  |  |  |  |  | 
| 161 |  |  |  |  |  |  | #----------------------- | 
| 162 |  |  |  |  |  |  | #my $str = unpack("H*", $cmd); | 
| 163 |  |  |  |  |  |  | #$str =~ s/(\w{2})/$1 /g; | 
| 164 |  |  |  |  |  |  | # Documentation is wrong! The example should be <0xC6><0x06><0xA2><0x03> in section X | 
| 165 |  |  |  |  |  |  | #print "cmd : $str \n";exit; | 
| 166 |  |  |  |  |  |  | #----------------------- | 
| 167 |  |  |  |  |  |  |  | 
| 168 | 0 |  |  |  |  |  | sleep 2; # Needed after loop | 
| 169 | 0 |  |  |  |  |  | $self->wake_up(); | 
| 170 |  |  |  |  |  |  |  | 
| 171 |  |  |  |  |  |  | # Ok let's start the communication sequence.... | 
| 172 | 0 |  |  |  |  |  | my $cnt_out = $port_obj->write("DMPAFT\n"); | 
| 173 | 0 | 0 |  |  |  |  | unless ($cnt_out) { warn "write failed\n" }; | 
|  | 0 |  |  |  |  |  |  | 
| 174 | 0 |  |  |  |  |  | my ($cnt_in, $str) = $self->read(1); | 
| 175 |  |  |  |  |  |  |  | 
| 176 | 0 |  |  |  |  |  | my $ack = ord $str; | 
| 177 | 0 | 0 |  |  |  |  | unless ($ack == 6) { warn "Ack not received on DMPAFT command: $ack"; exit -1; } | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 178 |  |  |  |  |  |  |  | 
| 179 | 0 |  |  |  |  |  | $cnt_out = $port_obj->write($cmd); | 
| 180 | 0 | 0 |  |  |  |  | unless ($cnt_out) { warn "write failed\n" }; | 
|  | 0 |  |  |  |  |  |  | 
| 181 | 0 |  |  |  |  |  | ($cnt_in, $str) = $self->read(7); | 
| 182 |  |  |  |  |  |  |  | 
| 183 | 0 |  |  |  |  |  | $ack = ord substr($str,0,1); | 
| 184 |  |  |  |  |  |  |  | 
| 185 | 0 |  |  |  |  |  | my $ls = unpack("H20",substr($str,1,4) ); | 
| 186 | 0 |  |  |  |  |  | $ls =~ s/(\w{2})/$1 /g; | 
| 187 |  |  |  |  |  |  |  | 
| 188 | 0 |  |  |  |  |  | my $pages = unpack("s",substr($str,1,2) ); | 
| 189 | 0 |  |  |  |  |  | my $rec_start = unpack("s",substr($str,3,2) ); | 
| 190 |  |  |  |  |  |  |  | 
| 191 | 0 |  |  |  |  |  | $crc = CRC_CCITT(substr($str,1,6) ); | 
| 192 |  |  |  |  |  |  |  | 
| 193 | 0 |  |  |  |  |  | print "Pages = $pages : rec = $rec_start Datestamp $vDateStamp $crc\n"; | 
| 194 |  |  |  |  |  |  |  | 
| 195 | 0 |  |  |  |  |  | $cnt_out = $port_obj->write( pack("h", 0x06) ); | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | #if ($pages == 513 ) { return -1 } | 
| 198 |  |  |  |  |  |  |  | 
| 199 | 0 |  |  |  |  |  | my @arc_rec_lst; | 
| 200 | 0 |  |  |  |  |  | foreach my $page (1..$pages) | 
| 201 |  |  |  |  |  |  | { | 
| 202 | 0 |  |  |  |  |  | my $page_sz = 267; | 
| 203 | 0 |  |  |  |  |  | my ($cnt_in, $str) = $self->read($page_sz,3); | 
| 204 | 0 | 0 |  |  |  |  | print "Page $page\n" if ( $Verbose ); | 
| 205 |  |  |  |  |  |  |  | 
| 206 | 0 |  |  |  |  |  | my $rec_sz = 52; | 
| 207 | 0 |  |  |  |  |  | my $date_prev = 0; | 
| 208 | 0 |  |  |  |  |  | my %hsh; | 
| 209 |  |  |  |  |  |  |  | 
| 210 | 0 |  |  |  |  |  | foreach my $rec ( 0..4 ) | 
| 211 |  |  |  |  |  |  | { | 
| 212 | 0 | 0 | 0 |  |  |  | if ( ($page == 1) && ($rec < $rec_start ) ) { next } # Find the right starting point... | 
|  | 0 |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  |  | 
| 214 | 0 |  |  |  |  |  | my $start_ptr = 1 + ($rec * $rec_sz ); | 
| 215 | 0 |  |  |  |  |  | my $rec_str = substr($str, $start_ptr ,52); | 
| 216 |  |  |  |  |  |  | #print "$start_ptr \t > " . unpack( "h*", $rec_str) . "\n"; | 
| 217 |  |  |  |  |  |  |  | 
| 218 | 0 |  |  |  |  |  | my $date = substr($rec_str,0,2); | 
| 219 | 0 |  |  |  |  |  | my $date_curr =  unpack "s", $date; | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | # Check if we have wrapped... | 
| 222 | 0 | 0 |  |  |  |  | if ( $date_curr < $date_prev ) { last; } | 
|  | 0 |  |  |  |  |  |  | 
| 223 | 0 |  |  |  |  |  | $date_prev = $date_curr; | 
| 224 |  |  |  |  |  |  |  | 
| 225 | 0 |  |  |  |  |  | $hsh{'date_stamp'} =  $date_curr; | 
| 226 | 0 |  |  |  |  |  | $hsh{'time_stamp'} =  unpack "s", substr($rec_str,2,2); | 
| 227 |  |  |  |  |  |  |  | 
| 228 | 0 |  |  |  |  |  | $hsh{'day'}    = unpack( "c", $date & pack("c",0x1F) ); | 
| 229 | 0 |  |  |  |  |  | $hsh{'month'}  = ( $hsh{'date_stamp'} >> 5) & 0xF; | 
| 230 | 0 |  |  |  |  |  | $hsh{'year'}  =  ( $hsh{'date_stamp'} >> 9) + 2000; | 
| 231 |  |  |  |  |  |  |  | 
| 232 | 0 |  |  |  |  |  | $hsh{'hour'}  = sprintf("%02d", int ( $hsh{'time_stamp'} / 100 )); | 
| 233 |  |  |  |  |  |  |  | 
| 234 | 0 |  |  |  |  |  | $hsh{'min'}  =  $hsh{'time_stamp'} - ($hsh{'hour'} * 100); | 
| 235 | 0 |  |  |  |  |  | $hsh{'min'}  =  sprintf("%02d", $hsh{'min'}); | 
| 236 |  |  |  |  |  |  |  | 
| 237 | 0 |  |  |  |  |  | $hsh{'time_stamp_fmt'}  =  "$hsh{'hour'}:$hsh{'min'}:00"; | 
| 238 | 0 |  |  |  |  |  | $hsh{'date_stamp_fmt'}  =  "$hsh{'year'}_$hsh{'month'}_$hsh{'day'}"; | 
| 239 |  |  |  |  |  |  |  | 
| 240 | 0 |  |  |  |  |  | $hsh{'unixtime'} = timelocal(0,$hsh{min}, $hsh{hour}, | 
| 241 |  |  |  |  |  |  | $hsh{day}, $hsh{month}-1, $hsh{year}-1900); | 
| 242 |  |  |  |  |  |  |  | 
| 243 | 0 |  |  |  |  |  | $hsh{'Air_Temp'} = unpack("s", substr($rec_str,4,2)) / 10; | 
| 244 | 0 |  |  |  |  |  | $hsh{'Air_Temp_Hi'} = unpack("s", substr($rec_str,6,2)) / 10; | 
| 245 | 0 |  |  |  |  |  | $hsh{'Air_Temp_Lo'} = unpack("s", substr($rec_str,8,2)) / 10; | 
| 246 | 0 |  |  |  |  |  | $hsh{'Rain_Clicks'} = unpack("s", substr($rec_str,10,2)); | 
| 247 | 0 |  |  |  |  |  | $hsh{'Rain_Rate_Clicks'}   = unpack("s", substr($rec_str,12,2)); | 
| 248 | 0 |  |  |  |  |  | $hsh{'Rain_Rate'}   = $hsh{'Rain_Rate_Clicks'} / 100; # Inches per hour | 
| 249 | 0 |  |  |  |  |  | $hsh{'Barometric_Press'}   = unpack("s", substr $rec_str,14,2) / 1000; | 
| 250 | 0 |  |  |  |  |  | $hsh{'Solar'}   = unpack("s", substr $rec_str,16,2);       # watt/m**2 | 
| 251 | 0 |  |  |  |  |  | $hsh{'Wind_Samples'}  = unpack("s", substr $rec_str,18,2); | 
| 252 | 0 |  |  |  |  |  | $hsh{'Air_Temp_Inside'}  = unpack("s", substr $rec_str,20,2) / 10; | 
| 253 |  |  |  |  |  |  |  | 
| 254 | 0 |  |  |  |  |  | $hsh{'Relative_Humidity_Inside'}  = unpack("C", substr $rec_str,22,1); | 
| 255 | 0 |  |  |  |  |  | $hsh{'Relative_Humidity'} = unpack("C", substr $rec_str,23,1); | 
| 256 |  |  |  |  |  |  |  | 
| 257 | 0 |  |  |  |  |  | $hsh{'Wind_Speed'}    =  unpack("C", substr($rec_str,24,1)); | 
| 258 | 0 |  |  |  |  |  | $hsh{'Wind_Gust_Max'} = unpack("C", substr($rec_str,25,1)); | 
| 259 | 0 |  |  |  |  |  | $hsh{'Wind_Dir_Max'}  = unpack("C", substr($rec_str,26,1)); | 
| 260 | 0 |  |  |  |  |  | $hsh{'Wind_Dir'}      = unpack("C", substr($rec_str,27,1)); | 
| 261 |  |  |  |  |  |  |  | 
| 262 | 0 |  |  |  |  |  | $hsh{'UV'} = unpack("C", substr($rec_str,28,1)) / 10; | 
| 263 | 0 |  |  |  |  |  | $hsh{'ET'} = unpack("C", substr($rec_str,29,1)) / 1000; | 
| 264 |  |  |  |  |  |  |  | 
| 265 | 0 |  |  |  |  |  | $hsh{'Solar_Max'} = unpack("s", substr($rec_str,30,2)); | 
| 266 | 0 |  |  |  |  |  | $hsh{'UV_Max'} = unpack("C", substr($rec_str,32,1)); | 
| 267 |  |  |  |  |  |  |  | 
| 268 | 0 |  |  |  |  |  | $hsh{'Forecast_Rule'} = unpack("C", substr($rec_str,33,1)); | 
| 269 |  |  |  |  |  |  |  | 
| 270 | 0 |  |  |  |  |  | $hsh{'Dew_Point'}  = _dew_point($hsh{'Air_Temp'},$hsh{'Relative_Humidity'}); | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | # Miscellaneous others omitted for now | 
| 273 |  |  |  |  |  |  |  | 
| 274 | 0 | 0 |  |  |  |  | print "date> $hsh{'time_stamp'} $hsh{'time_stamp_fmt'}  $hsh{'date_stamp'} $hsh{'date_stamp_fmt'}\n"  if ( $Verbose ); | 
| 275 |  |  |  |  |  |  | #print Dumper \%hsh; | 
| 276 |  |  |  |  |  |  |  | 
| 277 | 0 |  |  |  |  |  | push @arc_rec_lst, {%hsh}; | 
| 278 |  |  |  |  |  |  | } | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  | #$in = ; # Testing step through facility | 
| 281 |  |  |  |  |  |  | #if ($in =~ /q/i ) {  $port_obj->write( pack("h", 0x1B) ); last; } | 
| 282 |  |  |  |  |  |  | #else              {  $port_obj->write( pack("h", 0x06) ); } | 
| 283 | 0 |  |  |  |  |  | $port_obj->write( pack("h", 0x06) ); | 
| 284 |  |  |  |  |  |  |  | 
| 285 |  |  |  |  |  |  | } | 
| 286 |  |  |  |  |  |  |  | 
| 287 | 0 |  |  |  |  |  | return \@arc_rec_lst; | 
| 288 |  |  |  |  |  |  | } | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - - - - - - - - | 
| 291 |  |  |  |  |  |  | sub get_one_loop | 
| 292 |  |  |  |  |  |  | { | 
| 293 | 0 |  |  | 0 | 1 |  | my $self = shift @_; | 
| 294 |  |  |  |  |  |  |  | 
| 295 | 0 | 0 |  |  |  |  | unless ( $self->start_loop(1) ) { return 0; } | 
|  | 0 |  |  |  |  |  |  | 
| 296 | 0 |  |  |  |  |  | my $hsh_ref = $self->read_loop(); | 
| 297 |  |  |  |  |  |  |  | 
| 298 | 0 |  |  |  |  |  | return $hsh_ref; | 
| 299 |  |  |  |  |  |  | } | 
| 300 |  |  |  |  |  |  |  | 
| 301 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - - - - - - - - | 
| 302 |  |  |  |  |  |  | sub start_loop | 
| 303 |  |  |  |  |  |  | { | 
| 304 | 0 |  |  | 0 | 1 |  | my $self = shift @_; | 
| 305 | 0 |  | 0 |  |  |  | my $lp_cnt = shift @_ || 1; | 
| 306 |  |  |  |  |  |  |  | 
| 307 | 0 |  |  |  |  |  | $self->wake_up(); | 
| 308 |  |  |  |  |  |  |  | 
| 309 | 0 |  |  |  |  |  | my $cnt_out = $self->{'port_obj'}->write("LOOP $lp_cnt\n"); | 
| 310 |  |  |  |  |  |  |  | 
| 311 | 0 |  |  |  |  |  | my ($cnt_in, $str) = $self->read(1); | 
| 312 |  |  |  |  |  |  |  | 
| 313 | 0 | 0 |  |  |  |  | if ( ord($str) != 6 ) { warn("Ack not returned for Loop"); return 0; } | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 314 |  |  |  |  |  |  |  | 
| 315 | 0 |  |  |  |  |  | return 1; | 
| 316 |  |  |  |  |  |  | } | 
| 317 |  |  |  |  |  |  |  | 
| 318 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - - - - - - - - - | 
| 319 |  |  |  |  |  |  | sub read_loop | 
| 320 |  |  |  |  |  |  | { | 
| 321 | 0 |  |  | 0 | 1 |  | my $self = shift @_; | 
| 322 |  |  |  |  |  |  |  | 
| 323 | 0 |  |  |  |  |  | my ($cnt_in, $str) = $self->read(99, 4); # extend timeout to 3 seconds | 
| 324 | 0 | 0 |  |  |  |  | if ( $cnt_in != 99 ) { return 0 } | 
|  | 0 |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  |  | 
| 326 | 0 |  |  |  |  |  | my $hsh_ref = parse_loop_blck($str); | 
| 327 |  |  |  |  |  |  |  | 
| 328 | 0 |  |  |  |  |  | return $hsh_ref; | 
| 329 |  |  |  |  |  |  | } | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | 
| 332 |  |  |  |  |  |  | sub parse_loop_blck | 
| 333 |  |  |  |  |  |  | { | 
| 334 | 0 |  |  | 0 | 0 |  | my $blk = shift @_; | 
| 335 | 0 |  |  |  |  |  | my $loo =  substr $blk,0,3; | 
| 336 |  |  |  |  |  |  |  | 
| 337 | 0 |  |  |  |  |  | my $ack = ord substr($blk,0,1); | 
| 338 |  |  |  |  |  |  |  | 
| 339 | 0 | 0 |  |  |  |  | unless ( $loo eq 'LOO') { warn("Block invalid loo -> $loo\n"); return ""; } | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  |  | 
| 341 | 0 |  |  |  |  |  | my %hsh; | 
| 342 |  |  |  |  |  |  |  | 
| 343 | 0 |  |  |  |  |  | $hsh{'Barometric_Trend'}    = unpack("C", substr $blk,3,1); | 
| 344 | 0 |  |  |  |  |  | $hsh{'next_rec'}     = unpack("s", substr $blk,5,2); | 
| 345 | 0 |  |  |  |  |  | $hsh{'Barometric_Press'}          = unpack("s", substr $blk,7,2) / 1000; | 
| 346 | 0 |  |  |  |  |  | $hsh{'Air_Temp_Inside'}      = unpack("s", substr $blk,9,2) / 10; | 
| 347 | 0 |  |  |  |  |  | $hsh{'Humidity_Inside'}  = unpack("C", substr $blk,11,1); | 
| 348 | 0 |  |  |  |  |  | $hsh{'Air_Temp'}     = unpack("s", substr $blk,12,2) / 10; | 
| 349 | 0 |  |  |  |  |  | $hsh{'Wind_Speed'}   = unpack("C", substr $blk,14,1); | 
| 350 | 0 |  |  |  |  |  | $hsh{'Wind_Speed_10min_Ave'} = unpack("C", substr $blk,15,1); | 
| 351 | 0 |  |  |  |  |  | $hsh{'Wind_Dir'}     = unpack("s", substr $blk,16,2); | 
| 352 |  |  |  |  |  |  | # Skip other temps for now... | 
| 353 |  |  |  |  |  |  |  | 
| 354 | 0 |  |  |  |  |  | $hsh{'Relative_Humidity'} = unpack("C", substr $blk,33,1); | 
| 355 |  |  |  |  |  |  | # Skip other humidities for now... | 
| 356 |  |  |  |  |  |  |  | 
| 357 | 0 |  |  |  |  |  | $hsh{'Rain_Rate_Clicks'}  = unpack("s", substr $blk,41,2); | 
| 358 | 0 |  |  |  |  |  | $hsh{'Rain_Rate'}  = $hsh{'Rain_Rate_Clicks'} / 100; # Inches per hr | 
| 359 | 0 |  |  |  |  |  | $hsh{'UV'}         = unpack("C", substr $blk,43,1); | 
| 360 | 0 |  |  |  |  |  | $hsh{'Solar'}  = unpack("s", substr $blk,44,2);       # watt/m**2 | 
| 361 | 0 |  |  |  |  |  | $hsh{'Rain_Storm'} = unpack("s", substr $blk,46,2) / 100; # Inches per storm | 
| 362 |  |  |  |  |  |  |  | 
| 363 | 0 |  |  |  |  |  | $hsh{'Storm_Date'} = unpack("s", substr $blk,48,2);  # Need to parse data (not sure what this is) | 
| 364 | 0 |  |  |  |  |  | $hsh{'Rain_Day'}   = unpack("s", substr $blk,50,2)/100; | 
| 365 | 0 |  |  |  |  |  | $hsh{'Rain_Month'}  = unpack("s", substr $blk,52,2)/100; | 
| 366 | 0 |  |  |  |  |  | $hsh{'Rain_Year'}  = unpack("s", substr $blk,54,2)/100; | 
| 367 |  |  |  |  |  |  |  | 
| 368 | 0 |  |  |  |  |  | $hsh{'Day_ET'}   = unpack("s", substr $blk,56,2)/1000; | 
| 369 | 0 |  |  |  |  |  | $hsh{'Month_ET'}  = unpack("s", substr $blk,58,2)/100; | 
| 370 | 0 |  |  |  |  |  | $hsh{'Year_ET'}  = unpack("s", substr $blk,60,2)/100; | 
| 371 |  |  |  |  |  |  | # Skip Soil/Leaf Wetness | 
| 372 |  |  |  |  |  |  |  | 
| 373 | 0 |  |  |  |  |  | $hsh{'Alarms_Inside'}  = unpack("b8", substr $blk,70,1); | 
| 374 | 0 |  |  |  |  |  | $hsh{'Alarms_Rain'}  = unpack("b8", substr $blk,70,1); | 
| 375 | 0 |  |  |  |  |  | $hsh{'Alarms_Outside'}  = unpack("b8", substr $blk,70,1); | 
| 376 |  |  |  |  |  |  | # Skip extra alarms | 
| 377 |  |  |  |  |  |  |  | 
| 378 | 0 |  |  |  |  |  | $hsh{'Batt_Xmit'}  = unpack("C", substr $blk,86,1) * 0.005859375; | 
| 379 | 0 |  |  |  |  |  | $hsh{'Batt_Cons'}  = unpack("s", substr $blk,87,2) * 0.005859375; | 
| 380 |  |  |  |  |  |  |  | 
| 381 | 0 |  |  |  |  |  | $hsh{'Forecast_Icon'}  = unpack("C", substr $blk,89,1); | 
| 382 | 0 |  |  |  |  |  | $hsh{'Forecast_Rule'}  = unpack("C", substr $blk,90,1); | 
| 383 |  |  |  |  |  |  |  | 
| 384 | 0 |  |  |  |  |  | $hsh{'Sunrise'}  = sprintf( "%04d", unpack("S", substr $blk,91,2) ); | 
| 385 | 0 |  |  |  |  |  | $hsh{'Sunrise'}  =~ s/(\d{2})(\d{2})/$1:$2/; | 
| 386 |  |  |  |  |  |  |  | 
| 387 | 0 |  |  |  |  |  | $hsh{'Sunset'}   = sprintf( "%04d", unpack("S", substr $blk,93,2) ); | 
| 388 | 0 |  |  |  |  |  | $hsh{'Sunset'}  =~ s/(\d{2})(\d{2})/$1:$2/; | 
| 389 |  |  |  |  |  |  |  | 
| 390 | 0 |  |  |  |  |  | $hsh{'Dew_Point'}  = _dew_point($hsh{'Air_Temp'},$hsh{'Relative_Humidity'}); | 
| 391 |  |  |  |  |  |  |  | 
| 392 | 0 |  |  |  |  |  | my $nl  =  ord substr $blk,95,1; | 
| 393 | 0 |  |  |  |  |  | my $cr  =  ord substr $blk,96,1; | 
| 394 |  |  |  |  |  |  |  | 
| 395 | 0 |  |  |  |  |  | $hsh{crc} = unpack "%n", substr($blk,97,2); | 
| 396 | 0 |  |  |  |  |  | $hsh{'crc_calc'} = CRC_CCITT($blk); | 
| 397 |  |  |  |  |  |  |  | 
| 398 | 0 |  |  |  |  |  | return \%hsh; | 
| 399 |  |  |  |  |  |  | } | 
| 400 |  |  |  |  |  |  |  | 
| 401 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - - - - - - - - | 
| 402 |  |  |  |  |  |  | sub get_eeprom | 
| 403 |  |  |  |  |  |  | { | 
| 404 | 0 |  |  | 0 | 1 |  | my $self = shift @_; | 
| 405 | 0 |  |  |  |  |  | my $item = shift @_; | 
| 406 |  |  |  |  |  |  |  | 
| 407 | 0 |  |  |  |  |  | my ($loc, $size); | 
| 408 |  |  |  |  |  |  | # Not all supported.... More to follow | 
| 409 | 0 | 0 |  |  |  |  | if ( uc($item) eq 'ARCHIVE_PERIOD' ){ $loc = '2D'; $size = '01' } | 
|  | 0 | 0 |  |  |  |  |  | 
|  | 0 | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 410 | 0 |  |  |  |  |  | elsif ( uc($item) eq 'TIME_ZONE' ){ $loc = '11'; $size = '01' } | 
|  | 0 |  |  |  |  |  |  | 
| 411 | 0 |  |  |  |  |  | elsif ( uc($item) eq 'MANUAL_OR_AUTO' ){ $loc = '12'; $size = '01' } | 
|  | 0 |  |  |  |  |  |  | 
| 412 | 0 |  |  |  |  |  | elsif ( uc($item) eq 'DAYLIGHT_SAVINGS' ){ $loc = '13'; $size = '01' } | 
|  | 0 |  |  |  |  |  |  | 
| 413 | 0 |  |  |  |  |  | elsif ( uc($item) eq 'GMT_OFFSET' ){ $loc = '14'; $size = '02' } | 
|  | 0 |  |  |  |  |  |  | 
| 414 | 0 |  |  |  |  |  | elsif ( uc($item) eq 'GMT_OR_ZONE' ){ $loc = '16'; $size = '01' } | 
|  | 0 |  |  |  |  |  |  | 
| 415 | 0 |  |  |  |  |  | elsif ( uc($item) eq 'SETUP_BITS' ){ $loc = '2B'; $size = '01' } | 
|  | 0 |  |  |  |  |  |  | 
| 416 | 0 |  |  |  |  |  | else { warn "$item not found"; return -1; } | 
|  | 0 |  |  |  |  |  |  | 
| 417 |  |  |  |  |  |  |  | 
| 418 | 0 |  |  |  |  |  | my $port_obj = $self->{port_obj}; | 
| 419 |  |  |  |  |  |  |  | 
| 420 | 0 |  |  |  |  |  | my $cnt_out = $port_obj->write("EERD $loc $size\n"); | 
| 421 | 0 | 0 |  |  |  |  | unless ($cnt_out) { warn "write failed\n" }; | 
|  | 0 |  |  |  |  |  |  | 
| 422 |  |  |  |  |  |  |  | 
| 423 |  |  |  |  |  |  | # A \n\r is prefixed not as in the documentation... | 
| 424 | 0 |  |  |  |  |  | my $read_size = (hex($size) * 4) + 6; | 
| 425 |  |  |  |  |  |  |  | 
| 426 | 0 |  |  |  |  |  | my ($cnt_in, $str) = $self->read($read_size); | 
| 427 |  |  |  |  |  |  |  | 
| 428 | 0 |  |  |  |  |  | my @rsp_lst = split /\n\r/, $str; | 
| 429 | 0 |  |  |  |  |  | shift(@rsp_lst); | 
| 430 |  |  |  |  |  |  |  | 
| 431 | 0 | 0 |  |  |  |  | if ( $rsp_lst[0] ne 'OK' ) { _dump($str); warn "OK Not returned";  } | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 432 | 0 |  |  |  |  |  | shift(@rsp_lst); | 
| 433 |  |  |  |  |  |  |  | 
| 434 | 0 |  |  |  |  |  | return \@rsp_lst; | 
| 435 |  |  |  |  |  |  | } | 
| 436 |  |  |  |  |  |  |  | 
| 437 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - - - - - - - - | 
| 438 |  |  |  |  |  |  | sub gettime | 
| 439 |  |  |  |  |  |  | { | 
| 440 | 0 |  |  | 0 | 1 |  | my $self = shift @_; | 
| 441 |  |  |  |  |  |  |  | 
| 442 | 0 |  |  |  |  |  | my $port_obj = $self->{port_obj}; | 
| 443 |  |  |  |  |  |  |  | 
| 444 | 0 |  |  |  |  |  | my $cnt_out = $port_obj->write("GETTIME\n"); | 
| 445 | 0 | 0 |  |  |  |  | unless ($cnt_out) { warn "write failed\n" }; | 
|  | 0 |  |  |  |  |  |  | 
| 446 |  |  |  |  |  |  |  | 
| 447 | 0 |  |  |  |  |  | my ($cnt_in, $str) = $port_obj->read(9); | 
| 448 |  |  |  |  |  |  |  | 
| 449 | 0 |  |  |  |  |  | my $ck = CRC_CCITT(substr($str,1,9)); | 
| 450 | 0 | 0 |  |  |  |  | if ( $ck ) { warn "checksum error"; return 0; } | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 451 |  |  |  |  |  |  |  | 
| 452 | 0 |  |  |  |  |  | my @rsp_lst =  split //, $str; | 
| 453 | 0 |  |  |  |  |  | shift @rsp_lst; | 
| 454 |  |  |  |  |  |  |  | 
| 455 | 0 |  |  |  |  |  | @rsp_lst = map ord, @rsp_lst; | 
| 456 |  |  |  |  |  |  |  | 
| 457 | 0 |  |  |  |  |  | return \@rsp_lst; | 
| 458 |  |  |  |  |  |  | } | 
| 459 |  |  |  |  |  |  |  | 
| 460 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - - - - - - - - | 
| 461 |  |  |  |  |  |  | sub settime | 
| 462 |  |  |  |  |  |  | { | 
| 463 | 0 |  |  | 0 | 1 |  | my $self  = shift @_; | 
| 464 | 0 |  |  |  |  |  | my $t_ref = shift @_; | 
| 465 |  |  |  |  |  |  |  | 
| 466 | 0 |  |  |  |  |  | my $port_obj = $self->{port_obj}; | 
| 467 |  |  |  |  |  |  |  | 
| 468 | 0 |  |  |  |  |  | my $cnt_out = $port_obj->write("SETTIME\n"); | 
| 469 | 0 | 0 |  |  |  |  | unless ($cnt_out) { warn "write failed\n" }; | 
|  | 0 |  |  |  |  |  |  | 
| 470 |  |  |  |  |  |  |  | 
| 471 | 0 |  |  |  |  |  | my ($cnt_in, $str) = $port_obj->read(1); | 
| 472 | 0 |  |  |  |  |  | my $ack = ord $str; | 
| 473 | 0 | 0 |  |  |  |  | if ( $ack != 6 ) { warn "SETTIME not set ack $ack !"; return 0; } | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  |  | 
| 475 | 0 |  |  |  |  |  | my ($sec, $min, $hour, $day, $mon, $yr) = @{$t_ref}; | 
|  | 0 |  |  |  |  |  |  | 
| 476 |  |  |  |  |  |  |  | 
| 477 | 0 |  |  |  |  |  | $str = join "", map chr, ($sec, $min, $hour, $day, $mon, $yr); | 
| 478 |  |  |  |  |  |  |  | 
| 479 | 0 |  |  |  |  |  | my $ck = CRC_CCITT($str); | 
| 480 | 0 |  |  |  |  |  | $str = $str . pack("n",$ck); | 
| 481 |  |  |  |  |  |  |  | 
| 482 | 0 |  |  |  |  |  | $cnt_out = $port_obj->write($str); | 
| 483 | 0 | 0 |  |  |  |  | unless ($cnt_out) { warn "write failed\n" }; | 
|  | 0 |  |  |  |  |  |  | 
| 484 |  |  |  |  |  |  |  | 
| 485 | 0 |  |  |  |  |  | ($cnt_in, $str) = $port_obj->read(1); | 
| 486 | 0 | 0 |  |  |  |  | if ( ord($str) != 6 ) { warn "SETTIME not set!"; return 0; } | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 487 |  |  |  |  |  |  |  | 
| 488 | 0 |  |  |  |  |  | sleep 3;   # The console seems to need to some time here... | 
| 489 |  |  |  |  |  |  |  | 
| 490 | 0 |  |  |  |  |  | return 1; | 
| 491 |  |  |  |  |  |  | } | 
| 492 |  |  |  |  |  |  |  | 
| 493 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - - - - - - - - | 
| 494 |  |  |  |  |  |  | sub set_archive_period | 
| 495 |  |  |  |  |  |  | { | 
| 496 | 0 |  |  | 0 | 1 |  | my $self    = shift @_; | 
| 497 | 0 |  |  |  |  |  | my $period  = shift @_; | 
| 498 |  |  |  |  |  |  |  | 
| 499 | 0 | 0 |  |  |  |  | unless ( grep { $_ == $period } (1, 5, 10, 15, 30, 60, 120) ) | 
|  | 0 |  |  |  |  |  |  | 
| 500 |  |  |  |  |  |  | { | 
| 501 | 0 |  |  |  |  |  | warn "Not valid archive period"; # Limits in document | 
| 502 | 0 |  |  |  |  |  | return 0; | 
| 503 |  |  |  |  |  |  | } | 
| 504 |  |  |  |  |  |  |  | 
| 505 | 0 |  |  |  |  |  | my $port_obj = $self->{port_obj}; | 
| 506 |  |  |  |  |  |  |  | 
| 507 | 0 |  |  |  |  |  | my $cnt_out = $port_obj->write("SETPER $period\n"); | 
| 508 | 0 | 0 |  |  |  |  | unless ($cnt_out) { warn "write failed\n" }; | 
|  | 0 |  |  |  |  |  |  | 
| 509 |  |  |  |  |  |  |  | 
| 510 | 0 |  |  |  |  |  | my ($cnt_in, $str) = $port_obj->read(1); | 
| 511 |  |  |  |  |  |  |  | 
| 512 | 0 |  |  |  |  |  | my $ack = ord $str; | 
| 513 |  |  |  |  |  |  |  | 
| 514 | 0 | 0 |  |  |  |  | unless ( $ack != 6 ) { warn "Archive not set!"; return 0; } | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 515 |  |  |  |  |  |  |  | 
| 516 | 0 |  |  |  |  |  | return 1; | 
| 517 |  |  |  |  |  |  |  | 
| 518 |  |  |  |  |  |  | } | 
| 519 |  |  |  |  |  |  |  | 
| 520 |  |  |  |  |  |  | my $t_prv = time; | 
| 521 |  |  |  |  |  |  |  | 
| 522 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - | 
| 523 |  |  |  |  |  |  | sub get_archive_period | 
| 524 |  |  |  |  |  |  | { | 
| 525 | 0 |  |  | 0 | 1 |  | my $self    = shift @_; | 
| 526 |  |  |  |  |  |  |  | 
| 527 | 0 |  |  |  |  |  | my $rst = $self->get_eeprom('archive_period'); | 
| 528 | 0 |  |  |  |  |  | my $archive_period = hex($rst->[0]); | 
| 529 |  |  |  |  |  |  |  | 
| 530 | 0 |  |  |  |  |  | return $archive_period; | 
| 531 |  |  |  |  |  |  | } | 
| 532 |  |  |  |  |  |  |  | 
| 533 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - | 
| 534 |  |  |  |  |  |  | sub get_timezone | 
| 535 |  |  |  |  |  |  | { | 
| 536 | 0 |  |  | 0 | 1 |  | my $self    = shift @_; | 
| 537 |  |  |  |  |  |  |  | 
| 538 | 1 |  |  | 1 |  | 1123 | use DateTime::TimeZone; | 
|  | 1 |  |  |  |  | 92357 |  | 
|  | 1 |  |  |  |  | 1761 |  | 
| 539 |  |  |  |  |  |  |  | 
| 540 |  |  |  |  |  |  | # Calculate the time zone used by the VP and return as a TimeZone object | 
| 541 |  |  |  |  |  |  |  | 
| 542 | 0 |  |  |  |  |  | my $timezone; | 
| 543 | 0 | 0 |  |  |  |  | if (hex $self->get_eeprom('gmt_or_zone')->[0]) | 
| 544 |  |  |  |  |  |  | { | 
| 545 |  |  |  |  |  |  | # Unit is configured for GMT offset value | 
| 546 |  |  |  |  |  |  | # Wow, this is messy! | 
| 547 | 0 |  |  |  |  |  | my $dst = 0; # Manual daylight saving adjustment to make | 
| 548 | 0 | 0 |  |  |  |  | if (hex $self->get_eeprom('manual_or_auto')->[0]) | 
| 549 |  |  |  |  |  |  | { | 
| 550 |  |  |  |  |  |  | # Unit has daylight saving in manual | 
| 551 | 0 |  |  |  |  |  | $dst = hex $self->get_eeprom('daylight_savings')->[0]; | 
| 552 |  |  |  |  |  |  | } | 
| 553 | 0 |  |  |  |  |  | my $val = $self->get_eeprom('gmt_offset');  # Get offset in hours | 
| 554 | 0 |  |  |  |  |  | my $offset = hex ($val->[1].$val->[0]);     # Combine the 2 bytes together | 
| 555 | 0 | 0 |  |  |  |  | $offset -= 65536 if $offset > 32767;        # 2's complement if -ve | 
| 556 | 0 |  |  |  |  |  | $offset /= 100;                             # Convert to hours | 
| 557 | 0 |  |  |  |  |  | $offset += $dst;                            # Adjust for daylight saving if required | 
| 558 | 0 |  |  |  |  |  | my $hours = int $offset;                    # The whole number of hours | 
| 559 | 0 |  |  |  |  |  | my $minutes = abs ($offset - $hours) * 60;  # The number of minutes | 
| 560 | 0 |  |  |  |  |  | $minutes = sprintf("%02d", $minutes);       # Prefix with 0 if required | 
| 561 | 0 |  |  |  |  |  | my $tzstr = $hours.$minutes;                # The 2 together to create tz string | 
| 562 | 0 | 0 | 0 |  |  |  | $tzstr *= -1 if $offset < 0 && $hours == 0; # Fix negative for 0 hours | 
| 563 | 0 |  |  |  |  |  | $tzstr = sprintf("%+05d", $tzstr);          # The final formatted string | 
| 564 | 0 |  |  |  |  |  | $timezone = DateTime::TimeZone->new( name => $tzstr ); | 
| 565 |  |  |  |  |  |  | } | 
| 566 |  |  |  |  |  |  | else { | 
| 567 |  |  |  |  |  |  | # Unit configured for specific timezone | 
| 568 | 0 |  |  |  |  |  | my $tz = hex $self->get_eeprom('time_zone')->[0]; | 
| 569 | 0 |  |  |  |  |  | my @timezones = qw( Pacific/Kwajalein | 
| 570 |  |  |  |  |  |  | Pacific/Midway | 
| 571 |  |  |  |  |  |  | Pacific/Honolulu | 
| 572 |  |  |  |  |  |  | America/Anchorage | 
| 573 |  |  |  |  |  |  | America/Tijuana | 
| 574 |  |  |  |  |  |  | America/Denver | 
| 575 |  |  |  |  |  |  | America/Chicago | 
| 576 |  |  |  |  |  |  | America/Mexico_City | 
| 577 |  |  |  |  |  |  | America/Monterrey | 
| 578 |  |  |  |  |  |  | America/Bogota | 
| 579 |  |  |  |  |  |  | America/New_York | 
| 580 |  |  |  |  |  |  | America/Halifax | 
| 581 |  |  |  |  |  |  | America/Santiago | 
| 582 |  |  |  |  |  |  | America/St_Johns | 
| 583 |  |  |  |  |  |  | America/Sao_Paulo | 
| 584 |  |  |  |  |  |  | America/Argentina/Buenos_Aires | 
| 585 |  |  |  |  |  |  | Atlantic/South_Georgia | 
| 586 |  |  |  |  |  |  | Atlantic/Azores | 
| 587 |  |  |  |  |  |  | Europe/London | 
| 588 |  |  |  |  |  |  | Africa/Casablanca | 
| 589 |  |  |  |  |  |  | Europe/Berlin | 
| 590 |  |  |  |  |  |  | Europe/Paris | 
| 591 |  |  |  |  |  |  | Europe/Prague | 
| 592 |  |  |  |  |  |  | Europe/Athens | 
| 593 |  |  |  |  |  |  | Africa/Cairo | 
| 594 |  |  |  |  |  |  | Europe/Bucharest | 
| 595 |  |  |  |  |  |  | Africa/Harare | 
| 596 |  |  |  |  |  |  | Asia/Jerusalem | 
| 597 |  |  |  |  |  |  | Asia/Baghdad | 
| 598 |  |  |  |  |  |  | Europe/Moscow | 
| 599 |  |  |  |  |  |  | Asia/Tehran | 
| 600 |  |  |  |  |  |  | Asia/Muscat | 
| 601 |  |  |  |  |  |  | Asia/Kabul | 
| 602 |  |  |  |  |  |  | Asia/Karachi | 
| 603 |  |  |  |  |  |  | Asia/Kolkata | 
| 604 |  |  |  |  |  |  | Asia/Almaty | 
| 605 |  |  |  |  |  |  | Asia/Bangkok | 
| 606 |  |  |  |  |  |  | Asia/Shanghai | 
| 607 |  |  |  |  |  |  | Asia/Hong_Kong | 
| 608 |  |  |  |  |  |  | Asia/Tokyo | 
| 609 |  |  |  |  |  |  | Australia/Adelaide | 
| 610 |  |  |  |  |  |  | Australia/Darwin | 
| 611 |  |  |  |  |  |  | Australia/Brisbane | 
| 612 |  |  |  |  |  |  | Australia/Hobart | 
| 613 |  |  |  |  |  |  | Asia/Magadan | 
| 614 |  |  |  |  |  |  | Pacific/Fiji | 
| 615 |  |  |  |  |  |  | Pacific/Auckland | 
| 616 |  |  |  |  |  |  | ); | 
| 617 | 0 |  |  |  |  |  | $timezone = DateTime::TimeZone->new( name => $timezones[$tz] ); | 
| 618 |  |  |  |  |  |  | } | 
| 619 |  |  |  |  |  |  |  | 
| 620 | 0 |  |  |  |  |  | return $timezone; | 
| 621 |  |  |  |  |  |  | } | 
| 622 |  |  |  |  |  |  |  | 
| 623 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - | 
| 624 |  |  |  |  |  |  | sub make_date_time_stamp | 
| 625 |  |  |  |  |  |  | { | 
| 626 | 0 |  |  | 0 | 1 |  | my $self    = shift @_; | 
| 627 |  |  |  |  |  |  |  | 
| 628 | 0 |  |  |  |  |  | my ($year, $mon, $mday, $hour, $min) = @_; | 
| 629 |  |  |  |  |  |  |  | 
| 630 |  |  |  |  |  |  |  | 
| 631 |  |  |  |  |  |  | # Test Example as per Page 31 in Document | 
| 632 |  |  |  |  |  |  | #$mon = 6;$mday = 6;$year = 2003;$hour = 9;$min  = 30; | 
| 633 |  |  |  |  |  |  | # See print time stamps below after CRC and formatting | 
| 634 |  |  |  |  |  |  |  | 
| 635 |  |  |  |  |  |  | #print "Looking for record $year, $mon $mday $hour:$min\n"; | 
| 636 |  |  |  |  |  |  |  | 
| 637 |  |  |  |  |  |  | # The friggen Vantage pro requires time stamps that _exactly_ match | 
| 638 |  |  |  |  |  |  | # the record in memory or it sends the whole archive. | 
| 639 |  |  |  |  |  |  | #my $rmn = $self->get_archive_period(); | 
| 640 |  |  |  |  |  |  | #$min = $min - $rmn;                # Note this does not work for any archive_period > 60 | 
| 641 |  |  |  |  |  |  |  | 
| 642 |  |  |  |  |  |  | #if ( $min > 0 ) | 
| 643 |  |  |  |  |  |  | #{ $min = 60 + $min; | 
| 644 |  |  |  |  |  |  | #  $hour -= 1; | 
| 645 |  |  |  |  |  |  | #	if ($hour < 0 ) { $hour = 23;  } | 
| 646 |  |  |  |  |  |  | #} | 
| 647 |  |  |  |  |  |  |  | 
| 648 |  |  |  |  |  |  | #my $gap = $min % $rmn; | 
| 649 |  |  |  |  |  |  | #$min = $min - $gap; | 
| 650 |  |  |  |  |  |  |  | 
| 651 |  |  |  |  |  |  | #print "Looking for record $year, $mon $mday $hour:$min\n"; | 
| 652 |  |  |  |  |  |  |  | 
| 653 | 0 |  |  |  |  |  | my $vDateStamp = $mday + ($mon)*32 + ($year-2000)*512; | 
| 654 | 0 |  |  |  |  |  | my $vTimeStamp = (100 * $hour) + $min; | 
| 655 |  |  |  |  |  |  |  | 
| 656 | 0 |  |  |  |  |  | return ($vDateStamp, $vTimeStamp); | 
| 657 |  |  |  |  |  |  | } | 
| 658 |  |  |  |  |  |  |  | 
| 659 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - | 
| 660 |  |  |  |  |  |  | sub get_setup_bits | 
| 661 |  |  |  |  |  |  | { | 
| 662 | 0 |  |  | 0 | 1 |  | my $self    = shift @_; | 
| 663 |  |  |  |  |  |  |  | 
| 664 | 0 |  |  |  |  |  | my $rst = $self->get_eeprom('setup_bits'); | 
| 665 | 0 |  |  |  |  |  | my $enc = hex($rst->[0]); | 
| 666 | 0 |  |  |  |  |  | my %setup_bits; | 
| 667 | 0 |  |  |  |  |  | $setup_bits{TimeMode}          = $enc & 0x01; | 
| 668 | 0 |  |  |  |  |  | $setup_bits{IsAM}              = $enc >> 1 & 0x01; | 
| 669 | 0 |  |  |  |  |  | $setup_bits{MonthDayFormat}    = $enc >> 2 & 0x01; | 
| 670 | 0 |  |  |  |  |  | $setup_bits{WindCupSize}       = $enc >> 3 & 0x01; | 
| 671 | 0 |  |  |  |  |  | $setup_bits{RainCollectorSize} = $enc >> 4 & 0x03; | 
| 672 | 0 |  |  |  |  |  | $setup_bits{Latitude}          = $enc >> 6 & 0x01; | 
| 673 | 0 |  |  |  |  |  | $setup_bits{Longitude}         = $enc >> 7 & 0x01; | 
| 674 |  |  |  |  |  |  |  | 
| 675 | 0 |  |  |  |  |  | return \%setup_bits; | 
| 676 |  |  |  |  |  |  | } | 
| 677 |  |  |  |  |  |  |  | 
| 678 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - | 
| 679 |  |  |  |  |  |  | sub get_ymdhm | 
| 680 |  |  |  |  |  |  | { | 
| 681 | 0 |  |  | 0 | 0 |  | my $self    = shift @_; | 
| 682 | 0 |  |  |  |  |  | my $utime   = shift @_; | 
| 683 |  |  |  |  |  |  |  | 
| 684 | 0 |  |  |  |  |  | my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($utime); | 
| 685 | 0 |  |  |  |  |  | $mon = $mon + 1; | 
| 686 | 0 |  |  |  |  |  | $year = $year + 1900; | 
| 687 |  |  |  |  |  |  |  | 
| 688 | 0 |  |  |  |  |  | return ($year, $mon, $mday, $hour, $min); | 
| 689 |  |  |  |  |  |  | } | 
| 690 |  |  |  |  |  |  |  | 
| 691 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - | 
| 692 |  |  |  |  |  |  | sub read | 
| 693 |  |  |  |  |  |  | { | 
| 694 | 0 |  |  | 0 | 0 |  | my $self    = shift @_; | 
| 695 | 0 |  | 0 |  |  |  | my $bytes   = shift @_ || 255; | 
| 696 | 0 |  | 0 |  |  |  | my $timeout = shift @_ || 2; | 
| 697 |  |  |  |  |  |  |  | 
| 698 | 0 |  |  |  |  |  | my $port_obj = $self->{port_obj}; | 
| 699 |  |  |  |  |  |  |  | 
| 700 | 0 |  |  |  |  |  | my ($cnt_in, $str); | 
| 701 |  |  |  |  |  |  |  | 
| 702 | 0 |  |  |  |  |  | eval { | 
| 703 | 0 |  |  | 0 |  |  | local $SIG{ALRM} = sub { die "alarm $timeout expired\n" }; # NB: \n required | 
|  | 0 |  |  |  |  |  |  | 
| 704 | 0 |  |  |  |  |  | alarm $timeout; | 
| 705 |  |  |  |  |  |  |  | 
| 706 | 0 |  |  |  |  |  | ($cnt_in, $str) = $self->{'port_obj'}->read($bytes); | 
| 707 |  |  |  |  |  |  |  | 
| 708 | 0 |  |  |  |  |  | alarm 0; | 
| 709 |  |  |  |  |  |  | }; | 
| 710 |  |  |  |  |  |  |  | 
| 711 | 0 | 0 |  |  |  |  | if ($@) | 
| 712 |  |  |  |  |  |  | { | 
| 713 | 0 |  |  |  |  |  | warn "Read Timeout $timeout\n"; | 
| 714 | 0 |  |  |  |  |  | return 0; | 
| 715 |  |  |  |  |  |  | } | 
| 716 |  |  |  |  |  |  |  | 
| 717 | 0 |  |  |  |  |  | return ($cnt_in, $str); | 
| 718 |  |  |  |  |  |  |  | 
| 719 |  |  |  |  |  |  | } | 
| 720 |  |  |  |  |  |  |  | 
| 721 |  |  |  |  |  |  | sub _dew_point | 
| 722 |  |  |  |  |  |  | { | 
| 723 | 0 |  |  | 0 |  |  | my $temp = shift @_; | 
| 724 | 0 |  |  |  |  |  | my $rh   = shift @_; | 
| 725 |  |  |  |  |  |  |  | 
| 726 |  |  |  |  |  |  | #  Using the simplified approximation for dew point | 
| 727 |  |  |  |  |  |  | #  Accurate to 1 degree C for humidities > 50 % | 
| 728 |  |  |  |  |  |  | #  http://en.wikipedia.org/wiki/Dew_point | 
| 729 |  |  |  |  |  |  |  | 
| 730 | 0 |  |  |  |  |  | my $dew_point = $temp - ( (100 - $rh)/5 ); | 
| 731 |  |  |  |  |  |  |  | 
| 732 | 0 |  |  |  |  |  | return $dew_point; | 
| 733 |  |  |  |  |  |  | } | 
| 734 |  |  |  |  |  |  |  | 
| 735 |  |  |  |  |  |  |  | 
| 736 |  |  |  |  |  |  | sub _dump | 
| 737 |  |  |  |  |  |  | { | 
| 738 | 0 |  |  | 0 |  |  | my @lst = split //, $_[0]; | 
| 739 | 0 |  |  |  |  |  | print "Bytes " . scalar(@lst) . "\n"; | 
| 740 | 0 |  |  |  |  |  | foreach my $i ( @lst ) { | 
| 741 | 0 |  |  |  |  |  | print "> " . ord($i) . "\n"; | 
| 742 |  |  |  |  |  |  | } | 
| 743 |  |  |  |  |  |  | } | 
| 744 |  |  |  |  |  |  |  | 
| 745 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - | 
| 746 |  |  |  |  |  |  | sub CRC_CCITT | 
| 747 |  |  |  |  |  |  | { | 
| 748 |  |  |  |  |  |  | # Expects packed data... | 
| 749 | 0 |  |  | 0 | 0 |  | my $data_str = shift @_; | 
| 750 |  |  |  |  |  |  |  | 
| 751 | 0 |  |  |  |  |  | my @crc_table = crc_table(); | 
| 752 |  |  |  |  |  |  |  | 
| 753 | 0 |  |  |  |  |  | my $crc = 0; | 
| 754 | 0 |  |  |  |  |  | my @lst = split //, $data_str; | 
| 755 | 0 |  |  |  |  |  | foreach my $data (@lst) | 
| 756 |  |  |  |  |  |  | { | 
| 757 | 0 |  |  |  |  |  | my $data = unpack("c",$data); | 
| 758 |  |  |  |  |  |  |  | 
| 759 | 0 |  |  |  |  |  | my $crc_prev = $crc; | 
| 760 | 0 |  |  |  |  |  | my $index = $crc >> 8 ^ $data; | 
| 761 | 0 |  |  |  |  |  | my $lhs = $crc_table[$index]; | 
| 762 | 0 |  |  |  |  |  | my $rhs = ($crc << 8) & 0xFFFF; | 
| 763 | 0 |  |  |  |  |  | $crc = $lhs ^ $rhs; | 
| 764 |  |  |  |  |  |  |  | 
| 765 |  |  |  |  |  |  | #$data = unpack("H*",$data); | 
| 766 |  |  |  |  |  |  | #printf("%X\t %s\t %X\t %X\t %X\t : %x \n", $crc_prev, $data, $index, $lhs, $rhs, $crc); | 
| 767 |  |  |  |  |  |  | } | 
| 768 |  |  |  |  |  |  |  | 
| 769 | 0 |  |  |  |  |  | return $crc; | 
| 770 |  |  |  |  |  |  | } | 
| 771 |  |  |  |  |  |  |  | 
| 772 |  |  |  |  |  |  | # - - - - - - - - - - - - - - - - - - - | 
| 773 |  |  |  |  |  |  | sub crc_table | 
| 774 |  |  |  |  |  |  | { | 
| 775 |  |  |  |  |  |  |  | 
| 776 | 0 |  |  | 0 | 0 |  | my @crc_table = ( | 
| 777 |  |  |  |  |  |  | 0x0, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, | 
| 778 |  |  |  |  |  |  | 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, | 
| 779 |  |  |  |  |  |  | 0x1231, 0x210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, | 
| 780 |  |  |  |  |  |  | 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, | 
| 781 |  |  |  |  |  |  | 0x2462, 0x3443, 0x420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, | 
| 782 |  |  |  |  |  |  | 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, | 
| 783 |  |  |  |  |  |  | 0x3653, 0x2672, 0x1611, 0x630, 0x76d7, 0x66f6, 0x5695, 0x46b4, | 
| 784 |  |  |  |  |  |  | 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, | 
| 785 |  |  |  |  |  |  | 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x840, 0x1861, 0x2802, 0x3823, | 
| 786 |  |  |  |  |  |  | 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, | 
| 787 |  |  |  |  |  |  | 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0xa50, 0x3a33, 0x2a12, | 
| 788 |  |  |  |  |  |  | 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, | 
| 789 |  |  |  |  |  |  | 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0xc60, 0x1c41, | 
| 790 |  |  |  |  |  |  | 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, | 
| 791 |  |  |  |  |  |  | 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0xe70, | 
| 792 |  |  |  |  |  |  | 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, | 
| 793 |  |  |  |  |  |  | 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, | 
| 794 |  |  |  |  |  |  | 0x1080, 0xa1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, | 
| 795 |  |  |  |  |  |  | 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, | 
| 796 |  |  |  |  |  |  | 0x2b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, | 
| 797 |  |  |  |  |  |  | 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, | 
| 798 |  |  |  |  |  |  | 0x34e2, 0x24c3, 0x14a0, 0x481, 0x7466, 0x6447, 0x5424, 0x4405, | 
| 799 |  |  |  |  |  |  | 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, | 
| 800 |  |  |  |  |  |  | 0x26d3, 0x36f2, 0x691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, | 
| 801 |  |  |  |  |  |  | 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, | 
| 802 |  |  |  |  |  |  | 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x8e1, 0x3882, 0x28a3, | 
| 803 |  |  |  |  |  |  | 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, | 
| 804 |  |  |  |  |  |  | 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0xaf1, 0x1ad0, 0x2ab3, 0x3a92, | 
| 805 |  |  |  |  |  |  | 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, | 
| 806 |  |  |  |  |  |  | 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0xcc1, | 
| 807 |  |  |  |  |  |  | 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, | 
| 808 |  |  |  |  |  |  | 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0xed1, 0x1ef0); | 
| 809 |  |  |  |  |  |  | } | 
| 810 |  |  |  |  |  |  |  | 
| 811 |  |  |  |  |  |  |  | 
| 812 |  |  |  |  |  |  | 1; | 
| 813 |  |  |  |  |  |  | __END__ |