Skip to content

Commit 9c3c475

Browse files
committed
pbio/sys/hmi_virtual: Copy slot UI as starting point.
Get basic start/stop/slot working with virtual buttons.
1 parent 0d63b9f commit 9c3c475

1 file changed

Lines changed: 146 additions & 17 deletions

File tree

lib/pbio/sys/hmi_virtual.c

Lines changed: 146 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@
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>
@@ -26,6 +32,15 @@
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+
2944
static uint32_t pbsys_hmi_num_programs;
3045

3146
static 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+
5894
void 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+
82127
void 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
*/
110231
static 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

Comments
 (0)