#!/usr/bin/perl # charger_lib.pm revision 0.01b # useful subs: # new($debug (opt.)) - create a new object, optionally with debuglevel $debug) # 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 # 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; sub new { my %self = {}; my $type = shift; my $debug = shift; my $ob; $ob = Device::SerialPort->new("/dev/ttyS1"); $ob->baudrate(9600); $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); $ob->read_char_time(800); $self->{debug} = $debug; $ob->debug($self->{debug}) if($self->{debug} & 8); $ob->write_settings; $self->{ob} = $ob; $self->{unit} = 1; init_netdata($self); bless($self, $type); return($self); } sub init_netdata { my $self = shift; $self->{netdata} = {}; $self->{netdata}->{cmd}{"1"} = "Ping"; $self->{netdata}->{cmd}{"2"} = "Ping Reply"; $self->{netdata}->{cmd}{"3"} = "Prep EEWrite"; $self->{netdata}->{cmd}{"4"} = "Prep EEWrite 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}->{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"; } 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 - 512)/5)); } sub voltb_to_voltk { my $self = shift; my $voltb = shift; return(512 + (5*$voltb)); } sub voltb_to_volt { my $self = shift; my $voltb = shift; return(($self->voltb_to_voltk($voltb) / 1000)); } sub voltk_to_volt { my $self = shift; my $voltk = shift; return(($voltk / 1000)); } 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 force_on_charger { my $self = shift; my $pwm = shift; my $freq = shift; $self->do_write_cmd(0x15, $pwm, int($freq % 256), int($freq / 256)); 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 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; print "write_with_verify()\n" if $self->{debug} & 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 ping { my $self = shift; my $unit = $self->{unit}; my $cmd = shift || 1; my $string, $len, $data, $foo; print "ping\n" if ($self->{debug} & 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_eeprom { 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; print "read eeprom addr: $addr_hi $addr_lo\n" if($self->{debug} & 4); $self->do_write_cmd(0x09,($addr_hi + 0x80),$addr_lo); return -1 if ($self->do_read() < 8); return($self->{read_d0}); } sub write_eeprom { my $self = shift; my $addr = shift; my $data = shift; my $addr_lo, $addr_hi, $string; $addr_hi = ($addr & 0xFF00) / 256; $addr_lo = $addr & 0x00FF; print "write eeprom addr $addr -> $addr_lo / $addr_hi\n" if ($self->{debug} & 4); # prepare a prep cmd $self->do_write_cmd(0x03,($addr_hi+0x80),$addr_lo,$data); return -1 if($self->do_read() < 8); print_data($self->{data}) if ($self->{debug} & 4); return -2 if($self->{read_cmd} != 4); $self->do_write_cmd(0x06,($addr_hi+0x80),$addr_lo,$data); print_data($self->{write_data}) if ($self->{debug} & 4); return -2 if($self->do_read() < 8); if(($foo = $self->{read_cmd}) != 0x07) { print "ERROR: Expected 0x07 got back $foo\n" if($self->{debug} & 4); 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<") if ($self->{debug} & 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 @letters = split(//,$string); my $l = 0; my $o; my $p; my %purposes; my $wc; $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]); if(defined $self->{netdata}->{$p}{$wc}) { $n = $self->{netdata}->{$p}{$self->{"written_cmd"}} } else { $n = ""; } printf("%s%x:\t%x\t(%d)\t[%s\t] %s\n",$prefix,$l,$o,$o,$p,$n) if($self->{debug} & 2); $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_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:"); } $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}; print "$count: sending $ord [$purpose]\n" if($self->{debug} & 16); $count++; $self->{ob}->write($letter); select(undef,undef,undef,0.05); } } 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; print "Sensor value: $r Calculated value: $c Actual: $a Error: $e\n"; } } 1;