#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <HTTPUpdate.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>
#include <Ticker.h>
#include <esp_sleep.h>
#include <FS.h>
#include <SPIFFS.h>
#include <WebServer.h>
#define doorOpen 22       //升
#define doorClose 21      //降
#define doorOpenEXTI 26   //升 中断
#define doorCloseEXTI 25  //降 中断
#define dhtPin 0          //温湿度传感器
#define DHTTYPE DHT11     //传感器类型
DHT_Unified dht(dhtPin, DHTTYPE);

#define billboard1 4           //广告牌开关
#define billboard2 13          //寄快递广告牌
bool guangGaoPaiBool = false;  //guangGaoPaiBool为false代表没有开灯箱 true为已开启灯箱

#define light 2       //灯
#define gpio17 17     //播报电源开关
#define doorPower 15  //电源总开关
String version = "1.24.7";
String ssid = "";                 //wifi账号
String passwd = "";               //wifi密码
String html;                      //存html
String flash_wifi = "/wifi.txt";  //被读取的文件位置和名称
WebServer webserver;

volatile int doorStroke = 5;   //记录门静止状态 处于什么位置 10为上限  0为下限 5为可以上升可降
volatile bool runIng = false;  //true 为电机正在运行
volatile int command = 2;      //此时执行的指令  0关门  1开门  2停止
// 函数声明
void IRAM_ATTR handleInterruptOpen();
void IRAM_ATTR handleInterruptClose();

volatile bool extiOpen = false;   //true 代表上升中断触发了
volatile bool extiClose = false;  //true 代表下降中断触发了
volatile unsigned long extiTime = 0;


WiFiClient wificlient;

Ticker myTicker;

const char* mqtt_server = "192.168.0.254";
const char* mqtt_username = "door1";
const char* mqtt_password = "abcdefg";
const char* subTopic = "wifi/door1/control";
const char* pubshTopic = "wifi/door1/data";
PubSubClient client(wificlient);

String upBinUrl = "http://" + String(mqtt_server) + "/tool/ota/GolangCpp/mydoor1.bin";
void setup(void) {
  pinMode(doorOpen, OUTPUT);    //门上升
  pinMode(doorClose, OUTPUT);   //门下降
  pinMode(billboard1, OUTPUT);  //广播牌
  pinMode(gpio17, OUTPUT);      //语音播报
  pinMode(light, OUTPUT);
  pinMode(doorPower, OUTPUT);  //总电源
  pinMode(billboard2, OUTPUT);

  digitalWrite(doorOpen, LOW);
  digitalWrite(doorClose, LOW);
  digitalWrite(billboard1, LOW);
  digitalWrite(gpio17, LOW);
  digitalWrite(light, LOW);
  digitalWrite(doorPower, LOW);
  digitalWrite(billboard2, LOW);

  pinMode(doorOpenEXTI, INPUT_PULLUP);  //上拉电阻模式,默认高电平
  pinMode(doorCloseEXTI, INPUT_PULLUP);

  // 附加中断服务函数
  attachInterrupt(digitalPinToInterrupt(doorOpenEXTI), handleInterruptOpen, CHANGE);
  attachInterrupt(digitalPinToInterrupt(doorCloseEXTI), handleInterruptClose, CHANGE);


  Serial.begin(115200);
  //pinMode(flash_rst, INPUT_PULLUP);
  delay(200);
  dht.begin();
  sensor_t sensor;
  dht.temperature().getSensor(&sensor);
  dht.humidity().getSensor(&sensor);
  delay(500);

  SPIFFS.begin();
  if (SPIFFS.exists(flash_wifi) == false) {
    SPIFFS.begin();
    File dataFile = SPIFFS.open(flash_wifi, "w");
    dataFile.println("webserver&www.nobiggie.cn");
    dataFile.close();
  }

  if (FlashConnetWifi() == false) {  //自动flash联网不成功 就启用 html配置wifi账号密码
    HtmlSetWifi();
  }
  Serial.println("wifi ok");
  client.setServer(mqtt_server, 1883);
  client.setCallback(mqttCallback);
  delay(200);
  if (digitalRead(doorOpenEXTI) == LOW && digitalRead(doorCloseEXTI) == LOW) {
    doorStroke = 5;
  } else {
    if (digitalRead(doorOpenEXTI) == HIGH) {
      doorStroke = 10;
    }
    if (digitalRead(doorCloseEXTI) == HIGH) {
      doorStroke = 0;
    }
  }
}

