OTA and Deepsleep mode?

Hi There !

I use atom+platformio to develop some software on ESP8266.

I currently work on a weather sensor that spend most of its time in deepsleep, shortly wake up, measure and send data and goes back to deepsleep.

I added some OTA handling at the end of my code, before going back to sleep.

I do not have any “loop()” fonction, like most code with deepsleep, so I set a short loop at the end of my code, this way:

  mqtt.disconnect();
  ArduinoOTA.begin(); // initialisation de l'OTA
  uint8_t timeout_OTA = 30;
  while (timeout_OTA > 0) {
     ArduinoOTA.handle();
     delay(100);
     timeout_OTA--;
  }
//////////////////////////////////// end of OTA management
// job is done, let's disconnect
   delay(50);
   WiFi.disconnect();
   //Serial.println("Sleep");
   delay(50);
   ESP.deepSleep(sleep_duration * 1000000);
}

void loop() {
   // not used due to deepsleep
}

my deep sleep last 150sec.

So I launch an upload from atom and just wait…
It does not always works and I wonder the following:

  • in platformio.ini " upload_flags = --timeout=20" it looks like the unit for this flag is 10sec ? curious, no?

  • is my look of 30x 100ms enough to catch the OTA (the while loop with ota handle() ).

  • any other ideas to improve ?

I’d probably be tempted to increase the lookup to at least 5 seconds, so 50… I’ll have a look at the code I did to do something similar… basically all in setup instead of loop.

I’m not sure why the timeout would configurable… as going by the espota.py script, it is hard coded for 10 seconds, and doesn’t have a ‘timeout’ option… quite strange! Maybe platformio is running it multiple times?

Hi !
Thanx for you answer!
I think you may have guessed right for the timeout. At least that would explain why I have a 200sec timeout when I put 20 as parameter (20x hard coded 10sec ?)

I could push to 5sec, but I would like to reduce this as much as possible, for battery saving.
The point is that I have no clue how espota & handle() works and catch each other.
maybe it would be better to have 300x10ms instead of 30x100ms?

It certainly won’t hurt. As long as there is a delay of some description (even a delay(1)) the ESP will get the yield() call it needs to ensure it doesn’t lock up, as well as encouraging it to idle while not doing anything. You just want it to be hanging around for long enough to hear espota sending it the message to start processing OTA…

Below is the code I currently have on a pair of ESP8266s (DigiStump Oak) that have been running well for the last several months… (and is overdue some tweaking as it’s a mismash of example code). And I tell a lie… it does use the loop, and instead uses a state machine to determine whether to get the sensor reading, idle until a total of 20 seconds uptime has elapsed to allow OTA to happen, or go into deepsleep. It could probably be shorter, or instead count from when the delay stage first triggers, but it hasn’t been an issue so far, and just gave me enough time to hit the compile button, and power up the ESP8266 and gurantee it would ‘just work’ :wink:

ESP8266 Code
// OneWire DS18S20, DS18B20, DS1822 Temperature Example
/*
   0.71 - use enums for state machine
   0.70 - switch to ESP8266 core, WifiManager, ArduinoOTA.
   0.60 - use DNS lookup then fall back to IP, plus extra parameter for updateThingspeak allowing field to be parameterised
   0.50 - particle variable is now pushed (hence SYSTEM_MODE(SEMI_AUTOMATIC) mode)
   0.40 - changed to DallasTemperature library, some code optimisations. Also, since no safe mode, a manual delay has been added.
*/

#include <ESP8266WiFi.h>          //ESP8266 Core WiFi Library (you most likely already have this in your sketch)

#include <DNSServer.h>            //Local DNS Server used for redirecting all requests to the configuration portal
#include <ESP8266WebServer.h>     //Local WebServer used to serve the configuration portal
#include <WiFiManager.h>          //https://github.com/tzapu/WiFiManager WiFi Configuration Magic

#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>           // For OTA programming

#define STATUS_LED 5
#define DS18S20_PIN 0

#define DEBUG
#define DEBUG_OI Serial

