99
1010#if PBSYS_CONFIG_HMI_VIRTUAL
1111
12+ #include <pbdrv/display.h>
13+
1214#include <pbio/button.h>
15+ #include <pbio/image.h>
1316#include <pbio/os.h>
1417#include <pbsys/command.h>
18+ #include <pbsys/host.h>
1519#include <pbsys/main.h>
1620#include <pbsys/status.h>
21+
1722#include "storage.h"
23+ #include "hmi.h"
1824
1925#include <errno.h>
2026#include <signal.h>
2632#include <string.h>
2733#include <sys/stat.h>
2834
35+ #define DEBUG 0
36+
37+ #if DEBUG
38+ #include <pbio/debug.h>
39+ #define DEBUG_PRINT pbio_debug
40+ #else
41+ #define DEBUG_PRINT (...)
42+ #endif
43+
2944static uint32_t pbsys_hmi_num_programs ;
3045
3146static void load_program (const char * path ) {
@@ -55,6 +70,27 @@ static void load_program(const char *path) {
5570 pbsys_storage_set_program_size (program_size );
5671}
5772
73+ /**
74+ * Runs one program (or REPL if nothing given) and shuts down the hub.
75+ */
76+ static pbio_error_t pbsys_hmi_await_program_selection_one_off (void ) {
77+
78+ pbio_os_run_processes_and_wait_for_event ();
79+
80+ // With this HMI, we run a script once and then exit.
81+ static bool ran_once = false;
82+ if (ran_once ) {
83+ return PBIO_ERROR_CANCELED ;
84+ }
85+ ran_once = true;
86+
87+ pbio_pybricks_user_program_id_t id = pbsys_hmi_num_programs ?
88+ PBIO_PYBRICKS_USER_PROGRAM_ID_FIRST_SLOT :
89+ PBIO_PYBRICKS_USER_PROGRAM_ID_REPL ;
90+
91+ return pbsys_main_program_request_start (id , PBSYS_MAIN_PROGRAM_START_REQUEST_TYPE_BOOT );
92+ }
93+
5894void pbsys_hmi_init (void ) {
5995 extern int main_argc ;
6096 extern char * * main_argv ;
@@ -79,40 +115,133 @@ void pbsys_hmi_init(void) {
79115 }
80116}
81117
118+ static void hmi_lcd_grid_show_pixel (uint8_t row , uint8_t col , bool on ) {
119+ pbio_image_t * display = pbdrv_display_get_image ();
120+ uint8_t value = on ? pbdrv_display_get_max_value (): 0 ;
121+ const uint32_t size = PBDRV_CONFIG_DISPLAY_NUM_ROWS / PBSYS_CONFIG_HMI_NUM_SLOTS ;
122+ const uint32_t width = size * 4 / 5 ;
123+ const uint32_t offset = (PBDRV_CONFIG_DISPLAY_NUM_COLS - (PBSYS_CONFIG_HMI_NUM_SLOTS * size )) / 2 ;
124+ pbio_image_fill_rect (display , col * size + offset , row * size , width , width , value );
125+ }
126+
82127void pbsys_hmi_deinit (void ) {
128+ pbio_image_t * display = pbdrv_display_get_image ();
129+ pbio_image_fill (display , 0 );
130+ pbdrv_display_update ();
83131}
84132
133+ static pbio_error_t run_ui (pbio_os_state_t * state , pbio_os_timer_t * timer ) {
85134
86- /**
87- * Runs one program (or REPL if nothing given) and shuts down the hub.
88- */
89- static pbio_error_t pbsys_hmi_await_program_selection_one_off (void ) {
135+ PBIO_OS_ASYNC_BEGIN (state );
90136
91- pbio_os_run_processes_and_wait_for_event ();
137+ for (;;) {
92138
93- // With this HMI, we run a script once and then exit.
94- static bool ran_once = false;
95- if (ran_once ) {
96- return PBIO_ERROR_CANCELED ;
139+ DEBUG_PRINT ("Start HMI loop\n" );
140+
141+ // Visually indicate current slot.
142+ #if PBSYS_CONFIG_HMI_NUM_SLOTS
143+ uint8_t selected_slot = pbsys_status_get_selected_slot ();
144+ for (uint8_t c = 0 ; c < PBSYS_CONFIG_HMI_NUM_SLOTS ; c ++ ) {
145+ hmi_lcd_grid_show_pixel (4 , c , c == selected_slot );
146+ }
147+ #endif
148+
149+ pbdrv_display_update ();
150+
151+ // Buttons could be pressed at the end of the user program, so wait for
152+ // a release and then a new press, or until we have to exit early.
153+ DEBUG_PRINT ("Waiting for initial button release.\n" );
154+ PBIO_OS_AWAIT_WHILE (state , ({
155+ if (pbsys_status_test (PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST )) {
156+ return PBIO_ERROR_CANCELED ;
157+ }
158+ pbdrv_button_get_pressed ();
159+ }));
160+
161+ DEBUG_PRINT ("Start waiting for input.\n" );
162+ // Wait on a button, external program start, or connection change. Stop
163+ // waiting on timeout or shutdown.
164+ PBIO_OS_AWAIT_UNTIL (state , ({
165+ // Shutdown may be requested by a background process such as critical
166+ // battery or holding the power button.
167+ if (pbsys_status_test (PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST )) {
168+ return PBIO_ERROR_CANCELED ;
169+ }
170+
171+ // Exit on timeout except while connected to host.
172+ if (pbsys_host_is_connected ()) {
173+ pbio_os_timer_reset (timer );
174+ } else if (pbio_os_timer_is_expired (timer )) {
175+ return PBIO_ERROR_TIMEDOUT ;
176+ }
177+
178+ // Wait for button press, external program start, or connection change.
179+ pbdrv_button_get_pressed () || pbsys_main_program_start_is_requested ();
180+ }));
181+
182+ // External progran request takes precedence over buttons.
183+ if (pbsys_main_program_start_is_requested ()) {
184+ DEBUG_PRINT ("Start program from Pybricks Code.\n" );
185+ break ;
186+ }
187+
188+ // On right, increment slot when possible, then start waiting on new inputs.
189+ if (pbdrv_button_get_pressed () & PBIO_BUTTON_RIGHT ) {
190+ pbsys_status_increment_selected_slot (true);
191+ continue ;
192+ }
193+ // On left, decrement slot when possible, then start waiting on new inputs.
194+ if (pbdrv_button_get_pressed () & PBIO_BUTTON_LEFT ) {
195+ pbsys_status_increment_selected_slot (false);
196+ continue ;
197+ }
198+
199+ // On center, attempt to start program.
200+ if (pbdrv_button_get_pressed () & PBIO_BUTTON_CENTER ) {
201+ pbio_error_t err = pbsys_main_program_request_start (pbsys_status_get_selected_slot (), PBSYS_MAIN_PROGRAM_START_REQUEST_TYPE_HUB_UI );
202+ if (err == PBIO_SUCCESS ) {
203+ DEBUG_PRINT ("Start program with button\n" );
204+ break ;
205+ } else {
206+ DEBUG_PRINT ("Requested program not available.\n" );
207+ // We can run an animation here to indicate that the program is not available.
208+ }
209+ }
210+
211+ DEBUG_PRINT ("No valid action selected, start over.\n" );
97212 }
98- ran_once = true;
99213
100- pbio_pybricks_user_program_id_t id = pbsys_hmi_num_programs ?
101- PBIO_PYBRICKS_USER_PROGRAM_ID_FIRST_SLOT :
102- PBIO_PYBRICKS_USER_PROGRAM_ID_REPL ;
214+ // Wait for all buttons to be released so the user doesn't accidentally
215+ // push their robot off course.
216+ DEBUG_PRINT ("Waiting for final button release.\n" );
217+ PBIO_OS_AWAIT_WHILE (state , ({
218+ if (pbsys_status_test (PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST )) {
219+ return PBIO_ERROR_CANCELED ;
220+ }
221+ pbdrv_button_get_pressed ();
222+ }));
103223
104- return pbsys_main_program_request_start ( id , PBSYS_MAIN_PROGRAM_START_REQUEST_TYPE_BOOT );
224+ PBIO_OS_ASYNC_END ( PBIO_SUCCESS );
105225}
106226
227+
107228/**
108229 * Interactive menu similar to real embedded hubs.
109230 */
110231static pbio_error_t pbsys_hmi_await_program_selection_interactive (void ) {
111232
112- pbio_os_run_processes_and_wait_for_event ();
233+ pbio_os_timer_t idle_timer ;
234+ pbio_os_timer_set (& idle_timer , PBSYS_CONFIG_HMI_IDLE_TIMEOUT_MS );
235+
236+ pbio_os_state_t state = 0 ;
113237
114- // TODO: Implement interactive menu.
115- return pbsys_main_program_request_start (PBIO_PYBRICKS_USER_PROGRAM_ID_FIRST_SLOT , PBSYS_MAIN_PROGRAM_START_REQUEST_TYPE_BOOT );
238+ pbio_error_t err ;
239+ while ((err = run_ui (& state , & idle_timer )) == PBIO_ERROR_AGAIN ) {
240+ // run all processes and wait for next event.
241+ pbio_os_run_processes_and_wait_for_event ();
242+ }
243+ DEBUG_PRINT ("Finished program selection with status: %d\n" , err );
244+ return err ;
116245}
117246
118247/**
0 commit comments