/*
  ESP32 + ADS1015 + 0.96" OLED (SSD1306) 压力监测示例

  传感器参数：量程 0~0.2 MPa，供电 5V，输出 0.5~4.5V
  默认 I2C 引脚：SDA 21, SCL 22

  注意：若 ADS1015 以 3.3V 供电，请务必使用分压（或其他限压方案）将传感器 0.5~4.5V
        压到 ADC 允许范围内（例如 1:1 分压将 4.5V -> 2.25V）。
        若 ADS1015 以 5V 供电，请确保 I2C 上拉为 3.3V（或使用电平转换）。
*/

#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_ADS1X15.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <time.h>

// ------------------------------ 可配置参数 ------------------------------
// I2C 引脚（ESP32 默认：SDA=21, SCL=22）
#ifndef I2C_SDA_PIN
#define I2C_SDA_PIN 21
#endif
#ifndef I2C_SCL_PIN
#define I2C_SCL_PIN 22
#endif

// I2C 设备地址（常见：ADS1015=0x48, OLED SSD1306=0x3C）
#ifndef ADS1015_I2C_ADDR
#define ADS1015_I2C_ADDR 0x48
#endif
#ifndef OLED_I2C_ADDR
#define OLED_I2C_ADDR 0x3C
#endif

// OLED 分辨率
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// ------------------------------ Wi?Fi / HTTP 配置 ------------------------------
#ifndef WIFI_SSID
#define WIFI_SSID "hhhhhhhh"
#endif
#ifndef WIFI_PASSWORD
#define WIFI_PASSWORD "nihaoahhh"
#endif

#ifndef SERVER_HOST
// 注意：这里不要使用 "localhost"，请改为你电脑在局域网内的 IP，例如 192.168.1.100
#define SERVER_HOST "192.168.5.170"
#endif
#ifndef SERVER_PORT
#define SERVER_PORT 5000
#endif
#ifndef SERVER_PATH
#define SERVER_PATH "/data"
#endif
#ifndef DEVICE_ID
#define DEVICE_ID "esp32-pressure-1"
#endif

static const uint32_t HTTP_POST_INTERVAL_MS = 5000; // 每 5 秒上报一次

// 传感器与量程参数
static const float SENSOR_MIN_V = 0.5f;     // 0 MPa 对应电压 (V)
static const float SENSOR_MAX_V = 4.5f;     // 满量程电压 (V)
static const float PRESSURE_FULL_SCALE_MPA = 0.2f; // 满量程 0.2 MPa

// 简单零点/跨度微调（可用于小偏差补偿）
static const float ZERO_TRIM_V = 0.0f;      // 加在传感器电压上的零点微调 (V)
static const float SPAN_SCALE = 1.0f;       // 对 (V - Vmin) 的比例微调

// 分压网络（若未分压，请将 R1_OHMS 设为 0）
// 约定：传感器输出 -> R1 -> ADC 输入点 -> R2 -> GND
// 传感器电压 Vs 与 ADC 端电压 Vad 关系：Vad = Vs * R2 / (R1 + R2)
// 若 R1=0 则 Vad = Vs。
static const float R1_OHMS = 0.0f;     // 例如 10000.0f
static const float R2_OHMS = 0.0f;     // 例如 10000.0f

// 采样与显示
static const uint16_t SAMPLE_COUNT = 16;     // 移动平均样本数
static const uint32_t SAMPLE_INTERVAL_MS = 50; // 采样间隔
static const uint32_t DISPLAY_INTERVAL_MS = 200; // 屏幕刷新间隔

// ADS1015 增益（根据最大电压选择）。
// 若测量到 ADC 引脚最大约 4.0V，建议 GAIN_ONE(±4.096V)。
// 若最大接近 4.5V，可改为 GAIN_TWOTHIRDS(±6.144V)（分辨率略降）。
static const adsGain_t ADS_GAIN = GAIN_ONE;

