Add persistent OLED UI settings and recovery
This commit is contained in:
+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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user