#!/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;