// ------------------------------ 全局对象 ------------------------------
Adafruit_ADS1015 ads;  // 使用 ADS1015（12-bit）
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// ------------------------------ 工具函数 ------------------------------
static bool i2cDevicePresent(uint8_t address) {
  Wire.beginTransmission(address);
  uint8_t error = Wire.endTransmission();
  return (error == 0);
}

static float computeDividerInverseGain() {
  if (R1_OHMS <= 0.0f || R2_OHMS <= 0.0f) {
    return 1.0f; // 未使用分压
  }
  // Vs = Vad * (R1 + R2) / R2
  return (R1_OHMS + R2_OHMS) / R2_OHMS;
}

static float clampf(float v, float lo, float hi) {
  if (v < lo) return lo;
  if (v > hi) return hi;
  return v;
}

// 将传感器电压换算为压力 (MPa)，线性映射并应用零/跨度微调
static float voltsToPressureMPa(float sensorVolts) {
  const float adjusted = (sensorVolts + ZERO_TRIM_V);
  float x = (adjusted - SENSOR_MIN_V);
  x *= SPAN_SCALE;
  const float spanV = (SENSOR_MAX_V - SENSOR_MIN_V);
  if (spanV <= 0.0f) return 0.0f;
  float ratio = x / spanV;              // 0.0 ~ 1.0
  ratio = clampf(ratio, 0.0f, 1.0f);
  return ratio * PRESSURE_FULL_SCALE_MPA;
}

// 简单移动平均
class MovingAverage {
 public:
  explicit MovingAverage(uint16_t capacity)
      : _capacity(capacity), _sum(0.0f), _index(0), _count(0) {
    if (_capacity < 1) _capacity = 1;
    _buffer = new float[_capacity];
    for (uint16_t i = 0; i < _capacity; ++i) _buffer[i] = 0.0f;
  }
  ~MovingAverage() { delete[] _buffer; }

  void push(float v) {
    _sum -= _buffer[_index];
    _buffer[_index] = v;
    _sum += v;
    _index = (_index + 1) % _capacity;
    if (_count < _capacity) _count++;
  }

  float value() const {
    if (_count == 0) return 0.0f;
    return _sum / static_cast<float>(_count);
  }

 private:
  float* _buffer;
  uint16_t _capacity;
  float _sum;
  uint16_t _index;
  uint16_t _count;
};

// ------------------------------ 状态 ------------------------------
static bool g_displayOk = false;
static bool g_adsOk = false;
static bool g_wifiOk = false;
static bool g_timeOk = false;
static float g_dividerInv = 1.0f;
static MovingAverage g_avgPressure(SAMPLE_COUNT);
static MovingAverage g_avgSensorV(SAMPLE_COUNT);

// ------------------------------ 显示 ------------------------------
static void drawCentered(const char* text, int16_t y, const GFXfont* font = NULL) {
  if (!g_displayOk) return;
  if (font) display.setFont(font); else display.setFont();
  int16_t x1, y1; uint16_t w, h;
  display.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
  int16_t x = (SCREEN_WIDTH - (int16_t)w) / 2;
  if (x < 0) x = 0;
  display.setCursor(x, y);
  display.print(text);
}

static void renderUI(float pressureMPa, float sensorV, bool sensorOk) {
  if (!g_displayOk) return;
  display.clearDisplay();

  // 标题栏
  display.fillRect(0, 0, SCREEN_WIDTH, 16, SSD1306_WHITE);
  display.setTextColor(SSD1306_BLACK);
  display.setTextSize(1);
  display.setCursor(4, 4);
  display.print("PRESSURE");
  display.setCursor(SCREEN_WIDTH - 54, 4);
  display.print(sensorOk ? "OK" : "ERR");

  // 主读数（大字体）
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(2);
  char line1[32];
  snprintf(line1, sizeof(line1), "%.3f MPa", pressureMPa);
  drawCentered(line1, 26);

  // 次级信息（传感器电压与进度条）
  display.setTextSize(1);
  char line2[32];
  snprintf(line2, sizeof(line2), "Vs: %.3f V", sensorV);
  display.setCursor(4, 48);
  display.print(line2);

  // 进度条（相对满量程）
  const int barX = 4;
  const int barY = 56;
  const int barW = SCREEN_WIDTH - 8;
  const int barH = 6;
  display.drawRect(barX, barY, barW, barH, SSD1306_WHITE);
  float ratio = clampf(pressureMPa / PRESSURE_FULL_SCALE_MPA, 0.0f, 1.0f);
  int filled = (int)(ratio * (barW - 2));
  if (filled > 0) display.fillRect(barX + 1, barY + 1, filled, barH - 2, SSD1306_WHITE);

  display.display();
}

