Add bounded OLED boot identity animation
This commit is contained in:
@@ -20,7 +20,7 @@ Use these commands from the UART0 `serial-tool>` administration console. Run `he
|
||||
| `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 apply and persist them. |
|
||||
|
||||
When both transitions are enabled, `off-seconds` must be greater than `dim-seconds`. Applying settings counts as local UI activity. 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; after reconnecting it safely, one new button press requests a bounded reprobe and is consumed without navigating.
|
||||
|
||||
## Serial service
|
||||
|
||||
|
||||
@@ -157,9 +157,11 @@ For Wi-Fi lifecycle calls, confirm the immediate result says `Requested`, then u
|
||||
|
||||
Run these checks with UART0 available. Repeat appropriate stop/revoke cases with USB CDC, WebSocket, and SSH clients connected; verify the intended session/service is interrupted, unrelated recovery paths remain responsive, and no action injects serial data.
|
||||
|
||||
### 10. Phase 7E persistence and fault recovery
|
||||
### 10. Phase 7E boot animation, persistence, and fault recovery
|
||||
|
||||
First shorten the delays for a bounded aging-policy test:
|
||||
On each normal boot with an initialized OLED, first verify the bounded five-second identity animation: the full `ESP32 SERIAL SWISS ARMY KNIFE` name scrolls across the yellow panel; the blue panel shows the compact right-oriented monochrome logo with an upright `>_` prompt and serial/USB cable ends entering from the left. Wi-Fi activity and the cursor blink at the 4 Hz animation cadence. A missing OLED must skip the animation without preventing normal UART0 recovery.
|
||||
|
||||
Then shorten the delays for a bounded aging-policy test:
|
||||
|
||||
```text
|
||||
display status
|
||||
|
||||
@@ -227,6 +227,7 @@ The persistent yellow strip uses fixed-position serial, Wi-Fi-strength, USB, Web
|
||||
- Wi-Fi lifecycle results report a queued request rather than falsely claiming an asynchronous transition has completed. HTTPS/SSH start requests require a connected station or active AP. The current writer is only ever force-released to no writer; the UI is not a broker client and cannot acquire or assign a writer lease.
|
||||
- Target-hardware validation passed for menu navigation and timeout behavior, controls and asynchronous results, two-second confirmations and cancellation, Wi-Fi next-profile progression/wrap, chord rejection, wake-press suppression, and preservation of UART0 plus transport recovery.
|
||||
5. **Phase 7E — Reliability, persistence, and documentation — Implemented; validation pending**
|
||||
- At startup, a bounded five-second OLED-only identity animation renders a compact monochrome, right-oriented derivative of the project logo in the blue panel: its upright `>_` terminal remains readable, while both USB and serial cable ends enter from the left. The full device name scrolls through the yellow panel. A missing or failing OLED skips the animation without delaying recovery services further.
|
||||
- A versioned `local_ui` NVS configuration makes the dim and display-off inactivity delays independently configurable from 0 through 86400 seconds. Zero disables a transition; defaults remain 300/600 seconds. UART0 `display` commands provide status, RAM edits, save/load, defaults, and atomic reset behavior.
|
||||
- A button held for ten seconds is quarantined until its debounced release, after which it rearms. Quarantined inputs no longer keep a chord latched or block the other controls. A confirmation hold can execute at most once.
|
||||
- Dim/off wake and missing-display recovery now occur only on a new debounced press edge. A held or stuck input cannot continuously probe an absent OLED, flood logs, or indefinitely refresh the inactivity timer; the wake gesture remains consumed.
|
||||
|
||||
@@ -7,6 +7,7 @@ idf_component_register(
|
||||
"secure_random.c"
|
||||
"status_led.c"
|
||||
"local_display.c"
|
||||
"local_boot_animation.c"
|
||||
"local_status_ui.c"
|
||||
"local_ui_config.c"
|
||||
"local_ui_console.c"
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
/* Compact monochrome derivative of images/logo.png for the 128x48 content panel. */
|
||||
|
||||
#include "local_boot_animation.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "local_display.h"
|
||||
|
||||
#define BOOT_ANIMATION_FRAMES 20U
|
||||
#define BOOT_ANIMATION_FRAME_MS 250U
|
||||
#define BOOT_ANIMATION_NAME_SPEED 16U
|
||||
|
||||
static const char s_device_name[] = "ESP32 SERIAL SWISS ARMY KNIFE";
|
||||
|
||||
static void pixel(int x, int y)
|
||||
{
|
||||
if (x >= 0 && x < (int)LOCAL_DISPLAY_WIDTH &&
|
||||
y >= 0 && y < (int)LOCAL_DISPLAY_CONTENT_HEIGHT) {
|
||||
local_display_frame_set_pixel(LOCAL_DISPLAY_PANEL_CONTENT, (uint8_t)x,
|
||||
(uint8_t)y, true);
|
||||
}
|
||||
}
|
||||
|
||||
static void line(int x0, int y0, int x1, int y1)
|
||||
{
|
||||
int dx = x1 >= x0 ? x1 - x0 : x0 - x1;
|
||||
int sx = x0 < x1 ? 1 : -1;
|
||||
int dy = y1 >= y0 ? y0 - y1 : y1 - y0;
|
||||
int sy = y0 < y1 ? 1 : -1;
|
||||
int error = dx + dy;
|
||||
|
||||
for (;;) {
|
||||
pixel(x0, y0);
|
||||
if (x0 == x1 && y0 == y1) {
|
||||
return;
|
||||
}
|
||||
int twice_error = 2 * error;
|
||||
if (twice_error >= dy) {
|
||||
error += dy;
|
||||
x0 += sx;
|
||||
}
|
||||
if (twice_error <= dx) {
|
||||
error += dx;
|
||||
y0 += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void rectangle(int x, int y, int width, int height)
|
||||
{
|
||||
line(x, y, x + width - 1, y);
|
||||
line(x, y, x, y + height - 1);
|
||||
line(x + width - 1, y, x + width - 1, y + height - 1);
|
||||
line(x, y + height - 1, x + width - 1, y + height - 1);
|
||||
}
|
||||
|
||||
static void draw_serial_connector(void)
|
||||
{
|
||||
rectangle(2, 8, 16, 10);
|
||||
rectangle(4, 10, 12, 6);
|
||||
for (int column = 0; column < 5; ++column) {
|
||||
pixel(6 + column * 2, 12);
|
||||
pixel(6 + column * 2, 14);
|
||||
}
|
||||
line(18, 13, 30, 22);
|
||||
line(18, 16, 28, 25);
|
||||
}
|
||||
|
||||
static void draw_usb_connector(void)
|
||||
{
|
||||
rectangle(2, 31, 14, 11);
|
||||
rectangle(4, 33, 10, 7);
|
||||
line(16, 35, 29, 30);
|
||||
line(16, 39, 30, 34);
|
||||
pixel(7, 35);
|
||||
pixel(10, 38);
|
||||
}
|
||||
|
||||
static void draw_terminal(bool cursor_on)
|
||||
{
|
||||
rectangle(28, 21, 42, 22);
|
||||
rectangle(30, 23, 38, 18);
|
||||
/* Keep the prompt upright even though the source logo is reoriented. */
|
||||
line(38, 28, 45, 33);
|
||||
line(45, 33, 38, 38);
|
||||
if (cursor_on) {
|
||||
line(51, 37, 59, 37);
|
||||
}
|
||||
line(31, 27, 31, 39);
|
||||
line(67, 27, 67, 39);
|
||||
line(34, 22, 34, 20);
|
||||
line(63, 22, 63, 20);
|
||||
}
|
||||
|
||||
static void draw_board(void)
|
||||
{
|
||||
rectangle(72, 7, 28, 36);
|
||||
rectangle(78, 14, 16, 17);
|
||||
for (int row = 0; row < 5; ++row) {
|
||||
pixel(74, 11 + row * 6);
|
||||
pixel(76, 11 + row * 6);
|
||||
pixel(96, 11 + row * 6);
|
||||
pixel(98, 11 + row * 6);
|
||||
}
|
||||
for (int column = 0; column < 6; ++column) {
|
||||
pixel(79 + column * 3, 35);
|
||||
pixel(79 + column * 3, 38);
|
||||
}
|
||||
line(70, 28, 72, 28);
|
||||
line(70, 34, 72, 34);
|
||||
}
|
||||
|
||||
static void draw_wifi(uint8_t frame)
|
||||
{
|
||||
bool outer = (frame % 2U) == 0U;
|
||||
if (outer) {
|
||||
line(103, 8, 109, 3);
|
||||
line(109, 3, 115, 8);
|
||||
}
|
||||
line(105, 12, 109, 8);
|
||||
line(109, 8, 113, 12);
|
||||
line(107, 16, 109, 14);
|
||||
line(109, 14, 111, 16);
|
||||
pixel(109, 19);
|
||||
}
|
||||
|
||||
static void draw_logo(uint8_t frame)
|
||||
{
|
||||
draw_serial_connector();
|
||||
draw_usb_connector();
|
||||
draw_terminal((frame % 2U) == 0U);
|
||||
draw_board();
|
||||
draw_wifi(frame);
|
||||
}
|
||||
|
||||
static void draw_scrolling_name(uint8_t frame)
|
||||
{
|
||||
int text_width = ((int)sizeof(s_device_name) - 1) * 6;
|
||||
int x = (int)LOCAL_DISPLAY_WIDTH - (int)frame * BOOT_ANIMATION_NAME_SPEED;
|
||||
if (x < -text_width) {
|
||||
x += text_width + (int)LOCAL_DISPLAY_WIDTH;
|
||||
}
|
||||
const char *text = s_device_name;
|
||||
while (x < 0 && *text != '\0') {
|
||||
x += 6;
|
||||
++text;
|
||||
}
|
||||
if (x < (int)LOCAL_DISPLAY_WIDTH && *text != '\0') {
|
||||
local_display_frame_draw_text(LOCAL_DISPLAY_PANEL_STATUS, (uint8_t)x, 4U, text);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t local_boot_animation_play(void)
|
||||
{
|
||||
for (uint8_t frame = 0U; frame < BOOT_ANIMATION_FRAMES; ++frame) {
|
||||
esp_err_t error = local_display_frame_begin();
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
local_display_frame_clear_all();
|
||||
draw_scrolling_name(frame);
|
||||
draw_logo(frame);
|
||||
error = local_display_frame_end();
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(BOOT_ANIMATION_FRAME_MS));
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
/* Bounded boot animation for the optional local OLED. */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
/* Plays the OLED-only startup identity animation; a missing display is nonfatal. */
|
||||
esp_err_t local_boot_animation_play(void);
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "esp_psram.h"
|
||||
#include "network_console.h"
|
||||
#include "local_display.h"
|
||||
#include "local_boot_animation.h"
|
||||
#include "local_status_ui.h"
|
||||
#include "local_ui_config.h"
|
||||
#include "local_ui_console.h"
|
||||
@@ -70,6 +71,12 @@ void app_main(void)
|
||||
if (local_display_error != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Local display unavailable: %s",
|
||||
esp_err_to_name(local_display_error));
|
||||
} else {
|
||||
local_display_error = local_boot_animation_play();
|
||||
if (local_display_error != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Local boot animation unavailable: %s",
|
||||
esp_err_to_name(local_display_error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user