Moved FOTA to local library to deal with 2 bugs, OTA reported on OLED according to source.

This commit is contained in:
Ray Jones 2019-06-03 06:34:45 +10:00
parent f4a3ce45bb
commit 811c15093c
17 changed files with 559 additions and 36 deletions

View file

@ -1313,9 +1313,9 @@ int getBoardRevision()
return BoardRevision;
}
void ShowOTAScreen(int percent, bool webupdate)
void ShowOTAScreen(int percent, eOTAmodes updateType)
{
ScreenManager.showOTAMessage(percent, webupdate);
ScreenManager.showOTAMessage(percent, updateType);
feedWatchdog();
}

View file

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
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 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.
For more information, please refer to <http://unlicense.org>

View file

@ -0,0 +1,94 @@
# esp32FOTA library for Arduino
## Purpose
A simple library to add support for Over-The-Air (OTA) updates to your project.
## Features
- [x] Web update (requires web server)
- [x] Batch firmware sync
- [ ] Multi firmware update record
- [ ] Stream update (e.g. MQTT or other)
## How it works
This library tries to access a JSON file hosted on a webserver, and reviews it to decide if a newer firmware has been published, if so it will download it and install it.
There are a few things that need to be inplace for an update to work.
- A webserver with the firmware information in a JSON file
- Firmware version
- Firmware type
- Firmware bin
## Usage
### Hosted JSON
This is hosted by a webserver and contains information about the latest firmware
```json
{
"type": "esp32-fota-http",
"version": 2,
"host": "192.168.0.100",
"port": 80,
"bin": "/fota/esp32-fota-http-2.bin"
}
```
#### Firemare types
Types are used to compare with the current loaded firmware, this is used to make sure that when loaded the devie will still to the intended job.
As an example a device used as a data logger should ony be updated with new versions of the data logger.
##### examples
- TTGO-T8-ESP32-Logger
- TTGO-T8-ESP32-Temp
- TTGO-T8-ESP32-Relay
### Sketch
In this example a version 1 of 'esp32-fota-http' is in use, it would be updated when using the JSON example.
```cpp
#include <esp32fota.h>
#include <WiFi.h>
const char *ssid = "";
const char *password = "";
esp32FOTA esp32FOTA("esp32-fota-http", 1);
void setup()
{
esp32FOTA.checkURL = "http://server/fota/fota.json";
Serial.begin(115200);
setup_wifi();
}
void setup_wifi()
{
delay(10);
Serial.print("Connecting to ");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
}
void loop()
{
bool updatedNeeded = esp32FOTA.execHTTPcheck();
if (updatedNeeded)
{
esp32FOTA.execOTA();
}
delay(2000);
}
```

View file

@ -0,0 +1,6 @@
This has been touched up by Ray Jones to deal with a coupole of issues:
bad return from execHTTPcheck()
execHTTPcheck() uses a char[] with a variable
Examples have been detonated to avoid issues during BTC recompile

View file

@ -0,0 +1,7 @@
{
"type": "esp32-fota-http",
"version": 2,
"host": "192.168.0.100",
"port": 80,
"bin": "/fota/esp32-fota-http-2.bin"
}

View file

@ -0,0 +1,22 @@
#######################################
# Syntax Coloring Map For esp32FOTA
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
esp32FOTA KEYWORD1
useDeviceID KEYWORD1
checkURL KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
execOTA KEYWORD2
execHTTPcheck KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################

View file

@ -0,0 +1,24 @@
{
"name": "esp32FOTA",
"version": "0.1.1",
"keywords": "firmware, OTA, Over The Air Updates, ArduinoOTA",
"description": "Allows for firmware to be updated from a webserver, the device can check for updates at any time. Uses a simple JSON file to outline if a new firmware is avaiable.",
"examples": "examples/*/*.ino",
"repository": {
"type": "git",
"url": "https://github.com/chrisjoyce911/esp32FOTA.git"
},
"authors": [
{
"name": "Chris Joyce",
"email": "chris@joyce.id.au",
"url": "https://github.com/chrisjoyce911",
"maintainer": true
}
],
"frameworks": "arduino",
"platforms": [
"esp32",
"espressif32"
]
}

