#!/usr/bin/perl # charger_lib.pm revision 0.06b # useful subs: # new() - create a new object # write_eeprom($address,$data) - write eeprom $address with data $data # read_eeprom($address) - read eeprom $address, returns data # write_with_verify($address,$data) - write eeprom and verify write ok # turn_off_led() - guess # turn_on_led() - again, take a wild guess # stop_charger() - no matter what it's doing # parse_config_line($line,$line_number=1) # write_config() # solve - figure out charger calibration automatically, givin # two arrays # output is in y = m(x-b) + c form # write_eeprom() - write package charger_lib; use Device::SerialPort; use IO::Socket; sub new { my %self = {}; my $type = shift; my $baud = shift || 9600; my $ob; $ob = Device::SerialPort->new("/dev/ttyS1"); $ob->baudrate($baud); $ob->parity("none"); $ob->databits(8); $ob->stopbits(1); $ob->handshake("none"); $ob->rts_active(No); $ob->dtr_active(No); $ob->read_const_time(500); # 500 $ob->read_char_time(100); # 800 $self->{delay} = 0.02; $ob->debug($self->{debug}) if($self->{debug} & 0x20); $ob->write_settings; $self->{ob} = $ob; $self->{unit} = 1; init_netdata($self); bless($self, $type); return($self); } sub set_debug { my $self = shift; my $debug = shift || 15; my $facility = shift || 15; $self->{debug} = $debug; $self->{debug_facility} = $facility; } sub set_delay { my $self = shift; my $delay = shift; $self->{delay} = $delay; } sub open_bus { my $self = shift; my $unit = $self->{unit}; $self->{unit} = 0; $self->do_write_cmd(0x0); $self->{unit} = $unit; } sub vm_read_data { my $self = shift; my $socket = IO::Socket::INET->new(PeerAddr => 'localhost', PeerPort => 6556); if(!$socket) { print "Unable to connect to voltmeter IO thread. Is it running?\n"; } my $junk; $socket->recv($junk,255,0); print $socket "Junk\n"; my $return = <$socket>; $return =~ s/\r//; $return =~ s/\n//; $socket->close(); return($return); } sub init_netdata { my $self = shift; my $i; $self->{netdata} = {}; $self->{netdata}->{cmd}{"1"} = "Ping"; $self->{netdata}->{cmd}{"2"} = "Ping Reply"; $self->{netdata}->{cmd}{"3"} = "Prep Write"; $self->{netdata}->{cmd}{"4"} = "Prep Write OK"; $self->{netdata}->{cmd}{"5"} = "Unknown Command"; $self->{netdata}->{cmd}{"6"} = "Write"; $self->{netdata}->{cmd}{"7"} = "Write OK"; $self->{netdata}->{cmd}{"8"} = "Write Error"; $self->{netdata}->{cmd}{"9"} = "Read"; $self->{netdata}->{cmd}{"10"} = "Read OK"; $self->{netdata}->{cmd}{"102"} = "System Failure"; $self->{netdata}->{cmd}{"11"} = "Read raw sensor data"; $self->{netdata}->{cmd}{"12"} = "Setup charger"; $self->{netdata}->{cmd}{"13"} = "Engage charger"; $self->{netdata}->{cmd}{"14"} = "Engage charger error"; $self->{netdata}->{cmd}{"15"} = "Engage charger OK"; $self->{netdata}->{cmd}{"16"} = "Command charger parameters"; $self->{netdata}->{cmd}{"17"} = "Cmd setup phase (choose phase to engage)"; $self->{netdata}->{cmd}{"18"} = "Cmd engage phase"; # I skipped 13. For some reason it struck me as a funny thing to do - # I mean, would 13 in hex be lucky or unlucky? # Funny thought, eh? $self->{netdata}->{cmd}{"20"} = "Cmd engage phase OK"; $self->{netdata}->{cmd}{"21"} = "Cmd manual charge control"; $self->{netdata}->{cmd}{"22"} = "Cmd manual charge control OK"; $self->{netdata}->{cmd}{"23"} = "Kill charger"; $self->{netdata}->{cmd}{"24"} = "Turn On LED"; $self->{netdata}->{cmd}{"25"} = "Turn Off LED"; $self->{netdata}->{cmd}{"26"} = "Kill Charger Ok"; $self->{netdata}->{cmd}{"27"} = "Reply with sensor data"; $self->{netdata}->{cmd}{"28"} = "Massacre charger"; $self->{netdata}->{cmd}{"29"} = "Reboot charger"; $self->{netdata}->{cmd}{"30"} = "Read cooked values"; $self->{netdata}->{cmd}{"31"} = "Read cooked values reply"; $self->{netdata}->{cmd}{"223"} = "Read charger parameters reply"; $self->{netdata}->{cmd}{"240"} = "Cascading read cooked"; $self->{netdata}->{cmd}{"241"} = "Write block"; $self->{netdata}->{cmd}{"242"} = "Write block error"; $self->{netdata}->{cmd}{"243"} = "Write block okay"; $self->{netdata}->{cmd}{"244"} = "Read block"; $self->{netdata}->{cmd}{"245"} = "Read block ok"; $self->{netdata}->{cmd}{"246"} = "Execute loader"; $self->{netdata}->{cmd}{"247"} = "Execute loader error"; $self->{netdata}->{cmd}{"248"} = "Clear error"; $self->{netdata}->{cmd}{"249"} = "Set Baudrate"; $self->{netdata}->{cmd}{"250"} = "Cascading clear error"; $self->{netdata}->{cmd}{"251"} = "Cascading prep"; $self->{netdata}->{cmd}{"252"} = "Cascading Set Baudrate"; $self->{netdata}->{d0}{"3"} = "Write hi addr +0x80 for ee"; $self->{netdata}->{d1}{"3"} = "Write lo addr"; $self->{netdata}->{d2}{"3"} = "Write Data"; $self->{netdata}->{d0}{"6"} = "Write hi addr +0x80 for ee"; $self->{netdata}->{d1}{"6"} = "Write lo addr"; $self->{netdata}->{d2}{"6"} = "Write Data"; $self->{netdata}->{d0}{"7"} = "Write hi addr +0x80 for ee"; $self->{netdata}->{d1}{"7"} = "Write lo addr"; $self->{netdata}->{d2}{"7"} = "Write Data"; $self->{netdata}->{d0}{"9"} = "Read hi addr +0x80 for ee"; $self->{netdata}->{d1}{"9"} = "Read lo addr"; $self->{netdata}->{d0}{"10"} = "Returned Read Data"; $self->{netdata}->{d0}{"12"} = "limit bvolts (0 = none)"; $self->{netdata}->{d1}{"12"} = "limit bamps (0 = none)"; $self->{netdata}->{d2}{"12"} = "charge_time_lo"; $self->{netdata}->{d3}{"12"} = "charge_time_hi"; $self->{netdata}->{d4}{"12"} = "pwm_out"; $self->{netdata}->{d5}{"12"} = "charge_phase"; $self->{netdata}->{d0}{"13"} = "limit bvolts (0 = none)"; $self->{netdata}->{d1}{"13"} = "limit bamps (0 = none)"; $self->{netdata}->{d2}{"13"} = "charge_time_lo"; $self->{netdata}->{d3}{"13"} = "charge_time_hi"; $self->{netdata}->{d4}{"13"} = "pwm_out"; $self->{netdata}->{d5}{"13"} = "charge_phase"; $self->{netdata}->{d0}{"15"} = "limit bvolts (0 = none)"; $self->{netdata}->{d1}{"15"} = "limit bamps (0 = none)"; $self->{netdata}->{d2}{"15"} = "charge_time_lo"; $self->{netdata}->{d3}{"15"} = "charge_time_hi"; $self->{netdata}->{d4}{"15"} = "pwm_out"; $self->{netdata}->{d5}{"15"} = "charge_phase"; $self->{netdata}->{d0}{"21"} = "PWM * 2 (255 = 60%)"; $self->{netdata}->{d1}{"21"} = "Freq Hi Byte"; $self->{netdata}->{d2}{"21"} = "Freq Lo Byte"; $self->{netdata}->{d0}{"27"} = "RAW Analog input 0 / BatMod Temp"; $self->{netdata}->{d1}{"27"} = "RAW Analog input 1 / Battery Temp"; $self->{netdata}->{d2}{"27"} = "RAW Analog input 2 / Battery Volts"; $self->{netdata}->{d3}{"27"} = "RAW Analog input 3 / Battery Amps"; $self->{netdata}->{d4}{"27"} = "VFIN hi order byte"; $self->{netdata}->{d5}{"27"} = "VFIN low order byte"; $self->{netdata}->{d0}{"31"} = "current_bvolts"; $self->{netdata}->{d1}{"31"} = "current_bamps"; $self->{netdata}->{d2}{"31"} = "current_bwatts"; $self->{netdata}->{d3}{"31"} = "current_btemp"; $self->{netdata}->{d4}{"31"} = "current_bhv"; $self->{netdata}->{d5}{"31"} = "current_errno"; $self->{netdata}->{d0}{"223"} = "hold bvolts (0 = none)"; $self->{netdata}->{d1}{"223"} = "hold bamps (0 = none)"; $self->{netdata}->{d2}{"223"} = "charge_time_lo"; $self->{netdata}->{d3}{"223"} = "charge_time_hi"; $self->{netdata}->{d4}{"223"} = "pwm_out"; $self->{netdata}->{d5}{"223"} = "charge_phase"; $self->{netdata}->{addr}{"240"} = "Net addr plus1"; $self->{netdata}->{d0}{"240"} = "bvolts"; $self->{netdata}->{d1}{"240"} = "bamps"; $self->{netdata}->{d2}{"240"} = "btemp"; $self->{netdata}->{d3}{"240"} = "charge phase"; $self->{netdata}->{d4}{"240"} = "pwm out (255 = 60%)"; $self->{netdata}->{d5}{"240"} = "errno"; $self->{netdata}->{d0}{"241"} = "address 0xFF00"; $self->{netdata}->{d1}{"241"} = "address 0x00FF"; $self->{netdata}->{d2}{"241"} = "data byte 0 (addr)"; $self->{netdata}->{d3}{"241"} = "data byte 1 (addr+1)"; $self->{netdata}->{d4}{"241"} = "data byte 2 (addr+2)"; $self->{netdata}->{d5}{"241"} = "data byte 3 (addr+3)"; $self->{netdata}->{d0}{"244"} = "address 0xFF00"; $self->{netdata}->{d1}{"244"} = "address 0x00FF"; $self->{netdata}->{d0}{"245"} = "address 0xFF00"; $self->{netdata}->{d1}{"245"} = "address 0x00FF"; $self->{netdata}->{d2}{"245"} = "data byte 0 (addr)"; $self->{netdata}->{d3}{"245"} = "data byte 1 (addr+1)"; $self->{netdata}->{d4}{"245"} = "data byte 2 (addr+2)"; $self->{netdata}->{d5}{"245"} = "data byte 3 (addr+3)"; $self->{netdata}->{d0}{"246"} = "address 0xFF00"; $self->{netdata}->{d1}{"246"} = "address 0x00FF"; $self->{netdata}->{d2}{"246"} = "revision"; $self->{netdata}->{d0}{"247"} = "address 0xFF00"; $self->{netdata}->{d1}{"247"} = "address 0x00FF"; $self->{netdata}->{d2}{"247"} = "revision sent"; $self->{netdata}->{d2}{"247"} = "revision current"; $self->{netdata}->{d2}{"247"} = "software lock (121 to exec)"; $self->{netdata}->{d0}{"249"} = "bbaud"; $self->{netdata}->{d0}{"251"} = "address 0xFF00"; $self->{netdata}->{d1}{"251"} = "address 0x00FF"; $self->{netdata}->{d2}{"251"} = "data"; $self->{netdata}->{d0}{"252"} = "bbaud"; } sub init_memdata { my $s = shift; $s->scb("MY_NET_ADDR",8191); $s->scb("LAST_ERROR",8190); $s->scb("LAST_MESSAGE_SIZE",8189); $s->scb("NUM_SAMPLES",8188); $s->scb("NUM_VF",8187); $s->scb("DLY_VF",8186); $s->scb("DIV_VF",8185); $s->scb("DL_LOCK",8184); $s->sci("PWM_FREQ",8182); $s->scb("PWM_OFFSET",8181); $s->scb("AVG_NUM",8180); $s->scb("CONFIG_LOCKED",8179); $s->scb("PWM_SEED",8178); $s->scab("LAST_ERRNO",8176,2); $s->scb("SLEEP_SECONDS",8170); $s->scb("TIMEOUT_MINUTES",8169); $s->scb("CHARGE_INHIBIT_ERRNO_MASK",8168); $s->scb("SCALE_BACK_CHG_TEMP",8167); $s->scb("PHASES_ENABLED",8166); $s->sct("MAX_BATT_TEMP",8165); $s->scb("MAX_CHG_TEMP",8164); $s->scav("PHASE_CHARGE_VOLTS_LIMIT_LO",7976,8); $s->scav("PHASE_CHARGE_VOLTS_LIMIT_HI",7984,8); $s->scaa("PHASE_CHARGE_AMPS_LIMIT_LO",7992,8); $s->scaa("PHASE_CHARGE_AMPS_LIMIT_HI",8000,8); $s->scat("PHASE_CHARGE_TEMP_LIMIT_LO",8008,8); $s->scat("PHASE_CHARGE_TEMP_LIMIT_HI",8016,8); $s->sci("VOLTS_M",8144); $s->sci("VOLTS_C",8146); $s->sci("VOLTS_B",8148); $s->sci("AMPS_M",8150); $s->sci("AMPS_C",8152); $s->sci("AMPS_B",8154); $s->sci("VOLTS_HV_M",8112); $s->sci("VOLTS_HV_C",8114); $s->sci("VOLTS_HV_B",8116); $s->scab("TEMP_LOOKUP",7680,256); $s->scai("TEMP_COMP",7168,256); $s->schv("TURN_ON_BALANCE_HV",8118); $s->schv("TURN_ON_CHARGE_HV",8119); $s->schv("TURN_OFF_CHARGE_HV",8122); $s->schv("TURN_OFF_BALANCE_HV",8032); $s->scp("TURN_ON_BALANCE_PHASE",8120); $s->scp("TURN_ON_CHARGE_PHASE",8121); $s->scp("BALANCE_NUM_BATTERIES_PHASE",8105); $s->scv("CLEAR_CHARGED_THRESH",8123); $s->scb("DIDT_WINDOW",8124); $s->scb("DIDT_UP_LIMIT",8125); $s->scb("DIDT_CLIMB",8126); $s->scb("BEEP_ON_NET_TIMEOUT",8127); $s->scbaud("BAUD",8107); $s->scb("BAUD_LOCKED",8106); $s->scv("MIN_VOLTS",8111); $s->sct("MIN_BATT_TEMP",8110); $s->scb("MIN_CHG_TEMP",8109); $s->scb("BALANCE_NUM_BATTERIES",8104); $s->scamp("MAX_AMPS",8163); $s->scv("MAX_VOLTS",8162); $s->scw("MAX_WATTS",8160); $s->scav("PHASE_CHARGE_VOLTS",7936,8); $s->scaa("PHASE_CHARGE_AMPS",7944,8); $s->scab("PHASE_CHARGE_FLAGS",7952,8); $s->scab("PHASE_CHARGE_TIME_LO",7960,8); $s->scab("PHASE_CHARGE_TIME_HI",7968,8); $s->scab("PHASE_CHARGE_TIME",7960,8); $s->{cfg}{initilized} = 1; } sub scbaud { # internal function my $self = shift; my $name = shift; my $addr = shift; $self->scb($name,$addr); $self->{cfg}{$name}{conversion} = 8; $self->{cfg}{$addr}{conversion} = 8; } sub scp { # internal function my $self = shift; my $name = shift; my $addr = shift; $self->scb($name,$addr); $self->{cfg}{$name}{conversion} = 7; $self->{cfg}{$addr}{conversion} = 7; } sub schv { # internal function my $self = shift; my $name = shift; my $addr = shift; $self->scb($name,$addr); $self->{cfg}{$name}{conversion} = 6; $self->{cfg}{$addr}{conversion} = 6; } sub scat { # internal function my $self = shift; my $name = shift; my $addr = shift; my $len = shift; $self->scab($name,$addr,$len); $self->{cfg}{$name}{conversion} = 5; $self->{cfg}{$addr}{conversion} = 5; } sub scaa { # internal function my $self = shift; my $name = shift; my $addr = shift; my $len = shift; $self->scab($name,$addr,$len); $self->{cfg}{$name}{conversion} = 3; $self->{cfg}{$addr}{conversion} = 3; } sub scav { # internal function my $self = shift; my $name = shift; my $addr = shift; my $len = shift; $self->scab($name,$addr,$len); $self->{cfg}{$name}{conversion} = 2; $self->{cfg}{$addr}{conversion} = 2; } sub scamp { # internal function my $self = shift; my $name = shift; my $addr = shift; $self->scb($name,$addr); $self->{cfg}{$name}{conversion} = 3; $self->{cfg}{$addr}{conversion} = 3; } sub scai { # internal function my $self = shift; my $name = shift; my $addr = shift; my $len = shift; $self->{cfg}{$name}{addr} = $addr; $self->{cfg}{$addr}{name} = $name; $self->{cfg}{$name}{size} = $len; $self->{cfg}{$addr}{size} = $len; $self->{cfg}{$name}{type} = 2; $self->{cfg}{$addr}{type} = 2; } sub sct { my $self = shift; my $name = shift; my $addr = shift; $self->scb($name,$addr); $self->{cfg}{$name}{conversion} = 5; $self->{cfg}{$addr}{conversion} = 5; } sub scw { my $self = shift; my $name = shift; my $addr = shift; $self->sci($name,$addr); $self->{cfg}{$name}{conversion} = 1; $self->{cfg}{$addr}{conversion} = 1; } sub scv { my $self = shift; my $name = shift; my $addr = shift; $self->scb($name,$addr); $self->{cfg}{$name}{conversion} = 2; $self->{cfg}{$addr}{conversion} = 2; } sub scb { my $self = shift; my $name = shift; my $addr = shift; $self->{cfg}{$name}{addr} = $addr; $self->{cfg}{$addr}{name} = $name; $self->{cfg}{$name}{size} = 1; $self->{cfg}{$addr}{size} = 1; $self->{cfg}{$name}{type} = 1; $self->{cfg}{$addr}{type} = 1; } sub sci { my $self = shift; my $name = shift; my $addr = shift; $self->{cfg}{$name}{addr} = $addr; $self->{cfg}{$addr}{name} = $name; $self->{cfg}{$name}{type} = 2; $self->{cfg}{$addr}{type} = 2; $self->{cfg}{$name}{size} = 1; $self->{cfg}{$addr}{size} = 1; } sub scab { # array of bytes my $self = shift; my $name = shift; my $addr = shift; my $size = shift; $self->{cfg}{$name}{addr} = $addr; $self->{cfg}{$addr}{name} = $name; $self->{cfg}{$name}{size} = $size; $self->{cfg}{$addr}{size} = $size; $self->{cfg}{$name}{type} = 1; $self->{cfg}{$addr}{type} = 1; } sub parse_config_line { my $self = shift; my $line = shift; my $line_number = shift || 1; my $array = 0; my ($real,$comment) = split(/;/,$line); my ($tok,$slice,$size,$negative,$data_lo,$data_hi); my ($t,$addr8,$data,$tok_time); if(! defined $self->{cfg}{initilized}) { $self->init_memdata(); } if(defined $self->{write}{data_start}) { $data_start = $self->{write}{data_start}; } else { $data_start = 8192; } $self->log("($line_number) real: [$real] line: [$line]",1,1); my ($token,$value) = $real =~ /^\s*(.*)=(.*)\s*$/; return 0 if ! $token; $value =~ s/\s+//; $self->log("($line_number) token: [$token] value: [$value]",1,1); $token = uc $token; if($token =~ /\./) { ($tok,$slice) = $token =~ /(\w*)\.(\d*)/; $self->log("($line_number) token: [$tok] array slice: [$slice]",1,2); } else { $tok = undef; } $slice = $slice - 1; $tok_time = 0; $tok_time = 1 if ($tok eq "PHASE_CHARGE_TIME"); # this is such a hack if(! defined $self->{cfg}{$token}{addr}) { if(! defined $self->{cfg}{$tok}{addr}) { $self->log("($line_number) unknown token [$token]/[$tok]",1,0); return(1); } } # okay, we've lexically parsed out the line # now we have to check and make sure the arguments we're # given make sense if($tok) { $array = 1; $token = $tok; if(($size = $self->{cfg}{$tok}{size}) < $slice) { $size = $self->{cfg}{$tok}{size}; $self->log("($line_number) Can't update Array $tok slice $slice (array is $size sized)",1,0); return(2); } if($slice < 0) { $self->log("($line_number) Can't update array $tok slice $slice (slices are no longer indexed from zero)",1,0); return(2); } } else { $array = 0; $slice = 0; } if(($c = $self->{cfg}{$token}{conversion})) { if($c == 1) { # do nothing # this is watts $real = $value } elsif($c ==2) { # this is volts $real = $self->volt_to_voltb($value); } elsif($c == 3) { # this is amps $real = $self->amp_to_ampb($value); } elsif($c == 5) { # this is temp $real = $self->temp_to_tempb($value); } elsif($c == 6) { # this is hv $real = $self->hv_to_hvb($value); } elsif($c == 7) { # this is phase $real = $value; if($value > 8) { $self->log("($line_number) Bad phase $value (max 8)",1,0); return(3); } } elsif($c == 8) { # this is baud; if(!(($real = $self->baud_to_baudb($value)) != -1)) { $self->log("($line_number) Bad baud: $value. Allowed are 1200/2400/4800/9600",1,0); return(3); } } else { die "$line_number: Bad conversion: $c"; } } else { $real = $value; } $t = $self->{cfg}{$token}{type}; if($t == 1) { # byte if(!$tok_time && (($real < 0) || ($real > 255))) { $self->log("($line_number): $token - byte can be from 0 to 255, not $real",1,0); return(4); } } elsif($t == 2) { # int if(($real > 32768) || ($real < -32767)) { $self->log("($line_number): $token - int can be from -32767 to 32768, not $real",1,0); } } else { die "$line_number: (token $token) bad type $t"; } if($self->{cfg}{$token}{type} == 2) { $typelen = 2; } else { $typelen = 1; } $addr = $self->{cfg}{$token}{addr} + ($slice * $typelen); if($addr < $data_start) { $data_start = $addr; } $self->log("($line_number): $token=$value write $real @ $addr",1,8); if($tok_time) { # dirty hack to make time setting work $addr8 = $addr+8; $self->{write}{$addr8}{data} = $real % 256; $data = $self->{write}{$addr8}{data}; $real = $real & 0x00FF; $self->log("($line_number): $token=$value write $real @ $addr",1,8); $self->log("($line_number): $token=$value write $data @ $addr8",1,8); } if($t == 1) { $self->{write}{$addr}{data} = $real; } else { $negative = 0; if($real < 0) { $real = $real * -1; $negative = 1; } $data_lo = int($real / 256); $data_hi = $real % 256; if($negative) { $data_hi = 0xFF - $data_hi; $data_lo = 0xFF - $data_lo; } $write{$addr}{data} = $data_hi; $addr++; $write{$addr}{data} = $data_lo; } $self->{write}{data_start} = $data_start; return(0); } sub clear_write { my $self = shift; foreach $key (keys %{$self->{write}}) { delete $self->{write}{$key}; } return(0); } sub write_config { my $self = shift; my $start = $self->{write}{data_start}; my $sl; my $data; my ($i, $j, $chunk, $bytes, $single,$errors) = (0,0,0,0,0,0); my $got_it; if(defined $self->{write}{8179}{data}) { $sl = $self->{write}{8179}{data}; } else { $sl = $self->read_eeprom(8179); } #$errors += $self->write_eeprom(8179,55); while($self->write_eeprom(8179,55)) {}; for($i=$start;$i<8192;$i++) { # for each $write, determine if we have three above this one # if we do, write all four using a block write command # and then incriment $i by four next if $i == 8179; $got_it = 0; for($j=0;$j<4;$j++) { next if (($i+$j) == 8179); # avoid writing software lock $got_it++ if(defined $self->{write}{($i+$j)}{data}); } if($got_it > 3) { $chunk++; $self->log_write_data($i); $self->log_write_data(($i+1)); $self->log_write_data(($i+2)); $self->log_write_data(($i+3)); $self->log("$i: can output chunk",2,8); $data0 = $self->{write}{$i}{data}; $data1 = $self->{write}{($i+1)}{data}; $data2 = $self->{write}{($i+2)}{data}; $data3 = $self->{write}{($i+3)}{data}; # if(!($errors += $self->write_eeprom_chunk($i,$data0,$data1,$data2,$data3))) { while($self->write_eeprom_chunk($i,$data0,$data1,$data2,$data3)) {}; if(1) { $self->{write}{$i}{written} = 1; $self->{write}{($i+1)}{written} = 1; $self->{write}{($i+2)}{written} = 1; $self->{write}{($i+3)}{written} = 1; } else { $self->log("$i: chunk output failed",2,1); $self->{write}{$i}{written} = 0; $self->{write}{($i+1)}{written} = 0; $self->{write}{($i+2)}{written} = 0; $self->{write}{($i+3)}{written} = 0; } $bytes+=4; $i+=3; } elsif(defined $self->{write}{$i}{data}) { $self->log("$i: not big enough for chunk ($got_it)",2,8); $self->log_write_data($i); $bytes++; $single++; $data = $self->{write}{$i}{data}; # if(!($errors += $self->write_eeprom($i,$data))) { while($self->write_eeprom($i,$data)) {} if(1) { $self->{write}{$i}{written} = 1; } else { $self->log("$i: output failed",2,1); $self->{write}{$i}{written} = 0; } } else { # no data - do nothing } } while($self->write_eeprom(8179,$sl)) {}; $self->log("Written: $bytes bytes ($chunk chunks $single singles) wtih $errors errors",2,1); return($errors); } sub log_write_data { # internal function my $self = shift; my $addr = shift; my $purpose = $self->get_addr_name($addr); my $size = $self->{cfg}{$addr}{size}; my $conversion = $self->{cfg}{$addr}{conversion}; my $type = $self->{cfg}{$addr}{type}; my $data = $self->{write}{$addr}{data}; $self->log("$addr: $purpose -> $data",2,2); } sub get_addr_name { # internal function my $self = shift; my $addr = shift; return $self->{cfg}{$addr}{name}; } sub log { my $self = shift; my $msg = shift; my $facility = shift; my $level = shift; $facility_name = $self->get_facility_name($facility); if((($self->{debug_facility} & $facility) && ($self->{debug} & $level)) || ($level == 0)) { if($self->{log}) { $self->{log}->write("$facility:[$level]\t$msg"); } else { print "$facility:[$level]\t$msg\n"; } } return(0); } sub get_facility_name { my $self = shift; my $f = shift; if($f == 1) { return("config_parse_line"); } elsif($f == 2) { return("config_write_data"); } elsif($f == 4) { return("upload"); } else { return("unknown"); } } sub clear_errno { my $self = shift; my $bits = shift; if(!$bits) { $self->show_cooked(); $bits = $self->{read_d5}; } $self->do_write_cmd(0xF8,$bits); $self->do_read(8); return(0); } sub volt_to_voltb { my $self = shift; my $volt = shift; return 0 if($volt == 0); return(voltk_to_voltb($self,($volt * 1000))); } sub baudb_to_baud { my $self = shift; my $baudb = shift; if($baudb == 6) { return(75); } elsif($baudb == 5) { return(150); } elsif($baudb == 4) { return(300); } elsif($baudb == 3) { return(600); } elsif($baudb == 2) { return(1200); } elsif($baudb == 1) { return(2400); } elsif($baudb == 0) { return(4800); } elsif($baudb == 8) { return(9600); } else { return(0); } } sub baud_to_baudb { my $self = shift; my $baud = shift; if($baud == 9600) { return(8); } elsif($baud == 4800) { return(0); } elsif($baud == 2400) { return(1); } elsif($baud == 1200) { return(2); } elsif($baud == 600) { return(3); } elsif($baud == 300) { return(4); } elsif($baud == 150) { return(5); } elsif($baud == 75) { return(6); } else { return(-1); } } sub tempb_to_temp { my $self = shift; my $temp = shift; # 0 is -40 # 40 is 0 # 252 is 212 return($temp - 40); } sub temp_to_tempb { my $self = shift; my $temp = shift; return(0) if($temp == 0); return($temp + 40); } sub hvb_to_hv { my $self = shift; my $hv = shift; return($hv * 2); } sub hv_to_hvb { my $self = shift; my $hv = shift; return(int($hv / 2)); } sub voltk_to_voltb { my $self = shift; my $voltk = shift; # 0 = 5.12V # 255 = 17.87V return(0) if($voltk < 512); return(255) if($voltk > 17870); return(int(($voltk - 5120)/50)); } sub voltb_to_voltk { my $self = shift; my $voltb = shift; return(5120 + (50*$voltb)); } sub voltb_to_volt { my $self = shift; my $voltb = shift; return((voltb_to_voltk($self,$voltb) / 1000)); } sub voltk_to_volt { my $self = shift; my $voltk = shift; return(($voltk / 1000)); } sub ampb_to_amp { my $self = shift; my $voltb = shift; my $ret = ($voltb / 10); return($ret); } sub amp_to_ampb { my $self = shift; my $amp = shift; return(int($amp * 10)); } sub charge { my $self = shift; my $amps = shift; my $volts = shift; my $chgtime = shift; my $flags = shift || 0; $ampsb = $self->amp_to_ampb($amps); $voltsb = $self->volt_to_voltb($volts); $chgtime_hi = ($chtime & 0xFF00) / 256; $chgtime_lo = $chgtime & 0x00FF; $self->do_write_cmd(0x0C,$voltsb,$ampsb,$chgtime_lo,$chgtime_hi,$flags); return -1 if($self->do_read(8) < 8); $self->do_write_cmd(0x0D,$voltsb,$ampsb,$chgtime_lo,$chgtime_hi,$flags); return -1 if($self->do_read(8) < 8); return -2 if($self->{read_cmd} != 0x0F); return 0; } sub show_raw { $self->show_inputs(); } sub show_inputs { my $self = shift; $self->do_write_cmd(0x0B); return -1 if $self->do_read < 8; # this is all it really takes - make sure you # have read debugging engaged } sub show_charge_state { my $self = shift; $self->do_write_cmd(0x10); return -1 if $self->do_read < 8; } sub show_cooked { my $self = shift; $self->do_write_cmd(0x1E); return -1 if $self->do_read < 8; } sub force_on_charger { my $self = shift; my $pwm = shift; my $freq = shift; $self->do_write_cmd(0x15, $pwm, int($freq / 256), int($freq & 255)); return -1 if $self->do_read() < 8; return -2 if($self->{read_cmd} != 0x16); return 0; } sub turn_on_led { my $self = shift; $self->do_write_cmd(0x18,42); return -1 if $self->do_read() < 8; return -2 if ($self->{read_cmd} != 0x0A); return 0; } sub reboot { my $self = shift; $self->do_write_cmd(0x1d); return(0); # really no way to know if this worked or not } sub turn_off_led { my $self = shift; $self->do_write_cmd(0x19,42); return -1 if $self->do_read() < 8; return -2 if ($self->{read_cmd} != 0x0A); return 0; } sub stop_charger { my $self = shift; $self->do_write_cmd(0x17); return -1 if $self->do_read() < 8; return -2 if ($self->{read_cmd} != 0x1A); return 0; } sub write_with_verify { my $self = shift; my $addr = shift; my $data = shift; $self->log("write_with_verify($addr,$data)",4,4); $self->write_eeprom($addr,$data); return -1 if ($self->read_eeprom($addr) != $data); return 0; } sub set_unit { my $self = shift; $self->{unit} = shift || 1; } sub set_baud { my $self = shift; $self->{baud} = shift || 9600; $self->{ob}->baudrate($self->{baud}); $self->{ob}->write_settings(); } sub ping { my $self = shift; my $unit = $self->{unit}; my $cmd = shift || 1; my $string, $len, $data, $foo; $self->log("ping($unit,$cmd)",4,4); $self->do_write_cmd($cmd); return(0) if($self->do_read() < 8); if(($foo = $self->{read_cmd}) != 2) { print "Error: ping read unexpected result $foo\n" if($self{debug} & 4); return -2; } return $self->{read_addr}; } sub read_ram { my $self = shift; my $addr = shift; my $addr_lo, $addr_hi; my $string, $len, $data, $foo; $addr_hi = ($addr & 0xFF00) / 256; $addr_lo = $addr & 0x00FF; $self->log("read_ram($addr) [$addrhi/$addr_lo]",4,4); $self->do_write_cmd(0x09,$addr_hi,$addr_lo); return -1 if ($self->do_read() < 8); return($self->{read_d0}); } sub write_eeprom_chunk { my $self = shift; my $addr = shift; my $data0 = shift; my $data1 = shift; my $data2 = shift; my $data3 = shift; my ($addr_hi, $addr_lo); $addr_hi = ($addr & 0xFF00) / 256; $addr_lo = $addr & 0x00FF; $self->do_write_cmd(0x03,$addr_hi,$addr_lo); return -1 if($self->do_read() < 8); $self->do_write_cmd(0xF1,$addr_hi,$addr_lo,$data0,$data1,$data2,$data3); return -1 if($self->do_read() < 8); return -2 if($self->{read_cmd} != 0xF3); return 0; } sub write_eeprom_int_verify { my $self = shift; my $addr = shift; my $data = shift; my $negative = 0; my ($data_lo, $data_hi); my $ret; if($data < 0) { $data = $data * -1; $negative = 1; } $data_lo = int($data / 256); $data_hi = $data % 256; if($negative) { $data_hi = 0xFF - $data_hi; $data_lo = 0xFF - $data_lo; } $ret = $self->write_with_verify($addr+1, $data_lo); $ret =+ $self->write_with_verify($addr,$data_hi); return($ret); } sub write_eeprom_int { my $self = shift; my $addr = shift; my $data = shift; my $negative = 0; my ($data_lo, $data_hi); my $ret; if($data < 0) { $data = $data * -1; $negative = 1; } $data_lo = int($data / 256); $data_hi = $data % 256; if($negative) { $data_hi = 0xFF - $data_hi; $data_lo = 0xFF - $data_lo; } $ret = $self->write_eeprom($addr+1, $data_lo); $ret =+ $self->write_eeprom($addr,$data_hi); return($ret); } sub read_eeprom_int { my $self = shift; my $addr = shift; my ($data_lo, $data_hi); $data_lo = $self->read_eeprom($addr+1); return -1 if($data_lo < 0); $data_hi = $self->read_eeprom($addr); return -1 if($data_hi < 0); if($data_lo & 0x80) { $res = (((((0xFF - $data_lo) * 256) + (0xFF - $data_hi)) * -1) ); } else { $res = (($data_lo * 256) + $data_hi); } return($res); } sub write_eeprom { my $self = shift; my $addr = shift; my $data = shift; my ($addr_lo, $addr_hi, $new_addr); $addr_hi = ($addr & 0xFF00) / 256; $addr_lo = $addr & 0x00FF; $new_addr = (($addr_hi + 0x80) * 256) + $addr_lo; $ret = $self->write_ram($new_addr, $data); select(undef,undef,undef,0.10); # recovery time return($ret); } sub read_eeprom { my $self = shift; my $addr = shift; my $addr_lo, $addr_hi, $new_addr; $addr_hi = ($addr & 0xFF00) / 256; $addr_lo = $addr & 0x00FF; $new_addr = (($addr_hi + 0x80) * 256) + $addr_lo; return($self->read_ram($new_addr)); } sub write_ram { my $self = shift; my $addr = shift; my $data = shift; my $addr_lo, $addr_hi, $string; $addr_hi = ($addr & 0xFF00) / 256; $addr_lo = $addr & 0x00FF; $self->log("write_ram($addr,$data) [$addr_lo/$addr_hi]",4,4); # prepare a prep cmd $self->do_write_cmd(0x03,$addr_hi,$addr_lo,$data); return -1 if($self->do_read() < 8); $self->print_data($self->{data},"write_ram:",4,2); return -2 if($self->{read_cmd} != 4); $self->do_write_cmd(0x06,$addr_hi,$addr_lo,$data); $self->print_data($self->{write_data},"write_ram_return:",4,2); return -2 if($self->do_read() < 8); if(($foo = $self->{read_cmd}) != 0x07) { $self->log("write_ram($addr,$data) error - expected 0x07 got back $foo",4,1); return -3; } return(0); } sub do_read { my $self = shift; my $count = shift || 8; my($len,$data) = $self->{ob}->read(8); my @array; $self->{data} = $data; $self->{len} = $len; $self->print_data($self->{data},"read<",4,1); @array = split(//,$data); $self->{"read_addr"} = ord(@array[0]); $self->{"read_cmd"} = ord(@array[1]); $self->{"read_d0"} = ord(@array[2]); $self->{"read_d1"} = ord(@array[3]); $self->{"read_d2"} = ord(@array[4]); $self->{"read_d3"} = ord(@array[5]); $self->{"read_d4"} = ord(@array[6]); $self->{"read_d5"} = ord(@array[7]); return($len); } sub print_data { my $self = shift; my $string = shift; my $prefix = shift || ""; my $facility = shift || 4; my $level = shift || 1; my @letters = split(//,$string); my $l = 0; my $o; my $p; my %purposes; my $wc; my $d0; my $buf; my $errno; $purpose{"0"} = "addr"; $purpose{"1"} = "cmd"; $purpose{"2"} = "d0"; $purpose{"3"} = "d1"; $purpose{"4"} = "d2"; $purpose{"5"} = "d3"; $purpose{"6"} = "d4"; $purpose{"7"} = "d5"; $self->{"written_addr"} = ord($letters[0]); $self->{"written_cmd"} = ord($letters[1]); $self->{"written_d0"} = ord($letters[2]); $self->{"written_d1"} = ord($letters[3]); $self->{"written_d2"} = ord($letters[4]); $self->{"written_d3"} = ord($letters[5]); $self->{"written_d4"} = ord($letters[6]); $self->{"written_d5"} = ord($letters[7]); foreach $letterz (@letters) { $o = ord($letterz); $p = $purpose{$l}; $wc = ord($letters[1]); #cmd $dz = ord($letters[2]); #d0 if(defined $self->{netdata}->{$p}{$wc}) { $n = $self->{netdata}->{$p}{$self->{"written_cmd"}}; if($n =~ /bamps/) { $n .= "\t{"; $buf = "written_" . $p; $n .= $self->ampb_to_amp($self->{$buf}); $n .= "A }"; } if($n =~ /bvolts/) { $n .= "\t{"; $buf = "written_" . $p; $n .= $self->voltb_to_volt($self->{$buf}); $n .= "V }"; } if($n =~ /btemp/) { $n .= "\t{"; $buf = "written_" . $p; $n .= $self->tempb_to_temp($self->{$buf}); $n .= "F }"; } if($n =~ /bwatts/) { $n .= "\t{"; $buf = "written_" . $p; $n .= $self->{$buf}; $n .= "W }"; } if($n =~ /hv/) { $n .= "\t{"; $buf = "written_" . $p; $n .= $self->hvb_to_hv($self->{$buf}); $n .= "V }"; } if($n =~ /bbaud/) { $n .= "\t{"; $buf .= "written_" . $p; $n .= $self->baud_to_baudb($self->{$buf}); $n .= " baud}"; } if($n =~ /errno/) { $n .= "\t("; $buf = "written_" . $p; $errno = $self->{$buf}; $n .= "OVRTMP " if($errno & 1); $n .= "UNDTMP " if($errno & 2); $n .= "UNCMD! " if($errno & 4); $n .= "OVRVLT " if($errno & 8); $n .= "UNDVLT " if($errno & 16); $n .= "BOVTMP " if($errno & 32); $n .= "BUNTMP " if($errno & 64); $n .= "CRASH! " if($errno & 128); $n .= ")"; } } else { $n = ""; } $output = sprintf("%s%x:\t%x\t(%d)\t[%s\t] %s",$prefix,$l,$o,$o,$p,$n); $self->log($output,$facility,$level); $l++; } } sub do_write_cmd { my $self = shift; print "do_write_cmd\n" if ($self->{debug} & 16); my $string = chr($self->{unit}) . chr(shift) . chr(shift || 0) . chr(shift || 0) . chr(shift || 0) . chr(shift || 0) . chr(shift || 0) . chr(shift || 0); $self->do_write($string); } sub do_cmd { my $self = shift; my $addr = $self->{unit}; my $cmd = shift; my $d0 = shift || 0; my $d1 = shift || 0; my $d2 = shift || 0; my $d3 = shift || 0; my $d4 = shift || 0; my $d5 = shift || 0; } sub do_write { my $self = shift; my $string = shift; my @letters = split(//,$string); my $count = 0; my $ord; my $letter; my $purpose; my %purposes; $self->{write_data} = $string; if ($self->{debug} & 2) { $self->print_data($self->{write_data},"WRTE:",4,1); } $purpose{"0"} = "addr"; $purpose{"1"} = "cmd"; $purpose{"2"} = "d0"; $purpose{"3"} = "d1"; $purpose{"4"} = "d2"; $purpose{"5"} = "d3"; $purpose{"6"} = "d4"; $purpose{"7"} = "d5"; foreach $letter (@letters) { $ord = ord($letter); $purpose = $purposes{$count}; $self->log("$count: sending $ord [$purpose]",4,16); $count++; $self->{ob}->write($letter); select(undef,undef,undef,$self->{delay}); } } sub upload { my $self = shift; my $filename = shift; my $draw = shift; my $cmd; my $cnt = 0; my ($a, $b); $cmd = "D"; $self->{ob}->write($cmd); my ($len, $data) = $self->{ob}->read(1); return -2 if ($data ne "D"); open(FILE,"<$filename") || return -1; $self->log("upload: Uploading $filename",4,1); print "Uploading $filename: " if($draw); while($in = ) { foreach $l (split(//,$in)) { $cnt++; } } close(FILE); $self->log("upload: Length: $cnt",4,2); $cmd = "$cnt\n"; $self->{ob}->write($cmd); ($len, $data) = $self->{ob}->read(7); ($data) = $data =~ /^\D*(\d*)\D*$/; print "r: $data\n"; open(FILE,"<$filename") || return -1; my $c = 0; my $step = int($cnt / 40); my $errors = 0; while($in = ) { foreach $l (split(//,$in)) { $self->{ob}->write($l); ($len, $data) = $self->{ob}->read(1); # in an ideal world we'd do some kind # of error checking here! ;-) $a = ord($l); $c++; $b = ord($data); print "*" if($draw && !($c % $step)); $errors++ if($a != $b); $self->log("upload: sent $a got back $b",4,8); } } $self->log("Upload: completed with $errors errors",4,1); print "\nUploaded $filename ($cnt bytes) with $errors errors\n" if($draw); close(FILE); return(0); } sub solve { my $self = shift; my %params = @_; # x_array = \@array, # y_array = \@array $href = $self->find_best_mcb("m_res" => 100, "c_res" => 1, "b_res" => 10, "m_start" => 1, "m_stop" => 1000, "c_start" => 1, "c_stop" => 2, "b_start" => 1, "b_stop" => 1000, "x_array" => $params{x_array}, "y_array" => $params{y_array}); my $m_start = $href->{best_m} - 100; my $m_stop = $href->{best_m} + 100; $href = $self->find_best_mcb("m_res" => 1, "c_res" => 1, "b_res" => 1, "m_start" => $m_start, "m_stop" => $m_stop, "c_start" => 1, "c_stop" => 2, "b_start" => 1, "b_stop" => 255, "x_array" => $params{x_array}, "y_array" => $params{y_array}); $m_start = $href->{best_m} - 5; $m_stop = $href->{best_m} + 5; my $b_start = $href->{best_b} - 5; my $b_stop = $href->{best_b} + 5; $href = $self->find_best_mcb("m_res" => 1, "c_res" => 1, "b_res" => 1, "m_start" => $m_start, "m_stop" => $m_stop, "c_start" => 1, "c_stop" => 1000, "b_start" => $b_start, "b_stop" => $b_stop, "x_array" => $params{x_array}, "y_array" => $params{y_array}); return($href); } sub find_best_mcb { my $self = shift; my %params = @_; #options # x_array => \@array, # y_array => \@array, # m_res => 4, # c_res => 4, # b_res => 4, # m_start => 0, # m_stop => 1000, # c_start => 0, # c_stop => 1000, # b_start => 0, # b_stop => 1000 #results are unpredictable if no seed values are givin #return value contains these options: # best_error # best_m # best_c # best_b my $besterror = 100000000; # seed besterror with some improbably large number my ($m, $c, $b, $best_m, $best_c, $best_b); my ($m_start, $m_stop, $m_res); my ($b_start, $b_stop, $b_res); my ($c_start, $c_stop, $c_res); my ($error, $count, $max_count, $testresult); $m_start = $params{"m_start"} || 1; $m_stop = $params{"m_stop"} || 1000; $m_res = $params{"m_res"} || 1; $b_start = $params{"b_start"} || 1; $b_stop = $params{"b_stop"} || 1000; $b_res = $params{"b_res"} || 1; $c_start = $params{"c_start"} || 1; $c_stop = $params{"c_stop"} || 1000; $c_res = $params{"c_res"} || 1; $max_count = @{$params{"x_array"}}; for($m=$m_start;$m<$m_stop;$m+=$m_res) { for($b=$b_start;$b<$b_stop;$b+=$b_res) { for($c=$c_start;$c<$c_stop;$c+=$c_res) { $error = 0; for($count = 0;$count<$max_count;$count++) { $testresult = ((@{$params{"x_array"}}[$count] - $b) * $m) + $c; $error = $error + abs(@{$params{"y_array"}}[$count] - $testresult); } if(abs($error) < $besterror) { $best_b = $b; $best_m = $m; $best_c = $c; $besterror = $error; } } } } $ret = {}; $ret{"best_b"} = $best_b; $ret{"best_c"} = $best_c; $ret{"best_m"} = $best_m; $ret{"x_array"} = $params{"x_array"}; $ret{"y_array"} = $params{"y_array"}; $ret{"best_error"} = $besterror; $ret{"max_count"} = $max_count; return(\%ret); } sub print_mcb_error { my $self = shift; my %params = @_; my ($count, $max_count, $r, $c, $a, $e); my ($best_m, $best_c, $best_b); $best_m = $params{'best_m'}; $best_c = $params{'best_c'}; $best_b = $params{'best_b'}; $max_count = $params{'max_count'}; for($count=0;$count<$max_count;$count++) { $r = @{$params{x_array}}[$count]; $c = ((@{$params{x_array}}[$count] - $best_b) * $best_m) + $best_c; $a = @{$params{y_array}}[$count]; $e = $a - $c; open(LOG,">>calibration_error.log"); # todo: make this more OO $self->log("Sensor value: $r Calculated value: $c Actual: $a Error: $e",8,1); print LOG "sensor value: $r Calculated value: $c Actual: $a Error: $e\n"; close(LOG); } } 1;