Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion boards/common/components/stream32_deck/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
idf_component_register(
SRCS "deck_protocol.c" "deck_storage.c" "deck_ui.c"
SRCS "deck_clean.c" "deck_protocol.c" "deck_storage.c" "deck_ui.c"
INCLUDE_DIRS "."
REQUIRES esp_partition esp_timer lvgl
PRIV_REQUIRES json mbedtls
Expand Down
138 changes: 138 additions & 0 deletions boards/common/components/stream32_deck/deck_clean.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#include "deck_clean.h"

#include "esp_timer.h"
#include "sdkconfig.h"

#define CLEAN_SCREEN_W CONFIG_STREAM32_DECK_SCREEN_WIDTH
#define CLEAN_SCREEN_H CONFIG_STREAM32_DECK_SCREEN_HEIGHT
/* Long enough that a wiping cloth cannot dwell its way out of the lock. */
#define DECK_CLEAN_HOLD_MS 5000

static lv_obj_t *s_screen;
static lv_obj_t *s_fill;
/* Written from the LVGL task, read by the protocol task's poll. */
static volatile int64_t s_press_ms;

static int64_t now_ms(void)
{
return esp_timer_get_time() / 1000;
}

static void set_progress(int32_t percent)
{
if (s_fill != NULL) {
lv_obj_set_width(s_fill, lv_pct(percent > 100 ? 100 : percent));
}
}

/* Timed here rather than through LVGL's long-press event because that
threshold is shared with every other consumer of the same input device. */
static void hold_handler(lv_event_t *event)
{
const lv_event_code_t code = lv_event_get_code(event);

if (code == LV_EVENT_PRESSED) {
s_press_ms = now_ms();
set_progress(0);
} else if (code == LV_EVENT_PRESSING && s_press_ms != 0) {
set_progress(
(int32_t)(((now_ms() - s_press_ms) * 100) / DECK_CLEAN_HOLD_MS)
);
} else if (code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST) {
deck_clean_reset();
}
}

/* A rounded card in the deck's colours, without lv_obj's default chrome. */
static lv_obj_t *panel(lv_obj_t *parent, int32_t width, int32_t height)
{
lv_obj_t *object = lv_obj_create(parent);

lv_obj_set_size(object, width, height);
lv_obj_set_style_radius(object, LV_RADIUS_CIRCLE, LV_PART_MAIN);
lv_obj_set_style_bg_color(object, lv_color_hex(0x172630), LV_PART_MAIN);
lv_obj_set_style_border_width(object, 0, LV_PART_MAIN);
lv_obj_set_style_pad_all(object, 0, LV_PART_MAIN);
lv_obj_remove_flag(
object,
LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_CLICKABLE
);
return object;
}

static lv_obj_t *caption(lv_obj_t *parent, const char *text, uint32_t color)
{
lv_obj_t *label = lv_label_create(parent);

lv_label_set_text(label, text);
lv_obj_set_style_text_color(label, lv_color_hex(color), LV_PART_MAIN);
return label;
}

lv_obj_t *deck_clean_screen(void)
{
if (s_screen != NULL) {
return s_screen;
}

const int short_edge = CLEAN_SCREEN_W < CLEAN_SCREEN_H
? CLEAN_SCREEN_W
: CLEAN_SCREEN_H;
const int target_px = short_edge / 3;

s_screen = lv_obj_create(NULL);
lv_obj_remove_flag(s_screen, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_style_bg_color(s_screen, lv_color_hex(0x0b1116), LV_PART_MAIN);

lv_obj_t *title = caption(s_screen, "Screen cleaning", 0xffad22);

lv_obj_align(title, LV_ALIGN_TOP_MID, 0, short_edge / 8);
lv_obj_align_to(
caption(s_screen, "Wipe away. Hold the circle to exit.", 0x91a6b5),
title,
LV_ALIGN_OUT_BOTTOM_MID,
0,
12
);

lv_obj_t *target = panel(s_screen, target_px, target_px);

lv_obj_center(target);
lv_obj_set_style_border_width(target, 2, LV_PART_MAIN);
lv_obj_set_style_border_color(
target,
lv_color_hex(0x29404d),
LV_PART_MAIN
);
lv_obj_set_style_border_color(
target,
lv_color_hex(0xffad22),
LV_PART_MAIN | LV_STATE_PRESSED
);
lv_obj_add_flag(target, LV_OBJ_FLAG_CLICKABLE);
lv_obj_add_event_cb(target, hold_handler, LV_EVENT_ALL, NULL);
lv_obj_center(caption(target, "Hold 5s", 0xf3f7f9));

/* Two plain objects instead of a bar widget, so no board has to enable an
extra LVGL widget for the hold to show progress. */
lv_obj_t *track = panel(s_screen, target_px, 8);

lv_obj_align_to(track, target, LV_ALIGN_OUT_BOTTOM_MID, 0, 24);
s_fill = panel(track, 0, lv_pct(100));
lv_obj_align(s_fill, LV_ALIGN_LEFT_MID, 0, 0);
lv_obj_set_style_bg_color(s_fill, lv_color_hex(0xffad22), LV_PART_MAIN);
return s_screen;
}

void deck_clean_reset(void)
{
s_press_ms = 0;
set_progress(0);
}

bool deck_clean_held(int64_t now)
{
const int64_t started = s_press_ms;

return started != 0 && now - started >= DECK_CLEAN_HOLD_MS;
}
28 changes: 28 additions & 0 deletions boards/common/components/stream32_deck/deck_clean.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// The screen-cleaning overlay: a dark screen whose only live target is one
// centred hold. deck_ui decides when the lock is on; this owns what it looks
// like and how long the current hold has lasted.
#pragma once

#include <stdbool.h>
#include <stdint.h>

#include "lvgl.h"

#ifdef __cplusplus
extern "C" {
#endif

// Builds the overlay on first use and returns it. Call with the display lock
// held; the caller loads the returned screen.
lv_obj_t *deck_clean_screen(void);

// Drops any hold in progress and empties the progress bar. Display lock held.
void deck_clean_reset(void);

// True once the target has been held long enough to release the lock. Safe to
// poll from the protocol task: it reads only the press timestamp.
bool deck_clean_held(int64_t now_ms);

#ifdef __cplusplus
}
#endif
34 changes: 34 additions & 0 deletions boards/common/components/stream32_deck/deck_protocol.c
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,37 @@ static const char *handle_page(const cJSON *message)
return deck_ui_select_page((uint8_t)page);
}

static const char *handle_clean(
const cJSON *message,
char *reply,
size_t reply_capacity
)
{
const cJSON *active = cJSON_GetObjectItemCaseSensitive(message, "active");

if (!cJSON_IsBool(active)) {
return "clean-invalid";
}

const bool wanted = cJSON_IsTrue(active);
const char *error = deck_ui_set_clean(wanted);

if (error != NULL) {
return error;
}

const int written = snprintf(
reply,
reply_capacity,
"{\"type\":\"clean-ack\",\"active\":%s}",
wanted ? "true" : "false"
);

return written >= 0 && (size_t)written < reply_capacity
? NULL
: "reply-too-small";
}

static const char *handle_display(const cJSON *message)
{
const cJSON *blank_now =
Expand Down Expand Up @@ -859,6 +890,9 @@ bool deck_protocol_dispatch(
} else if (strcmp(type->valuestring, "key-update") == 0) {
*error_out = handle_key_update(message, reply, reply_capacity);
has_reply = true;
} else if (strcmp(type->valuestring, "clean") == 0) {
*error_out = handle_clean(message, reply, reply_capacity);
has_reply = true;
} else if (strcmp(type->valuestring, "page") == 0) {
*error_out = handle_page(message);
} else if (strcmp(type->valuestring, "display") == 0) {
Expand Down
67 changes: 64 additions & 3 deletions boards/common/components/stream32_deck/deck_ui.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <stdio.h>
#include <string.h>

#include "deck_clean.h"
#include "deck_storage.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
Expand Down Expand Up @@ -77,8 +78,10 @@ static bool s_forced_asleep;
static bool s_consume_touch;
static bool s_overlay_active;
static int64_t s_overlay_activity_ms;
static bool s_clean_active;

static lv_obj_t *s_deck_screen;
static lv_obj_t *s_clean_return_screen;
static uint8_t *s_key_buffers[DECK_MAX_KEYS];
static lv_image_dsc_t s_key_dsc[DECK_MAX_KEYS];

Expand Down Expand Up @@ -438,7 +441,11 @@ static void build_page_locked(uint8_t page_index)
}
}

lv_screen_load(s_deck_screen);
/* A sync that lands mid-wipe stages the grid without lifting the lock. */
if (!s_clean_active) {
lv_screen_load(s_deck_screen);
}

s_active = true;
}

