Restructure project documentation

Separate operator guides from development contracts, relocate
implementation notes under `docs/devel/`, and update cross-references
and
hardware tooling paths. Add getting-started, user administration, and
firmware update guides while preserving firmware behavior.
This commit is contained in:
2026-09-21 15:24:42 +02:00
parent 655f26a611
commit ab6a8010ba
43 changed files with 959 additions and 364 deletions
+26 -22
View File
@@ -1,10 +1,14 @@
# Command reference
UART0 and authenticated `admin` SSH sessions use the same registered command implementations through one serialized dispatcher. Admin SSH exposes the full operational registry, including interactive prompts, network diagnostics, reboot, and HTTPS/SSH material mutation. Create the first administrator through normal `user add <username> admin` on physical UART0; explicit recovery of an unavailable database is UART0-only. Admin SSH also rejects generating a replacement password for its own account so the one-time value cannot be lost when the session is revoked. Run `help` for root commands and `<group> help` for a group summary. Configuration changes are RAM-only unless explicitly saved.
UART0 and authenticated `admin` SSH sessions use the same registered command implementations through one serialized dispatcher. Admin SSH exposes the full operational registry, including interactive prompts, network diagnostics, reboot, and HTTPS/SSH material mutation. Create the first administrator through normal `user add <username> admin` on physical UART0; explicit recovery of an unavailable database is UART0-only. Admin SSH also rejects generating a replacement password for its own account so the one-time value cannot be lost when the session is revoked. Run `help` for root commands and `<group> help` for a group summary. For serial, Wi-Fi, mDNS, and display settings, use the documented save/reset commands to persist working changes. Do not assume account or certificate/key changes are RAM-only.
Browser admin uses the same dispatcher with a [narrower parsed frontend policy](web_administration.md#browser-shell-policy), independent of typed Settings permissions. It supports bounded deferred `reboot`, `web stop`, exact `web certificate rotate --force` and owner-relative `exit`. Drain (up to ten seconds plus 200 ms) is best-effort application-buffer acknowledgement, not peer receipt or an execution deadline; pending input is discarded. Certificate work runs on the existing dispatcher through the shared service-before-identity reservation, commits before stop/restart and never rolls back a committed identity after lifecycle failure. Failed stop retains ownership and skips start. Verify changed trust through UART0 `web certificate info`, recover with UART0/admin SSH `web stop` / `web start`, then sign in freshly. HTTPS-only actions leave SSH/native USB/UART0 independent; reboot affects every transport and loses unsaved RAM.
## Choosing an administration connection
Browser `web` allows only status/stop/exact forced certificate rotation; `mdns` allows status only. Browser `wifi` supports the canonical commands, including bare `wifi` status, settings, hidden secret prompts and explicit `wifi ap show-secret`. Secret input is not echoed, retained in history or completed. Wi-Fi changes can disconnect the browser before a result arrives, without deferred drain or cancellation; reconnect and inspect before retrying, or recover through UART0. Browser `user` allows status/list/show and interactive add/password plus forced role/delete for **other accounts**, not self/generated/key/recovery commands. Restricted SSH stop/disconnect/reset/host-key mutation remains unavailable in the browser shell. Typed Accounts/Network/SSH settings separately provide their documented bounded workflows; this is not shell parity. See [web administration](web_administration.md) for lifecycle/API ownership and uncertainty.
Keep physical UART0 available for recovery when changing network access, accounts, or service identities. Browser Admin uses a restricted command set, separate from the permissions of the Settings dialogs. It supports `reboot`, `web stop`, exact `web certificate rotate --force`, and `exit`. These actions briefly wait for queued output before disconnecting; this is not proof that your browser received the result, and pending input is discarded.
Certificate replacement is saved before HTTPS restarts. A restart failure does not restore the old identity. Verify the fingerprint through UART0 `web certificate info`; recover with UART0/admin SSH `web stop` / `web start`, then trust the new certificate and sign in again. HTTPS-only actions leave SSH, native USB, and UART0 independent. Reboot affects every transport and loses unsaved RAM settings.
Browser `web` allows only status/stop/exact forced certificate rotation; `mdns` allows status only. Browser `wifi` supports the canonical commands, including bare `wifi` status, settings, hidden secret prompts and explicit `wifi ap show-secret`. Secret input is not echoed, retained in history or completed. Wi-Fi changes can disconnect the browser before a result arrives, without deferred drain or cancellation; reconnect and inspect before retrying, or recover through UART0. Browser `user` allows status/list/show and interactive add/password plus forced role/delete for **other accounts**, not self/generated/key/recovery commands. Restricted SSH stop/disconnect/reset/host-key mutation remains unavailable in the browser shell. The Accounts, Network, and SSH Settings dialogs offer additional actions; an action available in Settings is not necessarily available in the browser shell. See the [browser user guide](web_administration.md).
## System
@@ -14,7 +18,7 @@ Browser `web` allows only status/stop/exact forced certificate rotation; `mdns`
| `reboot` | Drain console output briefly and restart the ESP32; refused while a firmware upload or another reboot owns exclusion. |
| `exit` | Close the current administrative SSH or browser session after its acknowledgement drains; unavailable on UART0. Browser `exit` leaves serial connected. Ctrl+D on an empty administrative command line does the same. |
Firmware upload is an admin **Settings → HTTPS / Reboot** action, not a shell command. Ordinary UART0/admin-SSH/browser-shell/local-display and typed reboot paths are gated against an active upload. If upload boot selection succeeded but its response failed, no automatic restart is scheduled: the selected image remains, another admissible upload returns 409 until reset, and a deliberate manual `reboot` is permitted after reservations release. Inspect first; a lost response is not cancellation. See [firmware update and wired recovery](roadmap.md#phase10-update-and-recovery). Do not erase for an ordinary update; native USB is UART1 access, not recovery administration.
Firmware upload is an admin **Settings → HTTPS / Reboot** action, not a shell command. Reboot requests are refused while an upload is active. If the new image was selected for boot but the upload response failed, no automatic restart is scheduled. The selected image remains, and further upload attempts return 409 until reset; a deliberate manual `reboot` is permitted once the upload has released the service. Inspect first; a lost response is not cancellation. See the [browser user guide](web_administration.md) for firmware upload and recovery. Do not erase for an ordinary update; native USB is UART1 access, not recovery administration.
## Role-based users
@@ -34,9 +38,9 @@ Firmware upload is an admin **Settings → HTTPS / Reboot** action, not a shell
| `user key clear <username> --force` | Delete all public keys for an account. |
| `user recover --force` | When normal user-database initialization failed, explicitly replace only its blob with an empty database; UART0-only, refuses a healthy database. |
Usernames must match `[a-z][a-z0-9_-]{0,15}`. Passwords contain 12–64 printable ASCII characters. The fixed database supports eight users and three SSH keys per user; initial key types are `ssh-ed25519` and `ecdsa-sha2-nistp256`. A key may be assigned to multiple accounts but cannot be duplicated within one account. Password verifiers, salts, raw key blobs, and passwords are absent from ordinary status output. `Ctrl-C` cancels a password or key prompt, and generated passwords are shown once.
Usernames must match `[a-z][a-z0-9_-]{0,15}`. Passwords contain 12–64 printable ASCII characters. The fixed database supports eight users and three SSH keys per user; supported key types are `ssh-ed25519` and `ecdsa-sha2-nistp256`. A key may be assigned to multiple accounts but cannot be duplicated within one account. Password verifiers, salts, raw key blobs, and passwords are absent from ordinary status output. `Ctrl-C` cancels a password or key prompt, and generated passwords are shown once.
Missing `user_db/database` storage is committed empty. On UART0 run `user add <username> admin`, optionally with `--generate`, to create the first administrator. There is no bootstrap command, imported shared credential, or synchronization with HTTPS material. Existing valid v1 user databases load unchanged, including previously migrated role-`user` accounts; no account is silently promoted.
A device with no stored user database starts with an empty database. On UART0 run `user add <username> admin`, optionally with `--generate`, to create the first administrator. There is no default shared login. Existing valid user databases retain their accounts and roles; no account is silently promoted.
`user recover --force` is destructive and succeeds only while the database is unavailable. It rebuilds only the user blob empty, importing no credentials; then create an administrator with normal `user add` on UART0. It refuses a healthy initialized database, including a healthy empty one, and does not erase unrelated NVS data. Successful password, role, key and delete operations invalidate only that username's outstanding tickets and active web/SSH sessions; unrelated users remain connected.
@@ -50,9 +54,9 @@ Missing `user_db/database` storage is committed empty. On UART0 run `user add <u
| `display save` / `display load` | Save the working aging settings to NVS or load them. |
| `display defaults` / `display reset` | Apply 300/600-second defaults in RAM, or save defaults first and then apply them. |
When both transitions are enabled, `off-seconds` must be greater than `dim-seconds`. Applying settings counts as local UI activity. At normal boot, an initialized OLED shows a bounded five-second identity animation before the status UI begins; it scrolls the device name in yellow and draws the compact upright-terminal logo in blue. A missing OLED remains nonfatal; after reconnecting it safely, one new button press requests a bounded reprobe and is consumed without navigating.
When both transitions are enabled, `off-seconds` must be greater than `dim-seconds`. Applying settings counts as local UI activity. At normal boot, an initialized OLED shows a bounded five-second identity animation before the status UI begins; it scrolls the device name in yellow and draws the compact upright-terminal logo in blue. A missing OLED remains nonfatal. With the local UI task running and the I²C bus initialized, one new button press requests a bounded reprobe and is consumed without navigating. If `debug display status` reports `bus=unavailable`, correct the wiring with power off and reboot instead.
Display settings require an available local UI task, not an attached panel. CLI and browser **Settings → Display** share the public configuration owner; concurrent mutations can report busy, and browser operations reject an intervening configuration edit rather than overwrite it. Save persists working RAM, not browser drafts. Load selects defaults when saved storage is absent/incompatible without rewriting NVS. Reset storage failure leaves RAM unchanged (commit-before-publication, no RAM rollback). Browser timeout/navigation does not cancel already-admitted work; Check Result and Refresh before retrying. [Display settings contract](web_administration.md#serial-and-display).
Display settings require a running local UI task, not an attached panel. Concurrent console/browser changes can report busy or reject an outdated browser edit. Save persists the device's working settings, not unsent browser drafts. Load selects defaults when saved storage is absent or incompatible without rewriting it. If Reset cannot save defaults, the working settings remain unchanged. A browser timeout or navigation does not cancel an accepted action; use **Check Result** and **Refresh** before retrying.
## Serial service
@@ -80,7 +84,7 @@ Defaults are 115200 baud, 8 data bits, no parity, one stop bit, no flow control,
| `broker read <client-id> [maximum-bytes]` | Read queued serial output for a client. |
| `broker events <client-id>` | Show ownership and connection events for a client. |
Each client has a generation-safe ID. There can be one writer and multiple observers; a slow observer loses only its own queued output. HWM is bounded by 4,096 bytes; read counts transport handoff, not peer receipt. Disconnected client rows disappear, while global totals retain traffic and unread-output discards until cleared. Capture counters before disconnect. `broker read` consumes queued data and must not be used as a throughput diagnostic probe. See [Web throughput diagnostics](web_throughput_diagnostics.md).
Each client has a generation-safe ID. There can be one writer and multiple observers; a slow observer loses only its own queued output. HWM is bounded by 4,096 bytes; read counts transport handoff, not peer receipt. Disconnected client rows disappear, while global totals retain traffic and unread-output discards until cleared. Capture counters before disconnect. `broker read` consumes queued data and must not be used as a throughput diagnostic probe. See [Web throughput diagnostics](devel/web_throughput_diagnostics.md).
## Native USB CDC-ACM
@@ -134,11 +138,9 @@ Link-local **probe destinations** require a scope on the ESP32: `%sta`, `%ap`, a
`nslookup` without a selector performs separate A and AAAA queries. The installed resolver returns one selected address per family (`CONFIG_LWIP_DNS_MAX_HOST_IP=1`), not a complete DNS RRset. Missing records, DNS timeouts and server failures can share the same SDK error; partial success is displayed rather than hidden. Unicast names require a usable configured DNS server; SLAAC alone does not provision one in this firmware. `.local` uses the existing lwIP mDNS query support. No DNS server or RA/DHCP policy is changed by these commands.
Traceroute sends one eight-byte echo probe per hop with a one-second absolute receive deadline; it can stop up to 999 microseconds early to avoid the SDK's zero-millisecond/infinite-wait conversion. Hop addresses are numeric. ICMPv6 replies are checked against the actual IDF raw framing/checksum, destination and probe identity; IPv6 extension headers/fragments in replies or quotes are unsupported and ignored. Filtering/rate limiting can produce `*` even when ordinary connectivity works. DNS, scheduler and send latency are outside the receive deadline.
Traceroute sends one eight-byte echo probe per hop and waits up to approximately one second for a reply. Hop addresses are numeric. IPv6 extension headers/fragments in replies or quoted packets are unsupported and ignored. Filtering/rate limiting can produce `*` even when ordinary connectivity works. DNS, scheduler and send latency are outside the receive deadline.
Ping retains the SDK's bounded-count transient task and existing PSRAM event queue. Its console wait is bounded to `count * 2 + 2` seconds after start, but unrelated ICMP can extend the SDK receive loop: after a console timeout, further pings are refused until the old END event arrives. END finishes callback production; SDK resource teardown may briefly overlap a subsequent session. No IPv6 TTL is printed because the installed SDK does not provide a valid received IPv6 hop-limit profile. These commands serialize the administration dispatcher while executing; they do not run on HTTPD or change the broker/serial path.
Focused host/SDK and surface tests: `tests/network_diagnostics/` and `tests/network_diagnostics_surfaces/`. Real IPv4/IPv6/scoped ping, DNS and multi-hop traceroute remain device-validation tasks for this follow-up.
Ping waits up to `count * 2 + 2` seconds in the console after starting. After a console timeout, further pings may be refused until the previous probe session finishes. IPv6 replies do not include a received TTL/hop-limit value in the output. Network diagnostics occupy the shared administration dispatcher while executing, so other administrative commands wait; serial traffic uses a separate path.
## mDNS
@@ -151,7 +153,7 @@ Focused host/SDK and surface tests: `tests/network_diagnostics/` and `tests/netw
When the Wi-Fi station has IPv4 or a preferred IPv6 address, the Wi-Fi manager announces `sak-<suffix>.local` and advertises available HTTPS/SSH services through DNS-SD. This is STA-only, local-link discovery, not certificate or host-key trust. The default suffix is the lower-case hexadecimal STA MAC address. Suffixes may contain lowercase ASCII letters, digits, and internal hyphens only. Changing a suffix queues a best-effort reannouncement and updates the DHCPv4 hostname (`sak-<suffix>`, without `.local`) for subsequent DHCP exchanges; it does not force a lease restart. A configured DHCP/DNS server may publish that name in its own zone.
`wifi status` and browser Network status distinguish IPv4 absence and IPv6 link-local/ULA/GUA availability. `wifi` / `wifi status` and the web Network settings dialog additionally list up to three actual preferred IPv6 addresses, labelled link-local, ULA or GUA; these are copied with the flags, not inferred from enabled IPv6 support. A link-local destination needs the client's interface as its zone. To separate client address preference from IPv6 reachability, run `ping -6 -c 3 sak-1024.local` on an IPv6-capable client; choosing IPv4 with plain `ping` does not mean the device lacks IPv6. If lookup fails, inspect AAAA resolution (for example `avahi-resolve-host-name -6 sak-1024.local` where Avahi is installed), then test the numeric address from `wifi status` directly. Link-local-only connectivity counts as `ONLINE` and can retire the fallback AP after the existing stability interval; it does not establish Internet access. HTTPS/WebSocket and SSH support both families; browser IPv6 literals require brackets and cannot contain interface zones. Use the `.local` hostname for link-local browser access where supported by the client. On-device diagnostics support explicit family selection and scoped IPv6 as described above; Linux client syntax and interface scope are separate. mDNS failures do not stop Wi-Fi, UART0, UART1, or native USB access. See [Phase 12](roadmap.md#phase-12--advanced-network-integration) for discovery convergence limits and recorded user acceptance.
`wifi status` and browser Network status distinguish IPv4 absence and IPv6 link-local/ULA/GUA availability. `wifi` / `wifi status` and the web Network settings dialog additionally list up to three actual preferred IPv6 addresses, labelled link-local, ULA or GUA; these are copied with the flags, not inferred from enabled IPv6 support. A link-local destination needs the client's interface as its zone. To separate client address preference from IPv6 reachability, run `ping -6 -c 3 sak-1024.local` on an IPv6-capable client; choosing IPv4 with plain `ping` does not mean the device lacks IPv6. If lookup fails, inspect AAAA resolution (for example `avahi-resolve-host-name -6 sak-1024.local` where Avahi is installed), then test the numeric address from `wifi status` directly. Link-local-only connectivity counts as `ONLINE` and can retire the fallback AP after the existing stability interval; it does not establish Internet access. HTTPS/WebSocket and SSH support both families; browser IPv6 literals require brackets and cannot contain interface zones. Use the `.local` hostname for link-local browser access where supported by the client. On-device diagnostics support explicit family selection and scoped IPv6 as described above; Linux client syntax and interface scope are separate. mDNS failures do not stop Wi-Fi, UART0, UART1, or native USB access. Discovery is best-effort and may not update immediately after network or service changes.
## HTTPS web terminal
@@ -167,10 +169,14 @@ When the Wi-Fi station has IPv4 or a preferred IPv6 address, the Wi-Fi manager a
| `web certificate rotate --force` | Replace the HTTPS certificate and private key. Browser admin defers commit and HTTPS restart; both browser routes close and new certificate trust/relogin is required. UART0/admin SSH behavior is unchanged. |
| `web reset --force` | Explicitly replace HTTPS certificate/private key only, including missing, incompatible or damaged material; never changes users. |
Use UART0 for quiet throughput captures (authenticated admin SSH also exposes these commands). Performance output includes current-epoch pending age and count/sum/estimated-average/max timings for queue entry to callback entry, synchronous send, and completion to drain return. These are not peer acknowledgements or scheduler-only measurements; nonempty intervals may include idle. No secrets or serial payloads are printed. See [Web throughput diagnostics](web_throughput_diagnostics.md) for exact semantics, saturation limits and the reset/burst/drain/disable/capture-before-disconnect recipe.
Use UART0 for quiet throughput captures (authenticated admin SSH also exposes these commands). Performance output includes current-epoch pending age and count/sum/estimated-average/max timings for queue entry to callback entry, synchronous send, and completion to drain return. These are not peer acknowledgements or scheduler-only measurements; nonempty intervals may include idle. No secrets or serial payloads are printed. See [Web throughput diagnostics](devel/web_throughput_diagnostics.md) for exact semantics, saturation limits and the reset/burst/drain/disable/capture-before-disconnect recipe.
HTTPS listens on port 443 only. Authenticate with any current user-database username/password; both `user` and `admin` roles receive the existing status and browser-terminal interface. The device serves vendored xterm.js without Internet access. Browser sessions use one-time account-bound tickets, binary WebSocket frames, and the broker's one-writer rule. The combined **Connect**/**Disconnect** control closes the current WebSocket and pauses automatic reconnect when active; after a user-paused disconnect, it changes to **Connect** to resume connection attempts. Account mutations revoke only that account's tickets and sessions.
### Older firmware compatibility
HTTPS storage upgrades preserve the certificate identity, but older firmware that understands only the v1 storage format cannot read the newer v2 format. Logical replacement of stored credentials or keys is not secure flash erasure. A factory erase is not required for the storage upgrade. See [storage compatibility notes](devel/roadmap.md#phase8-legacy-credential-compatibility) before downgrading firmware.
## SSH serial transport
| Command | Description |
@@ -185,13 +191,13 @@ HTTPS listens on port 443 only. Authenticate with any current user-database user
| `ssh host-key rotate --force` | Replace the persistent SSH host key. |
| `ssh reset --force` | Explicitly replace invalid or missing SSH material. |
SSH listens on port 22 and accepts user-database passwords plus stored `ssh-ed25519` and `ecdsa-sha2-nistp256` public keys. wolfSSH verifies key possession after the database authorizes the username/key pair; unsigned key probes do not complete authentication. A `user` receives the broker-backed UART1 serial stream. An `admin` receives the administration shell instead, does not become a broker client, and cannot acquire a UART1 writer lease.
SSH listens on port 22 and accepts user-database passwords plus stored `ssh-ed25519` and `ecdsa-sha2-nistp256` public keys. wolfSSH verifies key possession after the database authorizes the username/key pair; unsigned key probes do not complete authentication. A `user` receives the broker-backed UART1 serial stream and automatically requests writer ownership on connection. If denied, the session remains an observer and its input is discarded. Releasing another writer does not automatically promote it: deliberately assign control through administration or reconnect when the lease is free. An `admin` receives the administration shell instead, does not become a broker client, and cannot acquire a UART1 writer lease.
UART0 and admin SSH submit to one bounded queue, and one dispatcher task is the sole caller of `esp_console_run()`. Consequently, SSH commands execute the canonical UART0 handlers and produce the same status and mutation behavior rather than using a second command implementation. Remote output is routed into the authenticated session's bounded output ring; only the SSH transport task accesses wolfSSH.
UART0, admin SSH, and browser Admin share one command dispatcher. A long-running command or interactive prompt can therefore delay commands from another administrator.
UART0 and admin SSH use shared whole-line Tab completion. A unique/common prefix expands inline; a Tab that cannot extend an ambiguous prefix prints the matching candidates and redraws the unchanged input line instead of cycling candidates. Admin SSH additionally supports four-entry per-session command history with Up/Down, inline cursor editing with Left/Right, Home/End (including Pos1/Ende terminal sequences), Backspace/Delete, Ctrl-C, and visible or no-echo interactive prompts. Its history is RAM-only, private to the session, and wiped on disconnect. Ping callbacks enqueue bounded typed results so all formatting remains on the dispatcher task.
UART0 and admin SSH use shared whole-line Tab completion. A unique/common prefix expands inline; a Tab that cannot extend an ambiguous prefix prints the matching candidates and redraws the unchanged input line instead of cycling candidates. Admin SSH additionally supports four-entry per-session command history with Up/Down, inline cursor editing with Left/Right, Home/End (including Pos1/Ende terminal sequences), Backspace/Delete, Ctrl-C, and visible or no-echo interactive prompts. Its history is RAM-only, private to the session, and wiped on disconnect.
`exit`, `reboot`, `ssh stop`, session disconnect, and SSH host-key reset/rotation use bounded deferred control. The firmware waits on a best-effort basis for the administration output ring and transport TX buffer to drain before acting; this is not confirmation that the peer received the acknowledgement. The shell stops accepting another command while such an action is pending. SSH host-key replacement or service stop closes all SSH sessions; reconnect and verify the new fingerprint where applicable. TLS certificate management, Wi-Fi secrets, and interactive user passwords/keys are available to authenticated administrators and must therefore be treated as remotely accessible administrative material. `user recover --force` remains UART0-only; `user bootstrap` and all `web credentials` commands are removed. A connected administrator also cannot generate its own replacement password remotely, preventing the one-time password from being lost during self-revocation. SSH does not provide `exec`, SFTP, SCP, forwarding, or subsystems.
`exit`, `reboot`, `ssh stop`, session disconnect, and SSH host-key reset/rotation use bounded deferred control. The firmware waits on a best-effort basis for the administration output ring and transport TX buffer to drain before acting; this is not confirmation that the peer received the acknowledgement. The shell stops accepting another command while such an action is pending. SSH host-key replacement or service stop closes all SSH sessions; reconnect and verify the new fingerprint where applicable. TLS certificate management, Wi-Fi secrets, and interactive user passwords/keys are available to authenticated administrators and must therefore be treated as remotely accessible administrative material. `user recover --force` is UART0-only. A connected administrator also cannot generate its own replacement password remotely, preventing the one-time password from being lost during self-revocation. SSH does not provide `exec`, SFTP, SCP, forwarding, or subsystems.
## Hardware diagnostics
@@ -217,6 +223,4 @@ UART0 and admin SSH use shared whole-line Tab completion. A unique/common prefix
| `debug buttons status` | Show the current active-low state of previous/back GPIO10, select/confirm GPIO13, and next GPIO14. |
| `debug buttons test [seconds]` | Run the bounded button event test for 1–30 seconds; the default is 10 seconds. |
Follow the exact wiring in [Electrical tests](electrical_tests.md) before invoking diagnostics. The OLED must be powered from 3.3 V because module I²C pull-ups may connect to `VCC`; verify that all external pull-ups also terminate at 3.3 V. Display diagnostics probe the standard SSD1315-compatible 7-bit `0x3c`/`0x3d` addresses. The currently tested module acknowledges at `0x3c`, whose 8-bit write/read forms are `0x78`/`0x79`; an explicit `scan --force` is available only for the dedicated local-UI bus. Diagnostics initially run at 100 kHz and treat an absent display as nonfatal. RS-232 diagnostics that require UART1 refuse to use it until `serial stop` releases it. The RGB LED shows test state: blue idle, yellow/orange running, green passed, red failed.
HTTPS storage migration preserves the exact TLS identity and commits TLS-only v2 before publication. Older v1-only firmware cannot read v2. Logical NVS replacement is not secure flash erasure; no factory erase is required. See [legacy removal](roadmap.md#phase8-legacy-credential-compatibility).
Follow the exact wiring in [Electrical tests](electrical_tests.md) before invoking diagnostics. The OLED must be powered from 3.3 V because module I²C pull-ups may connect to `VCC`; verify that all external pull-ups also terminate at 3.3 V. Display diagnostics probe the standard SSD1315-compatible 7-bit `0x3c`/`0x3d` addresses. The currently tested module acknowledges at `0x3c`, whose 8-bit write/read forms are `0x78`/`0x79`; an explicit `scan --force` is available only for the dedicated local-UI bus. Diagnostics initially run at 100 kHz and treat an absent display as nonfatal. All RS-232 `debug` commands, including `debug status`, require `serial stop` to release the port. If diagnostic cleanup reports a port fault, reboot before continuing. The RGB LED shows test state: blue idle, yellow/orange running, green passed, red failed.