This is an old revision of the document!


main.cpp
#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ElegantOTA.h>
#include <Adafruit_NeoPixel.h>
#include <HTTPClient.h>
#include <Update.h>
#include <ArduinoJson.h>
#include "mbedtls/sha256.h"
 
const char* ssid     = "UPB-Guest";
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)
const char* fw_version       = "1.0.1";
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
constexpr unsigned long OTA_CHECK_INTERVAL_MS = 10UL * 1000UL;
unsigned long lastOtaCheckMs = 0;
 
constexpr uint8_t NEOPIXEL_PIN = 3;
constexpr uint16_t NEOPIXEL_COUNT = 1;
Adafruit_NeoPixel statusPixel(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
 
AsyncWebServer server(80);
 
struct OtaManifest {
  String version;
  String url;
  String sha256;
};
 
String toHex(const uint8_t* data, size_t len) {
  static const char* hex = "0123456789abcdef";
  String out;
  out.reserve(len * 2);
  for (size_t i = 0; i < len; ++i) {
    out += hex[(data[i] >> 4) & 0x0F];
    out += hex[data[i] & 0x0F];
  }
  return out;
}
 
String urlEncode(const String& input) {
  String encoded;
  for (size_t i = 0; i < input.length(); i++) {
    char c = input[i];
    if (isalnum(static_cast<unsigned char>(c)) || c == '-' || c == '_' || c == '.' || c == '~') {
      encoded += c;
    } else if (c == ' ') {
      encoded += "%20";
    } else {
      char buf[4];
      snprintf(buf, sizeof(buf), "%%%02X", static_cast<unsigned char>(c));
      encoded += buf;
    }
  }
  return encoded;
}
 
void sendTelemetry(const char* status, const String& detail) {
  if (ota_telemetry_url == nullptr || strlen(ota_telemetry_url) == 0) {
    return;
  }
 
  HTTPClient http;
  String url = String(ota_telemetry_url) +
               "?status=" + status +
               "&version=" + fw_version +
               "&msg=" + urlEncode(detail.substring(0, 60)); // keep it short
  if (http.begin(url)) {
    http.GET(); // best-effort; ignore response
    http.end();
  }
}
 
bool fetchManifest(OtaManifest& manifest) {
  HTTPClient http;
  if (!http.begin(ota_manifest_url)) {
    sendTelemetry("manifest_begin_fail", "http.begin failed");
    return false;
  }
 
  const int code = http.GET();
  if (code != HTTP_CODE_OK) {
    sendTelemetry("manifest_http_fail", "code " + String(code));
    http.end();
    return false;
  }
 
  JsonDocument doc;
  DeserializationError err = deserializeJson(doc, http.getString());
  http.end();
  if (err) {
    sendTelemetry("manifest_parse_fail", err.f_str());
    return false;
  }
 
  manifest.version = doc["version"] | "";
  manifest.url = doc["url"] | "";
  manifest.sha256 = doc["sha256"] | "";
 
  if (manifest.version.isEmpty() || manifest.url.isEmpty() || manifest.sha256.isEmpty()) {
    sendTelemetry("manifest_missing", "missing field");
    return false;
  }
 
  return true;
}
 
bool downloadAndUpdate(const String& firmwareUrl, const String& expectedSha256) {
  WiFiClient client;
  HTTPClient http;
  if (!http.begin(client, firmwareUrl)) {
    sendTelemetry("fw_begin_fail", "http.begin");
    return false;
  }
 
  const int code = http.GET();
  if (code != HTTP_CODE_OK) {
    sendTelemetry("fw_http_fail", "code " + String(code));
    http.end();
    return false;
  }
 
  const int contentLength = http.getSize();
  if (contentLength <= 0 && contentLength != -1) {
    sendTelemetry("fw_size_fail", "invalid length");
    http.end();
    return false;
  }
 
  if (!Update.begin(contentLength > 0 ? contentLength : UPDATE_SIZE_UNKNOWN)) {
    sendTelemetry("update_begin_fail", Update.errorString());
    http.end();
    return false;
  }
 
  mbedtls_sha256_context shaCtx;
  mbedtls_sha256_init(&shaCtx);
  mbedtls_sha256_starts(&shaCtx, 0); // 0 = SHA-256
 
  WiFiClient* stream = http.getStreamPtr();
  uint8_t buff[1024];
  size_t written = 0;
  while (http.connected()) {
    const size_t avail = stream->available();
    if (avail) {
      const size_t toRead = avail > sizeof(buff) ? sizeof(buff) : avail;
      const size_t read = stream->readBytes(buff, toRead);
      if (read == 0) {
        break;
      }
      if (Update.write(buff, read) != read) {
        sendTelemetry("update_write_fail", Update.errorString());
        Update.abort();
        mbedtls_sha256_free(&shaCtx);
        http.end();
        return false;
      }
      mbedtls_sha256_update(&shaCtx, buff, read);
      written += read;
    } else {
      delay(1);
      if (contentLength > 0 && written >= static_cast<size_t>(contentLength)) {
        break;
      }
    }
  }
 
  uint8_t hashResult[32];
  mbedtls_sha256_finish(&shaCtx, hashResult);
  mbedtls_sha256_free(&shaCtx);
  http.end();
 
  const String computedHash = toHex(hashResult, sizeof(hashResult));
  if (!computedHash.equalsIgnoreCase(expectedSha256)) {
    sendTelemetry("hash_mismatch", computedHash);
    Update.abort();
    return false;
  }
 
  if (!Update.end(true)) { // true = even if size is smaller
    sendTelemetry("update_end_fail", Update.errorString());
    return false;
  }
 
  if (!Update.isFinished()) {
    sendTelemetry("update_not_finished", "not finished");
    return false;
  }
 
  sendTelemetry("update_ok", "restarting");
  delay(100);
  ESP.restart();
  return true; // not reached
}
 
void checkForOtaUpdate() {
  OtaManifest manifest;
  if (!fetchManifest(manifest)) {
    return;
  }
 
  if (manifest.version == fw_version) {
    return; // already up to date
  }
 
  sendTelemetry("update_found", "v" + manifest.version);
  downloadAndUpdate(manifest.url, manifest.sha256);
}
 
void setup() {
  Serial.begin(115200);
  delay(200);
 
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
 
  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(300);
    Serial.print(".");
  }
  Serial.println();
 
  Serial.print("Connected. IP: ");
  Serial.println(WiFi.localIP());
 
  // Simple landing page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/plain",
      "ESP32-C6 Sparrow OTA ready.\n"
      "Go to /update to upload new firmware.");
  });
 
  statusPixel.begin();
  statusPixel.setBrightness(40);
  statusPixel.show(); // initialize to off
 
  // Start ElegantOTA (async mode with ESPAsyncWebServer)
  ElegantOTA.begin(&server, ota_user, ota_pass);
 
  server.begin();
  Serial.println("HTTP server started.");
  Serial.println("Open http://<device-ip>/update");
 
  // First pull-based OTA check after boot
  checkForOtaUpdate();
}
 
void loop() {
  ElegantOTA.loop();
 
  static bool ledOn = false;
  static unsigned long lastToggle = 0;
  const unsigned long blinkIntervalMs = 500;
 
  const unsigned long now = millis();
  if (now - lastToggle >= blinkIntervalMs) {
    lastToggle = now;
    ledOn = !ledOn;
    const uint32_t color = ledOn ? statusPixel.Color(255, 0, 0) : 0;
    statusPixel.setPixelColor(0, color);
    statusPixel.show();
  }
 
  if (WiFi.isConnected() && now - lastOtaCheckMs >= OTA_CHECK_INTERVAL_MS) {
    lastOtaCheckMs = now;
    checkForOtaUpdate();
  }
}
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