/**
 * @file main.cpp
 * @author  jonnie Walker
 * @brief   ESP32-S3-test
 * @version 0.1
 * @date 2023-12-19
 * 
 * @copyright Copyright (c) 2023
 * -------------------------------------------------------/
 * 芯片型号：ESP32-S3 模组：N16R8
 * 
 * 调试内容描述：
 * 1.调用现有的OTA的API。进行局域网络的OTA升级功能。
 * 2.使用开源WiFiManager库，通过移动端进行网络配置。
 * 3.板载RGB点亮。
 * 4.在深度睡眠模式下，ESP32-S3会关闭大部分电源，并只保持RTC（实时时钟）外设的供电。当定时器超时后，
 *   ESP32-S3将唤醒并从之前的状态继续执行代码。
 * 5.使用看门狗定时器.如果系统在 15 秒内未能响应，则会触发看门狗定时器，系统将自动重新启动
 *   需要注意的是，在使用看门狗定时器时，我们需要确保在程序的任何地方都不会阻塞时间太长，
 *   否则定时器可能会错误地重置系统。此外，看门狗定时器只是一个应急措施，我们仍需要尽量避免程序出现崩溃或死机的情况。
 * 6.接入远程OTA升级，《使用巴法云平台》，需要提前编译为二进制文件。
 *
 *
 *
 *
 *
 */

#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
//#include <ArduinoOTA.h>
#include <WiFiManager.h>
#include <esp_sleep.h>
#include <esp_task_wdt.h>
#include <NTPClient.h>//网络时间
#include <HTTPUpdate.h>

//--数据定义------------------------------------------/
// 设置NTP服务器
const char *ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 28800; // 这里是你所在时区的偏移量，这里是东一区的偏移量
const int   daylightOffset_sec = 28800;
 //设置以秒为单位的偏移时间以调整您的时区，例如
  // GMT +1 = 3600
  // GMT +8 = 28800
  // GMT -1 = -3600
  // GMT 0 = 0

// Define struct to store time data
struct TimeData {
  uint8_t hours;
  uint8_t minutes;
  uint8_t seconds;
};
TimeData currentTime;

// 定义NTP客户端对象
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, ntpServer, gmtOffset_sec, daylightOffset_sec);

uint8_t count =0;
void printCurrentTime();

// 创建全局对象
WiFiManager wifiManager;

#define WATCHDOG_TIMEOUT 15 //设置了定时器的超时时间为 15 秒

//固件链接，在巴法云控制台复制、粘贴到这里即可
String upUrl = "http://bin.bemfa.com/b/3BcYWFhNTBiMzUzYWFlNGE4YTgyM2IzYjk1OWRmYmYzNmI=E3S2TAPV1.bin";


// 进入深度睡眠模式
void enterDeepSleep() {
  // 设置睡眠时间（微秒）
  //您可以根据需要调整睡眠时间 sleepTime 的值
  uint64_t sleepTime = 10 * 1000000; // 10秒

  // 配置睡眠模式
  esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
  esp_sleep_enable_timer_wakeup(sleepTime);//启用定时器唤醒

  // 进入深度睡眠模式
  esp_deep_sleep_start();
}

void setup() {
  Serial.begin(115200);
  Serial.println("Booting");

  
 
   // 尝试连接保存的WiFi网络
  if (!wifiManager.autoConnect("ESP_S3-AP", "31415926")) {
    Serial.println("Failed to connect and hit timeout");
    delay(3000);
    // 重启设备
    ESP.restart();
    delay(5000);
  }



  // Port defaults to 3232
  // ArduinoOTA.setPort(3232);

  // Hostname defaults to esp3232-[MAC]
  // ArduinoOTA.setHostname("ESP32-S3_Test");

  // No authentication by default
  // ArduinoOTA.setPassword("314159");

  // Password can be set with it's md5 value as well
  // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");
  /*
  ArduinoOTA
    .onStart([]() {
      String type;
      if (ArduinoOTA.getCommand() == U_FLASH)
        type = "sketch";
      else // U_SPIFFS
        type = "filesystem";

      // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
      Serial.println("Start updating " + type);
    })
    .onEnd([]() {
      Serial.println("\nEnd");
    })
    .onProgress([](unsigned int progress, unsigned int total) {
      Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
    })
    .onError([](ota_error_t error) {
      Serial.printf("Error[%u]: ", error);
      if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
      else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
      else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
      else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
      else if (error == OTA_END_ERROR) Serial.println("End Failed");
    });

  ArduinoOTA.begin();
  */

   updateBin();  //开始远程升级

  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP()); //输出IP地址

  
   // 初始化看门狗定时器
  esp_task_wdt_init(WATCHDOG_TIMEOUT, true);
  Serial.println("Watchdog timer started.");
  
  Serial.println("V:1.00.06");

}

