2018年1月7日 星期日

ESP8266 Automatic Restart Router or Wi-Fi AP



有現成不需銲接電路就可以使用的模組了




網拍搜尋  esp8266 relay

底板加ESP-01 全部不到台幣200元


這有一篇軟體安裝教學,用網頁翻譯一下很好懂

http://nothans.com/measure-wi-fi-signal-levels-with-the-esp8266-and-thingspeak


第一版          第二版

與前兩個版本比較起來增加了網頁功能,可遠端強制重開被監控的設備





如果你的草稿是使用上圖中的USB設備傳送的請記得測試運作的話不要在這個設備上面運作,
否則Wi-Fi斷線後或重新連線時會發生 wdt reset 訊息 RST CAUSE:2, BOOT MODE:(1,7)







以下紅字部分依現場環境調整後上傳


/* for Arduino IDE 1.6.13
   2018-04-10 修正運作中WiFi斷線後重新連線異常部分
   2017-12-01 device x5  
   2017-07-19 for 8266Relay公版,relay off 防呆
   2017-04-24 按鈕依狀態改變顏色
   2017-04-24 add watchdog on/off
   2017-04-23 add Web control Relay
   2017-04-19 WiFi error count change to power Volt
   2017-03-20 可選擇是否啟用 ThingSpeak 紀錄狀態& log Rssi
   2016-10-03 簡化程式碼,增加被測裝置數量
   2016-09-27 功能測試完成
   for  arduinoIDE 1.6.12 支援 ADC_MODE(ADC_VCC 但檔案目錄不支援中文,否則會檢查編譯錯誤

   ** 避免使用 gpio 16 , 以免不穩或無法運作

 big5 to html code   http://www.convertstring.com/zh_TW/EncodeDecode/HtmlEncode

  定義update區域欄位
  1:  0=loss, 1=Device Active,  2= local boot
  2:  0=loss, 1=Device Active,  2= local boot
  3:  0=loss, 1=Device Active,  2= local boot
  4:  0=loss, 1=Device Active,  2= local boot
  5:  Run Level
  6:  reboot count
  7:  Power Volt
  8:  WiFi RSSI
  thingspeak Server 只傳單一區域值第一筆之後的資料沒辦法再進資料庫
  reedited for ESP8266 2016-09-17 by yulie~
  H/W ESP8266
  Network  service  check ipaddress & port
  2015-07-26 by yulie~
*/

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
MDNSResponder mdns;
ESP8266WebServer server(80);
boolean watchDog =1;    // Device ip_1     1=看門狗開啟
boolean logger =  1;    //1=ture 啟用 ThingSpeak紀錄狀態 , 0=false 停用 ThingSpeak紀錄狀態
word chkTime  =  60;    // Sec 檢測連狀態間隔時間
byte actTime  = 120;    //  次  檢測連狀態正常達該次數後發 Active 紀錄 dev0 or dev1
byte wait     =   5;    //  次  檢測連線狀態失敗達該次數後關電重啟 only for Device ip_1
word wifiFail = 600;    // Sec Wi-Fi connect fail  Reboot wait time
byte off_time =  15;    // Sec   設備斷電該秒數之後重開

const char* Ctl_username = "Admin";     //Relay控制認證帳號
const char* Ctl_password = "0487";      //Relay控制認證密碼
const char* wd_username = "admin";      //WatchDog認證帳號
const char* wd_password = "8787";       //WatchDog認證密碼
String webPage = "";                    //網頁內容(rePage)

///*  //*****for my home NEW *****------------------------------------------------------------------
const byte Relay_1  = 0;                          // 使用第0腳控制繼電器 
const byte LED      = 2;                          // 使用第0腳控LED 顯示ip_1 狀態是否在線
const byte D1_pin   = 0;                          //網頁控制的遙控開關腳位 #1
const byte D2_pin   = 2;                          //網頁控制的遙控開關腳位 #2
const char* ssid = "WiFi_SSID";                        // 連線名稱SSID
const char* password = "WiFi_PASSWORD";                        // 連線密碼
char ip_1[] = "www.google.com"; word port_1 = 80; // 被檢查ip位址 & port
char ip_2[] = "192.168.1.31";   word port_2 = 80; // 被檢查ip位址 & port
char ip_3[] = "192.168.1.81";   word port_3 = 23; // 被檢查ip位址 & port
char ip_4[] = "192.168.1.51";   word port_4 = 23; // 被檢查ip位址 & port
char ip_5[] = "192.168.1.61";   word port_5 = 80; // 被檢查ip位址 & port
const char* Host = "ESP8266_Monitor";           //設備名稱
const char* html = "<h1>Device view</h1>";      //網頁標題
const char* D1_sta_1 = "Router ON";             // digitalRead = 1   敘述文字
const char* D1_sta_0 = "Router OFF";            // digitalRead = 0   敘述文字
const char* D2_sta_1 = "  LED  ON";             // digitalRead = 1   敘述文字
const char* D2_sta_0 = "  LED  OFF";            // digitalRead = 0   敘述文字
const String GET = "GET /update?key=XXXXXXXXXXXXX"; //--------- for 網路設備重開記錄
const char* thingspeakID = "-----------";
///*
// Mac address should be different for each device in your LAN
byte arduino_mac[] = { 0x00, 0x09, 0x37, 0x16, 0x89, 0x40 };
IPAddress device_ip  (192, 168,   1,  5);
IPAddress dns_ip     (168,  95, 192,  1);
IPAddress gateway_ip (192, 168,   1, 254);
IPAddress subnet_mask(255, 255, 255,   0);
//*/
//*/ //------------------------------------------------------------------

char serverIP[] = "api.thingspeak.com"; word serverPort = 80;
unsigned long t, u, v, w = 0;
byte actCoun, devFail, status_1, status_1a, status_2, status_2a, status_3, status_3a, status_4, status_4a, status_5, status_5a;;;
word WiFi_fail, reboot_count;
int rssi;
byte old_watchDog;

void setup() {
  t, u, v, w = millis();
  Serial.begin(115200);
  WiFi.hostname(Host); //no set default ESP_XXXXXX (MAC) 
 // WiFi.config(device_ip, gateway_ip, subnet_mask); //disable to  DHCP
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  /*
 if (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("WiFi Connect Failed! Rebooting...10Sec...");
    delay(20000);
    ESP.restart();
  }
*/   if (mdns.begin("esp8266", WiFi.localIP())) {
    Serial.println("MDNS responder started");
  }

  pinMode(Relay_1, OUTPUT); pinMode(LED, OUTPUT);
  // preparing GPIOs
  pinMode(D1_pin, OUTPUT);  digitalWrite(D1_pin, LOW);
  pinMode(D2_pin, OUTPUT);  digitalWrite(D2_pin, LOW);

  for (byte i = 0; i < 6; i++) {
    digitalWrite(LED, !HIGH); delay(500); digitalWrite(LED, !LOW); delay(500); //LED閃5下
  }

  if (logger == 1) updata(2, 2, 2, 2, 2, 0, 9);                 //開機信息

  digitalWrite(LED, chkdev(ip_1, port_1));      //LED 顯示裝置"ip_1"是否連線
//  rePage();

 /* //不認證
  server.on("/", []() { server.send(200, "text/gtml", webPage); });  //無認證
  server.on("/D1_On",  []() {digitalWrite(D1_pin, LOW);  delay(100);    rePage();    server.send(200, "text/html", webPage);  });
  server.on("/D1_Off", []() {digitalWrite(D1_pin, HIGH); delay(100);    rePage();    server.send(200, "text/html", webPage);  });
  server.on("/D2_On",  []() {digitalWrite(D2_pin, LOW);  delay(100);    rePage();    server.send(200, "text/html", webPage);  });
  server.on("/D2_Off", []() {digitalWrite(D2_pin, HIGH); delay(100);    rePage();    server.send(200, "text/html", webPage);  });
 */
  // /*
 //認證不過狀態不會變
  //server.on("/", []() { if (!server.authenticate(www_username, www_password))return server.requestAuthentication(); server.send(200, "text/html", webPage);  });
  server.on("/", []() {rePage(); server.send(200, "text/html", webPage); });
  server.on("/wd_on",  []() { if (!server.authenticate(wd_username, wd_password))return server.requestAuthentication(); watchDog = 1; rePage(); server.send(200, "text/html", webPage); });
  server.on("/wd_off", []() { if (!server.authenticate(wd_username, wd_password))return server.requestAuthentication(); watchDog = 0; rePage(); server.send(200, "text/html", webPage); });
  server.on("/D1_On",  []() { if (!server.authenticate(Ctl_username, Ctl_password))return server.requestAuthentication(); digitalWrite(D1_pin, !HIGH); rePage(); server.send(200, "text/html", webPage); });
  server.on("/D1_Off", []() { if (!server.authenticate(Ctl_username, Ctl_password))return server.requestAuthentication(); digitalWrite(D1_pin, !LOW ); rePage(); server.send(200, "text/html", webPage); });
  server.on("/D2_On",  []() { digitalWrite(D2_pin, HIGH); rePage(); server.send(200, "text/html", webPage); });
  server.on("/D2_Off", []() { digitalWrite(D2_pin, LOW ); rePage(); server.send(200, "text/html", webPage); });
   server.on("/reboot",  []() { if (!server.authenticate(Ctl_username, Ctl_password))return server.requestAuthentication(); rebootPower_1(); rePage(); server.send(200, "text/html", webPage); });
  //*/
  server.begin();  Serial.println("HTTP server started");
  server.begin();
  Serial.print("Open http://");
  Serial.print(WiFi.localIP());
  Serial.println("/ in your browser to see it working");
}

