#include <Wire.h>
#include <INA226.h>

INA226 ina(Wire);

// #include <Arduino.h>
// #include "misakiUTF16FontData.h"
#include <LovyanGFX.hpp>
// ESP32でLovyanGFXを独自設定で利用する場合の設定例

/// 独自の設定を行うクラスを、LGFX_Deviceから派生して作成します。
class LGFX : public lgfx::LGFX_Device
{
  lgfx::Panel_ST7789 _panel_instance;
  lgfx::Bus_SPI _bus_instance; // SPIバスのインスタンス
  lgfx::Light_PWM _light_instance;

public:
  // コンストラクタを作成し、ここで各種設定を行います。
  // クラス名を変更した場合はコンストラクタも同じ名前を指定してください。
  LGFX(void)
  {
    {                                    // バス制御の設定を行います。
      auto cfg = _bus_instance.config(); // バス設定用の構造体を取得します。

      // SPIバスの設定
      cfg.spi_host = SPI2_HOST; // 使用するSPIを選択  ESP32-S2,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
      // ※ ESP-IDFバージョンアップに伴い、VSPI_HOST , HSPI_HOSTの記述は非推奨になるため、エラーが出る場合は代わりにSPI2_HOST , SPI3_HOSTを使用してください。
      cfg.spi_mode = 3;                  // SPI通信モードを設定 (0 ~ 3)
      cfg.freq_write = 80000000;         // 送信時のSPIクロック (最大80MHz, 80MHzを整数で割った値に丸められます)
      cfg.freq_read = 16000000;          // 受信時のSPIクロック
      cfg.spi_3wire = true;              // 受信をMOSIピンで行う場合はtrueを設定
      cfg.use_lock = true;               // トランザクションロックを使用する場合はtrueを設定
      cfg.dma_channel = SPI_DMA_CH_AUTO; // 使用するDMAチャンネルを設定 (0=DMA不使用 / 1=1ch / 2=ch / SPI_DMA_CH_AUTO=自動設定)
      // ※ ESP-IDFバージョンアップに伴い、DMAチャンネルはSPI_DMA_CH_AUTO(自動設定)が推奨になりました。1ch,2chの指定は非推奨になります。
      cfg.pin_sclk = 4;                       // SPIのSCLKピン番号を設定
      cfg.pin_mosi = 5;                       // SPIのMOSIピン番号を設定
      cfg.pin_miso = -1;                      // SPIのMISOピン番号を設定 (-1 = disable)
      cfg.pin_dc = 6;                         // SPIのD/Cピン番号を設定  (-1 = disable)
                                              // SDカードと共通のSPIバスを使う場合、MISOは省略せず必ず設定してください。
      _bus_instance.config(cfg);              // 設定値をバスに反映します。
      _panel_instance.setBus(&_bus_instance); // バスをパネルにセットします。
    }

    {                                      // 表示パネル制御の設定を行います。
      auto cfg = _panel_instance.config(); // 表示パネル設定用の構造体を取得します。

      cfg.pin_cs = 8;    // CSが接続されているピン番号   (-1 = disable)
      cfg.pin_rst = 7;   // RSTが接続されているピン番号  (-1 = disable)
      cfg.pin_busy = -1; // BUSYが接続されているピン番号 (-1 = disable)

      // ※ 以下の設定値はパネル毎に一般的な初期値が設定されていますので、不明な項目はコメントアウトして試してみてください。

      cfg.panel_width = 172;    // 実際に表示可能な幅
      cfg.panel_height = 320;   // 実際に表示可能な高さ
      cfg.offset_x = 34;        // パネルのX方向オフセット量
      cfg.offset_y = 0;         // パネルのY方向オフセット量
      cfg.offset_rotation = 3;  // 回転方向の値のオフセット 0~7 (4~7は上下反転)
      cfg.dummy_read_pixel = 8; // ピクセル読出し前のダミーリードのビット数
      cfg.dummy_read_bits = 1;  // ピクセル以外のデータ読出し前のダミーリードのビット数
      cfg.readable = false;      // データ読出しが可能な場合 trueに設定
      cfg.invert = true;       // パネルの明暗が反転してしまう場合 trueに設定
      cfg.rgb_order = false;    // パネルの赤と青が入れ替わってしまう場合 trueに設定
      cfg.dlen_16bit = false;   // 16bitパラレルやSPIでデータ長を16bit単位で送信するパネルの場合 trueに設定
      cfg.bus_shared = false;    // SDカードとバスを共有している場合 trueに設定(drawJpgFile等でバス制御を行います)

      // 以下はST7735やILI9163のようにピクセル数が可変のドライバで表示がずれる場合にのみ設定してください。
      //    cfg.memory_width     =   240;  // ドライバICがサポートしている最大の幅
      //    cfg.memory_height    =   320;  // ドライバICがサポートしている最大の高さ

      _panel_instance.config(cfg);
    }

    {                                      // バックライト制御の設定を行います。(必要なければ削除)
      auto cfg = _light_instance.config(); // バックライト設定用の構造体を取得します。

      cfg.pin_bl = 10;     // バックライトが接続されているピン番号
      cfg.invert = true;   // バックライトの輝度を反転させる場合 true
      cfg.freq = 44100;    // バックライトのPWM周波数
      cfg.pwm_channel = 1; // 使用するPWMのチャンネル番号

      _light_instance.config(cfg);
      _panel_instance.setLight(&_light_instance); // バックライトをパネルにセットします。
    }

    setPanel(&_panel_instance); // 使用するパネルをセットします。
  }
};