void loop() {
   // 等待接收串口1的数据并输出到串口2
  while (Serial.available()>0) {
    char data_c = Serial.read();
    Serial.println(data_c);
    if(data_c == 'A'){
         // 删除保存的网络信息
         wifiManager.resetSettings();
         Serial.println("delete--");
        break;

    }
    else if(data_c == 'B'){
        // 进入深度睡眠模式
        Serial.println("sleep--");
        enterDeepSleep();
        break;
    }
    else if(data_c == 'C'){
      // 定期喂狗
      //触发看门狗定时器，系统将自动重新启动
      Serial.println("WDTdog--");
      esp_task_wdt_reset();
    }
    else if("D"){
       printCurrentTime();
    }

  }


  #ifdef RGB_BUILTIN
  digitalWrite(RGB_BUILTIN, HIGH);   // Turn the RGB LED white
  delay(1000);
  digitalWrite(RGB_BUILTIN, LOW);    // Turn the RGB LED off
  delay(1000);

  neopixelWrite(RGB_BUILTIN,RGB_BRIGHTNESS,0,0); // Red
  delay(1000);
  neopixelWrite(RGB_BUILTIN,0,RGB_BRIGHTNESS,0); // Green
  delay(1000);
  neopixelWrite(RGB_BUILTIN,0,0,RGB_BRIGHTNESS); // Blue
  delay(1000);
  neopixelWrite(RGB_BUILTIN,0,0,0); // Off / black
  delay(1000);
  #endif


  
  //ArduinoOTA.handle(); //处理OTA升级程序

}

//从NTP服务器获取最新的时间----------------------------------------------------/
void printCurrentTime() {
  timeClient.update();
  // Get hours, minutes, and seconds
  currentTime.hours = timeClient.getHours();
  currentTime.minutes = timeClient.getMinutes();
  currentTime.seconds = timeClient.getSeconds();

   // 检查是否读取到完整的时间数据
  if (currentTime.hours >= 0 && currentTime.minutes >= 0 && currentTime.seconds >= 0) {
  
    // 将时间数据转换成字符串形式。调试时用
    String timeString = String( currentTime.hours) + ":" + String(currentTime.minutes) + ":" + String(currentTime.seconds);
    Serial.print("UTC time: ");
    Serial.println(timeString); 

  }
  switch (count)  //不断刷新屏幕上时间数据
  {
    case 1:
     // display_oled(3);
      break;
    
    default:
      break;
  }

}

//当升级开始时，打印日志
void update_started() {
  Serial.println("CALLBACK:  HTTP update process started");
}

//当升级结束时，打印日志
void update_finished() {
  Serial.println("CALLBACK:  HTTP update process finished");
}

//当升级中，打印日志
void update_progress(int cur, int total) {
  Serial.printf("CALLBACK:  HTTP update process at %d of %d bytes...\n", cur, total);
}

//当升级失败时，打印日志
void update_error(int err) {
  Serial.printf("CALLBACK:  HTTP update fatal error code %d\n", err);
}

/**
 * 固件升级函数
 * 在需要升级的地方，加上这个函数即可，例如setup中加的updateBin(); 
 * 原理：通过http请求获取远程固件，实现升级
 */
void updateBin(){
  Serial.println("start update");    
  WiFiClient UpdateClient;
  
  httpUpdate.onStart(update_started);//当升级开始时
  httpUpdate.onEnd(update_finished);//当升级结束时
  httpUpdate.onProgress(update_progress);//当升级中
  httpUpdate.onError(update_error);//当升级失败时
  
  t_httpUpdate_return ret = httpUpdate.update(UpdateClient, upUrl);
  switch(ret) {
    case HTTP_UPDATE_FAILED:      //当升级失败
        Serial.println("[update] Update failed.");
        break;
    case HTTP_UPDATE_NO_UPDATES:  //当无升级
        Serial.println("[update] Update no Update.");
        break;
    case HTTP_UPDATE_OK:         //当升级成功
        Serial.println("[update] Update ok.");
        break;
  }
}