void rePage() {                                               //更新webPage內容
  webPage = html;
//頁面上第1個按鈕,依ON/OFF狀態改變按鈕背景顏色
  webPage += "<p><a href=\"/wd_on\"><button type=\"button\" style=\"background-color:";
  webPage += ((watchDog)?"#FF5050":"#DDDDDD"),                                          //依狀態改變ON按鈕背景顏色
  webPage += ";color:#000000;\">ON</button></a>&nbsp;";                                 //按鈕字型顏色
  webPage += "<a href=\"/wd_off\"><button type=\"button\" style=\"background-color:";
  webPage += ((!watchDog)?"#7FFF7F":"#DDDDDD"),                                         //依狀態改變OFF按鈕背景顏色
  webPage += ";color:#000000;\">OFF</button></a>";                                      //按鈕字型顏色
  webPage += "watchDog: ";
  webPage +=  ip_1;
  webPage += " -> ";
  webPage += ((watchDog)?"ON":"OFF"),       //  digitalRead=1 直接顯示 ON , digitalRead=0 直接顯示 OFF, 
  webPage += "</p>";
//頁面上第2個按鈕,依ON/OFF狀態改變按鈕背景顏色
  webPage += "<p><a href=\"/D1_On\"><button type=\"button\" style=\"background-color:";
  webPage += (!digitalRead( D1_pin )?"#FF5050":"#DDDDDD"),                              //依狀態改變(ON反向)按鈕背景顏色
  webPage += ";color:#000000;\">ON</button></a>&nbsp;";                                 //按鈕字型顏色
  webPage += "<a href=\"/D1_Off\"><button type=\"button\" style=\"background-color:";
  webPage += (digitalRead( D1_pin )?"#7FFF7F":"#DDDDDD"),                               //依狀態改變OFF按鈕背景顏色
  webPage += ";color:#000000;\">OFF</button></a>";                                      //按鈕字型顏色
  webPage += "  #0 Pin ";
  webPage +=  D1_pin;
  webPage += "  -> ";
  webPage += ((digitalRead( D1_pin ))?D1_sta_1:D1_sta_0),     // D1_pin=1 顯示 lampON 字串 ,D1_pind=0 顯示 lampOFF 字串( ! 為使顯示狀態反向,實際上低電位繼電器模組為On)
  webPage += "</p>";
//頁面上第3個按鈕,依ON/OFF狀態改變按鈕背景顏色
  webPage += "<p><a href=\"/D2_On\"><button type=\"button\" style=\"background-color:";
  webPage += (digitalRead( D2_pin )?"#FF5050":"#DDDDDD"),                               //依狀態改變ON按鈕背景顏色
  webPage += ";color:#000000;\">ON</button></a>&nbsp;";                                 //按鈕字型顏色
  webPage += "<a href=\"/D2_Off\"><button type=\"button\" style=\"background-color:";
  webPage += (!digitalRead( D2_pin )?"#7FFF7F":"#DDDDDD"),                              //依狀態改變(OFF反向)按鈕背景顏色
  webPage += ";color:#000000;\">OFF</button></a>";                                      //按鈕字型顏色
  webPage += "  #2 Pin ";
  webPage +=  D2_pin;
  webPage += "  -> ";
  webPage += ((digitalRead( D2_pin ))?D2_sta_1:D2_sta_0),     // digitalRead=1 顯示 lampON 字串 , digitalRead=0 顯示 lampOFF 字串
  webPage += "</p>";

  // webPage += "<p><a href=\"https://thingspeak.com/channels/101630\">View device logger</a></p>"; //加入連結及敘述
   webPage += "<p><a href=\"https://thingspeak.com/channels/";
   webPage +=thingspeakID;
   webPage +="\">View device logger</a></p>";
  // webPage += "<iframe width=\"450\" height=\"260\" style=\"border: 1px solid #cccccc;\" src=\"https://thingspeak.com/channels/101630/charts/1?bgcolor=%23ffffff&color=%23d62020&dynamic=true&results=24&title=%E5%85%A7%E9%83%A8+IP&type=line\"></iframe>";

  webPage += "<p>WiFi RSSI  ";
  webPage += WiFi.RSSI();   
  webPage += " dBm</p>";
  webPage += "<p> </p>""<p>       Power By ESP8266~</p>";                         // 純顯示文字

/*
  //頁面上按鈕,不依ON/OFF狀態改變按鈕背景顏色
  webPage += "<p><a href=\"/wd_on\"><button type=\"button\" style=\"background-color:dddddd;color:#ffffff;\">ON</button></a>&nbsp;";
  webPage += "  <a href=\"/wd_off\"><button type=\"button\" style=\"background-color:#cccccc;color:#ffffff;\">OFF</button></a>";
  webPage += " device: ";
  webPage +=  ip_1;
  webPage += " ->  ";
  webPage += ((watchDog)?"Watchdog ON":"Watchdog OFF"),       //  digitalRead=1 直接顯示 ON , digitalRead=0 直接顯示 OFF, 
  webPage += "</p>";
    
  webPage += "<p><a href=\"D1_On\"><button>ON</button></a>&nbsp;<a href=\"D1_Off\"><button>OFF</button></a>";
  webPage += "  #0 Pin ";
  webPage +=  D1_pin;
  webPage += "  -> ";
  webPage += ((digitalRead( D1_pin ))?D1_sta_1:D1_sta_0),     // D1_pin=1 顯示 lampON 字串 ,D1_pind=0 顯示 lampOFF 字串( ! 為使顯示狀態反向,實際上低電位繼電器模組為On)
  webPage += "</p>";

  webPage += "<p><a href=\"D2_On\"><button>ON</button></a>&nbsp;<a href=\"D2_Off\"><button>OFF</button></a>";
  webPage += "  #2 Pin ";
  webPage +=  D2_pin;
  webPage += "  -> ";
  webPage += ((digitalRead( D2_pin ))?D2_sta_1:D2_sta_0),     // digitalRead=1 顯示 lampON 字串 , digitalRead=0 顯示 lampOFF 字串
  webPage += "</p>";  
  */
}