void loop(void) {
  if (!client.connected()) {
    digitalWrite(doorOpen, LOW);
    digitalWrite(doorClose, LOW);

    //防止开启突然没有网络,lop循环检测不到状态 这里再进行检测
    if (digitalRead(doorOpenEXTI) == HIGH && doorStroke != 10) {
      doorStroke = 10;
      closeAll();
      //Serial.println("Disconnected  上限关闭");
    }
    //查询是否下降 限制,及时把状态改好
    if (digitalRead(doorCloseEXTI) == HIGH && doorStroke != 0) {
      doorStroke = 0;
      closeAll();
      //Serial.println("Disconnected  下限关闭");
    }
    reconnect();
  }
  client.loop();
  if (extiOpen) {      //上升中断触发
    extiOpen = false;  //复位
    if (command == 1) {
      doorStroke = 10;               //位置变为上限
      command = 2;                   //命令变为停止
      extiTime = 0;                  //防抖复位
      digitalWrite(doorPower, LOW);  //断开电源接通
      const char* tmp = "上升中断触发了";
      client.publish(pubshTopic, tmp);
    }
    return;
  }
  if (extiClose) {      //下降中断触发
    extiClose = false;  //复位
    if (command == 0) {
      doorStroke = 0;                 //位置变为下限
      command = 2;                    //命令变为停止
      extiTime = 0;                   //防抖复位
      digitalWrite(doorPower, LOW);   //断开电源接通
      digitalWrite(billboard1, LOW);  //关门就把广告灯箱关了
      const char* tmp = "下降中断触发了";
      client.publish(pubshTopic, tmp);
    }
    return;
  }
}
void IRAM_ATTR handleInterruptOpen() {
  unsigned long currentTime = millis();
  if (command == 1) {     //上升命令
    if (extiTime == 0) {  //上升中断 防抖 麻痹时间计数开始
      extiTime = currentTime;
    } else {
      // 消抖处理
      if (currentTime - extiTime > 1500) {
        if (digitalRead(doorOpenEXTI) == HIGH) {
          digitalWrite(doorOpen, LOW);
          if (command == 1) {              //此时执行中的状态  0关门  1开门  2停止 5中断正常触发模式  9什么也不代表
            digitalWrite(doorPower, LOW);  //断开电源接通
          }
          //开门中断
          extiOpen = true;
          //中断服务程序（ISR）必须简短，避免调用耗时函数（如串口打印、网络通信），否则可能引发看门狗超时或系统重启
        }
      }
    }
  }
}