static LGFX lcd;
// static LGFX_Sprite sprite(&lcd);

void drawGradation(void)
{
  // 背景にグラデーションを描画する
  lcd.startWrite();
  lcd.setAddrWindow(0, 0, lcd.width(), lcd.height());
  for (int y = 0; y < lcd.height(); ++y)
  {
    for (int x = 0; x < lcd.width(); ++x)
    {
      lcd.writeColor(lcd.color888(x >> 1, (x + y) >> 2, y >> 1), 1);
    }
  }
  lcd.endWrite();
}

void checkConfig()
{
  Serial.print("Mode:                  ");
  switch (ina.getMode())
  {
  case INA226_MODE_POWER_DOWN:
    Serial.println("Power-Down");
    break;
  case INA226_MODE_SHUNT_TRIG:
    Serial.println("Shunt Voltage, Triggered");
    break;
  case INA226_MODE_BUS_TRIG:
    Serial.println("Bus Voltage, Triggered");
    break;
  case INA226_MODE_SHUNT_BUS_TRIG:
    Serial.println("Shunt and Bus, Triggered");
    break;
  case INA226_MODE_ADC_OFF:
    Serial.println("ADC Off");
    break;
  case INA226_MODE_SHUNT_CONT:
    Serial.println("Shunt Voltage, Continuous");
    break;
  case INA226_MODE_BUS_CONT:
    Serial.println("Bus Voltage, Continuous");
    break;
  case INA226_MODE_SHUNT_BUS_CONT:
    Serial.println("Shunt and Bus, Continuous");
    break;
  default:
    Serial.println("unknown");
  }

  Serial.print("Samples average:       ");
  switch (ina.getAverages())
  {
  case INA226_AVERAGES_1:
    Serial.println("1 sample");
    break;
  case INA226_AVERAGES_4:
    Serial.println("4 samples");
    break;
  case INA226_AVERAGES_16:
    Serial.println("16 samples");
    break;
  case INA226_AVERAGES_64:
    Serial.println("64 samples");
    break;
  case INA226_AVERAGES_128:
    Serial.println("128 samples");
    break;
  case INA226_AVERAGES_256:
    Serial.println("256 samples");
    break;
  case INA226_AVERAGES_512:
    Serial.println("512 samples");
    break;
  case INA226_AVERAGES_1024:
    Serial.println("1024 samples");
    break;
  default:
    Serial.println("unknown");
  }

  Serial.print("Bus conversion time:   ");
  switch (ina.getBusConversionTime())
  {
  case INA226_BUS_CONV_TIME_140US:
    Serial.println("140uS");
    break;
  case INA226_BUS_CONV_TIME_204US:
    Serial.println("204uS");
    break;
  case INA226_BUS_CONV_TIME_332US:
    Serial.println("332uS");
    break;
  case INA226_BUS_CONV_TIME_588US:
    Serial.println("558uS");
    break;
  case INA226_BUS_CONV_TIME_1100US:
    Serial.println("1.100ms");
    break;
  case INA226_BUS_CONV_TIME_2116US:
    Serial.println("2.116ms");
    break;
  case INA226_BUS_CONV_TIME_4156US:
    Serial.println("4.156ms");
    break;
  case INA226_BUS_CONV_TIME_8244US:
    Serial.println("8.244ms");
    break;
  default:
    Serial.println("unknown");
  }

  Serial.print("Shunt conversion time: ");
  switch (ina.getShuntConversionTime())
  {
  case INA226_SHUNT_CONV_TIME_140US:
    Serial.println("140uS");
    break;
  case INA226_SHUNT_CONV_TIME_204US:
    Serial.println("204uS");
    break;
  case INA226_SHUNT_CONV_TIME_332US:
    Serial.println("332uS");
    break;
  case INA226_SHUNT_CONV_TIME_588US:
    Serial.println("558uS");
    break;
  case INA226_SHUNT_CONV_TIME_1100US:
    Serial.println("1.100ms");
    break;
  case INA226_SHUNT_CONV_TIME_2116US:
    Serial.println("2.116ms");
    break;
  case INA226_SHUNT_CONV_TIME_4156US:
    Serial.println("4.156ms");
    break;
  case INA226_SHUNT_CONV_TIME_8244US:
    Serial.println("8.244ms");
    break;
  default:
    Serial.println("unknown");
  }

  Serial.print("Max possible current:  ");
  Serial.print(ina.getMaxPossibleCurrent());
  Serial.println(" A");

  Serial.print("Max current:           ");
  Serial.print(ina.getMaxCurrent());
  Serial.println(" A");

  Serial.print("Max shunt voltage:     ");
  Serial.print(ina.getMaxShuntVoltage());
  Serial.println(" V");

  Serial.print("Max power:             ");
  Serial.print(ina.getMaxPower());
  Serial.println(" W");
}

