Example sketches for the new Arduino IDE for ESP8266

Moderator: igrr

User avatar
By UrsEppenberger
#69117 Thanks for the offer, I'm only too willing to take it up.
The whole code is below.

Some of the configuration is in SPIFFS, stored as a JSON formatted string. But from the variable names it should be obvious what I fetch from there and how it is used.

And thanks for the link. I had a look at it. The scheduler looks interesting. You use the well known and proven MQTT library from knolleary. It does not support TLS encryption which I need in my environment. Else I would have just taken your code and be done with it.

As for error logs. I do not have any. The sonoff devices run on 220V mains, it is not adviseable to have them connected via the serial interface to the USB plug of my laptop. Which leaves me without any logging. I tried the Wifi terminal interface using the development phase, but deleted it from the operational code since it has no password protection.

The MQTT broker log (mosquitto on a Raspberry Pi) shows that the device does not send any keepalive messages and disconnects the device. So there is no useful information neither.

The core problem is the unreliable WiFi I have to cope with. From what I see the main loop of the code is still running fine. The code from the ticker routine which checks the button and switches the relay still works fine. But the whole WiFi and MQTT connection is broken, no communication with the device using WiFi is possible. But the tests at the beginning of the main loop do not detect the WiFi failure and therefore do not execute the recovery code.

Any help is very much appreciated.

Kind regards,

Urs.


Code: Select all/* For Sonoff POW and Sonoff TH devices

   Startup function setup() :
   read device config from flash memory (top 64k)
   connect to WiFi and MQTT Server

   in main programm loop():
     verify WiFi & MQTT connection, try to recover and reboot if not working
     poll sensors and publish values every 10 seconds
     do householding tasks

   if a MQTT message arrives on topic command/'device' :
     verify payload, publish warning to input/'device' if corrupt
     act on payload command:
     1 relay:     set relay and publish confirmation
     2 config:    retrieve config and save config in SPIFFS, publish confirmation
     3 sw update: fetch new firmware using HTTP and restart
     4 restart:   restart

   in a timer function every 50ms
     read button status and switch relay if pressed longer than 1 sec

   In addition it offers:
   - Firmware over the air update FOTA, secured by key
   - save relay state in EEPROM to set the same state after power up or restart

   Compile Options for Arduino IDE: CPU Frequency:  160MHz, Flash Size: 1M (64k SPIFFS),  Board: Generic ESP8266
*/

#include <ESP8266WiFi.h>                  // needed for the WiFi communication
#include <ESP8266mDNS.h>                  // for FOTA Suport
#include <WiFiUdp.h>                      //    ditto
#include <ArduinoOTA.h>                   //    ditto
#include <Ticker.h>                       // interrupt based scheduler
#include <EEPROM.h>                       // to save RELAY state accross power outage
#include <ArduinoJson.h>                  // to parse and safe the values in the config file
#include "FS.h"                           // file system to safe the config in the upper 64k of memory
#include <PubSubClient.h>                 // Imroy MQTT library, set fix port to 8883 in source code!
#include <power.h>                        // for sonoff POW measurement routines
#include "DHT.h"                          // for sonoff TH10 which uses the temperature sensor DHT22
#include <ESP8266HTTPClient.h>            // for Firmware Update using HTTP
#include <ESP8266httpUpdate.h>            //    ditto

const char* Version = "{\"Version\":\"Sonoff_Generic_v1i-2\"}";
const char* msg_measure_fail = "{\"Status\":\"measurement failed, retry in 10s.\"}";

unsigned long lastMeasurement = 0;        // counter for sensor measurements
unsigned long now;                        // will contain actual time
unsigned long pressCount = 0;             // contains the time the button is pressed
bool publishRelay = false;                // tells main loop to publish relay status
uint32_t ip_sensor;                       // for the static IP address of the device
uint32_t ip_gateway;                      // for the static IP address of the router/gateway
uint32_t ip_subnet;                       // for the subnet mask 255.255.255.0
String input_topic;                       //    input/'device'    for data from device to broker
String cmd_topic;                         //    command/'device'  for data from broker to device
String payload_str;                       //    used to compose message to the broker

uint8_t RELAY = 12;                       // for Sonoff POW and Sonoff TH : Relay and red LED
uint8_t LED = 15;                         // for Sonoff POW and Sonoff TH : blue LED
uint8_t BUTTON = 0;                       // for Sonoff POW and Sonoff TH : button
String DType;                             // contains the device type (sonoffpow, sonoffth, test)
unsigned long MsgNumber = 0;
bool WiFiRestart;
String HostnameWifi;

