From 4b245ac0bc0924713ee2f59054db750a881dc77a Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Tue, 23 Sep 2025 13:17:59 +0200 Subject: [PATCH 01/12] pbio/sys/core: Consolidate power button logic. This keeps the power button logic in one place. This used to be separate because the hmi logic was implicit in pbio/sys/bluetooth, but we don't have that anymore. By splitting this out of the sys/hmi module, we can make a platform specific HMI. --- lib/pbio/sys/core.c | 5 +++-- lib/pbio/sys/hmi.c | 27 --------------------------- lib/pbio/sys/hmi.h | 2 -- lib/pbio/sys/program_stop.c | 17 ++++++++++++++--- 4 files changed, 17 insertions(+), 34 deletions(-) diff --git a/lib/pbio/sys/core.c b/lib/pbio/sys/core.c index 2fdc16d49..87b23922e 100644 --- a/lib/pbio/sys/core.c +++ b/lib/pbio/sys/core.c @@ -35,8 +35,8 @@ PROCESS_THREAD(pbsys_system_process, ev, data) { if (ev == PROCESS_EVENT_TIMER && etimer_expired(&timer)) { etimer_reset(&timer); pbsys_battery_poll(); - pbsys_hmi_poll(); pbsys_program_stop_poll(); + pbsys_status_light_poll(); // keep the hub from resetting itself pbdrv_watchdog_update(); @@ -53,7 +53,8 @@ void pbsys_init(void) { pbsys_battery_init(); pbsys_host_init(); - pbsys_hmi_init(); + pbsys_status_light_init(); + pbsys_hub_light_matrix_init(); process_start(&pbsys_system_process); diff --git a/lib/pbio/sys/hmi.c b/lib/pbio/sys/hmi.c index a439afe53..0e425741c 100644 --- a/lib/pbio/sys/hmi.c +++ b/lib/pbio/sys/hmi.c @@ -37,33 +37,6 @@ #define DEBUG_PRINT(...) #endif -void pbsys_hmi_init(void) { - pbsys_status_light_init(); - pbsys_hub_light_matrix_init(); -} - -/** - * Polls the HMI. - * - * This is called periodically to update the current HMI state. - */ -void pbsys_hmi_poll(void) { - pbio_button_flags_t btn = pbdrv_button_get_pressed(); - - if (btn & PBIO_BUTTON_CENTER) { - pbsys_status_set(PBIO_PYBRICKS_STATUS_POWER_BUTTON_PRESSED); - - // power off when button is held down for 2 seconds - if (pbsys_status_test_debounce(PBIO_PYBRICKS_STATUS_POWER_BUTTON_PRESSED, true, 2000)) { - pbsys_status_set(PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST); - } - } else { - pbsys_status_clear(PBIO_PYBRICKS_STATUS_POWER_BUTTON_PRESSED); - } - - pbsys_status_light_poll(); -} - /** * Registers button presses to update the visual UI state and request the * launch of a program. diff --git a/lib/pbio/sys/hmi.h b/lib/pbio/sys/hmi.h index 0087c2882..c2357345f 100644 --- a/lib/pbio/sys/hmi.h +++ b/lib/pbio/sys/hmi.h @@ -7,8 +7,6 @@ #include #include -void pbsys_hmi_init(void); -void pbsys_hmi_poll(void); pbio_error_t pbsys_hmi_await_program_selection(void); #endif // _PBSYS_SYS_HMI_H_ diff --git a/lib/pbio/sys/program_stop.c b/lib/pbio/sys/program_stop.c index 039055d3b..2e65fc2f3 100644 --- a/lib/pbio/sys/program_stop.c +++ b/lib/pbio/sys/program_stop.c @@ -53,18 +53,29 @@ void pbsys_program_stop_set_buttons(pbio_button_flags_t buttons) { } /** - * This is called periodically to monitor the user program. + * This is called periodically to monitor the power button and shutdown requests. */ void pbsys_program_stop_poll(void) { + pbio_button_flags_t btn = pbdrv_button_get_pressed(); + + if (btn & PBIO_BUTTON_CENTER) { + pbsys_status_set(PBIO_PYBRICKS_STATUS_POWER_BUTTON_PRESSED); + + // power off when button is held down for 2 seconds + if (pbsys_status_test_debounce(PBIO_PYBRICKS_STATUS_POWER_BUTTON_PRESSED, true, 2000)) { + pbsys_status_set(PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST); + } + } else { + pbsys_status_clear(PBIO_PYBRICKS_STATUS_POWER_BUTTON_PRESSED); + } + // Cancel user application program if shutdown was requested. if (pbsys_status_test(PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST)) { pbsys_program_stop(true); return; } - pbio_button_flags_t btn = pbdrv_button_get_pressed(); - if (!stop_buttons) { return; } From cb344aeb62e4803fd522d12dc82677f9eec2a86d Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 25 Sep 2025 14:22:25 +0200 Subject: [PATCH 02/12] pbio/sys/light_matrix: Fix crash on unfinished boot animation. If we start a program on boot, the startup animation isn't finished. If we start the run animation then, we get a crash on running pbio_light_animation_stop_all after running the user program. Plenty of time wasted on this one. There is nothing wrong with the MicroPython exception handler. Thank you for reading. --- lib/pbio/sys/light_matrix.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pbio/sys/light_matrix.c b/lib/pbio/sys/light_matrix.c index 6c5e62139..33ab460e4 100644 --- a/lib/pbio/sys/light_matrix.c +++ b/lib/pbio/sys/light_matrix.c @@ -166,6 +166,7 @@ void pbsys_hub_light_matrix_handle_user_program_start(void) { // The user animation updates only a subset of pixels to save time, // so the rest must be cleared before it starts. pbsys_hub_light_matrix_user_program_animation_clear(); + pbio_light_animation_stop_all(); pbio_light_animation_init(&pbsys_hub_light_matrix->animation, pbsys_hub_light_matrix_user_program_animation_next); pbio_light_animation_start(&pbsys_hub_light_matrix->animation); } From cb997284a0337c601c8c05999f9eb5a1e3848bd9 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 25 Sep 2025 14:31:47 +0200 Subject: [PATCH 03/12] pbio/sys/hmi: Split to functional variants. Systems with an LCD will have their own UI, so we shouldn't be trying to share everything across all systems. --- bricks/_common/sources.mk | 4 +- lib/pbio/platform/city_hub/pbsysconfig.h | 2 + lib/pbio/platform/essential_hub/pbsysconfig.h | 2 + lib/pbio/platform/ev3/pbsysconfig.h | 5 +- lib/pbio/platform/move_hub/pbsysconfig.h | 2 + lib/pbio/platform/nxt/pbsysconfig.h | 7 +- lib/pbio/platform/prime_hub/pbsysconfig.h | 7 +- lib/pbio/platform/technic_hub/pbsysconfig.h | 4 +- lib/pbio/sys/hmi.c | 253 ------------------ lib/pbio/sys/hmi.h | 12 + lib/pbio/sys/hmi_lcd.c | 147 ++++++++++ lib/pbio/sys/hmi_none.c | 32 +++ lib/pbio/sys/hmi_pup.c | 239 +++++++++++++++++ lib/pbio/sys/storage_settings.c | 8 +- pybricks/common/pb_type_ble.c | 4 +- .../iodevices/pb_type_iodevices_lwp3device.c | 4 +- .../pb_type_iodevices_xbox_controller.c | 4 +- 17 files changed, 464 insertions(+), 272 deletions(-) delete mode 100644 lib/pbio/sys/hmi.c create mode 100644 lib/pbio/sys/hmi_lcd.c create mode 100644 lib/pbio/sys/hmi_none.c create mode 100644 lib/pbio/sys/hmi_pup.c diff --git a/bricks/_common/sources.mk b/bricks/_common/sources.mk index 003df3b56..8772b8733 100644 --- a/bricks/_common/sources.mk +++ b/bricks/_common/sources.mk @@ -235,7 +235,9 @@ PBIO_SRC_C = $(addprefix lib/pbio/,\ sys/battery.c \ sys/command.c \ sys/core.c \ - sys/hmi.c \ + sys/hmi_lcd.c \ + sys/hmi_pup.c \ + sys/hmi_none.c \ sys/host.c \ sys/light_matrix.c \ sys/light.c \ diff --git a/lib/pbio/platform/city_hub/pbsysconfig.h b/lib/pbio/platform/city_hub/pbsysconfig.h index 9c8e89fa9..33a61c758 100644 --- a/lib/pbio/platform/city_hub/pbsysconfig.h +++ b/lib/pbio/platform/city_hub/pbsysconfig.h @@ -10,6 +10,8 @@ #define PBSYS_CONFIG_FEATURE_PROGRAM_FORMAT_MULTI_MPY_V6_3_NATIVE (0) #define PBSYS_CONFIG_BATTERY_CHARGER (0) #define PBSYS_CONFIG_BLUETOOTH (1) +#define PBSYS_CONFIG_HMI (1) +#define PBSYS_CONFIG_HMI_PUP (1) #define PBSYS_CONFIG_HMI_NUM_SLOTS (0) #define PBSYS_CONFIG_HUB_LIGHT_MATRIX (0) #define PBSYS_CONFIG_HOST (1) diff --git a/lib/pbio/platform/essential_hub/pbsysconfig.h b/lib/pbio/platform/essential_hub/pbsysconfig.h index 80d436dc9..fa8f3272f 100644 --- a/lib/pbio/platform/essential_hub/pbsysconfig.h +++ b/lib/pbio/platform/essential_hub/pbsysconfig.h @@ -8,6 +8,8 @@ #define PBSYS_CONFIG_FEATURE_PROGRAM_FORMAT_MULTI_MPY_V6_3_NATIVE (1) #define PBSYS_CONFIG_BATTERY_CHARGER (1) #define PBSYS_CONFIG_BLUETOOTH (1) +#define PBSYS_CONFIG_HMI (1) +#define PBSYS_CONFIG_HMI_PUP (1) #define PBSYS_CONFIG_HMI_NUM_SLOTS (0) #define PBSYS_CONFIG_HUB_LIGHT_MATRIX (0) #define PBSYS_CONFIG_HOST (1) diff --git a/lib/pbio/platform/ev3/pbsysconfig.h b/lib/pbio/platform/ev3/pbsysconfig.h index 287aa5eeb..b2cd2ea29 100644 --- a/lib/pbio/platform/ev3/pbsysconfig.h +++ b/lib/pbio/platform/ev3/pbsysconfig.h @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2020-2024 The Pybricks Authors +// Copyright (c) 2020-2025 The Pybricks Authors #define PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_REPL (1) #define PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_PORT_VIEW (0) @@ -7,6 +7,8 @@ #define PBSYS_CONFIG_FEATURE_PROGRAM_FORMAT_MULTI_MPY_V6 (1) #define PBSYS_CONFIG_FEATURE_PROGRAM_FORMAT_MULTI_MPY_V6_3_NATIVE (0) #define PBSYS_CONFIG_BATTERY_TEMP_ESTIMATION (1) +#define PBSYS_CONFIG_HMI (1) +#define PBSYS_CONFIG_HMI_LCD (1) #define PBSYS_CONFIG_HMI_NUM_SLOTS (5) #define PBSYS_CONFIG_HOST (1) #define PBSYS_CONFIG_HOST_STDIN_BUF_SIZE (21) @@ -22,5 +24,4 @@ #define PBSYS_CONFIG_STATUS_LIGHT_STATE_ANIMATIONS (1) #define PBSYS_CONFIG_STATUS_LIGHT_STATE_ANIMATIONS_HUE (120) #define PBSYS_CONFIG_USER_PROGRAM (1) -#define PBSYS_CONFIG_USER_PROGRAM_AUTO_START (0) #define PBSYS_CONFIG_PROGRAM_STOP (1) diff --git a/lib/pbio/platform/move_hub/pbsysconfig.h b/lib/pbio/platform/move_hub/pbsysconfig.h index 7ad28aad6..dd0813034 100644 --- a/lib/pbio/platform/move_hub/pbsysconfig.h +++ b/lib/pbio/platform/move_hub/pbsysconfig.h @@ -10,6 +10,8 @@ #define PBSYS_CONFIG_FEATURE_PROGRAM_FORMAT_MULTI_MPY_V6_3_NATIVE (0) #define PBSYS_CONFIG_BATTERY_CHARGER (0) #define PBSYS_CONFIG_BLUETOOTH (1) +#define PBSYS_CONFIG_HMI (1) +#define PBSYS_CONFIG_HMI_PUP (1) #define PBSYS_CONFIG_HMI_NUM_SLOTS (0) #define PBSYS_CONFIG_HUB_LIGHT_MATRIX (0) #define PBSYS_CONFIG_HOST (1) diff --git a/lib/pbio/platform/nxt/pbsysconfig.h b/lib/pbio/platform/nxt/pbsysconfig.h index a3c54eb2c..8b6cb5bae 100644 --- a/lib/pbio/platform/nxt/pbsysconfig.h +++ b/lib/pbio/platform/nxt/pbsysconfig.h @@ -1,12 +1,14 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2020-2023 The Pybricks Authors +// Copyright (c) 2020-2025 The Pybricks Authors #define PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_REPL (1) #define PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_PORT_VIEW (0) #define PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_IMU_CALIBRATION (0) #define PBSYS_CONFIG_FEATURE_PROGRAM_FORMAT_MULTI_MPY_V6 (1) #define PBSYS_CONFIG_FEATURE_PROGRAM_FORMAT_MULTI_MPY_V6_3_NATIVE (0) -#define PBSYS_CONFIG_HMI_NUM_SLOTS (1) +#define PBSYS_CONFIG_HMI (1) +#define PBSYS_CONFIG_HMI_NONE (1) +#define PBSYS_CONFIG_HMI_NUM_SLOTS (0) #define PBSYS_CONFIG_HOST (1) #define PBSYS_CONFIG_HOST_STDIN_BUF_SIZE (64) #define PBSYS_CONFIG_MAIN (1) @@ -18,5 +20,4 @@ #define PBSYS_CONFIG_STATUS_LIGHT_BLUETOOTH (0) #define PBSYS_CONFIG_STATUS_LIGHT_STATE_ANIMATIONS (0) #define PBSYS_CONFIG_USER_PROGRAM (1) -#define PBSYS_CONFIG_USER_PROGRAM_AUTO_START (1) #define PBSYS_CONFIG_PROGRAM_STOP (1) diff --git a/lib/pbio/platform/prime_hub/pbsysconfig.h b/lib/pbio/platform/prime_hub/pbsysconfig.h index 3faf52067..d2b70e7b2 100644 --- a/lib/pbio/platform/prime_hub/pbsysconfig.h +++ b/lib/pbio/platform/prime_hub/pbsysconfig.h @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2020-2023 The Pybricks Authors +// Copyright (c) 2020-2025 The Pybricks Authors #define PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_REPL (1) #define PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_PORT_VIEW (1) @@ -9,8 +9,11 @@ #define PBSYS_CONFIG_BATTERY_CHARGER (1) #define PBSYS_CONFIG_BLUETOOTH (1) #define PBSYS_CONFIG_BLUETOOTH_TOGGLE (1) -#define PBSYS_CONFIG_BLUETOOTH_TOGGLE_BUTTON (512) // PBIO_BUTTON_RIGHT_UP, but enum value cannot be used here. +#define PBSYS_CONFIG_HMI (1) #define PBSYS_CONFIG_HMI_NUM_SLOTS (5) +#define PBSYS_CONFIG_HMI_PUP (1) +#define PBSYS_CONFIG_HMI_PUP_LEFT_RIGHT_BUTTONS (1) +#define PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON (512) // PBIO_BUTTON_RIGHT_UP, but enum value cannot be used here. #define PBSYS_CONFIG_HUB_LIGHT_MATRIX (1) #define PBSYS_CONFIG_HUB_LIGHT_MATRIX_LED_ARRAY (1) #define PBSYS_CONFIG_HOST (1) diff --git a/lib/pbio/platform/technic_hub/pbsysconfig.h b/lib/pbio/platform/technic_hub/pbsysconfig.h index 9c8e89fa9..54e866c72 100644 --- a/lib/pbio/platform/technic_hub/pbsysconfig.h +++ b/lib/pbio/platform/technic_hub/pbsysconfig.h @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2020-2023 The Pybricks Authors +// Copyright (c) 2020-2025 The Pybricks Authors #include "pbdrvconfig.h" @@ -10,6 +10,8 @@ #define PBSYS_CONFIG_FEATURE_PROGRAM_FORMAT_MULTI_MPY_V6_3_NATIVE (0) #define PBSYS_CONFIG_BATTERY_CHARGER (0) #define PBSYS_CONFIG_BLUETOOTH (1) +#define PBSYS_CONFIG_HMI (1) +#define PBSYS_CONFIG_HMI_PUP (1) #define PBSYS_CONFIG_HMI_NUM_SLOTS (0) #define PBSYS_CONFIG_HUB_LIGHT_MATRIX (0) #define PBSYS_CONFIG_HOST (1) diff --git a/lib/pbio/sys/hmi.c b/lib/pbio/sys/hmi.c deleted file mode 100644 index 0e425741c..000000000 --- a/lib/pbio/sys/hmi.c +++ /dev/null @@ -1,253 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2018-2024 The Pybricks Authors - -// Provides Human Machine Interface (HMI) between hub and user. - -// TODO: implement additional buttons and menu system (via matrix display) for SPIKE Prime -// TODO: implement additional buttons and menu system (via screen) for NXT - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "light_matrix.h" -#include "light.h" - -#define DEBUG 0 - -#if DEBUG -#include -#define DEBUG_PRINT pbdrv_uart_debug_printf -#else -#define DEBUG_PRINT(...) -#endif - -/** - * Registers button presses to update the visual UI state and request the - * launch of a program. - * - * NB: Must allow calling after completion, so must set os state prior to returning. - */ -static pbio_error_t pbsys_hmi_launch_program_with_button(pbio_os_state_t *state) { - - pbio_button_flags_t pressed; - - PBIO_OS_ASYNC_BEGIN(state); - - pbsys_hub_light_matrix_update_program_slot(); - - for (;;) { - - // Buttons could be pressed at the end of the user program, so wait for - // a release and then a new press. - PBIO_OS_AWAIT_WHILE(state, pressed = pbdrv_button_get_pressed()); - PBIO_OS_AWAIT_UNTIL(state, (pressed = pbdrv_button_get_pressed()) || pbsys_main_program_start_is_requested()); - - // Abandon this UI thread if a program was scheduled to start from the host. - if (pbsys_main_program_start_is_requested()) { - break; - } - - if (pressed & PBIO_BUTTON_CENTER) { - - 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 - // start it. First wait for all buttons to be released so the - // user doesn't accidentally push their robot off course. - PBIO_OS_AWAIT_WHILE(state, pbdrv_button_get_pressed()); - break; - } - - // TODO: Show brief visual indicator if program not available. - } - - // On right, increment slot when possible. - 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) { - pbsys_status_increment_selected_slot(false); - pbsys_hub_light_matrix_update_program_slot(); - } - } - - // Wait for all buttons to be released so the user doesn't accidentally - // push their robot off course. This await also makes it safe to call this - // function after completion. We just exit from here right away again. - PBIO_OS_AWAIT_WHILE(state, pbdrv_button_get_pressed()); - PBIO_OS_ASYNC_END(PBIO_SUCCESS); -} - -/** - * Monitors Bluetooth enable button and starts/stops advertising as needed. - * - * NB: Must allow calling after completion, so must set os state prior to returning. - */ -static pbio_error_t pbsys_hmi_monitor_bluetooth_state(pbio_os_state_t *state) { - - #if !PBSYS_CONFIG_BLUETOOTH - return PBIO_ERROR_NOT_SUPPORTED; - #endif - - #if PBSYS_CONFIG_BLUETOOTH_TOGGLE - bool bluetooth_button_is_pressed = pbdrv_button_get_pressed() & PBSYS_CONFIG_BLUETOOTH_TOGGLE_BUTTON; - #else - bool bluetooth_button_is_pressed = false; - #endif - - static pbio_os_state_t sub; - - PBIO_OS_ASYNC_BEGIN(state); - - for (;;) { - - // No need to monitor Bluetooth button if connected, so just wait for - // program start or disconnect. - if (pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_LE)) { - pbsys_status_set(PBIO_PYBRICKS_STATUS_BLE_HOST_CONNECTED); - PBIO_OS_AWAIT_UNTIL(state, - pbsys_main_program_start_is_requested() || - !pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_LE) - ); - if (pbsys_main_program_start_is_requested()) { - // Done, ready to run the program. - break; - } - } - - // Start with Bluetooth off. - PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_power_on(&sub, false)); - - // Since bluetooth is off, we just have to wait for a program start - // with the buttons or until Bluetooth is enabled with the button or as - // loaded from settings. - pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_HOST_CONNECTED); - PBIO_OS_AWAIT_WHILE(state, pbdrv_button_get_pressed()); - PBIO_OS_AWAIT_UNTIL(state, - pbsys_storage_settings_bluetooth_enabled_get() || - bluetooth_button_is_pressed || - pbsys_main_program_start_is_requested() - ); - if (pbsys_main_program_start_is_requested()) { - // Done, ready to run the program. - break; - } - - // Enable bluetooth and begin advertising. - PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_power_on(&sub, true)); - pbdrv_bluetooth_start_advertising(true); - PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_await_advertise_or_scan_command(&sub, NULL)); - - pbsys_storage_settings_bluetooth_enabled_set(true); - pbsys_status_set(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); - - // Wait for connection, program run, or bluetooth toggle. - PBIO_OS_AWAIT_WHILE(state, pbdrv_button_get_pressed()); - PBIO_OS_AWAIT_UNTIL(state, - bluetooth_button_is_pressed || - pbsys_main_program_start_is_requested() || - pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_LE) - ); - - pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); - if (pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_LE)) { - // On connecting we can stop monitoring the button. Advertising stops - // automatically. - continue; - } - - pbdrv_bluetooth_start_advertising(false); - PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_await_advertise_or_scan_command(&sub, NULL)); - - if (pbsys_main_program_start_is_requested()) { - // Done, ready to run the program. - break; - } - - // Otherwise, we got here because the Bluetooth button was toggled. - pbsys_storage_settings_bluetooth_enabled_set(false); - } - - // Wait for all buttons to be released before starting under all conditions. - PBIO_OS_AWAIT_WHILE(state, pbdrv_button_get_pressed()); - PBIO_OS_ASYNC_END(PBIO_SUCCESS); -} - -/** - * Drives all processes while we wait for user input. This completes when a - * user program request is made using the buttons or by a connected host. - * - * @return Error code. - * ::PBIO_SUCCESS when a program is selected. - * ::PBIO_ERROR_CANCELED when selection was cancelled by shutdown request. - * ::PBIO_ERROR_TIMEDOUT when there was no user interaction for a long time. - */ -pbio_error_t pbsys_hmi_await_program_selection(void) { - - #if PBSYS_CONFIG_USER_PROGRAM_AUTO_START - // Skip any UI, always just start the REPL except on shutdown. - if (pbsys_status_test(PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST)) { - return PBIO_ERROR_CANCELED; - } - pbsys_main_program_request_start(PBIO_PYBRICKS_USER_PROGRAM_ID_REPL, PBSYS_MAIN_PROGRAM_START_REQUEST_TYPE_BOOT); - return PBIO_SUCCESS; - #endif - - pbio_os_state_t btn_state = 0; - pbio_os_state_t ble_state = 0; - - pbio_error_t btn_err; - pbio_os_state_t ble_err; - - uint32_t time_start = pbdrv_clock_get_ms(); - - do { - // Shutdown may be requested by a background process such as critical - // battery or user interaction. This means we should skip the user - // program so return an error. - if (pbsys_status_test(PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST)) { - return PBIO_ERROR_CANCELED; - } - - // Don't time out while connected to host. - if (pbsys_host_is_connected()) { - time_start = pbdrv_clock_get_ms(); - } - - // Timeout on idle. - if (pbdrv_clock_get_ms() - time_start > 3 * 60000) { - return PBIO_ERROR_TIMEDOUT; - } - - // Iterate UI once. We will be back here on the next event, including - // on the next timer tick. - btn_err = pbsys_hmi_launch_program_with_button(&btn_state); - ble_err = pbsys_hmi_monitor_bluetooth_state(&ble_state); - - // run all processes and wait for next event. - pbio_os_run_processes_and_wait_for_event(); - - } while ((btn_err == PBIO_ERROR_AGAIN || ble_err == PBIO_ERROR_AGAIN)); - - return PBIO_SUCCESS; -} diff --git a/lib/pbio/sys/hmi.h b/lib/pbio/sys/hmi.h index c2357345f..45fb74f06 100644 --- a/lib/pbio/sys/hmi.h +++ b/lib/pbio/sys/hmi.h @@ -7,6 +7,18 @@ #include #include +#define PBSYS_CONFIG_HMI_IDLE_TIMEOUT_MS (3 * 60000) + +#if PBSYS_CONFIG_HMI + pbio_error_t pbsys_hmi_await_program_selection(void); +#else + +static inline pbio_error_t pbsys_hmi_await_program_selection(void) { + return PBIO_ERROR_NOT_SUPPORTED; +} + +#endif + #endif // _PBSYS_SYS_HMI_H_ diff --git a/lib/pbio/sys/hmi_lcd.c b/lib/pbio/sys/hmi_lcd.c new file mode 100644 index 000000000..250219e82 --- /dev/null +++ b/lib/pbio/sys/hmi_lcd.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2018-2025 The Pybricks Authors + +// Provides Human Machine Interface (HMI) between hub and user for systems +// with directional buttons and an LCD display. + +#include + +#if PBSYS_CONFIG_HMI_LCD + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "hmi.h" +#include "light_matrix.h" + +#define DEBUG 0 + +#if DEBUG +#include +#define DEBUG_PRINT pbdrv_uart_debug_printf +#else +#define DEBUG_PRINT(...) +#endif + +static pbio_error_t run_ui(pbio_os_state_t *state, pbio_os_timer_t *timer) { + + PBIO_OS_ASYNC_BEGIN(state); + + for (;;) { + + DEBUG_PRINT("Start HMI loop\n"); + + // Visually indicate current state on supported hubs. + pbsys_hub_light_matrix_update_program_slot(); + + // Buttons could be pressed at the end of the user program, so wait for + // a release and then a new press, or until we have to exit early. + DEBUG_PRINT("Waiting for initial button release.\n"); + PBIO_OS_AWAIT_WHILE(state, ({ + if (pbsys_status_test(PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST)) { + return PBIO_ERROR_CANCELED; + } + pbdrv_button_get_pressed(); + })); + + DEBUG_PRINT("Start waiting for input.\n"); + // Wait on a button, external program start, or connection change. Stop + // waiting on timeout or shutdown. + PBIO_OS_AWAIT_UNTIL(state, ({ + // Shutdown may be requested by a background process such as critical + // battery or holding the power button. + if (pbsys_status_test(PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST)) { + return PBIO_ERROR_CANCELED; + } + + // Exit on timeout except while connected to host. + if (pbsys_host_is_connected()) { + pbio_os_timer_set(timer, timer->duration); + } else if (pbio_os_timer_is_expired(timer)) { + return PBIO_ERROR_TIMEDOUT; + } + + // Wait for button press, external program start, or connection change. + pbdrv_button_get_pressed() || pbsys_main_program_start_is_requested(); + })); + + // External progran request takes precedence over buttons. + if (pbsys_main_program_start_is_requested()) { + DEBUG_PRINT("Start program from Pybricks Code.\n"); + break; + } + + // On right, increment slot when possible, then start waiting on new inputs. + if (pbdrv_button_get_pressed() & PBIO_BUTTON_RIGHT) { + pbsys_status_increment_selected_slot(true); + continue; + } + // On left, decrement slot when possible, then start waiting on new inputs. + if (pbdrv_button_get_pressed() & PBIO_BUTTON_LEFT) { + pbsys_status_increment_selected_slot(false); + continue; + } + + // On center, attempt to start program. + if (pbdrv_button_get_pressed() & PBIO_BUTTON_CENTER) { + 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) { + DEBUG_PRINT("Start program with button\n"); + break; + } else { + DEBUG_PRINT("Requested program not available.\n"); + // We can run an animation here to indicate that the program is not available. + } + } + + DEBUG_PRINT("No valid action selected, start over.\n"); + } + + // Wait for all buttons to be released so the user doesn't accidentally + // push their robot off course. + DEBUG_PRINT("Waiting for final button release.\n"); + PBIO_OS_AWAIT_WHILE(state, ({ + if (pbsys_status_test(PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST)) { + return PBIO_ERROR_CANCELED; + } + pbdrv_button_get_pressed(); + })); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); +} + +/** + * Drives all processes while we wait for user input. This completes when a + * user program request is made using the buttons or by a connected host. + * + * @return Error code. + * ::PBIO_SUCCESS when a program is selected. + * ::PBIO_ERROR_CANCELED when selection was cancelled by shutdown request. + * ::PBIO_ERROR_TIMEDOUT when there was no user interaction for a long time. + */ +pbio_error_t pbsys_hmi_await_program_selection(void) { + + pbio_os_timer_t idle_timer; + pbio_os_timer_set(&idle_timer, PBSYS_CONFIG_HMI_IDLE_TIMEOUT_MS); + + pbio_os_state_t state = 0; + + pbio_error_t err; + while ((err = run_ui(&state, &idle_timer)) == PBIO_ERROR_AGAIN) { + // run all processes and wait for next event. + pbio_os_run_processes_and_wait_for_event(); + } + DEBUG_PRINT("Finished program selection with status: %d\n", err); + return err; +} + +#endif // PBSYS_CONFIG_HMI_LCD diff --git a/lib/pbio/sys/hmi_none.c b/lib/pbio/sys/hmi_none.c new file mode 100644 index 000000000..442b02986 --- /dev/null +++ b/lib/pbio/sys/hmi_none.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2018-2025 The Pybricks Authors + +// Provides Human Machine Interface (HMI) between hub and user for Powered Up +// hubs with BLE, lights, and one or more buttons. + +#include + +#if PBSYS_CONFIG_HMI_NONE + +#include +#include +#include + +#include +#include +#include +#include + +pbio_error_t pbsys_hmi_await_program_selection(void) { + + while (pbdrv_button_get_pressed()) { + if (pbsys_status_test(PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST)) { + return PBIO_ERROR_CANCELED; + } + pbio_os_run_processes_and_wait_for_event(); + } + + return pbsys_main_program_request_start(PBIO_PYBRICKS_USER_PROGRAM_ID_REPL, PBSYS_MAIN_PROGRAM_START_REQUEST_TYPE_BOOT); +} + +#endif // PBSYS_CONFIG_HMI_NONE diff --git a/lib/pbio/sys/hmi_pup.c b/lib/pbio/sys/hmi_pup.c new file mode 100644 index 000000000..26c1f1bf3 --- /dev/null +++ b/lib/pbio/sys/hmi_pup.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2018-2025 The Pybricks Authors + +// Provides Human Machine Interface (HMI) between hub and user for Powered Up +// hubs with BLE, lights, and one or more buttons. + +#include + +#if PBSYS_CONFIG_HMI_PUP + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "hmi.h" +#include "light_matrix.h" + +#define DEBUG 0 + +#if DEBUG +#include +#define DEBUG_PRINT pbdrv_uart_debug_printf +#else +#define DEBUG_PRINT(...) +#endif + +/** + * The HMI is a loop running the following steps: + * + * - Update Bluetooth state, based on current state. This enables or + * disables bluetooth and starts/stop advertising. + * - Wait for any buttons to be released in case they were pressed + * - Wait for a button press, external program start, or connection change. + * - If valid program requested, break out of loop to start program. Otherwise, + * update state based on what happened and start over. + * - After leaving the loop, wait for all buttons to be released. + * + * The three waiting operations are cancelled if poweroff is requested. + */ +static pbio_error_t run_ui(pbio_os_state_t *state, pbio_os_timer_t *timer) { + + static pbio_os_state_t sub; + + /** + * Persistent state indicating whether we were connected the last time the + * HMI ran. If it was connected before but not now, we know we became + * disconnected. This is when we restart Bluetooth to get a new address and + * avoid reconnection issues. We also want to do that on boot, so we start + * this in true. + */ + static bool previously_connected = true; + + PBIO_OS_ASYNC_BEGIN(state); + + for (;;) { + + DEBUG_PRINT("Start HMI loop\n"); + + // Visually indicate current state on supported hubs. + pbsys_hub_light_matrix_update_program_slot(); + + // Initialize Bluetooth depending on current state. + if (pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_LE)) { + DEBUG_PRINT("Connected: yes\n"); + pbsys_status_set(PBIO_PYBRICKS_STATUS_BLE_HOST_CONNECTED); + pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); + previously_connected = true; + // No need to stop advertising since this is automatic. + } else { + // Not connected right now. + DEBUG_PRINT("Connected: No\n"); + if (previously_connected) { + // Became disconnected just now or some time throughout the + // last user program run. Reset Bluetooth to get a new address. + // Also used the very first time we power on. + DEBUG_PRINT("Reset Bluetooth.\n"); + PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_power_on(&sub, false)); + } + pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_HOST_CONNECTED); + previously_connected = false; + + // Enable or disable Bluetooth depending on user setting. This is + // a safe no-op if this was already set. + DEBUG_PRINT("Bluetooth is configured to be: %s. \n", pbsys_storage_settings_bluetooth_enabled_get() ? "on" : "off"); + PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_power_on(&sub, pbsys_storage_settings_bluetooth_enabled_get())); + + // Update advertising state. + if (pbsys_storage_settings_bluetooth_enabled_get()) { + // Start advertising if we aren't already. + if (!pbsys_status_test(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING)) { + pbsys_status_set(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); + DEBUG_PRINT("Start advertising.\n"); + pbdrv_bluetooth_start_advertising(true); + PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_await_advertise_or_scan_command(&sub, NULL)); + } + } else { + // Not advertising if Bluetooth is disabled. The physical state + // is already off, but we need the blinking to stop too. + pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); + } + } + + // Buttons could be pressed at the end of the user program, so wait for + // a release and then a new press, or until we have to exit early. + DEBUG_PRINT("Waiting for initial button release.\n"); + PBIO_OS_AWAIT_WHILE(state, ({ + if (pbsys_status_test(PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST)) { + return PBIO_ERROR_CANCELED; + } + pbdrv_button_get_pressed(); + })); + + DEBUG_PRINT("Start waiting for input.\n"); + // Wait on a button, external program start, or connection change. Stop + // waiting on timeout or shutdown. + PBIO_OS_AWAIT_UNTIL(state, ({ + // Shutdown may be requested by a background process such as critical + // battery or holding the power button. + if (pbsys_status_test(PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST)) { + return PBIO_ERROR_CANCELED; + } + + // Exit on timeout except while connected to host. + if (pbsys_host_is_connected()) { + pbio_os_timer_set(timer, timer->duration); + } else if (pbio_os_timer_is_expired(timer)) { + return PBIO_ERROR_TIMEDOUT; + } + + // Wait for button press, external program start, or connection change. + pbdrv_button_get_pressed() || + pbsys_main_program_start_is_requested() || + pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_LE) != previously_connected; + })); + + // Became connected or disconnected, so go back to handle it. + if (pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_LE) != previously_connected) { + DEBUG_PRINT("Connection changed.\n"); + continue; + } + + // External progran request takes precedence over buttons. + if (pbsys_main_program_start_is_requested()) { + DEBUG_PRINT("Start program from Pybricks Code.\n"); + break; + } + + #if PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON + // Toggle Bluetooth enable setting if Bluetooth button pressed. Only if disconnected. + if ((pbdrv_button_get_pressed() & PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON) && !pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_LE)) { + pbsys_storage_settings_bluetooth_enabled_set(!pbsys_storage_settings_bluetooth_enabled_get()); + DEBUG_PRINT("Toggling Bluetooth to: %s. \n", pbsys_storage_settings_bluetooth_enabled_get() ? "on" : "off"); + continue; + } + #endif // PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON + + #if PBSYS_CONFIG_HMI_PUP_LEFT_RIGHT_BUTTONS + // On right, increment slot when possible, then start waiting on new inputs. + if (pbdrv_button_get_pressed() & PBIO_BUTTON_RIGHT) { + pbsys_status_increment_selected_slot(true); + continue; + } + // On left, decrement slot when possible, then start waiting on new inputs. + if (pbdrv_button_get_pressed() & PBIO_BUTTON_LEFT) { + pbsys_status_increment_selected_slot(false); + continue; + } + #endif // PBSYS_CONFIG_HMI_PUP_LEFT_RIGHT_BUTTONS + + // On center, attempt to start program. + if (pbdrv_button_get_pressed() & PBIO_BUTTON_CENTER) { + 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) { + DEBUG_PRINT("Start program with button\n"); + break; + } else { + DEBUG_PRINT("Requested program not available.\n"); + // We can run an animation here to indicate that the program is not available. + } + } + + DEBUG_PRINT("No valid action selected, start over.\n"); + } + + // Stop advertising if we are still doing so. + if (pbsys_status_test(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING)) { + pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); + DEBUG_PRINT("Stop advertising on HMI exit.\n"); + pbdrv_bluetooth_start_advertising(false); + PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_await_advertise_or_scan_command(&sub, NULL)); + } + + // Wait for all buttons to be released so the user doesn't accidentally + // push their robot off course. + DEBUG_PRINT("Waiting for final button release.\n"); + PBIO_OS_AWAIT_WHILE(state, ({ + if (pbsys_status_test(PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST)) { + return PBIO_ERROR_CANCELED; + } + pbdrv_button_get_pressed(); + })); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); +} + +/** + * Drives all processes while we wait for user input. This completes when a + * user program request is made using the buttons or by a connected host. + * + * @return Error code. + * ::PBIO_SUCCESS when a program is selected. + * ::PBIO_ERROR_CANCELED when selection was cancelled by shutdown request. + * ::PBIO_ERROR_TIMEDOUT when there was no user interaction for a long time. + */ +pbio_error_t pbsys_hmi_await_program_selection(void) { + + pbio_os_timer_t idle_timer; + pbio_os_timer_set(&idle_timer, PBSYS_CONFIG_HMI_IDLE_TIMEOUT_MS); + + pbio_os_state_t state = 0; + + pbio_error_t err; + while ((err = run_ui(&state, &idle_timer)) == PBIO_ERROR_AGAIN) { + // run all processes and wait for next event. + pbio_os_run_processes_and_wait_for_event(); + } + DEBUG_PRINT("Finished program selection with status: %d\n", err); + return err; +} + +#endif // PBSYS_CONFIG_HMI_PUP diff --git a/lib/pbio/sys/storage_settings.c b/lib/pbio/sys/storage_settings.c index ae03955bd..9041a3c33 100644 --- a/lib/pbio/sys/storage_settings.c +++ b/lib/pbio/sys/storage_settings.c @@ -25,7 +25,7 @@ * @param [in] settings Settings to populate. */ void pbsys_storage_settings_set_defaults(pbsys_storage_settings_t *settings) { - #if PBSYS_CONFIG_BLUETOOTH_TOGGLE + #if PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON settings->flags |= PBSYS_STORAGE_SETTINGS_FLAGS_BLUETOOTH_ENABLED; #endif #if PBIO_CONFIG_IMU @@ -45,7 +45,7 @@ void pbsys_storage_settings_apply_loaded_settings(pbsys_storage_settings_t *sett } bool pbsys_storage_settings_bluetooth_enabled_get(void) { - #if PBSYS_CONFIG_BLUETOOTH_TOGGLE + #if PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON pbsys_storage_settings_t *settings = pbsys_storage_settings_get_settings(); if (!settings) { return false; @@ -53,10 +53,10 @@ bool pbsys_storage_settings_bluetooth_enabled_get(void) { return settings->flags & PBSYS_STORAGE_SETTINGS_FLAGS_BLUETOOTH_ENABLED; #else return true; - #endif // PBSYS_CONFIG_BLUETOOTH_TOGGLE + #endif // PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON } void pbsys_storage_settings_bluetooth_enabled_set(bool enable) { - #if PBSYS_CONFIG_BLUETOOTH_TOGGLE + #if PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON pbsys_storage_settings_t *settings = pbsys_storage_settings_get_settings(); bool current_value = pbsys_storage_settings_bluetooth_enabled_get(); diff --git a/pybricks/common/pb_type_ble.c b/pybricks/common/pb_type_ble.c index 77557f505..67ad2aea4 100644 --- a/pybricks/common/pb_type_ble.c +++ b/pybricks/common/pb_type_ble.c @@ -552,11 +552,11 @@ mp_obj_t pb_type_BLE_new(mp_obj_t broadcast_channel_in, mp_obj_t observe_channel } // Raise if Bluetooth is attempted to be used while not enabled. - #if PBSYS_CONFIG_BLUETOOTH_TOGGLE + #if PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON if (!pbsys_storage_settings_bluetooth_enabled_get() && (num_observe_channels > 0 || broadcast_channel_in != mp_const_none)) { mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Bluetooth not enabled")); } - #endif // PBSYS_CONFIG_BLUETOOTH_TOGGLE + #endif // PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON pb_obj_BLE_t *self = mp_obj_malloc_var_with_finaliser(pb_obj_BLE_t, observed_data_t, num_observe_channels, &pb_type_BLE); self->broadcast_channel = broadcast_channel_in; diff --git a/pybricks/iodevices/pb_type_iodevices_lwp3device.c b/pybricks/iodevices/pb_type_iodevices_lwp3device.c index fb9989661..f76bb61eb 100644 --- a/pybricks/iodevices/pb_type_iodevices_lwp3device.c +++ b/pybricks/iodevices/pb_type_iodevices_lwp3device.c @@ -378,11 +378,11 @@ static pbio_error_t pb_lwp3device_connect_thread(pbio_os_state_t *state, mp_obj_ static mp_obj_t pb_lwp3device_connect(mp_obj_t self_in, mp_obj_t name_in, mp_obj_t timeout_in, lwp3_hub_kind_t hub_kind, pbdrv_bluetooth_receive_handler_t notification_handler, bool pair) { - #if PBSYS_CONFIG_BLUETOOTH_TOGGLE + #if PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON if (!pbsys_storage_settings_bluetooth_enabled_get()) { mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Bluetooth not enabled")); } - #endif // PBSYS_CONFIG_BLUETOOTH_TOGGLE + #endif // PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON pb_lwp3device_obj_t *self = MP_OBJ_TO_PTR(self_in); diff --git a/pybricks/iodevices/pb_type_iodevices_xbox_controller.c b/pybricks/iodevices/pb_type_iodevices_xbox_controller.c index 5f737dcf0..56ec1b274 100644 --- a/pybricks/iodevices/pb_type_iodevices_xbox_controller.c +++ b/pybricks/iodevices/pb_type_iodevices_xbox_controller.c @@ -372,11 +372,11 @@ static mp_obj_t pb_type_xbox_make_new(const mp_obj_type_t *type, size_t n_args, #endif // PYBRICKS_HUB_TECHNICHUB ); - #if PBSYS_CONFIG_BLUETOOTH_TOGGLE + #if PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON if (!pbsys_storage_settings_bluetooth_enabled_get()) { mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Bluetooth not enabled")); } - #endif // PBSYS_CONFIG_BLUETOOTH_TOGGLE + #endif // PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON pb_module_tools_assert_blocking(); From 2ebada09e0a74093ed090e16c772dbd749c5312e Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 25 Sep 2025 14:51:13 +0200 Subject: [PATCH 04/12] pbio/sys/hmi: Move animations to respective HMI. Relavant animations can be started when successfully starting a program, rather than having variant specific code in the main system loop. --- lib/pbio/sys/hmi_lcd.c | 11 +++++++++++ lib/pbio/sys/hmi_pup.c | 10 ++++++++++ lib/pbio/sys/light_matrix.c | 6 ------ lib/pbio/sys/main.c | 9 --------- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/lib/pbio/sys/hmi_lcd.c b/lib/pbio/sys/hmi_lcd.c index 250219e82..53d3f6754 100644 --- a/lib/pbio/sys/hmi_lcd.c +++ b/lib/pbio/sys/hmi_lcd.c @@ -13,10 +13,13 @@ #include #include +#include #include #include #include +#include +#include #include #include #include @@ -116,6 +119,14 @@ static pbio_error_t run_ui(pbio_os_state_t *state, pbio_os_timer_t *timer) { } pbdrv_button_get_pressed(); })); + + // Clear UI from display to start user program. + pbio_image_fill(pbdrv_display_get_image(), 0); + pbdrv_display_update(); + + // Start light or display animations. + pbio_color_light_start_breathe_animation(pbsys_status_light_main, PBSYS_CONFIG_STATUS_LIGHT_STATE_ANIMATIONS_HUE); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } diff --git a/lib/pbio/sys/hmi_pup.c b/lib/pbio/sys/hmi_pup.c index 26c1f1bf3..9ccab1353 100644 --- a/lib/pbio/sys/hmi_pup.c +++ b/lib/pbio/sys/hmi_pup.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -208,6 +209,15 @@ static pbio_error_t run_ui(pbio_os_state_t *state, pbio_os_timer_t *timer) { } pbdrv_button_get_pressed(); })); + + // Start run animations + pbsys_hub_light_matrix_handle_user_program_start(); + #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 + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } diff --git a/lib/pbio/sys/light_matrix.c b/lib/pbio/sys/light_matrix.c index 33ab460e4..347600e52 100644 --- a/lib/pbio/sys/light_matrix.c +++ b/lib/pbio/sys/light_matrix.c @@ -157,12 +157,6 @@ static uint32_t pbsys_hub_light_matrix_user_program_animation_next(pbio_light_an */ void pbsys_hub_light_matrix_handle_user_program_start(void) { - #if PBSYS_CONFIG_HUB_LIGHT_MATRIX_DISPLAY - pbio_image_fill(pbdrv_display_get_image(), 0); - pbdrv_display_update(); - return; - #endif - // The user animation updates only a subset of pixels to save time, // so the rest must be cleared before it starts. pbsys_hub_light_matrix_user_program_animation_clear(); diff --git a/lib/pbio/sys/main.c b/lib/pbio/sys/main.c index 5a95a6f66..4cf6ce083 100644 --- a/lib/pbio/sys/main.c +++ b/lib/pbio/sys/main.c @@ -102,15 +102,6 @@ int main(int argc, char **argv) { pbsys_status_set_program_id(program.id); pbsys_status_set(PBIO_PYBRICKS_STATUS_USER_PROGRAM_RUNNING); pbsys_host_stdin_set_callback(pbsys_main_stdin_event); - pbsys_hub_light_matrix_handle_user_program_start(); - - #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. From c4cf5d2e1df574124852d27c4d25757b3d089820 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 25 Sep 2025 14:56:34 +0200 Subject: [PATCH 05/12] pbio/platform/ev3: Disable hard reset by default. We will be using the back button for normal poweroff, and losing your data is a confusing user experience. It can still be enabled with a config option for developers. --- lib/pbio/platform/ev3/pbdrvconfig.h | 1 + lib/pbio/platform/ev3/platform.c | 17 +++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/pbio/platform/ev3/pbdrvconfig.h b/lib/pbio/platform/ev3/pbdrvconfig.h index 7f523a53a..c3c3e7fd6 100644 --- a/lib/pbio/platform/ev3/pbdrvconfig.h +++ b/lib/pbio/platform/ev3/pbdrvconfig.h @@ -45,6 +45,7 @@ #define PBDRV_CONFIG_BUTTON (1) #define PBDRV_CONFIG_BUTTON_GPIO (1) #define PBDRV_CONFIG_BUTTON_GPIO_NUM_BUTTON (6) +#define PBDRV_CONFIG_BUTTON_INSTANT_RESET (0) #define PBDRV_CONFIG_DISPLAY (1) #define PBDRV_CONFIG_DISPLAY_EV3 (1) diff --git a/lib/pbio/platform/ev3/platform.c b/lib/pbio/platform/ev3/platform.c index e65c80bed..1fb617c8e 100644 --- a/lib/pbio/platform/ev3/platform.c +++ b/lib/pbio/platform/ev3/platform.c @@ -55,10 +55,13 @@ #include +#include +#include + #include #include #include -#include +#include #include "exceptionhandler.h" @@ -852,18 +855,16 @@ void SystemInit(void) { umm_init_heap(&pb_umm_heap_start, &pb_umm_heap_end - &pb_umm_heap_start); } - -#include -#include - /* - * This is called from the IRQ handler after every systick. This should be - * removed when the final user interface is implemented. For now this serves - * as a very convenient way to power off the EV3 for fast iteration. + * This is called from the IRQ handler after every systick. Can be enabled + * to run emergency poweroff for faster iteration and debugging. User data + * is not saved when powering off this way. */ void lazy_poweroff_hook(void) { + #if PBDRV_CONFIG_BUTTON_INSTANT_RESET if (pbdrv_button_get_pressed() & PBIO_BUTTON_LEFT_UP) { pbdrv_reset_power_off(); return; } + #endif } From 7431355610b717a17e44861609ad2b38d7467ecd Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 25 Sep 2025 15:11:02 +0200 Subject: [PATCH 06/12] pbio/sys/hmi: Make default stop button configurable. It's always been the center button, but we use the top left back button on EV3. It will be the dark gray button on NXT. --- lib/pbio/platform/city_hub/pbsysconfig.h | 1 + lib/pbio/platform/essential_hub/pbsysconfig.h | 1 + lib/pbio/platform/ev3/pbsysconfig.h | 1 + lib/pbio/platform/move_hub/pbsysconfig.h | 1 + lib/pbio/platform/nxt/pbsysconfig.h | 1 + lib/pbio/platform/prime_hub/pbsysconfig.h | 5 +++-- lib/pbio/platform/technic_hub/pbsysconfig.h | 1 + lib/pbio/sys/hmi_pup.c | 4 ++-- lib/pbio/sys/main.c | 2 +- lib/pbio/sys/program_stop.c | 4 ++-- 10 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/pbio/platform/city_hub/pbsysconfig.h b/lib/pbio/platform/city_hub/pbsysconfig.h index 33a61c758..7c02c9401 100644 --- a/lib/pbio/platform/city_hub/pbsysconfig.h +++ b/lib/pbio/platform/city_hub/pbsysconfig.h @@ -11,6 +11,7 @@ #define PBSYS_CONFIG_BATTERY_CHARGER (0) #define PBSYS_CONFIG_BLUETOOTH (1) #define PBSYS_CONFIG_HMI (1) +#define PBSYS_CONFIG_HMI_STOP_BUTTON (1 << 5) // center #define PBSYS_CONFIG_HMI_PUP (1) #define PBSYS_CONFIG_HMI_NUM_SLOTS (0) #define PBSYS_CONFIG_HUB_LIGHT_MATRIX (0) diff --git a/lib/pbio/platform/essential_hub/pbsysconfig.h b/lib/pbio/platform/essential_hub/pbsysconfig.h index fa8f3272f..add172e38 100644 --- a/lib/pbio/platform/essential_hub/pbsysconfig.h +++ b/lib/pbio/platform/essential_hub/pbsysconfig.h @@ -9,6 +9,7 @@ #define PBSYS_CONFIG_BATTERY_CHARGER (1) #define PBSYS_CONFIG_BLUETOOTH (1) #define PBSYS_CONFIG_HMI (1) +#define PBSYS_CONFIG_HMI_STOP_BUTTON (1 << 5) // center #define PBSYS_CONFIG_HMI_PUP (1) #define PBSYS_CONFIG_HMI_NUM_SLOTS (0) #define PBSYS_CONFIG_HUB_LIGHT_MATRIX (0) diff --git a/lib/pbio/platform/ev3/pbsysconfig.h b/lib/pbio/platform/ev3/pbsysconfig.h index b2cd2ea29..2007b3a1b 100644 --- a/lib/pbio/platform/ev3/pbsysconfig.h +++ b/lib/pbio/platform/ev3/pbsysconfig.h @@ -8,6 +8,7 @@ #define PBSYS_CONFIG_FEATURE_PROGRAM_FORMAT_MULTI_MPY_V6_3_NATIVE (0) #define PBSYS_CONFIG_BATTERY_TEMP_ESTIMATION (1) #define PBSYS_CONFIG_HMI (1) +#define PBSYS_CONFIG_HMI_STOP_BUTTON (1 << 7) // top left, back button #define PBSYS_CONFIG_HMI_LCD (1) #define PBSYS_CONFIG_HMI_NUM_SLOTS (5) #define PBSYS_CONFIG_HOST (1) diff --git a/lib/pbio/platform/move_hub/pbsysconfig.h b/lib/pbio/platform/move_hub/pbsysconfig.h index dd0813034..249ae094e 100644 --- a/lib/pbio/platform/move_hub/pbsysconfig.h +++ b/lib/pbio/platform/move_hub/pbsysconfig.h @@ -11,6 +11,7 @@ #define PBSYS_CONFIG_BATTERY_CHARGER (0) #define PBSYS_CONFIG_BLUETOOTH (1) #define PBSYS_CONFIG_HMI (1) +#define PBSYS_CONFIG_HMI_STOP_BUTTON (1 << 5) // center #define PBSYS_CONFIG_HMI_PUP (1) #define PBSYS_CONFIG_HMI_NUM_SLOTS (0) #define PBSYS_CONFIG_HUB_LIGHT_MATRIX (0) diff --git a/lib/pbio/platform/nxt/pbsysconfig.h b/lib/pbio/platform/nxt/pbsysconfig.h index 8b6cb5bae..de3710148 100644 --- a/lib/pbio/platform/nxt/pbsysconfig.h +++ b/lib/pbio/platform/nxt/pbsysconfig.h @@ -7,6 +7,7 @@ #define PBSYS_CONFIG_FEATURE_PROGRAM_FORMAT_MULTI_MPY_V6 (1) #define PBSYS_CONFIG_FEATURE_PROGRAM_FORMAT_MULTI_MPY_V6_3_NATIVE (0) #define PBSYS_CONFIG_HMI (1) +#define PBSYS_CONFIG_HMI_STOP_BUTTON (1 << 2) // down #define PBSYS_CONFIG_HMI_NONE (1) #define PBSYS_CONFIG_HMI_NUM_SLOTS (0) #define PBSYS_CONFIG_HOST (1) diff --git a/lib/pbio/platform/prime_hub/pbsysconfig.h b/lib/pbio/platform/prime_hub/pbsysconfig.h index d2b70e7b2..2e599c7fd 100644 --- a/lib/pbio/platform/prime_hub/pbsysconfig.h +++ b/lib/pbio/platform/prime_hub/pbsysconfig.h @@ -10,10 +10,11 @@ #define PBSYS_CONFIG_BLUETOOTH (1) #define PBSYS_CONFIG_BLUETOOTH_TOGGLE (1) #define PBSYS_CONFIG_HMI (1) +#define PBSYS_CONFIG_HMI_STOP_BUTTON (1 << 5) // center #define PBSYS_CONFIG_HMI_NUM_SLOTS (5) #define PBSYS_CONFIG_HMI_PUP (1) -#define PBSYS_CONFIG_HMI_PUP_LEFT_RIGHT_BUTTONS (1) -#define PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON (512) // PBIO_BUTTON_RIGHT_UP, but enum value cannot be used here. +#define PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON (1 << 9) // right up +#define PBSYS_CONFIG_HMI_PUP_LEFT_RIGHT_ENABLE (1) #define PBSYS_CONFIG_HUB_LIGHT_MATRIX (1) #define PBSYS_CONFIG_HUB_LIGHT_MATRIX_LED_ARRAY (1) #define PBSYS_CONFIG_HOST (1) diff --git a/lib/pbio/platform/technic_hub/pbsysconfig.h b/lib/pbio/platform/technic_hub/pbsysconfig.h index 54e866c72..330f344f2 100644 --- a/lib/pbio/platform/technic_hub/pbsysconfig.h +++ b/lib/pbio/platform/technic_hub/pbsysconfig.h @@ -11,6 +11,7 @@ #define PBSYS_CONFIG_BATTERY_CHARGER (0) #define PBSYS_CONFIG_BLUETOOTH (1) #define PBSYS_CONFIG_HMI (1) +#define PBSYS_CONFIG_HMI_STOP_BUTTON (1 << 5) // center #define PBSYS_CONFIG_HMI_PUP (1) #define PBSYS_CONFIG_HMI_NUM_SLOTS (0) #define PBSYS_CONFIG_HUB_LIGHT_MATRIX (0) diff --git a/lib/pbio/sys/hmi_pup.c b/lib/pbio/sys/hmi_pup.c index 9ccab1353..0da739117 100644 --- a/lib/pbio/sys/hmi_pup.c +++ b/lib/pbio/sys/hmi_pup.c @@ -164,7 +164,7 @@ static pbio_error_t run_ui(pbio_os_state_t *state, pbio_os_timer_t *timer) { } #endif // PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON - #if PBSYS_CONFIG_HMI_PUP_LEFT_RIGHT_BUTTONS + #if PBSYS_CONFIG_HMI_PUP_LEFT_RIGHT_ENABLE // On right, increment slot when possible, then start waiting on new inputs. if (pbdrv_button_get_pressed() & PBIO_BUTTON_RIGHT) { pbsys_status_increment_selected_slot(true); @@ -175,7 +175,7 @@ static pbio_error_t run_ui(pbio_os_state_t *state, pbio_os_timer_t *timer) { pbsys_status_increment_selected_slot(false); continue; } - #endif // PBSYS_CONFIG_HMI_PUP_LEFT_RIGHT_BUTTONS + #endif // PBSYS_CONFIG_HMI_PUP_LEFT_RIGHT_ENABLE // On center, attempt to start program. if (pbdrv_button_get_pressed() & PBIO_BUTTON_CENTER) { diff --git a/lib/pbio/sys/main.c b/lib/pbio/sys/main.c index 4cf6ce083..e3dc4c49e 100644 --- a/lib/pbio/sys/main.c +++ b/lib/pbio/sys/main.c @@ -120,7 +120,7 @@ int main(int argc, char **argv) { // Get system back in idle state. pbsys_status_clear(PBIO_PYBRICKS_STATUS_USER_PROGRAM_RUNNING); pbsys_host_stdin_set_callback(NULL); - pbsys_program_stop_set_buttons(PBIO_BUTTON_CENTER); + pbsys_program_stop_set_buttons(PBSYS_CONFIG_HMI_STOP_BUTTON); program.start_request_type = PBSYS_MAIN_PROGRAM_START_REQUEST_TYPE_NONE; // Handle pending events triggered by the status change, such as diff --git a/lib/pbio/sys/program_stop.c b/lib/pbio/sys/program_stop.c index 2e65fc2f3..bb4f104c5 100644 --- a/lib/pbio/sys/program_stop.c +++ b/lib/pbio/sys/program_stop.c @@ -16,7 +16,7 @@ #include // Button combination that will trigger user program stop callback -static pbio_button_flags_t stop_buttons = PBIO_BUTTON_CENTER; +static pbio_button_flags_t stop_buttons = PBSYS_CONFIG_HMI_STOP_BUTTON; // State for button press one-shot static bool stop_button_pressed; @@ -59,7 +59,7 @@ void pbsys_program_stop_poll(void) { pbio_button_flags_t btn = pbdrv_button_get_pressed(); - if (btn & PBIO_BUTTON_CENTER) { + if (btn & PBSYS_CONFIG_HMI_STOP_BUTTON) { pbsys_status_set(PBIO_PYBRICKS_STATUS_POWER_BUTTON_PRESSED); // power off when button is held down for 2 seconds From 99275893f6e71013491401e2c20bc5b12340f96f Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 25 Sep 2025 16:53:17 +0200 Subject: [PATCH 07/12] pbio/sys/hmi_pup: Change when advertising blink stops. It is more intuitive to keep it on until the program starts or until you shut down. --- lib/pbio/sys/hmi_pup.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pbio/sys/hmi_pup.c b/lib/pbio/sys/hmi_pup.c index 0da739117..72164cc5d 100644 --- a/lib/pbio/sys/hmi_pup.c +++ b/lib/pbio/sys/hmi_pup.c @@ -194,7 +194,6 @@ static pbio_error_t run_ui(pbio_os_state_t *state, pbio_os_timer_t *timer) { // Stop advertising if we are still doing so. if (pbsys_status_test(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING)) { - pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); DEBUG_PRINT("Stop advertising on HMI exit.\n"); pbdrv_bluetooth_start_advertising(false); PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_await_advertise_or_scan_command(&sub, NULL)); @@ -210,6 +209,11 @@ static pbio_error_t run_ui(pbio_os_state_t *state, pbio_os_timer_t *timer) { pbdrv_button_get_pressed(); })); + // We have already stopped advertising above, but it is nicer to keep the + // visual active until you release the button. Otherwise, it appears as if + // the hub is already off when you are shutting down. + pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); + // Start run animations pbsys_hub_light_matrix_handle_user_program_start(); #if PBSYS_CONFIG_STATUS_LIGHT_STATE_ANIMATIONS From e951375ae21bc0988d96a858696c787e71bd33a1 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Sun, 28 Sep 2025 20:12:25 +0200 Subject: [PATCH 08/12] pbio/os: Drop separate init method. Make it explicit that we have a proper restart. --- lib/pbio/include/pbio/os.h | 2 -- lib/pbio/src/os.c | 55 ++++++++++++++++---------------------- lib/pbio/src/port.c | 4 +-- 3 files changed, 25 insertions(+), 36 deletions(-) diff --git a/lib/pbio/include/pbio/os.h b/lib/pbio/include/pbio/os.h index 9c13d16b9..c8a9fcbd0 100644 --- a/lib/pbio/include/pbio/os.h +++ b/lib/pbio/include/pbio/os.h @@ -304,6 +304,4 @@ pbio_error_t pbio_port_process_none_thread(pbio_os_state_t *state, void *context void pbio_os_process_start(pbio_os_process_t *process, pbio_os_process_func_t func, void *context); -void pbio_os_process_init(pbio_os_process_t *process, pbio_os_process_func_t func); - #endif // _PBIO_OS_H_ diff --git a/lib/pbio/src/os.c b/lib/pbio/src/os.c index fc955bc4c..e292de7a8 100644 --- a/lib/pbio/src/os.c +++ b/lib/pbio/src/os.c @@ -57,7 +57,6 @@ void pbio_os_request_poll(void) { poll_request_is_pending = true; } -static pbio_os_process_t *process_list = NULL; /** * Placeholder thread that does nothing and never completes. @@ -69,50 +68,42 @@ pbio_error_t pbio_port_process_none_thread(pbio_os_state_t *state, void *context return PBIO_ERROR_AGAIN; } -/** - * Adds a process to the list of processes to run and starts it soon. - * - * @param process The process to start. - * @param func The process thread function. - * @param context The context to pass to the process. - */ -void pbio_os_process_start(pbio_os_process_t *process, pbio_os_process_func_t func, void *context) { +static pbio_os_process_t *process_list = NULL; + +static void add_process(pbio_os_process_t *process) { + + pbio_os_process_t **pp = &process_list; - // Add the new process to the end of the list. - pbio_os_process_t *last = process_list; - if (!last) { - process_list = process; - } else { - while (last->next) { - last = last->next; + while (*pp) { + if (*pp == process) { + // Already in the list. + return; } - last->next = process; + pp = &(*pp)->next; } - // Initialize the process. + // Insert at end. + *pp = process; process->next = NULL; - process->context = context; - - pbio_os_process_init(process, func); } /** - * Initializes a process to the initial state with a protothread function to run. - * - * Can also be used to reset a process to the initial state or to change the - * protothread function. Doing so should be done with caution, but can be useful - * to make a process behave in distinct operation modes. + * Adds a process to the list of processes to run and starts it soon. * - * @param process The process to start. - * @param func The process thread function. Choose NULL if it does not need changing. + * @param process The process to start. Can be an existing process which will be reset. + * @param func The process thread function. + * @param context The context to pass to the process. */ -void pbio_os_process_init(pbio_os_process_t *process, pbio_os_process_func_t func) { +void pbio_os_process_start(pbio_os_process_t *process, pbio_os_process_func_t func, void *context) { + + // Add the new process to the end of the list if not already in it. + add_process(process); + + process->context = context; process->err = PBIO_ERROR_AGAIN; process->state = 0; process->request = PBIO_OS_PROCESS_REQUEST_TYPE_NONE; - if (func) { - process->func = func; - } + process->func = func; // Request a poll to start the process soon, running to its first yield. pbio_os_request_poll(); diff --git a/lib/pbio/src/port.c b/lib/pbio/src/port.c index bd7755a07..8007e4e83 100644 --- a/lib/pbio/src/port.c +++ b/lib/pbio/src/port.c @@ -541,7 +541,7 @@ pbio_error_t pbio_port_set_mode(pbio_port_t *port, pbio_port_mode_t mode) { } // Disable thread activity by attaching a thread that does nothing. - pbio_os_process_init(&port->process, pbio_port_process_none_thread); + pbio_os_process_start(&port->process, pbio_port_process_none_thread, port); port->mode = mode; switch (mode) { @@ -552,7 +552,7 @@ pbio_error_t pbio_port_set_mode(pbio_port_t *port, pbio_port_mode_t mode) { case PBIO_PORT_MODE_LEGO_DCM: // Physical modes for this mode will be set by the process so this // is all we need to do here. - pbio_os_process_init(&port->process, pbio_port_process_lego_dcm_thread); + pbio_os_process_start(&port->process, pbio_port_process_lego_dcm_thread, port); // Returning e-again allows user module to wait for the port to be // ready after first entering LEGO mode, avoiding NODEV errors when // switching from direct access modes back to LEGO mode. From ecf1342e580261c78c693af63c260737c2ccab5d Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Sun, 28 Sep 2025 14:34:45 +0200 Subject: [PATCH 09/12] pbio/sys: Begin simplifying light_matrix. There are three files named light_matrix.h, and several structures with a single field defined in one of the other ones. Some were directly including from the ../src/ directory. There were also funcs types, but they had only one function, and they always use a led_array. In practice, there will be only one light matrix. It has only one function to set one pixel on one led array. This simplifies it by cutting out some of the abstractions. And it still allows having more than one light matrix if we ever need it. We can remove the pbio/sys/light_matrix abstraction in the next commit. --- lib/pbio/include/pbio/light_matrix.h | 9 +++- lib/pbio/platform/ev3/pbioconfig.h | 1 - lib/pbio/platform/prime_hub/pbioconfig.h | 1 + lib/pbio/src/light/light_matrix.c | 61 +++++++++++++++++------- lib/pbio/src/light/light_matrix.h | 22 ++------- lib/pbio/sys/light_matrix.c | 44 +++-------------- lib/pbio/test/src/test_light_matrix.c | 8 ++-- pybricks/common/pb_type_lightmatrix.c | 6 +-- 8 files changed, 68 insertions(+), 84 deletions(-) diff --git a/lib/pbio/include/pbio/light_matrix.h b/lib/pbio/include/pbio/light_matrix.h index 515c7f963..0f7717344 100644 --- a/lib/pbio/include/pbio/light_matrix.h +++ b/lib/pbio/include/pbio/light_matrix.h @@ -20,17 +20,22 @@ typedef struct _pbio_light_matrix_t pbio_light_matrix_t; #if PBIO_CONFIG_LIGHT_MATRIX +pbio_error_t pbio_light_matrix_get_dev(uint8_t index, uint8_t size, pbio_light_matrix_t **light_matrix); uint8_t pbio_light_matrix_get_size(pbio_light_matrix_t *light_matrix); void pbio_light_matrix_set_orientation(pbio_light_matrix_t *light_matrix, pbio_geometry_side_t up_side); pbio_error_t pbio_light_matrix_clear(pbio_light_matrix_t *light_matrix); pbio_error_t pbio_light_matrix_set_rows(pbio_light_matrix_t *light_matrix, const uint8_t *rows); -pbio_error_t pbio_light_matrix_set_pixel(pbio_light_matrix_t *light_matrix, uint8_t row, uint8_t col, uint8_t brightness); +pbio_error_t pbio_light_matrix_set_pixel(pbio_light_matrix_t *light_matrix, uint8_t row, uint8_t col, uint8_t brightness, bool clear_animation); pbio_error_t pbio_light_matrix_set_image(pbio_light_matrix_t *light_matrix, const uint8_t *image); void pbio_light_matrix_start_animation(pbio_light_matrix_t *light_matrix, const uint8_t *cells, uint8_t num_cells, uint16_t interval); void pbio_light_matrix_stop_animation(pbio_light_matrix_t *light_matrix); #else // PBIO_CONFIG_LIGHT_MATRIX +static inline pbio_error_t pbio_light_matrix_get_dev(uint8_t index, uint8_t size, pbio_light_matrix_t **light_matrix) { + return PBIO_ERROR_NOT_SUPPORTED; +} + static inline uint8_t pbio_light_matrix_get_size(pbio_light_matrix_t *light_matrix) { return 0; } @@ -46,7 +51,7 @@ static inline pbio_error_t pbio_light_matrix_set_rows(pbio_light_matrix_t *light return PBIO_ERROR_NOT_SUPPORTED; } -static inline pbio_error_t pbio_light_matrix_set_pixel(pbio_light_matrix_t *light_matrix, uint8_t row, uint8_t col, uint8_t brightness) { +static inline pbio_error_t pbio_light_matrix_set_pixel(pbio_light_matrix_t *light_matrix, uint8_t row, uint8_t col, uint8_t brightness, bool clear_animation) { return PBIO_ERROR_NOT_SUPPORTED; } diff --git a/lib/pbio/platform/ev3/pbioconfig.h b/lib/pbio/platform/ev3/pbioconfig.h index aa0c2301d..3b9ae25d7 100644 --- a/lib/pbio/platform/ev3/pbioconfig.h +++ b/lib/pbio/platform/ev3/pbioconfig.h @@ -8,7 +8,6 @@ #define PBIO_CONFIG_IMAGE (1) #define PBIO_CONFIG_IMU (0) #define PBIO_CONFIG_LIGHT (1) -#define PBIO_CONFIG_LIGHT_MATRIX (1) #define PBIO_CONFIG_LOGGER (1) #define PBIO_CONFIG_MOTOR_PROCESS (1) #define PBIO_CONFIG_PORT (1) diff --git a/lib/pbio/platform/prime_hub/pbioconfig.h b/lib/pbio/platform/prime_hub/pbioconfig.h index 291b90e76..72a8c9d0e 100644 --- a/lib/pbio/platform/prime_hub/pbioconfig.h +++ b/lib/pbio/platform/prime_hub/pbioconfig.h @@ -9,6 +9,7 @@ #define PBIO_CONFIG_LIGHT (1) #define PBIO_CONFIG_LOGGER (1) #define PBIO_CONFIG_LIGHT_MATRIX (1) +#define PBIO_CONFIG_LIGHT_MATRIX_NUM_DEV (1) #define PBIO_CONFIG_MOTOR_PROCESS (1) #define PBIO_CONFIG_PORT (1) #define PBIO_CONFIG_PORT_NUM_DEV (6) diff --git a/lib/pbio/src/light/light_matrix.c b/lib/pbio/src/light/light_matrix.c index 4d65fe509..5cde67e68 100644 --- a/lib/pbio/src/light/light_matrix.c +++ b/lib/pbio/src/light/light_matrix.c @@ -14,6 +14,41 @@ #include "animation.h" #include "light_matrix.h" +static pbio_light_matrix_t light_matrices[PBIO_CONFIG_LIGHT_MATRIX_NUM_DEV]; + +/** + * Gets and initializes the light matrix. + * + * Skips initialization if already initialized. + * + * @param [in] index Device index. + * @param [in] size The size of the square light matrix. + * @param [out] light_matrix The light matrix + * @return ::PBIO_SUCCESS if device is available and initialization succeeds or completed previously. + * ::PBIO_ERROR_NO_DEV if this index is invalid. + */ +pbio_error_t pbio_light_matrix_get_dev(uint8_t index, uint8_t size, pbio_light_matrix_t **light_matrix) { + if (index >= PBIO_CONFIG_LIGHT_MATRIX_NUM_DEV) { + return PBIO_ERROR_NO_DEV; + } + pbio_light_matrix_t *dev = &light_matrices[index]; + *light_matrix = dev; + + if (dev->led_array_dev) { + // Already configured. + return PBIO_SUCCESS; + } + + pbio_error_t err = pbdrv_led_array_get_dev(index, &dev->led_array_dev); + if (err != PBIO_SUCCESS) { + return err; + } + + dev->size = size; + pbio_light_animation_init(&dev->animation, NULL); + return PBIO_SUCCESS; +} + /** * Sets the pixel to a given brightness. * @@ -54,23 +89,12 @@ static pbio_error_t _pbio_light_matrix_set_pixel(pbio_light_matrix_t *light_matr } } - // Set the pixel brightness - return light_matrix->funcs->set_pixel(light_matrix, row, col, brightness); -} + if (!light_matrix->led_array_dev) { + return PBIO_ERROR_NO_DEV; + } -/** - * Initializes the required fields in a ::pbio_light_matrix_t. - * - * This function must be called before using the ::pbio_light_matrix_t. - * - * @param [in] light_matrix The struct to initialize. - * @param [in] size The size of the light matrix. - * @param [in] funcs The instance-specific callback functions. - */ -void pbio_light_matrix_init(pbio_light_matrix_t *light_matrix, uint8_t size, const pbio_light_matrix_funcs_t *funcs) { - light_matrix->size = size; - light_matrix->funcs = funcs; - pbio_light_animation_init(&light_matrix->animation, NULL); + // Set the pixel brightness + return pbdrv_led_array_set_brightness(light_matrix->led_array_dev, row * light_matrix->size + col, brightness); } /** @@ -173,12 +197,13 @@ pbio_error_t pbio_light_matrix_set_rows(pbio_light_matrix_t *light_matrix, const * @param [in] row Row index (0 to size-1) * @param [in] col Column index (0 to size-1) * @param [in] brightness Brightness (0 to 100) + * @param [in] clear_animation Whether to clear the matrix if an animation was active. * @return ::PBIO_SUCCESS on success or an * implementation-specific error on failure. */ -pbio_error_t pbio_light_matrix_set_pixel(pbio_light_matrix_t *light_matrix, uint8_t row, uint8_t col, uint8_t brightness) { +pbio_error_t pbio_light_matrix_set_pixel(pbio_light_matrix_t *light_matrix, uint8_t row, uint8_t col, uint8_t brightness, bool clear_animation) { - if (pbio_light_animation_is_started(&light_matrix->animation)) { + if (clear_animation && pbio_light_animation_is_started(&light_matrix->animation)) { pbio_light_matrix_clear(light_matrix); } return _pbio_light_matrix_set_pixel(light_matrix, row, col, brightness); diff --git a/lib/pbio/src/light/light_matrix.h b/lib/pbio/src/light/light_matrix.h index 11ad6876b..8d848b94c 100644 --- a/lib/pbio/src/light/light_matrix.h +++ b/lib/pbio/src/light/light_matrix.h @@ -3,6 +3,8 @@ #include +#include + #include #include @@ -11,25 +13,9 @@ #ifndef _PBIO_LIGHT_LIGHT_MATRIX_H_ #define _PBIO_LIGHT_LIGHT_MATRIX_H_ -/** Implementation-specific callbacks for a light matrix. */ -typedef struct { - /** - * Sets the light at @p row, @p col to @p brightness. - * - * @param [in] light_matrix The light matrix instance. - * @param [in] row The row index (0 to size-1). - * @param [in] col The column index (0 to size-1). - * @param [in] brightness The apparent brightness (0 to 100). - * @return Success/failure of the operation. - */ - pbio_error_t (*set_pixel)(pbio_light_matrix_t *light_matrix, uint8_t row, uint8_t col, uint8_t brightness); -} pbio_light_matrix_funcs_t; - struct _pbio_light_matrix_t { /** Animation instance for background animation. */ pbio_light_animation_t animation; - /** Implementation specific callback functions. */ - const pbio_light_matrix_funcs_t *funcs; /** Animation cell data. */ const uint8_t *animation_cells; /** The number of cells in @p animation_cells */ @@ -42,8 +28,8 @@ struct _pbio_light_matrix_t { uint8_t size; /** Orientation of the matrix: which side is "up". */ pbio_geometry_side_t up_side; + /** The driver for this light matrix. */ + pbdrv_led_array_dev_t *led_array_dev; }; -void pbio_light_matrix_init(pbio_light_matrix_t *light_matrix, uint8_t size, const pbio_light_matrix_funcs_t *funcs); - #endif // _PBIO_LIGHT_LIGHT_MATRIX_H_ diff --git a/lib/pbio/sys/light_matrix.c b/lib/pbio/sys/light_matrix.c index 347600e52..6749da592 100644 --- a/lib/pbio/sys/light_matrix.c +++ b/lib/pbio/sys/light_matrix.c @@ -23,40 +23,8 @@ #if PBSYS_CONFIG_HUB_LIGHT_MATRIX -typedef struct { - /** Struct for PBIO light matrix implementation. */ - pbio_light_matrix_t light_matrix; -} pbsys_hub_light_matrix_t; - -static pbsys_hub_light_matrix_t pbsys_hub_light_matrix_instance; - -/** The hub built-in light matrix instance. */ -pbio_light_matrix_t *pbsys_hub_light_matrix = &pbsys_hub_light_matrix_instance.light_matrix; - -static pbio_error_t pbsys_hub_light_matrix_set_pixel(pbio_light_matrix_t *light_matrix, uint8_t row, uint8_t col, uint8_t brightness) { - #if PBSYS_CONFIG_HUB_LIGHT_MATRIX_LED_ARRAY - // REVISIT: currently hub light matrix is hard-coded as LED array at index 0 - // on all platforms - pbdrv_led_array_dev_t *array; - if (pbdrv_led_array_get_dev(0, &array) == PBIO_SUCCESS) { - return pbdrv_led_array_set_brightness(array, row * light_matrix->size + col, brightness); - } - #elif PBSYS_CONFIG_HUB_LIGHT_MATRIX_DISPLAY - pbio_image_t *display = pbdrv_display_get_image(); - uint8_t value = brightness * (pbdrv_display_get_max_value() + 1) / 100; - const uint32_t size = PBDRV_CONFIG_DISPLAY_NUM_ROWS / PBSYS_CONFIG_HMI_NUM_SLOTS; - const uint32_t width = size * 4 / 5; - const uint32_t offset = (PBDRV_CONFIG_DISPLAY_NUM_COLS - (PBSYS_CONFIG_HMI_NUM_SLOTS * size)) / 2; - pbio_image_fill_rect(display, col * size + offset, row * size, width, width, value); - pbdrv_display_update(); - return PBIO_SUCCESS; - #endif - return PBIO_ERROR_NOT_SUPPORTED; -} - -static const pbio_light_matrix_funcs_t pbsys_hub_light_matrix_funcs = { - .set_pixel = pbsys_hub_light_matrix_set_pixel, -}; +// revisit, we will drop this in the next commit +pbio_light_matrix_t *pbsys_hub_light_matrix; /** * Displays the idle UI. Has a square stop sign and selected slot on bottom row. @@ -70,7 +38,7 @@ static void pbsys_hub_light_matrix_show_idle_ui(uint8_t brightness) { #if PBSYS_CONFIG_HMI_NUM_SLOTS 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); + pbio_light_matrix_set_pixel(pbsys_hub_light_matrix, r, c, is_on ? brightness : 0, false); } } } @@ -109,7 +77,7 @@ static void pbsys_hub_light_matrix_start_power_animation(void) { } void pbsys_hub_light_matrix_init(void) { - pbio_light_matrix_init(pbsys_hub_light_matrix, 5, &pbsys_hub_light_matrix_funcs); + pbio_light_matrix_get_dev(0, 5, &pbsys_hub_light_matrix); pbsys_hub_light_matrix_start_power_animation(); } @@ -124,7 +92,7 @@ void pbsys_hub_light_matrix_deinit(void) { static void pbsys_hub_light_matrix_user_program_animation_clear(void) { for (uint8_t r = 0; r < 3; r++) { for (uint8_t c = 1; c < 4; c++) { - pbsys_hub_light_matrix_set_pixel(pbsys_hub_light_matrix, r, c, 0); + pbio_light_matrix_set_pixel(pbsys_hub_light_matrix, r, c, 0, true); } } } @@ -144,7 +112,7 @@ static uint32_t pbsys_hub_light_matrix_user_program_animation_next(pbio_light_an uint8_t brightness = offset > 200 ? 0 : (offset < 100 ? offset : 200 - offset); // Set the brightness for this pixel - pbsys_hub_light_matrix_set_pixel(pbsys_hub_light_matrix, indexes[i] / 5, indexes[i] % 5, brightness); + pbio_light_matrix_set_pixel(pbsys_hub_light_matrix, indexes[i] / 5, indexes[i] % 5, brightness, false); } // This increment controls the speed of the pattern cycle += 9; diff --git a/lib/pbio/test/src/test_light_matrix.c b/lib/pbio/test/src/test_light_matrix.c index d2d5abef9..b1636c457 100644 --- a/lib/pbio/test/src/test_light_matrix.c +++ b/lib/pbio/test/src/test_light_matrix.c @@ -58,17 +58,17 @@ static PT_THREAD(test_light_matrix(struct pt *pt)) { // set pixel should only set one pixel test_light_matrix_reset(); - tt_want_uint_op(pbio_light_matrix_set_pixel(&test_light_matrix, 0, 0, 100), ==, PBIO_SUCCESS); + tt_want_uint_op(pbio_light_matrix_set_pixel(&test_light_matrix, 0, 0, 100, true), ==, PBIO_SUCCESS); tt_want_light_matrix_data(100, 0, 0, 0, 0, 0, 0, 0, 0); tt_want_uint_op(pbio_light_matrix_set_pixel(&test_light_matrix, - MATRIX_SIZE - 1, MATRIX_SIZE - 1, 100), ==, PBIO_SUCCESS); + MATRIX_SIZE - 1, MATRIX_SIZE - 1, 100, true), ==, PBIO_SUCCESS); tt_want_light_matrix_data(100, 0, 0, 0, 0, 0, 0, 0, 100); // out of bounds checking - tt_want_uint_op(pbio_light_matrix_set_pixel(&test_light_matrix, MATRIX_SIZE, 0, 100), ==, PBIO_SUCCESS); + tt_want_uint_op(pbio_light_matrix_set_pixel(&test_light_matrix, MATRIX_SIZE, 0, 100, true), ==, PBIO_SUCCESS); tt_want_light_matrix_data(100, 0, 0, 0, 0, 0, 0, 0, 100); - tt_want_uint_op(pbio_light_matrix_set_pixel(&test_light_matrix, 0, MATRIX_SIZE, 100), ==, PBIO_SUCCESS); + tt_want_uint_op(pbio_light_matrix_set_pixel(&test_light_matrix, 0, MATRIX_SIZE, 100, true), ==, PBIO_SUCCESS); tt_want_light_matrix_data(100, 0, 0, 0, 0, 0, 0, 0, 100); // bitwise mapping diff --git a/pybricks/common/pb_type_lightmatrix.c b/pybricks/common/pb_type_lightmatrix.c index e46df968f..06c25b0e5 100644 --- a/pybricks/common/pb_type_lightmatrix.c +++ b/pybricks/common/pb_type_lightmatrix.c @@ -160,7 +160,7 @@ static mp_obj_t common_LightMatrix_on(size_t n_args, const mp_obj_t *pos_args, m for (uint8_t i = 0; i < size; i++) { for (uint8_t j = 0; j < size; j++) { - pb_assert(pbio_light_matrix_set_pixel(self->light_matrix, i, j, brightness)); + pb_assert(pbio_light_matrix_set_pixel(self->light_matrix, i, j, brightness, true)); } } @@ -218,7 +218,7 @@ void pb_type_LightMatrix_display_number(pbio_light_matrix_t *light_matrix, mp_ob // Display one faint dot in the middle to indicate negative if (negative) { - pb_assert(pbio_light_matrix_set_pixel(light_matrix, 2, 2, 50)); + pb_assert(pbio_light_matrix_set_pixel(light_matrix, 2, 2, 50, true)); } } @@ -276,7 +276,7 @@ static mp_obj_t common_LightMatrix_pixel(size_t n_args, const mp_obj_t *pos_args PB_ARG_DEFAULT_INT(brightness, 100)); // Set pixel at the given brightness - pb_assert(pbio_light_matrix_set_pixel(self->light_matrix, pb_obj_get_int(row_in), pb_obj_get_int(column_in), pb_obj_get_pct(brightness_in))); + pb_assert(pbio_light_matrix_set_pixel(self->light_matrix, pb_obj_get_int(row_in), pb_obj_get_int(column_in), pb_obj_get_pct(brightness_in), true)); return mp_const_none; } From 1fb73908e19022e17ed0309a5256a86520e4ec52 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Mon, 29 Sep 2025 11:50:39 +0200 Subject: [PATCH 10/12] pbio/test/pwm: Implement led array pwm for tests. This is used for light matrix tests, which now require this. --- lib/pbio/drv/pwm/pwm_test.c | 13 +++++ lib/pbio/platform/test/pbdrvconfig.h | 4 +- lib/pbio/platform/test/pbioconfig.h | 1 + lib/pbio/platform/test/platform.c | 14 ++++++ lib/pbio/test/src/test_light_matrix.c | 69 ++++++++++++++------------- 5 files changed, 66 insertions(+), 35 deletions(-) diff --git a/lib/pbio/drv/pwm/pwm_test.c b/lib/pbio/drv/pwm/pwm_test.c index f0386bfe7..b6390ae03 100644 --- a/lib/pbio/drv/pwm/pwm_test.c +++ b/lib/pbio/drv/pwm/pwm_test.c @@ -5,6 +5,8 @@ #if PBDRV_CONFIG_PWM_TEST +#include +#include #include #include @@ -16,6 +18,10 @@ #include "../drv/pwm/pwm.h" +#define MATRIX_SIZE (3) + +uint8_t test_light_matrix_set_pixel_last_brightness[MATRIX_SIZE][MATRIX_SIZE]; + typedef struct { uint32_t duty_channel; uint32_t duty_value; @@ -27,6 +33,13 @@ static pbio_error_t test_set_duty(pbdrv_pwm_dev_t *dev, uint32_t ch, uint32_t va test_private_data_t *priv = dev->priv; priv->duty_channel = ch; priv->duty_value = value; + + // The value we get from the LED PWM driver is squared for gamma correction, so undo here. + float val = (value * 10000.0f) / UINT16_MAX; + uint8_t brightness = sqrt(val) + 0.5; + + test_light_matrix_set_pixel_last_brightness[ch / MATRIX_SIZE][ch % MATRIX_SIZE] = brightness; + return PBIO_SUCCESS; } diff --git a/lib/pbio/platform/test/pbdrvconfig.h b/lib/pbio/platform/test/pbdrvconfig.h index 0d11040e2..023b9c954 100644 --- a/lib/pbio/platform/test/pbdrvconfig.h +++ b/lib/pbio/platform/test/pbdrvconfig.h @@ -26,7 +26,9 @@ #define PBDRV_CONFIG_LED_NUM_DEV (0) #define PBDRV_CONFIG_LED_ARRAY (1) -#define PBDRV_CONFIG_LED_ARRAY_NUM_DEV (0) +#define PBDRV_CONFIG_LED_ARRAY_NUM_DEV (1) +#define PBDRV_CONFIG_LED_ARRAY_PWM (1) +#define PBDRV_CONFIG_LED_ARRAY_PWM_NUM_DEV (1) #define PBDRV_CONFIG_MOTOR_DRIVER (1) #define PBDRV_CONFIG_MOTOR_DRIVER_NUM_DEV (6) diff --git a/lib/pbio/platform/test/pbioconfig.h b/lib/pbio/platform/test/pbioconfig.h index 3818779b2..4d8116e33 100644 --- a/lib/pbio/platform/test/pbioconfig.h +++ b/lib/pbio/platform/test/pbioconfig.h @@ -8,6 +8,7 @@ #define PBIO_CONFIG_LIGHT (1) #define PBIO_CONFIG_LOGGER (1) #define PBIO_CONFIG_LIGHT_MATRIX (1) +#define PBIO_CONFIG_LIGHT_MATRIX_NUM_DEV (1) #define PBIO_CONFIG_MOTOR_PROCESS (1) #define PBIO_CONFIG_PORT (1) #define PBIO_CONFIG_PORT_NUM_DEV (6) diff --git a/lib/pbio/platform/test/platform.c b/lib/pbio/platform/test/platform.c index 647284f0a..017ce3a18 100644 --- a/lib/pbio/platform/test/platform.c +++ b/lib/pbio/platform/test/platform.c @@ -5,6 +5,7 @@ #include #include "../../drv/motor_driver/motor_driver_virtual_simulation.h" +#include "../../drv/led/led_array_pwm.h" const pbdrv_gpio_t pbdrv_ioport_platform_data_vcc_pin = { .bank = NULL, @@ -128,3 +129,16 @@ const pbdrv_motor_driver_virtual_simulation_platform_data_t .endstop_angle_positive = INFINITY, }, }; + +const pbdrv_led_array_pwm_platform_data_t pbdrv_led_array_pwm_platform_data[PBDRV_CONFIG_LED_ARRAY_PWM_NUM_DEV] = { + { + .pwm_chs = (const uint8_t[]) { + 0, 1, 2, + 3, 4, 5, + 6, 7, 8 + }, + .num_pwm_chs = 9, + .pwm_id = 0, + .id = 0, + }, +}; diff --git a/lib/pbio/test/src/test_light_matrix.c b/lib/pbio/test/src/test_light_matrix.c index b1636c457..f3d7c029c 100644 --- a/lib/pbio/test/src/test_light_matrix.c +++ b/lib/pbio/test/src/test_light_matrix.c @@ -15,6 +15,8 @@ #include "../src/light/light_matrix.h" #include "../drv/clock/clock_test.h" +#include "../drv/pwm/pwm.h" +#include "../drv/led/led_array.h" #define MATRIX_SIZE 3 #define INTERVAL 10 @@ -32,60 +34,55 @@ static const uint8_t test_animation[] = { 11, 12, 13, 14, 15, 16, 17, 18, 19, }; -static uint8_t test_light_matrix_set_pixel_last_brightness[MATRIX_SIZE][MATRIX_SIZE]; +// Buffer in the led_array pwm driver. +extern uint8_t test_light_matrix_set_pixel_last_brightness[MATRIX_SIZE][MATRIX_SIZE]; static void test_light_matrix_reset(void) { memset(test_light_matrix_set_pixel_last_brightness, 0, DATA_SIZE); } -static pbio_error_t test_light_matrix_set_pixel(pbio_light_matrix_t *light_matrix, uint8_t row, uint8_t col, uint8_t brightness) { - test_light_matrix_set_pixel_last_brightness[row][col] = brightness; - return PBIO_SUCCESS; -} - -static const pbio_light_matrix_funcs_t test_light_matrix_funcs = { - .set_pixel = test_light_matrix_set_pixel, -}; - static PT_THREAD(test_light_matrix(struct pt *pt)) { PT_BEGIN(pt); - static pbio_light_matrix_t test_light_matrix; - pbio_light_matrix_init(&test_light_matrix, MATRIX_SIZE, &test_light_matrix_funcs); + pbdrv_pwm_init(); + pbdrv_led_array_init(); + + static pbio_light_matrix_t *test_light_matrix; + pbio_light_matrix_get_dev(0, MATRIX_SIZE, &test_light_matrix); // ensure get size works - tt_want_uint_op(pbio_light_matrix_get_size(&test_light_matrix), ==, MATRIX_SIZE); + tt_want_uint_op(pbio_light_matrix_get_size(test_light_matrix), ==, MATRIX_SIZE); // set pixel should only set one pixel test_light_matrix_reset(); - tt_want_uint_op(pbio_light_matrix_set_pixel(&test_light_matrix, 0, 0, 100, true), ==, PBIO_SUCCESS); + tt_want_uint_op(pbio_light_matrix_set_pixel(test_light_matrix, 0, 0, 100, true), ==, PBIO_SUCCESS); tt_want_light_matrix_data(100, 0, 0, 0, 0, 0, 0, 0, 0); - tt_want_uint_op(pbio_light_matrix_set_pixel(&test_light_matrix, + tt_want_uint_op(pbio_light_matrix_set_pixel(test_light_matrix, MATRIX_SIZE - 1, MATRIX_SIZE - 1, 100, true), ==, PBIO_SUCCESS); tt_want_light_matrix_data(100, 0, 0, 0, 0, 0, 0, 0, 100); // out of bounds checking - tt_want_uint_op(pbio_light_matrix_set_pixel(&test_light_matrix, MATRIX_SIZE, 0, 100, true), ==, PBIO_SUCCESS); + tt_want_uint_op(pbio_light_matrix_set_pixel(test_light_matrix, MATRIX_SIZE, 0, 100, true), ==, PBIO_SUCCESS); tt_want_light_matrix_data(100, 0, 0, 0, 0, 0, 0, 0, 100); - tt_want_uint_op(pbio_light_matrix_set_pixel(&test_light_matrix, 0, MATRIX_SIZE, 100, true), ==, PBIO_SUCCESS); + tt_want_uint_op(pbio_light_matrix_set_pixel(test_light_matrix, 0, MATRIX_SIZE, 100, true), ==, PBIO_SUCCESS); tt_want_light_matrix_data(100, 0, 0, 0, 0, 0, 0, 0, 100); // bitwise mapping test_light_matrix_reset(); - tt_want_uint_op(pbio_light_matrix_set_rows(&test_light_matrix, ROW_DATA(0b100, 0b010, 0b001)), ==, PBIO_SUCCESS); + tt_want_uint_op(pbio_light_matrix_set_rows(test_light_matrix, ROW_DATA(0b100, 0b010, 0b001)), ==, PBIO_SUCCESS); tt_want_light_matrix_data(100, 0, 0, 0, 100, 0, 0, 0, 100); // bytewise mapping test_light_matrix_reset(); - tt_want_uint_op(pbio_light_matrix_set_image(&test_light_matrix, + tt_want_uint_op(pbio_light_matrix_set_image(test_light_matrix, IMAGE_DATA(1, 2, 3, 4, 5, 6, 7, 8, 9)), ==, PBIO_SUCCESS); tt_want_light_matrix_data(1, 2, 3, 4, 5, 6, 7, 8, 9); // starting animation should schedule timer event at 0 ms to call // set_pixel() after handling pending events. test_light_matrix_reset(); - pbio_light_matrix_start_animation(&test_light_matrix, test_animation, 2, INTERVAL); + pbio_light_matrix_start_animation(test_light_matrix, test_animation, 2, INTERVAL); pbio_handle_pending_events(); tt_want_light_matrix_data(1, 2, 3, 4, 5, 6, 7, 8, 9); @@ -110,7 +107,7 @@ static PT_THREAD(test_light_matrix(struct pt *pt)) { // stopping the animation should not change any pixels test_light_matrix_reset(); - pbio_light_matrix_stop_animation(&test_light_matrix); + pbio_light_matrix_stop_animation(test_light_matrix); pbio_test_clock_tick(INTERVAL * 2); PT_YIELD(pt); tt_want_light_matrix_data(0); @@ -119,12 +116,16 @@ static PT_THREAD(test_light_matrix(struct pt *pt)) { } static void test_light_matrix_rotation(void *env) { - static pbio_light_matrix_t test_light_matrix; - pbio_light_matrix_init(&test_light_matrix, MATRIX_SIZE, &test_light_matrix_funcs); + + pbdrv_pwm_init(); + pbdrv_led_array_init(); + + static pbio_light_matrix_t *test_light_matrix; + pbio_light_matrix_get_dev(0, MATRIX_SIZE, &test_light_matrix); // Default orientation has pixels in same order as underlying light array test_light_matrix_reset(); - tt_want_uint_op(pbio_light_matrix_set_image(&test_light_matrix, + tt_want_uint_op(pbio_light_matrix_set_image(test_light_matrix, IMAGE_DATA(1, 2, 3, 4, 5, 6, 7, 8, 9)), ==, PBIO_SUCCESS); tt_want_light_matrix_data( 1, 2, 3, @@ -134,8 +135,8 @@ static void test_light_matrix_rotation(void *env) { // Check that other orientations work test_light_matrix_reset(); - pbio_light_matrix_set_orientation(&test_light_matrix, PBIO_GEOMETRY_SIDE_LEFT); - tt_want_uint_op(pbio_light_matrix_set_image(&test_light_matrix, + pbio_light_matrix_set_orientation(test_light_matrix, PBIO_GEOMETRY_SIDE_LEFT); + tt_want_uint_op(pbio_light_matrix_set_image(test_light_matrix, IMAGE_DATA(1, 2, 3, 4, 5, 6, 7, 8, 9)), ==, PBIO_SUCCESS); tt_want_light_matrix_data( 3, 6, 9, @@ -143,8 +144,8 @@ static void test_light_matrix_rotation(void *env) { 1, 4, 7); test_light_matrix_reset(); - pbio_light_matrix_set_orientation(&test_light_matrix, PBIO_GEOMETRY_SIDE_BOTTOM); - tt_want_uint_op(pbio_light_matrix_set_image(&test_light_matrix, + pbio_light_matrix_set_orientation(test_light_matrix, PBIO_GEOMETRY_SIDE_BOTTOM); + tt_want_uint_op(pbio_light_matrix_set_image(test_light_matrix, IMAGE_DATA(1, 2, 3, 4, 5, 6, 7, 8, 9)), ==, PBIO_SUCCESS); tt_want_light_matrix_data( 9, 8, 7, @@ -152,8 +153,8 @@ static void test_light_matrix_rotation(void *env) { 3, 2, 1); test_light_matrix_reset(); - pbio_light_matrix_set_orientation(&test_light_matrix, PBIO_GEOMETRY_SIDE_RIGHT); - tt_want_uint_op(pbio_light_matrix_set_image(&test_light_matrix, + pbio_light_matrix_set_orientation(test_light_matrix, PBIO_GEOMETRY_SIDE_RIGHT); + tt_want_uint_op(pbio_light_matrix_set_image(test_light_matrix, IMAGE_DATA(1, 2, 3, 4, 5, 6, 7, 8, 9)), ==, PBIO_SUCCESS); tt_want_light_matrix_data( 7, 4, 1, @@ -162,8 +163,8 @@ static void test_light_matrix_rotation(void *env) { // front is same as top test_light_matrix_reset(); - pbio_light_matrix_set_orientation(&test_light_matrix, PBIO_GEOMETRY_SIDE_FRONT); - tt_want_uint_op(pbio_light_matrix_set_image(&test_light_matrix, + pbio_light_matrix_set_orientation(test_light_matrix, PBIO_GEOMETRY_SIDE_FRONT); + tt_want_uint_op(pbio_light_matrix_set_image(test_light_matrix, IMAGE_DATA(1, 2, 3, 4, 5, 6, 7, 8, 9)), ==, PBIO_SUCCESS); tt_want_light_matrix_data( 1, 2, 3, @@ -172,8 +173,8 @@ static void test_light_matrix_rotation(void *env) { // back is same as bottom test_light_matrix_reset(); - pbio_light_matrix_set_orientation(&test_light_matrix, PBIO_GEOMETRY_SIDE_BACK); - tt_want_uint_op(pbio_light_matrix_set_image(&test_light_matrix, + pbio_light_matrix_set_orientation(test_light_matrix, PBIO_GEOMETRY_SIDE_BACK); + tt_want_uint_op(pbio_light_matrix_set_image(test_light_matrix, IMAGE_DATA(1, 2, 3, 4, 5, 6, 7, 8, 9)), ==, PBIO_SUCCESS); tt_want_light_matrix_data( 9, 8, 7, From 5a7b2300adae2b97b0eda66227970ae6ef24d39b Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Sun, 28 Sep 2025 20:43:26 +0200 Subject: [PATCH 11/12] pbio/light_animation: Make API public. Separation of powers is nice, but since we have #include "../src/light/light_matrix.h" there is clearly something not quite right. Let's be pragmatic and make the APIs for animations public. --- .../pbio/light_animation.h} | 0 lib/pbio/include/pbio/light_matrix.h | 22 +++++++++++- lib/pbio/src/light/animation.c | 2 +- lib/pbio/src/light/color_light.c | 2 +- lib/pbio/src/light/color_light.h | 2 +- lib/pbio/src/light/light_matrix.c | 4 +-- lib/pbio/src/light/light_matrix.h | 35 ------------------- lib/pbio/src/main.c | 3 +- lib/pbio/sys/light_matrix.c | 2 +- lib/pbio/test/src/test_animation.c | 3 +- lib/pbio/test/src/test_light_matrix.c | 1 - 11 files changed, 30 insertions(+), 46 deletions(-) rename lib/pbio/{src/light/animation.h => include/pbio/light_animation.h} (100%) delete mode 100644 lib/pbio/src/light/light_matrix.h diff --git a/lib/pbio/src/light/animation.h b/lib/pbio/include/pbio/light_animation.h similarity index 100% rename from lib/pbio/src/light/animation.h rename to lib/pbio/include/pbio/light_animation.h diff --git a/lib/pbio/include/pbio/light_matrix.h b/lib/pbio/include/pbio/light_matrix.h index 0f7717344..a279d8258 100644 --- a/lib/pbio/include/pbio/light_matrix.h +++ b/lib/pbio/include/pbio/light_matrix.h @@ -11,12 +11,32 @@ #include +#include + #include #include #include +#include /** A light matrix instance. */ -typedef struct _pbio_light_matrix_t pbio_light_matrix_t; +typedef struct { + /** Animation instance for background animation. */ + pbio_light_animation_t animation; + /** Animation cell data. */ + const uint8_t *animation_cells; + /** The number of cells in @p animation_cells */ + uint8_t num_animation_cells; + /** The index of the currently displayed animation cell. */ + uint8_t current_cell; + /** Animation update rate in milliseconds. */ + uint16_t interval; + /** Size of the matrix (assumes matrix is square). */ + uint8_t size; + /** Orientation of the matrix: which side is "up". */ + pbio_geometry_side_t up_side; + /** The driver for this light matrix. */ + pbdrv_led_array_dev_t *led_array_dev; +} pbio_light_matrix_t; #if PBIO_CONFIG_LIGHT_MATRIX diff --git a/lib/pbio/src/light/animation.c b/lib/pbio/src/light/animation.c index 9947413dc..980365864 100644 --- a/lib/pbio/src/light/animation.c +++ b/lib/pbio/src/light/animation.c @@ -12,7 +12,7 @@ #include -#include "animation.h" +#include /** * This is used as a value for the next_animation field to indicate when an diff --git a/lib/pbio/src/light/color_light.c b/lib/pbio/src/light/color_light.c index 26deafaf0..b8429b359 100644 --- a/lib/pbio/src/light/color_light.c +++ b/lib/pbio/src/light/color_light.c @@ -14,7 +14,7 @@ #include #include -#include "animation.h" +#include #include "color_light.h" /** diff --git a/lib/pbio/src/light/color_light.h b/lib/pbio/src/light/color_light.h index 43e6954cd..84f532af3 100644 --- a/lib/pbio/src/light/color_light.h +++ b/lib/pbio/src/light/color_light.h @@ -7,7 +7,7 @@ #include #include -#include "animation.h" +#include #ifndef _PBIO_LIGHT_COLOR_LIGHT_H_ #define _PBIO_LIGHT_COLOR_LIGHT_H_ diff --git a/lib/pbio/src/light/light_matrix.c b/lib/pbio/src/light/light_matrix.c index 5cde67e68..15b7b0163 100644 --- a/lib/pbio/src/light/light_matrix.c +++ b/lib/pbio/src/light/light_matrix.c @@ -11,8 +11,8 @@ #include #include -#include "animation.h" -#include "light_matrix.h" +#include +#include static pbio_light_matrix_t light_matrices[PBIO_CONFIG_LIGHT_MATRIX_NUM_DEV]; diff --git a/lib/pbio/src/light/light_matrix.h b/lib/pbio/src/light/light_matrix.h deleted file mode 100644 index 8d848b94c..000000000 --- a/lib/pbio/src/light/light_matrix.h +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2020 The Pybricks Authors - -#include - -#include - -#include -#include - -#include "animation.h" - -#ifndef _PBIO_LIGHT_LIGHT_MATRIX_H_ -#define _PBIO_LIGHT_LIGHT_MATRIX_H_ - -struct _pbio_light_matrix_t { - /** Animation instance for background animation. */ - pbio_light_animation_t animation; - /** Animation cell data. */ - const uint8_t *animation_cells; - /** The number of cells in @p animation_cells */ - uint8_t num_animation_cells; - /** The index of the currently displayed animation cell. */ - uint8_t current_cell; - /** Animation update rate in milliseconds. */ - uint16_t interval; - /** Size of the matrix (assumes matrix is square). */ - uint8_t size; - /** Orientation of the matrix: which side is "up". */ - pbio_geometry_side_t up_side; - /** The driver for this light matrix. */ - pbdrv_led_array_dev_t *led_array_dev; -}; - -#endif // _PBIO_LIGHT_LIGHT_MATRIX_H_ diff --git a/lib/pbio/src/main.c b/lib/pbio/src/main.c index ece5b2b18..6df9c7378 100644 --- a/lib/pbio/src/main.c +++ b/lib/pbio/src/main.c @@ -15,11 +15,10 @@ #include #include #include +#include #include #include -#include "light/animation.h" - /** * Initialize the Pybricks I/O Library. This function must be called once, * usually at the beginning of a program, before using any other functions in diff --git a/lib/pbio/sys/light_matrix.c b/lib/pbio/sys/light_matrix.c index 6749da592..507f3ba38 100644 --- a/lib/pbio/sys/light_matrix.c +++ b/lib/pbio/sys/light_matrix.c @@ -18,7 +18,7 @@ #include #include -#include "../src/light/light_matrix.h" +#include #include "hmi.h" #if PBSYS_CONFIG_HUB_LIGHT_MATRIX diff --git a/lib/pbio/test/src/test_animation.c b/lib/pbio/test/src/test_animation.c index 36b8cec89..65e044dce 100644 --- a/lib/pbio/test/src/test_animation.c +++ b/lib/pbio/test/src/test_animation.c @@ -9,7 +9,8 @@ #include -#include "../src/light/animation.h" +#include + #include "../drv/clock/clock_test.h" #define TEST_ANIMATION_TIME 10 diff --git a/lib/pbio/test/src/test_light_matrix.c b/lib/pbio/test/src/test_light_matrix.c index f3d7c029c..0f8a97927 100644 --- a/lib/pbio/test/src/test_light_matrix.c +++ b/lib/pbio/test/src/test_light_matrix.c @@ -13,7 +13,6 @@ #include #include -#include "../src/light/light_matrix.h" #include "../drv/clock/clock_test.h" #include "../drv/pwm/pwm.h" #include "../drv/led/led_array.h" From 5c3a1daff193b4a24d210221c63d46b5846cac45 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Mon, 29 Sep 2025 09:50:18 +0200 Subject: [PATCH 12/12] pbio/hmi: Use init and deinit for boot animations. This lets us run it to completion before showing the UI, and keep variant-specific statuses like BLE_ADVERTISING in a dedicated deinit. Also drop the pbio/sys/light_matrix abstraction as explained in the prior commits. The HMI and user applications are now just users of the pbio/light_matrix device. For EV3, this means we can drop the depency of the light matrix. Instead, it will have its dedicated UI. To have something in place, we'll keep the grid-like display for now, but in hmi_lcd so we can easily replace it. --- bricks/_common/sources.mk | 1 - lib/pbio/platform/ev3/pbsysconfig.h | 2 - lib/pbio/platform/prime_hub/pbsysconfig.h | 1 + lib/pbio/sys/core.c | 8 +- lib/pbio/sys/hmi.h | 8 ++ lib/pbio/sys/hmi_lcd.c | 36 +++++- lib/pbio/sys/hmi_none.c | 6 + lib/pbio/sys/hmi_pup.c | 117 ++++++++++++++++++- lib/pbio/sys/light_matrix.c | 136 ---------------------- lib/pbio/sys/light_matrix.h | 23 ---- lib/pbio/sys/main.c | 1 - 11 files changed, 165 insertions(+), 174 deletions(-) delete mode 100644 lib/pbio/sys/light_matrix.c delete mode 100644 lib/pbio/sys/light_matrix.h diff --git a/bricks/_common/sources.mk b/bricks/_common/sources.mk index 8772b8733..f60083aba 100644 --- a/bricks/_common/sources.mk +++ b/bricks/_common/sources.mk @@ -239,7 +239,6 @@ PBIO_SRC_C = $(addprefix lib/pbio/,\ sys/hmi_pup.c \ sys/hmi_none.c \ sys/host.c \ - sys/light_matrix.c \ sys/light.c \ sys/main.c \ sys/program_stop.c \ diff --git a/lib/pbio/platform/ev3/pbsysconfig.h b/lib/pbio/platform/ev3/pbsysconfig.h index 2007b3a1b..5e643bb38 100644 --- a/lib/pbio/platform/ev3/pbsysconfig.h +++ b/lib/pbio/platform/ev3/pbsysconfig.h @@ -13,8 +13,6 @@ #define PBSYS_CONFIG_HMI_NUM_SLOTS (5) #define PBSYS_CONFIG_HOST (1) #define PBSYS_CONFIG_HOST_STDIN_BUF_SIZE (21) -#define PBSYS_CONFIG_HUB_LIGHT_MATRIX (1) -#define PBSYS_CONFIG_HUB_LIGHT_MATRIX_DISPLAY (1) #define PBSYS_CONFIG_MAIN (1) #define PBSYS_CONFIG_STORAGE (1) #define PBSYS_CONFIG_STORAGE_NUM_SLOTS (5) diff --git a/lib/pbio/platform/prime_hub/pbsysconfig.h b/lib/pbio/platform/prime_hub/pbsysconfig.h index 2e599c7fd..e0f1c0a2e 100644 --- a/lib/pbio/platform/prime_hub/pbsysconfig.h +++ b/lib/pbio/platform/prime_hub/pbsysconfig.h @@ -13,6 +13,7 @@ #define PBSYS_CONFIG_HMI_STOP_BUTTON (1 << 5) // center #define PBSYS_CONFIG_HMI_NUM_SLOTS (5) #define PBSYS_CONFIG_HMI_PUP (1) +#define PBSYS_CONFIG_HMI_PUP_LIGHT_MATRIX_INDEX (0) #define PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON (1 << 9) // right up #define PBSYS_CONFIG_HMI_PUP_LEFT_RIGHT_ENABLE (1) #define PBSYS_CONFIG_HUB_LIGHT_MATRIX (1) diff --git a/lib/pbio/sys/core.c b/lib/pbio/sys/core.c index 87b23922e..66640dff6 100644 --- a/lib/pbio/sys/core.c +++ b/lib/pbio/sys/core.c @@ -16,7 +16,6 @@ #include "hmi.h" #include "light.h" -#include "light_matrix.h" #include "storage.h" #include "program_stop.h" @@ -52,9 +51,9 @@ void pbsys_init(void) { pbsys_storage_init(); pbsys_battery_init(); + pbsys_hmi_init(); pbsys_host_init(); pbsys_status_light_init(); - pbsys_hub_light_matrix_init(); process_start(&pbsys_system_process); @@ -65,11 +64,8 @@ void pbsys_init(void) { void pbsys_deinit(void) { - pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); - pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_HOST_CONNECTED); - pbsys_storage_deinit(); - pbsys_hub_light_matrix_deinit(); + pbsys_hmi_deinit(); uint32_t start = pbdrv_clock_get_ms(); diff --git a/lib/pbio/sys/hmi.h b/lib/pbio/sys/hmi.h index 45fb74f06..6822023c9 100644 --- a/lib/pbio/sys/hmi.h +++ b/lib/pbio/sys/hmi.h @@ -11,10 +11,18 @@ #if PBSYS_CONFIG_HMI +void pbsys_hmi_init(void); +void pbsys_hmi_deinit(void); + pbio_error_t pbsys_hmi_await_program_selection(void); #else +static inline void pbsys_hmi_init(void) { +} +static inline void pbsys_hmi_deinit(void) { +} + static inline pbio_error_t pbsys_hmi_await_program_selection(void) { return PBIO_ERROR_NOT_SUPPORTED; } diff --git a/lib/pbio/sys/hmi_lcd.c b/lib/pbio/sys/hmi_lcd.c index 53d3f6754..679dcf811 100644 --- a/lib/pbio/sys/hmi_lcd.c +++ b/lib/pbio/sys/hmi_lcd.c @@ -25,7 +25,6 @@ #include #include "hmi.h" -#include "light_matrix.h" #define DEBUG 0 @@ -36,6 +35,39 @@ #define DEBUG_PRINT(...) #endif +static void hmi_lcd_grid_show_pixel(uint8_t row, uint8_t col, bool on) { + pbio_image_t *display = pbdrv_display_get_image(); + uint8_t value = on ? pbdrv_display_get_max_value(): 0; + const uint32_t size = PBDRV_CONFIG_DISPLAY_NUM_ROWS / PBSYS_CONFIG_HMI_NUM_SLOTS; + const uint32_t width = size * 4 / 5; + const uint32_t offset = (PBDRV_CONFIG_DISPLAY_NUM_COLS - (PBSYS_CONFIG_HMI_NUM_SLOTS * size)) / 2; + pbio_image_fill_rect(display, col * size + offset, row * size, width, width, value); + pbdrv_display_update(); +} + +static void hmi_lcd_show_program_slot(void) { + pbio_image_t *display = pbdrv_display_get_image(); + pbio_image_fill(display, 0); + + for (uint8_t r = 0; r < PBSYS_CONFIG_HMI_NUM_SLOTS; r++) { + for (uint8_t c = 0; c < PBSYS_CONFIG_HMI_NUM_SLOTS; c++) { + bool is_on = r < 3 && c > 0 && c < 4; + is_on |= (r == 4 && c == pbsys_status_get_selected_slot()); + hmi_lcd_grid_show_pixel(r, c, is_on); + } + } +} + +void pbsys_hmi_init(void) { + +} + +void pbsys_hmi_deinit(void) { + pbio_image_t *display = pbdrv_display_get_image(); + pbio_image_fill(display, 0); + pbdrv_display_update(); +} + static pbio_error_t run_ui(pbio_os_state_t *state, pbio_os_timer_t *timer) { PBIO_OS_ASYNC_BEGIN(state); @@ -45,7 +77,7 @@ static pbio_error_t run_ui(pbio_os_state_t *state, pbio_os_timer_t *timer) { DEBUG_PRINT("Start HMI loop\n"); // Visually indicate current state on supported hubs. - pbsys_hub_light_matrix_update_program_slot(); + hmi_lcd_show_program_slot(); // Buttons could be pressed at the end of the user program, so wait for // a release and then a new press, or until we have to exit early. diff --git a/lib/pbio/sys/hmi_none.c b/lib/pbio/sys/hmi_none.c index 442b02986..b7eb8e9a4 100644 --- a/lib/pbio/sys/hmi_none.c +++ b/lib/pbio/sys/hmi_none.c @@ -17,6 +17,12 @@ #include #include +void pbsys_hmi_init(void) { +} + +void pbsys_hmi_deinit(void) { +} + pbio_error_t pbsys_hmi_await_program_selection(void) { while (pbdrv_button_get_pressed()) { diff --git a/lib/pbio/sys/hmi_pup.c b/lib/pbio/sys/hmi_pup.c index 72164cc5d..5414b8dac 100644 --- a/lib/pbio/sys/hmi_pup.c +++ b/lib/pbio/sys/hmi_pup.c @@ -13,8 +13,11 @@ #include #include +#include #include +#include +#include #include #include #include @@ -23,7 +26,6 @@ #include #include "hmi.h" -#include "light_matrix.h" #define DEBUG 0 @@ -34,6 +36,110 @@ #define DEBUG_PRINT(...) #endif +#if PBIO_CONFIG_LIGHT_MATRIX + +pbio_light_matrix_t *pbsys_hub_light_matrix; + +/** + * Displays the idle UI. Has a square stop sign and selected slot on bottom row. + * + * @param brightness Brightness (0--100%). + */ +static void light_matrix_show_idle_ui(uint8_t brightness) { + for (uint8_t r = 0; r < PBSYS_CONFIG_HMI_NUM_SLOTS; r++) { + for (uint8_t c = 0; c < PBSYS_CONFIG_HMI_NUM_SLOTS; c++) { + bool is_on = r < 3 && c > 0 && c < 4; + is_on |= (r == 4 && c == pbsys_status_get_selected_slot()); + pbio_light_matrix_set_pixel(pbsys_hub_light_matrix, r, c, is_on ? brightness : 0, true); + } + } +} + +/** + * Bootup and shutdown animation. This is a process rather than an animation + * process so we can await completion, and later add sound. + */ +static pbio_error_t boot_animation_process_boot_thread(pbio_os_state_t *state, void *context) { + + static pbio_os_timer_t timer; + static uint8_t step; + + const int num_steps = 10; + + // Makes the brightness increment or decrement. + bool booting = context; + + PBIO_OS_ASYNC_BEGIN(state); + + for (step = 1; step <= num_steps; step++) { + uint8_t brightness = booting ? step * (100 / num_steps) : 100 - (100 / num_steps) * step; + light_matrix_show_idle_ui(brightness); + PBIO_OS_AWAIT_MS(state, &timer, 200 / num_steps); + } + pbio_busy_count_down(); + + PBIO_OS_ASYNC_END(PBIO_SUCCESS); +} + +/** + * Animation frame for program running animation. + */ +static uint32_t pbio_light_matrix_5x5_spinner_animation_next(pbio_light_animation_t *animation) { + + // The indexes of pixels to light up + static const uint8_t indexes[] = { 1, 2, 3, 8, 13, 12, 11, 6 }; + + // Each pixel has a repeating brightness pattern of the form /\_ through + // which we can cycle in 256 steps. + static uint8_t cycle = 0; + + for (size_t i = 0; i < PBIO_ARRAY_SIZE(indexes); i++) { + // The pixels are spread equally across the pattern. + uint8_t offset = cycle + i * (UINT8_MAX / PBIO_ARRAY_SIZE(indexes)); + uint8_t brightness = offset > 200 ? 0 : (offset < 100 ? offset : 200 - offset); + + // Set the brightness for this pixel + pbio_light_matrix_set_pixel(pbsys_hub_light_matrix, indexes[i] / 5, indexes[i] % 5, brightness, false); + } + // This increment controls the speed of the pattern + cycle += 9; + + return 40; +} + +static void light_matrix_start_run_animation(void) { + // Central pixel in spinner is off and will not be updated. + pbio_light_matrix_set_pixel(pbsys_hub_light_matrix, 1, 2, 0, true); + pbio_light_animation_init(&pbsys_hub_light_matrix->animation, pbio_light_matrix_5x5_spinner_animation_next); + pbio_light_animation_start(&pbsys_hub_light_matrix->animation); +} + +static pbio_os_process_t boot_animation_process; + +#endif + +void pbsys_hmi_init(void) { + #if PBIO_CONFIG_LIGHT_MATRIX + pbio_busy_count_up(); + pbio_error_t err = pbio_light_matrix_get_dev(0, 5, &pbsys_hub_light_matrix); + if (err != PBIO_SUCCESS) { + // Effectively stopping boot if we can't get hardware. + return; + } + pbio_os_process_start(&boot_animation_process, boot_animation_process_boot_thread, (void *)true); + #endif +} + +void pbsys_hmi_deinit(void) { + pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); + pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_HOST_CONNECTED); + + #if PBIO_CONFIG_LIGHT_MATRIX + pbio_busy_count_up(); + pbio_os_process_start(&boot_animation_process, boot_animation_process_boot_thread, (void *)false); + #endif +} + /** * The HMI is a loop running the following steps: * @@ -67,7 +173,9 @@ static pbio_error_t run_ui(pbio_os_state_t *state, pbio_os_timer_t *timer) { DEBUG_PRINT("Start HMI loop\n"); // Visually indicate current state on supported hubs. - pbsys_hub_light_matrix_update_program_slot(); + #if PBIO_CONFIG_LIGHT_MATRIX + light_matrix_show_idle_ui(100); + #endif // Initialize Bluetooth depending on current state. if (pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_LE)) { @@ -215,7 +323,10 @@ static pbio_error_t run_ui(pbio_os_state_t *state, pbio_os_timer_t *timer) { pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); // Start run animations - pbsys_hub_light_matrix_handle_user_program_start(); + #if PBIO_CONFIG_LIGHT_MATRIX + light_matrix_start_run_animation(); + #endif + #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 diff --git a/lib/pbio/sys/light_matrix.c b/lib/pbio/sys/light_matrix.c deleted file mode 100644 index 507f3ba38..000000000 --- a/lib/pbio/sys/light_matrix.c +++ /dev/null @@ -1,136 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2020 The Pybricks Authors - -// OS-level hub built-in light matrix management. - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "hmi.h" - -#if PBSYS_CONFIG_HUB_LIGHT_MATRIX - -// revisit, we will drop this in the next commit -pbio_light_matrix_t *pbsys_hub_light_matrix; - -/** - * Displays the idle UI. Has a square stop sign and selected slot on bottom row. - * - * @param brightness Brightness (0--100%). - */ -static void pbsys_hub_light_matrix_show_idle_ui(uint8_t brightness) { - for (uint8_t r = 0; r < pbsys_hub_light_matrix->size; r++) { - 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_status_get_selected_slot()); - #endif - pbio_light_matrix_set_pixel(pbsys_hub_light_matrix, r, c, is_on ? brightness : 0, false); - } - } -} - -void pbsys_hub_light_matrix_update_program_slot(void) { - pbsys_hub_light_matrix_show_idle_ui(100); -} - -// Animation frame for on/off animation. -static uint32_t pbsys_hub_light_matrix_user_power_animation_next(pbio_light_animation_t *animation) { - - // Start at 2% and increment up to 100% in 7 steps. - static uint8_t brightness = 2; - static int8_t increment = 14; - - // Show the stop sign fading in/out. - brightness += increment; - pbsys_hub_light_matrix_show_idle_ui(brightness); - - // Stop at 100% and re-initialize so we can use this again for shutdown. - if (brightness == 100 || brightness == 0) { - pbio_light_animation_stop(&pbsys_hub_light_matrix->animation); - brightness = 96; - increment = -8; - } - return 40; -} - -/** - * Starts the power up and down animation. The first call makes it fade in the - * stop sign. All subsequent calls are fade out. - */ -static void pbsys_hub_light_matrix_start_power_animation(void) { - pbio_light_animation_init(&pbsys_hub_light_matrix->animation, pbsys_hub_light_matrix_user_power_animation_next); - pbio_light_animation_start(&pbsys_hub_light_matrix->animation); -} - -void pbsys_hub_light_matrix_init(void) { - pbio_light_matrix_get_dev(0, 5, &pbsys_hub_light_matrix); - pbsys_hub_light_matrix_start_power_animation(); -} - -void pbsys_hub_light_matrix_deinit(void) { - // Starting it again continues it in reverse. - pbsys_hub_light_matrix_start_power_animation(); -} - -/** - * Clears the pixels needed for the run animation - */ -static void pbsys_hub_light_matrix_user_program_animation_clear(void) { - for (uint8_t r = 0; r < 3; r++) { - for (uint8_t c = 1; c < 4; c++) { - pbio_light_matrix_set_pixel(pbsys_hub_light_matrix, r, c, 0, true); - } - } -} - -// Animation frame for program running animation. -static uint32_t pbsys_hub_light_matrix_user_program_animation_next(pbio_light_animation_t *animation) { - // The indexes of pixels to light up - static const uint8_t indexes[] = { 1, 2, 3, 8, 13, 12, 11, 6 }; - - // Each pixel has a repeating brightness pattern of the form /\_ through - // which we can cycle in 256 steps. - static uint8_t cycle = 0; - - for (size_t i = 0; i < PBIO_ARRAY_SIZE(indexes); i++) { - // The pixels are spread equally across the pattern. - uint8_t offset = cycle + i * (UINT8_MAX / PBIO_ARRAY_SIZE(indexes)); - uint8_t brightness = offset > 200 ? 0 : (offset < 100 ? offset : 200 - offset); - - // Set the brightness for this pixel - pbio_light_matrix_set_pixel(pbsys_hub_light_matrix, indexes[i] / 5, indexes[i] % 5, brightness, false); - } - // This increment controls the speed of the pattern - cycle += 9; - - return 40; -} - -/** - * Updates light matrix behavior when program is started. - */ -void pbsys_hub_light_matrix_handle_user_program_start(void) { - - // The user animation updates only a subset of pixels to save time, - // so the rest must be cleared before it starts. - pbsys_hub_light_matrix_user_program_animation_clear(); - pbio_light_animation_stop_all(); - pbio_light_animation_init(&pbsys_hub_light_matrix->animation, pbsys_hub_light_matrix_user_program_animation_next); - pbio_light_animation_start(&pbsys_hub_light_matrix->animation); -} - -#endif // PBSYS_CONFIG_HUB_LIGHT_MATRIX diff --git a/lib/pbio/sys/light_matrix.h b/lib/pbio/sys/light_matrix.h deleted file mode 100644 index 679ae11d7..000000000 --- a/lib/pbio/sys/light_matrix.h +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2020 The Pybricks Authors - -#ifndef _PBSYS_SYS_LIGHT_MATRIX_H_ -#define _PBSYS_SYS_LIGHT_MATRIX_H_ - -#include - -#include - -#if PBSYS_CONFIG_HUB_LIGHT_MATRIX -void pbsys_hub_light_matrix_init(void); -void pbsys_hub_light_matrix_deinit(void); -void pbsys_hub_light_matrix_handle_user_program_start(void); -void pbsys_hub_light_matrix_update_program_slot(void); -#else -#define pbsys_hub_light_matrix_init() -#define pbsys_hub_light_matrix_deinit() -#define pbsys_hub_light_matrix_handle_user_program_start() -#define pbsys_hub_light_matrix_update_program_slot() -#endif - -#endif // _PBSYS_SYS_LIGHT_MATRIX_H_ diff --git a/lib/pbio/sys/main.c b/lib/pbio/sys/main.c index e3dc4c49e..fb7fe1701 100644 --- a/lib/pbio/sys/main.c +++ b/lib/pbio/sys/main.c @@ -19,7 +19,6 @@ #include #include "hmi.h" -#include "light_matrix.h" #include "program_stop.h" #include "storage.h" #include