diff --git a/.gitignore b/.gitignore index eb3b370..70d9b68 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ .pio .vscode /RX_FSK/git_version.h +/RX_FSK/git_version.c diff --git a/RX_FSK/RX_FSK.ino b/RX_FSK/RX_FSK.ino index 9643d53..84cc676 100644 --- a/RX_FSK/RX_FSK.ino +++ b/RX_FSK/RX_FSK.ino @@ -44,6 +44,9 @@ #include "src/conn-aprs.h" #endif +#include "src/ota.h" +#include "common.h" + //#define ESP_MEM_DEBUG 1 //int e; @@ -57,13 +60,15 @@ PMU *pmu = NULL; SemaphoreHandle_t axpSemaphore; extern uint8_t pmu_irq; +#if 0 const char *updateHost = "rdzsonde.tuu.fi"; +//const char *updateHost = "heittoka.kapsi.fi"; int updatePort = 80; const char *updatePrefixM = "/master/"; const char *updatePrefixD = "/devel/"; const char *updatePrefix = updatePrefixM; - +#endif // 0 #define LOCALUDPPORT 9002 //Get real UTC time from NTP server @@ -119,7 +124,7 @@ static int currentDisplay = 1; // timestamp when spectrum display was activated static unsigned long specTimer; -void enterMode(int mode); +//void enterMode(int mode); void WiFiEvent(WiFiEvent_t event); @@ -1224,6 +1229,13 @@ void SetupAsyncServer() { handleUpdatePost(request); request->send(200, "text/html", createUpdateForm(1)); }); + server.on("/ota.html", HTTP_GET, [](AsyncWebServerRequest * request) { + request->send(200, "text/html", ota_create_form(0, message)); + }); + server.on("/ota.html", HTTP_POST, [](AsyncWebServerRequest * request) { + ota_handle_post(request); + request->send(200, "text/html", ota_create_form(1, message)); + }); server.on("/control.html", HTTP_GET, [](AsyncWebServerRequest * request) { request->send(200, "text/html", createControlForm()); @@ -2636,7 +2648,7 @@ void loopWifiScan() { initialMode(); } - +#if 0 /// Testing OTA Updates /// somewhat based on Arduino's AWS_S3_OTA_Update // Utility to extract header value from headers @@ -2663,7 +2675,7 @@ void execOTA() { dispys = 20; disp.rdis->drawString(0, 0, updateHost); } - +#if 1 Serial.print("Connecting to: "); Serial.println(updateHost); // Connect to Update host if (!client.connect(updateHost, updatePort)) { @@ -2683,6 +2695,8 @@ void execOTA() { int type = 0; int res = fetchHTTPheader(&type); if (res < 0) { + // Back to some normal state + enterMode(ST_DECODER); return; } // process data... @@ -2718,7 +2732,7 @@ void execOTA() { } } client.stop(); - +#endif // 0 Serial.print("Connecting to: "); Serial.println(updateHost); // Connect to Update host if (!client.connect(updateHost, updatePort)) { @@ -2873,7 +2887,7 @@ int fetchHTTPheader(int *validType) { } return contentLength; } - +#endif // 0 void loop() { diff --git a/RX_FSK/common.h b/RX_FSK/common.h new file mode 100644 index 0000000..3490378 --- /dev/null +++ b/RX_FSK/common.h @@ -0,0 +1,7 @@ + +extern const char *updatePrefixM; +extern const char *updatePrefixD; +extern const char *updatePrefix; +extern WiFiClient client; + +void enterMode(int mode); \ No newline at end of file diff --git a/RX_FSK/data/index.html b/RX_FSK/data/index.html index 3e13977..14c0932 100755 --- a/RX_FSK/data/index.html +++ b/RX_FSK/data/index.html @@ -17,6 +17,7 @@ Control Config WiFi + OTA About @@ -48,14 +49,19 @@ +
+ +
+
%VERSION_NAME%
Copyright © 2019-2022 by Hansi Reiser, DL9RDZ
(version %VERSION_ID%)

+ with contributions by Vigor and Xavier (M20 support),
Luke Prior and OH3BSG (SondeHub support), Meinhard Guenther, DL2MF, diff --git a/RX_FSK/src/ota.cpp b/RX_FSK/src/ota.cpp new file mode 100644 index 0000000..111c9c3 --- /dev/null +++ b/RX_FSK/src/ota.cpp @@ -0,0 +1,330 @@ +#include +#include +#include "ota.h" +#include "git_version.h" +#include "common.h" +#include "Sonde.h" +#include "Display.h" +//#include +#include + +#define OTA_FIRMWARE 0 +#define OTA_FILESYSTEM 1 + +//const char *updateHost = "rdzsonde.tuu.fi"; +const char *updateHost = "heittoka.kapsi.fi"; +int updatePort = 80; + +const char *updatePrefixM = "/master/"; +//const char *updatePrefixD = "/multi_ch/devel/"; +const char *updatePrefixD = "/devel/"; +const char *updatePrefix = updatePrefixM; + +uint8_t ota_image_type; + +extern HardwareSerial Serial; +extern WiFiClient client; + +int fetchHTTPheader(int *validType); + +const char *ota_create_form(boolean run, char *message) { +#if 1 + char *ptr = message; + strcpy(ptr, "
"); + if (run) { + strcat(ptr, "