void IRAM_ATTR handleInterruptClose() {
  unsigned long currentTime = millis();
  if (command == 0) {
    if (extiTime == 0) {
      extiTime = currentTime;
    } else {
      // 消抖处理
      if (currentTime - extiTime > 1500) {
        if (digitalRead(doorCloseEXTI) == HIGH) {
          digitalWrite(doorClose, LOW);
          if (command == 0) {              //0关门 状态
            digitalWrite(doorPower, LOW);  //断开电源接通
          }
          //关门中断
          extiClose = true;
          //中断服务程序（ISR）必须简短，避免调用耗时函数（如串口打印、网络通信），否则可能引发看门狗超时或系统重启
        }
      }
    }
  }
}
void mqttCallback(char* topic, byte* payload, unsigned int length) {
  String msg;
  for (int i = 0; i < length; i++) {
    msg += (char)payload[i];
  }
  if (length > 0) {
    if (msg == "open") {
      if (doorStroke == 10) {
        runIng = false;
        command = 2;
        const char* tmp = "无效,上升达到限制doorStroke == 10";
        client.publish(pubshTopic, tmp);
      }
      if (digitalRead(doorOpenEXTI) == HIGH) {
        runIng = false;
        command = 2;
        const char* tmp = "无效,上升达到限制";
        client.publish(pubshTopic, tmp);
        return;
      }

      digitalWrite(doorPower, HIGH);  //主电源接通
      delay(200);
      //开 上升
      const char* tmp = "正在开门 ...";
      client.publish(pubshTopic, tmp);
      digitalWrite(doorClose, LOW);
      delay(50);
      digitalWrite(doorOpen, HIGH);
      runIng = true;
      command = 1;

      return;
    }
    if (msg == "stop") {
      closeAll();
      runIng = false;
      command = 2;
      String tempstr;
      String rez;
      //查询是否下降 限制,及时把状态改好
      switch (doorStroke) {
        case 10:
          if (runIng) {  //如果在电机运行了按停止.说明已经在中间
            doorStroke = 5;
            client.publish(pubshTopic, "悬停播报模式");
          } else {
            tempstr = guangGaoPaiBool == true ? "灯箱亮" : "灯箱关";
            rez = "开门达到限制 " + tempstr;
            client.publish(pubshTopic, rez.c_str());
          }
          break;
        case 0:
          if (runIng) {
            doorStroke = 5;
            const char* tmp = "悬停播报模式";
            client.publish(pubshTopic, tmp);
          } else {
            tempstr = guangGaoPaiBool == true ? "灯箱亮" : "灯箱关";
            rez = "关门达到限制 " + tempstr;
            client.publish(pubshTopic, rez.c_str());
          }

          break;
        case 5:
          rez = "悬停播报模式";
          client.publish(pubshTopic, rez.c_str());
          break;
        default:
          if (runIng) {
            doorStroke = 5;
            rez = "悬停播报模式0";
            client.publish(pubshTopic, rez.c_str());
          } else {
            if (digitalRead(doorOpenEXTI) == LOW && digitalRead(doorCloseEXTI) == LOW) {
              doorStroke = 5;
              rez = "悬停播报模式1";
              client.publish(pubshTopic, rez.c_str());
            } else {
              if (digitalRead(doorOpenEXTI) == HIGH) {
                doorStroke = 10;
                tempstr = guangGaoPaiBool == true ? "灯箱亮" : "灯箱关";
                rez = "开门达到限制1 " + tempstr;
                client.publish(pubshTopic, rez.c_str());
              }
              if (digitalRead(doorCloseEXTI) == HIGH) {
                doorStroke = 0;
                tempstr = guangGaoPaiBool == true ? "灯箱亮" : "灯箱关";
                rez = "关门达到限制1 " + tempstr;
                client.publish(pubshTopic, rez.c_str());
              }
            }
          }
      }
      return;
    }
    if (msg == "close") {
      //限位开关导通时为高电平达到限位
      if (doorStroke == 0) {
        runIng = false;
        command = 2;
        const char* tmp = "无效,关门达到限制doorStroke==0";
        client.publish(pubshTopic, tmp);
        return;
      }
      if (digitalRead(doorCloseEXTI) == HIGH) {
        runIng = false;
        command = 2;
        const char* tmp = "无效,关门达到限制";
        client.publish(pubshTopic, tmp);
        return;
      }
      //主电源接通
      digitalWrite(doorPower, HIGH);
      delay(200);
      //关 下降
      const char* tmp = "正在关门 ...";
      client.publish(pubshTopic, tmp);
      digitalWrite(doorOpen, LOW);
      digitalWrite(doorClose, HIGH);
      runIng = true;
      command = 0;
      return;
    }

    if (msg == "temperatureHumidity") {
      float temperature;
      float humidity;
      sensors_event_t event;
      dht.temperature().getEvent(&event);
      if (isnan(event.temperature)) {
        Serial.println(F("Error reading temperature!"));
      } else {
        temperature = event.temperature;
      }
      delay(500);
      // Get humidity event and print its value.
      dht.humidity().getEvent(&event);
      if (isnan(event.relative_humidity)) {
        Serial.println(F("Error reading humidity!"));
      } else {
        humidity = event.relative_humidity;
      }
      String th = String(temperature) + "," + String(int(humidity));
      client.publish(pubshTopic, th.c_str());
      return;
    }
    if (msg == "billboard1:on") {
      guangGaoPaiBool = true;
      digitalWrite(billboard1, HIGH);
      String strtem = "系统任务 广告牌1灯已开";
      client.publish(pubshTopic, strtem.c_str());
      return;
    }
    if (msg == "billboard1:off") {
      guangGaoPaiBool = false;
      digitalWrite(billboard1, LOW);
      String strtem = "系统任务 广告牌1灯已关";
      client.publish(pubshTopic, strtem.c_str());
      return;
    }
    if (msg == "sleep") {  //休眠3个小时
      espSleep();
      return;
    }

    if (msg == "gpio17:on") {  //播报开
      digitalWrite(gpio17, HIGH);
      String strtem = "goip17已开";
      client.publish(pubshTopic, strtem.c_str());
      return;
    }
    if (msg == "gpio17:off") {  //播报关 按停止键也是会关的
      digitalWrite(gpio17, LOW);
      String strtem = "gpio17已关";
      client.publish(pubshTopic, strtem.c_str());
      return;
    }

    if (msg == "reboot") {
      ESP.restart();
      return;
    }
    if (msg == "version") {
      client.publish(pubshTopic, version.c_str());
      return;
    }
    if (msg == "updateBin") {
      // 执行 OTA 更新前的准备工作
      noInterrupts();                                         // 关闭全局中断
      detachInterrupt(digitalPinToInterrupt(doorOpenEXTI));   // 禁用特定中断源（如果需要）
      detachInterrupt(digitalPinToInterrupt(doorCloseEXTI));  // 禁用特定中断源（如果需要）
      delay(200);
      httpUpdate.onStart(update_started);
      httpUpdate.onEnd(update_finished);
      httpUpdate.onProgress(update_progress);
      httpUpdate.onError(update_error);
      t_httpUpdate_return ret = httpUpdate.update(wificlient, upBinUrl);

      interrupts();  // 恢复全局中断
      espReboot();
    }
    if (msg == "billboard2:on") {  //13脚高电平 广告牌2开启
      digitalWrite(billboard2, HIGH);
      const char* strtem = "系统任务 广告牌2已开";
      client.publish(pubshTopic, strtem);
      return;
    }
    if (msg == "billboard2:off") {  //13脚低电平 广告牌2关闭
      digitalWrite(billboard2, LOW);
      const char* tmp = "系统任务 广告牌2已关";
      client.publish(pubshTopic, tmp);
      return;
    }
    if (msg == "light:on") {
      digitalWrite(light, HIGH);
      const char* strtem = "灯 已开";
      client.publish(pubshTopic, strtem);
      return;
    }
    if (msg == "light:off") {
      digitalWrite(light, LOW);
      const char* strtem = "灯 已关";
      client.publish(pubshTopic, strtem);
      return;
    }
    if (!runIng) {
      Serial.write((const uint8_t*)payload, length);
    }
    // client.publish(pub,str.c_str());
  }
}

