/* Copyright 2025 by COG9 Enterprises LLC
 * All rights reserved.
 * Title: record.ino
 * Author: Jed Marti KI7NNP
 * Description: The current/voltage sensor and recorder.
 * Created: Mon Oct  6 12:20:14 2025
 * Revision History:
 *   Thu Oct 16 09:58:32 2025 Remove INA260, switch to INA219 with fixed addresses.
 */


#include <I2C_24LC1025.h>

#include "powerard.h"
#include "ina219.h"
#include "record.h"
#include "batv.h"
#include "ds1340.h"
#include "eepromconst.h"
#include "mcp9808.h"

// I2C temperature addresses.
extern uint8_t aTemp_i2c;
extern uint8_t bTemp_i2c;
extern uint8_t cTemp_i2c;
extern uint8_t auxTemp_i2c;

// Calibration stuff.
extern eePROMValues E;

// Serial EEPROM I2C.
I2C_24LC1025 seeprom(0x50);

// Recording.
uint8_t  recording;                 // Set non-zero when recording.
uint32_t recordByte;                // Byte in serial EEPROM to write to.
uint16_t sampleMS;                  // Milliseconds per sample.
uint16_t sampleSize;                // Samples per record.

// All set to full scale reading of 150 millivolts (3 amps through .05 ohms) so we / 4 on the
// shunt voltage register which gives a range of +- 160 mv.
// Supply A. 3 to 5.3 volts or so only.
INA219 MA = {
             .i2caddr = MONITORA,
             .range = 16,           // 3-5.5 volts here.
             .pg = 0x03,            // Divided by 8.
             .badc = 0x03,         
             .sadc = 0x03,          // 12 bits, single sample.
             .mode = 0x07 };        // Continuous sampling.

// Supply B, buck/boost generally 12 volts.
INA219 MB = {
             .i2caddr = MONITORB,
             .range = 32,           // 3.3 -> 32 volts.
             .pg = 0x03,            // Divided by 8
             .badc = 0x03,
             .sadc = 0x03,          // 12 bit, single sample.
             .mode = 0x07 };

// Supply C, buck/boost generally 12 - 25 volts.
INA219 MC = {
             .i2caddr = MONITORC,
             .range = 32,            // 3.3 -> 32 volts.
             .pg = 0x03,             // Divided by 8.
             .badc = 0x03,
             .sadc = 0x03,          // 12 bit single sample.
             .mode = 0x07 }; 


// Battery is from A/D.
float batterySum;                   // Battery sum

uint16_t sCount;                    // Count saved so far for short report.
uint32_t sampleCount;               // Count since time on.

// Temperature sums in degrees fahrenheit.
float  fASum;
float  fBSum;
float  fCSum;
float  fAuxSum;

// For reading/writing SEEPROM. Each record goes into one of these. There's 2 extra bytes for last
// record. Only one of these 4 records is possible at a time.
recordAmperage            rA;
recordAmperageVoltage     rAV;
recordAmperageTemperature rAT;
recordAll                 rAVT;




void initSEEPROM(void)
/*     Fire up the serial EEPROM. No cascade failure but.
 * Parameters: none
 * External References:
 *   recordByte: Byte for reading/writing.
 * Returned values: none
 * Errors detected: Failure to start.
 */
{
  if (!seeprom.begin())
  {
    rollString("**SEEPROM fails");
    return;
  }

  // SEEPROM read/write address.
  recordByte = 0;
}



void initRecorder(void)
/*     Initialize the recorder values. This is for the INA219 current/voltage sensors and their
 *  summations.
 * Parameters: none
 * External References:
 *   MA, MB, MC: The I2C addresses of the 3 * INA219 chips.
 *   isMA, isMB, isMC: Set to I2C address if present, 0 if not.
 *   aTemp_i2c, bTemp_i2c, cTemp_i2c: addresses of the 3 temperature sensors.
 */
{
  char msg[20];
  recordByte = 0;                     // Start writing first sample.
  sampleMS = E.eeSampleMS;            // Taken from EEPROM
  sampleSize = E.eeSampleSize;        // Taken from EEPROM

  // Various sums.
  fASum = fBSum = fCSum = fAuxSum = 0.0f;     // Temperature sums.
  MA.voltsSum = MB.voltsSum = MC.voltsSum = 0.0f;   // Voltage sums.
  batterySum = 0.0f;
  MA.maSum = MB.maSum = MC.maSum = 0;      // Amperage sums.

  // Life time sums of ma consumed. At 3 amps each, this allow almost 20 hours.
  sampleCount = MA.Wms = MB.Wms = MC.Wms = 0;
  
  // How many we've summed so far.
  sCount = 0;

  // This is the buck converter. Typically 5.1 volts.
  ina219Begin(&MA);
  if (MA.errno != INA219_OK)
  {
    snprintf(msg, 20, "A %s", ina219Emsg(&MA));
    rollString(msg);
  }

  // The first buck/boost converter, typically 12 volts.
  ina219Begin(&MB);
  if (MB.errno != INA219_OK)
  {
    snprintf(msg, 20, "B %s", ina219Emsg(&MB));
    rollString(msg);
  }

  // The second buck/boos converter, typically 19 or 24 volts.
  ina219Begin(&MC);
  if (MB.errno != INA219_OK)
  {
    snprintf(msg, 20, "C %s", ina219Emsg(&MC));
    rollString(msg);
  }
}


