Skip to content

Commit 709c3f9

Browse files
angeloINTJclaude
andcommitted
feat: update OTA subsystem from feature/ota-self-flash (v4.6.2)
All 20 OTA files updated to latest version from feature branch. Key changes: - F-DISPLAY-MARGINS: fillMarginsBlack + fillScreen override in TftWithOffset - F-TEL-HTTPS-RESILIENT: fix crash+reboot when HTTPS server drops - F-UI-OTA-FLOW: user-facing OTA + restore UX messages - F-BOOT-CYW43-CYCLE: power-cycle WL_REG_ON always in setup() - F-OTA-STAGE-NOBLOCK + F-FLASH-DIET: fix TCP drop during OTA stage - F-TEL-ADAPTIVE: adaptive-throughput telemetry (backend-only) - F-SCREENSHOT-INTEGRITY: eliminate row loss/corruption in /api/screenshot - F-OTA-ADMIN-ONLY: OTA endpoints require PERM_FULL_ADMIN - F-RAM-SLIM-FIX: fix broken mDNS and RSSI from v4.2.0 - F-RAM-SLIM: RAM 49.6% → 33.7% (-41 KB / -16pp) - F-RESTORE: reliable restore via API (98/100 PASS) - F-OTA: fully closed — Bootloop fix, loop20 OTA 100% PASS Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent e4f76a7 commit 709c3f9

20 files changed

Lines changed: 1764 additions & 1710 deletions

src/ota/applier.cpp

Lines changed: 255 additions & 227 deletions
Large diffs are not rendered by default.