void loop() {

  
if(millis() - w >=500 && watchDog !=old_watchDog){
  w=millis();
  Serial.print("watchDog =");
  Serial.println(watchDog);
  old_watchDog=watchDog;
  if(digitalRead(Relay_1)==HIGH)rebootPower_1(); // Relay N.C connect (device power down) 2017-07-19 add 防呆
}

  server.handleClient();

//=============== 2018-04-10 add ===========================================================
    if (millis() - t >= 30000 && WiFi.status() != WL_CONNECTED ) {          // 連接到指定的 WiFi SSID 如果連線AP 失敗時執行這一段程式
      t = millis();
      Serial.println("WiFi Connect Failed! wait...30Sec..ReConnect.");
      for (byte i = 0; i < 6; i++) {digitalWrite(LED, HIGH); delay(100); digitalWrite(LED, LOW); delay(100);} //LED閃5下
      delay(30000);
      WiFi_fail++;
      WiFi_fail = constrain ( WiFi_fail, 0, 65534);   //  限制該值在0~100 之間
      Serial.println(WiFi_fail);
       if (WiFi.status() != WL_CONNECTED ){ 
        Serial.println("Esp8266 WiFi ReConnect... ");
        WiFi.begin(ssid, password);
        //ESP.restart();
       } 
       else{
        Serial.println("-------------------------------------->>ESP8266  Wi-Fi Link UP.....  ");
       // if(WiFi_fail != 0 )ESP.restart();
       }
       
      if (WiFi_fail >= wifiFail) {                    //connect Wifi fail over 3min Run
        rebootPower_1();
        WiFi_fail = 0;
      }
    }
//=============== 2018-04-10 add end===========================================================


  if (millis() - u > (chkTime * 1000)) {     //30000 mS on chk
    u = millis();
    actCoun++;
    Serial.println(" ");
    status_1 =  chkdev(ip_1, port_1); //回傳"0"為斷線, 回傳為"1"連線
    status_2 =  chkdev(ip_2, port_2);
    status_3 =  chkdev(ip_3, port_3);
    status_4 =  chkdev(ip_4, port_4);
    status_5 =  chkdev(ip_5, port_5);
    digitalWrite(LED, status_1);      //顯示裝置"ip_1"是否連線


    //任何裝置狀態改變時上傳
    if (status_1 !=  status_1a || status_2 !=  status_2a || status_3 !=  status_3a  || status_4 != status_4a  || status_5 != status_5a) {    //任何裝置狀態改變
      Serial.println(" Detect status change......");
      status_1a = status_1; status_2a = status_2; status_3a = status_3; status_4a = status_4; status_5a = status_5;                     //狀態更新,做為下一次狀態比較用
      if ( status_1 == 1 && logger == 1)updata(status_1, status_2, status_3, status_4, status_5, reboot_count, 6); //上傳
    }

    if (status_1 == 1) devFail = 0;           //斷線計數歸零
    else {
      devFail++;                              //裝置斷線計數,給重開設備判斷用
      actCoun = 0;
    }

    // device 0 or device 1  每60次傳送一次存活信息
    if (actCoun >= actTime && logger == 1 )updata(status_1, status_2, status_3, status_4, status_5, reboot_count,4);

    //裝置矢聯達指定次數則啓動繼電器關機重開
    if (devFail >= wait) {
      if(watchDog == 1)rebootPower_1();     //watchDog 開啟才執行
      devFail = 0;
      WiFi_fail = 0;
    }
    
    //showCount();  //for debug
    //reboot_count != 0 or WiFi_fail != 0 and connect OK send infomation...
    if ((reboot_count != 0 || WiFi_fail != 0) && status_4 == 1 && logger == 1) updata(status_1, status_2, status_3, status_4, status_5, reboot_count,0);
  }
}

