//#define simulation //battery voltage is forced to start at 60V and decrements by 5V every 2 seconds. Shunt voltage stays at 2.5V until battery voltage drops below 40V. Then it goes to 0.1.
//#define diagPrint1
//#define simulationNoStart 
//#define simulationLowVoltageAlarm

/****************************************************

XPbatteryLoadTest12.ino 9/15/2021

This version outputs only data, no text. It starts when Vbat is > 20V and stops when Vbat is < 20V. The first load test of my new battery shut down at 38.17V which is 2.9V per cell. That is too low. A relay will cut off the load when the voltage falls below 40V.

The first sample of Vbat is with no load so I can calculate ESR. When the voltage falls below 40V, I will continue to record Vbat so can calcualte the ESR when the battery is depleated. 

________________________________________________________________

               LED flash sequences
			   
in milliseconds 			   
ON		OFF 				meaning
200		200				data is being collected
1000	1000			program is waiting for battery voltage to appear
****************************************************************
     Lectric XP Battery State of Health (SOH) Tester
						  

Each minute, the program output through the USB:

                  time,Vbat,Vsh<cr>

Where time is in minutes, Vbat is in volts, and Vsh is in volts. They are separated by commas. After Vsh a carriage return is added. This format is compatible with Excel. Copy the data into a .txt file and then import into Excel as a CSV.

The processor should be powered up first. It will disconnect the load from the battery. Then the battery can be turned on.

A reading is taken and output. This open circuit battery voltage along with the next reading, taken under load, are used to calcualate the initial ESR. If the battery voltage is above 42V, the load is connected. If the battery voltage is at or below 42V, the load remains off.
	
**********************************************************/

//Constants
byte LEDoffByte = 0;//LED used to verify processor cycling. 
byte LEDonByte = 1;
byte loadConnectedByte = 1;
byte loadDisconnectedByte = 0;
long oneMinuteSecondsLong = 60;


//Variable
boolean OneMinuteBool = true;//set true by timer at the start of each minute; initialize to true to signal the start of the first minute
long startTimeSecondsLong = millis()/1000;
int	timeMinutesInt = 0;//clock incremented each minute and stops when load current goes to zero
float VbatteryFloat = 0;//battery voltage
float VshuntFloat = 0;//shunt voltage
boolean pauseClockQ = false;//if the battery is zero or negative or load current is too small, pause clock. This permits me to periodically collect the open circuit voltage so I can calculate the ESR.

void setup(){
	setUpGPIO();
	Serial.begin(19200); //set up communications path between Arduino and USB; specify data rate
	
				#ifdef simulation
				oneMinuteSecondsLong = 2;//shortens time between readings from every 60 seconds down to every 2 seconds
				#endif
	
	waitForPower();//start test when battery voltage is > 20; flash LED 1 second on and 1 second off while waiting
	delay(10000);//let battery voltage stabilize for 10 seconds
	startTimeSecondsLong = nowInSecondsLong();//we see battery voltage so start timer and begin to collect and output data
	for (byte sample = 0; sample < 6;sample++){
		collectAndOutputData();//this first output is with the load disconnected
		delay(10000);
	}
}

void loop(){
	lowVoltageCuttoff();//disconnect the load when the battery voltage falls below 40V. This is an average of 3.1V per cell. The battery voltage will still be read. Connect the load when the battery voltage is above 42V. This difference prevents relay oscillation due to excessive ESR. 
	heartbeat(200);//flash LED 0.2 second on and 0.2 second off to indicate program is running
	timer();//holds the one minute timer
	if(OneMinuteBool == true){//flag is true at the start of each minute
		collectAndOutputData();
		OneMinuteBool = false;//done processing output string
		}
	}

void timer(){
	if(nowInSecondsLong() - startTimeSecondsLong > oneMinuteSecondsLong){
		startTimeSecondsLong = nowInSecondsLong();//restart timer
		OneMinuteBool = true;//flag start of a new minute; user clears flag when done
		timeMinutesInt++;
	}
}

void collectAndOutputData(){
		readBattery();//measure battery and shunt voltages and convert to voltages placed in VbatteryFloat and VshuntFloat
		outputData();//outputs to USB		
}