static void renderError(const char* msgTop, const char* msgBottom = "") {
  if (!g_displayOk) return;
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(1);
  drawCentered(msgTop, 20);
  drawCentered(msgBottom, 40);
  display.display();
}

// ------------------------------ Wi?Fi / 时间 / HTTP ------------------------------
static void connectWiFi(uint32_t timeoutMs = 15000) {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  const uint32_t start = millis();
  while (WiFi.status() != WL_CONNECTED && (millis() - start) < timeoutMs) {
    delay(250);
    Serial.print('.');
  }
  g_wifiOk = (WiFi.status() == WL_CONNECTED);
  Serial.println();
  if (g_wifiOk) {
    Serial.print(F("[INFO] WiFi connected: ")); Serial.println(WiFi.localIP());
  } else {
    Serial.println(F("[WARN] WiFi connect timeout"));
  }
}

static bool syncTime(uint32_t timeoutMs = 6000) {
  configTime(0, 0, "pool.ntp.org", "time.nist.gov"); // UTC
  struct tm tmnow;
  const uint32_t start = millis();
  while ((millis() - start) < timeoutMs) {
    if (getLocalTime(&tmnow, 200)) {
      g_timeOk = true;
      return true;
    }
  }
  g_timeOk = false;
  return false;
}

static String getTimestampISO8601() {
  struct tm tmnow;
  if (getLocalTime(&tmnow, 50)) {
    char buf[25];
    strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", &tmnow);
    return String(buf);
  }
  return String("1970-01-01T00:00:00Z");
}

static void postReading(float pressureMPa, float sensorV) {
  if (!g_wifiOk) return;
  String url = String("http://") + SERVER_HOST + ":" + String(SERVER_PORT) + SERVER_PATH;
  String ts = getTimestampISO8601();

  char payload[160];
  snprintf(payload, sizeof(payload),
           "{\"device_id\":\"%s\",\"timestamp\":\"%s\",\"pressure\":%.3f,\"voltage\":%.3f}",
           DEVICE_ID, ts.c_str(), pressureMPa, sensorV);

  HTTPClient http;
  http.begin(url);
  http.addHeader("Content-Type", "application/json");
  int code = http.POST((uint8_t*)payload, strlen(payload));
  if (code > 0) {
    Serial.print(F("[HTTP] POST -> ")); Serial.println(code);
    // 可按需读取响应体：String resp = http.getString();
  } else {
    Serial.print(F("[HTTP] POST failed: ")); Serial.println(http.errorToString(code));
  }
  http.end();
}

