/*
  SFF Minilab PDU Project
  Current Sensing & Display Driver Board

  Author: tobychui
*/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

/* Hardware Pin Definations */
#define SDA 23 //0.91 inch OLED SDA pin
#define SCK 19 //0.91 inch OLED SCK pin

#define I_sense_A 32
#define I_sense_B 33
#define I_sense_C 34
#define I_sense_D 35
#define ZERO_CURRENT_V 2.55 //Calibrated voltage of no current flow

#define OLED_ADDR 0x3C
#define SCREEN_WIDTH  128
#define SCREEN_HEIGHT 32
// Create display object
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// Number of current samples
#define NUM_SAMPLES 10

const float Rt = 5100.0;   // top resistor (ohms)
const float Rb = 10000.0;  // bottom resistor (ohms)

// --- Utility functions ---
String formatUptime(unsigned long ms) {
  unsigned long totalSec = ms / 1000;
  unsigned int days = totalSec / 86400;
  totalSec %= 86400;
  unsigned int hours = totalSec / 3600;
  totalSec %= 3600;
  unsigned int minutes = totalSec / 60;
  unsigned int seconds = totalSec % 60;

  char buf[32];
  if (days > 0) {
    snprintf(buf, sizeof(buf), "%dd %02dh", days, hours);
  } else if (hours > 0) {
    snprintf(buf, sizeof(buf), "%02dh %02dm", hours, minutes);
  } else if (minutes > 0) {
    snprintf(buf, sizeof(buf), "%02dm %02ds", minutes, seconds);
  } else {
    snprintf(buf, sizeof(buf), "%ds", seconds);
  }
  return String(buf);
}

void clear_display() {
  display.clearDisplay();
  display.display();
}

void print_text(const char* text, int x = 0, int y = 0, int size = 1) {
  display.clearDisplay();
  display.setTextSize(size);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(x, y);
  display.println(text);
  display.display();
}
void setup() {
  Serial.begin(115200);

  //ADC_11db set ADC to ~0–3.3 V range
  analogSetPinAttenuation(I_sense_A, ADC_11db);
  analogSetPinAttenuation(I_sense_B, ADC_11db);
  analogSetPinAttenuation(I_sense_C, ADC_11db);
  analogSetPinAttenuation(I_sense_D, ADC_11db);

  // Init I2C
  Wire.begin(SDA, SCK);

  // Init OLED
  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;); // Don’t proceed, loop forever
  }

  clear_display();
  print_text("imuslab prototype", 0, 0, 1);
  delay(2000);
}

// get_current_sensor_voltage() gets the sensor raw output voltage from the given pin number
// The sensor output is passed through a voltage divider of 5.1k - 10k that
// convert the 5V signal output of ACS712 to 3.3v logic level readable by ESP32 3.3v ADC
float get_current_sensor_voltage(int pin_number) {
  long sum_mV = 0;

  // take multiple samples
  for (int i = 0; i < NUM_SAMPLES; i++) {
    sum_mV += analogReadMilliVolts(pin_number);
    delayMicroseconds(200);  // short delay to stabilize readings
  }

  // average the readings
  int avg_mV = sum_mV / NUM_SAMPLES;

  // Convert to float volts
  float vout = avg_mV / 1000.0f;

  // Reconstruct original pre-divider voltage
  float vin = vout * (Rt + Rb) / Rb;

  //Serial.printf("Pin %d -> Vout=%.3f V  Vin=%.3f V\n", pin_number, vout, vin);
  return vin;
}

// get_current_from_voltage_reading() gets the actual current
// flowing throught the current sensor given the voltage read
// from ESP32 ADC pins
float get_current_from_voltage_reading(float voltage) {
  const float sensitivity = 0.100;   // V/A for ACS712-20A
  float current = (voltage - ZERO_CURRENT_V) / sensitivity;
  return current;
}

void loop() {
  float vinA = get_current_sensor_voltage(I_sense_A);
  float vinB = get_current_sensor_voltage(I_sense_B);
  float vinC = get_current_sensor_voltage(I_sense_C);
  float vinD = get_current_sensor_voltage(I_sense_D);

  float curA = get_current_from_voltage_reading(vinA);
  float curB = get_current_from_voltage_reading(vinB);
  float curC = get_current_from_voltage_reading(vinC);
  float curD = get_current_from_voltage_reading(vinD);

  // Keep current reading above 0
  curA = (curA < 0.0) ? 0.0 : curA;
  curB = (curB < 0.0) ? 0.0 : curB;
  curC = (curC < 0.0) ? 0.0 : curC;
  curD = (curD < 0.0) ? 0.0 : curD;

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);

  display.setCursor(0, 0);
  display.printf("A: %.2fA", curA);

  display.setCursor(64, 0);
  display.printf("B: %.2fA", curB);

  display.setCursor(0, 12);
  display.printf("C: %.2fA", curC);

  display.setCursor(64, 12);
  display.printf("D: %.2fA", curD);

  display.setCursor(0, 24);
  display.print("Uptime: ");
  display.print(formatUptime(millis()));

  display.display();

  // --- JSON printout ---
  Serial.printf(
    "{"
    "\"voltage\":{"
    "\"A\":%.3f,"
    "\"B\":%.3f,"
    "\"C\":%.3f,"
    "\"D\":%.3f"
    "},"
    "\"current\":{"
    "\"A\":%.3f,"
    "\"B\":%.3f,"
    "\"C\":%.3f,"
    "\"D\":%.3f"
    "},"
    "\"uptime\":%lu"
    "}\n",
    vinA, vinB, vinC, vinD,
    curA, curB, curC, curD,
    millis() / 1000  // uptime in seconds
  );

  delay(100);
}