Doing update, wait until reboot

"); + } else { + sprintf(ptr + strlen(ptr), "

Currently installed: %s-%c%d

\n", version_id, SPIFFS_MAJOR + 'A' - 1, SPIFFS_MINOR); + strcat(ptr, "

Available release::
" + "Available devel:

"); + strcat(ptr, "
"); + strcat(ptr, "

Note: If suffix is the same, update should work fully. If the number is different, update contains changes in the file system. A full re-flash is required to get all new features, but the update should not break anything. If the letter is different, a full re-flash is mandatory, update will not work

"); + } + strcat(ptr, "
"); + //Serial.printf("Update form: size=%d bytes\n", strlen(message)); +#endif // 0 + return message; +} + +const char *ota_handle_post(AsyncWebServerRequest * request) { +#if 1 + Serial.println("Handling post request"); + int params = request->params(); + for (int i = 0; i < params; i++) { + String param = request->getParam(i)->name(); + Serial.println(param.c_str()); + if (param.equals("devel")) { + Serial.println("equals devel"); + updatePrefix = updatePrefixD; + } + else if (param.equals("master")) { + Serial.println("equals master"); + updatePrefix = updatePrefixM; + } + else if (param.equals("firmware")) { + Serial.println("equals firmware"); + ota_image_type = OTA_FIRMWARE; + } + else if (param.equals("filesystem")) { + Serial.println("equals filesystem"); + ota_image_type = OTA_FILESYSTEM; + } + } + Serial.printf("Updating: %supdate.ino.bin\n", updatePrefix); + enterMode(3 /*ST_UPDATE*/); // tää käynnistää execOTA() +#endif // 0 + return ""; +} + +#if 1 +/// Testing OTA Updates +/// somewhat based on Arduino's AWS_S3_OTA_Update +// Utility to extract header value from headers +String getHeaderValue(String header, String headerName) { + return header.substring(strlen(headerName.c_str())); +} + +// OTA Logic +void execOTA() { + int contentLength = 0; + bool isValidContentType = false; + sonde.clearDisplay(); + uint8_t dispxs, dispys; + if ( ISOLED(sonde.config) ) { + disp.rdis->setFont(FONT_SMALL); + dispxs = dispys = 1; + char uh[17]; + strncpy(uh, updateHost, 17); + uh[16] = 0; + disp.rdis->drawString(0, 0, uh); + } else { + disp.rdis->setFont(5); + dispxs = 18; + dispys = 20; + disp.rdis->drawString(0, 0, updateHost); + } + + if (ota_image_type == OTA_FILESYSTEM) { +#if 1 + //Serial.print("Connecting to: "); Serial.println(updateHost); + // Connect to Update host + if (!client.connect(updateHost, updatePort)) { + //Serial.println("Connection to " + String(updateHost) + " failed. Please check your setup"); + return; + } + + // First, update file system + //Serial.println("Fetching fs update"); + disp.rdis->drawString(0, 1 * dispys, "Fetching fs..."); + client.printf("GET %supdate.fs.bin HTTP/1.1\r\n" + "Host: %s\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n\r\n", updatePrefix, updateHost); + // see if we get some data.... + + int type = 0; + int res = fetchHTTPheader(&type); + if (res < 0) { + // Back to some normal state + enterMode(0 /*ST_DECODER*/); + return; + } + // process data... + while (client.available()) { + // get header... + char fn[128]; + fn[0] = '/'; + client.readBytesUntil('\n', fn + 1, 128); + char *sz = strchr(fn, ' '); + if (!sz) { + client.stop(); + return; + } + *sz = 0; + int len = atoi(sz + 1); + //Serial.printf("Updating file %s (%d bytes)\n", fn, len); + char fnstr[17]; + memset(fnstr, ' ', 16); + strncpy(fnstr, fn, 16); + fnstr[16] = 0; + disp.rdis->drawString(0, 2 * dispys, fnstr); + File f = SPIFFS.open(fn, FILE_WRITE); + // read sz bytes........ + while (len > 0) { + unsigned char buf[1024]; + int r = client.read(buf, len > 1024 ? 1024 : len); + if (r == -1) { + client.stop(); + return; + } + f.write(buf, r); + len -= r; + } + } + client.stop(); +#endif // 0 + } + + if (ota_image_type == OTA_FIRMWARE) { + Serial.print("Connecting to: "); Serial.println(updateHost); + // Connect to Update host + if (!client.connect(updateHost, updatePort)) { + Serial.println("Connection to " + String(updateHost) + " failed. Please check your setup"); + return; + } + + // Connection succeeded, fecthing the bin + //Serial.printf("Fetching bin: %supdate.ino.bin\n", updatePrefix); + disp.rdis->drawString(0, 3 * dispys, "Fetching update"); + Serial.println("A"); + + // Get the contents of the bin file + client.printf("GET %supdate.ino.bin HTTP/1.1\r\n" + "Host: %s\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n\r\n", + updatePrefix, updateHost); + Serial.println("AA"); + // Check what is being sent + // Serial.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"); + + int validType = 0; + contentLength = fetchHTTPheader( &validType ); + Serial.println("AAA"); + if (validType == 1) isValidContentType = true; + + // Check what is the contentLength and if content type is `application/octet-stream` + Serial.println("contentLength : " + String(contentLength) + ", isValidContentType : " + String(isValidContentType)); + disp.rdis->drawString(0, 4 * dispys, "Len: "); + String cls = String(contentLength); + disp.rdis->drawString(5 * dispxs, 4 * dispys, cls.c_str()); + + // 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) { + disp.rdis->drawString(0, 5 * dispys, "Starting update"); + Serial.println("Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!"); + // No activity would appear on the Serial monitor + // So be patient. This may take 2 - 5mins to complete + size_t written = Update.writeStream(client); + + if (written == contentLength) { + //Serial.println("Written : " + String(written) + " successfully"); + } else { + //Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?" ); + // retry?? + // execOTA(); + } + + if (Update.end()) { + //Serial.println("OTA done!"); + if (Update.isFinished()) { + //Serial.println("Update successfully completed. Rebooting."); + disp.rdis->drawString(0, 7 * dispys, "Rebooting...."); + delay(1000); + ESP.restart(); + } else { + //Serial.println("Update not finished? Something went wrong!"); + } + } else { + //Serial.println("Error Occurred. Error #: " + String(Update.getError())); + } + } else { + // not enough space to begin OTA + // Understand the partitions and + // space availability + //Serial.println("Not enough space to begin OTA"); + client.flush(); + } + } else { + //Serial.println("There was no content in the response"); + client.flush(); + } + } + // Back to some normal state + enterMode(0 /*ST_DECODER*/); + Serial.println("AAAA"); +} + +int fetchHTTPheader(int *validType) { + int contentLength = -1; + unsigned long timeout = millis(); + while (client.available() == 0) { + if (millis() - timeout > 5000) { + Serial.println("Client Timeout !"); + client.stop(); + return -1; + } + } + // Once the response is available, check stuff + + /* + Response Structure + HTTP/1.1 200 OK + x-amz-id-2: NVKxnU1aIQMmpGKhSwpCBh8y2JPbak18QLIfE+OiUDOos+7UftZKjtCFqrwsGOZRN5Zee0jpTd0= + x-amz-request-id: 2D56B47560B764EC + Date: Wed, 14 Jun 2017 03:33:59 GMT + Last-Modified: Fri, 02 Jun 2017 14:50:11 GMT + ETag: "d2afebbaaebc38cd669ce36727152af9" + Accept-Ranges: bytes + Content-Type: application/octet-stream + Content-Length: 357280 + Server: AmazonS3 + + {{BIN FILE CONTENTS}} + + */ + while (client.available()) { + Serial.println("While()"); + // read line till \n + String line = client.readStringUntil('\n'); + // remove space, to check if the line is end of headers + line.trim(); + + // if the the line is empty, + // this is end of headers + // break the while and feed the + // remaining `client` to the + // Update.writeStream(); + 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."); + return -1; + } + } + + // 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") { + if (validType) *validType = 1; + } + } + } + Serial.println("END - END"); + Serial.println(contentLength); + return contentLength; +} +#endif // 0 \ No newline at end of file diff --git a/RX_FSK/src/ota.h b/RX_FSK/src/ota.h new file mode 100644 index 0000000..863d56c --- /dev/null +++ b/RX_FSK/src/ota.h @@ -0,0 +1,7 @@ +#ifndef _OTA_H +#define _OTA_H + +const char *ota_create_form(boolean run, char *message); +const char *ota_handle_post(AsyncWebServerRequest * request); + +#endif // _OTA_H \ No newline at end of file diff --git a/git_version.py b/git_version.py index 72a9dfb..a4f5610 100644 --- a/git_version.py +++ b/git_version.py @@ -10,13 +10,20 @@ result = subprocess.run("git describe --abbrev=6 --dirty --always --tags", git_versio = result.stdout print("GIT Version: ", git_versio) -f = open("RX_FSK/git_version.h", "w") +fh = open("RX_FSK/git_version.h", "w") -f.write("const char *version_name = \"rdzTTGOsonde\";\n") -f.write("const char *version_id = \"Multi_ch-") -f.write(git_versio[:-1]) -f.write("\";\n") -f.write("const int SPIFFS_MAJOR=2;\n") -f.write("const int SPIFFS_MINOR=17;\n") +fh.write("#define SPIFFS_MAJOR 2\n") +fh.write("#define SPIFFS_MINOR 17\n\n") +fh.write("extern const char *version_id;\n") +fh.write("extern const char *version_name;\n") +fh.close() -f.close() +fc = open("RX_FSK/git_version.c", "w") + +fc.write("#include \"git_version.h\"\n") +fc.write("\n") +fc.write("const char *version_name = \"rdzTTGOsonde\";\n") +fc.write("const char *version_id = \"Multi_ch-") +fc.write(git_versio[:-1]) +fc.write("\";\n") +fc.close()