View file

@ -0,0 +1,10 @@
name=esp32FOTA
version=0.1.1
author=Chris Joyce
maintainer=Chris Joyce <chris@joyce.id.au>
sentence=A simple library for firmware OTA updates
paragraph=Allows for firmware to be updated from a webserver, the device can check for updates at any time. Uses a simple JSON file to outline if a new firmware is avaiable.
category=Communication
url=https://github.com/chrisjoyce911/esp32FOTA
architectures=esp32,espressif32
includes=esp32fota.h

View file

@ -0,0 +1,271 @@
/*
esp32 firmware OTA
Date: December 2018
Author: Chris Joyce <https://chrisjoyce911/esp32FOTA>
Purpose: Perform an OTA update from a bin located on a webserver (HTTP Only)
*/
#include "esp32fota.h"
#include "Arduino.h"
#include <WiFi.h>
#include <HTTPClient.h>
#include <Update.h>
#include "ArduinoJson.h"
esp32FOTA::esp32FOTA(String firwmareType, int firwmareVersion)
{
_firwmareType = firwmareType;
_firwmareVersion = firwmareVersion;
useDeviceID = false;
}
// Utility to extract header value from headers
String esp32FOTA::getHeaderValue(String header, String headerName)
{
return header.substring(strlen(headerName.c_str()));
}
// OTA Logic
void esp32FOTA::execOTA()
{
WiFiClient client;
int contentLength = 0;
bool isValidContentType = false;
Serial.println("Connecting to: " + String(_host));
// Connect to Webserver
if (client.connect(_host.c_str(), _port))
{
// Connection Succeed.
// Fecthing the bin
Serial.println("Fetching Bin: " + String(_bin));
// Get the contents of the bin file
client.print(String("GET ") + _bin + " HTTP/1.1\r\n" +
"Host: " + _host + "\r\n" +
"Cache-Control: no-cache\r\n" +
"Connection: close\r\n\r\n");
unsigned long timeout = millis();
while (client.available() == 0)
{
if (millis() - timeout > 5000)
{
Serial.println("Client Timeout !");
client.stop();
return;
}
}
while (client.available())
{
// read line till /n
String line = client.readStringUntil('\n');
// remove space, to check if the line is end of headers
line.trim();
if (!line.length())
{
//headers ended
break; // and get the OTA started
}
// Check if the HTTP Response is 200
// else break and Exit Update
if (line.startsWith("HTTP/1.1"))
{
if (line.indexOf("200") < 0)
{
Serial.println("Got a non 200 status code from server. Exiting OTA Update.");
break;
}
}
// extract headers here
// Start with content length
if (line.startsWith("Content-Length: "))
{
contentLength = atoi((getHeaderValue(line, "Content-Length: ")).c_str());
Serial.println("Got " + String(contentLength) + " bytes from server");
}
// Next, the content type
if (line.startsWith("Content-Type: "))
{
String contentType = getHeaderValue(line, "Content-Type: ");
Serial.println("Got " + contentType + " payload.");
if (contentType == "application/octet-stream")
{
isValidContentType = true;
}
}
}
}
else
{
// Connect to webserver failed
// May be try?
// Probably a choppy network?
Serial.println("Connection to " + String(_host) + " failed. Please check your setup");
// retry??
// execOTA();
}
// Check what is the contentLength and if content type is `application/octet-stream`
Serial.println("contentLength : " + String(contentLength) + ", isValidContentType : " + String(isValidContentType));
// check contentLength and content type
if (contentLength && isValidContentType)
{
// Check if there is enough to OTA Update
bool canBegin = Update.begin(contentLength);
// If yes, begin
if (canBegin)
{
Serial.println("Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!");
// No activity would appear on the Serial monitor
// So be patient. This may take 2 - 5mins to complete
size_t written = Update.writeStream(client);
if (written == contentLength)
{
Serial.println("Written : " + String(written) + " successfully");
}
else
{
Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?");
// retry??
// execOTA();
}
if (Update.end())
{
Serial.println("OTA done!");
if (Update.isFinished())
{
Serial.println("Update successfully completed. Rebooting.");
ESP.restart();
}
else
{
Serial.println("Update not finished? Something went wrong!");
}
}
else
{
Serial.println("Error Occurred. Error #: " + String(Update.getError()));
}
}
else
{
// not enough space to begin OTA
// Understand the partitions and
// space availability
Serial.println("Not enough space to begin OTA");
client.flush();
}
}
else
{
Serial.println("There was no content in the response");
client.flush();
}
}
bool esp32FOTA::execHTTPcheck()
{
String useURL;
if (useDeviceID)
{
// String deviceID = getDeviceID() ;
useURL = checkURL + "?id=" + getDeviceID();
}
else
{
useURL = checkURL;
}
WiFiClient client;
_port = 80;
Serial.println("Getting HTTP");
Serial.println(useURL);
Serial.println("------");
if ((WiFi.status() == WL_CONNECTED))
{ //Check the current connection status
HTTPClient http;
http.begin(useURL); //Specify the URL
int httpCode = http.GET(); //Make the request
if (httpCode == 200)
{ //Check is a file was returned
String payload = http.getString();
int str_len = payload.length() + 1;
char* JSONMessage = new char[str_len];
payload.toCharArray(JSONMessage, str_len);
StaticJsonBuffer<300> JSONBuffer; //Memory pool
JsonObject &parsed = JSONBuffer.parseObject(JSONMessage); //Parse message
if (!parsed.success())
{ //Check for errors in parsing
delete[] JSONMessage;
Serial.println("Parsing failed");
delay(5000);
return false;
}
const char *pltype = parsed["type"];
int plversion = parsed["version"];
const char *plhost = parsed["host"];
_port = parsed["port"];
const char *plbin = parsed["bin"];
String jshost(plhost);
String jsbin(plbin);
_host = jshost;
_bin = jsbin;
String fwtype(pltype);
delete[] JSONMessage;
if (plversion > _firwmareVersion && fwtype == _firwmareType)
{
return true;
}
else
{
return false;
}
}
else
{
Serial.println("Error on HTTP request");
return false;
}
http.end(); //Free the resources
}
return false;
}
String esp32FOTA::getDeviceID()
{
char deviceid[21];
uint64_t chipid;
chipid = ESP.getEfuseMac();
sprintf(deviceid, "%" PRIu64, chipid);
String thisID(deviceid);
return thisID;
}

View file

@ -0,0 +1,32 @@
/*
esp32 firmware OTA
Date: December 2018
Author: Chris Joyce <https://chrisjoyce911/esp32FOTA>
Purpose: Perform an OTA update from a bin located on a webserver (HTTP Only)
*/
#ifndef esp32fota_h
#define esp32fota_h
#include "Arduino.h"
class esp32FOTA
{
public:
esp32FOTA(String firwmareType, int firwmareVersion);
void execOTA();
bool execHTTPcheck();
bool useDeviceID;
String checkURL;
private:
String getHeaderValue(String header, String headerName);
String getDeviceID();
String _firwmareType;
int _firwmareVersion;
String _host;
String _bin;
int _port;
};
#endif

View file

@ -490,16 +490,17 @@ CScreenManager::showRebootMsg(const char* content[2], long delayTime)
}
void
CScreenManager::showOTAMessage(int percent, bool webupdate)
CScreenManager::showOTAMessage(int percent, eOTAmodes updateType)
{
static int prevPercent = -1;
if(percent != prevPercent) {
_pDisplay->clearDisplay();
_pDisplay->setCursor(64,22);
if(webupdate)
_pDisplay->printCentreJustified("Web update active");
else
_pDisplay->printCentreJustified("OTA update active");
switch(updateType) {
case eOTAnormal: _pDisplay->printCentreJustified("OTA update active"); break;
case eOTAbrowser: _pDisplay->printCentreJustified("Browser update active"); break;
case eOTAWWW: _pDisplay->printCentreJustified("WWW update active"); break;
}
if(percent) {
char msg[16];
sprintf(msg, "%d%%", percent);

View file

@ -24,8 +24,9 @@
#include <Arduino.h>
#include <vector>
#include "../Utility/UtilClasses.h"
class CProtocol;
//class CProtocol;
class C128x64_OLED;
class CScreen;
class CRebootScreen;
@ -66,7 +67,7 @@ public:
void reqUpdate();
void selectMenu(eUIMenuSets menuset, int specific = -1); // use to select loop menus, including the root or branches
void showRebootMsg(const char* content[2], long delayTime);
void showOTAMessage(int percent, bool webupdate);
void showOTAMessage(int percent, eOTAmodes updateType);
};
#endif // __SCREEN_MANAGER_H__

View file

@ -24,6 +24,7 @@
#define __BTC_HELPERS_H__
#include "Protocol.h"
#include "../Utility/UtilClasses.h"
extern void ToggleOnOff();
@ -73,6 +74,9 @@ extern void checkFOTA();
extern void setUploadSize(long val);
extern void ShowOTAScreen(int percent=0, eOTAmodes updateType=eOTAnormal);
#define LOWERLIMIT(A, B) if((A) < (B)) (A) = (B)

View file

@ -80,3 +80,19 @@ CommStates::delayExpired()
long test = millis() - _delay;
return(test >= 0);
}
CProfile::CProfile()
{
tStart = millis();
}
unsigned long
CProfile::elapsed(bool reset/* = false*/)
{
unsigned long now = millis();
unsigned long retval = now - tStart;
if(reset)
tStart = now;
return retval;
}

View file

@ -155,4 +155,16 @@ struct CRect {
}
};
class CProfile {
unsigned long tStart;
public:
CProfile();
unsigned long elapsed(bool reset = false);
};
enum eOTAmodes {
eOTAnormal, eOTAbrowser, eOTAWWW
};
#endif // __UTIL_CLASSES_H__

