/**
 * Copyright (c) 2023, Erich Styger
 *
 * SPDX-License-Identifier: BSD-3-Clause
 * 
 * \brief Driver for the TI INA260 current/voltage sensor.
 */

#include "McuINA260.h"
#include "McuUtility.h"
#include "McuGenericI2C.h"

#define SwapBytes(u16)  ((u16<<8)|(u16>>8))

/*! 
 * \brief Convert the 16bit current register value to micro amps
 * \param value McuINA260_ADDR_CURRENT_REGISTER register value
 * \return Current in micro amp
 */
static int32_t McuINA260_ConvertCurrentRegisterToMicroAmps(int16_t value) {
  /* LSB == 1.25 mA == 1250 uA */
  return value*1250;
}

/*!
 * \brief Convert the voltage register value into miro volts
 * \param value McuINA260_ADDR_BUS_VOLTAGE_REGISTER register value
 * \return Voltage in micro volt
 */
static uint32_t McuINA260_ConvertBusVoltageRegisterToMicroVoltage(uint16_t value) {
  /* LSB == 1.25 mV == 1250 uV */
  return (uint32_t)value*1250;
}

/*!
 * \brief Convert the power register value into miro volts
 * \param value McuINA260_ADDR_POWER_REGISTER register value
 * \return Voltage in milli watt
 */
static uint32_t McuINA260_ConvertPowerRegisterToMilliWatt(uint16_t value) {
  /* LSB == 10 mW */
  return (uint32_t)value*10;
}

uint8_t McuINA260_ReadRegister(INA260_REG_e reg, uint16_t *value) {
  uint8_t res;
  uint16_t data;

  res = McuGenericI2C_ReadWordAddress8(McuINA260_CONFIG_I2C_ADDRESS, reg, &data);
  if (res!=ERR_OK) {
    return res;
  }
  *value = SwapBytes(data);
  return ERR_OK;
}

uint8_t McuINA260_WriteRegister(INA260_REG_e reg, uint16_t value) {
  uint8_t res;
  uint16_t data;

  data = SwapBytes(value);
  res = McuGenericI2C_WriteWordAddress8(McuINA260_CONFIG_I2C_ADDRESS, reg, data);
  if (res!=ERR_OK) {
    return res;
  }
  return ERR_OK;
}

uint8_t McuINA260_ReadConfigRegister(uint16_t *value) {
  return McuINA260_ReadRegister(McuINA260_ADDR_CONFIGURATION_REGISTER, value);
}

uint8_t McuINA260_WriteConfigRegister(uint16_t value) {
  return McuINA260_WriteRegister(McuINA260_ADDR_CONFIGURATION_REGISTER, value);
}

uint8_t McuINA260_WriteResetBit(void) {
  return McuINA260_WriteConfigRegister(1<<15);
}

uint8_t McuINA260_ReadCurrentRegister(int16_t *value) {
  return McuINA260_ReadRegister(McuINA260_ADDR_CURRENT_REGISTER, (uint16_t*)value);
}

uint8_t McuINA260_ReadBusVoltageRegister(uint16_t *value) {
  return McuINA260_ReadRegister(McuINA260_ADDR_BUS_VOLTAGE_REGISTER, value);
}

uint8_t McuINA260_ReadPowerRegister(uint16_t *value) {
  return McuINA260_ReadRegister(McuINA260_ADDR_POWER_REGISTER, value);
}

uint8_t McuINA260_ReadMaskEnableRegister(uint16_t *value) {
  return McuINA260_ReadRegister(McuINA260_ADDR_MASK_ENABLE_REGISTER, value);
}

uint8_t McuINA260_ReadAlertLimitRegister(uint16_t *value) {
  return McuINA260_ReadRegister(McuINA260_ADDR_ALERT_LIMIT_REGISTER, value);
}

uint8_t McuINA260_ReadManufacturereIDRegister(uint16_t *value) {
  return McuINA260_ReadRegister(McuINA260_ADDR_MANUFACTURER_ID_REGISTER, value);
}

uint8_t McuINA260_ReadDieIDRegister(uint16_t *value) {
  return McuINA260_ReadRegister(McuINA260_ADDR_DIE_ID_REGISTER, value);
}

/*!
 * \brief Read the bus voltage measurement value from the device.
 * \return Power value, as mV (micro-Volt).
 */