//-------------------------------------------------------------------------------void updata()
word updata(word field1, word field2, word field3, word field4, word field5, word field6, word field7) {
  rssi = WiFi.RSSI();
//  float volt = analogRead(A0) * 0.0125 ;
  // volt = (analogRead(A0) * (1.0 / 1024) * 12.83) ; // 220K + 18.6k 分壓 , (220 + 18.6)/18.6=12.83
  WiFiClient client;
  if (client.connect(serverIP, serverPort)) {
    String getStr = GET +
                    "&field1=" + String((float)field1, 0) +     // active 01
                    "&field2=" + String((float)field2, 0) +     // active 02
                    "&field3=" + String((float)field3, 0) +     // active 03
                    "&field4=" + String((float)field4, 0) +     // active 04
                    "&field5=" + String((float)field5, 0) +     // active 05
                    "&field6=" + String((float)field6, 0) +     // reboot_count
                    "&field7=" + String((float)field7, 0) +   //input Volt
                    "&field8=" + String((float)rssi, 2) +    // RSSI
                    " HTTP/1.1\r\n";;
    client.print( getStr );
    client.print( "Host: api.thingspeak.com\n" );
    client.print( "Connection: close\r\n\r\n" );
    delay(10);
    // 處理遠端伺服器回傳的訊息,程式碼可以寫在這裡!
    Serial.println(getStr);
    client.stop();
    actCoun = 0;      //如果狀態傳送就將先前的存活計數歸零
    WiFi_fail  = 0;
    reboot_count = 0;
  }
  else {                               //連線失敗執行此段程式
    Serial.print(ip_1); Serial.print(" : "); Serial.print(port_1); Serial.println(" ThingSpeak Server disconnection  ---- X  ");
  }
  client.stop();
}