#define frac(x)     (int(100*(x - int(x))))



void powerPageScreen(void)
/*     Show the current voltages and amperages These are instantaneous, not the current
 *   averages.
 * Parameters: none
 * External References:
 *   MA, MB, MC: Control blocks of the monitors.
 *   isMA, isMB, isMC: Set to I2C address if present, 0 if not.
 *   aTemp_i2c, bTemp_i2c, cTemp_i2c: addresses of the 3 temperature sensors.
 * Returned values: none
 * Errors detected: none
 */
{
  float    v;
  float    a;
  uint32_t ua;
  char     msg[21];
  
  display.clearDisplay();
  display.setCursor(0,0);
  display.setTextSize(2);
  display.print(F("  POWER"));
  batteryIcon();
  
  // Battery voltage.
  display.setCursor(0, 17);
  display.setTextSize(1);
  display.print(F("Battery "));
  display.print(raw2Voltf(batRaw()), 1);
  display.print(F(" volts"));

  // Buck volt power supply. If running off serial port, will read negative.
  display.setCursor(0, 27);
  if (isOK(MA))
  {
    v = ina219GetVoltage(&MA);
    ua = ina219GetCurrent(&MA);
    if (ua > 32767) ua = 65535 - ua;
    snprintf(msg, 20, "%2d.%02dV %4luma %3dF", int(v), frac(v), ua, getFahrenheit(aTemp_i2c));
    display.print(msg);
  }
  else display.print(F("no A sensor"));
    
  // Buck/Boost second power supply. This should never read negative.
  display.setCursor(0, 37);
  if (isOK(MB))
  {
    v = ina219GetVoltage(&MB);
    ua = ina219GetCurrent(&MB);
    snprintf(msg, 20, "%2d.%02dV %4luma %3dF", int(v), frac(v), ua, getFahrenheit(bTemp_i2c));
    display.print(msg);
  }
  else display.print(F("no B sensor"));

  // Buck/Boost third power supply.
  display.setCursor(0, 47);
  if (isOK(MC))
  {
    v = ina219GetVoltage(&MC);
    ua = ina219GetCurrent(&MC);
    snprintf(msg, 20, "%2d.%02dV %4luma %3dF", int(v), frac(v), ua, getFahrenheit(cTemp_i2c));
    display.print(msg);
  }
  else display.print(F("no C sensor"));
}



static void resetSamples(void)
/*     Clear the sums and count before starting and after each collection. 
 * Parameters: none
 * External References:
 *   MA, MB, MC: Control blocks for the sensors.
 *   sCount: The number of samples collected.
 *   batterySum: Analog battery reading sum.
 *   f?Sum: Temperature sensor sums.
 * Returned values: none
 * Errors detected: none
 */
{
  // Reset integer values for the short sample series.
  sCount = MA.maSum = MB.maSum = MC.maSum = batterySum = 0;

  // Reset voltage averages.
  MA.voltsSum = MB.voltsSum = MC.voltsSum = 0.0f;

  // Temperature sums.
  fASum = fBSum = fCSum = fAuxSum = 0.0f;
}



void getLifeTime(void)
/*     Compute the consumption since start. A simple Riemann sum integration.
 *  
 *         sum((I * dt) / 100)  
 *
 *   which is maSeconds * 10. 
 * Notes: dt needs to be accurate and the recording sum may lag behind its time because it isn't
 *   interrupt driven on a reliable clock. Hence we do an extra I2C call here. Likewise, Those
 *   values aren't accumulated unless we're recording and this must be done even if not.
 * Parameters: none
 * External References:
 *   MA, MB, MC: Control blocks for the sensors.
 * Returned values: none
 * Errors detected: Sensors not found are not tested.
 */
{
  int32_t  ma;
  uint32_t nt;
  uint32_t sum = 0;
  if (isOK(MA))
  {
    nt = millis();
    ma = (int32_t) ina219GetCurrent(&MA);
    if (ma < 0) ma = -ma;
    MA.Wms += (ina219GetVoltage(&MA) * ma * (nt - MA.lastSumTime)) / 100;
    sum += MA.Wms;
    MA.lastSumTime = nt;
  }
  
  if (isOK(MB))
  {
    nt = millis();
    ma = (int32_t) ina219GetCurrent(&MB);
    MB.Wms += (ina219GetVoltage(&MB) * ma * (nt - MB.lastSumTime)) / 100;
    sum += MB.Wms;
    MB.lastSumTime = nt;
  }

  if (isOK(MC))
  {
    nt = millis();
    ma = (int32_t) ina219GetCurrent(&MC);
    MC.Wms += (ina219GetVoltage(&MC) * ma * (nt - MC.lastSumTime)) / 100;
    sum += MC.Wms;
    MC.lastSumTime = nt;
  }

  // Now set last watt hours from our sum.
  E.lastWh += sum / 10.0f;
  remainingPercent = (uint8_t) ((E.lastWh / E.Wh) * 100.0f);
}