#ifdef DEBUG
#define   DebugBegin(...) DEBUG_OI.begin(__VA_ARGS__)
#define   DebugPrint(...) DEBUG_OI.print(__VA_ARGS__)
#define   DebugPrintln(...) DEBUG_OI.println(__VA_ARGS__)
#define   DebugPrintf(...) DEBUG_OI.printf(__VA_ARGS__)
#define   DebugWrite(...) DEBUG_OI.write(__VA_ARGS__)
#define   DebugFlush(...) DEBUG_OI.flush(__VA_ARGS__)
#else
// Define the counterparts that cause the compiler to generate no code
#define   DebugBegin(...) (void(0))
#define   DebugPrint(...) (void(0))
#define   DebugPrintln(...) (void(0))
#define   DebugPrintf(...) (void(0))
#define   DebugWrite(...) (void(0))
#define   DebugFlush(...) (void(0))
#endif

#include <OneWire.h>
#include <DallasTemperature.h>
#include <WiFiClient.h>

WiFiClient client;

OneWire oneWire(DS18S20_PIN); // Setup a oneWire instance to communicate with any OneWire devices (4.7K pullup needed)
DallasTemperature sensors(&oneWire); // Pass our oneWire reference to Dallas Temperature.
DeviceAddress thermometer; // arrays to hold device address

char tempStr[8];
double tempC = 0.0;

String myWriteAPIKey = "";   // Put your API key here
String fieldToPost = "field4";               // Put the field to update here
const char * server_ip = "184.106.153.149";
const char * server_dns = "api.thingspeak.com";

//if (resetInfo.reason == REASON_DEEP_SLEEP_AWAKE)  // wake up from deep-sleep

unsigned long currentMillis = 0;
unsigned long previousMillis = 0;

enum machineStates {
  PUBLISH, // publish sensor readings
  WAIT_OTA, // delay incase OTA pending
  SLEEP  //go to sleep
};

enum machineStates state;

void setup(void)
{
  DebugBegin(115200);
  DebugPrintln("");
  DebugPrintln(F("Sketch starting: DS18x20_Thingspeak v0.71"));
  DebugPrint("Reset reason: ");
  DebugPrintln(ESP.getResetReason());
  DebugPrint("Core Version: ");
  DebugPrintln(ESP.getCoreVersion());
  DebugPrint("SDK Version: ");
  DebugPrintln(ESP.getSdkVersion());
  DebugPrintln("");

  pinMode(STATUS_LED, OUTPUT);
  digitalWrite(STATUS_LED, LOW);

  //DS18x20
  sensors.begin();
  sensors.getAddress(thermometer, 0);
  sensors.setResolution(thermometer, 12);
  sensors.requestTemperatures();

  //WiFi
  WiFiManager wifiManager;
  wifiManager.autoConnect("Oak2 AP");

  //OTA
  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()
    DebugPrintln("Start updating " + type);
  });
  ArduinoOTA.onEnd([]() {
    DebugPrintln("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    DebugPrintf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    DebugPrintf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) DebugPrintln("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) DebugPrintln("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) DebugPrintln("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) DebugPrintln("Receive Failed");
    else if (error == OTA_END_ERROR) DebugPrintln("End Failed");
  });

  ArduinoOTA.setHostname("Oak2");
  ArduinoOTA.setPassword("");
  ArduinoOTA.begin();

  //Ready!
  DebugPrintln("");
  DebugPrintln("Ready");
  DebugPrint(F("IP address: "));
  DebugPrintln(WiFi.localIP());
  DebugPrintln("");

  state = PUBLISH;
  previousMillis = millis();
}

void loop(void)
{
  ArduinoOTA.handle();
  unsigned long currentMillis = millis();

  switch (state)
  {
    case PUBLISH:
      sensors.requestTemperatures();
      tempC = sensors.getTempC(thermometer);

      dtostrf(tempC, 5, 2, tempStr);

      // either publish error state to particle, or temperature to particle and thingspeak
      if (tempC == 85.00 || tempC == -127.00)
      {
        DebugPrint("DS18B20 Error: ");
        DebugPrintln(tempC);
      }
      else
      {
        DebugPrint("Temperature = ");
        DebugPrint(tempC);
        DebugPrintln(" Celsius");

        bool thingspeakUpdate = false;
        int uploadRetries = 0;
        do
        {
          thingspeakUpdate = updateThingspeak(fieldToPost, tempC);
          uploadRetries++;
        }
        while (thingspeakUpdate == false && uploadRetries < 3);
        uploadRetries = 0;
      }

      DebugPrint("Delay for OTA ");
      state = WAIT_OTA;
      break;

    case WAIT_OTA:
      //add a 10 second delay to give time for OTA programming to kick in if it needs to
      if (currentMillis - previousMillis >= 20000)
      {
        previousMillis = currentMillis;
        state = SLEEP;
      }

      delay(100);
      DebugPrint(".");
      break;

    case SLEEP:
      // power save for 10 minutes
      DebugPrintln("");
      DebugPrintln("Going to deepSleep!");
      DebugFlush();
      ESP.deepSleep(10 * 60 * 1000000, WAKE_RF_DEFAULT);
      break;
  }
}

// thingspeak needs minimum 15 sec delay between updates
boolean updateThingspeak(String field, float data)
{
  DebugPrint("Attempting to send temperature update to ThingSpeak (using DNS lookup)... ");

  if (client.connect(server_dns, 80))
  {
    digitalWrite(STATUS_LED, HIGH);

    //construct ThinkSpeak POST URL
    String postStr = myWriteAPIKey;
    postStr += "&";
    postStr += field;
    postStr += "=";
    postStr += String(data);
    postStr += "\r\n\r\n";

    //process
    client.print("POST /update HTTP/1.1\n");
    client.print("Host: api.thingspeak.com\n");
    client.print("Connection: close\n");
    client.print("X-THINGSPEAKAPIKEY: " + myWriteAPIKey + "\n");
    client.print("Content-Type: application/x-www-form-urlencoded\n");
    client.print("Content-Length: ");
    client.print(postStr.length());
    client.print("\n\n");
    client.print(postStr);

    client.stop();

    DebugPrintln("SUCCESS!");
    delay(1000); // Pulse stretch LED
    digitalWrite(STATUS_LED, LOW);

    return true;
  } // END if(client.connect())
  else
  {
    DebugPrintln("FAILED!");
    DebugPrint("Falling back to Thingspeak IP, retrying... ");
    if (client.connect(server_ip, 80))
    {
      digitalWrite(STATUS_LED, HIGH);

      //construct ThinkSpeak POST URL
      String postStr = myWriteAPIKey;
      postStr += "&";
      postStr += field;
      postStr += "=";
      postStr += String(data);
      postStr += "\r\n\r\n";

      //process
      client.print("POST /update HTTP/1.1\n");
      client.print("Host: api.thingspeak.com\n");
      client.print("Connection: close\n");
      client.print("X-THINGSPEAKAPIKEY: " + myWriteAPIKey + "\n");
      client.print("Content-Type: application/x-www-form-urlencoded\n");
      client.print("Content-Length: ");
      client.print(postStr.length());
      client.print("\n\n");
      client.print(postStr);

      client.stop();

      DebugPrintln("SUCCESS!");
      delay(1000); // Pulse stretch LED
      digitalWrite(STATUS_LED, LOW);

      return true;
    } // END if(client.connect())
    else
    {
      DebugPrintln("FAILED!");
      return false; //failed to connect
    }
  }
}


Hi !
From my understanding, our both software shall have similar behavior.
Only timings are pretty different.
I do: 3sec wait for OTA / 150sec deepsleep.
You do: 20 sec wait for OTA / 10min deepsleep. ?

By the way: how do you deal with OTA with such long deepsleep ? you launch OTA when you see a temperature update ?

I will do some test with longer wait as soon as possible.
If it helps, I can figure out a wait to enable long wait, only when needed (I have several ideas: enable OTA from an MQTT message or adding a physical switch or enable OTA if sensor is disconnected etc…)

It’s more like a 10 second wait as the count is from the boot of the processor, so includes the time needed to re-acquire the WiFi link and upload the measurements, etc.

When I was actively programming them, it was a matter of pressing compile and power cycling. Alternately, I would just wait for led to go on, and then hit upload… Another way to do it would be to have the ESP8266 check for OTA updates itself and download the file from a server…

mmm I have seen this OTA from server, but never tried.
I barely manage my ATOM setup :smiley:, so I focused on a direct OTA from atom.
Does it mean something like: put the bin file on a local http server so that the ESP can just download it available ?

