Skip to content

Instantly share code, notes, and snippets.

@DeanCording
Last active April 29, 2023 03:23
Show Gist options
  • Star 28 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save DeanCording/5308e474d909f7d70613722fd86b09bb to your computer and use it in GitHub Desktop.
Save DeanCording/5308e474d909f7d70613722fd86b09bb to your computer and use it in GitHub Desktop.
ESP8266 Arduino project template with optional OTA firmware update
/**
* ESP8266 project template with optional:
* - WiFi config portal - auto or manual trigger
* - OTA update - Arduino or web server
* - Deep sleep
* - Process timeout watchdog
*
* Copyright (c) 2016 Dean Cording <dean@cording.id.au>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* ESP8266 Pin Connections
*
* GPIO0/SPI-CS2/_FLASH_ - Pull low for Flash download by UART
* GPIO1/TXD0/SPI-CS1 - _LED_
* GPIO2/TXD1/I2C-SDA/I2SO-WS -
* GPIO3/RXD0/I2SO-DATA -
* GPIO4 -
* GPIO5/IR-Rx -
* GPIO6/SPI-CLK - Flash CLK
* GPIO7/SPI-MISO - Flash DI
* GPIO8/SPI-MOSI/RXD1 - Flash DO
* GPIO9/SPI-HD - Flash _HD_
* GPIO10/SPI-WP - Flash _WP_
* GPIO11/SPI-CS0 - Flash _CS_
* GPIO12/MTDI/HSPI-MISO/I2SI-DATA/IR-Tx -
* GPIO13/MTCK/CTS0/RXD2/HSPI-MOSI/I2S-BCK -
* GPIO14/MTMS/HSPI-CLK/I2C-SCL/I2SI_WS -
* GPIO15/MTDO/RTS0/TXD2/HSPI-CS/SD-BOOT/I2SO-BCK - Pull low for Flash boot
* GPIO16/WAKE -
* ADC -
* EN -
* RST -
* GND -
* VCC -
*/
#include <Arduino.h>
// Optional functionality. Comment out defines to disable feature
#define WIFI_PORTAL // Enable WiFi config portal
//#define ARDUINO_OTA // Enable Arduino IDE OTA updates
#define HTTP_OTA // Enable OTA updates from http server
#define LED_STATUS_FLASH // Enable flashing LED status
//#define DEEP_SLEEP_SECONDS 5 // Define for sleep period between process repeats. No sleep if not defined
#define STATUS_LED 2 // Built-in blue LED on pin 2
#include <ESP8266WiFi.h>
#ifdef WIFI_PORTAL
#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
WiFiManager wifiManager;
#define WIFI_PORTAL_TRIGGER_PIN 4 // A low input on this pin will trigger the Wifi Manager Console at boot. Comment out to disable.
#else
#define WIFI_SSID "SSID"
#define WIFI_PASSWORD "password"
#endif
#ifdef ARDUINO_OTA
/* Over The Air updates directly from Arduino IDE */
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#define ARDUINO_OTA_PORT 8266
#define ARDUINO_OTA_HOSTNAME "esp8266"
#define ARDUINO_OTA_PASSWD "123"
#endif
#ifdef HTTP_OTA
/* Over The Air automatic firmware update from a web server. ESP8266 will contact the
* server on every boot and check for a firmware update. If available, the update will
* be downloaded and installed. Server can determine the appropriate firmware for this
* device from any combination of HTTP_OTA_VERSION, MAC address, and firmware MD5 checksums.
*/
#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>
#define HTTP_OTA_ADDRESS F("iot.cording.id.au") // Address of OTA update server
#define HTTP_OTA_PATH F("/esp8266-ota/update") // Path to update firmware
#define HTTP_OTA_PORT 1880 // Port of update server
// Name of firmware
#define HTTP_OTA_VERSION String(__FILE__).substring(String(__FILE__).lastIndexOf('/')+1) + ".generic"
#endif
const char* SSID = (String("ESP") + String(ESP.getChipId())).c_str();
/* Watchdog to guard against the ESP8266 wasting battery power looking for
* non-responsive wifi networks and servers. Expiry of the watchdog will trigger
* either a deep sleep cycle or a delayed reboot. The ESP8266 OS has another built-in
* watchdog to protect against infinite loops and hangups in user code.
*/
#include <Ticker.h>
Ticker watchdog;
#define WATCHDOG_SETUP_SECONDS 30 // Setup should complete well within this time limit
#define WATCHDOG_LOOP_SECONDS 20 // Loop should complete well within this time limit
void timeout_cb() {
// This sleep happened because of timeout. Do a restart after a sleep
Serial.println(F("Watchdog timeout..."));
#ifdef DEEP_SLEEP_SECONDS
// Enter DeepSleep so that we don't exhaust our batteries by countinuously trying to
// connect to a network that isn't there.
ESP.deepSleep(DEEP_SLEEP_SECONDS * 1000, WAKE_RF_DEFAULT);
// Do nothing while we wait for sleep to overcome us
while(true){};
#else
delay(1000);
ESP.restart();
#endif
}
#ifdef LED_STATUS_FLASH
Ticker flasher;
void flash() {
digitalWrite(STATUS_LED, !digitalRead(STATUS_LED));
}
#endif
#ifdef WIFI_PORTAL
// Callback for entering config mode
void configModeCallback (WiFiManager *myWiFiManager) {
// Config mode has its own timeout
watchdog.detach();
#ifdef LED_STATUS_FLASH
flasher.attach(0.2, flash);
#endif
}
#endif
// Put any project specific initialisation here
void setup() {
Serial.begin(115200);
Serial.println(F("Booting"));
#ifdef LED_STATUS_FLASH
pinMode(STATUS_LED, OUTPUT);
flasher.attach(0.6, flash);
#endif
// Watchdog timer - resets if setup takes longer than allocated time
watchdog.once(WATCHDOG_SETUP_SECONDS, &timeout_cb);
// Set up WiFi connection
// Previous connection details stored in eeprom
#ifdef WIFI_PORTAL
#ifdef WIFI_PORTAL_TRIGGER_PIN
pinMode(WIFI_PORTAL_TRIGGER_PIN, INPUT_PULLUP);
delay(100);
if ( digitalRead(WIFI_PORTAL_TRIGGER_PIN) == LOW ) {
watchdog.detach();
if (!wifiManager.startConfigPortal(SSID, NULL)) {
Serial.println(F("Config Portal Failed!"));
timeout_cb();
}
} else {
#endif
wifiManager.setConfigPortalTimeout(180);
wifiManager.setAPCallback(configModeCallback);
if (!wifiManager.autoConnect()) {
Serial.println(F("Connection Failed!"));
timeout_cb();
}
#ifdef WIFI_PORTAL_TRIGGER_PIN
}
#endif
#else
// Save boot up time by not configuring them if they haven't changed
if (WiFi.SSID() != WIFI_SSID) {
Serial.println(F("Initialising Wifi..."));
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
WiFi.persistent(true);
WiFi.setAutoConnect(true);
WiFi.setAutoReconnect(true);
}
#endif
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println(F("Connection Failed!"));
timeout_cb();
}
Serial.print(F("IP address: "));
Serial.println(WiFi.localIP());
#ifdef LED_STATUS_FLASH
flasher.detach();
digitalWrite(STATUS_LED, HIGH);
#endif
#ifdef HTTP_OTA
// Check server for firmware updates
Serial.print("Checking for firmware updates from server http://");
Serial.print(HTTP_OTA_ADDRESS);
Serial.print(":");
Serial.print(HTTP_OTA_PORT);
Serial.println(HTTP_OTA_PATH);
switch(ESPhttpUpdate.update(HTTP_OTA_ADDRESS, HTTP_OTA_PORT, HTTP_OTA_PATH, HTTP_OTA_VERSION)) {
case HTTP_UPDATE_FAILED:
Serial.printf("HTTP update failed: Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
break;
case HTTP_UPDATE_NO_UPDATES:
Serial.println(F("No updates"));
break;
case HTTP_UPDATE_OK:
Serial.println(F("Update OK"));
break;
}
#endif
#ifdef ARDUINO_OTA
// Arduino OTA Initalisation
ArduinoOTA.setPort(ARDUINO_OTA_PORT);
ArduinoOTA.setHostname(SSID);
ArduinoOTA.setPassword(ARDUINO_OTA_PASSWD);
ArduinoOTA.onStart([]() {
watchdog.detach();
Serial.println("Start");
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.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();
#endif
// Put your initialisation code here
Serial.println(F("Ready"));
watchdog.detach();
}
void loop() {
// Watchdog timer - resets if setup takes longer than allocated time
watchdog.once(WATCHDOG_LOOP_SECONDS, &timeout_cb);
// put your main code here, to run repeatedly:
delay(10000);
watchdog.detach();
#ifdef ARDUINO_OTA
// Handle any OTA upgrade
ArduinoOTA.handle();
#endif
#ifdef DEEP_SLEEP_SECONDS
// Enter DeepSleep
Serial.println(F("Sleeping..."));
ESP.deepSleep(DEEP_SLEEP_SECONDS * 1000000, WAKE_RF_DEFAULT);
// Do nothing while we wait for sleep to overcome us
while(true){};
#endif
}
@lkollasch
Copy link

Dean, your global const char* SSID pointer points to a deleted String pool object returned on the stack. This kinda works until String constructor allocates that pool resource. I suggest you assign the SSID value to a statically allocated character array.

Regards

@Swiftnesses
Copy link

Hello, I'm a little confused what defines if it needs a SPIFFS update? Obviously the sketch MD5 is compared to the one on the server and issued if it's different, but how does the SPIFFS update work?

Many thanks in advance...

@alextrical
Copy link

Likewise, i'm not that clear on how to initiate a SPIFFS update, or how to set up the server to manage the SPIFFS updates

@mozzhead164
Copy link

mozzhead164 commented Feb 11, 2020

That makes three of us...

(By the way @DeanCording - Thanks very much for the effort put into this code and the counterpart nodered server side code - your a genius and saved me a LOT of time!!)

I've managed to get the update server working through (node red) HTTPS using my servers certificate fingerprint to verify the connection,
I've also modified ESP8266httpUpdate to add an additional header for BasicAuth so my ESP's can access the password protected server.
I've added a few nodes to node red so on an update request, the ESP's MAC address is queried against a database of known units.
This is so that the update process will continue and send data ONLY IF the unit has already been initialized into the server DB.

I've just now moved my thoughts to the fact that the fingerprint of the root cert will eventually run out (2021 actually) so i will need to update it at some point. I would prefer to update this to SPIFFS so it is separated from the code, ideally.

Over the next few days I will be looking into how this can be achieved, but was wondering if anyone above has managed to achieve this yet..?
My ESP's will be deployed remotely and thus after the initial deployment, I will NEVER have ANY access to them to update via serial. Worrying! ....Need to get this right first time so i don't look an ass

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment