Differences

This shows you the differences between two versions of the page.

Link to this comparison view

iothings:laboratoare:2025_code:lab10_3 [2025/12/06 20:49]
dan.tudose
iothings:laboratoare:2025_code:lab10_3 [2025/12/06 21:23] (current)
dan.tudose
Line 4: Line 4:
 #include <​AsyncTCP.h>​ #include <​AsyncTCP.h>​
 #include <​ESPAsyncWebServer.h>​ #include <​ESPAsyncWebServer.h>​
-#include <​ElegantOTA.h>​ 
 #include <​Adafruit_NeoPixel.h>​ #include <​Adafruit_NeoPixel.h>​
 #include <​HTTPClient.h>​ #include <​HTTPClient.h>​
 #include <​Update.h>​ #include <​Update.h>​
 #include <​ArduinoJson.h>​ #include <​ArduinoJson.h>​
 +#include <​Preferences.h>​
 #include "​mbedtls/​sha256.h"​ #include "​mbedtls/​sha256.h"​
  
 const char* ssid     = "​UPB-Guest";​ const char* ssid     = "​UPB-Guest";​
 const char* password = "";​ const char* password = "";​
-// OTA basic auth (recommended) 
-const char* ota_user = "​admin";​ 
-const char* ota_pass = "​change-me";​ 
  
 // Pull-OTA settings (host these on your local machine) // Pull-OTA settings (host these on your local machine)
-const char* fw_version ​      = "1.0.1";+const char* fw_version ​      = "1.0.0";
 const char* ota_manifest_url = "​http://​192.168.0.104:​8000/​manifest.json";​ // adjust to your LAN host const char* ota_manifest_url = "​http://​192.168.0.104:​8000/​manifest.json";​ // adjust to your LAN host
 const char* ota_telemetry_url = "​http://​192.168.0.104:​8000/​telemetry"; ​    // optional: logs update results const char* ota_telemetry_url = "​http://​192.168.0.104:​8000/​telemetry"; ​    // optional: logs update results
-constexpr unsigned long OTA_CHECK_INTERVAL_MS = 10UL * 1000UL;+constexpr unsigned long OTA_CHECK_INTERVAL_MS = 30UL * 1000UL;
 unsigned long lastOtaCheckMs = 0; unsigned long lastOtaCheckMs = 0;
  
Line 29: Line 26:
  
 AsyncWebServer server(80); AsyncWebServer server(80);
 +Preferences prefs;
 +
 +String getPrefStringSafe(const char* key) {
 +  if (!prefs.isKey(key)) {
 +    return "";​
 +  }
 +  return prefs.getString(key,​ ""​);​
 +}
  
 struct OtaManifest { struct OtaManifest {
Line 35: Line 40:
   String sha256;   String sha256;
 }; };
 +
 +struct TelemetryState {
 +  String lastResult; ​  // success | failed | noop | ""​
 +  String lastError; ​   // sha_mismatch | manifest_fail | ... | ""​
 +  String fromVersion;​
 +  String toVersion;
 +  String message; ​     // human-readable summary
 +} telemetry;
 +
 +void loadTelemetryFromNvs() {
 +  prefs.begin("​ota",​ false);
 +  telemetry.lastResult = getPrefStringSafe("​result"​);​
 +  telemetry.lastError = getPrefStringSafe("​error"​);​
 +  telemetry.fromVersion = getPrefStringSafe("​from"​);​
 +  telemetry.toVersion = getPrefStringSafe("​to"​);​
 +  telemetry.message = getPrefStringSafe("​msg"​);​
 +}
 +
 +void storeTelemetryToNvs() {
 +  prefs.putString("​result",​ telemetry.lastResult);​
 +  prefs.putString("​error",​ telemetry.lastError);​
 +  prefs.putString("​from",​ telemetry.fromVersion);​
 +  prefs.putString("​to",​ telemetry.toVersion);​
 +  prefs.putString("​msg",​ telemetry.message);​
 +}
 +
 +void recordTelemetry(const char* result, const char* error, const String& fromVer, const String& toVer, const String& msg) {
 +  telemetry.lastResult = result ? result : "";​
 +  telemetry.lastError = error ? error : "";​
 +  telemetry.fromVersion = fromVer;
 +  telemetry.toVersion = toVer;
 +  telemetry.message = msg;
 +  storeTelemetryToNvs();​
 +}
  
 String toHex(const uint8_t* data, size_t len) { String toHex(const uint8_t* data, size_t len) {
Line 84: Line 123:
   if (!http.begin(ota_manifest_url)) {   if (!http.begin(ota_manifest_url)) {
     sendTelemetry("​manifest_begin_fail",​ "​http.begin failed"​);​     sendTelemetry("​manifest_begin_fail",​ "​http.begin failed"​);​
 +    recordTelemetry("​failed",​ "​manifest_begin_fail",​ fw_version, "",​ "​Manifest fetch failed"​);​
     return false;     return false;
   }   }
Line 90: Line 130:
   if (code != HTTP_CODE_OK) {   if (code != HTTP_CODE_OK) {
     sendTelemetry("​manifest_http_fail",​ "code " + String(code));​     sendTelemetry("​manifest_http_fail",​ "code " + String(code));​
 +    recordTelemetry("​failed",​ "​manifest_http_fail",​ fw_version, "",​ "HTTP " + String(code));​
     http.end();     http.end();
     return false;     return false;
   }   }
  
-  ​JsonDocument ​doc;+  ​StaticJsonDocument<​512> ​doc;
   DeserializationError err = deserializeJson(doc,​ http.getString());​   DeserializationError err = deserializeJson(doc,​ http.getString());​
   http.end();   http.end();
   if (err) {   if (err) {
     sendTelemetry("​manifest_parse_fail",​ err.f_str());​     sendTelemetry("​manifest_parse_fail",​ err.f_str());​
 +    recordTelemetry("​failed",​ "​manifest_parse_fail",​ fw_version, "",​ "​Manifest parse failed"​);​
     return false;     return false;
   }   }
Line 108: Line 150:
   if (manifest.version.isEmpty() || manifest.url.isEmpty() || manifest.sha256.isEmpty()) {   if (manifest.version.isEmpty() || manifest.url.isEmpty() || manifest.sha256.isEmpty()) {
     sendTelemetry("​manifest_missing",​ "​missing field"​);​     sendTelemetry("​manifest_missing",​ "​missing field"​);​
 +    recordTelemetry("​failed",​ "​manifest_missing",​ fw_version, "",​ "​Manifest missing field"​);​
     return false;     return false;
   }   }
Line 114: Line 157:
 } }
  