void getReadings(void)
/*     Get and sum the readings. This happens every 1 / 'rate' seconds. Every 'sample' count,
 *   the values are averaged and written to the serial EEPROM. This only occurs when the start
 *   recording happens.
 * Parameters: none
 *   sCount: The number of samples collected.
 *   mamps?Sum: Milliamps sums for the 3 supplies.
 *   batterySum: Analog battery reading sum.
 *   f?Sum: Temperature sensor sums.
 */
{
  uint32_t batv;
  int32_t  ma;
  int16_t  rc;
  char msg[20];

  
  // Battery always there though reading will be poor if running off serial port as during
  // debugging also causing the 5 volt amperage reading to be backward.
  batterySum += raw2Voltf(batRaw());

  // Power A, typically 5 volts. Readings will be negative if running off serial port hence swap
  // signs to ignore. Also get the regulator temperature if available.
  if (isOK(MA))
  {
    ma = (int32_t) ina219GetCurrentf(&MA);
    if (ma < 0) ma = -ma;
    MA.maSum += ma;
    MA.voltsSum += ina219GetVoltage(&MA);
    if (aTemp_i2c) fASum += getFahrenheit(aTemp_i2c);
  }

  // Power B, typically 12 volts. This should not read negative.
  if (isOK(MB))
  {
    ma = (int32_t) ina219GetCurrent(&MB);
    MB.maSum += ma;
    MB.voltsSum += ina219GetVoltage(&MB);
    if (bTemp_i2c) fBSum += getFahrenheit(bTemp_i2c);
  }

  // Power C, typically 19 volts.
  if (isOK(MC))
  {
    ma = (int32_t) ina219GetCurrent(&MC);
    MC.maSum += ma;
    MC.voltsSum += ina219GetVoltage(&MC);
    if (cTemp_i2c) fCSum += getFahrenheit(cTemp_i2c);
  }

  // Auxiliary temperature.
  if (auxTemp_i2c != 0) fAuxSum +=  getFahrenheit(auxTemp_i2c);

  // Done with sampling. Compute averages into record.
  if (++sCount == sampleSize)
  {
    switch (E.whichRecord) {

    // Just recording amperage.
    case rAmps:
      getDateTime((uint8_t*) &rA.dateTime);
      rA.mampsA = MA.maSum / sCount;
      rA.mampsB = MB.maSum / sCount;
      rA.mampsC = MC.maSum / sCount;
      rA.zeroes = 0;
      rc = seeprom.writeBlock(recordByte, (uint8_t*) &(rA), sizeof(recordAmperage));
      recordByte += sizeof(recordAmperage) - sizeof(uint16_t);

      break;

    // Recording amperages and voltages.
    case rAmpsVolts:
      getDateTime((uint8_t*) &rAV.dateTime);
      rAV.mampsA = MA.maSum / sCount;
      rAV.mampsB = MB.maSum / sCount;
      rAV.mampsC = MC.maSum / sCount;

      // We save the battery voltage * 100 so we get 0 -> 2000 or so and we can then show 2
      // digits to the right of the decimal.
      rAV.battery = (uint16_t) ((batterySum * 100.0f) / (float) sCount);

      // We do the same for these so we can show 2 digits.
      rAV.voltA = (uint16_t) (((MA.voltsSum * 100.0f) / (float) sCount) + 0.5f);
      rAV.voltB = (uint16_t) (((MB.voltsSum * 100.0f) / (float) sCount) + 0.5f);
      rAV.voltC = (uint16_t) (((MC.voltsSum * 100.0f) / (float) sCount) + 0.5f);
      rAV.zeroes = 0;
      rc = seeprom.writeBlock(recordByte, (uint8_t*) &(rAV), sizeof(recordAmperageVoltage));
      recordByte += sizeof(recordAmperageVoltage) - sizeof(uint16_t);
      break;

      // Recording amerates and Temperatures.
    case rAmpsTemps:
      getDateTime((uint8_t*) &rAV.dateTime);
      rAT.mampsA = MA.maSum / sCount;
      rAT.mampsB = MB.maSum / sCount;
      rAT.mampsC = MC.maSum / sCount;
      rAT.tempA = (uint8_t) (fASum / sCount + 0.5f);
      rAT.tempB = (uint8_t) (fBSum / sCount + 0.5f);
      rAT.tempC = (uint8_t) (fCSum / sCount + 0.5f);
      rAT.tempX = (uint8_t) (fAuxSum / sCount + 0.5f);
      rAT.zeroes = 0;
      rc = seeprom.writeBlock(recordByte, (uint8_t*) &(rAT), sizeof(recordAmperageTemperature));
      recordByte += sizeof(recordAmperageTemperature) - sizeof(uint16_t);
      break;

      // Record everything.
    case rAll:
      getDateTime((uint8_t*) &rAVT.dateTime);
      rAVT.mampsA = MA.maSum / sCount;
      rAVT.mampsB = MB.maSum / sCount;
      rAVT.mampsC = MC.maSum / sCount;

      // We save the battery voltage * 100 so we get 0 -> 2000 or so and we can then show 2
      // digits to the right of the decimal.
      rAVT.battery = (uint16_t) ((batterySum * 100.0f) / (float) sCount);

      // We do the same for these so we can show 2 digits.
      rAVT.voltA = (uint16_t) (((MA.voltsSum * 100.0f) / (float) sCount) + 0.5f);
      rAVT.voltB = (uint16_t) (((MB.voltsSum * 100.0f) / (float) sCount) + 0.5f);
      rAVT.voltC = (uint16_t) (((MC.voltsSum * 100.0f) / (float) sCount) + 0.5f);

      // Temperatire averages rounded up. Most LiFePO4's won't turn on below zero F.
      rAVT.tempA = (uint8_t) (fASum / sCount + 0.5f);
      rAVT.tempB = (uint8_t) (fBSum / sCount + 0.5f);
      rAVT.tempC = (uint8_t) (fCSum / sCount + 0.5f);
      rAVT.tempX = (uint8_t) (fAuxSum / sCount + 0.5f);
      rAVT.zeroes = 0;
      rc = seeprom.writeBlock(recordByte, (uint8_t*) &(rAVT), sizeof(recordAll));
      recordByte += sizeof(recordAll) - sizeof(uint16_t);
      break;

    default:
      break;
    }

    resetSamples();
  }
}