void reconnect() {
  while (!client.connected()) {
    String clientId = "ESP32Client-";
    clientId += String(random(0xffff), HEX);
    if (client.connect(clientId.c_str(), mqtt_username, mqtt_password)) {  // 无认证时留空
      //Serial.println("MQTT 连接成功");
      client.subscribe(subTopic);
    } else {
      Serial.print("连接失败, rc=");
      Serial.print(client.state());
      delay(5000);
    }
  }
}
//关闭上升,下降,变压器主电源
void closeAll() {
  digitalWrite(doorOpen, LOW);
  digitalWrite(doorClose, LOW);
  delay(200);
  digitalWrite(doorPower, LOW);  //断开电源接通
}
void espReboot() {
  ESP.restart();
}
void espSleep() {
  closeAll();
  delay(200);
  const char* tmp = "door1进入休眠状态,3小时后醒...";
  client.publish(pubshTopic, tmp);
  delay(200);
  WiFi.disconnect();
  myTicker.detach();
  delay(200);
  //进入深睡眠状态，将在 3 小时后唤醒
  esp_sleep_enable_timer_wakeup(10800000000);
  esp_deep_sleep_start();
}
void update_started() {
  // client.publish(pubshTopic, "CALLBACK:  HTTP update process started");
}

void update_finished() {
  // client.publish(pubshTopic, "CALLBACK:  HTTP update process finished");
}