if yes, that may be the best power efficient solution, no need to wait

1 Like

Yes, spot on. Since you already have a MQTT setup, you could probably have a message set as a flag that an update is waiting, and then trigger the lookup for the HTTP server. Then it would just be a matter of putting the binary in the right place, setting the flag, and waiting for the next 150 second window to lapse. Alternately, setting a flag to say… hang on, don’t go to sleep this time… wait for manual OTA programming.

https://arduino-esp8266.readthedocs.io/en/latest/ota_updates/readme.html#http-server

I have currently some troubles with mqtt subscribe (adafruit mqtt lib): it seems not catching retained messages, only if I publish when it’s awake and waiting for this…(so we go back to a few seconds loop to wait mqtt flag…, note power efficient)…maybe I will solve this, but not sure.

I could just add the line to check for a new firmware each time it wake up. like: ESPhttpUpdate.update("192.168.0.2", 80, "/arduino.bin");

I just wonder: If I let the file here on the server: will re-flash at each wake with same file (ie every 150sec for me) ? or can it detect it was already flashed previously ?
What happened if I delete the file on server in the middle of a reflash ?

I haven’t played with that sort of stuff, but the docs do indicate …

ESPhttpUpdate.update = Simple updater downloads the file every time the function is called.

… so it would flash every time it runs. Hence the need for the version check script mentioned further down, which either checks for a version number or compares the MD5 checksum of the binary on the ESP with the one on the server. I suppose you could have another file on the server as an ‘update/don’t update’ flag.

As far as when happens if you delete the file… potentially nothing if the server has it in memory… which is likely since the file is really insignificantly small. Otherwise. I would expect the OTA.onError() handler to fire, and the update be aborted. Since OTA works on a two slot system… i.e. one slot for the current program, the second for the next version loaded via OTA, since the OTA update failed, it will stick with the current slot for the next reboot. But that is only a guess! :wink: You’d want to make sure that it is reset or gets put to sleep on the failure also.

I think it should be possible to flash only once, at least in the link you provide, I read this:

With this information the script now can check if an update is needed. It is also possible to deliver different binaries based on the MAC address for example.

I assume it shall be possible to provide a version number in a small PHP script on the server (as given in exemple).

it’s maybe possible that the ESP check the available version and compare with some internal defined constant.
It is missing a example of the code in the devine (only the php is shown)…but I will dig this a little :slight_smile:

Indeed… when you do more than just the ‘simple’ updater :wink:

It looks like you do the same call… ESPhttpUpdate.update … but of instead the path being to the binary, it would be to the update php script… and then you have return values you can check to if an update was not available or if it failed. I suspect this is all you need… as updating should be handled automatically just like with ArduinoOTA, and the only other thing you would need would be the WiFi connection established and the files and server setup.

t_httpUpdate_return ret = ESPhttpUpdate.update("192.168.0.2", 80, "/esp/update/arduino.php", "optional current version string here");
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."); // may not called we reboot the ESP
        break;
}

There are some infos here:
https://www.bakke.online/index.php/2017/06/02/self-updating-ota-firmware-for-esp8266/

I will give a try as soon as I can.

The server is no problem. I have apache &php already running on same device as my mosquitto broker (an orange pi).

Sounds like you’re on track. I just marked one of your earlier replies as a ‘solution’ so this thread doesn’t get left open as an ‘unresolved issue’. Please let me know you you go, as I am interested in MQTT but haven’t dont anything about it yet, and server OTA sounds like a handy way to go with MQTT type setups also :wink:

Hi !!

I made it work!

The function is pretty simple.
it look like this:

void check_OTA() {
HTTPClient http;
if (http.begin(client, fwVersionURL)) {
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
      String newFWVersion = http.getString();
      int newVersion = newFWVersion.toInt();
      if( newVersion > FW_VERSION ) {
        delay(100);
        ESPhttpUpdate.update( fwImageURL );
      } 
    }
  } // end if http.begin
} // end check_OTA()

It will just look for a text file at address fwVersionURL and if this text file containt a version number higher that what is in the code, it will do OTA.

My full code is here if some are interested:

so I need to put on the server only 2 files:

  • the firmware (compiled bin file)
  • a text file containing only 1 line with a version number (integer).
1 Like