Added OLED and JSON Web Content Update functionality

This commit is contained in:
Ray Jones 2020-03-25 20:28:12 +11:00
parent 1d80e34c4b
commit 87b1704335
15 changed files with 918 additions and 456 deletions

View File

@ -8,6 +8,8 @@
RLJ Added usage of AsyncHTTPrequest to avoid hang issues with flaky internet connections during update poll
However using AsyncTCP for the actual binary update causes other issues in the callback realm,
so persisting with the original synchronous update method which blocks all user mode code.
Modifications Mar 2020:
RLJ Added FreeRTOS queue to separate callbacks from potential system calls - random reboots in some afterburners...
*/
#include <Arduino.h>
@ -20,19 +22,23 @@
extern void forceBootInit();
#define USE_QUEUE
esp32FOTA::esp32FOTA(String firwmareType, int firwmareVersion)
{
_firwmareType = firwmareType;
_firwmareVersion = firwmareVersion;
useDeviceID = false;
_firwmareType = firwmareType;
_firwmareVersion = firwmareVersion;
useDeviceID = false;
// _endCallback = NULL;
_queue = xQueueCreate(1, 256);
}
// Utility to extract header value from headers
String
esp32FOTA::getHeaderValue(String header, String headerName)
{
return header.substring(strlen(headerName.c_str()));
return header.substring(strlen(headerName.c_str()));
}
// OTA Logic
@ -40,165 +46,165 @@ void
esp32FOTA::execOTA()
{
WiFiClient client;
int contentLength = 0;
bool isValidContentType = false;
WiFiClient client;
int contentLength = 0;
bool isValidContentType = false;
Serial.println("Connecting to: " + String(_host));
// Connect to Webserver
if (client.connect(_host.c_str(), _port))
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)
{
// 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;
}
}
}
if (millis() - timeout > 5000)
{
Serial.println("Client Timeout !");
client.stop();
return;
}
}
else
while (client.available())
{
// Connect to webserver failed
// May be try?
// Probably a choppy network?
Serial.println("Connection to " + String(_host) + " failed. Please check your setup");
// 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();
}
}
// Check what is the contentLength and if content type is `application/octet-stream`
Serial.println("contentLength : " + String(contentLength) + ", isValidContentType : " + String(isValidContentType));
if ( _onComplete != NULL) {
if(!_onComplete(contentLength)) {
Serial.println("ESP32FOTA: OnComplete handler returned false");
Update.abort();
}
}
// 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)
if (Update.end())
{
Serial.println("OTA done!");
if (Update.isFinished())
{
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 ( _onComplete != NULL) {
if(!_onComplete(contentLength)) {
Serial.println("ESP32FOTA: OnComplete handler returned false");
Update.abort();
}
}
if (Update.end())
{
Serial.println("OTA done!");
if (Update.isFinished())
{
Serial.println("Update successfully completed. Rebooting.");
if(_onSuccess != NULL) {
_onSuccess();
}
ESP.restart();
}
else
{
if(_onFail != NULL) {
_onFail();
}
Serial.println("Update not finished? Something went wrong!");
}
}
else
{
if(_onFail != NULL) {
_onFail();
}
Serial.println("Error Occurred. Error #: " + String(Update.getError()));
}
Serial.println("Update successfully completed. Rebooting.");
if(_onSuccess != NULL) {
_onSuccess();
}
ESP.restart();
}
else
{
// not enough space to begin OTA
// Understand the partitions and
// space availability
Serial.println("Not enough space to begin OTA");
client.flush();
if(_onFail != NULL) {
_onFail();
}
Serial.println("Update not finished? Something went wrong!");
}
}
else
{
if(_onFail != NULL) {
_onFail();
}
Serial.println("Error Occurred. Error #: " + String(Update.getError()));
}
}
else
{
Serial.println("There was no content in the response");
client.flush();
// 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();
}
}
// Synchronous mode update check - may hang on flakey Internet connections
@ -206,100 +212,100 @@ bool
esp32FOTA::execHTTPcheck()
{
String useURL;
String useURL;
if (useDeviceID)
{
// String deviceID = getDeviceID() ;
useURL = _checkURL + "?id=" + getDeviceID();
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)
{
_newVersion = plversion;
return true;
}
else
{
_newVersion = 0;
return false;
}
}
else
{
useURL = _checkURL;
Serial.println("Error on HTTP request");
return false;
}
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)
{
_newVersion = plversion;
return true;
}
else
{
_newVersion = 0;
return false;
}
}
else
{
Serial.println("Error on HTTP request");
return false;
}
http.end(); //Free the resources
}
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;
char deviceid[21];
uint64_t chipid;
chipid = ESP.getEfuseMac();
sprintf(deviceid, "%" PRIu64, chipid);
String thisID(deviceid);
return thisID;
}
/**
@ -347,20 +353,30 @@ esp32FOTA::onFail( std::function<void()> func ) {
// Callback for when AsyncTCP ready state changes
void FOTA_PollCallback(void* optParm, asyncHTTPrequest* pRequest, int readyState){
if(readyState == 4) { // response
String JSONinfo(pRequest->responseText());
Serial.println(JSONinfo);
Serial.println();
esp32FOTA* pFOTA = (esp32FOTA*) optParm;
if(pFOTA) {
if(pFOTA->decodeResponse(JSONinfo)) {
}
}
}
if(readyState == 1) { // connection established
pRequest->send();
// queue data to be processed later in user loop
void FOTA_PollCallback(void* optParm, asyncHTTPrequest* pRequest, int readyState)
{
if(readyState == 4) { // response
#ifdef USE_QUEUE
esp32FOTA* pFOTA = (esp32FOTA*)optParm;
pFOTA->queueDLdata(pRequest);
#else
String JSONinfo(pRequest->responseText());
Serial.println(JSONinfo);
Serial.println();
esp32FOTA* pFOTA = (esp32FOTA*) optParm;
if(pFOTA) {
if(pFOTA->decodeResponse(JSONinfo)) {
}
}
#endif
}
if(readyState == 1) { // connection established
pRequest->send();
}
}
void
@ -372,7 +388,7 @@ esp32FOTA::setCheckURL(const char* host)
void
esp32FOTA::setupAsync(const char* host)
{
_versionTest.setDebug(true);
// _versionTest.setDebug(true);
}
// Asynchronous update check - performs more reliably with flakey Internet connections
@ -381,7 +397,7 @@ esp32FOTA::execAsyncHTTPcheck()
{
_newVersion = 0;
if(_versionTest.readyState() == 0 || _versionTest.readyState() == 4) {
Serial.println("Querying server");
Serial.println("Querying firmware update server");
_versionTest.onReadyStateChange(FOTA_PollCallback, this);
_versionTest.onBuildHeaders(NULL);
_versionTest.onData(NULL);
@ -432,13 +448,46 @@ esp32FOTA::decodeResponse(char* resp)
if (plversion > _firwmareVersion && fwtype == _firwmareType)
{
_newVersion = plversion;
return true;
_newVersion = plversion;
return true;
}
else
{
_newVersion = 0;
return false;
_newVersion = 0;
return false;
}
}
void
esp32FOTA::queueDLdata(asyncHTTPrequest* pRequest)
{
sFOTAqueue entry;
int len = pRequest->available();
if(len <= sizeof(sFOTAqueue::data)) {
entry.len = len;
pRequest->responseRead(entry.data, len);
BaseType_t awoken;
xQueueSendFromISR(_queue, &entry, &awoken);
}
}
// routine called regularly by the "loop" task - ie not IRQL
// it is not safe to do system things in the AsyncTCP callbacks!
void
esp32FOTA::process()
{
sFOTAqueue entry;
if(xQueueReceive(_queue, &entry, 0)) {
int16_t len = entry.len;
char working[256];
memcpy(working, entry.data, 255);
working[len] = 0;
String JSONinfo(working);
Serial.println(JSONinfo);
Serial.println();
decodeResponse(JSONinfo);
}
}

View File

@ -16,6 +16,12 @@
//#include <Arduino.h>
#include <functional>
#include "../../asyncHTTPrequest/src/asyncHTTPrequest.h"
#include "freertos/queue.h"
struct sFOTAqueue{
uint8_t len;
uint8_t data[255];
};
class esp32FOTA
{
@ -35,6 +41,8 @@ public:
void execAsyncHTTPcheck();
bool decodeResponse(String payload);
bool decodeResponse(char* resp);
void process();
void queueDLdata(asyncHTTPrequest* request);
private:
String getHeaderValue(String header, String headerName);
@ -50,6 +58,8 @@ private:
std::function<void()> _onSuccess;
std::function<void()> _onFail;
asyncHTTPrequest _versionTest;
QueueHandle_t _queue;
String _pollResponse;
};

View File

@ -1568,7 +1568,7 @@ void checkDebugCommands()
TxManage.queueOffRequest();
}
else if(rxVal == 'h') {
getWebContent();
getWebContent(true);
}
else if(rxVal == 'r') {
ESP.restart(); // reset the esp

View File

@ -51,6 +51,7 @@
#include "TempSensorScreen.h"
#include "FrostScreen.h"
#include "HumidityScreen.h"
#include "WebPageUpdateScreen.h"
#include <Wire.h>
#include "../cfg/pins.h"
#include "../cfg/BTCConfig.h"
@ -96,74 +97,6 @@ extern CScreenManager ScreenManager;
// Identifier: DieselSplash
// Draw Mode: Horizontal
//
/*const uint8_t DieselSplash [] PROGMEM = {
// 'Splash3, 128x64px
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x18, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00,
0x02, 0x3e, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00,
0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x08, 0x00, 0x00, 0x01, 0xf0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x08, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x88, 0x00, 0x00,
0x00, 0x10, 0x00, 0x00, 0x20, 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x84, 0x00, 0x00,
0x00, 0x18, 0x00, 0x00, 0x20, 0x40, 0x00, 0x20, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x84, 0x00, 0x00,
0x00, 0x14, 0x00, 0x00, 0x40, 0x40, 0x00, 0x10, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x84, 0x00, 0x00,
0x00, 0x52, 0x00, 0x00, 0x40, 0x20, 0x00, 0x08, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00,
0x00, 0x34, 0x00, 0x00, 0x40, 0x10, 0x00, 0x04, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00,
0x00, 0x18, 0x00, 0x00, 0x80, 0x10, 0x00, 0x02, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00,
0x00, 0x34, 0x00, 0x00, 0x80, 0x08, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00,
0x00, 0x52, 0x00, 0x00, 0x80, 0x08, 0x01, 0xf1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00,
0x00, 0x14, 0x00, 0x01, 0x00, 0x04, 0x3e, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00,
0x00, 0x18, 0x00, 0x01, 0x00, 0x07, 0xc0, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00,
0x00, 0x10, 0x00, 0x06, 0x80, 0x1c, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0xc4, 0x00, 0x00,
0x00, 0x00, 0x00, 0x18, 0x40, 0x64, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x3c, 0x78, 0x00, 0x00,
0x00, 0x00, 0x00, 0x3c, 0x40, 0x84, 0x00, 0x00, 0x01, 0x80, 0x00, 0x01, 0xc0, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x26, 0x23, 0x04, 0x00, 0x00, 0x00, 0x60, 0x00, 0x1e, 0x00, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x41, 0x88, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x43, 0x1c, 0x02, 0x00, 0x00, 0x00, 0x1c, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x41, 0xf8, 0x02, 0x00, 0x00, 0x00, 0x03, 0xfe, 0x00, 0x00, 0x40, 0x00, 0x00,
0x00, 0x00, 0x00, 0x40, 0x88, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x40, 0x88, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x40, 0x88, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x60, 0x88, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x30, 0x88, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x10, 0x88, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x19, 0x88, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0f, 0x88, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xf8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x02, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x07, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0xf8, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x1f, 0x20, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x03, 0xe8, 0x20, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x7c, 0x08, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x08, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x46, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x10, 0x00, 0x50, 0x00, 0x00,
0x00, 0x02, 0x28, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0x50, 0x00, 0x00,
0x00, 0x02, 0x28, 0x73, 0x2d, 0xc9, 0x5a, 0x8c, 0xb0, 0x20, 0x31, 0xdd, 0x66, 0x53, 0x2c, 0x00,
0x00, 0x03, 0xee, 0x44, 0xb1, 0x29, 0x63, 0x52, 0xc0, 0x20, 0x4a, 0x51, 0x89, 0x54, 0xb0, 0x00,
0x00, 0x02, 0x28, 0x47, 0xa1, 0x29, 0x42, 0x5e, 0x80, 0x20, 0x4a, 0x51, 0x09, 0x57, 0xa0, 0x00,
0x00, 0x02, 0x28, 0x44, 0x21, 0x2b, 0x42, 0x50, 0x80, 0x21, 0x4a, 0x51, 0x09, 0x54, 0x20, 0x00,
0x00, 0x02, 0x28, 0x33, 0x21, 0xc5, 0x42, 0x4c, 0x80, 0x1e, 0x32, 0x4d, 0x06, 0x53, 0x20, 0x00
};
*/
const uint8_t DieselSplash [] PROGMEM =
@ -489,6 +422,7 @@ CScreenManager::_loadScreens()
if(NVstore.getUserSettings().menuMode == 0 || NVstore.getUserSettings().menuMode == 2) {
menuloop.clear();
menuloop.push_back(new CVersionInfoScreen(*_pDisplay, *this)); // GPIO settings screen
menuloop.push_back(new CWebPageUpdateScreen(*_pDisplay, *this)); // Web Page update screen
if(NVstore.getUserSettings().menuMode == 0) {
menuloop.push_back(new CHourMeterScreen(*_pDisplay, *this)); // Hour Meter screen
}

View File

@ -0,0 +1,172 @@
/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2020 Ray Jones <ray@mrjones.id.au>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "128x64OLED.h"
#include "WebPageUpdateScreen.h"
#include "KeyPad.h"
#include "../Utility/helpers.h"
#include "fonts/Arial.h"
#include "../WiFi/BTCWiFi.h"
#include "../WiFi/BTCWebServer.h"
CWebPageUpdateScreen::CWebPageUpdateScreen(C128x64_OLED& display, CScreenManager& mgr) : CUIEditScreen(display, mgr)
{
_holdoff = 0;
}
void
CWebPageUpdateScreen::onSelect()
{
CScreen::onSelect();
}
bool
CWebPageUpdateScreen::show()
{
_display.clearDisplay();
// standard version information screens,
// animation of update available via animate() if firmware update is available on web server
_showTitle("Web Content Update");
int col = _display.xCentre();
if(_rowSel == 0) {
if(isWifiSTA()) {
_printMenuText(col, 16, "Press Up to update", false, eCentreJustify);
_printMenuText(col, 26, "web page content ", false, eCentreJustify);
_printMenuText(col, 36, "stored in SPIFFS. ", false, eCentreJustify);
}
else {
_printMenuText(col, 16, "WiFi STA connection", false, eCentreJustify);
_printMenuText(col, 26, "must be active to ", false, eCentreJustify);
_printMenuText(col, 36, "update web content!", false, eCentreJustify);
}
_printMenuText(_display.xCentre(), 53, " \021 \020 ", true, eCentreJustify); // " < > "
_printMenuText(_display.xCentre(), 53, "Exit", false, eCentreJustify); // " < Exit > "
}
else if(_rowSel == 1) {
_display.writeFillRect(12, 21, 104, 26, WHITE);
CTransientFont AF(_display, &arial_8ptBoldFontInfo);
_printInverted(col, 24, "Press Center to", true, eCentreJustify);
_printInverted(col, 34, "confirm update ", true, eCentreJustify);
}
else if(_rowSel == 2) {
_printMenuText(col, 22, "Getting:", false, eCentreJustify);
const char* filename = _getFileName();
_printMenuText(col, 34, filename, false, eCentreJustify);
}
return true;
}
bool
CWebPageUpdateScreen::animate()
{
int col = _display.xCentre();
if(_rowSel == 2) {
_printMenuText(col, 22, "Getting:", false, eCentreJustify);
const char* filename = _getFileName();
_display.fillRect(0,34,_display.width(), 10, BLACK);
_printMenuText(col, 34, filename, false, eCentreJustify);
return true;
}
if(_rowSel == 3) {
_display.writeFillRect(12, 21, 104, 26, WHITE);
CTransientFont AF(_display, &arial_8ptBoldFontInfo);
_printInverted(col, 29, "ERROR OCCURED", true, eCentreJustify);
long tDelta = millis() - _holdoff;
if(tDelta > 0) {
_holdoff = 0;
_rowSel = 0;
}
return true;
}
return false;
}
bool
CWebPageUpdateScreen::keyHandler(uint8_t event)
{
if(event & keyPressed) {
// UP press
if(event & key_Up) {
if(_rowSel == 0) {
if(isWifiSTA())
_rowSel = 1;
}
else {
_rowSel = 0;
}
}
// DOWN press
if(event & key_Down) {
_rowSel = 0;
}
// LEFT press
if(event & key_Left) {
if(_rowSel == 0)
_ScreenManager.prevMenu();
_rowSel = 0;
}
// RIGHT press
if(event & key_Right) {
if(_rowSel == 0)
_ScreenManager.nextMenu();
_rowSel = 0;
}
// CENTRE press
if(event & key_Centre) {
if(_rowSel == 1) {
getWebContent(true);
_rowSel = 2;
}
else {
_ScreenManager.selectMenu(CScreenManager::RootMenuLoop); // force return to main menu
}
}
}
_ScreenManager.reqUpdate();
return true;
}
const char*
CWebPageUpdateScreen::_getFileName()
{
const char* livename = getWebContent(false);
if(strcmp(livename, "DONE") == 0) {
_rowSel = 0;
}
if(strcmp(livename, "ERROR") == 0) {
_holdoff = (millis() + 2000) | 1;
_rowSel = 3;
}
return livename;
}

View File

@ -0,0 +1,45 @@
/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2020 Ray Jones <ray@mrjones.id.au>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#ifndef __WEBPAGEUPDATESCREEN_H__
#define __WEBPAGEUPDATESCREEN_H__
#include <stdint.h>
#include "UIEditScreen.h"
class C128x64_OLED;
class CScreenManager;
class CWebPageUpdateScreen : public CUIEditScreen
{
std::string _filename;
unsigned long _holdoff;
const char* _getFileName();
public:
CWebPageUpdateScreen(C128x64_OLED& display, CScreenManager& mgr);
bool show();
bool animate();
bool keyHandler(uint8_t event);
void onSelect();
};
#endif

View File

@ -409,11 +409,7 @@ void updateJSONclients(bool report)
if (report) {
DebugPort.printf("JSON send: %s\r\n", jsonStr);
}
sendWebSocketString( jsonStr );
mqttPublishJSON(jsonStr);
std::string expand = jsonStr;
Expand(expand);
getBluetoothClient().send( expand.c_str() );
sendJSONtext(jsonStr);
}
}
// update extended params
@ -422,11 +418,7 @@ void updateJSONclients(bool report)
if (report) {
DebugPort.printf("JSON send: %s\r\n", jsonStr);
}
sendWebSocketString( jsonStr );
mqttPublishJSON(jsonStr);
std::string expand = jsonStr;
Expand(expand);
getBluetoothClient().send( expand.c_str() );
sendJSONtext(jsonStr);
}
}
// update timer parameters
@ -437,11 +429,7 @@ void updateJSONclients(bool report)
if (report) {
DebugPort.printf("JSON send: %s\r\n", jsonStr);
}
sendWebSocketString( jsonStr );
mqttPublishJSON(jsonStr);
std::string expand = jsonStr;
Expand(expand);
getBluetoothClient().send( expand.c_str() );
sendJSONtext(jsonStr);
bNewTimerInfo = true;
}
}
@ -460,11 +448,7 @@ void updateJSONclients(bool report)
if (report) {
DebugPort.printf("JSON send: %s\r\n", jsonStr);
}
sendWebSocketString( jsonStr );
mqttPublishJSON(jsonStr);
std::string expand = jsonStr;
Expand(expand);
getBluetoothClient().send( expand.c_str() );
sendJSONtext(jsonStr);
}
// report MQTT params
@ -473,11 +457,7 @@ void updateJSONclients(bool report)
if (report) {
DebugPort.printf("JSON send: %s\r\n", jsonStr);
}
sendWebSocketString( jsonStr );
mqttPublishJSON(jsonStr);
std::string expand = jsonStr;
Expand(expand);
getBluetoothClient().send( expand.c_str() );
sendJSONtext(jsonStr);
}
}
@ -487,11 +467,7 @@ void updateJSONclients(bool report)
if (report) {
DebugPort.printf("JSON send: %s\r\n", jsonStr);
}
sendWebSocketString( jsonStr );
mqttPublishJSON(jsonStr);
std::string expand = jsonStr;
Expand(expand);
getBluetoothClient().send( expand.c_str() );
sendJSONtext(jsonStr);
}
}
@ -501,11 +477,7 @@ void updateJSONclients(bool report)
if (report) {
DebugPort.printf("JSON send: %s\r\n", jsonStr);
}
sendWebSocketString( jsonStr );
mqttPublishJSON(jsonStr);
std::string expand = jsonStr;
Expand(expand);
getBluetoothClient().send( expand.c_str() );
sendJSONtext(jsonStr);
}
}
@ -514,11 +486,7 @@ void updateJSONclients(bool report)
if (report) {
DebugPort.printf("JSON send: %s\r\n", jsonStr);
}
sendWebSocketString( jsonStr );
mqttPublishJSON(jsonStr);
std::string expand = jsonStr;
Expand(expand);
getBluetoothClient().send( expand.c_str() );
sendJSONtext(jsonStr);
}
}
@ -538,7 +506,8 @@ void resetAllJSONmoderators()
resetJSONSysModerator(); // initJSONSysModerator();
GPIOmoderator.reset();
// create and send a validation code (then client knows AB is capable of reboot over JSON)
doJSONreboot(0);
doJSONreboot(0);
sendJSONtext("{\"LoadWebContent\":\"Supported\"}");
}
void initJSONMQTTmoderator()
@ -596,6 +565,8 @@ void sendJSONtext(const char* jsonStr)
{
sendWebSocketString( jsonStr );
mqttPublishJSON(jsonStr);
std::string expand = jsonStr;
Expand(expand);
getBluetoothClient().send( jsonStr );
}

