Tab5 OS is an ESP-IDF/FreeRTOS firmware with one LVGL display shell. It deliberately uses the platform's native drivers, NVS, SPIFFS, FAT VFS, HTTP/TLS, OTA, NimBLE, and ordinary files instead of introducing an application framework or database.
Root CMake checks the SDK's exported major.minor version before configuration can regenerate sdkconfig, then requires the ESP32-P4 MCU, ESP32-C6 hosted target and SDIO transport. This prevents an incomplete SDK environment from silently omitting Wi-Fi Remote's version-selected configuration. The existing firmware compile-time checks enforce the board's SDIO pins, bus width and reset polarity. See Firmware configuration checks.
app_main runs assertion-based parser/math and component checks, initializes the shared internal I2C devices, display/touch, ESP-Hosted Wi-Fi transport, NVS-backed settings, internal SPIFFS, and microSD, then creates the fixed header/content shell. With assertions enabled, reaching the Starting Tab5 OS boot log means those checks passed. A 30-second LVGL timer validates an OTA candidate only after NVS and internal storage are usable and the event loop remains alive.
On the pinned ESP-IDF 5.4.2, ESP32-P4 with PSRAM XIP needs psram_exec_compat.c: the SDK's executable-pointer check omits the linker-reserved PSRAM instruction range, causing a valid pthread TLS deletion callback to abort after a network worker exits (upstream issue 15997). The IRAM linker wrapper accepts only that instruction range and delegates every other address to the original SDK check; the FreeRTOS corruption guard remains enabled. CMake limits the wrapper to that SDK version, chip and configuration. Assertion-enabled boot checks validate the range boundaries and actual task/TLS cleanup on each CPU core. Reassess the upstream check and repeat these hardware tests when upgrading the SDK before removing the workaround.
LVGL objects and callbacks run on the display task under the BSP display lock. Network, audio, ADC, BLE, download, and cleanup work runs in bounded FreeRTOS tasks or timers. Workers never update deleted LVGL objects directly: they store bounded state/results, and the visible app's LVGL timer renders them.
LVGL's fixed 96 KiB object pool is allocated once at display initialization in byte-addressable PSRAM through its supported LV_MEM_POOL_ALLOC hook. The size and allocator stay unchanged; moving the pool out of static internal RAM leaves space for the ESP-Hosted SDIO driver's DMA receive buffers. main/CMakeLists.txt applies this only to the firmware's LVGL component, without editing managed sources or changing the native test build. Boot logs confirm the allocation as ui-memory: LVGL pool: 98304 bytes in PSRAM.
System identifies the running image with the SDK's semantic version and ELF SHA-256 prefix, plus the active partition label, address and capacity. The prefix matches boot/crash logs and distinguishes incremental builds whose version text is unchanged; cached app-description timestamps are not used as build identity. Partition details describe the installed layout, which may differ from the repository's current partition table.
OTA result loading requires a successful VALID state read before recording installation of the matching pending version. An unavailable or unconfirmed state remains visible in System and preserves the pending NVS record for later reconciliation. At the health window, an already VALID image reloads and reconciles that record, recovering a transient boot-time read error or an earlier pending/unconfirmed status. This branch observes existing validation and never marks the image valid. PENDING_VERIFY still requires ready NVS/internal storage and a successful SDK validation call; a different running version follows the rollback/non-activation check.
OTA busy/completion signals are atomic. The worker captures its outcome locally and publishes completion before releasing busy ownership on failure; the UI consumes failure only after release. Successful installation keeps ownership through pending-version persistence and the restart delay. Health processing defers while the updater is busy, preserving that newly saved record. After the initial health window, its independent timer retries every 250 ms until the updater is idle, then deletes itself and validates or reconciles the running image. This also resumes after an update fails with System closed. The native fixture compiles eleven actual result/health/worker/diagnostics/storage functions with controlled APIs and signal observation; physical flash, boot, task/timer scheduling and both panels remain device checks.
Successful installation records the manifest version already compared with the embedded image version and accepted with the image length, digest and SDK finalization. A second boot-partition/description read cannot prevent or replace that pending record. Tracking remains best effort: unavailable NVS skips the write, while open/set/commit errors are logged. Boot-time state checks still decide whether the pending version became installed or rolled back.
System's OTA image state, last result and invalid-image version share a separate label. Opening System, completing an idle health check and retrying validation after storage initialization refresh those fields. Leaving System clears its label pointer before content deletion; offscreen health processing performs no diagnostic reads or label access. These event-driven updates preserve validation and persistence behavior without repeatedly polling flash from the UI timer.
The speaker amplifier's SPK_EN output (PI4IOE1 P1) is preloaded low before configuring the expander outputs. Alarm playback configures and mutes the codec, writes silence, then unmutes and enables the amplifier only if setup succeeds. It mutes and disables the amplifier before closing the codec. Failed setup leaves a visual alarm and logs the error. This prevents deliberately enabling an uninitialized audio path; its quiet-startup and alarm behavior still need hardware verification after the reported USB brownouts. Brownout protection and charging settings remain unchanged.
The static launcher table in main/main.c owns each tile's enter callback and optional leave hook. clear_content() invokes the active leave hook, stops shared tools/timers, releases pins and outputs, nulls screen pointers, deletes content children, and resets scroll/layout state. New substantial tools live in their own main/*_tool.c file; legacy code moves only when materially changed.
Electronics, Byte Lab, Subnet Lab, and Resistor Lab are offline LVGL apps with pure calculation/parser modules tested on the host. Their leave hooks detach the keyboard and clear screen pointers before content deletion. Inputs are retained only in RAM; they own no pins, worker tasks, network sessions, or storage writers. Byte Lab coalesces bulk-input rendering into one LVGL async call and cancels it on exit, avoiding per-character button-animation allocation. Subnet Lab uses bounded IPv4 inputs and 64-bit counts, including explicit /31 point-to-point and /32 host-route semantics. Resistor Lab decodes color bands and bounded SMD markings, preserving separate drafts and clearing invalid results. See Offline bench helpers and Resistor Lab.
Byte Lab also encodes bounded decimal integers and Float32 values into a selected byte order, reusing its existing checksum/interpretation calculation. Syntax and length checks precede standard-library float conversion; range checks reject finite overflow and nonzero underflow to zero. The encoder requires a binary32 C float, covered by compile-time guards and known-byte startup/host checks. An explicit UI action copies a valid result to the Hex draft.
payload_clipboard.c is a bounded 128-byte RAM clipboard shared by the bench apps and saved serial log viewer. Only LVGL callbacks read or change it; workers continue using their immutable request snapshots, so the clipboard needs no lock. Failed copies preserve the previous value; clearing or shorter copies zero unused bytes. Hex formatting either succeeds completely or returns an empty error output. Each console validates its own size limit before replacing a draft. Paste does not start a bus/worker or send traffic, and UDP paste disarms confirmation. Only complete UDP replies of at most 128 bytes can be copied, after socket cleanup.
The legacy I2C inspector uses i2c_register.c for bounded synchronous transactions: one register-pointer byte followed by 1-32 read bytes, or one confirmed register/value write. It accepts only ordinary 7-bit addresses and 100/400 kHz, rejects invalid parameters before bus activity, and zeroes receive output on transaction or cleanup failure. Device and bus handles survive failed removals for retry; another transaction cannot begin until cleanup succeeds. Home retries cleanup, restart/OTA and GPIO/Scope/Servo entry respect retained bus ownership, and unconditional Servo cleanup skips shared G54 while I2C still owns it.
i2c_result.c is the inspector's UI-only saved-read panel. It owns up to 32 bytes and the original address/register pointer/speed. Failed or invalid reads clear it; confirmed writes invalidate it before bus I/O. Copy/clear callbacks first cancel pending write confirmation. Home drops panel pointers and stops the existing Watch timer while retaining bytes in RAM. SCAN and READ ONCE are explicit. One-shot reads stop Watch and finish CSV capture; Watch/CSV remain one byte per sample. The panel adds no driver, timer, file or worker. Native transaction-fault/panel/Byte Lab checks and the 2026-09-29 firmware build pass. ST7121 passed empty-bus failures, length selection/retention, Watch-to-read transition and popup teardown; known-device reads, CSV faults and electrical validation remain open.
SPI stores one completed transfer in a static snapshot: up to 32 TX bytes, 32 RX bytes, and the original mode/clock. COPY RX validates that snapshot and copies raw receive bytes without bus activity. Draft or setting edits leave it intact; an actual driver transfer failure zeroes it, clears the display and disables copying. CLEAR RESULT zeroes the snapshot independently of the clipboard. A separate RAM draft retains the first excess character and up to four UTF-8 bytes per LVGL character; restore happens before attaching input callbacks to avoid modifying the source during per-character events. CLEAR TX affects only that draft. The hex keyboard attaches on focus/tap and detaches on Done/Cancel or Home. Stop/Home releases the bus and UI pointers while retaining the draft and snapshot in RAM. No worker, timer or storage path is added. Native UI checks and the 2026-09-29 firmware build pass. ST7121 passed keyboard entry/dismissal, one disconnected transfer, original-setting retention and COPY RX -> Byte Lab. Electrical checks, a real peripheral and the second panel family remain open.
The Serial terminal uses uart_data.c for bounded ASCII/Hex parsing, explicit ASCII suffixes, and exact history reconstruction. Its eight RAM history entries retain binary bytes and format/suffix instead of truncating text. The existing receive timer coalesces draft validation after a bulk paste; SEND always revalidates synchronously. Home invokes its explicit launcher leave hook, cancels that timer, detaches the keyboard, drains pending TX using the selected framing/baud, and releases its output buffer and interface. See Serial terminal.
Serial's optional RX window uses a static 128-byte buffer in that same LVGL task. Capture start first drains queued input into the transcript/log; receive ticks append across driver reads. Freeze/Stop/Home drain a bounded tail and freeze the window, retaining its original link settings. Overflow or a read/drain failure clears the captured bytes and prevents copy. Explicit COPY RX alone changes the shared clipboard; no capture worker, timer or protocol framing layer is added. The window observes driver-delivered bytes and cannot prove physical frame completeness or absence of FIFO loss.
Serial logging retains its existing stream and 96-byte path buffers. Start advances to another name only on a final/TMP collision, stopping on other preparation or file errors. Each write/sync boundary captures a fresh first error before tick or close calls can change errno. Rejected header/row writes close the owned stream and retain the named TMP; later Stop has no stream to publish. Healthy Stop uses the existing storage helper. Native file checks compile the actual three functions and helper, including the start constructor and same-second recovery; UI, driver and physical storage behavior use separate validation gates.
RTU Frames is a separate offline app that builds eight-byte read requests into the shared clipboard and decodes complete pasted replies. It shares the pure read PDU decoder/value formatter with Modbus TCP and the CRC helper with Byte Lab. Input edits invalidate results once per edit sequence, avoiding per-character rendering allocations. Its leave hook detaches the keyboard and clears UI pointers; it owns no UART, timer, worker, network session or storage writer. A pasted frame cannot establish serial timing or its originating request address. Native checks and the 2026-09-29 firmware build pass. ST7121 passed the example decode and request handoff into stopped Serial; broader panel and wired-fixture checks remain open.
Modbus TCP uses a single bounded network worker for a connection test or one read request. Its locked request snapshot captures the acceptance time; the worker uses that original five-second budget and includes scheduling time in the result duration. An already expired worker creates no socket. The worker owns and closes its nonblocking socket; Stop/Home signals cancellation without waiting in LVGL. OTA checks its busy state through cleanup. It publishes raw register/bit values with the request identity; the UI formats that snapshot into 16-bit or selectable 32-bit views without new traffic. Float32 interpretation shares Byte Lab's pure binary32 decoder. Its framing/decoding module is host-testable and supports only read functions 01-04. See Modbus TCP inspector.
Network Diagnostics creates one task per accepted operation and releases it after cleanup. It shares network_resolver.c with HTTP: two locked lookup slots attach worker-owned state, while queued dispatch and late DNS replies carry IDs instead of caller pointers. Each lookup captures its caller's absolute deadline before attachment. The core rejects expired dispatch under the lookup lock before copying the name or invoking DNS, even if the caller has not resumed to detach; other live lookups retain their own budgets. Cached-answer buffers match the SDK's configured capacity and callers receive at most four addresses. Diagnostics includes task scheduling in its ten-second DNS wait and requests cancellation on Cancel/Home. Its four raw echo probes use network_ping.c, which owns one nonblocking socket, checks cancellation around I/O and gaps, applies absolute probe waits and a six-second budget after DNS, and validates replies against the selected source and fresh payload. mDNS retains busy ownership through its existing three-second query and result cleanup. Native worker/LVGL and socket/clock checks cover these paths; physical latency and routing remain open. See Network Diagnostics.
NTP Lab and Wake-on-LAN each own one bounded UDP worker and publish immutable request/result snapshots for the LVGL timer. Their ten-second and two-second budgets begin when the UI accepts the request, include worker scheduling, and prohibit socket creation after expiry. Displayed duration includes that wait; NTP's clock reference and clock-step checks begin when the worker measures the clock. Home requests cancellation and detaches screen objects; the worker remains busy through socket cleanup, preventing OTA restart. NTP Lab measures four samples without setting the clock. Wake-on-LAN sends one confirmed magic packet without retrying or claiming that the target woke. Neither app opens a listener, saves settings, or logs traffic. See Device and network tools.
UDP Console uses the same worker/snapshot lifecycle for one confirmed custom datagram. It reuses Byte Lab's bounded payload parser, transmits at most 128 bytes, and receives a 512-byte preview plus one sentinel byte to distinguish oversized replies. A connected, nonblocking UDP socket filters replies to the selected peer; its three-second budget begins at request acceptance and includes scheduling, socket setup, sending and receiving. An expired worker creates no socket, and displayed duration includes startup delay. The worker alone closes the socket and remains visible to the OTA blocker until cleanup completes. No listener, task, or UI timer remains after an idle exit. See UDP Console.
Every resource has one owner at a time:
- G53/G54: GPIO, Scope ADC, or external I2C; Servo Toy also uses G54 for its LED driver.
- G0: GPIO or Servo Toy PWM.
- G6 and LEDC timer 2/channel 3: Signal Generator.
- LEDC timer 1/channel 2: Servo; display backlight remains timer 0/channel 1.
- UART1: G47/G48 terminal or onboard RS-485 through its explicit mode.
- SPI2: bounded M5-Bus transaction console.
- BLE connection/scanner: optional product view or generic GATT Explorer, never both.
- SD writer/capture: the owning app until stop/commit/abort completes.
Leaving an app restores shared pins to disabled/high-impedance or an explicitly safe receive/off state. OTA checks the same ownership state and refuses restart while a writer, output, sampling task, network operation, or BLE session is active.
Ender 3 joins its connection worker before uninstalling CDC or freeing callback semaphores. The BSP USB event task handles pending device-free events and uninstalls the host outside usb_host_lib_handle_events; a completion semaphore makes stop idempotent and allows a timed-out caller to retry. Failed cleanup retains ownership and blocks re-entry/restart/OTA, while UI pointers detach immediately. Host install and task-allocation errors return to the UI instead of aborting. ST7121 passed 16 empty-port cycles after this corrected a reproducible second-entry abort; live printer and fault-injection coverage remains open.
BLE receives copy at most 64 bytes out of NimBLE buffer chains into locked state, retaining the original handle, total received length and indication/notification kind. Only the UI copy handler touches the shared clipboard, after taking a snapshot under that lock. Truncated values, failed buffer copies and stopping/disconnected sessions cannot replace the clipboard. Firmware startup tests exercise real buffer traversal and the receive/copy callbacks without radio traffic. The explicitly saved CSV marks unavailable data and distinguishes indications from notifications.
The serial log viewer loads at most 512 validated rows and closes the file before presenting its eight-row pages. Its selector maps each visible option to the original loaded-row index, which remains stable across direction filters. COPY ROW revalidates that mapping and copies raw bytes to the UI-owned clipboard only for a whole row of 1-128 bytes; formatted ASCII/Hex text is never the source. Partial allocation failures free both buffers immediately. Home/Back frees all viewer buffers and clears selection/UI pointers. Native fixture/UI checks and the 2026-09-29 firmware build pass; physical SD/panel checks for record copying remain pending.
- NVS stores small validated settings and credentials. Initialization faults preserve data rather than auto-erasing it.
- Internal
storageis SPIFFS for built-in state. Only provably blank flash auto-initializes. - FAT microSD stores user files and evidence. Writers use durable sync, unique
.TMPpublication, recoverable.BAKreplacement, or incomplete-tail repair according to the data type.
Shared storage operations clear errno at each new I/O boundary and use EIO when a failure supplies no cause. Sync preserves a supplied preexisting stream error and checks the stream again after flush. Failed path probes stop publication or recovery; close, discard and rollback cannot replace the captured first cause. Existing distinct same-filesystem paths, serialized writers and consumed temporary-stream ownership remain required.
CSV tail repair uses a 512-byte scratch buffer and scans backwards by blocks before truncating at the most recent LF. HTTP, MQTT and Ring health logging share this helper with serialized writers. An open/seek/size/read failure prevents truncation, including a successful byte/count accompanied by a stream error; later truncate/sync/close failures preserve the first error. Native checks verify retained bytes and reduced stdio call counts, while physical FAT/SD latency and power-loss durability remain device checks.
HTTP and MQTT metadata appenders capture fresh errors for preparation, file positioning, every write and close. A failed write or stream flag stops the remaining fields and explicit sync; cleanup keeps the first cause. Their worker ownership, error reporting and metadata schemas stay the same. Native byte/fault checks compile the actual appenders, formatters, metadata types and shared helper; real UI/SDK/socket suites cover the asynchronous callers separately.
Notes uses a temporary PSRAM buffer to load an entire file of up to 65,535 bytes and frees that buffer after LVGL copies the text. It applies the editor's character limit after loading, using LVGL's bulk text path. Recovery/open/read/close failures, oversized files and NUL data disable editing and Save. Fresh errno at each load boundary prevents a failed open from becoming an empty draft; read/probe errors survive close. The save callback rechecks ownership, SD availability and byte length, confirms directory creation and old-TMP removal before opening the writer, and discards rejected or stream-flagged writes without retrying publication. It preserves the first cause through cleanup and keeps the draft available for retry. The launcher leave hook clears its editor pointer and permission before content deletion. The existing replacement helper syncs/closes accepted NOTE.TMP writes, retains the previous complete generation in NOTE.BAK, and reports publication failures. Real LVGL checks use the unchanged 96 KiB pool; physical SD and panel behavior remain open.
Ride history shows totals only after recovery, reading and closing the summary succeed; only a confirmed missing index is valid empty history. Fresh errno at every I/O boundary prevents a failed open from becoming empty history or replacing an existing index. A flagged history line stops further reads before parsing. Storage errors display an unavailable message and update the shared SD error. Appending checks known SD availability, directory creation and temporary-file removal before copying. Failed read chunks and rejected or stream-flagged output stop before later writes or publication; the first cause survives source/destination close and temporary cleanup failures. The existing replacement helper owns sync, backup, publication and rollback. Stop keeps a successfully published ride authoritative when updating the index fails, reports that failure and reloads the original totals. Native tests verify exact file bytes and closed modeled handles; physical FAT/SD and panel behavior remain open.
Ride recording checks both directories and advances through its 100 same-second names only on CSV/TMP collisions. Other creation errors stop Start; a failed stream conversion closes its descriptor and attempts to remove the newly created empty TMP. Rejected headers/rows and periodic sync failures use one abort path that closes recording ownership, retains the TMP and reports the captured first cause. Tick and Stop also abort on known SD loss before another row or publication. Healthy Stop commits the CSV before rebuilding history, so an index failure leaves the published ride authoritative. Native tests compile the actual lifecycle and history helpers together with storage; UI, BLE data, time and SD boundaries are controlled.
BLE evidence copies its bounded shared cache under the existing lock, then writes that snapshot synchronously. Each directory, final-name probe and file-creation boundary uses a fresh error; only actual CSV/TMP collisions advance the ten-name search. Header and row failures capture the first cause and close the stream without invoking publication. The named TMP remains available for inspection, and cleanup/error reporting cannot replace that cause. Healthy saves use the unchanged storage helper and CSV schema. Native checks compile the callback, its format/status helpers and cache typedefs with modeled BLE/UUID/lock boundaries and real files; no worker or LVGL pool change is introduced.
Ring's saved heart-rate timestamp becomes loaded only after incomplete-tail repair, seek/size checks, the expected bounded tail read and close succeed. Read or close errors leave the checkpoint unchanged and retryable. Its existing health timer retains queued samples and defers history requests until that load succeeds, then filters timestamps against the recovered checkpoint. Known SD unavailability blocks queue processing even when a checkpoint is cached; timeout state still advances before the storage gate. Appends preserve the first error through close and tail repair. Native files and controlled queue/timer/SD boundaries verify this behavior; physical SD, BLE and scheduling remain open.
Ebooks reads up to 8 KiB into its existing cached PSRAM buffer. Page text, current/next byte positions, navigation state and scroll position change only after successful seek, read and close. Failure preserves the confirmed page and positions, reports the first error and allows retry when storage is available. Reader initialization clears both offsets and disables navigation until loading succeeds. Callbacks recheck disabled state and screen pointers; negative positions and additions beyond the platform's long range are rejected. Native files and controlled UI/heap/SD boundaries verify the transitions; physical SD and panel behavior remain open.
The Scope/I2C capture and saved serial CSV viewers require a successful close before constructing their graph or row-copy controls. A read or close failure after format recognition shows an error and releases the viewer buffers immediately. Empty recognized captures also release their row buffer. Real LVGL checks cover error/reopen transitions and stable memory in the unchanged 96 KiB pool; filesystem faults and PSRAM allocation are controlled host boundaries, with physical SD and panel checks still open.
Files dispatches supported CSVs to those viewers before its text fallback. The fallback reads at most 4,095 bytes into its existing static buffer and publishes them only after checking the stream and closing successfully. Failed open/read/close operations display the first error; missing errno becomes EIO. Error reporting to the shared SD callback is scoped to the exact /sdcard/ prefix. Native files and controlled UI/probe/path boundaries check this callback; physical storage and panel behavior remain open.
Files directory browsing resets errno before each directory read so per-entry stat errors cannot masquerade as an enumeration error at EOF. A failed read or close keeps the visited entries visible with an explicit incomplete-list warning, preserving the first error. Entry capacity and skipped long paths are disclosed. The existing 64 path slots include a parent-navigation slot when needed, and each path remains bounded to 256 bytes. SD root and descendant errors reach the shared callback. Native directory/file checks verify these transitions and callback path ownership; UI, mount paths and error injection are controlled.
The ebook library verifies regular-file metadata before offering a .txt entry, excluding matching directories and unconfirmed paths. Directory setup/open failures show their cause; read/stat/close failures keep confirmed rows visible with an incomplete-list warning. A newly created directory writes its welcome file with fresh errno and stream checks, preserving the first failure through cleanup. Failed welcome creation shows its cause without an empty-library hint; confirmed entries remain available. Healthy creation uses the existing new-file commit helper, while an existing directory never rewrites the welcome file. Failed or skipped listings cannot claim the library is empty. The existing 64-entry and 256-byte path bounds are disclosed. Native checks cover directory listing and welcome bytes with controlled UI, timer, SD, download and fault boundaries; physical storage remains a separate check.
Default ebook downloads require a regular file larger than 1,024 bytes before treating a default as installed. A directory reports EISDIR; other nonregular metadata reports EINVAL. Both stop before touching the TMP or creating a writer/HTTP client, preserving the conflicting path for the owner to resolve. Downloads keep the file pointer and first write error in a per-download stack context passed to the HTTP callback. The pinned SDK body parser ignores callback return codes, so this context stops later file writes and gates publication independently of the client result. Fresh errno at probe/preparation/write/close boundaries prevents stale causes; close/removal cannot overwrite a captured write error. Confirmed 2xx downloads still use the existing new-file commit helper. Callback state lives until SDK cleanup finishes, then file ownership is closed or consumed by storage. Native file tests control HTTP events and faults; the unflashed build checks the actual SDK interface. Separate worker tests cover admission and completion with controlled scheduling boundaries. Real scheduling, Wi-Fi/TLS, physical SD and panel behavior remain separate checks.
The default-book worker has a 30-second initial Wi-Fi wait with 500 ms polling and wrap-safe tick subtraction. Known SD loss ends that wait; both Wi-Fi and SD availability are checked before each book attempt. Atomic busy admission prevents duplicate workers, and busy is checked before metadata probes. Completion is published before busy is released; the UI timer consumes it only after release. Failed task creation also publishes completion. The worker uses the existing 10,240-byte PSRAM stack and vTaskDeleteWithCaps, which releases the separately allocated stack/TCB through the SDK's cleanup path. It requests no output task handle, avoiding a stale write if it completes before the creator returns. HTTP transfer completion precedes ownership release; its SDK I/O timeout remains unchanged. Clock/task/timer adapters check this lifecycle; physical scheduler and memory-recovery evidence remains open.
Scope captures the first rejected header/row write before cleanup and closes an invalid temporary stream without attempting publication. I2C uses a shared finish path for explicit Stop and error cleanup: known row/flush/sync/SD failures close and release recording ownership, preserve the first cause, and report once; only a healthy Stop attempts publication. This prevents a successful retry during cleanup from turning an earlier failure into a Saved result. Native bytes and closed handles are checked with controlled UI/data/time/fault boundaries; sampling, drivers and physical SD behavior remain separate gates.
Scope and I2C constructors clear errno before each directory/metadata/open/stream boundary and stop on a non-collision failure. Scope closes the descriptor, attempts to remove its new empty TMP and reports a failed stream conversion without another filename attempt. I2C retains that empty file, and clears capture paths/ownership after a rejected header. Its watch flag is enabled only after an accepted header. Native constructor checks verify both 100-name bounds, retained-TMP restart, first errors and closed handles; existing path buffers, sample data, CSV formats and watch timing remain unchanged.
See Storage and compatibility contract and Data formats.
main/ipv4_data.c provides the strict decimal IPv4 parser shared by Subnet Lab, Wake-on-LAN, NTP Lab, Modbus TCP, and UDP Console. Its numeric result has the most-significant octet first; socket callers convert with htonl at the boundary. Address syntax is independent of platform socket parsers, while each app applies its own unicast/broadcast policy. Input fields retain invalid characters and line breaks for validation, with bounded UTF-8 drafts and enough space to detect excess length.
The ESP32-P4 uses the onboard ESP32-C6 over the Tab5 fixed four-bit SDIO bus for Wi-Fi and BLE. Offline startup is valid; wired tools and the launcher do not depend on cloud availability. HTTP, MQTT, browser, weather, relay, and OTA traffic use bounded buffers and I/O timeouts. TLS certificate bundles verify secure transports. Plain HTTP/MQTT requires an unchanged second tap within five seconds.
HTTP Console validates response framing with the SDK parser in its transport guard and checks completion before reporting success. Short Content-Length bodies, invalid/unfinished chunks and interrupted trailers are shown as incomplete. Close-delimited bodies require an actual FIN; a transport timeout cannot act as EOF. Bodyless 204/304 responses follow the no-body rule even when a 304 includes a representation Content-Length, which the pinned client otherwise interprets as missing body bytes. Its 640 x 300 keyboard appears only while editing and temporarily replaces the response preview; Done/Cancel closes it without sending. The worker owns an immutable request snapshot while the UI may be edited or closed. A separate label displays that snapshot's method and query/fragment-free endpoint when SEND is accepted and when the result is consumed, including after Home/re-entry. Accepting a new SEND clears the earlier preview; validation/confirmation alone does not. A locked handoff transfers each completed job once: the UI timer displays it, idle CLEAR discards it while reporting any storage failure, and SEND displays a waiting result before accepting another request. CLEAR during active work clears only the preview and retains worker ownership.
HTTP Console injects http_transport.c through ESP-IDF's supported custom transport option. It delegates TCP/TLS to the SDK, attaches the same certificate bundle explicitly, polls nonblocking I/O in at most 100 ms slices, and checks a 15-second request budget before and after transport calls. Cancel/Home only sets a locked flag; the worker closes and destroys the client/transport, finishes any opted-in metadata write, publishes a bounded result and deletes its task. OTA remains blocked through connection/storage cleanup. Hostnames resolve through nonblocking lwIP core callbacks with Cancel/deadline checks while waiting. Numeric peers go to ESP-TLS while the original hostname remains its verification/SNI name and the HTTP Host. A locked active lookup and nonzero IDs isolate late replies and queued work without retaining request pointers; the platform DNS backend may continue after detachment. The DNS wait shares the connect timeout and request budget. Physical DNS/TLS latency remains a gate. The same 8 KiB PSRAM buffer validates each response header block and trailer section. Informational responses are consumed before delivering the final response; 101 upgrades are rejected. Trailers are validated and discarded, exposing an empty trailer section to the client. This avoids the pinned SDK's unreleased partial-header value and last trailer value, including on successful exchanges. Validated final headers finish delivery before cancellation is observed; no new socket traffic is sent. See HTTP Console.
HTTP metadata logging is captured in the request snapshot and uses storage_repair_csv_tail plus flush/sync/close before reporting an appended row. Every result shows logging status independently of its network outcome. Storage failures turn logging off for the screen and reach the storage callback on the UI thread when the result is consumed. SEND also consumes a pending storage error before launching another worker, preventing that worker from inheriting a failed log choice. The worker retains busy ownership through file closure even after Home detaches the UI.
MQTT uses one preallocated worker for Subscribe, Publish enqueue, all metadata filesystem calls and cleanup. An accepted action owns a bounded copy of its topic/payload/QoS/retain settings; the SDK network write/API lock runs off the UI thread. Worker selection of pending actions and cleanup is atomic, so coalesced notifications cannot destroy a client while an action still owns it. Publish enforces 512 UTF-8 bytes and requests outbox storage to support QoS 0's zero success ID. Metadata capture has a separate eight-entry queue, sampled at completed receive/successful publish acceptance; it contains no payload and shares one PSRAM allocation with the UI's message ring. Overflow counts remain visible, and the first storage error disables further logging and reports once. The UI timer drains history and refreshes controls. SDK actions and shutdown precede the next queued write; an in-flight filesystem call remains cooperative. Disconnect/Home hands off the client and captures the final-event log choice; busy ownership remains active through SDK action completion, stop/destroy, SD close, buffer release and the thread-safe storage-error callback. A stopped screen can detach immediately and re-open while the worker is still busy. Stale actions and delayed connection events cannot re-enable work during cleanup. Idle state and completion status publish together after cleanup finishes; a fresh Connect restores buffers and clears pending old status before starting its client. See MQTT lifecycle.
MQTT broker validation uses the same URI parser as the pinned SDK, rejects unsupported/ambiguous endpoint components and checks bracketed IPv6 literals with the platform address parser. Connect and Save Profile enforce 255-byte endpoints and 64-byte UTF-8 credentials before SDK/NVS calls; fields retain the first excess character for rejection. The accepted transport controls certificate-bundle attachment and connected status. Fatal SDK errors disable new actions, retain their diagnosis through disconnect/acknowledgment callbacks and remain visible alongside storage/drop warnings; session cleanup retains ownership. Subscription refusal is handled in the SDK's subscription acknowledgment event.
MQTT transport diagnostics use a fixed 192-byte status buffer, include each nonzero ESP/TLS-stack/certificate/socket code and bound the error-name/socket text. A zero ESP code is omitted; an entirely empty SDK report is identified explicitly. TLS magnitude uses unsigned arithmetic so INT_MIN retains its sign without signed overflow. Formatting runs inside the SDK event callback before copying the result under the existing app lock; it allocates no additional memory and does not touch LVGL.
MQTT cleartext confirmation is disarmed by focus and URI/username/password value-change events, including changes restored to their original value. The second tap checks the still-armed five-second window using unsigned elapsed time. This replaces the collision-prone 32-bit settings fingerprint and adds no retained credential copy. The pinned LVGL emits value changes from text replacement, insertion and deletion; the native checks run those actual widget paths.
MQTT's version-1 NVS profile keeps its 515-byte layout. Load checks length before and after the bounded read; malformed values remain available for explicit deletion or replacement. Read errors and attempted writes conservatively enable deletion when key presence is uncertain, while confirmed absence disables it. Save/delete failures report unconfirmed persistence and retain the form because NVS may already have applied the operation. Confirmation ownership resets when another profile/connection action takes over. Profile callbacks check actual disabled widget state as well as running/stopping ownership, protecting saved credentials from stale clicks after worker cleanup but before UI refresh. Profile NVS calls remain synchronous; native RAM-store checks do not verify device flash latency or reboot recovery.
MQTT receive assembly recognizes the first event by its topic, including a header-only event followed by data at offset zero. Continuations must be contiguous and retain the declared size, message ID, QoS and retain flag; only a complete assembly enters history and the metadata queue. Disconnect/error events discard unfinished assembly, while a final fragment during Home may complete before SDK shutdown joins. Received payloads have a 512-byte ASCII view, and topics have a 127-byte printable ASCII view. Changed or shortened topics are labeled as previews and use the existing preview_truncated metadata outcome; control bytes cannot create additional CSV lines.
MQTT keeps up to 128 raw incoming bytes inside the existing PSRAM ring allocation, separately from ASCII previews. Complete assembly publishes one locked receive snapshot with its captured topic/QoS/retain/length; partial or rejected messages cannot replace it. Only the UI COPY RX handler stores an immutable snapshot in the shared clipboard. Oversized receives disable copy; Home retains the last complete receive independently of SDK buffers, and a new session or Clear resets it. Clear also discards queued UI history without dropping accepted metadata. The copy control adds no worker, SDK call or filesystem operation.
MQTT's UI PASTE BYTES handler copies the 0-128-byte shared clipboard into a separate retained RAM draft. Text and Bytes select the publish body explicitly; the hidden text widget preserves its original draft. The worker owns a complete captured action, never a pointer into the clipboard or UI draft. Enqueue receives exact binary bytes and length; after the SDK copies them, only the history body is converted to its labeled ASCII view. Home detaches widgets without dropping the prepared bytes. Paste/mode actions add no SDK or filesystem call and cannot change an accepted action.
MQTT action completion compares the event-status revision captured before its SDK call under the existing app lock. A fast acknowledgment, subscription rejection, receive or disconnect event can run after the API unlocks and before it returns; that newer status must survive the worker's queue/request result. The comparison adds no task or retained message copy.
Native MQTT app/packet checks compile the pinned SDK's unmodified 3.1.1 codec. Independent fixtures check captured-action encoding and decoding before modeled event dispatch, including a topic-filled first buffer with zero payload bytes. The client/broker suite runs the unmodified SDK client, receive/dispatch loop, outbox and transport dispatch against loopback TCP sockets. Sixteen standalone cases verify protocol exchanges; thirty-one additional cases include the actual console, LVGL and CSV helper. Real early-ACK controls reproduce the worker status race, and Home cases verify completed/abandoned receive metadata while the client is blocked. Nine storage cases combine continued broker traffic with held native file calls, overflow, one-shot boundary errors, pending-action cancellation and reconnect recovery. Callback waits retain ownership through Home and shutdown buffer release. Separate 25-session soaks recover tracked resources and native handles. OS/event services are adapted and explicit joins precede client destruction, strengthening the native task lifetime; device scheduling, TLS/WSS, flash NVS and Wi-Fi/SD behavior remain unverified.
Settings opens a blank, masked Wi-Fi password field with an explicit Show/Hide button. Toggling changes only visibility and preserves the current edit; reopening the form resets to masked input without loading the stored credential. Its 640 x 320 keyboard keeps the connection form visible on the portrait screen.
Chat and transcription send authenticated HTTPS requests to a separately deployed relay, which holds the upstream OpenAI API key. The tablet stores only a revocable device token. Generic BLE scanning and connection are explicit; product profiles are off at boot.
sdkconfig.defaults, partitions.csv, main/idf_component.yml, and dependencies.lock are release inputs. Compile-time guards reject the wrong ESP-Hosted SDIO pins or a hardware-UART console. Push/PR CI runs host checks and a clean ESP-IDF build. Stable tags additionally create app-only and 16 MiB factory images, an OTA manifest, checksums, and license/dependency notices. See OTA manifest contract.