void autoStartRecording(uint8_t pbs)
/*     One or more pushbuttons pressed at power on. 
 *    1:     Record Amps
 *    2:     Record Amps and volts.
 *    3:     Record all.
 *   Wait for pb's to be 0 before starting.
 * Parameters:
 *   pbs: Will be non-zero from detected at startup.
 * External References:
 *   E: EEPROM copy indicates which data we're recording.
 *   recording: Indicates we're recording.
 */
{
  switch(pbs) {
  case 1: E.whichRecord = rAmps; break;
  case 2: E.whichRecord = rAmpsVolts; break;
  case 3: E.whichRecord = rAll; break;
  default: return;
  }

  // Busy wait.
  waitForAllReleased();

  resetSamples();
  recording = 1;
  updateEEPROM();
}


  
void startRecording(void)
{
  char *tok;

  tok = strtok(NULL, " \n");
  if (tok == NULL) E.whichRecord = rAll;
  else if (strcasecmp(tok, "amps") == 0) E.whichRecord = rAmps;
  else if (strcasecmp(tok, "volts") == 0) E.whichRecord = rAmpsVolts;
  else if (strcasecmp(tok, "temps") == 0) E.whichRecord = rAmpsTemps;
  else if (strcasecmp(tok, "all") == 0) E.whichRecord = rAll;
  else
  {
    Serial.print(tok);
    Serial.println(F(" is not a known option (amps, volts, temps, all)"));
    return;
  }

  // Clear sums, set recording and time for next.
  resetSamples();
  recording = 1;
  updateEEPROM();

  // Indicate we've started.
  Serial.print("\nRecording ");
  Serial.println(tok);
}



void stopRecording(void)
/*     Either a serial port command, battery running low, or both pushbuttons down. If we're not
 *   recording then just punt.
 * Parameters: none
 * External References:
 *   recording: indicates recording or not.
 *   seeprom: I2C control block for the serial eeprom.
 * Returned values: none
 * Errors detected: none
 */
{
  // Not recording, just punt.
  if (recording == 0) return;
  
  // Indicate we're not recording.
  recording = 0;

  // Write 0 block to indicate we're done.
  seeprom.setBlock(recordByte, 0, 512);
  Serial.print("\nRecording stopped at ");
  Serial.println(recordByte);
}


// EOF