View File

@ -41,6 +41,7 @@ void resetJSONSysModerator();
void resetJSONMQTTmoderator();
void validateTimer(int ID);
void doJSONreboot(uint16_t code);
void sendJSONtext(const char* JSONstr);
template<class T>
const char* createJSON(const char* name, T value)

View File

@ -26,6 +26,7 @@
#include "HourMeter.h"
#include "macros.h"
#include "BTC_JSON.h"
#include "../WiFi/BTCWebServer.h"
// a class to track the blue wire receive / transmit states
@ -439,5 +440,12 @@ void DecodeCmd(const char* cmd, String& payload)
int16_t code = payload.toInt();
doJSONreboot(code);
}
else if(strcmp("LoadWebContent", cmd) == 0) {
getWebContent(true);
}
}
void setHoldoff(unsigned long& holdoff, unsigned long period)
{
holdoff = (millis() + period) | 1;
}

View File

@ -177,5 +177,6 @@ enum eOTAmodes {
eOTAnormal, eOTAbrowser, eOTAWWW
};
void setHoldoff(unsigned long& holdoff, unsigned long period);
#endif // __UTIL_CLASSES_H__

View File

@ -55,7 +55,7 @@ extern void checkSplashScreenUpdate();
sBrowserUpload BrowserUpload;
WebServer server(80);
WebSocketsServer webSocket = WebSocketsServer(81);
CWebContentDL WebContentDL;
CGetWebContent GetWebContent;
bool bRxWebData = false; // flags for OLED animation
bool bTxWebData = false;
@ -85,7 +85,17 @@ void onUploadProgression();
void onRename();
void build404Response(String& content, String file);
void build500Response(String& content, String file);
void manageWegContentUpdate();
const char* getWebContent(bool start) {
if(isWifiSTA() && start) {
GetWebContent.start();
}
return GetWebContent.getFilename();
}
void initWebServer(void) {
@ -149,7 +159,7 @@ bool doWebServer(void)
{
webSocket.loop();
server.handleClient();
manageWegContentUpdate();
GetWebContent.manage();
return true;
}
@ -872,7 +882,7 @@ body {
<body>
<h1>SPIFFS partition has been formatted</h1>
<h3>You must now upload the web content.</h3>
<p>Latest web content can be downloaded from <a href='http://www.mrjones.id.au/afterburner/firmware.html' target='_blank'>http://www.mrjones.id.au/afterburner/firmware.html</a>
<p>Latest web content can be downloaded from <a href='http://afterburner.mrjones.id.au/firmware.html' target='_blank'>http://afterburner.mrjones.id.au/firmware.html</a>
<h4 class="throb">Please ensure you unzip the web page content, then upload all the files contained.</h4>
<p><button onclick=location.assign('/update')>Upload web content</button>
</body>
@ -1027,7 +1037,7 @@ for (uint8_t i = 0; i < server.args(); i++) {
content += R"=====(<hr>
<p>Please check the URL.<br>
If OK please try uploading the file from the web content.
<p>Latest web content can be downloaded from <a href="http://www.mrjones.id.au/afterburner/firmware.html" target="_blank">http://www.mrjones.id.au/afterburner/firmware.html</a>
<p>Latest web content can be downloaded from <a href="http://afterburner.mrjones.id.au/firmware.html" target="_blank">http://afterburner.mrjones.id.au/firmware.html</a>
<h4 class="throb">Please ensure you unzip the web page content, then upload all the files contained.</h4>
<p><a href="/update"><button>Upload web content</button></a><br>
)=====";
@ -1054,7 +1064,7 @@ content += file;
content += R"=====(" </i></b> exists, but cannot be streamed?
<hr>
<p>Recommended remedy is to re-format the SPIFFS partition, then reload the web content files.
<br>Latest web content can be downloaded from <a href="http://www.mrjones.id.au/afterburner/firmware.html" target="_blank">http://www.mrjones.id.au/afterburner/firmware.html</a> <i>(opens in new page)</i>.
<br>Latest web content can be downloaded from <a href="http://afterburner.mrjones.id.au/firmware.html" target="_blank">http://afterburner.mrjones.id.au/firmware.html</a> <i>(opens in new page)</i>.
<p>To format the SPIFFS partition, press <button class='redbutton' onClick=location.assign('/formatspiffs')>Format SPIFFS</button>
<p>You will then need to upload each file of the web content by using the subsequent "<b>Upload</b>" button.
<hr>
@ -1064,41 +1074,4 @@ content += R"=====(" </i></b> exists, but cannot be streamed?
)=====";
}
static int webContentState = 0;
void getWebContent() {
webContentState = 1;
// WebContentDL.get("index.html.gz");
// getWebContent("favicon.ico");
}
void manageWegContentUpdate()
{
switch(webContentState) {
case 1:
DebugPort.println("Requesting index.html.gz from Afterburner web site");
WebContentDL.get("index.html.gz");
webContentState++;
break;
case 2:
WebContentDL.process();
if(!WebContentDL.busy()) {
DebugPort.println("Completed index.html.gz from Afterburner web site");
webContentState++;
}
break;
case 3:
DebugPort.println("Requesting favicon.ico from Afterburner web site");
WebContentDL.get("favicon.ico");
webContentState++;
break;
case 4:
WebContentDL.process();
if(!WebContentDL.busy()) {
DebugPort.println("Completed favicon.ico from Afterburner web site");
webContentState = 0;
}
break;
}
}