uint32_t McuINA260_ReadVoltage(void) {
  uint8_t res;
  uint16_t val;

  res = McuINA260_ReadBusVoltageRegister(&val);
  if (res==ERR_OK) {
    return McuINA260_ConvertBusVoltageRegisterToMicroVoltage(val);
  } else {
    return 0; /* error case */
  } 
}

/*!
 * \brief Read the power measurement value from the device.
 * \return Power value, as mW (milli-Watt).
 */
uint32_t McuINA260_ReadPower(void) {
  uint8_t res;
  uint16_t val;

  res = McuINA260_ReadPowerRegister(&val);
  if (res==ERR_OK) {
    return McuINA260_ConvertPowerRegisterToMilliWatt(val);
  } else {
    return 0; /* error case */
  } 
}

/*!
 * \brief Read the current measurement value from the device.
 * \return Current value, as uA (micro-Amps).
 */
int32_t McuINA260_ReadCurrent(void) {
  uint8_t res;
  int16_t val;

  res = McuINA260_ReadCurrentRegister(&val);
  if (res==ERR_OK) {
    return McuINA260_ConvertCurrentRegisterToMicroAmps(val);
  } else {
    return 0; /* error case */
  }
}

#if McuINA260_CONFIG_PARSE_COMMAND_ENABLED
static uint8_t PrintStatus(const McuShell_StdIOType *io) {
  uint8_t buf[32];
  uint16_t value;
  int16_t i16;

  McuShell_SendStatusStr((unsigned char*)"McuINA260", (unsigned char*)"INA260 sensor status\r\n", io->stdOut);

  McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"0x");
  McuUtility_strcatNum8Hex(buf, sizeof(buf), McuINA260_CONFIG_I2C_ADDRESS);
  McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n");
  McuShell_SendStatusStr((unsigned char*)"  I2C addr", buf, io->stdOut);

  if (McuINA260_ReadConfigRegister(&value)==ERR_OK) {
    buf[0] = '\0';
    McuUtility_strcatNum8Hex(buf, sizeof(buf), McuINA260_ADDR_CONFIGURATION_REGISTER);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"h: 0x");
    McuUtility_strcatNum16Hex(buf, sizeof(buf), value);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n");
  } else {
    McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"FAILED\r\n");
  }
  McuShell_SendStatusStr((unsigned char*)"  Config", buf, io->stdOut);

  if (McuINA260_ReadCurrentRegister(&i16)==ERR_OK) {
    int32_t uA = McuINA260_ConvertCurrentRegisterToMicroAmps(i16);

    buf[0] = '\0';
    McuUtility_strcatNum8Hex(buf, sizeof(buf), McuINA260_ADDR_CURRENT_REGISTER);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"h: 0x");
    McuUtility_strcatNum16Hex(buf, sizeof(buf), i16);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)", ");
    McuUtility_strcatNum32s(buf, sizeof(buf), uA/1000);
    McuUtility_chcat(buf, sizeof(buf), '.');
    if (uA<0) { /* value can be negative */
      uA = -uA; /* make it positive for the modulo operation below */
    }
    McuUtility_strcatNum32sFormatted(buf, sizeof(buf), uA%1000, '0', 3);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)" mA\r\n");
  } else {
    McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"FAILED\r\n");
  }
  McuShell_SendStatusStr((unsigned char*)"  Current", buf, io->stdOut);

  if (McuINA260_ReadBusVoltageRegister(&value)==ERR_OK) {
    uint32_t uV = McuINA260_ConvertBusVoltageRegisterToMicroVoltage(value);

    buf[0] = '\0';
    McuUtility_strcatNum8Hex(buf, sizeof(buf), McuINA260_ADDR_BUS_VOLTAGE_REGISTER);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"h: 0x");
    McuUtility_strcatNum16Hex(buf, sizeof(buf), value);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)", ");
    McuUtility_strcatNum32u(buf, sizeof(buf), uV/1000);
    McuUtility_chcat(buf, sizeof(buf), '.');
    McuUtility_strcatNum32sFormatted(buf, sizeof(buf), uV%1000, '0', 3);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)" mV\r\n");
  } else {
    McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"FAILED\r\n");
  }
  McuShell_SendStatusStr((unsigned char*)"  Bus voltage", buf, io->stdOut);

  if (McuINA260_ReadPowerRegister(&value)==ERR_OK) {
    buf[0] = '\0';
    McuUtility_strcatNum8Hex(buf, sizeof(buf), McuINA260_ADDR_POWER_REGISTER);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"h: 0x");
    McuUtility_strcatNum16Hex(buf, sizeof(buf), value);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)", ");
    McuUtility_strcatNum32u(buf, sizeof(buf), McuINA260_ConvertPowerRegisterToMilliWatt(value));
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)" mW\r\n");
  } else {
    McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"FAILED\r\n");
  }
  McuShell_SendStatusStr((unsigned char*)"  Power", buf, io->stdOut);

  if (McuINA260_ReadAlertLimitRegister(&value)==ERR_OK) {
    buf[0] = '\0';
    McuUtility_strcatNum8Hex(buf, sizeof(buf), McuINA260_ADDR_ALERT_LIMIT_REGISTER);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"h: 0x");
    McuUtility_strcatNum16Hex(buf, sizeof(buf), value);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n");
  } else {
    McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"FAILED\r\n");
  }
  McuShell_SendStatusStr((unsigned char*)"  Alert limit", buf, io->stdOut);

  if (McuINA260_ReadManufacturereIDRegister(&value)==ERR_OK) {
    buf[0] = '\0';
    McuUtility_strcatNum8Hex(buf, sizeof(buf), McuINA260_ADDR_MANUFACTURER_ID_REGISTER);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"h: 0x");
    McuUtility_strcatNum16Hex(buf, sizeof(buf), value);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n");
  } else {
    McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"FAILED\r\n");
  }
  McuShell_SendStatusStr((unsigned char*)"  Manufac. ID", buf, io->stdOut);

  if (McuINA260_ReadDieIDRegister(&value)==ERR_OK) {
    buf[0] = '\0';
    McuUtility_strcatNum8Hex(buf, sizeof(buf), McuINA260_ADDR_DIE_ID_REGISTER);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"h: 0x");
    McuUtility_strcatNum16Hex(buf, sizeof(buf), value);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n");
  } else {
    McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"FAILED\r\n");
  }
  McuShell_SendStatusStr((unsigned char*)"  Die ID", buf, io->stdOut);

  return ERR_OK;
}