src/ota/applier.h

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
11
/**
2-
* @file src/ota/applier.h
3-
* @brief OTA applier — runs entirely from SRAM.
2+
* @file src/ota/applier.h
3+
* @brief Aplicador OTA — roda inteiramente da SRAM (Fase 7).
44
*
5-
* @details `ota_applier_run` is the destructive routine that erases the app slot
6-
* and rewrites it with the uncompressed staging content. IT IS MARKED
7-
* WITH `__not_in_flash_func` to reside in SRAM, and therefore can
8-
* execute even while flash is being erased/programmed.
5+
* @details `ota_applier_run` é a rotina destrutiva que apaga o slot da app
6+
* e reescreve com o conteúdo descomprimido do staging. ESTÁ MARCADA
7+
* COM `__not_in_flash_func` para residir em SRAM, e portanto pode
8+
* executar mesmo enquanto a flash está sendo erased/programmed.
99
*
10-
* PRECONDITIONS (caller orchestrator guarantees):
11-
* - WiFi/CYW43 off (`WiFi.end()`).
12-
* - LittleFS unmounted (`LittleFS.end()`).
13-
* - Core 1 paused via `multicore_lockout_start_blocking()`.
14-
* - Global IRQs disabled (`save_and_disable_interrupts`).
15-
* - Metadata.state == APPLYING and persisted in flash.
10+
* PRECONDIÇÕES (caller orchestrator garante):
11+
* - WiFi/CYW43 desligado (`WiFi.end()`).
12+
* - LittleFS desmontada (`LittleFS.end()`).
13+
* - Core 1 pausado via `multicore_lockout_start_blocking()`.
14+
* - IRQs globais desabilitadas (`save_and_disable_interrupts`).
15+
* - Metadata.state == APPLYING e persistida em flash.
1616
*
17-
* @project SIMUT — Sistema Integrado de Monitoramento Universal e Telemetria
18-
* SIMUT — Integrated Universal Monitoring and Telemetry System
19-
* @target Raspberry Pi Pico W (RP2040) — Arduino Framework
20-
* @author Ângelo Moisés Alves
17+
* 7a (atual — não destrutivo): a função apenas faz uma pausa breve
18+
* e dispara `watchdog_reboot`. Demonstra que a infraestrutura de
19+
* jump-to-SRAM + lockout + IRQ-off + reboot funciona, sem riscar a
20+
* app slot. Validar 7a antes de evoluir pra 7b real.
21+
*
22+
* 7b (a implementar — destrutivo): apaga slot da app sector-by-
23+
* sector, descomprime staging via uzlib (que precisará estar em
24+
* SRAM), grava no slot, valida CRC do output relendo via XIP,
25+
* watchdog_reboot. NÃO retorna em sucesso.
26+
*
27+
* @project SIMUT
28+
* @target Raspberry Pi Pico W (RP2040) — Arduino Framework
29+
* @author Ângelo Moisés Alves
2130
* @license MIT License
2231
*/
2332
#pragma once
@@ -27,12 +36,13 @@
2736
namespace ota {
2837

2938
/**
30-
* @brief Applies the update in destructive mode. DOES NOT RETURN on success.
39+
* @brief Aplica o update em modo destrutivo. NÃO RETORNA em sucesso.
3140
*
32-
* @param meta Metadata in RAM (caller read before IRQ disable).
41+
* @param meta Metadata em RAM (caller leu antes de IRQ disable).
42+
* Em 7a (no-op), parâmetro é ignorado.
3343
*
34-
* @return false only if error detected BEFORE any destructive action.
35-
* On success, NEVER returns (always reboots).
44+
* @return false só se erro detectado ANTES de qualquer ação destrutiva.
45+
* Em 7a, NUNCA retorna (sempre reboota).
3646
*/
3747
bool ota_applier_run(const UpdateMetadata* meta);
3848

src/ota/backup.cpp

Lines changed: 223 additions & 221 deletions
Large diffs are not rendered by default.

src/ota/backup.h

Lines changed: 64 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
/**
2-
* @file src/ota/backup.h
3-
* @brief Public API of the backup (.bkp) generator — OTA.
2+
* @file src/ota/backup.h
3+
* @brief API pública do gerador de backup (.bkp) — Fase 1 do OTA.
44
*
5-
* @details Generation does two passes over LittleFS to avoid buffering in RAM:
6-
* 1) scan: calculates payload_size + payload_crc32 + file_count.
7-
* 2) emit: writes header (with CRCs) + payload streaming via Print.
5+
* @details A geração faz dois passes sobre a LittleFS para evitar buffer em RAM:
6+
* 1) scan: calcula payload_size + payload_crc32 + file_count.
7+
* 2) emit: escreve header (com CRCs) + payload em streaming via Print.
88
*
9-
* The caller is responsible for ensuring LittleFS is mounted and
10-
* that the operation is serialized against concurrent writes (e.g.
11-
* via HeavyTaskGuard in WebManager).
9+
* O caller é responsável por garantir que o LittleFS está montado e
10+
* que a operação é serializada contra escritas concorrentes (ex.:
11+
* via HeavyTaskGuard no WebManager).
1212
*
13-
* @project SIMUT — Sistema Integrado de Monitoramento Universal e Telemetria
14-
* SIMUT — Integrated Universal Monitoring and Telemetry System
15-
* @target Raspberry Pi Pico W (RP2040) — Arduino Framework
16-
* @author Ângelo Moisés Alves
13+
* @project SIMUT
14+
* @target Raspberry Pi Pico W (RP2040) — Arduino Framework
15+
* @author Ângelo Moisés Alves
1716
* @license MIT License
1817
*/
1918
#pragma once
@@ -26,99 +25,98 @@
2625
namespace ota {
2726

2827
/**
29-
* @brief Incremental CRC32 (poly 0xEDB88320, init 0xFFFFFFFF, xor-out 0xFFFFFFFF).
28+
* @brief CRC32 incremental (poly 0xEDB88320, init 0xFFFFFFFF, xor-out 0xFFFFFFFF).
3029
*
31-
* Compatible with gzip/zlib CRC32. Reused in other steps (staging
32-
* validation, metadata).
30+
* Compatível com CRC32 do gzip/zlib. Reutilizado em outras fases (validação
31+
* de staging, metadata).
3332
*
34-
* @param crc Previous state (pass OTA_CRC32_INIT on first chunk).
35-
* @param data Byte buffer.
36-
* @param len Length.
37-
* @return Partial CRC; apply `^ 0xFFFFFFFFu` when done to get the final CRC.
33+
* @param crc Estado anterior (passar OTA_CRC32_INIT no primeiro chunk).
34+
* @param data Buffer de bytes.
35+
* @param len Tamanho.
36+
* @return CRC parcial; aplicar `^ 0xFFFFFFFFu` ao terminar para obter o CRC final.
3837
*/
3938
uint32_t crc32_update(uint32_t crc, const uint8_t* data, size_t len);
4039

4140
/**
42-
* @brief Reads the unique RP2040 ID (flash_get_unique_id).
41+
* @brief Lê o ID único do RP2040 (flash_get_unique_id).
4342
*
44-
* Cached after first call (operation costs ~100us and disables IRQs).
43+
* Cacheado após a primeira chamada (operação custa ~100us e desabilita IRQs).
4544
*
46-
* @param out 8-byte buffer; filled with the ID.
45+
* @param out Buffer de 8 bytes; preenchido com o ID.
4746
*/
4847
void read_chip_id(uint8_t out[8]);
4948

5049
/**
51-
* @brief Encodes version string "vMAJOR.MINOR.PATCH" into packed uint32.
50+
* @brief Encoda string de versão "vMAJOR.MINOR.PATCH" em uint32 packed.
5251
*
53-
* Format: (major<<16) | (minor<<8) | patch. Each field truncated to 8 bits.
54-
* E.g.: "v3.37.8" → 0x00032508.
52+
* Formato: (major<<16) | (minor<<8) | patch. Cada campo é truncado para 8 bits.
53+
* Ex.: "v3.37.8" → 0x00032508.
5554
*
56-
* @param version_str String like "v3.37.8" or "3.37.8" (prefix 'v' optional).
57-
* @return uint32 encoded; 0 if string is invalid.
55+
* @param version_str String tipo "v3.37.8" ou "3.37.8" (prefixo 'v' opcional).
56+
* @return uint32 encoded; 0 se string for inválida.
5857
*/
5958
uint32_t encode_version_u32(const char* version_str);
6059

6160
/**
62-
* @brief Result of the LittleFS scan (prerequisite to generate header).
61+
* @brief Resultado do scan da LittleFS (pré-requisito para gerar header).
6362
*/
6463
struct BackupScanResult {
65-
uint32_t payload_size; /**< Total payload size in bytes. */
66-
uint32_t payload_crc32; /**< Final payload CRC32 (already with xor-out applied). */
67-
uint16_t file_count; /**< Number of files enumerated. */
64+
uint32_t payload_size; /**< Tamanho total do payload em bytes. */
65+
uint32_t payload_crc32; /**< CRC32 final do payload (já com xor-out aplicado). */
66+
uint16_t file_count; /**< Quantidade de arquivos enumerados. */
6867
};
6968

7069
/**
71-
* @brief Backup validation status codes.
70+
* @brief Códigos de status da validação de backup (Fase 2).
7271
*
73-
* Ordered so lower values correspond to failures detected
74-
* earlier in parsing — useful for hierarchical error messages.
72+
* Ordenados de forma que valores baixos correspondam a falhas detectadas
73+
* mais cedo no parse — útil pra mensagens de erro hierárquicas.
7574
*/
7675
enum class BackupStatus : uint8_t {
77-
OK = 0,
78-
BAD_MAGIC = 1,
79-
UNSUPPORTED_SCHEMA = 2,
80-
HEADER_CRC_MISMATCH = 3,
81-
PAYLOAD_TRUNCATED = 4,
82-
PAYLOAD_CRC_MISMATCH = 5,
83-
CHIP_ID_MISMATCH = 6,
84-
PATH_INVALID = 7,
85-
PATH_TOO_LONG = 8,
86-
IO_ERROR = 9,
87-
INTERNAL_ERROR = 10,
76+
OK = 0,
77+
BAD_MAGIC = 1,
78+
UNSUPPORTED_SCHEMA = 2,
79+
HEADER_CRC_MISMATCH = 3,
80+
PAYLOAD_TRUNCATED = 4,
81+
PAYLOAD_CRC_MISMATCH = 5,
82+
CHIP_ID_MISMATCH = 6,
83+
PATH_INVALID = 7,
84+
PATH_TOO_LONG = 8,
85+
IO_ERROR = 9,
86+
INTERNAL_ERROR = 10,
8887
};
8988

90-
/* Validation of .bkp in production is done by the state machine in
91-
* ota::RestoreSession (VALIDATE mode) — see src/ota/restore.h. The
92-
* Stream-based backup_validate was removed to save flash; the web upload
93-
* pipeline already consumes chunks and maintains state, so the state
94-
* machine covers both scenarios. */
89+
/* Validação de .bkp em produção é feita pelo state machine em ota::RestoreSession
90+
* (modo VALIDATE) — ver src/ota/restore.h. backup_validate Stream-based foi
91+
* removido para economizar flash; o pipeline de upload web já consome chunks e
92+
* mantém estado, então a state machine cobre os dois cenários. */
9593

9694
/**
97-
* @brief Pass 1: scans LittleFS, computes total size and payload CRC32.
95+
* @brief Pass 1: varre a LittleFS, computa tamanho total e CRC32 do payload.
9896
*
99-
* Does NOT write anything; only measures. Returns metrics to build the header
100-
* before pass 2.
97+
* NÃO escreve nada; apenas mede. Retorna métricas para construir o header
98+
* antes do pass 2.
10199
*
102-
* @param out Result.
103-
* @return true if scan was successful; false on I/O error.
100+
* @param out Resultado.
101+
* @return true se o scan foi bem-sucedido; false em I/O error.
104102
*/
105103
bool backup_scan(BackupScanResult& out);
106104

107105
/**
108-
* @brief Pass 2: writes the full backup (header + payload) in streaming.
106+
* @brief Pass 2: escreve o backup completo (header + payload) em streaming.
109107
*
110-
* Requires a valid BackupScanResult obtained by backup_scan (ideally
111-
* immediately before, under HeavyTaskGuard, to ensure consistency).
108+
* Requer um `BackupScanResult` válido obtido por `backup_scan` (idealmente
109+
* imediatamente antes, sob HeavyTaskGuard, para garantir consistência).
112110
*
113-
* @param out Output stream (Print&; e.g. WebServer client).
114-
* @param scan Result of pass 1.
115-
* @param firmware_version Encoded version via encode_version_u32.
116-
* @param timestamp Unix epoch UTC; 0 if NTP unavailable.
117-
* @return true if write completed without I/O errors.
111+
* @param out Stream de saída (Print&; ex.: WebServer client).
112+
* @param scan Resultado do pass 1.
113+
* @param firmware_version Versão encoded via encode_version_u32.
114+
* @param timestamp Unix epoch UTC; 0 se NTP indisponível.
115+
* @return true se a escrita completou sem erros de I/O.
118116
*/
119117
bool backup_emit(Print& out,
120-
const BackupScanResult& scan,
121-
uint32_t firmware_version,
122-
uint32_t timestamp);
118+
const BackupScanResult& scan,
119+
uint32_t firmware_version,
120+
uint32_t timestamp);
123121

124122
} /* namespace ota */