void update_progress(int cur, int total) {
  // client.publish(pubshTopic, "CALLBACK:  HTTP update process");
}

void update_error(int err) {
  // client.publish(pubshTopic, "CALLBACK:  HTTP update fatal");
}

//无网特殊状态下安全限位
void html_whlie() {
  //操作运行时 上升达到限位了,并且上次状态不是上升上限, 并且执行状态是上升 限位开关断开
  if (digitalRead(doorOpenEXTI) == HIGH || digitalRead(doorCloseEXTI) == HIGH) {
    //关闭所有开关
    closeAll();
    Serial.println("达到上限");
    myTicker.detach();
    delay(200);
    ESP.restart();
  }
}


void SetWifi() {
  ssid = webserver.arg(0);
  passwd = webserver.arg(1);
  String sp = ssid + "&" + passwd;
  Serial.println(sp);
  Serial.println("密码在前面");
  Serial.begin(115200);
  delay(500);
  const char* Ssid = ssid.c_str();
  const char* WifiPw = passwd.c_str();
  WiFi.begin(Ssid, WifiPw);
  WiFi.mode(WIFI_STA);
  delay(200);
  Serial.println("wifi_connet");
  int ii = 0;
  for (ii; ii < 20; ii++) {
    delay(500);
    Serial.print(".");
    if (WiFi.status() == WL_CONNECTED) {
      webserver.send(200, "text/plane", "Wifi密码正确,ESP准备重启");
      webserver.close();
      delay(500);
      WiFi.disconnect();
      delay(500);

      //写入数据
      SPIFFS.format();
      delay(500);
      SPIFFS.begin();
      File dataFile = SPIFFS.open(flash_wifi, "w");
      dataFile.println(sp);
      dataFile.close();

      Serial.println("HTML连接成功");
      delay(500);
      ESP.restart();
    }
  }
  webserver.send(200, "text/plane", "Wifi密码错误,请重新输入");
}
//------- 展示页面-------
void handlejsonArr() {
  webserver.send(500, "text/html", html);
}
void handleNotFound(void) {
  webserver.send(404, "text/plain", "404-The page is not Found!");
}

void ApStart() {
  WiFi.disconnect();
  delay(500);
  Serial.begin(115200);
  WiFi.mode(WIFI_AP);
  IPAddress local_IP(192, 168, 168, 2);
  IPAddress gateway(192, 168, 168, 1);
  IPAddress subnet(255, 255, 255, 0);
  WiFi.softAPConfig(local_IP, gateway, subnet);

  //启动AP模式,并设置账号和密码
  WiFi.softAP("192.168.168.2", "");
}
void webserverStart() {
  //-----------------启动webserver网络服务--------------------
  webserver.begin(80);               // 启用网络服务
  webserver.on("/", handlejsonArr);  // / 在HTTP中,表示请求首页服务,
  webserver.on("/SetWifi", SetWifi);
  webserver.onNotFound(handleNotFound);  // 当服务器没找到所需的服务,调用404函数
  Serial.println("webserver started");
}

