From b4339c71eec83a0860cf0c627e80ce7915f385df Mon Sep 17 00:00:00 2001 From: Mau Abata Date: Tue, 14 Sep 2021 18:18:11 -0500 Subject: [PATCH] Self updating. --- include/update_helper.h | 50 +++++++++ src/tscode_manager.cpp | 53 ++++++++++ src/update_helper.cpp | 229 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 332 insertions(+) create mode 100644 include/update_helper.h create mode 100644 src/update_helper.cpp diff --git a/include/update_helper.h b/include/update_helper.h new file mode 100644 index 0000000..aab9ecf --- /dev/null +++ b/include/update_helper.h @@ -0,0 +1,50 @@ +#ifndef __UPDATEHELPER_H +#define __UPDATEHELPER_H + +// This is taken from the Edge-o-Matic, and eventually should be replaced with proper esp-idf framework methods. + +#include +#include +#include + +#define REMOTE_UPDATE_URL "http://us-central1-maustec-io.cloudfunctions.net/m1k-update-data" +#define UPDATE_FILENAME "/update.bin" + +enum UpdateSource { + NoUpdate, + UpdateFromSD, + UpdateFromServer +}; + +namespace UpdateHelper { + // perform the actual update from a given stream + bool performUpdate(Stream &updateSource, size_t updateSize); + + // check given FS for valid update.bin and perform update if available + void updateFromFS(fs::FS &fs); + void updateFromWeb(); + + /** + * Compares two versions, returning a value based on their SemVer equivalence: + * -1 : a < b + * 0 : a == b + * 1 : a > b + * + * Any prefix 'v' will be ignored. + * @param a SemVer string + * @param b SemVer string + * @return -1, 0, 1 + */ + int compareVersion(const char *a, const char *b); + + String checkWebLatestVersion(); + + // Update Available + UpdateSource checkForUpdates(); + + // Flags to check if there's an update pending. Set by checkForUpdates(); + extern bool pendingLocalUpdate; + extern bool pendingWebUpdate; +} + +#endif \ No newline at end of file diff --git a/src/tscode_manager.cpp b/src/tscode_manager.cpp index 085d246..fea8a6d 100644 --- a/src/tscode_manager.cpp +++ b/src/tscode_manager.cpp @@ -3,6 +3,11 @@ #include "pressure_manager.hpp" #include "version.h" #include "m1k-hal.hpp" +#include +#include "update_helper.h" + +static char WIFI_SSID[32] = ""; +static char WIFI_PASSWORD[64] = ""; tscode_command_response_t tscode_callback(tscode_command_t* cmd, char* response, size_t resp_len); @@ -84,6 +89,54 @@ tscode_command_response_t tscode_callback(tscode_command_t* cmd, char* response, m1k_hal_set_milker_speed(0x00); break; + // Set WiFi SSID + case __S(170): { + if (cmd->str != NULL && cmd->str[0] != '\0') { + printf("WIFI SET SSID: %s\n", cmd->str); + strncpy(WIFI_SSID, cmd->str, 31); + } else { + return TSCODE_RESPONSE_FAULT; + } + break; + } + + // Set WiFi Password + case __S(171): { + if (cmd->str != NULL && cmd->str[0] != '\0') { + printf("WIFI SET PASSWORD: %s\n", cmd->str); + strncpy(WIFI_PASSWORD, cmd->str, 63); + } else { + return TSCODE_RESPONSE_FAULT; + } + break; + } + + // Connect to WiFi + case __S(172): { + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + printf("connecting to wifi"); + + int attempts = 0; + while (WiFi.status() != WL_CONNECTED) { + if (attempts++ > 100) { + printf("failed\n"); + return TSCODE_RESPONSE_FAULT; + } + printf("."); + vTaskDelay(100 * portTICK_PERIOD_MS); + } + + printf("ok\n"); + printf("IP: %s\n", WiFi.localIP().toString().c_str()); + break; + } + + // Update from Web + case __S(173): { + UpdateHelper::updateFromWeb(); + break; + } + default: return TSCODE_RESPONSE_NO_CAPABILITY; } diff --git a/src/update_helper.cpp b/src/update_helper.cpp new file mode 100644 index 0000000..54f88bf --- /dev/null +++ b/src/update_helper.cpp @@ -0,0 +1,229 @@ +#include "update_helper.h" +#include "VERSION.h" + +#include +#include + +#define UPDATE_BUFFER_SIZE (1024 * 1) + +namespace UpdateHelper { + bool pendingLocalUpdate = false; + bool pendingWebUpdate = false; + + size_t waitUntilAvailable(Stream *stream, long timeout_ms = 10000) { + long start_ms = millis(); + size_t available = 0; + while(available == 0 && millis() - start_ms < timeout_ms) { + available = stream->available(); + } + return available; + } + + // perform the actual update from a given stream + bool performUpdate(Stream &updateSource, size_t updateSize) { + printf("Starting update...\n"); + + if (Update.begin(updateSize)) { + printf("Update began.\n"); + size_t written = 0; + size_t availableBytes = 0; + byte buffer[UPDATE_BUFFER_SIZE]; + + while ((availableBytes = updateSource.available()) > 0) { + size_t read = updateSource.readBytes(buffer, UPDATE_BUFFER_SIZE); + written += Update.write(buffer, read); + log_d("Read %d bytes, Written %d/%d bytes. (Heap Free: %d bytes)\n", read, written, updateSize, xPortGetFreeHeapSize()); + + if (written < updateSize) { + waitUntilAvailable(&updateSource); + } + } + + log_w("Stream ended. Last available: %d\n", availableBytes); + + if (written == updateSize) { + printf("Written : %d successfully\n", written); + } else { + printf("Written only : %d/%d. Retry?\n", written, updateSize); + } + + if (Update.end()) { + printf("OTA done!\n"); + + if (Update.isFinished()) { + printf("Update successfully completed. Rebooting.\n"); + return true; + + } else { + printf("Update not finished? Something went wrong!\n"); + } + } else { + printf("Error Occurred. Error #: %s\n", Update.getError()); + } + + } else { + printf("Not enough space to begin OTA\n"); + } + + return false; + } + + // check given FS for valid update.bin and perform update if available + void updateFromFS(fs::FS &fs) { + File updateBin = fs.open("/update.bin"); + bool success = false; + + if (updateBin) { + if (updateBin.isDirectory()) { + printf("Error, update.bin is not a file\n"); + updateBin.close(); + return; + } + + size_t updateSize = updateBin.size(); + + if (updateSize > 0) { + printf("Try to start update\n"); + success = performUpdate(updateBin, updateSize); + } else { + printf("Error, file is empty\n"); + } + + updateBin.close(); + + if (success) { + // whe finished remove the binary from sd card to indicate end of the process + // fs.mv("/update.bin", "/update-" VERSION ".bin"); + delay(2000); + ESP.restart(); + } + } else { + printf("Could not load update.bin from sd root\n"); + } + } + + UpdateSource checkForUpdates() { + UpdateSource source = NoUpdate; + + // Check Network: + if (WiFi.isConnected()) { + String web = checkWebLatestVersion(); + printf("Web version was: %s", web); + + if (compareVersion(web.c_str(), VERSION) > 0) { + printf("Web Update available!\n"); + pendingWebUpdate = true; + source = UpdateFromServer; + } + } + + return source; + } + + String checkWebLatestVersion() { + String version; + HTTPClient http; + http.begin(REMOTE_UPDATE_URL "/version.txt"); + int httpCode = http.GET(); + + if (httpCode > 0) { + if (httpCode == HTTP_CODE_OK) { + version = http.getString(); + } else { + printf("GET Error: %d\n", httpCode); + } + } else { + printf("GET Error: %s\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + return version; + } + + String followRedirects(String url, int &httpCode) { + String location = url; + int redirectCount = 0; + + while (redirectCount < 10) { + HTTPClient http; + http.begin(location.c_str()); + + // We want to keep the "Location" header. + const char *collectHeaders[] = { "Location" }; + http.collectHeaders(collectHeaders, 1); + + httpCode = http.GET(); + + if (httpCode > 0) { + if (http.hasHeader("Location")) { + location = http.header("Location"); + printf("Redirect: %s\n", location); + } else if (httpCode == HTTP_CODE_OK) { + break; + } else { + printf("GET Error from File: %d\n", httpCode); + break; + } + } else { + printf("GET Error: %s\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + redirectCount++; + } + + return location; + } + + void updateFromWeb() { + HTTPClient fileHttp; + int httpCode = 0; + String location = followRedirects(REMOTE_UPDATE_URL "/update.bin", httpCode); + + if (httpCode == HTTP_CODE_OK) { + fileHttp.begin(location.c_str()); + httpCode = fileHttp.GET(); + + if (httpCode > 0) { + if (httpCode == HTTP_CODE_OK) { + int len = fileHttp.getSize(); + printf("Got size: %d bytes\n", len); + Stream *stream = fileHttp.getStreamPtr(); + size_t available = waitUntilAvailable(stream); + + if (available > 0) { + bool success = performUpdate(*stream, len); + if (success) { + delay(2000); + ESP.restart(); + } + } else { + printf("%d bytes sent from server.\n", available); + } + } else { + printf("GET Error from File: %d\n", httpCode); + } + } else { + printf("GET Error: %s\n", fileHttp.errorToString(httpCode).c_str()); + } + + fileHttp.end(); + } + } + + int compareVersion(const char *a, const char *b) { + int va[3] = { 0, 0, 0 }; + int vb[3] = { 0, 0, 0 }; + + sscanf(a[0] == 'v' ? a + 1 : a, "%d.%d.%d", &va[0], &va[1], &va[2]); + sscanf(b[0] == 'v' ? b + 1 : b, "%d.%d.%d", &vb[0], &vb[1], &vb[2]); + + for (int i = 0; i <= 2; i++) { + if (va[i] != vb[i]) { + return va[i] > vb[i] ? 1 : -1; + } + } + + return 0; + } +} \ No newline at end of file