src/ota/backup_format.h

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,67 @@
11
/**
2-
* @file src/ota/backup_format.h
3-
* @brief Binary layout of the SIMUT backup file (.bkp).
4-
* @details Defines the on-disk format used by the OTA system.
5-
* Schema-versioned with CRC32 protection at both header and payload
6-
* levels. Tied to the RP2040 chip_id (prevents cross-device restore).
2+
* @file src/ota/backup_format.h
3+
* @brief Binary layout of the SIMUT backup file (.bkp).
4+
* @details Defines the on-disk format used by Phases 1-2 of the OTA system.
5+
* Schema-versioned with CRC32 protection at both header and payload
6+
* levels. Atrelado ao chip_id do RP2040 (impede restauração cruzada).
77
*
8-
* @project SIMUT — Sistema Integrado de Monitoramento Universal e Telemetria
9-
* SIMUT — Integrated Universal Monitoring and Telemetry System
10-
* @target Raspberry Pi Pico W (RP2040) — Arduino Framework
11-
* @author Ângelo Moisés Alves
8+
* Conforme IMPLEMENTATION_PLAN.md §5.1.
9+
*
10+
* @project SIMUT
11+
* @target Raspberry Pi Pico W (RP2040) — Arduino Framework
12+
* @author Ângelo Moisés Alves
1213
* @license MIT License
1314
*/
1415
#pragma once
1516
#include <stdint.h>
1617
#include <stddef.h>
1718

