#!/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 = <FILE>) {
		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 = <FILE>) {
		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;