//------------------------------------------------------------------------------- check device
byte chkdev(char* IP, word PORT) {
  WiFiClient client; delay(200);
  if (client.connect(IP, PORT)) {    //連線成功執行此段程式
    Serial.print(IP); Serial.print(" : "); Serial.print(PORT); Serial.println(" check device  connected ");
    return 1;  //傳回值"1"
  }

  else if (client.connect(IP, PORT)) {    //第一次連線不成功執行此段程式
     Serial.print(IP); Serial.print(" : "); Serial.print(PORT); Serial.println(" check device  connected ");
    return 1;  //傳回值"1"
  }
  else {                               //兩次連線失敗執行此段程式
    Serial.print(IP); Serial.print(" : "); Serial.print(PORT); Serial.println(" check device  disconnection  ---- X  ");
    return 0;  //傳回值"0"
  }
  
}

//-------------------------------------------------------------------------------status_1, status_2
void showCount() {   //for debug
  Serial.print("status_1 : "); Serial.println(status_1);   Serial.print("status_2 : "); Serial.println(status_2);
  Serial.print("WiFi_fail : ");  Serial.println(WiFi_fail);  Serial.print("reboot_count : "); Serial.println(reboot_count);
  uint32_t getVcc = ESP.getVcc();  float voltaje = ESP.getVcc();  Serial.println("core Volt : "); Serial.print(voltaje / 1024);
  Serial.println(" V");
}

//-------------------------------------------------------------------------------void rebootPower_1()
void rebootPower_1() {
  digitalWrite(Relay_1, HIGH); // Relay N.C connect (device power down)
  Serial.print("power down~  Wait  "); Serial.print(off_time); Serial.println(" Sec ..");
  delay(off_time * 1000);          //device power down (off_time) sec
  digitalWrite(Relay_1, LOW);
  Serial.println("power up");
  reboot_count++;

}








5 則留言:

  1. char ip_2[] = "192.168.1.31"; word port_2 = 80; // 被檢查ip位址 & port
    char ip_3[] = "192.168.1.81"; word port_3 = 23; // 被檢查ip位址 & port
    char ip_4[] = "192.168.1.51"; word port_4 = 23; // 被檢查ip位址 & port
    char ip_5[] = "192.168.1.61"; word port_5 = 80; // 被檢查ip位址 & port

    請問這幾組是外圍的device嗎? 是怎樣的device? 是運行同一個軟件嗎? 可以的話請多提供資料。 謝你。

    回覆刪除
  2. 是附加被監視的網路設備,狀態可以上傳thingspeak
    但是不會影響重開機的判斷~

    回覆刪除
  3. 謝你回覆
    被監視附加的網絡設備也是採用ESP-01嗎? 是如何監視它的狀態? 如你的另一網絡設備192.168.1.31, 假設是作為另一組偵測山頂中繼站電源, 從這程式是怎樣實現監視山頂中繼站電源的狀態?

    char ip_2[] = "192.168.1.31"; word port_2 = 80; // 被檢查ip位址 & port

    回覆刪除
  4. 其實不是真的去偵測電源的狀態,而是偵測網路設備是否正常運作,算是間接的偵測電源

    被監視的網絡設備只要是有提供TCP/IP功能的都可以,例如中繼站電源的偵測方式並不需要真的在中繼站裝設備

    我這邊實務上的例子是在山頂的中繼站擺放兩個無線AP做為訊號的轉發,而我只要在同一個網段內可以偵測到轉發AP的網頁,也就是port:80 那就表示設備是運作正常的

    有一些網路設備沒有提供網頁設定的功能,有可能是用telnet登入,那麼偵測的port就要改到port:23

    回覆刪除
  5. 那我有點明白了, 謝你~

    回覆刪除