const char* Hostname;
const char* MQTT_User;
const char* MQTT_PW;
const char* MQTT_Broker;
const char* WiFi_SSID;
const char* WiFi_PW;


WiFiClientSecure TCP;                     // TCP client object, uses SSL/TLS
PubSubClient mqttClient(TCP);             // MQTT client object
ESP8266PowerClass powRead;                // power measurement object
Ticker buttonTimer;                       // object to read button status
DHT dht(14, 22);                          // DHT22 Type Sensor on Pin 14 for Sonoff TH devices

boolean parseIP(uint32_t* addr, const char* str) {                // Converts IP address string to 4 byte
  uint8_t *part = (uint8_t*)addr;                                 //    found on the net, no clue how this works
  byte i;
  *addr = 0;
  for (i = 0; i < 4; i++) {
    part[i] = strtoul(str, NULL, 10);                             //    convert one byte
    str = strchr(str, '.');
    if (str == NULL || *str == '\0') break;                       //    no more separators, exit
    str++;                                                        //    point to next character after separator
  }
  return (i == 3);                                                //    true if there were 4 values separated by dots
}                                                                 // end IP address conversion

void command_callback(const MQTT::Publish& pub) {                 // Callback is called when MQTT messages is received
  String command = pub.payload_string();                          //   put payload into string for processing
  StaticJsonBuffer<700> commandBuffer;
  JsonObject& root = commandBuffer.parseObject(command);
  const char* Command = root["Command"];                          //   get command number from JSON string 
  int command_nr = String(Command).toInt();
   
  if (command_nr == 1) {                                          //   command: set relay
    const char* Relay1 = root["Relay1"];
    if (strncmp(Relay1, "on", 2) == 0) {                          //      check command and act
      digitalWrite(RELAY, HIGH);                                  //      turn relay on
      publishRelay = true;                                        //      do the MQTT publish in main loop
    } else {
      digitalWrite(RELAY, LOW);                                   //      turn relay off
      publishRelay = true;                                        //      do the MQTT publish in main loop
    }
     
  } else if (command_nr == 2) {                                   //   command: load new configfunction
    File wconfigFile = SPIFFS.open("/config.json", "w");          //     write to config file in SPIFFS
    root.printTo(wconfigFile);
    mqttClient.publish(input_topic, "{\"Status\":\"config updated\"}");
     
  } else if (command_nr == 3) {                                   //   command: load new firmware
    buttonTimer.detach();                                         //     detach Ticker routine for button handling, not needed anymore
    const char* FW = root["FW"];
    String FW_URL = "http://192.168.11.71/sw/" + String(FW);
    mqttClient.publish(input_topic, "{\"Status\":\"loading new firmware: "+FW_URL+"\"}");
    mqttClient.loop();
    delay(1000);                                                  //     wait to get all debug info out of the door
    mqttClient.disconnect();                                      //     gracefully disconnect
    delay(1000);                                                  //     wait to get it done
    ESPhttpUpdate.update(FW_URL);                                 //     and now update the firmware and restart
     
  } else if (command_nr == 4) {                                   //   command: restart
    mqttClient.publish(input_topic, "{\"Status\":\"Received reset command: restarting...\"}");
    delay(1000);
    ESP.restart();
  }
}                                                                 // end MQTT callback function

void read_button() {                                              // start check if button is pressed,
  if (!digitalRead(BUTTON)) {                                     //      ( called 20 times per second by Ticker )
    pressCount++;
  } else {
    if (pressCount > 20) {                                        //      button is at least 1 second pressed
      digitalWrite(RELAY, !digitalRead(RELAY));                   //      immediately switch the relay
      publishRelay = true;                                        //      do the MQTT publish in main loop due to timing
    }
    pressCount = 0;
  }
}                                                                 // end

