diff --git a/lib/esp32FOTA/src/esp32fota.cpp b/lib/esp32FOTA/src/esp32fota.cpp index 6a69287..bff0d26 100644 --- a/lib/esp32FOTA/src/esp32fota.cpp +++ b/lib/esp32FOTA/src/esp32fota.cpp @@ -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 @@ -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 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); } } diff --git a/lib/esp32FOTA/src/esp32fota.h b/lib/esp32FOTA/src/esp32fota.h index cd7ff74..b6bdbca 100644 --- a/lib/esp32FOTA/src/esp32fota.h +++ b/lib/esp32FOTA/src/esp32fota.h @@ -16,6 +16,12 @@ //#include #include #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 _onSuccess; std::function _onFail; asyncHTTPrequest _versionTest; + QueueHandle_t _queue; + String _pollResponse; }; diff --git a/src/Afterburner.cpp b/src/Afterburner.cpp index 4d144ba..c6f4497 100644 --- a/src/Afterburner.cpp +++ b/src/Afterburner.cpp @@ -1568,7 +1568,7 @@ void checkDebugCommands() TxManage.queueOffRequest(); } else if(rxVal == 'h') { - getWebContent(); + getWebContent(true); } else if(rxVal == 'r') { ESP.restart(); // reset the esp diff --git a/src/OLED/ScreenManager.cpp b/src/OLED/ScreenManager.cpp index 87b701f..e126097 100644 --- a/src/OLED/ScreenManager.cpp +++ b/src/OLED/ScreenManager.cpp @@ -51,6 +51,7 @@ #include "TempSensorScreen.h" #include "FrostScreen.h" #include "HumidityScreen.h" +#include "WebPageUpdateScreen.h" #include #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 } diff --git a/src/OLED/WebPageUpdateScreen.cpp b/src/OLED/WebPageUpdateScreen.cpp new file mode 100644 index 0000000..edb1559 --- /dev/null +++ b/src/OLED/WebPageUpdateScreen.cpp @@ -0,0 +1,172 @@ +/* + * 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 . + * + */ + +#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; +} diff --git a/src/OLED/WebPageUpdateScreen.h b/src/OLED/WebPageUpdateScreen.h new file mode 100644 index 0000000..d96de54 --- /dev/null +++ b/src/OLED/WebPageUpdateScreen.h @@ -0,0 +1,45 @@ +/* + * 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 . + * + */ + +#ifndef __WEBPAGEUPDATESCREEN_H__ +#define __WEBPAGEUPDATESCREEN_H__ + +#include +#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 diff --git a/src/Utility/BTC_JSON.cpp b/src/Utility/BTC_JSON.cpp index 2591808..af43bd3 100644 --- a/src/Utility/BTC_JSON.cpp +++ b/src/Utility/BTC_JSON.cpp @@ -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 ); } diff --git a/src/Utility/BTC_JSON.h b/src/Utility/BTC_JSON.h index 0a759f1..68b9231 100644 --- a/src/Utility/BTC_JSON.h +++ b/src/Utility/BTC_JSON.h @@ -41,6 +41,7 @@ void resetJSONSysModerator(); void resetJSONMQTTmoderator(); void validateTimer(int ID); void doJSONreboot(uint16_t code); +void sendJSONtext(const char* JSONstr); template const char* createJSON(const char* name, T value) diff --git a/src/Utility/UtilClasses.cpp b/src/Utility/UtilClasses.cpp index a673df5..e0bdd40 100644 --- a/src/Utility/UtilClasses.cpp +++ b/src/Utility/UtilClasses.cpp @@ -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; +} \ No newline at end of file diff --git a/src/Utility/UtilClasses.h b/src/Utility/UtilClasses.h index de92e1b..cc07859 100644 --- a/src/Utility/UtilClasses.h +++ b/src/Utility/UtilClasses.h @@ -177,5 +177,6 @@ enum eOTAmodes { eOTAnormal, eOTAbrowser, eOTAWWW }; +void setHoldoff(unsigned long& holdoff, unsigned long period); #endif // __UTIL_CLASSES_H__ diff --git a/src/WiFi/BTCWebServer.cpp b/src/WiFi/BTCWebServer.cpp index 9365cc7..ef8364f 100644 --- a/src/WiFi/BTCWebServer.cpp +++ b/src/WiFi/BTCWebServer.cpp @@ -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 {

SPIFFS partition has been formatted

You must now upload the web content.

-

Latest web content can be downloaded from http://www.mrjones.id.au/afterburner/firmware.html +

Latest web content can be downloaded from http://afterburner.mrjones.id.au/firmware.html

Please ensure you unzip the web page content, then upload all the files contained.

@@ -1027,7 +1037,7 @@ for (uint8_t i = 0; i < server.args(); i++) { content += R"=====(


Please check the URL.
If OK please try uploading the file from the web content. -

Latest web content can be downloaded from http://www.mrjones.id.au/afterburner/firmware.html +

Latest web content can be downloaded from http://afterburner.mrjones.id.au/firmware.html

Please ensure you unzip the web page content, then upload all the files contained.


)====="; @@ -1054,7 +1064,7 @@ content += file; content += R"=====(" exists, but cannot be streamed?


Recommended remedy is to re-format the SPIFFS partition, then reload the web content files. -
Latest web content can be downloaded from http://www.mrjones.id.au/afterburner/firmware.html (opens in new page). +
Latest web content can be downloaded from http://afterburner.mrjones.id.au/firmware.html (opens in new page).

To format the SPIFFS partition, press

You will then need to upload each file of the web content by using the subsequent "Upload" button.


@@ -1064,41 +1074,4 @@ content += R"=====(" 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; - } -} diff --git a/src/WiFi/BTCWebServer.h b/src/WiFi/BTCWebServer.h index a33dd37..5ba26de 100644 --- a/src/WiFi/BTCWebServer.h +++ b/src/WiFi/BTCWebServer.h @@ -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 diff --git a/src/WiFi/BTCota.cpp b/src/WiFi/BTCota.cpp index b269cea..9485732 100644 --- a/src/WiFi/BTCota.cpp +++ b/src/WiFi/BTCota.cpp @@ -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(); diff --git a/src/WiFi/WebContentDL.cpp b/src/WiFi/WebContentDL.cpp index 00fc5cf..54a4a20 100644 --- a/src/WiFi/WebContentDL.cpp +++ b/src/WiFi/WebContentDL.cpp @@ -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 . + * + */ + // 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 && iresponseRead(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()); +} + diff --git a/src/WiFi/WebContentDL.h b/src/WiFi/WebContentDL.h index 70033d1..47d8ddf 100644 --- a/src/WiFi/WebContentDL.h +++ b/src/WiFi/WebContentDL.h @@ -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 . + * + */ + // 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);