// ------------------------------ 主流程 ------------------------------
void setup() {
  Serial.begin(115200);
  delay(50);
  Serial.println();
  Serial.println(F("[BOOT] ESP32 Pressure Monitor"));

  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
  Wire.setClock(400000);

  // 检测与初始化 OLED
  if (i2cDevicePresent(OLED_I2C_ADDR)) {
    if (display.begin(SSD1306_SWITCHCAPVCC, OLED_I2C_ADDR)) {
      g_displayOk = true;
      display.clearDisplay();
      display.setTextColor(SSD1306_WHITE);
      display.setTextSize(1);
      drawCentered("ESP32 Pressure Monitor", 16);
      drawCentered("Init...", 36);
      display.display();
    } else {
      g_displayOk = false;
      Serial.println(F("[ERR] SSD1306 init failed"));
    }
  } else {
    g_displayOk = false;
    Serial.println(F("[ERR] SSD1306 not found on I2C"));
  }

  // 检测并初始化 ADS1015
  if (i2cDevicePresent(ADS1015_I2C_ADDR)) {
    if (ads.begin(ADS1015_I2C_ADDR, &Wire)) {
      g_adsOk = true;
      ads.setGain(ADS_GAIN);
      // 可选：设置数据率（库默认 1600 SPS 对 ADS1015）
      // ads.setDataRate(RATE_ADS1015_1600SPS);
      Serial.println(F("[INFO] ADS1015 initialized"));
    } else {
      g_adsOk = false;
      Serial.println(F("[ERR] ADS1015 init failed (begin)"));
    }
  } else {
    g_adsOk = false;
    Serial.println(F("[ERR] ADS1015 not found on I2C"));
  }

  g_dividerInv = computeDividerInverseGain();
  Serial.print(F("[INFO] Divider inverse gain: "));
  Serial.println(g_dividerInv, 6);

  if (!g_adsOk && g_displayOk) {
    renderError("ADS1015 MISSING", "Check wiring & addr");
  }

  // 连接 Wi?Fi 并尝试时间同步（用于构造 ISO8601 时间戳）
  connectWiFi();
  if (!syncTime()) {
    Serial.println(F("[WARN] NTP time sync failed, using epoch fallback"));
  }
}

void loop() {
  static uint32_t lastSampleMs = 0;
  static uint32_t lastDisplayMs = 0;
  static uint32_t lastHttpPostMs = 0;

  const uint32_t now = millis();

  // 采样
  if (now - lastSampleMs >= SAMPLE_INTERVAL_MS) {
    lastSampleMs = now;
    float adcVolts = NAN;
    bool readingOk = false;
    if (g_adsOk) {
      // 单端通道 A0
      int16_t raw = ads.readADC_SingleEnded(0);
      // Adafruit 库将根据增益给出电压换算
      adcVolts = ads.computeVolts(raw);
      // 粗略有效性检查（若有分压，实际 ADC 端应 <= 4.1V in GAIN_ONE）
      if (isnan(adcVolts) || adcVolts < -0.1f || adcVolts > 6.2f) {
        readingOk = false;
      } else {
        readingOk = true;
      }
    }

    float sensorV = NAN;
    float pressureMPa = 0.0f;
    if (readingOk) {
      // 将 ADC 端电压换算回传感器端电压（若分压）
      sensorV = adcVolts * g_dividerInv;
      // 基于传感器 0.5~4.5V 线性映射压力
      pressureMPa = voltsToPressureMPa(sensorV);
    }

    // 推入平均器
    if (readingOk) {
      g_avgSensorV.push(sensorV);
      g_avgPressure.push(pressureMPa);
    }

    // 串口输出（便于调试）
    if (readingOk) {
      Serial.print(F("Vadc=")); Serial.print(adcVolts, 4);
      Serial.print(F("V, Vs=")); Serial.print(sensorV, 4);
      Serial.print(F("V, P=")); Serial.print(pressureMPa, 4);
      Serial.println(F(" MPa"));
    } else {
      Serial.println(F("[WARN] ADC read invalid"));
    }
  }

  // 显示刷新
  if (now - lastDisplayMs >= DISPLAY_INTERVAL_MS) {
    lastDisplayMs = now;
    const float vs = g_avgSensorV.value();
    const float p  = g_avgPressure.value();
    const bool ok = g_adsOk && (vs > 0.0f);
    if (g_displayOk) {
      if (ok) {
        renderUI(p, vs, true);
      } else if (!g_adsOk) {
        renderError("ADS1015 ERROR", "See Serial log");
      } else {
        renderError("SENSOR INVALID", "Check wiring/range");
      }
    }
  }

  // 定时 HTTP 上报
  if (now - lastHttpPostMs >= HTTP_POST_INTERVAL_MS) {
    lastHttpPostMs = now;
    const float vs = g_avgSensorV.value();
    const float p  = g_avgPressure.value();
    if (g_wifiOk && p >= 0.0f && vs > 0.0f) {
      postReading(p, vs);
    }
  }
}