View file

@ -38,8 +38,6 @@
#endif
#include "../Utility/NVStorage.h"
extern void ShowOTAScreen(int percent=0, bool webpdate=false);
extern WiFiManager wm;
File fsUploadFile; // a File object to temporarily store the received file
@ -95,7 +93,6 @@ void handleWMConfig() {
server.send(200, "text/plain", "Start Config Portal - Retaining credential");
DebugPort.println("Starting web portal for wifi config");
delay(500);
// wm.startWebPortal();
wifiEnterConfigPortal(true, false, 3000);
}
@ -249,7 +246,7 @@ void initWebServer(void) {
int percent = (progress / (total / 100));
DebugPort.printf("Progress: %u%%\r", percent);
DebugPort.handle(); // keep telnet spy alive
ShowOTAScreen(percent, true);
ShowOTAScreen(percent, eOTAWWW); // WWW update in place
DebugPort.print("^");
});
@ -267,7 +264,6 @@ void initWebServer(void) {
DebugPort.println("WEB: GET /tst");
server.sendHeader("Location","/"); // reselect the update page
server.send(303);
// rootRedirect();
});
// Magical code originally shamelessly lifted from Arduino WebUpdate example, then modified
@ -301,8 +297,8 @@ void initWebServer(void) {
// completion functionality
if(SPIFFSupload) {
if(SPIFFSupload == 1) {
server.send(200, "text/plain", "OK - File uploaded to SPIFFS");
DebugPort.println("WEB: SPIFFS OK");
server.send(200, "text/plain", "OK - File uploaded to SPIFFS");
// javascript reselects the /update page!
}
else {
@ -325,7 +321,6 @@ void initWebServer(void) {
ESP.restart(); // reboot
}
}, []() {
// DebugPort.println("WEB: POST /updatenow handler");
if(bUpdateAccessed) { // only allow progression via /update, attempts to directly access /updatenow will fail
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
@ -344,20 +339,22 @@ void initWebServer(void) {
SPIFFSupload = fsUploadFile ? 1 : 2;
//filename = String();
}
} else if (upload.status == UPLOAD_FILE_WRITE) {
// handle upload segments
}
// handle file segments
else if (upload.status == UPLOAD_FILE_WRITE) {
#if USE_SW_WATCHDOG == 1
feedWatchdog(); // we get stuck here for a while, don't let the watchdog bite!
#endif
if(upload.totalSize) {
char JSON[64];
sprintf(JSON, "{\"progress\":%d}", upload.totalSize);
sendWebServerString(JSON); // feedback proper byte count of update
sendWebServerString(JSON); // feedback proper byte count of update to browser via websocket
}
int percent = 0;
if(_SuppliedFileSize)
percent = 100 * upload.totalSize / _SuppliedFileSize;
ShowOTAScreen(percent);
ShowOTAScreen(percent, eOTAbrowser); // browser update
DebugPort.print(".");
if(fsUploadFile) {
@ -368,8 +365,10 @@ void initWebServer(void) {
Update.printError(DebugPort);
}
}
} else if (upload.status == UPLOAD_FILE_END) {
// handle end of upload
}
// handle end of upload
else if (upload.status == UPLOAD_FILE_END) {
if(SPIFFSupload) {
if(fsUploadFile) {
fsUploadFile.close(); // Close the file again
@ -413,14 +412,15 @@ void initWebServer(void) {
server.begin();
webSocket.begin();
webSocket.onEvent(webSocketEvent);
DebugPort.println("HTTP server started");
}
unsigned char cVal;
// called my main sketch loop()
bool doWebServer(void) {
// called by main sketch loop()
bool doWebServer(void)
{
webSocket.loop();
server.handleClient();
}
@ -440,21 +440,21 @@ bool isWebServerClientChange()
bool sendWebServerString(const char* Str)
{
unsigned long tStart = millis();
CProfile profile;
if(webSocket.connectedClients()) {
unsigned long tCon = millis() - tStart;
tStart = millis();
unsigned long tCon = profile.elapsed(true);
bTxWebData = true; // OLED tx data animation flag
webSocket.broadcastTXT(Str);
unsigned long tWeb = millis() - tStart;
// DebugPort.printf("Websend times : %ld,%ld\r\n", tCon, tWeb);
unsigned long tWeb = profile.elapsed(true);
DebugPort.printf("Websend times : %ld,%ld\r\n", tCon, tWeb);
return true;
}
return false;
}
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length)
{
if (type == WStype_TEXT) {
bRxWebData = true;
char cmd[256];

View file

@ -24,10 +24,9 @@
#if USE_SPIFFS == 1
#include <SPIFFS.h>
#endif
#include <esp32fota.h>
#include "../Libraries/esp32FOTA/src/esp32fota.h" // local copy used due to a couple of issues
#include "../Protocol/helpers.h"
extern void ShowOTAScreen(int percent=0, bool webpdate=false);
esp32FOTA FOTA("afterburner-fota-http", int(getVersion()*1000));
unsigned long FOTAtime = millis() + 60000; // initially check in a minutes time
@ -74,7 +73,6 @@ void initOTA(){
DebugPort.printf("Progress: %u%%\r", percent);
DebugPort.handle(); // keep telnet spy alive
ShowOTAScreen(percent);
DebugPort.print("%%");
})
.onError([](ota_error_t error) {
DebugPort.printf("Error[%u]: ", error);
@ -94,10 +92,11 @@ void DoOTA(){
// manage Firmware OTA
// this is where the controller contacts a web server to discover if new firmware is available
// if so, it can dowload and implant using OTA and become effective next reboot!
// if so, it can download and implant using OTA and become effective next reboot!
long tDelta = millis() - FOTAtime;
if(tDelta > 0) {
FOTAtime = millis() + 600000; // 10 minutes
// FOTAtime = millis() + 600000; // 10 minutes
FOTAtime = millis() + 3600000; // 1 hour
if ((WiFi.status() == WL_CONNECTED)) { // bug workaround in FOTA where execHTTPcheck does not return false in this condition
DebugPort.println("Checking for new firmware...");
if(FOTA.execHTTPcheck()) {