Skip to content

Commit 84e46b9

Browse files
Merge pull request #17 from Valar-Systems/feat/ota-manual-check
feat(ota): manual update check + robust release parse, friendlier status (1.0.7)
2 parents 86e9121 + ce45e6c commit 84e46b9

4 files changed

Lines changed: 59 additions & 15 deletions

File tree

‎version/r1.1/firmware/platformio/esp32_firmware_platformio/platformio.ini‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ build_flags =
6363
-DARDUINO_USB_CDC_ON_BOOT=1
6464
; ESP32 firmware version: reported in the heartbeat and used by the OTA
6565
; flow to decide whether this board needs updating. Bump on every release.
66-
'-DFW_VERSION="1.0.6"'
66+
'-DFW_VERSION="1.0.7"'
6767
; Bootstrap token for register_device/capture transport auth. Sourced from
6868
; the environment so the secret is never committed: set MSC_BOOTSTRAP_TOKEN
6969
; before building (PowerShell: $env:MSC_BOOTSTRAP_TOKEN="..."; bash:

‎version/r1.1/firmware/platformio/esp32_firmware_platformio/src/config_portal.h‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ button.warn{background:#3a2326;color:#f87171;border:1px solid #5b2a2e}
129129
<div class=row><span class=k>Boot count</span><span class=v id=bootCount>&mdash;</span></div>
130130
<div class=row><span class=k>Firmware</span><span class=v id=fwVersion>&mdash;</span></div>
131131
<div class=row><span class=k>Update status</span><span class=v id=otaStatus>&mdash;</span></div>
132+
<button onclick=checkUpdate()>Check for updates now</button>
132133
</div>
133134
134135
<div class=toast id=toast></div>
@@ -186,6 +187,10 @@ async function clearWifi(){
186187
await post('/api/clear',{});
187188
document.body.innerHTML='<h1>Cleared. Rebooting&hellip;</h1><p class=sub>Join the MiniSpeedCam WiFi to reconfigure.</p>';
188189
}
190+
async function checkUpdate(){
191+
await post('/api/ota/check',{});
192+
toast('Checking GitHub for updates...');
193+
}
189194
function syncStream(on){
190195
if(on===streamShown)return;
191196
const img=$('streamImg');
@@ -401,6 +406,16 @@ static esp_err_t portalStreamPost(httpd_req_t* req) {
401406
return httpd_resp_sendstr(req, "{\"ok\":true}");
402407
}
403408

409+
// POST /api/ota/check -> request an immediate GitHub update check. Only sets the
410+
// flag; taskCore0's otaAutoService() runs the check (and applies any newer image)
411+
// the next time it's idle, bypassing the periodic timer. The result appears in
412+
// the "Update status" line the page already polls.
413+
static esp_err_t portalOtaCheckPost(httpd_req_t* req) {
414+
ota_check_now = true;
415+
httpd_resp_set_type(req, "application/json");
416+
return httpd_resp_sendstr(req, "{\"ok\":true}");
417+
}
418+
404419
static httpd_handle_t config_httpd = NULL;
405420

406421
/**
@@ -417,7 +432,7 @@ void load_config_portal(void) {
417432
// /api/state polls; the old separate :81 stream server is gone (its sockets
418433
// freed), so this single server can afford more open sockets.
419434
config.max_open_sockets = 7;
420-
config.max_uri_handlers = 8;
435+
config.max_uri_handlers = 10;
421436
config.lru_purge_enable = true;
422437
config.stack_size = 8192; // headroom for ArduinoJson + String building
423438

@@ -432,12 +447,14 @@ void load_config_portal(void) {
432447
httpd_uri_t u_wifi = { .uri = "/api/wifi", .method = HTTP_POST, .handler = portalWifiPost, .user_ctx = NULL };
433448
httpd_uri_t u_clear = { .uri = "/api/clear", .method = HTTP_POST, .handler = portalClearPost, .user_ctx = NULL };
434449
httpd_uri_t u_stream = { .uri = "/api/stream", .method = HTTP_POST, .handler = portalStreamPost, .user_ctx = NULL };
450+
httpd_uri_t u_otachk = { .uri = "/api/ota/check",.method = HTTP_POST, .handler = portalOtaCheckPost, .user_ctx = NULL };
435451
httpd_register_uri_handler(config_httpd, &u_root);
436452
httpd_register_uri_handler(config_httpd, &u_state);
437453
httpd_register_uri_handler(config_httpd, &u_set);
438454
httpd_register_uri_handler(config_httpd, &u_wifi);
439455
httpd_register_uri_handler(config_httpd, &u_clear);
440456
httpd_register_uri_handler(config_httpd, &u_stream);
457+
httpd_register_uri_handler(config_httpd, &u_otachk);
441458

442459
// The aiming MJPEG stream shares this same server (GET /stream), shown inline
443460
// in a small window on the config page. See camera_stream.cpp.

‎version/r1.1/firmware/platformio/esp32_firmware_platformio/src/ota.h‎

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,21 @@ bool otaCheckGithub() {
122122
filter["tag_name"] = true;
123123
filter["assets"][0]["name"] = true;
124124
filter["assets"][0]["browser_download_url"] = true;
125-
JsonDocument doc;
126-
DeserializationError err = deserializeJson(doc, https.getStream(), DeserializationOption::Filter(filter));
125+
// Buffer the body before parsing. Reading the (large) release JSON straight
126+
// off the TLS stream is fragile -- a mid-transfer stall (seen on the check
127+
// that runs right after an STM flash) surfaces as "bad API response"
128+
// (IncompleteInput). getString() reads the whole body within the timeout so
129+
// the parser sees it intact; the error detail makes any recurrence concrete
130+
// (and is NOT an "up to date" signal).
131+
String body = https.getString();
127132
https.end();
128-
if (err) { otaSetStatus("check: bad API response"); return false; }
133+
JsonDocument doc;
134+
DeserializationError err = deserializeJson(doc, body, DeserializationOption::Filter(filter));
135+
if (err) {
136+
Serial.printf("[OTA] api parse failed: %s (body=%u B)\n", err.c_str(), (unsigned)body.length());
137+
otaSetStatus("check: bad API response (%s)", err.c_str());
138+
return false;
139+
}
129140

130141
tag = (const char*)(doc["tag_name"] | "");
131142
for (JsonObject a : doc["assets"].as<JsonArray>()) {
@@ -171,12 +182,18 @@ bool otaCheckGithub() {
171182
// a valid health signal for the post-update watchdog (otaHealthService()).
172183
g_cloud_ok = true;
173184

174-
char espinfo[44], stminfo[44];
175-
if (ota_esp_available) snprintf(espinfo, sizeof espinfo, "%s->%s", FW_VERSION, ota_esp_version.c_str());
176-
else snprintf(espinfo, sizeof espinfo, "%s ok", FW_VERSION);
177-
if (ota_stm_available) snprintf(stminfo, sizeof stminfo, "%s->%s", stm_fw_version.c_str(), ota_stm_version.c_str());
178-
else snprintf(stminfo, sizeof stminfo, "%s ok", stm_fw_version.c_str());
179-
otaSetStatus("ESP %s | STM %s", espinfo, stminfo);
185+
if (!ota_esp_available && !ota_stm_available) {
186+
// Both current -- the common, friendly "nothing to do" result.
187+
otaSetStatus("Up to date (ESP %s, STM %s)", FW_VERSION, stm_fw_version.c_str());
188+
} else {
189+
// Something newer is available (and about to be applied below) -- show what.
190+
char espinfo[44], stminfo[44];
191+
if (ota_esp_available) snprintf(espinfo, sizeof espinfo, "%s->%s", FW_VERSION, ota_esp_version.c_str());
192+
else snprintf(espinfo, sizeof espinfo, "%s ok", FW_VERSION);
193+
if (ota_stm_available) snprintf(stminfo, sizeof stminfo, "%s->%s", stm_fw_version.c_str(), ota_stm_version.c_str());
194+
else snprintf(stminfo, sizeof stminfo, "%s ok", stm_fw_version.c_str());
195+
otaSetStatus("Update available: ESP %s | STM %s", espinfo, stminfo);
196+
}
180197
return true;
181198
}
182199

@@ -398,15 +415,21 @@ void otaAutoService() {
398415

399416
if (stm_flash_busy) return;
400417
if (WiFi.getMode() != WIFI_STA || WiFi.status() != WL_CONNECTED) return;
401-
if ((long)(millis() - nextCheck) < 0) return;
402418

403-
// Only act when idle. Don't advance the timer if busy, so we retry promptly
404-
// once the current pass/stream/upload finishes (passes last only seconds).
419+
// A manual "check now" (config-page button) bypasses the interval timer; the
420+
// periodic check otherwise waits for nextCheck to elapse.
421+
bool manual = ota_check_now;
422+
if (!manual && (long)(millis() - nextCheck) < 0) return;
423+
424+
// Only act when idle. Don't advance the timer or consume the manual request if
425+
// busy, so it retries promptly once the current pass/stream/upload finishes
426+
// (passes last only seconds).
405427
bool idle = !g_run_active && !stream_active &&
406428
(uploadQueue == nullptr || uxQueueMessagesWaiting(uploadQueue) == 0);
407429
if (!idle) return;
408430

409-
nextCheck = millis() + OTA_CHECK_INTERVAL_MS; // schedule the next check regardless of outcome
431+
ota_check_now = false; // consume the manual request (if any)
432+
nextCheck = millis() + OTA_CHECK_INTERVAL_MS; // schedule the next periodic check regardless of outcome
410433

411434
if (!otaCheckGithub()) return;
412435

‎version/r1.1/firmware/platformio/esp32_firmware_platformio/src/variables.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ const char* server_github_latest = "https://api.github.com/repos/" GITHUB_OWNER
189189
// URLs/MD5s and the *_available flags are touched only on Core 0, so they need
190190
// no guarding.
191191
char ota_status[96] = "idle";
192+
// Manual "check for updates now" request from the config-page button. Set by the
193+
// POST /api/ota/check handler (httpd task) and consumed by otaAutoService() on
194+
// Core 0, which then runs a check immediately instead of waiting for the timer.
195+
volatile bool ota_check_now = false;
192196
String ota_esp_version, ota_esp_url, ota_esp_md5;
193197
String ota_stm_version, ota_stm_url, ota_stm_md5;
194198
bool ota_esp_available = false;

0 commit comments

Comments
 (0)