void ForPing() {
  //定时时间到了,先取消定时状态
  myTicker.detach();
  closeAll();
  delay(500);

  ESP.restart();
}
//扫描附件WIFI
void WifiScan() {
  Serial.begin(115200);
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);
  delay(100);
  int n = WiFi.scanNetworks();
  if (n > 0) {
    Serial.println(n);
    String a = "附近WiFi <select id=\"ssid\">";
    for (int i = 0; i < n; i++) {
      Serial.println(WiFi.SSID(i));
      a = a + "<option value=\"" + WiFi.SSID(i) + "\">" + WiFi.SSID(i) + "</option>";
      delay(50);
    }
    String HtmlStart = String("") + "<!DOCTYPE html>" + "<html>" + "<head>" + "<meta charset=\"utf-8\">" + "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no\" />" + "<title>设置WiFi</title>" + "<link rel=\"stylesheet\" href=\"\">" + "</head>" + "<script>" + "onload  = function() {" + "document.getElementById(\"up\").onclick = function(){" + "var ssida=document.getElementById(\"ssid\").value;" + "var ssid=ssida.replace(/\\s+/g, \" \");" + "var passwda=document.getElementsByTagName('input')[0].value;" + "var passwd=passwda.replace(/\\s+/g, \" \");" + "xhr = new XMLHttpRequest();" + "xhr.open(\"POST\",\"./SetWifi\",true);" + "var posts = (\"ssid=\"+ssid+\"&passwd=\"+ passwd);" + "xhr.setRequestHeader(\"Content-Type\",\"application/x-www-form-urlencoded\");" + "xhr.send(posts);" + "xhr.onreadystatechange = function(){" + "if(this.readyState == 4 && this.status == 200){" + "alert(this.responseText);" + "}" + "}" + "}" + "}" + "</script>" + "<body>";
    String HtmlEnd = String("") + "<p>WiFi密码<input type=\"text\" id=\"passwd\"></p>" + "<input type=\"submit\" id=\"up\" value=\"设 置\">" + "<hr/>" + "<font color=\"red\">附近WiFi没有显示到自己的WIFI请重启或离信号源近点</font>" + "</body>" + "</html>";
    a = a + "</select>";
    html = HtmlStart + a + HtmlEnd;
  } else {
    html = "扫描不到网络";
    Serial.println("扫描不到网络");
  }
}
bool FlashConnetWifi() {
  Serial.begin(115200);
  delay(500);
  //是否存在数据
  SPIFFS.begin();
  if (SPIFFS.exists(flash_wifi) == false) {
    Serial.print(flash_wifi);
    Serial.println(" NOT FOUND");
    return false;
  }

  File dateFile = SPIFFS.open(flash_wifi, "r");
  String result = dateFile.readString();
  result.trim();
  Serial.println(result);
  int i = result.indexOf("&");
  String wifiname = result.substring(0, i);
  String wifipw = result.substring(i + 1);
  const char* Ssid = wifiname.c_str();
  const char* WifiPw = wifipw.c_str();
  Serial.println(Ssid);
  Serial.println(WifiPw);
  WiFi.begin(Ssid, WifiPw);
  delay(500);
  WiFi.mode(WIFI_STA);
  Serial.println("FlashConnetWifi->connet");
  int wifiTimeout = 0;
  for (wifiTimeout; wifiTimeout < 30; wifiTimeout++) {
    delay(500);
    Serial.print(" . ");
    if (WiFi.status() == WL_CONNECTED) {
      Serial.println("FlashConnetWifi 闪存配置连接成功");
      return true;
    }
  }
  Serial.println("FlashConnetWifi-> timeout");
  return false;
}

void HtmlSetWifi() {
  WifiScan();
  ApStart();
  delay(1000);
  Serial.println("启动网页");
  myTicker.detach();
  delay(200);
  //5分后重启 防止停电后复电,路由器连接WIFI速度慢于ESP导致ESP连接不到路由器进入无限AP模式,所以这里只让AP 激活5分钟后就重启
  myTicker.once(300, espReboot);
  delay(200);
  webserverStart();
  while (1) {
    webserver.handleClient();  // 检查网络服务请求并处理
  }
}