-bool downloadAndUpdate(const String& firmwareUrl,​ const String& expectedSha256) {+bool downloadAndUpdate(const String& firmwareUrl,​ const String& expectedSha256, const String& targetVersion) {
   WiFiClient client;   WiFiClient client;
   HTTPClient http;   HTTPClient http;
   if (!http.begin(client,​ firmwareUrl)) {   if (!http.begin(client,​ firmwareUrl)) {
     sendTelemetry("​fw_begin_fail",​ "​http.begin"​);​     sendTelemetry("​fw_begin_fail",​ "​http.begin"​);​
 +    recordTelemetry("​failed",​ "​fw_begin_fail",​ fw_version, targetVersion,​ "​Firmware HTTP begin failed"​);​
     return false;     return false;
   }   }
Line 125: Line 169:
   if (code != HTTP_CODE_OK) {   if (code != HTTP_CODE_OK) {
     sendTelemetry("​fw_http_fail",​ "code " + String(code));​     sendTelemetry("​fw_http_fail",​ "code " + String(code));​
 +    recordTelemetry("​failed",​ "​fw_http_fail",​ fw_version, targetVersion,​ "HTTP " + String(code));​
     http.end();     http.end();
     return false;     return false;
Line 132: Line 177:
   if (contentLength <= 0 && contentLength != -1) {   if (contentLength <= 0 && contentLength != -1) {
     sendTelemetry("​fw_size_fail",​ "​invalid length"​);​     sendTelemetry("​fw_size_fail",​ "​invalid length"​);​
 +    recordTelemetry("​failed",​ "​fw_size_fail",​ fw_version, targetVersion,​ "​Invalid content length"​);​
     http.end();     http.end();
     return false;     return false;
Line 138: Line 184:
   if (!Update.begin(contentLength > 0 ? contentLength : UPDATE_SIZE_UNKNOWN)) {   if (!Update.begin(contentLength > 0 ? contentLength : UPDATE_SIZE_UNKNOWN)) {
     sendTelemetry("​update_begin_fail",​ Update.errorString());​     sendTelemetry("​update_begin_fail",​ Update.errorString());​
 +    recordTelemetry("​failed",​ "​update_begin_fail",​ fw_version, targetVersion,​ "​Update.begin failed"​);​
     http.end();     http.end();
     return false;     return false;
Line 159: Line 206:
       if (Update.write(buff,​ read) != read) {       if (Update.write(buff,​ read) != read) {
         sendTelemetry("​update_write_fail",​ Update.errorString());​         sendTelemetry("​update_write_fail",​ Update.errorString());​
 +        recordTelemetry("​failed",​ "​update_write_fail",​ fw_version, targetVersion,​ "Write failed"​);​
         Update.abort();​         Update.abort();​
         mbedtls_sha256_free(&​shaCtx);​         mbedtls_sha256_free(&​shaCtx);​
Line 182: Line 230:
   if (!computedHash.equalsIgnoreCase(expectedSha256)) {   if (!computedHash.equalsIgnoreCase(expectedSha256)) {
     sendTelemetry("​hash_mismatch",​ computedHash);​     sendTelemetry("​hash_mismatch",​ computedHash);​
 +    recordTelemetry("​failed",​ "​sha_mismatch",​ fw_version, targetVersion,​ "SHA mismatch"​);​
     Update.abort();​     Update.abort();​
     return false;     return false;
Line 188: Line 237:
   if (!Update.end(true)) { // true = even if size is smaller   if (!Update.end(true)) { // true = even if size is smaller
     sendTelemetry("​update_end_fail",​ Update.errorString());​     sendTelemetry("​update_end_fail",​ Update.errorString());​
 +    recordTelemetry("​failed",​ "​update_end_fail",​ fw_version, targetVersion,​ "​Update end failed"​);​
     return false;     return false;
   }   }
Line 193: Line 243:
   if (!Update.isFinished()) {   if (!Update.isFinished()) {
     sendTelemetry("​update_not_finished",​ "not finished"​);​     sendTelemetry("​update_not_finished",​ "not finished"​);​
 +    recordTelemetry("​failed",​ "​update_not_finished",​ fw_version, targetVersion,​ "​Update not finished"​);​
     return false;     return false;
   }   }
  
   sendTelemetry("​update_ok",​ "​restarting"​);​   sendTelemetry("​update_ok",​ "​restarting"​);​
 +   ​recordTelemetry("​success",​ "",​ fw_version, targetVersion,​ "​Update applied; restarting"​);​
   delay(100);   delay(100);
   ESP.restart();​   ESP.restart();​
Line 209: Line 261:
  
   if (manifest.version == fw_version) {   if (manifest.version == fw_version) {
 +    recordTelemetry("​noop",​ "",​ fw_version, manifest.version,​ "No update needed."​);​
     return; // already up to date     return; // already up to date
   }   }
  
   sendTelemetry("​update_found",​ "​v"​ + manifest.version);​   sendTelemetry("​update_found",​ "​v"​ + manifest.version);​
-  downloadAndUpdate(manifest.url,​ manifest.sha256);​+  downloadAndUpdate(manifest.url,​ manifest.sha256, manifest.version);
 } }
  
Line 219: Line 272:
   Serial.begin(115200);​   Serial.begin(115200);​
   delay(200);   delay(200);
 +
 +  loadTelemetryFromNvs();​
  
   WiFi.mode(WIFI_STA);​   WiFi.mode(WIFI_STA);​
Line 237: Line 292:
     request->​send(200,​ "​text/​plain",​     request->​send(200,​ "​text/​plain",​
       "​ESP32-C6 Sparrow OTA ready.\n"​       "​ESP32-C6 Sparrow OTA ready.\n"​
-      "Go to /update to upload new firmware.");+      ​"​Updates pull from manifest server.\n"​ 
 +      ​"Go to /status for telemetry."); 
 +  }); 
 + 
 +  server.on("/​status",​ HTTP_GET, [](AsyncWebServerRequest *request) { 
 +    String payload; 
 +    payload.reserve(256);​ 
 +    payload += "​{";​ 
 +    payload += "​\"​device_version\":​\""​ + String(fw_version) + "​\",";​ 
 +    payload += "​\"​last_result\":​\""​ + telemetry.lastResult + "​\",";​ 
 +    payload += "​\"​last_error\":​\""​ + telemetry.lastError + "​\",";​ 
 +    payload += "​\"​from_version\":​\""​ + telemetry.fromVersion + "​\",";​ 
 +    payload += "​\"​to_version\":​\""​ + telemetry.toVersion + "​\",";​ 
 +    payload += "​\"​message\":​\""​ + telemetry.message + "​\"";​ 
 +    payload += "​}";​ 
 +    request->​send(200,​ "​application/​json",​ payload);
   });   });
  
Line 243: Line 313:
   statusPixel.setBrightness(40);​   statusPixel.setBrightness(40);​
   statusPixel.show();​ // initialize to off   statusPixel.show();​ // initialize to off
- 
-  // Start ElegantOTA (async mode with ESPAsyncWebServer) 
-  ElegantOTA.begin(&​server,​ ota_user, ota_pass); 
  
   server.begin();​   server.begin();​
   Serial.println("​HTTP server started."​);​   Serial.println("​HTTP server started."​);​
-  Serial.println("​Open http://<​device-ip>/​update");+  Serial.println("​Open http://<​device-ip>/​status");
  
   // First pull-based OTA check after boot   // First pull-based OTA check after boot
Line 256: Line 323:
  
 void loop() { void loop() {
-  ElegantOTA.loop();​ 
- 
   static bool ledOn = false;   static bool ledOn = false;
   static unsigned long lastToggle = 0;   static unsigned long lastToggle = 0;
Line 275: Line 340:
     checkForOtaUpdate();​     checkForOtaUpdate();​
   }   }
 +
 +  // Yield to keep the watchdog happy
 +  delay(1);
 } }
  
 </​code>​ </​code>​
iothings/laboratoare/2025_code/lab10_3.1765046999.txt.gz · Last modified: 2025/12/06 20:49 by dan.tudose
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0