void setup() {
  SPIFFS.begin();
  File rconfigFile = SPIFFS.open("/config.json", "r");            // Start read config from SPIFFS
  size_t sizefile = rconfigFile.size();
  std::unique_ptr<char[]> buf(new char[sizefile]);
  rconfigFile.readBytes(buf.get(), sizefile);

  StaticJsonBuffer<700>  jsonBuffer;
  JsonObject& readc = jsonBuffer.parseObject(buf.get());          // parse the JSON string from the config file
  Hostname =                readc["Hostname"];                    //       and save parameters in variables for later use
  MQTT_User =               readc["MQTT_User"];
  MQTT_PW =                 readc["MQTT_PW"];
  WiFi_SSID =               readc["WiFi_SSID"];
  WiFi_PW =                 readc["WiFi_PW"];
  const char* Sensor_IP =   readc["Sensor_IP"];
  const char* Gateway_IP =  readc["Gateway_IP"];
  const char* Subnet_IP =   readc["Subnet_IP"];
  MQTT_Broker =             readc["MQTT_Broker"];
  const char* Device_Type = readc["Device_Type"];
  const char* AES_Key =     readc["AES_Key"];
 
  parseIP(&ip_sensor, Sensor_IP);                                 //       convert the IP address strings to 4 Byte form
  parseIP(&ip_gateway, Gateway_IP);
  parseIP(&ip_subnet, Subnet_IP);


  DType = String(Device_Type);
  if (DType == "sonoffth") {                                      // start sonoff TH specific config
    RELAY = 12;
    LED = 15;
    BUTTON = 0;
    dht.begin();                                                  //       initialize DHT sensor
  }else if (DType == "sonoffpow") {                               // start sonoff POW specific config
    RELAY = 12;
    LED = 15;
    BUTTON = 0;   
    powRead.enableMeasurePower();                                 //       initialize power measurement
    powRead.selectMeasureCurrentOrVoltage(CURRENT);
    powRead.startMeasure();
  } else {                                                        // start ESP8266 dev board config
    RELAY = 16;
    LED = 14;
  }

  pinMode(LED, OUTPUT);                                           // start setup GPIOs for relay and LED
  digitalWrite(LED, HIGH);                                        //       blue LED, turn it on
  pinMode(RELAY, OUTPUT);                                         //       set GPIO pin for relay/red LED                           
  EEPROM.begin(8);                                                //       start use of EEPROM, where the old relay state is stored
  if (EEPROM.read(0)) {digitalWrite(RELAY, HIGH);}                // end   and switch relay to on if it was on before power loss
 
  WiFi.mode(WIFI_STA);                                            // start setup WiFi
  WiFi.config(ip_sensor, ip_gateway, ip_subnet);                  //       set fix WiFi config
  delay(10);
  WiFi.begin(WiFi_SSID, WiFi_PW);
  delay(100);
  for ( int i = 0; i < 300; i++) {                                //       try to connect to WiFi for max 30s
    if (WiFi.status() == WL_CONNECTED) {break;}
    delay(100);
  }
  if (WiFi.status() != WL_CONNECTED) {                            //       WiFi failed
    delay(5000);                                                  //       Wait 5 sec before reboot
    ESP.restart();
  }
  HostnameWifi = Hostname;
  HostnameWifi.concat(".local");
  WiFi.hostname(HostnameWifi);                                    // end   at this point WiFi is set up

  MDNS.begin(Hostname);                                           // start OTA. First start MDNS
  ArduinoOTA.setHostname(Hostname);                               //       initialize and start OTA
  ArduinoOTA.setPassword(AES_Key);                                //       the AES key is also the OTA password
  ArduinoOTA.onError([](ota_error_t error) {ESP.restart();});     //       restart in case of an error
  ArduinoOTA.onStart([]() {buttonTimer.detach();});               //       stop button interrup routine while new SW loads
  ArduinoOTA.begin();
  delay(100);                                                     // end   at this point OTA is set up

  mqttClient.set_server(MQTT_Broker, 8883);                       // start config MQTT Server
  mqttClient.set_callback(command_callback);                      //       connection setup is done in main loop
  input_topic = "input/" + String(Hostname);
  cmd_topic = "command/" + String(Hostname);                      // end MQTT config
 
  buttonTimer.attach(0.05, read_button);                          // reads button status 20 times per second
}

