Add persistent OLED UI settings and recovery
This commit is contained in:
@@ -8,6 +8,8 @@ idf_component_register(
|
||||
"status_led.c"
|
||||
"local_display.c"
|
||||
"local_status_ui.c"
|
||||
"local_ui_config.c"
|
||||
"local_ui_console.c"
|
||||
"local_ui_hw_test.c"
|
||||
"rs232_hw_test.c"
|
||||
"rs232_port_owner.c"
|
||||
|
||||
@@ -53,6 +53,16 @@ static const char *const s_completion_candidates[] = {
|
||||
"debug buttons status",
|
||||
"debug buttons test",
|
||||
|
||||
/* Persistent local OLED aging settings. */
|
||||
"display status",
|
||||
"display set",
|
||||
"display set dim-seconds",
|
||||
"display set off-seconds",
|
||||
"display save",
|
||||
"display load",
|
||||
"display defaults",
|
||||
"display reset",
|
||||
|
||||
/* Serial service lifecycle, persistence, counters, and settings. */
|
||||
"serial status",
|
||||
"serial start",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "board_pins.h"
|
||||
#include "driver/i2c_master.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
@@ -21,6 +22,7 @@
|
||||
#define LOCAL_DISPLAY_DEFAULT_CONTRAST 127U
|
||||
#define LOCAL_DISPLAY_COMMAND_CAPACITY 32U
|
||||
#define LOCAL_DISPLAY_DATA_CHUNK_SIZE 128U
|
||||
#define LOCAL_DISPLAY_FLUSH_BUDGET_US (500LL * 1000LL)
|
||||
|
||||
static const uint8_t s_expected_addresses[] = {0x3cU, 0x3dU};
|
||||
|
||||
@@ -172,6 +174,7 @@ static esp_err_t flush_dirty_locked(void)
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
int64_t started = esp_timer_get_time();
|
||||
uint8_t transfer[LOCAL_DISPLAY_DATA_CHUNK_SIZE + 1U];
|
||||
transfer[0] = 0x40U;
|
||||
for (uint8_t page = 0U; page < LOCAL_DISPLAY_PAGE_COUNT; ++page) {
|
||||
@@ -179,6 +182,9 @@ static esp_err_t flush_dirty_locked(void)
|
||||
if ((s_dirty_pages & page_mask) == 0U) {
|
||||
continue;
|
||||
}
|
||||
if ((esp_timer_get_time() - started) >= LOCAL_DISPLAY_FLUSH_BUDGET_US) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
const uint8_t commands[] = {
|
||||
0x21U, 0x00U, (uint8_t)(LOCAL_DISPLAY_WIDTH - 1U),
|
||||
@@ -188,6 +194,9 @@ static esp_err_t flush_dirty_locked(void)
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
if ((esp_timer_get_time() - started) >= LOCAL_DISPLAY_FLUSH_BUDGET_US) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
memcpy(&transfer[1], &s_framebuffer[(size_t)page * LOCAL_DISPLAY_WIDTH],
|
||||
LOCAL_DISPLAY_WIDTH);
|
||||
error = i2c_master_transmit(s_device, transfer, sizeof(transfer),
|
||||
|
||||
+82
-24
@@ -35,8 +35,6 @@
|
||||
#define LOCAL_STATUS_UI_TEXT_CAPACITY 22U
|
||||
#define LOCAL_STATUS_UI_CLIENT_ROWS 4U
|
||||
#define LOCAL_STATUS_UI_DIAGNOSTIC_HOLD_MS 30000U
|
||||
#define LOCAL_STATUS_UI_DIM_DELAY_MS (5U * 60U * 1000U)
|
||||
#define LOCAL_STATUS_UI_OFF_DELAY_MS (10U * 60U * 1000U)
|
||||
#define LOCAL_STATUS_UI_DIM_CONTRAST 1U
|
||||
#define LOCAL_STATUS_UI_ACTIVE_CONTRAST 127U
|
||||
#define LOCAL_STATUS_UI_ICON_SIZE 8U
|
||||
@@ -45,6 +43,7 @@
|
||||
#define LOCAL_STATUS_UI_MENU_TIMEOUT_MS 30000U
|
||||
#define LOCAL_STATUS_UI_RESULT_TIMEOUT_MS 2500U
|
||||
#define LOCAL_STATUS_UI_CONFIRM_HOLD_MS 2000U
|
||||
#define LOCAL_STATUS_UI_STUCK_BUTTON_MS 10000U
|
||||
|
||||
static const char *TAG = "local_status_ui";
|
||||
|
||||
@@ -117,6 +116,7 @@ typedef struct {
|
||||
TickType_t pressed_since;
|
||||
bool long_emitted;
|
||||
bool suppress_gesture;
|
||||
bool quarantined;
|
||||
} local_status_button_state_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -201,6 +201,8 @@ static portMUX_TYPE s_timing_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
static bool s_diagnostic_hold_active;
|
||||
static TickType_t s_diagnostic_hold_started;
|
||||
static uint32_t s_external_activity_sequence;
|
||||
static local_ui_config_t s_config;
|
||||
static bool s_config_available;
|
||||
|
||||
static const gpio_num_t s_button_gpios[LOCAL_STATUS_BUTTON_COUNT] = {
|
||||
LOCAL_UI_BUTTON_PREVIOUS_GPIO,
|
||||
@@ -417,7 +419,9 @@ static bool snapshot_has_alert(const local_status_snapshot_t *snapshot)
|
||||
return !snapshot->broker_available || !snapshot->usb_available ||
|
||||
!snapshot->wifi_available || !snapshot->web_available ||
|
||||
!snapshot->web_serial_available || !snapshot->ssh_available ||
|
||||
(snapshot->wifi_available && snapshot->wifi.last_error != ESP_OK) ||
|
||||
(snapshot->wifi_available &&
|
||||
(snapshot->wifi.last_error != ESP_OK ||
|
||||
snapshot->wifi.counters.queue_drops != 0U)) ||
|
||||
(snapshot->web_available && snapshot->web.last_error != ESP_OK) ||
|
||||
(snapshot->ssh_available && snapshot->ssh.last_error != ESP_OK) ||
|
||||
snapshot->serial_counters.rx_dropped_bytes != 0U ||
|
||||
@@ -927,9 +931,16 @@ static bool poll_button_event(local_status_button_state_t states[LOCAL_STATUS_BU
|
||||
}
|
||||
if (!states[i].stable_pressed) {
|
||||
states[i].suppress_gesture = false;
|
||||
states[i].quarantined = false;
|
||||
}
|
||||
}
|
||||
if (states[i].stable_pressed) {
|
||||
if (states[i].stable_pressed && !states[i].quarantined &&
|
||||
(now - states[i].pressed_since) >=
|
||||
pdMS_TO_TICKS(LOCAL_STATUS_UI_STUCK_BUTTON_MS)) {
|
||||
states[i].quarantined = true;
|
||||
states[i].suppress_gesture = true;
|
||||
}
|
||||
if (states[i].stable_pressed && !states[i].quarantined) {
|
||||
++pressed_count;
|
||||
}
|
||||
}
|
||||
@@ -945,7 +956,7 @@ static bool poll_button_event(local_status_button_state_t states[LOCAL_STATUS_BU
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0U; i < LOCAL_STATUS_BUTTON_COUNT; ++i) {
|
||||
if (pressed_edge[i]) {
|
||||
if (pressed_edge[i] && !states[i].quarantined) {
|
||||
*event = (local_status_button_event_t){
|
||||
.button = (local_status_button_t)i,
|
||||
.type = LOCAL_STATUS_BUTTON_EVENT_PRESS,
|
||||
@@ -954,7 +965,8 @@ static bool poll_button_event(local_status_button_state_t states[LOCAL_STATUS_BU
|
||||
}
|
||||
}
|
||||
for (size_t i = 0U; i < LOCAL_STATUS_BUTTON_COUNT; ++i) {
|
||||
if (states[i].stable_pressed && !states[i].long_emitted &&
|
||||
if (states[i].stable_pressed && !states[i].quarantined &&
|
||||
!states[i].long_emitted &&
|
||||
!states[i].suppress_gesture &&
|
||||
(now - states[i].pressed_since) >= pdMS_TO_TICKS(LOCAL_STATUS_UI_CONFIRM_HOLD_MS)) {
|
||||
states[i].long_emitted = true;
|
||||
@@ -1196,13 +1208,16 @@ static void handle_button_event(local_status_ui_state_t *state,
|
||||
|
||||
static void update_display_power(local_status_power_t *power,
|
||||
TickType_t now,
|
||||
TickType_t last_activity)
|
||||
TickType_t last_activity,
|
||||
const local_ui_config_t *config)
|
||||
{
|
||||
TickType_t inactive = now - last_activity;
|
||||
bool should_stop = *power != LOCAL_STATUS_POWER_OFF &&
|
||||
inactive >= pdMS_TO_TICKS(LOCAL_STATUS_UI_OFF_DELAY_MS);
|
||||
bool should_dim = *power == LOCAL_STATUS_POWER_ACTIVE &&
|
||||
inactive >= pdMS_TO_TICKS(LOCAL_STATUS_UI_DIM_DELAY_MS);
|
||||
bool should_stop = config->off_timeout_seconds != 0U &&
|
||||
*power != LOCAL_STATUS_POWER_OFF &&
|
||||
inactive >= pdMS_TO_TICKS(config->off_timeout_seconds * 1000U);
|
||||
bool should_dim = config->dim_timeout_seconds != 0U &&
|
||||
*power == LOCAL_STATUS_POWER_ACTIVE &&
|
||||
inactive >= pdMS_TO_TICKS(config->dim_timeout_seconds * 1000U);
|
||||
if ((!should_stop && !should_dim) || s_diagnostic_gate == NULL ||
|
||||
xSemaphoreTake(s_diagnostic_gate, pdMS_TO_TICKS(100U)) != pdTRUE) {
|
||||
return;
|
||||
@@ -1267,21 +1282,14 @@ static void local_status_ui_task(void *context)
|
||||
last_activity = now;
|
||||
}
|
||||
sync_display_power(&power);
|
||||
bool wake_gesture = false;
|
||||
if (power != LOCAL_STATUS_POWER_ACTIVE) {
|
||||
if (diagnostics_held) {
|
||||
for (size_t i = 0U; i < LOCAL_STATUS_BUTTON_COUNT; ++i) {
|
||||
if (buttons[i].stable_pressed) {
|
||||
buttons[i].suppress_gesture = true;
|
||||
wake_gesture = true;
|
||||
}
|
||||
}
|
||||
if (wake_gesture && !diagnostics_held) {
|
||||
last_activity = now;
|
||||
render_requested = wake_display(&power);
|
||||
}
|
||||
}
|
||||
|
||||
if (have_event && !wake_gesture) {
|
||||
if (have_event) {
|
||||
last_activity = now;
|
||||
if (!diagnostics_held) {
|
||||
local_status_snapshot_t snapshot;
|
||||
@@ -1309,7 +1317,11 @@ static void local_status_ui_task(void *context)
|
||||
render_requested = true;
|
||||
}
|
||||
|
||||
update_display_power(&power, now, last_activity);
|
||||
local_ui_config_t config;
|
||||
portENTER_CRITICAL(&s_timing_mux);
|
||||
config = s_config;
|
||||
portEXIT_CRITICAL(&s_timing_mux);
|
||||
update_display_power(&power, now, last_activity, &config);
|
||||
diagnostics_held = diagnostic_hold_is_active(now);
|
||||
bool rendered = false;
|
||||
if (power != LOCAL_STATUS_POWER_OFF && !diagnostics_held &&
|
||||
@@ -1344,14 +1356,55 @@ void local_status_ui_hold_for_diagnostics(void)
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t local_status_ui_start(void)
|
||||
esp_err_t local_status_ui_get_config(local_ui_config_t *config)
|
||||
{
|
||||
if (config == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
portENTER_CRITICAL(&s_timing_mux);
|
||||
bool available = s_config_available;
|
||||
*config = s_config;
|
||||
portEXIT_CRITICAL(&s_timing_mux);
|
||||
return available ? ESP_OK : ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_err_t local_status_ui_apply_config(const local_ui_config_t *config)
|
||||
{
|
||||
esp_err_t error = local_ui_config_validate(config);
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
portENTER_CRITICAL(&s_timing_mux);
|
||||
if (!s_config_available) {
|
||||
portEXIT_CRITICAL(&s_timing_mux);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
s_config = *config;
|
||||
++s_external_activity_sequence;
|
||||
portEXIT_CRITICAL(&s_timing_mux);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t local_status_ui_start(const local_ui_config_t *config)
|
||||
{
|
||||
esp_err_t error = local_ui_config_validate(config);
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
if (s_task != NULL || s_diagnostic_gate != NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
portENTER_CRITICAL(&s_timing_mux);
|
||||
s_config = *config;
|
||||
s_config_available = true;
|
||||
portEXIT_CRITICAL(&s_timing_mux);
|
||||
|
||||
s_diagnostic_gate = xSemaphoreCreateMutexStatic(&s_diagnostic_gate_storage);
|
||||
if (s_diagnostic_gate == NULL) {
|
||||
portENTER_CRITICAL(&s_timing_mux);
|
||||
s_config_available = false;
|
||||
portEXIT_CRITICAL(&s_timing_mux);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
@@ -1362,11 +1415,16 @@ esp_err_t local_status_ui_start(void)
|
||||
s_task = NULL;
|
||||
vSemaphoreDelete(s_diagnostic_gate);
|
||||
s_diagnostic_gate = NULL;
|
||||
portENTER_CRITICAL(&s_timing_mux);
|
||||
s_config_available = false;
|
||||
portEXIT_CRITICAL(&s_timing_mux);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"Read-only status UI started at %u Hz maximum; dim/off after 5/10 minutes",
|
||||
1000U / LOCAL_STATUS_UI_REFRESH_MS);
|
||||
"Local status/control UI started at %u Hz maximum; dim/off after %u/%u seconds",
|
||||
1000U / LOCAL_STATUS_UI_REFRESH_MS,
|
||||
(unsigned int)config->dim_timeout_seconds,
|
||||
(unsigned int)config->off_timeout_seconds);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "local_ui_config.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -14,7 +15,11 @@ extern "C" {
|
||||
* task never becomes a broker client or serial writer. The OLED and buttons
|
||||
* are optional, so a missing display is not an error.
|
||||
*/
|
||||
esp_err_t local_status_ui_start(void);
|
||||
esp_err_t local_status_ui_start(const local_ui_config_t *config);
|
||||
|
||||
/* Runtime settings are copied atomically and never expose display-frame ownership. */
|
||||
esp_err_t local_status_ui_get_config(local_ui_config_t *config);
|
||||
esp_err_t local_status_ui_apply_config(const local_ui_config_t *config);
|
||||
|
||||
/* Preserve a manually selected display diagnostic for a bounded interval. */
|
||||
void local_status_ui_hold_for_diagnostics(void);
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
/* Versioned persistent configuration for the optional local OLED UI. */
|
||||
|
||||
#include "local_ui_config.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
void local_ui_config_defaults(local_ui_config_t *config)
|
||||
{
|
||||
if (config == NULL) {
|
||||
return;
|
||||
}
|
||||
*config = (local_ui_config_t){
|
||||
.version = LOCAL_UI_CONFIG_VERSION,
|
||||
.dim_timeout_seconds = LOCAL_UI_CONFIG_DEFAULT_DIM_SECONDS,
|
||||
.off_timeout_seconds = LOCAL_UI_CONFIG_DEFAULT_OFF_SECONDS,
|
||||
};
|
||||
}
|
||||
|
||||
esp_err_t local_ui_config_validate(const local_ui_config_t *config)
|
||||
{
|
||||
if (config == NULL || config->version != LOCAL_UI_CONFIG_VERSION ||
|
||||
config->dim_timeout_seconds > LOCAL_UI_CONFIG_MAX_TIMEOUT_SECONDS ||
|
||||
config->off_timeout_seconds > LOCAL_UI_CONFIG_MAX_TIMEOUT_SECONDS) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (config->dim_timeout_seconds != 0U && config->off_timeout_seconds != 0U &&
|
||||
config->off_timeout_seconds <= config->dim_timeout_seconds) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t local_ui_config_load(local_ui_config_t *config, bool *used_stored_config)
|
||||
{
|
||||
if (config == NULL || used_stored_config == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
local_ui_config_defaults(config);
|
||||
*used_stored_config = false;
|
||||
|
||||
esp_err_t error = nvs_flash_init();
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
|
||||
nvs_handle_t handle;
|
||||
error = nvs_open(LOCAL_UI_CONFIG_NVS_NAMESPACE, NVS_READONLY, &handle);
|
||||
if (error == ESP_ERR_NVS_NOT_FOUND) {
|
||||
return ESP_OK;
|
||||
}
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
|
||||
size_t stored_size = 0U;
|
||||
error = nvs_get_blob(handle, LOCAL_UI_CONFIG_NVS_BLOB_KEY, NULL, &stored_size);
|
||||
if (error == ESP_ERR_NVS_NOT_FOUND || error == ESP_ERR_NVS_TYPE_MISMATCH ||
|
||||
(error == ESP_OK && stored_size != sizeof(local_ui_config_t))) {
|
||||
nvs_close(handle);
|
||||
return ESP_OK;
|
||||
}
|
||||
if (error != ESP_OK) {
|
||||
nvs_close(handle);
|
||||
return error;
|
||||
}
|
||||
|
||||
local_ui_config_t stored;
|
||||
error = nvs_get_blob(handle, LOCAL_UI_CONFIG_NVS_BLOB_KEY, &stored, &stored_size);
|
||||
nvs_close(handle);
|
||||
if (error == ESP_ERR_NVS_INVALID_LENGTH) {
|
||||
return ESP_OK;
|
||||
}
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
if (stored_size != sizeof(stored) || local_ui_config_validate(&stored) != ESP_OK) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
*config = stored;
|
||||
*used_stored_config = true;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t local_ui_config_save(const local_ui_config_t *config)
|
||||
{
|
||||
esp_err_t error = local_ui_config_validate(config);
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
error = nvs_flash_init();
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
|
||||
nvs_handle_t handle;
|
||||
error = nvs_open(LOCAL_UI_CONFIG_NVS_NAMESPACE, NVS_READWRITE, &handle);
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
error = nvs_set_blob(handle, LOCAL_UI_CONFIG_NVS_BLOB_KEY, config, sizeof(*config));
|
||||
if (error == ESP_OK) {
|
||||
error = nvs_commit(handle);
|
||||
}
|
||||
nvs_close(handle);
|
||||
return error;
|
||||
}
|
||||
|
||||
esp_err_t local_ui_config_reset_storage(void)
|
||||
{
|
||||
local_ui_config_t config;
|
||||
local_ui_config_defaults(&config);
|
||||
return local_ui_config_save(&config);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
/* Versioned persistent configuration for the optional local OLED UI. */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#define LOCAL_UI_CONFIG_VERSION 1U
|
||||
#define LOCAL_UI_CONFIG_DEFAULT_DIM_SECONDS 300U
|
||||
#define LOCAL_UI_CONFIG_DEFAULT_OFF_SECONDS 600U
|
||||
#define LOCAL_UI_CONFIG_MAX_TIMEOUT_SECONDS 86400U
|
||||
|
||||
#define LOCAL_UI_CONFIG_NVS_NAMESPACE "local_ui"
|
||||
#define LOCAL_UI_CONFIG_NVS_BLOB_KEY "config"
|
||||
|
||||
typedef struct {
|
||||
uint32_t version;
|
||||
/* Zero disables the corresponding inactivity transition. */
|
||||
uint32_t dim_timeout_seconds;
|
||||
uint32_t off_timeout_seconds;
|
||||
} local_ui_config_t;
|
||||
|
||||
void local_ui_config_defaults(local_ui_config_t *config);
|
||||
esp_err_t local_ui_config_validate(const local_ui_config_t *config);
|
||||
esp_err_t local_ui_config_load(local_ui_config_t *config, bool *used_stored_config);
|
||||
esp_err_t local_ui_config_save(const local_ui_config_t *config);
|
||||
esp_err_t local_ui_config_reset_storage(void);
|
||||
@@ -0,0 +1,202 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
/* UART0 administration commands for local UI aging settings. */
|
||||
|
||||
#include "local_ui_console.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "esp_console.h"
|
||||
#include "local_display.h"
|
||||
#include "local_status_ui.h"
|
||||
#include "local_ui_config.h"
|
||||
|
||||
static void print_usage(void)
|
||||
{
|
||||
printf("Usage:\n");
|
||||
printf(" display status\n");
|
||||
printf(" display set <dim-seconds|off-seconds> <0..%u>\n",
|
||||
LOCAL_UI_CONFIG_MAX_TIMEOUT_SECONDS);
|
||||
printf(" display save|load|defaults|reset\n");
|
||||
printf("Zero disables the selected inactivity transition.\n");
|
||||
}
|
||||
|
||||
static void print_config(const local_ui_config_t *config)
|
||||
{
|
||||
printf("Local UI configuration v%u: dim-seconds=%u off-seconds=%u\n",
|
||||
(unsigned int)config->version,
|
||||
(unsigned int)config->dim_timeout_seconds,
|
||||
(unsigned int)config->off_timeout_seconds);
|
||||
}
|
||||
|
||||
static bool parse_timeout(const char *text, uint32_t *value)
|
||||
{
|
||||
if (text == NULL || *text == '\0') {
|
||||
return false;
|
||||
}
|
||||
for (const char *character = text; *character != '\0'; ++character) {
|
||||
if (*character < '0' || *character > '9') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
errno = 0;
|
||||
char *end = NULL;
|
||||
unsigned long parsed = strtoul(text, &end, 10);
|
||||
if (errno != 0 || end == text || *end != '\0' ||
|
||||
parsed > LOCAL_UI_CONFIG_MAX_TIMEOUT_SECONDS) {
|
||||
return false;
|
||||
}
|
||||
*value = (uint32_t)parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int show_status(void)
|
||||
{
|
||||
local_ui_config_t config;
|
||||
esp_err_t error = local_status_ui_get_config(&config);
|
||||
if (error != ESP_OK) {
|
||||
printf("Could not read local UI configuration: %s\n", esp_err_to_name(error));
|
||||
return 1;
|
||||
}
|
||||
print_config(&config);
|
||||
|
||||
local_display_snapshot_t display;
|
||||
error = local_display_get_snapshot(&display);
|
||||
if (error != ESP_OK) {
|
||||
printf("Display service unavailable: %s\n", esp_err_to_name(error));
|
||||
return 1;
|
||||
}
|
||||
printf("OLED: bus=%s initialized=%s address=0x%02x contrast=%u last-error=%s\n",
|
||||
display.bus_ready ? "ready" : "unavailable",
|
||||
display.initialized ? "yes" : "no",
|
||||
display.address_7bit,
|
||||
(unsigned int)display.contrast,
|
||||
esp_err_to_name(display.last_error));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apply_parameter(const char *parameter, const char *text)
|
||||
{
|
||||
uint32_t value;
|
||||
if (!parse_timeout(text, &value)) {
|
||||
printf("Timeout must be 0..%u seconds.\n",
|
||||
LOCAL_UI_CONFIG_MAX_TIMEOUT_SECONDS);
|
||||
return 1;
|
||||
}
|
||||
|
||||
local_ui_config_t config;
|
||||
esp_err_t error = local_status_ui_get_config(&config);
|
||||
if (error != ESP_OK) {
|
||||
printf("Could not read local UI configuration: %s\n", esp_err_to_name(error));
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(parameter, "dim-seconds") == 0) {
|
||||
config.dim_timeout_seconds = value;
|
||||
} else if (strcmp(parameter, "off-seconds") == 0) {
|
||||
config.off_timeout_seconds = value;
|
||||
} else {
|
||||
printf("Unknown display parameter '%s'.\n", parameter);
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
error = local_status_ui_apply_config(&config);
|
||||
if (error != ESP_OK) {
|
||||
printf("Invalid display configuration: %s. When both timeouts are enabled, off must be later than dim.\n",
|
||||
esp_err_to_name(error));
|
||||
return 1;
|
||||
}
|
||||
print_config(&config);
|
||||
printf("Applied in RAM; run 'display save' to persist it.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int command_display(int argc, char **argv)
|
||||
{
|
||||
if (argc == 1 || (argc == 2 && strcmp(argv[1], "status") == 0)) {
|
||||
return show_status();
|
||||
}
|
||||
if (argc == 4 && strcmp(argv[1], "set") == 0) {
|
||||
return apply_parameter(argv[2], argv[3]);
|
||||
}
|
||||
if (argc == 2 && strcmp(argv[1], "save") == 0) {
|
||||
local_ui_config_t config;
|
||||
esp_err_t error = local_status_ui_get_config(&config);
|
||||
if (error == ESP_OK) {
|
||||
error = local_ui_config_save(&config);
|
||||
}
|
||||
if (error != ESP_OK) {
|
||||
printf("Could not save display configuration: %s\n", esp_err_to_name(error));
|
||||
return 1;
|
||||
}
|
||||
printf("Display configuration saved to NVS.\n");
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && strcmp(argv[1], "load") == 0) {
|
||||
local_ui_config_t config;
|
||||
bool used_stored_config;
|
||||
esp_err_t error = local_ui_config_load(&config, &used_stored_config);
|
||||
if (error == ESP_OK) {
|
||||
error = local_status_ui_apply_config(&config);
|
||||
}
|
||||
if (error != ESP_OK) {
|
||||
printf("Could not load display configuration: %s\n", esp_err_to_name(error));
|
||||
return 1;
|
||||
}
|
||||
printf("Loaded %s display configuration.\n",
|
||||
used_stored_config ? "stored" : "default");
|
||||
print_config(&config);
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && strcmp(argv[1], "defaults") == 0) {
|
||||
local_ui_config_t config;
|
||||
local_ui_config_defaults(&config);
|
||||
esp_err_t error = local_status_ui_apply_config(&config);
|
||||
if (error != ESP_OK) {
|
||||
printf("Could not apply display defaults: %s\n", esp_err_to_name(error));
|
||||
return 1;
|
||||
}
|
||||
printf("Display defaults applied in RAM; run 'display save' to persist them.\n");
|
||||
print_config(&config);
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && strcmp(argv[1], "reset") == 0) {
|
||||
local_ui_config_t previous;
|
||||
local_ui_config_t defaults;
|
||||
local_ui_config_defaults(&defaults);
|
||||
esp_err_t error = local_status_ui_get_config(&previous);
|
||||
if (error == ESP_OK) {
|
||||
error = local_status_ui_apply_config(&defaults);
|
||||
}
|
||||
if (error == ESP_OK) {
|
||||
error = local_ui_config_reset_storage();
|
||||
if (error != ESP_OK) {
|
||||
(void)local_status_ui_apply_config(&previous);
|
||||
}
|
||||
}
|
||||
if (error != ESP_OK) {
|
||||
printf("Could not reset display configuration: %s\n", esp_err_to_name(error));
|
||||
return 1;
|
||||
}
|
||||
printf("Display defaults applied and saved to NVS.\n");
|
||||
print_config(&defaults);
|
||||
return 0;
|
||||
}
|
||||
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
esp_err_t local_ui_console_register_commands(void)
|
||||
{
|
||||
const esp_console_cmd_t command = {
|
||||
.command = "display",
|
||||
.help = "Configure persistent local OLED aging timeouts; use 'display' for status",
|
||||
.hint = NULL,
|
||||
.func = &command_display,
|
||||
.argtable = NULL,
|
||||
};
|
||||
return esp_console_cmd_register(&command);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
/* UART0 administration commands for local UI aging settings. */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
esp_err_t local_ui_console_register_commands(void);
|
||||
+18
-2
@@ -7,6 +7,8 @@
|
||||
#include "network_console.h"
|
||||
#include "local_display.h"
|
||||
#include "local_status_ui.h"
|
||||
#include "local_ui_config.h"
|
||||
#include "local_ui_console.h"
|
||||
#include "local_ui_hw_test.h"
|
||||
#include "rs232_hw_test.h"
|
||||
#include "rs232_port_owner.h"
|
||||
@@ -38,7 +40,7 @@ static const char *TAG = "firmware";
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "ESP32-S3 Serial Swiss Army Knife Phase 7D local controls started");
|
||||
ESP_LOGI(TAG, "ESP32-S3 Serial Swiss Army Knife Phase 7E local UI reliability started");
|
||||
|
||||
if (esp_psram_is_initialized()) {
|
||||
ESP_LOGI(TAG, "PSRAM initialized: %u bytes", (unsigned int)esp_psram_get_size());
|
||||
@@ -77,6 +79,17 @@ void app_main(void)
|
||||
esp_err_to_name(local_ui_error));
|
||||
}
|
||||
|
||||
local_ui_config_t local_ui_config;
|
||||
bool used_stored_local_ui_config = false;
|
||||
esp_err_t local_ui_config_error =
|
||||
local_ui_config_load(&local_ui_config, &used_stored_local_ui_config);
|
||||
if (local_ui_config_error != ESP_OK) {
|
||||
local_ui_config_defaults(&local_ui_config);
|
||||
ESP_LOGW(TAG,
|
||||
"NVS local UI configuration unavailable (%s); using RAM defaults",
|
||||
esp_err_to_name(local_ui_config_error));
|
||||
}
|
||||
|
||||
serial_config_t serial_config;
|
||||
bool used_stored_config = false;
|
||||
esp_err_t config_error = serial_config_load(&serial_config, &used_stored_config);
|
||||
@@ -203,13 +216,15 @@ void app_main(void)
|
||||
}
|
||||
|
||||
if (local_ui_error == ESP_OK) {
|
||||
esp_err_t local_status_ui_error = local_status_ui_start();
|
||||
esp_err_t local_status_ui_error = local_status_ui_start(&local_ui_config);
|
||||
if (local_status_ui_error != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Local status UI unavailable: %s",
|
||||
esp_err_to_name(local_status_ui_error));
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Using %s local UI configuration",
|
||||
used_stored_local_ui_config ? "stored" : "default");
|
||||
ESP_LOGI(
|
||||
TAG,
|
||||
"Using %s serial configuration; UART service starts on 'serial start' or native USB open",
|
||||
@@ -235,6 +250,7 @@ void app_main(void)
|
||||
|
||||
/* The REPL constructor initializes esp_console and installs `help`. */
|
||||
ESP_ERROR_CHECK(rs232_hw_test_register_console_commands());
|
||||
ESP_ERROR_CHECK(local_ui_console_register_commands());
|
||||
ESP_ERROR_CHECK(serial_console_register_commands());
|
||||
ESP_ERROR_CHECK(session_console_register_commands());
|
||||
ESP_ERROR_CHECK(usb_console_register_commands());
|
||||
|
||||
Reference in New Issue
Block a user