OTA and git version

This commit is contained in:
oh3bsg 2023-11-14 21:30:45 +02:00
parent 5a53d000e1
commit 0958f62ea7
7 changed files with 387 additions and 15 deletions

1
.gitignore vendored
View File

@ -34,3 +34,4 @@
.pio
.vscode
/RX_FSK/git_version.h
/RX_FSK/git_version.c

View File

@ -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() {

7
RX_FSK/common.h Normal file
View File

@ -0,0 +1,7 @@
extern const char *updatePrefixM;
extern const char *updatePrefixD;
extern const char *updatePrefix;
extern WiFiClient client;
void enterMode(int mode);

View File

@ -17,6 +17,7 @@
<a href="#ctrl" onclick="selTab(event,'Control')" class="tablinks">Control</a>
<a href="#config" onclick="selTab(event,'Config')" class="tablinks">Config</a>
<a href="#wifi" onclick="selTab(event,'WiFi')" class="tablinks">WiFi</a>
<a href="#OTA" onclick="selTab(event,'OTA')" class="tablinks">OTA</a>
<a href="#about" onclick="selTab(event,'About')" class="tablinks">About</a>
<a href="javascript:void(0);" class="icon" onclick="myFunction()">
<span class="hamburger"></span>
@ -48,14 +49,19 @@
<iframe class="tci" src="" ></iframe>
</div>
<div id="OTA" class="tabcontent" data-src="ota.html">
<iframe class="tci" src="" ></iframe>
</div>
<div id="About" class="tabcontent">
<div class="tci">
%VERSION_NAME%<br>
Copyright &copy; 2019-2022 by Hansi Reiser, DL9RDZ<br>
(version %VERSION_ID%)<br><br>
<!--
<a href="/upd.html">Check for update (requires TTGO internet connection via WiFi)</a><br><br>
-->
with contributions by Vigor and Xavier (M20 support),
<a href="https://github.com/LukePrior">Luke Prior</a> and <a href="https://github.com/oh3bsg">OH3BSG</a> (SondeHub support),
<a href="https://www.dl2mf.de/" target="_blank">Meinhard Guenther, DL2MF</a>,

330
RX_FSK/src/ota.cpp Normal file
View File

@ -0,0 +1,330 @@
#include <arduino.h>
#include <ESPAsyncWebServer.h>
#include "ota.h"
#include "git_version.h"
#include "common.h"
#include "Sonde.h"
#include "Display.h"
//#include <WiFi.h>
#include <Update.h>
#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, "<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"></head><body><form action=\"ota.html\" method=\"post\">");
if (run) {
strcat(ptr, "<p>Doing update, wait until reboot</p>");
} else {
sprintf(ptr + strlen(ptr), "<p>Currently installed: %s-%c%d</p>\n", version_id, SPIFFS_MAJOR + 'A' - 1, SPIFFS_MINOR);
strcat(ptr, "<p>Available release:: <iframe src=\"http://rdzsonde.mooo.com/master/update-info.html\" style=\"height:40px;width:400px\"></iframe><br>"
"Available devel: <iframe src=\"http://rdzsonde.mooo.com/devel/update-info.html\" style=\"height:40px;width:400px\"></iframe></p>");
strcat(ptr, "<input type=\"submit\" name=\"master\" value=\"Master-Update\"></input><br><input type=\"submit\" name=\"devel\" value=\"Devel-Update\">");
strcat(ptr, "<br><p>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</p>");
}
strcat(ptr, "</form></body></html>");
//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

7
RX_FSK/src/ota.h Normal file
View File

@ -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

View File

@ -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()