void loop() {
 
//  if (millis() > 900000) { ESP.restart(); }                       // reboot every 15 min (=900000ms) (to fix unknown problems)
 
  if (WiFi.status() != WL_CONNECTED) {                            //       WiFi not connected,

    WiFi.mode(WIFI_STA);                                          // start setup WiFi
    WiFi.config(ip_sensor, ip_gateway, ip_subnet);                //       set fix WiFi config
    delay(10);
    WiFi.begin(WiFi_SSID, WiFi_PW);
    for ( int i = 0; i < 300; i++) {                              //       try to connect to WiFi for max 30s
      if (WiFi.status() == WL_CONNECTED) {break;}
      delay(100);
    }
    WiFiRestart = true;
    HostnameWifi = Hostname;
    HostnameWifi.concat(".local");
    WiFi.hostname(HostnameWifi);                                  //       at this point WiFi should be set up
  }
   
  if (WiFi.status() != WL_CONNECTED) {                            //       if WiFi still failed, then
    delay(5000);                                                  //         wait 5 sec and reboot
    ESP.restart();
  }
 
  if (!mqttClient.connected() || WiFiRestart) {                   // start MQTT connection if not connected
    mqttClient.set_server(MQTT_Broker, 8883);                     //       config MQTT Server
    mqttClient.connect(MQTT::Connect(Hostname).set_auth(MQTT_User, MQTT_PW));
    mqttClient.loop();
    delay(1000);
    if (!mqttClient.connected()) {                                //       if MQTT still not connected, then
      delay(5000);                                                //         wait 5 sec and reboot
      ESP.restart();
    } else {                                                      //       MQTT connection succeeded, therefore
      mqttClient.set_callback(command_callback);                  //       
      mqttClient.subscribe(cmd_topic);                            //         subscribe to commands via broker from node-red
      mqttClient.publish(input_topic, Version);                   //         publish SW version as a (re-)connect info
      mqttClient.loop();
      delay(100);                                                 //         and get the messages out of the door before continuing
      WiFiRestart = false;
    }
  }                                                               // at this point WiFi and MQTT are up and running
 
  now = millis();                                                 // Start measuring every 10 sec
  if (now - lastMeasurement > 10000) {
    mqttClient.loop();
    yield();
    lastMeasurement = now;
    MsgNumber++;                                                  //   this is the message number for the current measurement run
    payload_str = "{\"MsgNr\":";                                  //   create a JSON formatted string for sensor data
    payload_str += MsgNumber;
     
    if (DType == "sonoffth") {
      float t = dht.readTemperature() -1.0;                       //   read temperature from sensor and correct typical DHT22 offset
      float h = dht.readHumidity();                               //   read humidity from sensor
      if (isnan(h) || isnan(t)) {
        mqttClient.publish(input_topic, msg_measure_fail);
      } else {
        payload_str += ",\"Temp\":" + String(t);
        payload_str += ",\"Hum\":" + String(h) + "}";             //   at this stage the JSON sensor data object is finished
        mqttClient.publish(input_topic, payload_str);             //   publish finished measurement
      }
      delay(100);                                                 //   get broker message out of the door
    }

    if (DType == "sonoffpow") {
      double power = powRead.getPower();
      yield();
      double current = powRead.getCurrent();
      yield();
      if (isnan(power) || isnan(current)) {
        mqttClient.publish(input_topic, msg_measure_fail);
      } else {
        payload_str += ",\"Power\":" + String(power);
        payload_str += ",\"Current\":" + String(current) + "}";   //   at this stage the JSON sensor data object is finished
        mqttClient.publish(input_topic, payload_str);             //   publish finished measurement
      }
      delay(100);                                                 //   get broker message out of the door
    }

    if (DType =="test") {                                         // it's just the test device without any sensors
      payload_str += "}";                                         //      we publish at least the message number as status
      mqttClient.publish(input_topic, payload_str);               //      so we know it is alive and well
      delay(100);                                                 //      get broker message out of the door
    }
  }                                                               // end measuring section

  if (publishRelay) {                                             // start publish relay state and save to EEPROM
    MsgNumber++;                                                  //     this is the new message number
    payload_str = "{\"MsgNr\":";                                  //     create a JSON formatted string for relay status
    payload_str += MsgNumber;
    if (digitalRead(RELAY)) {                                     //     figure out the status of the relay
      payload_str += ",\"Relay1\":\"on\"}";
      EEPROM.write(0,1);
    } else {
      payload_str += ",\"Relay1\":\"off\"}";
      EEPROM.write(0,0);
    }
    EEPROM.commit();                                              //      save relay state to EEPROM
    mqttClient.publish(input_topic, payload_str);                 //      send relay state to Broker
    delay(100);                                                   //      get broker message out of the door
    publishRelay = false;                                         // end publish relay state, reset flag
  }
 
  ArduinoOTA.handle();                                            // start section with regular household functions
  mqttClient.loop();
  yield();                                                        // end
}