From c0b0cbd1417926ea3109d8fb6a6c8b935ca87a18 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Mon, 1 Sep 2025 15:19:38 +0200 Subject: [PATCH 1/7] pbsys/status: Move slot container. This is part of the status, so it should be managed here rather than in the hmi. --- lib/pbio/include/pbsys/status.h | 2 ++ lib/pbio/sys/bluetooth.c | 2 +- lib/pbio/sys/command.c | 2 +- lib/pbio/sys/hmi.c | 24 +++++---------------- lib/pbio/sys/hmi.h | 1 - lib/pbio/sys/light_matrix.c | 2 +- lib/pbio/sys/status.c | 37 +++++++++++++++++++++++++++------ lib/pbio/sys/storage.c | 4 ++-- 8 files changed, 43 insertions(+), 31 deletions(-) diff --git a/lib/pbio/include/pbsys/status.h b/lib/pbio/include/pbsys/status.h index e78c9443f..2b220af6d 100644 --- a/lib/pbio/include/pbsys/status.h +++ b/lib/pbio/include/pbsys/status.h @@ -33,6 +33,8 @@ bool pbsys_status_test(pbio_pybricks_status_t status); bool pbsys_status_test_debounce(pbio_pybricks_status_t status, bool state, uint32_t ms); uint32_t pbsys_status_get_flags(void); uint32_t pbsys_status_get_status_report(uint8_t *buf); +void pbsys_status_increment_selected_slot(bool increment); +pbio_pybricks_user_program_id_t pbsys_status_get_selected_slot(void); #endif // _PBSYS_STATUS_H_ diff --git a/lib/pbio/sys/bluetooth.c b/lib/pbio/sys/bluetooth.c index 16f2f5b81..ed46e1a3b 100644 --- a/lib/pbio/sys/bluetooth.c +++ b/lib/pbio/sys/bluetooth.c @@ -198,7 +198,7 @@ static PT_THREAD(pbsys_bluetooth_monitor_status(struct pt *pt)) { // wait for status to change or timeout PT_WAIT_UNTIL(pt, pbsys_status_get_flags() != old_status_flags || #if PBSYS_CONFIG_HMI_NUM_SLOTS - pbsys_hmi_get_selected_program_slot() != old_program_slot || + pbsys_status_get_selected_slot() != old_program_slot || #endif etimer_expired(&timer)); diff --git a/lib/pbio/sys/command.c b/lib/pbio/sys/command.c index 1885e21b0..aa6ab9c55 100644 --- a/lib/pbio/sys/command.c +++ b/lib/pbio/sys/command.c @@ -49,7 +49,7 @@ pbio_pybricks_error_t pbsys_command(const uint8_t *data, uint32_t size) { } // Use payload as program ID, otherwise use active user slot. return pbio_pybricks_error_from_pbio_error( - pbsys_main_program_request_start((size == 2 ? data[1] : pbsys_hmi_get_selected_program_slot()), PBSYS_MAIN_PROGRAM_START_REQUEST_TYPE_REMOTE)); + pbsys_main_program_request_start((size == 2 ? data[1] : pbsys_status_get_selected_slot()), PBSYS_MAIN_PROGRAM_START_REQUEST_TYPE_REMOTE)); } #if PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_REPL case PBIO_PYBRICKS_COMMAND_START_REPL: diff --git a/lib/pbio/sys/hmi.c b/lib/pbio/sys/hmi.c index 6c715b608..4505c6b32 100644 --- a/lib/pbio/sys/hmi.c +++ b/lib/pbio/sys/hmi.c @@ -37,20 +37,6 @@ #define DEBUG_PRINT(...) #endif -// The selected slot is not persistent across reboot, so that the first slot -// is always active on boot. This allows consistently starting programs without -// visibility of the display. -static uint8_t selected_slot = 0; - -/** - * Gets the currently selected program slot. - * - * @return The currently selected program slot (zero-indexed). - */ -uint8_t pbsys_hmi_get_selected_program_slot(void) { - return selected_slot; -} - void pbsys_hmi_init(void) { pbsys_status_light_init(); pbsys_hub_light_matrix_init(); @@ -110,7 +96,7 @@ static pbio_error_t pbsys_hmi_launch_program_with_button(pbio_os_state_t *state) if (pressed & PBIO_BUTTON_CENTER) { - pbio_error_t err = pbsys_main_program_request_start(selected_slot, PBSYS_MAIN_PROGRAM_START_REQUEST_TYPE_HUB_UI); + pbio_error_t err = pbsys_main_program_request_start(pbsys_status_get_selected_slot(), PBSYS_MAIN_PROGRAM_START_REQUEST_TYPE_HUB_UI); if (err == PBIO_SUCCESS) { // Program is available so we can leave this UI thread and @@ -124,14 +110,14 @@ static pbio_error_t pbsys_hmi_launch_program_with_button(pbio_os_state_t *state) } // On right, increment slot when possible. - if ((pressed & PBIO_BUTTON_RIGHT) && selected_slot < 4) { - selected_slot++; + if (pressed & PBIO_BUTTON_RIGHT) { + pbsys_status_increment_selected_slot(true); pbsys_hub_light_matrix_update_program_slot(); } // On left, decrement slot when possible. - if ((pressed & PBIO_BUTTON_LEFT) && selected_slot > 0) { - selected_slot--; + if (pressed & PBIO_BUTTON_LEFT) { + pbsys_status_increment_selected_slot(false); pbsys_hub_light_matrix_update_program_slot(); } } diff --git a/lib/pbio/sys/hmi.h b/lib/pbio/sys/hmi.h index ba561ba5f..1e62032ba 100644 --- a/lib/pbio/sys/hmi.h +++ b/lib/pbio/sys/hmi.h @@ -11,6 +11,5 @@ void pbsys_hmi_init(void); void pbsys_hmi_handle_status_change(pbsys_status_change_t event, pbio_pybricks_status_t data); void pbsys_hmi_poll(void); pbio_error_t pbsys_hmi_await_program_selection(void); -uint8_t pbsys_hmi_get_selected_program_slot(void); #endif // _PBSYS_SYS_HMI_H_ diff --git a/lib/pbio/sys/light_matrix.c b/lib/pbio/sys/light_matrix.c index 43f96c691..6da0003ec 100644 --- a/lib/pbio/sys/light_matrix.c +++ b/lib/pbio/sys/light_matrix.c @@ -68,7 +68,7 @@ static void pbsys_hub_light_matrix_show_idle_ui(uint8_t brightness) { for (uint8_t c = 0; c < pbsys_hub_light_matrix->size; c++) { bool is_on = r < 3 && c > 0 && c < 4; #if PBSYS_CONFIG_HMI_NUM_SLOTS - is_on |= (r == 4 && c == pbsys_hmi_get_selected_program_slot()); + is_on |= (r == 4 && c == pbsys_status_get_selected_slot()); #endif pbsys_hub_light_matrix_set_pixel(pbsys_hub_light_matrix, r, c, is_on ? brightness : 0); } diff --git a/lib/pbio/sys/status.c b/lib/pbio/sys/status.c index bffdb87df..6255714e1 100644 --- a/lib/pbio/sys/status.c +++ b/lib/pbio/sys/status.c @@ -23,6 +23,8 @@ static struct { uint32_t changed_time[NUM_PBIO_PYBRICKS_STATUS]; /** Currently active program identifier, if it is running according to the flags. */ pbio_pybricks_user_program_id_t program_id; + /** Currently selected program slot */ + pbio_pybricks_user_program_id_t slot; } pbsys_status; static void pbsys_status_update_flag(pbio_pybricks_status_t status, bool set) { @@ -59,16 +61,39 @@ static void pbsys_status_update_flag(pbio_pybricks_status_t status, bool set) { * @return The number of bytes written to @p buf. */ uint32_t pbsys_status_get_status_report(uint8_t *buf) { - #if PBSYS_CONFIG_HMI_NUM_SLOTS - uint8_t slot = pbsys_hmi_get_selected_program_slot(); - #else - uint8_t slot = 0; - #endif _Static_assert(PBSYS_STATUS_REPORT_SIZE == PBIO_PYBRICKS_EVENT_STATUS_REPORT_SIZE, "size of status report does not match size of event"); - return pbio_pybricks_event_status_report(buf, pbsys_status.flags, pbsys_status.program_id, slot); + return pbio_pybricks_event_status_report(buf, pbsys_status.flags, pbsys_status.program_id, pbsys_status.slot); +} + +/** + * Increments or decrements the currently active slot. + * + * It does not wrap around. This is safe to call even if the maximum or minimum + * slot is already reached. + * + * @param [in] increment @c true for increment @c false for decrement + */ +void pbsys_status_increment_selected_slot(bool increment) { + #if PBSYS_CONFIG_HMI_NUM_SLOTS + if (increment && pbsys_status.slot + 1 < PBSYS_CONFIG_HMI_NUM_SLOTS) { + pbsys_status.slot++; + } + if (!increment && pbsys_status.slot > 0) { + pbsys_status.slot--; + } + #endif +} + +/** + * Gets the currently selected program slot. + * + * @return The currently selected program slot (zero-indexed). + */ +pbio_pybricks_user_program_id_t pbsys_status_get_selected_slot(void) { + return pbsys_status.slot; } /** diff --git a/lib/pbio/sys/storage.c b/lib/pbio/sys/storage.c index c855474c4..042fcab8f 100644 --- a/lib/pbio/sys/storage.c +++ b/lib/pbio/sys/storage.c @@ -171,7 +171,7 @@ static pbio_error_t pbsys_storage_prepare_receive(void) { return PBIO_SUCCESS; #endif // PBSYS_CONFIG_STORAGE_NUM_SLOTS == 1 - incoming_slot = pbsys_hmi_get_selected_program_slot(); + incoming_slot = pbsys_status_get_selected_slot(); // There are three cases: // - The current slot is already the last used slot @@ -309,7 +309,7 @@ void pbsys_storage_get_program_data(pbsys_main_program_t *program) { // extended to providing access to the active slot. Do we really // want that though? Maybe REPL should just really be independent. // - uint8_t slot = program->id < PBSYS_CONFIG_STORAGE_NUM_SLOTS ? program->id : pbsys_hmi_get_selected_program_slot(); + uint8_t slot = program->id < PBSYS_CONFIG_STORAGE_NUM_SLOTS ? program->id : pbsys_status_get_selected_slot(); // Only requested slot is available to user. program->code_start = map->program_data + map->slot_info[slot].offset; From e4936a5383ddca4a1193f0528c94dab67c1ff26c Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Mon, 1 Sep 2025 15:50:16 +0200 Subject: [PATCH 2/7] pbio/sys/light: Move breathe animation. This animation runs in user space, so it doesn't belong with the status event code. It used to be closely tied to the event messages since this used to be the only way to know that a program was started. Now we can just start the animation when the program starts. This will also allow the light instance to reset the light to the default behavior. --- lib/pbio/include/pbio/light.h | 4 ++++ lib/pbio/src/light/color_light.c | 35 ++++++++++++++++++++++++++++++++ lib/pbio/sys/light.c | 34 ------------------------------- lib/pbio/sys/main.c | 9 ++++++++ 4 files changed, 48 insertions(+), 34 deletions(-) diff --git a/lib/pbio/include/pbio/light.h b/lib/pbio/include/pbio/light.h index 3586632bf..fdf53bc19 100644 --- a/lib/pbio/include/pbio/light.h +++ b/lib/pbio/include/pbio/light.h @@ -38,6 +38,7 @@ pbio_error_t pbio_color_light_on_hsv(pbio_color_light_t *light, const pbio_color pbio_error_t pbio_color_light_on(pbio_color_light_t *light, pbio_color_t color); pbio_error_t pbio_color_light_off(pbio_color_light_t *light); void pbio_color_light_start_blink_animation(pbio_color_light_t *light, const pbio_color_hsv_t *hsv, const uint16_t *cells); +void pbio_color_light_start_breathe_animation(pbio_color_light_t *light, uint16_t hue); void pbio_color_light_start_animation(pbio_color_light_t *light, uint16_t interval, const pbio_color_compressed_hsv_t *cells); #else // PBIO_CONFIG_LIGHT @@ -57,6 +58,9 @@ static inline pbio_error_t pbio_color_light_off(pbio_color_light_t *light) { static inline void pbio_color_light_start_blink_animation(pbio_color_light_t *light, const pbio_color_hsv_t *hsv, const uint16_t *cells) { } +static inline void pbio_color_light_start_breathe_animation(pbio_color_light_t *light, uint16_t hue) { +} + static inline void pbio_color_light_start_animation(pbio_color_light_t *light, uint16_t interval, const pbio_color_compressed_hsv_t *cells) { } diff --git a/lib/pbio/src/light/color_light.c b/lib/pbio/src/light/color_light.c index 50cc29a16..26deafaf0 100644 --- a/lib/pbio/src/light/color_light.c +++ b/lib/pbio/src/light/color_light.c @@ -140,6 +140,41 @@ void pbio_color_light_start_blink_animation(pbio_color_light_t *light, const pbi pbio_light_animation_start(&light->animation); } +static uint8_t breathe_animation_progress; +static uint16_t breathe_animation_hue; + +static uint32_t pbio_color_light_breathe_next(pbio_light_animation_t *animation) { + + pbio_color_light_t *light = PBIO_CONTAINER_OF(animation, pbio_color_light_t, animation); + + // The brightness pattern has the form /\ through which we cycle in N steps. + // It is reset back to the start when the user program starts. + const uint8_t animation_progress_max = 200; + + pbio_color_hsv_t hsv = { + .h = breathe_animation_hue, + .s = 100, + .v = breathe_animation_progress < animation_progress_max / 2 ? + breathe_animation_progress : + animation_progress_max - breathe_animation_progress, + }; + + light->funcs->set_hsv(light, &hsv); + + // This increment controls the speed of the pattern and wraps on completion + breathe_animation_progress = (breathe_animation_progress + 4) % animation_progress_max; + + return 40; +} + +void pbio_color_light_start_breathe_animation(pbio_color_light_t *light, uint16_t hue) { + pbio_color_light_stop_animation(light); + breathe_animation_progress = 0; + breathe_animation_hue = hue; + pbio_light_animation_init(&light->animation, pbio_color_light_breathe_next); + pbio_light_animation_start(&light->animation); +} + static uint32_t pbio_color_light_animate_next(pbio_light_animation_t *animation) { pbio_color_light_t *light = PBIO_CONTAINER_OF(animation, pbio_color_light_t, animation); diff --git a/lib/pbio/sys/light.c b/lib/pbio/sys/light.c index ee9690ce6..bcd034098 100644 --- a/lib/pbio/sys/light.c +++ b/lib/pbio/sys/light.c @@ -222,44 +222,10 @@ static void pbsys_status_light_update_patterns(void) { } } -#if PBSYS_CONFIG_STATUS_LIGHT_STATE_ANIMATIONS -static uint8_t animation_progress; - -static uint32_t default_user_program_light_animation_next(pbio_light_animation_t *animation) { - // The brightness pattern has the form /\ through which we cycle in N steps. - // It is reset back to the start when the user program starts. - const uint8_t animation_progress_max = 200; - - pbio_color_hsv_t hsv = { - .h = PBSYS_CONFIG_STATUS_LIGHT_STATE_ANIMATIONS_HUE, - .s = 100, - .v = animation_progress < animation_progress_max / 2 ? - animation_progress : - animation_progress_max - animation_progress, - }; - - pbsys_status_light_main->funcs->set_hsv(pbsys_status_light_main, &hsv); - - // This increment controls the speed of the pattern and wraps on completion - animation_progress = (animation_progress + 4) % animation_progress_max; - - return 40; -} -#endif // PBSYS_CONFIG_STATUS_LIGHT_STATE_ANIMATIONS - void pbsys_status_light_handle_status_change(pbsys_status_change_t event, pbio_pybricks_status_t data) { if (event == PBSYS_STATUS_CHANGE_SET || event == PBSYS_STATUS_CHANGE_CLEARED) { pbsys_status_light_update_patterns(); } - if (event == PBSYS_STATUS_CHANGE_SET && (pbio_pybricks_status_t)(intptr_t)data == PBIO_PYBRICKS_STATUS_USER_PROGRAM_RUNNING) { - #if PBSYS_CONFIG_STATUS_LIGHT_STATE_ANIMATIONS - animation_progress = 0; - pbio_light_animation_init(&pbsys_status_light_main->animation, default_user_program_light_animation_next); - pbio_light_animation_start(&pbsys_status_light_main->animation); - #else - pbio_color_light_off(pbsys_status_light_main); - #endif // PBSYS_CONFIG_STATUS_LIGHT_STATE_ANIMATIONS - } } /** diff --git a/lib/pbio/sys/main.c b/lib/pbio/sys/main.c index 87f5f3ba4..1611be3f5 100644 --- a/lib/pbio/sys/main.c +++ b/lib/pbio/sys/main.c @@ -23,6 +23,7 @@ #include "program_stop.h" #include "storage.h" #include +#include #include // Singleton with information about the currently (or soon) active program. @@ -103,6 +104,14 @@ int main(int argc, char **argv) { pbsys_host_stdin_set_callback(pbsys_main_stdin_event); pbsys_hub_light_matrix_handle_user_program_start(true); + #if PBSYS_CONFIG_STATUS_LIGHT + #if PBSYS_CONFIG_STATUS_LIGHT_STATE_ANIMATIONS + pbio_color_light_start_breathe_animation(pbsys_status_light_main, PBSYS_CONFIG_STATUS_LIGHT_STATE_ANIMATIONS_HUE); + #else + pbio_color_light_off(pbsys_status_light_main); + #endif + #endif + // Handle pending events triggered by the status change, such as // starting status light animation. while (pbio_os_run_processes_once()) { From 1503fcc2b5596f430b7d0ef86ba45e27fbb8fff4 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Mon, 1 Sep 2025 16:01:02 +0200 Subject: [PATCH 3/7] pbio/sys/light: Don't pass unused status data around. This was passed to the light status update function, but it does the same regardless of setting or clearing. This is not related to the pbsys_status_set function, which takes a bool for set/clear. So pbsys_status_change_t made things a bit confusing now that it is no longer used. --- lib/pbio/include/pbsys/status.h | 10 ---------- lib/pbio/sys/hmi.c | 4 ---- lib/pbio/sys/hmi.h | 1 - lib/pbio/sys/light.c | 8 +------- lib/pbio/sys/light.h | 4 ++-- lib/pbio/sys/status.c | 5 +++-- 6 files changed, 6 insertions(+), 26 deletions(-) diff --git a/lib/pbio/include/pbsys/status.h b/lib/pbio/include/pbsys/status.h index 2b220af6d..00a73a75a 100644 --- a/lib/pbio/include/pbsys/status.h +++ b/lib/pbio/include/pbsys/status.h @@ -16,16 +16,6 @@ #define PBSYS_STATUS_REPORT_SIZE 7 -/** - * Status flag change. - */ -typedef enum { - /** System status indicator was set. */ - PBSYS_STATUS_CHANGE_SET, - /** System status indicator was cleared. */ - PBSYS_STATUS_CHANGE_CLEARED, -} pbsys_status_change_t; - void pbsys_status_set_program_id(pbio_pybricks_user_program_id_t program_id); void pbsys_status_set(pbio_pybricks_status_t status); void pbsys_status_clear(pbio_pybricks_status_t status); diff --git a/lib/pbio/sys/hmi.c b/lib/pbio/sys/hmi.c index 4505c6b32..205d23aaf 100644 --- a/lib/pbio/sys/hmi.c +++ b/lib/pbio/sys/hmi.c @@ -42,10 +42,6 @@ void pbsys_hmi_init(void) { pbsys_hub_light_matrix_init(); } -void pbsys_hmi_handle_status_change(pbsys_status_change_t event, pbio_pybricks_status_t data) { - pbsys_status_light_handle_status_change(event, data); -} - /** * Polls the HMI. * diff --git a/lib/pbio/sys/hmi.h b/lib/pbio/sys/hmi.h index 1e62032ba..0087c2882 100644 --- a/lib/pbio/sys/hmi.h +++ b/lib/pbio/sys/hmi.h @@ -8,7 +8,6 @@ #include void pbsys_hmi_init(void); -void pbsys_hmi_handle_status_change(pbsys_status_change_t event, pbio_pybricks_status_t data); void pbsys_hmi_poll(void); pbio_error_t pbsys_hmi_await_program_selection(void); diff --git a/lib/pbio/sys/light.c b/lib/pbio/sys/light.c index bcd034098..9532e00e2 100644 --- a/lib/pbio/sys/light.c +++ b/lib/pbio/sys/light.c @@ -182,7 +182,7 @@ void pbsys_status_light_init(void) { #endif } -static void pbsys_status_light_update_patterns(void) { +void pbsys_status_light_handle_status_change(void) { // Warning pattern precedence. pbsys_status_light_indication_warning_t warning_indication = PBSYS_STATUS_LIGHT_INDICATION_WARNING_NONE; @@ -222,12 +222,6 @@ static void pbsys_status_light_update_patterns(void) { } } -void pbsys_status_light_handle_status_change(pbsys_status_change_t event, pbio_pybricks_status_t data) { - if (event == PBSYS_STATUS_CHANGE_SET || event == PBSYS_STATUS_CHANGE_CLEARED) { - pbsys_status_light_update_patterns(); - } -} - /** * Advances the light to the next state in the pattern. * diff --git a/lib/pbio/sys/light.h b/lib/pbio/sys/light.h index c13fa0b69..0721bbbc6 100644 --- a/lib/pbio/sys/light.h +++ b/lib/pbio/sys/light.h @@ -11,11 +11,11 @@ #if PBSYS_CONFIG_STATUS_LIGHT void pbsys_status_light_init(void); -void pbsys_status_light_handle_status_change(pbsys_status_change_t event, pbio_pybricks_status_t data); +void pbsys_status_light_handle_status_change(void); void pbsys_status_light_poll(void); #else #define pbsys_status_light_init() -#define pbsys_status_light_handle_status_change(event, data) +#define pbsys_status_light_handle_status_change() #define pbsys_status_light_poll() #endif diff --git a/lib/pbio/sys/status.c b/lib/pbio/sys/status.c index 6255714e1..25999c04d 100644 --- a/lib/pbio/sys/status.c +++ b/lib/pbio/sys/status.c @@ -15,6 +15,7 @@ #include #include "hmi.h" +#include "light.h" static struct { /** Status indications as bit flags */ @@ -38,10 +39,10 @@ static void pbsys_status_update_flag(pbio_pybricks_status_t status, bool set) { pbsys_status.flags = new_flags; pbsys_status.changed_time[status] = pbdrv_clock_get_ms(); - // hmi is the only subscriber to status changes, so call its handler + // status light is the only subscriber to status changes, so call its handler // directly. All other processes just poll the status as needed. If we ever // need more subscribers, we could register callbacks and call them here. - pbsys_hmi_handle_status_change(set ? PBSYS_STATUS_CHANGE_SET : PBSYS_STATUS_CHANGE_CLEARED, status); + pbsys_status_light_handle_status_change(); // Other processes may be awaiting status changes, so poll. pbio_os_request_poll(); From db9d33b8684b10d449d8121a09997c247ab63218 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Mon, 1 Sep 2025 16:09:04 +0200 Subject: [PATCH 4/7] pbio/sys/status: Rename pbio_pybricks_status_t. The status used to be just flags, but contains other statuses too now. Rename the flag part to pbio_pybricks_status_flags_t to make this a bit clearer. --- lib/pbio/include/pbio/protocol.h | 6 +++--- lib/pbio/include/pbsys/status.h | 8 ++++---- lib/pbio/sys/status.c | 10 +++++----- lib/pbio/test/sys/test_status.c | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/pbio/include/pbio/protocol.h b/lib/pbio/include/pbio/protocol.h index 992301c34..5d2f35f40 100644 --- a/lib/pbio/include/pbio/protocol.h +++ b/lib/pbio/include/pbio/protocol.h @@ -235,7 +235,7 @@ typedef enum { * Status report event. * * The payload is one 32-bit little-endian unsigned integer containing - * ::pbio_pybricks_status_t flags and a one byte program identifier + * ::pbio_pybricks_status_flags_t flags and a one byte program identifier * representing the currently active program if it is running. * * @since Pybricks Profile v1.0.0. Program identifier added in Pybricks Profile v1.4.0. @@ -331,12 +331,12 @@ typedef enum { PBIO_PYBRICKS_STATUS_BLE_HOST_CONNECTED = 9, /** Total number of indications. */ NUM_PBIO_PYBRICKS_STATUS, -} pbio_pybricks_status_t; +} pbio_pybricks_status_flags_t; /** * Converts a status value to a bit flag. * - * @param [in] status A ::pbio_pybricks_status_t value. + * @param [in] status A ::pbio_pybricks_status_flags_t value. * @return A bit flag corresponding to @p status. */ #define PBIO_PYBRICKS_STATUS_FLAG(status) (1 << status) diff --git a/lib/pbio/include/pbsys/status.h b/lib/pbio/include/pbsys/status.h index 00a73a75a..ab2cd04b5 100644 --- a/lib/pbio/include/pbsys/status.h +++ b/lib/pbio/include/pbsys/status.h @@ -17,10 +17,10 @@ #define PBSYS_STATUS_REPORT_SIZE 7 void pbsys_status_set_program_id(pbio_pybricks_user_program_id_t program_id); -void pbsys_status_set(pbio_pybricks_status_t status); -void pbsys_status_clear(pbio_pybricks_status_t status); -bool pbsys_status_test(pbio_pybricks_status_t status); -bool pbsys_status_test_debounce(pbio_pybricks_status_t status, bool state, uint32_t ms); +void pbsys_status_set(pbio_pybricks_status_flags_t status); +void pbsys_status_clear(pbio_pybricks_status_flags_t status); +bool pbsys_status_test(pbio_pybricks_status_flags_t status); +bool pbsys_status_test_debounce(pbio_pybricks_status_flags_t status, bool state, uint32_t ms); uint32_t pbsys_status_get_flags(void); uint32_t pbsys_status_get_status_report(uint8_t *buf); void pbsys_status_increment_selected_slot(bool increment); diff --git a/lib/pbio/sys/status.c b/lib/pbio/sys/status.c index 25999c04d..7608ddc7f 100644 --- a/lib/pbio/sys/status.c +++ b/lib/pbio/sys/status.c @@ -28,7 +28,7 @@ static struct { pbio_pybricks_user_program_id_t slot; } pbsys_status; -static void pbsys_status_update_flag(pbio_pybricks_status_t status, bool set) { +static void pbsys_status_update_flag(pbio_pybricks_status_flags_t status, bool set) { uint32_t new_flags = set ? pbsys_status.flags | PBIO_PYBRICKS_STATUS_FLAG(status) : pbsys_status.flags & ~PBIO_PYBRICKS_STATUS_FLAG(status); if (pbsys_status.flags == new_flags) { @@ -112,7 +112,7 @@ void pbsys_status_set_program_id(pbio_pybricks_user_program_id_t program_id) { * Sets a system status status indication. * @param [in] status The status indication to set. */ -void pbsys_status_set(pbio_pybricks_status_t status) { +void pbsys_status_set(pbio_pybricks_status_flags_t status) { assert(status < NUM_PBIO_PYBRICKS_STATUS); pbsys_status_update_flag(status, true); } @@ -121,7 +121,7 @@ void pbsys_status_set(pbio_pybricks_status_t status) { * Clears a system status status indication. * @param [in] status The status indication to clear. */ -void pbsys_status_clear(pbio_pybricks_status_t status) { +void pbsys_status_clear(pbio_pybricks_status_flags_t status) { assert(status < NUM_PBIO_PYBRICKS_STATUS); pbsys_status_update_flag(status, false); } @@ -131,7 +131,7 @@ void pbsys_status_clear(pbio_pybricks_status_t status) { * @param [in] status The status indication to to test. * @return *true* if @p status is set, otherwise *false*. */ -bool pbsys_status_test(pbio_pybricks_status_t status) { +bool pbsys_status_test(pbio_pybricks_status_flags_t status) { assert(status < NUM_PBIO_PYBRICKS_STATUS); return !!(pbsys_status.flags & PBIO_PYBRICKS_STATUS_FLAG(status)); } @@ -148,7 +148,7 @@ bool pbsys_status_test(pbio_pybricks_status_t status) { * @return *true* if @p status has been set to @p state for at * least @p ms, otherwise *false*. */ -bool pbsys_status_test_debounce(pbio_pybricks_status_t status, bool state, uint32_t ms) { +bool pbsys_status_test_debounce(pbio_pybricks_status_flags_t status, bool state, uint32_t ms) { assert(status < NUM_PBIO_PYBRICKS_STATUS); if (pbsys_status_test(status) != state) { return false; diff --git a/lib/pbio/test/sys/test_status.c b/lib/pbio/test/sys/test_status.c index 3edd24842..9dbcc504a 100644 --- a/lib/pbio/test/sys/test_status.c +++ b/lib/pbio/test/sys/test_status.c @@ -36,7 +36,7 @@ static PT_THREAD(test_status(struct pt *pt)) { process_start(&status_test_process); // use the last valid flag for edge case - static const pbio_pybricks_status_t test_flag = NUM_PBIO_PYBRICKS_STATUS - 1; + static const pbio_pybricks_status_flags_t test_flag = NUM_PBIO_PYBRICKS_STATUS - 1; // ensure flags are initialized as unset tt_want(!pbsys_status_test(test_flag)); From b46250cd8ab9d8962932b5112f52cda408d68252 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Mon, 1 Sep 2025 16:30:26 +0200 Subject: [PATCH 5/7] pbio/sys/status: Drop duplicate size constant. This is a protocol constant, so we don't need to hardcode something of the same value elsewhere and then statically assert equality. --- lib/pbio/drv/usb/usb_stm32.c | 2 +- lib/pbio/include/pbsys/status.h | 2 -- lib/pbio/sys/status.c | 6 +----- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/pbio/drv/usb/usb_stm32.c b/lib/pbio/drv/usb/usb_stm32.c index 3ef7c494b..88353a071 100644 --- a/lib/pbio/drv/usb/usb_stm32.c +++ b/lib/pbio/drv/usb/usb_stm32.c @@ -36,7 +36,7 @@ PROCESS(pbdrv_usb_process, "USB"); // to/from FIFOs in 32-bit chunks. static uint8_t usb_in_buf[USBD_PYBRICKS_MAX_PACKET_SIZE] __aligned(4); static uint8_t usb_response_buf[PBIO_PYBRICKS_USB_MESSAGE_SIZE(sizeof(uint32_t))] __aligned(4); -static uint8_t usb_status_buf[PBIO_PYBRICKS_USB_MESSAGE_SIZE(PBSYS_STATUS_REPORT_SIZE)] __aligned(4); +static uint8_t usb_status_buf[PBIO_PYBRICKS_USB_MESSAGE_SIZE(PBIO_PYBRICKS_EVENT_STATUS_REPORT_SIZE)] __aligned(4); static uint8_t usb_stdout_buf[USBD_PYBRICKS_MAX_PACKET_SIZE] __aligned(4); static volatile uint32_t usb_in_sz; static volatile uint32_t usb_response_sz; diff --git a/lib/pbio/include/pbsys/status.h b/lib/pbio/include/pbsys/status.h index ab2cd04b5..a5a080aa7 100644 --- a/lib/pbio/include/pbsys/status.h +++ b/lib/pbio/include/pbsys/status.h @@ -14,8 +14,6 @@ #include -#define PBSYS_STATUS_REPORT_SIZE 7 - void pbsys_status_set_program_id(pbio_pybricks_user_program_id_t program_id); void pbsys_status_set(pbio_pybricks_status_flags_t status); void pbsys_status_clear(pbio_pybricks_status_flags_t status); diff --git a/lib/pbio/sys/status.c b/lib/pbio/sys/status.c index 7608ddc7f..5b161a0ce 100644 --- a/lib/pbio/sys/status.c +++ b/lib/pbio/sys/status.c @@ -56,16 +56,12 @@ static void pbsys_status_update_flag(pbio_pybricks_status_flags_t status, bool s /** * Gets the Pybricks status report and writes it to @p buf. * - * The buffer must be at least ::PBSYS_STATUS_REPORT_SIZE bytes. + * The buffer must be at least ::PBIO_PYBRICKS_EVENT_STATUS_REPORT_SIZE bytes. * * @param [in] buf The buffer to hold the binary data. * @return The number of bytes written to @p buf. */ uint32_t pbsys_status_get_status_report(uint8_t *buf) { - - _Static_assert(PBSYS_STATUS_REPORT_SIZE == PBIO_PYBRICKS_EVENT_STATUS_REPORT_SIZE, - "size of status report does not match size of event"); - return pbio_pybricks_event_status_report(buf, pbsys_status.flags, pbsys_status.program_id, pbsys_status.slot); } From c11c419bb134aab126ab55fc4d6cdab38e13f5e1 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Mon, 1 Sep 2025 19:07:53 +0200 Subject: [PATCH 6/7] pbio/sys/status: Emit all status changes. Not just status flags, but emit changes to slot and programs too. --- lib/pbio/sys/status.c | 45 +++++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/lib/pbio/sys/status.c b/lib/pbio/sys/status.c index 5b161a0ce..5dc5f4533 100644 --- a/lib/pbio/sys/status.c +++ b/lib/pbio/sys/status.c @@ -7,11 +7,8 @@ #include #include -#include - #include -#include #include #include "hmi.h" @@ -28,6 +25,26 @@ static struct { pbio_pybricks_user_program_id_t slot; } pbsys_status; +/** + * Let other processes and external hosts know that the status changed. + */ +static void pbsys_status_update_emit(void) { + + uint8_t buf[PBIO_PYBRICKS_EVENT_STATUS_REPORT_SIZE]; + pbio_pybricks_event_status_report(buf, pbsys_status.flags, pbsys_status.program_id, pbsys_status.slot); + + // TODO: Here we will call pbsys_host_schedule_status_update(buf), which + // sets the status in host drivers such as USB and bluetooth so they + // can write it out when possible. + + // Other processes may be awaiting status changes, so poll. + pbio_os_request_poll(); + + // REVISIT: Can be deleted once all processes that poll the status are + // updated to use the new pbio os event loop. + process_post(PROCESS_BROADCAST, PROCESS_EVENT_COM, NULL); +} + static void pbsys_status_update_flag(pbio_pybricks_status_flags_t status, bool set) { uint32_t new_flags = set ? pbsys_status.flags | PBIO_PYBRICKS_STATUS_FLAG(status) : pbsys_status.flags & ~PBIO_PYBRICKS_STATUS_FLAG(status); @@ -39,18 +56,11 @@ static void pbsys_status_update_flag(pbio_pybricks_status_flags_t status, bool s pbsys_status.flags = new_flags; pbsys_status.changed_time[status] = pbdrv_clock_get_ms(); - // status light is the only subscriber to status changes, so call its handler - // directly. All other processes just poll the status as needed. If we ever - // need more subscribers, we could register callbacks and call them here. - pbsys_status_light_handle_status_change(); - - // Other processes may be awaiting status changes, so poll. - pbio_os_request_poll(); - - // REVISIT: Can be deleted once all processes that poll the status are - // updated to use the new pbio os event loop. - process_post(PROCESS_BROADCAST, PROCESS_EVENT_COM, NULL); + // Let everyone know about new flags. + pbsys_status_update_emit(); + // Status light may need updating if flags have changed. + pbsys_status_light_handle_status_change(); } /** @@ -77,9 +87,11 @@ void pbsys_status_increment_selected_slot(bool increment) { #if PBSYS_CONFIG_HMI_NUM_SLOTS if (increment && pbsys_status.slot + 1 < PBSYS_CONFIG_HMI_NUM_SLOTS) { pbsys_status.slot++; + pbsys_status_update_emit(); } if (!increment && pbsys_status.slot > 0) { pbsys_status.slot--; + pbsys_status_update_emit(); } #endif } @@ -101,7 +113,12 @@ pbio_pybricks_user_program_id_t pbsys_status_get_selected_slot(void) { * @param [in] program_id The identifier to set. */ void pbsys_status_set_program_id(pbio_pybricks_user_program_id_t program_id) { + if (pbsys_status.program_id == program_id) { + return; + } + pbsys_status.program_id = program_id; + pbsys_status_update_emit(); } /** From 19d921b33851c30db0118dc5dcfad9ea366f44d0 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Tue, 9 Sep 2025 15:28:19 +0200 Subject: [PATCH 7/7] pbio/sys/light_matrix: Disable animation for EV3. Having the light matrix for the UI is useful for now, but we don't need the animation, which gets in the way of other developments. The hub light serves as an indicator that the program is running. --- lib/pbio/sys/light_matrix.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/pbio/sys/light_matrix.c b/lib/pbio/sys/light_matrix.c index 6da0003ec..c125864c2 100644 --- a/lib/pbio/sys/light_matrix.c +++ b/lib/pbio/sys/light_matrix.c @@ -158,6 +158,13 @@ static uint32_t pbsys_hub_light_matrix_user_program_animation_next(pbio_light_an * @param start @c true for start or @c false for stop. */ void pbsys_hub_light_matrix_handle_user_program_start(bool start) { + + #if PBSYS_CONFIG_HUB_LIGHT_MATRIX_DISPLAY + pbio_image_fill(pbdrv_display_get_image(), 0); + pbdrv_display_update(); + return; + #endif + if (start) { // The user animation updates only a subset of pixels to save time, // so the rest must be cleared before it starts.