18-
/* Magic = "BKP1" in ASCII little-endian. */
19-
#define OTA_BACKUP_MAGIC 0x31504B42u
20-
#define OTA_BACKUP_SCHEMA 1u
19+
/* Magic = "BKP1" em ASCII little-endian. */
20+
#define OTA_BACKUP_MAGIC 0x31504B42u
21+
#define OTA_BACKUP_SCHEMA 1u
2122

22-
/* CRC32 polynomial (identical to gzip/zlib). */
23-
#define OTA_CRC32_POLY 0xEDB88320u
24-
#define OTA_CRC32_INIT 0xFFFFFFFFu
23+
/* CRC32 polinômio (idêntico ao gzip/zlib). */
24+
#define OTA_CRC32_POLY 0xEDB88320u
25+
#define OTA_CRC32_INIT 0xFFFFFFFFu
2526

2627
#ifdef __cplusplus
2728
extern "C" {
2829
#endif
2930

3031
/**
31-
* @brief Fixed backup file header. Always 40 bytes.
32+
* @brief Cabeçalho fixo do arquivo de backup. Sempre 40 bytes.
3233
*
33-
* All multi-byte fields are little-endian (RP2040 native).
34+
* Todos os campos multi-byte são little-endian (nativo do RP2040).
3435
*/
3536
struct __attribute__((packed)) BackupHeader {
36-
uint32_t magic; /**< OTA_BACKUP_MAGIC. */
37-
uint16_t schema_version; /**< Current OTA_BACKUP_SCHEMA. */
38-
uint16_t reserved0; /**< Padding/alignment. Always 0. */
39-
uint8_t chip_id[8]; /**< RP2040 unique ID (flash_get_unique_id). */
40-
uint32_t firmware_version; /**< Encoded (major<<16)|(minor<<8)|patch. */
41-
uint32_t timestamp; /**< Unix epoch UTC; 0 if NTP not ready. */
42-
uint32_t payload_size; /**< Payload bytes after this header. */
43-
uint32_t payload_crc32; /**< CRC32 (poly EDB88320, init 0xFFFFFFFF, xor-out 0xFFFFFFFF) of payload. */
44-
uint32_t reserved1; /**< Reserved for future extension. Always 0 — included in header_crc32. */
45-
uint32_t header_crc32; /**< CRC32 of the preceding 36 bytes of this struct. */
37+
uint32_t magic; /**< OTA_BACKUP_MAGIC. */
38+
uint16_t schema_version; /**< OTA_BACKUP_SCHEMA atual. */
39+
uint16_t reserved0; /**< Padding/alinhamento. Sempre 0. */
40+
uint8_t chip_id[8]; /**< RP2040 unique ID (flash_get_unique_id). */
41+
uint32_t firmware_version; /**< Encoded (major<<16)|(minor<<8)|patch. */
42+
uint32_t timestamp; /**< Unix epoch UTC; 0 se NTP não estiver pronto. */
43+
uint32_t payload_size; /**< Bytes de payload após este header. */
44+
uint32_t payload_crc32; /**< CRC32 (poly EDB88320, init 0xFFFFFFFF, xor-out 0xFFFFFFFF) do payload. */
45+
uint32_t reserved1; /**< Reserva para extensão futura. Sempre 0 — incluído no header_crc32. */
46+
uint32_t header_crc32; /**< CRC32 dos 36 bytes anteriores deste struct. */
4647
};
4748

48-
/* Compile-time check: guarantees stable layout regardless of compiler/arch. */
49+
/* Compile-time check: garante layout estável independente do compilador/arch. */
4950
#ifdef __cplusplus
5051
static_assert(sizeof(BackupHeader) == 40, "BackupHeader must be exactly 40 bytes");
5152
#else
5253
_Static_assert(sizeof(struct BackupHeader) == 40, "BackupHeader must be exactly 40 bytes");
5354
#endif
5455

5556
/**
56-
* @brief Header of a TLV entry in the payload.
57+
* @brief Cabeçalho de uma entrada TLV no payload.
5758
*
58-
* Followed by: char path[path_length] (no null terminator) + uint8_t content[content_length].
59-
* Sequence repeated until totaling payload_size.
59+
* Seguido por: char path[path_length] (sem nul-terminator) + uint8_t content[content_length].
60+
* Sequência repetida até totalizar payload_size.
6061
*/
6162
struct __attribute__((packed)) BackupEntry {
62-
uint16_t path_length; /**< Path bytes (no terminator). */
63-
uint32_t content_length; /**< File content bytes. */
63+
uint16_t path_length; /**< Bytes do path (sem terminador). */
64+
uint32_t content_length; /**< Bytes do conteúdo do arquivo. */
6465
};
6566

6667
#ifdef __cplusplus

0 commit comments

Comments
 (0)