View File

@ -34,7 +34,7 @@ bool sendWebSocketString(const char* Str);
bool isWebSocketClientChange();
void listSPIFFS(const char * dirname, uint8_t levels, String& HTMLreport, int withHTMLanchors=0);
void getWebContent();
const char* getWebContent(bool start);
void getWebContent(const char* filename);
#endif

View File

@ -126,9 +126,9 @@ void DoOTA()
FOTA.onComplete(CheckFirmwareCRC); // upload complete, but not yet verified
FOTA.onSuccess(onSuccess);
#ifdef TESTFOTA
FOTA.setCheckURL("http://www.mrjones.id.au/afterburner/fota/fotatest.json");
FOTA.setCheckURL("http://afterburner.mrjones.id.au/fota/fotatest.json");
#else
FOTA.setCheckURL("http://www.mrjones.id.au/afterburner/fota/fota.json");
FOTA.setCheckURL("http://afterburner.mrjones.id.au/fota/fota.json");
#endif
#ifdef SYNCHRONOUS_FOTA
@ -174,6 +174,8 @@ void DoOTA()
int isUpdateAvailable(bool test)
{
FOTA.process(); // manage any queued responses
if(test) {
if(FOTAauth >= 1) {
return FOTA.getNewVersion();

View File

@ -1,9 +1,37 @@
/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2020 Ray Jones
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
// seek a web page update from the afterburner web server
#include "WebContentDL.h"
#include "../Utility/DebugPort.h"
#include "../Utility/helpers.h"
#include "../Utility/BTC_JSON.h"
// #define DUMP_WEB_BYTES
// callback function for bulk of file download.
// It appears the callback may well be running at an elevated priority, perhaps IRQL
// and that causes random core crashes when accessing SPIFFS.
// Instead me marshall the data via FreeRTOS queue to be handled at a definite user level later.
void WebPageDataCB(void* pClass, asyncHTTPrequest* request, size_t available)
{
CWebContentDL* pParent = (CWebContentDL*)pClass;
@ -17,25 +45,29 @@ void WebPageDataCB(void* pClass, asyncHTTPrequest* request, size_t available)
}
// callback function for completion of the Async TCP "GET" request.
// Not all file data may be processed yet...
void WebPageRequestCB(void* pClass, asyncHTTPrequest* request, int readyState)
{
CWebContentDL* pParent = (CWebContentDL*)pClass;
if(readyState == 4){
if(readyState == 4) {
while(request->available()) {
pParent->queueDLdata(request->available(), request);
}
pParent->finalise();
pParent->finalise(request->responseHTTPcode() == 200); // mark end of file data, causes SPIFFS file to be closed.
request->close();
request->close(); // graceful close the Async TCP connection
}
}
CWebContentDL::CWebContentDL()
{
// _request.setDebug(true);
_request.setDebug(true);
_request.onReadyStateChange(WebPageRequestCB, this);
_request.onData(WebPageDataCB, this);
_queue = xQueueCreate(10, sizeof(sQueueEntry));
_queue = NULL;
_fileActive = false;
_bytecount = 0;
_queuecount = 0;
@ -43,71 +75,83 @@ CWebContentDL::CWebContentDL()
CWebContentDL::~CWebContentDL()
{
vQueueDelete(_queue);
_closequeue();
}
bool
CWebContentDL::busy() const
{
return _fileActive || (_request.readyState() != 0 && _request.readyState() != 4) ;
if(_fileActive)
return true;
return _request.readyState() != 0 && _request.readyState() != 4;
}
bool
CWebContentDL::OK() const
{
return _bOK;
}
void CWebContentDL::get(const char* filename)
{
if(_request.readyState() == 0 || _request.readyState() == 4){
// ensure leading forward slash, required for SPIFFS
_filename = "";
if(filename[0] != '/') _filename = "/";
_filename += filename;
// replace with sanitised name
filename = _filename.c_str();
DebugPort.printf("Loading file to SPIFFS: '%s'\r\n", filename);
if(SPIFFS.exists(filename)) {
DebugPort.println("Removing existing file from SPIFFS");
SPIFFS.remove(filename);
}
_file = SPIFFS.open(filename, "w"); // Open the file for writing in SPIFFS (create if it doesn't exist)
_fileActive = true;
_openqueue();
_openfile(filename);
_bytecount = 0;
_queuecount = 0;
_bOK = false;
String URL = "http://afterburner.mrjones.id.au/fota/web";
URL += filename;
std::string URL = "http://afterburner.mrjones.id.au/fota/web";
URL += _filename;
_request.open("GET", URL.c_str());
_request.send();
}
}
void CWebContentDL::process()
// routine called regualrly by the "loop" task - ie not IRQL
// it is no safe to write to SPIFFS in the AsyncTCP callbacks!
void
CWebContentDL::process()
{
sQueueEntry entry;
while(xQueueReceive(_queue, &entry, 0)) {
while(_queue != NULL && xQueueReceive(_queue, &entry, 0)) {
int16_t len = entry.len;
if(len == -1) {
if(_file) {
_file.close();
_fileActive = false;
}
_closefile();
_closequeue();
_bOK = true;
DebugPort.printf("Downloaded %s (%d bytes) - CLOSED OK\r\n", _filename.c_str(), _bytecount);
}
else if(len == -2) {
_closefile();
SPIFFS.remove(_filename.c_str()); // remove the bad file from SPIFFS
_closequeue();
DebugPort.printf("HTTP ERROR ENCOUNTERED: %s\r\n", _filename.c_str());
}
else if(len > 0) {
if(_file) {
if(_file.write(entry.data, len) != len) { // Write the received bytes to the file
_file.close();
_fileActive = false;
DebugPort.printf("Web content downlod - FILE_WRITE error: removing %s\r\n", _filename.c_str());
_closefile();
DebugPort.printf("Web content download - FILE_WRITE error: removing %s\r\n", _filename.c_str());
SPIFFS.remove(_filename.c_str()); // remove the bad file from SPIFFS
}
else {
#ifdef DUMP_WEB_BYTES
for(int i=0; i< len;) {
for(int j=0; j< 32 && i<len; j++) {
DebugPort.printf("%02X ", entry.data[i++]);
if((i & 0xf) == 0)
DebugPort.print(" ");
if((i & 0x7) == 0)
DebugPort.print(" ");
}
DebugPort.print("\r\n");
}
#endif
_bytecount += len;
}
}
}
// DebugPort.printf("Len=%d Queuecount=%d/%d total=%d\r\n", entry.len, entry.count, queuecount, webpagecount);
}
}
@ -121,30 +165,234 @@ CWebContentDL::queueDLdata(int size, asyncHTTPrequest* request)
int16_t read = request->responseRead(entry.data, size);
if(read > 0) {
// available -= read;
entry.len = read;
entry.count = ++_queuecount;
BaseType_t awoken;
xQueueSendFromISR(_queue, &entry, &awoken);
if(_queue == NULL) {
DebugPort.println("CWebContentDL::queueDLdata - no queue!");
}
else if(read <= 0) {
DebugPort.println("CWebContentDL::queueDLdata - read error?");
}
else {
DebugPort.println(" page read error?");
// available -= read;
if(request->responseHTTPcode() == 200) { // only push to queue if HTTP OK
entry.len = read;
entry.count = ++_queuecount;
BaseType_t awoken;
xQueueSendFromISR(_queue, &entry, &awoken);
}
}
return read;
}
void
CWebContentDL::finalise()
CWebContentDL::finalise(bool OK)
{
sQueueEntry entry;
if(_queue != NULL) {
sQueueEntry entry;
entry.len = -1;
entry.count = -1;
BaseType_t awoken;
xQueueSendFromISR(_queue, &entry, &awoken);
entry.len = OK ? -1 : -2;
entry.count = -1;
BaseType_t awoken;
xQueueSendFromISR(_queue, &entry, &awoken);
}
}
void
CWebContentDL::abort()
{
_request.close();
_closefile();
}
void
CWebContentDL::_closefile()
{
if(_file) {
_file.close();
_fileActive = false;
}
}
void
CWebContentDL::_openqueue()
{
if(_queue == NULL) {
_queue = xQueueCreate(10, sizeof(sQueueEntry));
_queuecount = 0;
}
}
void
CWebContentDL::_closequeue()
{
if(_queue)
vQueueDelete(_queue);
_queue = NULL;
}
void
CWebContentDL::_setfilename(const char* filename)
{
// ensure leading forward slash, required for SPIFFS
_filename = "";
if(filename[0] != '/') _filename = "/";
_filename += filename;
}
void
CWebContentDL::_openfile(const char* filename)
{
_setfilename(filename);
filename = _filename.c_str(); // replace with sanitised name
DebugPort.printf("Loading file to SPIFFS: '%s'\r\n", filename);
if(SPIFFS.exists(filename)) {
DebugPort.println(" Already exists! - removing");
SPIFFS.remove(filename);
}
_file = SPIFFS.open(filename, "w"); // Open the file for writing in SPIFFS
_fileActive = true;
}
const char*
CWebContentDL::getState()
{
return _filename.c_str();
}
CGetWebContent::CGetWebContent()
{
_state = 0;
_holdoff = 0;
}
void
CGetWebContent::start() {
_state = 1;
manage();
}
void
CGetWebContent::_get(const char* filename)
{
_filename = filename;
DebugPort.printf("Requesting %s from Afterburner web site\r\n", _filename.c_str());
handler.get(_filename.c_str());
setHoldoff(_holdoff, 15000);
}
bool
CGetWebContent::_done()
{
if(!handler.busy()) {
DebugPort.printf("Completed %s from Afterburner web site\r\n", _filename.c_str());
return true;
}
return false;
}
void
CGetWebContent::manage()
{
switch(_state) {
case 1:
_get("index.html.gz");
_state++;
break;
case 2:
handler.process();
if(_timeout()) {
handler.abort();
_state = -1;
}
if(_done()) {
if(!handler.OK()) {
setHoldoff(_holdoff, 1000);
_state = -2;
}
else {
_sendJSON();
_state++;
}
}
break;
case 3:
_get("favicon.ico");
_state++;
break;
case 4:
handler.process();
if(_timeout()) {
handler.abort();
_state = -1;
}
if(_done()) {
setHoldoff(_holdoff, 1000);
if(!handler.OK()) {
setHoldoff(_holdoff, 1000);
_state = -2;
}
else {
_state++;
_sendJSON();
}
}
break;
case 5:
if(_timeout()) {
_state = 0;
_sendJSON("DONE");
}
break;
case -2:
if(_timeout()) {
_state = -1;
_sendJSON("ERROR");
}
break;
}
}
bool
CGetWebContent::_timeout() {
if(_holdoff) {
long tDelta = millis() - _holdoff;
if(tDelta > 0) {
_holdoff = 0;
return true;
}
else
return false;
}
return true;
}
const char*
CGetWebContent::getFilename()
{
if(_state == -1)
return "ERROR";
if(_state == 0)
return "DONE";
return _filename.c_str();
}
void
CGetWebContent::_sendJSON(const char* name)
{
std::string JSONmsg;
JSONmsg = "{\"LoadWebContent\":\"";
if(name == NULL)
JSONmsg += _filename;
else
JSONmsg += name;
JSONmsg += "\"}";
sendJSONtext(JSONmsg.c_str());
}

View File

@ -1,3 +1,24 @@
/*
* This file is part of the "bluetoothheater" distribution
* (https://gitlab.com/mrjones.id.au/bluetoothheater)
*
* Copyright (C) 2020 Ray Jones
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
// seek a web page update from the afterburner web server
#include "../../asyncHTTPrequest/src/asyncHTTPrequest.h"
@ -21,6 +42,12 @@ class CWebContentDL {
int _bytecount;
int _queuecount;
QueueHandle_t _queue;
bool _bOK;
void _closefile();
void _openfile(const char* filename);
void _openqueue();
void _closequeue();
void _setfilename(const char* filename);
public:
CWebContentDL();
~CWebContentDL();
@ -28,10 +55,31 @@ public:
void process();
// callback handlers
int16_t queueDLdata(int size, asyncHTTPrequest* request);
void finalise();
void finalise(bool OK);
bool busy() const;
bool OK() const;
void abort();
const char* getState();
};
class CGetWebContent
{
unsigned long _holdoff;
int _state;
std::string _filename;
void _get(const char* filename);
bool _done();
bool _timeout();
void _sendJSON(const char* filename=NULL);
CWebContentDL handler;
public:
CGetWebContent();
void start();
void manage();
const char* getFilename();
};
void WebPageRequestCB(void* optParm, asyncHTTPrequest* request, int readyState);
void WebPageDataCB(void* optParm, asyncHTTPrequest*, size_t available);