Expand Down Expand Up @@ -510,11 +517,22 @@ bool deck_ui_active(void)

void deck_ui_poll(void)
{
if (s_panel_awake && !s_forced_asleep && s_idle_timeout_ms > 0 &&
/* Wiping never touches the idle timer, so the exit target would otherwise
blank out from under the person holding it. */
if (s_panel_awake && !s_clean_active && !s_forced_asleep &&
s_idle_timeout_ms > 0 &&
now_ms() - s_last_activity_ms >= s_idle_timeout_ms) {
set_panel_awake(false);
}

/* Released here rather than in the press handler, which already holds the
display lock that deck_ui_set_clean needs. A busy lock leaves the hold
standing, so the next poll retries. */
if (s_clean_active && deck_clean_held(now_ms()) &&
deck_ui_set_clean(false) == NULL) {
notify_line("{\"type\":\"clean\",\"active\":false}");
}

if (s_overlay_active &&
now_ms() - s_overlay_activity_ms >= DECK_OVERLAY_LEASE_MS) {
deck_ui_clear_overlays();
Expand Down Expand Up @@ -542,7 +560,9 @@ void deck_ui_poll(void)

bool deck_ui_handle_touch(bool pressed)
{
if (s_forced_asleep) {
/* A wipe reaches neither the host nor a local goPage switch. The hold
target lives on its own screen and never routes through here. */
if (s_clean_active || s_forced_asleep) {
return true;
}

Expand Down Expand Up @@ -814,6 +834,47 @@ const char *deck_ui_select_page(uint8_t page)
return NULL;
}

bool deck_ui_clean_active(void)
{
return s_clean_active;
}

const char *deck_ui_set_clean(bool active)
{
if (active == s_clean_active) {
return NULL;
}

if (!bsp_display_lock(1000)) {
return "display-busy";
}

deck_clean_reset();

if (active) {
s_clean_return_screen = lv_screen_active();
s_clean_active = true;
lv_screen_load(deck_clean_screen());
} else {
s_clean_active = false;
/* A sync during the wipe may have built the deck behind the lock. */
lv_screen_load(
s_active && s_deck_screen != NULL
? s_deck_screen
: s_clean_return_screen
);
}

bsp_display_unlock();
s_last_activity_ms = now_ms();

if (active && !s_forced_asleep) {
set_panel_awake(true);
}

return NULL;
}

const char *deck_ui_blank_display(void)
{
s_consume_touch = false;
Expand Down
7 changes: 7 additions & 0 deletions boards/common/components/stream32_deck/deck_ui.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ bool deck_ui_clear_overlays(void);
// first touch wakes an idle display; host-forced sleep consumes every touch.
bool deck_ui_handle_touch(bool pressed);

// Screen-cleaning lock. While it is on, a wipe cannot reach the deck: every
// touch is swallowed, including the local goPage switches. Only a five second
// hold on the centred target, or the host, releases it. The hold is reported
// through deck_ui_poll as a clean notification.
const char *deck_ui_set_clean(bool active);
bool deck_ui_clean_active(void);

// Narrow typed operations used by deck_protocol after JSON validation.
const char *deck_ui_apply_layout(
const deck_protocol_layout_t *layout,
Expand Down
4 changes: 2 additions & 2 deletions boards/elecrow-crowpanel-advanced-10-1-esp32-p4/board.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
"maxPages": 8
},
"firmware": {
"version": "0.2.0",
"version": "0.3.0",
"projectPath": "firmware",
"imageName": "elecrow-crowpanel-advanced-10-1-esp32-p4-0.2.0.bin",
"imageName": "elecrow-crowpanel-advanced-10-1-esp32-p4-0.3.0.bin",
"offset": 0
},
"capabilities": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ set(EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/../../common/components")

include($ENV{IDF_PATH}/tools/cmake/project.cmake)

set(PROJECT_VER "0.2.0")
set(PROJECT_VER "0.3.0")
project(stream32_elecrow_crowpanel_advanced_10_1_esp32_p4)
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ static void send_hello(void)
"{\"type\":\"hello\",\"protocol\":%d,\"boardId\":\"%s\","
"\"firmwareVersion\":\"%s\",\"deviceId\":\"%02x%02x%02x%02x%02x%02x\","
"\"features\":[\"display-control\",\"display-brightness\",\"display-blank\","
"\"key-update\",\"image-rle\",\"%s\"]}",
"\"key-update\",\"image-rle\",\"clean-mode\",\"%s\"]}",
STREAM32_PROTOCOL_VERSION,
STREAM32_BOARD_ID,
app->version,
Expand Down Expand Up @@ -234,6 +234,12 @@ static void handle_host_message(const char *line, size_t length)
: "UART0 connected to Stream32"
);
send_hello();

/* A cleaning lock outlives the link, so a desktop that reconnects
mid-wipe learns the panel is still locked. */
if (deck_ui_clean_active()) {
serial_write_line("{\"type\":\"clean\",\"active\":true}");
}
}
} else if (strcmp(type->valuestring, "ping") == 0) {
const cJSON *id = cJSON_GetObjectItemCaseSensitive(message, "id");
Expand Down
Loading