long nowInSecondsLong(){
	long nowLong = millis()/1000;//the time, in seconds, since power up
	return nowLong;
}

	
void readBattery(){//measure battery and shunt voltages and convert to voltagers placed in VbatteryFloat and VshuntFloat
	readBatteryVoltage();
	readShuntVoltage();
	
	#ifdef simulation
	VbatteryFloat = 60 - 5*timeMinutesInt;
	if(VbatteryFloat < 40){
		VbatteryFloat = 0;
		VshuntFloat = 0.1;//simulates the battery turning off
	}else{
		VshuntFloat = 2.5;
	}
	#endif
}


float readBatteryVoltage(){//measure battery voltage and place in VbatteryFloat and also in return value
	VbatteryFloat = float(analogRead(A0));//convert from int to float
	VbatteryFloat = VbatteryFloat*13.419*5/1023;//we read a value between 0 and 1023 and convert it to a voltage. It is then scaled up to compensate for the voltage divider. The odd multiplier is due to calibrating the displayed value wrt a direct voltage reading.
	return VbatteryFloat;
}

void readShuntVoltage(){//measure shunt voltage place in VshuntFloat
	VshuntFloat = float(analogRead(A1));//convert from int to float
	VshuntFloat = VshuntFloat*1.0338*5/1023;//there is no voltage divider for the shunt voltage. The odd multiplier is due to calibrating the displayed value wrt a direct voltage reading.
}	
	

void outputData(){//format is time,Vbat,Vsh<cr>
	Serial.print(timeMinutesInt);
	Serial.print(",");
	Serial.print(VbatteryFloat);
	Serial.print(",");
	Serial.println(VshuntFloat);
}


void lowVoltageCuttoff(){//when the battery voltage falls below 40V, disconnect the load to prevent deep discharge. When the battery voltage is above 42V, connect the load. This logic provides hysteresis so the relay doesn't toggle back and forth due to load current and ESR. 2 V/1.4 amps = 1.4 ohm. The defective battery had an ESR of 1 ohm.
 
	readBatteryVoltage();
	
					#ifdef simulationLowVoltageAlarm
					VbatteryFloat = 39;
					#endif
		
	if(VbatteryFloat < 40) digitalWrite(9, loadDisconnectedByte);

	if(VbatteryFloat > 42) digitalWrite(9, loadConnectedByte);
}


void heartbeat(int delayInt){//delayByte is both the on and off time for one cycle of LED flashing. Time is in milliseconds	
	digitalWrite(10, LEDonByte);
	delay(delayInt);
	digitalWrite(10, LEDoffByte);
	delay(delayInt);
}


void waitForPower(){
	while(readBatteryVoltage() <= 20)heartbeat(1000);//no battery voltage yet so flash LED on for 1 second and then off for 1 second
		
					#ifdef simulation
					VbatteryFloat = 21;
					#endif
					
					#ifdef simulationNoStart
					VbatteryFloat = 19;
					#endif  
		
					#ifdef diagPrint1
					Serial.print("VbatteryFloat = ");
					Serial.println(VbatteryFloat);
					delay(2000);
					#endif
					
					#ifdef simulationLowVoltageAlarm
					VbatteryFloat = 39;
					#endif
		
		return;//battery voltage is present so start taking data
	}

void pauseWhenPowerOff(){
		
					#ifdef simulation
					VbatteryFloat = 21;
					#endif
					
					#ifdef simulationLowVoltageAlarm
					VbatteryFloat = 39;
					#endif

	while(readBatteryVoltage() <= 20)heartbeat(3000);//pause program while the battery voltage falls below 20V and tell user with slow flash rate
}
				
		
void setUpGPIO(){
	pinMode(10, OUTPUT); //drives LED. HIGH means LED operated
	digitalWrite(10, LEDoffByte);
	pinMode(A0, INPUT); //prepare to read scaled down Vbattery
	pinMode(A1, INPUT); //prepare to read Vshunt
	pinMode(9, OUTPUT); //drives the relay
	digitalWrite(9, loadDisconnectedByte);
}	