static uint8_t PrintHelp(const McuShell_StdIOType *io) {
  McuShell_SendHelpStr((unsigned char*)"INA260", (unsigned char*)"Group of INA260 commands\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  help|status", (unsigned char*)"Print help or status information\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  reset", (unsigned char*)"Reset the device with the reset bit\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  config <val>", (unsigned char*)"Write a value to the configuration register\r\n", io->stdOut);
  return ERR_OK;
}

uint8_t McuINA260_ParseCommand(const unsigned char* cmd, bool *handled, const McuShell_StdIOType *io) {
  uint8_t res = ERR_OK;

  if (McuUtility_strcmp((char*)cmd, McuShell_CMD_HELP) == 0
    || McuUtility_strcmp((char*)cmd, "INA260 help") == 0)
  {
    *handled = true;
    return PrintHelp(io);
  } else if (   (McuUtility_strcmp((char*)cmd, McuShell_CMD_STATUS)==0)
             || (McuUtility_strcmp((char*)cmd, "INA260 status")==0)
            )
  {
    *handled = true;
    res = PrintStatus(io);
  } else if (McuUtility_strcmp((char*)cmd, "INA260 reset")==0) {
    *handled = true;
    return McuINA260_WriteResetBit();
  } else if (McuUtility_strncmp((char*)cmd, "INA260 config ", sizeof("INA260 config "-1))==0) {
    const unsigned char *p;
    int32_t val;

    *handled = true;
    p = cmd + sizeof("INA260 config "-1);
    if (McuUtility_xatoi(&p, &val)==ERR_OK && val>=0) {
      return McuINA260_WriteConfigRegister(val);
    }
  }
  return res;
}
#endif /* McuINA260_CONFIG_PARSE_COMMAND_ENABLED */

void McuINA260_Deinit(void) {
  /* nothing needed */
}

void McuINA260_Init(void) {
  /* nothing needed */
}
