/* Copyright 2025 by COG9 Enterprises LLC
 * All rights reserved.
 * Title: batv.ino
 * Author: Jed Marti KI7NNP
 * Description: Read and work with battery voltage.
 * Created: Wed Oct  8 19:35:35 2025
 * Revision History:
 */


#include "powerard.h"
#include "batv.h"
#include "eepromconst.h"

// Values from EEPROM.
extern eePROMValues E;

// For LLSQ calculations.
static float reading[10];

// The llsq fit values - you need to modify cM and Cb in bartv.h after a calibration run.
static float M = cM;
static float B = cB;

// The current battery state.
uint8_t  batState = batNoSample;
uint32_t batStartMS = 0;


void initBatv(void)
{
  // Default values from constants.
  if (E.eeM == 0.0 || isnanf(E.eeM))
  {
    E.eeM = cM,
      E.eeB = cB,
      E.eeBatLow = LOWBAT,
      E.eeBatOver = BATOVER,
      E.eeBatBad = BADBAT;
    updateEEPROM();
  }
}


    
uint16_t batRaw(void)
/*     Just get the raw reading  from the ADC and return. 10 bits, 0 - 1023.
 * Parameters: none
 * External References:
 *   BATMON: constant for bit. See batv.h
 * Returned values: reading 0 (0 volts), 1023 (15 volts or so).
 * Errors detected: none
 */
{
  return analogRead(BATMON);
}


float raw2Voltf(uint16_t raw)
/*     Convert a raw reading into volts using the linear least squares approximation for this
 *   board. This needs to be calibrated with a digital power supply (see below).
 *       volts = M * raw + B
 * Parameters:
 *   raw: Raw reading, 0 - 1023
 * External References:
 *   E.eeM: Slope of the straight line.
 *   E.eeB: Zero offset from llsq.
 * Returned values: Voltage.
 * Errors detected: none
 */
{
  return E.eeM * ((float) raw) + E.eeB;
}



uint16_t raw2Volt(uint16_t raw)
/*     Convert raw voltage (perhaps an average) into a voltage * 100 which should give us
 *   0 -> 1380. The 16 bit value is used for storage during recording.
 * Parameters:
 *   raw: Value 0 -> 1023.
 * External References:
 *   M, B: from linear least squares fit.
 * Returned values: the voltage as an integer * 100.
 * Errors detected: remove top bits from the raw value. They shouldn't be there.
 */
{
  float f;
  raw &= 0x3FF;          // mask off any other bits.
  f = E.eeM * ((float) raw) + E.eeB;
  return (uint16_t) (f * 100.0);
}


void volt2string(uint16_t v100, char *str)
/*     Convert a voltage 100 (from raw2volt) into a string nn.mmv.
 * Parameters:
 *   v100: Voltage * 100.
 *   str: Pointer to a string with at least 6 characters.
 * External References: none
 * Returned values: none, string destination modified.
 */
{
  uint8_t vd, vp;
  vd = v100 / 100;
  vp = v100 % 100;
  if (vd > 9) *str++ = (vd / 10) + '0';
  *str++ = (vd % 10) + '0';
  *str++ = '.';
  *str++ = (vp / 10) + '0';
  *str++ = (vp % 10) + '0';
  *str++ = 'V';
  *str++ = 0;
}


static float sqrf(float f) { return f * f; }

static float llsq(float *m, float *b)
/*     Linear least squares on 10 data elements. Used to calibrate A/D converter for the battery.
 *   Y values are precomputed for 5 - 14 volts.
 * Parameters:
 *   m: Address to put slope.
 *   b: Address to put 0.
 * External References:
 *   reading[]: A/D readings for different voltages.
 * Returned values: none, values stored in the parameter addresses.
 * Errors detected: none
 */
{
  float   avgx, num, den;
  uint8_t i;

  num = den = avgx = 0.0;
  for (i = 0; i < 10; ++i) avgx += reading[i];
  avgx /= 10.0f;
  for (i = 0; i < 10; ++i)
  {
    num += (reading[i] - avgx) * ((i + 5) - 9.5f);
    den += sqrf(reading[i] - avgx);
  }
  *m = num / den;
  *b = 9.4f - (*m) * avgx;
}

  

static char waitAnswer(void)
/*     Wait for a return or anything and return it. Busy wait.
 * Parameters: none
 * External References: none
 * Returned values: the character enetered.
 * Errors detected: none
 */
{
  char c;
  while (!Serial.available());
  c = Serial.read();
  Serial.println("");
}



static void waitForPower(void)
{
  Serial.println(F("Calibrate battery monitor"));
  Serial.print(F("Remove power board, attach variable supply"));
  if (waitAnswer() != 'y') return;
}


static float setVolts(float v)
{
  uint16_t br;
  Serial.print(F("Set power to "));
  Serial.print(v);
  Serial.print(F(" volts"));
  waitAnswer();
  br = batRaw();
  Serial.print("   "); Serial.println(br);
  return (float) br;
}
  


void calibrateBatMon(void)
{
  int32_t i;
  
  waitForPower();

  for (i = 0; i < 10; ++i) reading[i] = setVolts((float) (i + 5));

  llsq(&E.eeM, &E.eeB);
  Serial.print("M=");
  Serial.print(E.eeM, 5);
  Serial.print("X + ");
  Serial.println(E.eeB, 5);
  updateEEPROM();
}



void checkBatState(void)
{
  float v;
  char msg[23];
  v = raw2Voltf(batRaw());

  // Over voltage - 5/second blink
  if (v >= E.eeBatOver)
  {
    if (batState != batOverVoltage)
    {
      ledBlink(OVERON, OVEROFF);
      Serial.print(F("Battery overvoltage at "));
      Serial.print(v, 3);
      Serial.println(F(" volts"));
      batState = batOverVoltage;
      sprintf(msg, "%4.1f volts", v);
      errorMessage("Battery overvoltage", msg, "Bad!!!");
    }
  }

  // Good state.
  else if (v >= E.eeBatLow)
  {
    if (batState != batGood)
    {
      ledBlink(NORMALON, NORMALOFF);
      Serial.print(F("Battery good at "));
      Serial.print(v, 3);
      Serial.println(F(" volts"));
      batState = batGood;
    }
  }

  // Getting low.
  else if (v >= E.eeBatBad)
  {
    if (batState != batLow)
    {
      ledBlink(LOWON, LOWOFF);
      Serial.print(F("Battery low at "));
      Serial.print(v, 3);
      Serial.println(F("volts"));
      batState = batLow;
      sprintf(msg, "%4.1f volts", v);
      errorMessage("Battery low", msg, "Wrap it up!");
    }
  }

  // Causes cascade of stopping.
  else
  {
    if (batState != batDead)
    {
      ledBlink(BADON, BADOFF);
      Serial.print(F("Battery dead at "));
      Serial.print(v, 3);
      Serial.println(F(" volts"));
      batState = batDead;

      sprintf(msg, "%4.1f volts", v);
      errorMessage("Battery dead", msg, "Good bye!");
      // Dim the display.
      //      display.dim(true);
      //      recordStop();
    }
  }
}
  



// EOF