float InitialDataV,InitialDataI;
float ProcessedDataV,ProcessedDataI;
float DataPower;
void setup(void)
{
  lcd.init();
  lcd.startWrite();
  lcd.fillScreen(0xFFFFFFU);
  // drawGradation();

  // lcd.fillScreen(0xFFFFFFU);
  lcd.setBrightness(128);


  Serial.begin(115200);

  Wire.begin(1, 0, 100000);
  // Default INA226 address is 0x40
  bool success = ina.begin();
  // Check if the connection was successful, stop if not
  if (!success)
  {
    Serial.println("Connection error");
    while (1)
      ;
  }
  // Configure INA226
  ina.configure(INA226_AVERAGES_1, INA226_BUS_CONV_TIME_1100US, INA226_SHUNT_CONV_TIME_1100US, INA226_MODE_SHUNT_BUS_CONT);
  // Calibrate INA226. Rshunt = 0.001 ohm, Max excepted current = 4A
  ina.calibrate(0.001, 40);
  // Display configuration
  checkConfig();

  lcd.setTextColor(0xFFFFFFU, 0x000000U);
  lcd.fillScreen(0x000000U);
  
  lcd.setFont(&fonts::Font4);
  lcd.setTextDatum(bottom_center);
  lcd.drawString("V", 180, 50);
  lcd.drawString("A", 180, 108);
  lcd.drawString("W", 180, 166);
  
  InitialDataV=ina.readBusVoltage();
  InitialDataI=ina.readShuntCurrent();
  ProcessedDataV=InitialDataV;
  ProcessedDataI=InitialDataI;

  lcd.setFont(&fonts::Font6);
  lcd.setTextDatum(bottom_right);
}
// 一阶滞后滤波法
#define FILTER_A 0.01
float Filter(float PreData,float NewData) 
{
  float Value;
  Value = (float)NewData * FILTER_A + (1.0 - FILTER_A) * (float)PreData;
  return Value;
}
void DataProcess(void)
{
  InitialDataV=ina.readBusVoltage();
  InitialDataI=ina.readShuntCurrent();
  ProcessedDataV=InitialDataV;
  ProcessedDataI=abs(InitialDataI);
  DataPower=ProcessedDataI*ProcessedDataV;
  // ProcessedDataV=Filter(ProcessedDataV,InitialDataV);
  // ProcessedDataI=abs(Filter(ProcessedDataI,InitialDataI));
}
void DataDisplay(void)
{
  DataProcess();
  if(ProcessedDataV<10.0f)
    lcd.drawFloat(ProcessedDataV, 4, 160, 58);
  else
    lcd.drawFloat(ProcessedDataV, 3, 160, 58);
  lcd.drawFloat(ProcessedDataI, 4, 160, 116);
  if(DataPower<10.0f)
    lcd.drawFloat(DataPower, 4, 160, 174);
  else
    lcd.drawFloat(DataPower, 3, 160, 174);
  // Serial.print(DataPower,5);
}
unsigned short int lastX, lastY;
unsigned char firstPoint = 1;
void drawCurve(short int rawValue)
{
  unsigned short int x, y;
  y = 120 - rawValue / 280; // data processing code
  // 这里之所以是120-rawValue/280,与屏幕的扫描方向有关,如果出现上下颠倒的情况,可以改成120 +
  if (firstPoint) // 如果是第一次画点,则无需连线,直接描点即可
  {
    lcd.drawPixel(0, y);
    lastX = 0;
    lastY = y;
    firstPoint = 0;
  }
  else
  {
    x = lastX + 1;
    if (x < 320) // 不超过屏幕宽度
    {
      lcd.drawLine(lastX, lastY, x, y);
      lastX = x;
      lastY = y;
    }
    else // 超出屏幕宽度,清屏,从第一个点开始绘制,实现动态更新效果
    {
      lcd.fillScreen(0xFFFFFFU); // 清屏,白色背景
      lcd.drawPixel(0, y);
      lastX = 0;
      lastY = y;
    }
  }
}

void loop(void)
{
  DataDisplay();
  delay(100);

}