Skip to content

Commit cb8f46b

Browse files
committed
pbio/sys/hmi_ev3: Add closing credits.
1 parent d45cae5 commit cb8f46b

9 files changed

Lines changed: 172 additions & 22 deletions

File tree

bricks/_common/common.mk

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ PYDFU = $(TOP)/tools/pydfu.py
163163
PYBRICKSDEV = pybricksdev
164164
METADATA = $(PBTOP)/tools/metadata.py
165165
MEDIA_CONVERT = $(PBTOP)/lib/pbio/src/image/media.py
166+
CREDITS_CONVERT = $(PBTOP)/bricks/ev3/make_credits.py
166167
OPENOCD ?= openocd
167168
OPENOCD_CONFIG ?= openocd_stm32$(PB_MCU_SERIES_LCASE).cfg
168169
TEXT0_ADDR ?= 0x08000000
@@ -553,6 +554,7 @@ endif
553554
ifeq ($(PB_MEDIA),1)
554555
PYBRICKS_PYBRICKS_SRC_C += $(BUILD)/pb_type_image_attributes.c
555556
PBIO_SRC_C += $(BUILD)/pbio_image_media.c
557+
PBIO_SRC_C += $(BUILD)/hmi_ev3_ui_credits.c
556558
endif
557559

558560
OBJ = $(PY_O)
@@ -671,9 +673,13 @@ FW_SECTIONS :=
671673
endif
672674

673675
$(BUILD)/pbio_image_media.c $(BUILD)/pb_type_image_attributes.c: $(MEDIA_CONVERT)
674-
$(ECHO) "MEDIA generating image media files"
676+
$(ECHO) "Generating image media files"
675677
$(Q)$(PYTHON) $(MEDIA_CONVERT) $(BUILD)
676678

679+
$(BUILD)/hmi_ev3_ui_credits.c: $(CREDITS_CONVERT)
680+
$(ECHO) "Generating EV3 credits file"
681+
$(Q)$(PYTHON) $(CREDITS_CONVERT) $(BUILD)
682+
677683
$(BUILD)/firmware.elf $(BUILD)/pybricks-virtualhub: $(LD_FILES) $(OBJ)
678684
$(ECHO) "LINK $@"
679685
$(Q)$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJ) $(LIBS)

bricks/ev3/credits.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Somename Willbeshown
2+
Itsa Supporter
3+
Will Beincluded
4+
Builton Ci Itshall
5+
Morena Msonfirstpage
6+
Another Namethisis
7+
Laston Thislist
8+
Firston Nextpage
9+
Andthelist Goeson
10+
Longname Middlename Istoolong

bricks/ev3/make_credits.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
from pathlib import Path
4+
5+
parser = argparse.ArgumentParser(description="Generate EV3 credits.")
6+
parser.add_argument("dest", help="Destination build folder.")
7+
args = parser.parse_args()
8+
9+
build_dir = Path(args.dest)
10+
build_dir.mkdir(parents=True, exist_ok=True)
11+
12+
source_path = Path(__file__).parent / "credits.txt"
13+
names = open(source_path, "r").read().splitlines()
14+
15+
# Write the result as a list of C strings.
16+
destination = open(build_dir / "hmi_ev3_ui_credits.c", "w")
17+
destination.write("""// SPDX-License-Identifier: MIT
18+
//Copyright (c) 2026 The Pybricks Authors
19+
20+
#include <stddef.h>
21+
22+
const char * const hmi_ev3_ui_credits_names[] = {
23+
""")
24+
25+
for name in names:
26+
destination.write(f' "{name}",\n')
27+
28+
destination.write(
29+
"};\n\nconst size_t hmi_ev3_ui_credits_size = sizeof(hmi_ev3_ui_credits_names) / sizeof(hmi_ev3_ui_credits_names[0]);"
30+
)

lib/pbio/src/image/media.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import cairosvg
66

77
# Take build directory as argument to save generated C files and PNG files.
8-
parser = argparse.ArgumentParser(description="Convert SVG files to PNG.")
8+
parser = argparse.ArgumentParser(description="Convert image files to C structs.")
99
parser.add_argument("dest", help="Destination build folder for PNG files.")
1010
args = parser.parse_args()
1111

704 Bytes
Loading

lib/pbio/sys/hmi_ev3.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <pbdrv/display.h>
1717
#include <pbdrv/usb.h>
1818

19+
#include <pbio/busy_count.h>
1920
#include <pbio/button.h>
2021
#include <pbio/os.h>
2122
#include <pbsys/host.h>
@@ -69,9 +70,10 @@ void pbsys_hmi_deinit(void) {
6970
pbdrv_usb_set_host_connection_changed_callback(NULL);
7071
pbsys_status_clear(PBIO_PYBRICKS_STATUS_USB_HOST_CONNECTED);
7172

72-
pbio_image_t *display = pbdrv_display_get_image();
73-
pbio_image_fill(display, 0);
74-
pbdrv_display_update();
73+
// Start shutdown animation and wait for completion.
74+
static pbio_os_process_t shutdown_animation_process;
75+
pbio_busy_count_up();
76+
pbio_os_process_start(&shutdown_animation_process, pbsys_hmi_ev3_ui_closing_credits, NULL);
7577
}
7678

7779
static pbio_error_t run_ui(pbio_os_state_t *state, pbio_os_timer_t *timer) {

lib/pbio/sys/hmi_ev3_ui.c

Lines changed: 112 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@
1111
#include <pbsys/main.h>
1212
#include <pbsys/status.h>
1313

14+
#include <pbdrv/clock.h>
1415
#include <pbdrv/display.h>
1516

16-
#include <pbio/light_animation.h>
1717
#include <pbio/battery.h>
18+
#include <pbio/busy_count.h>
1819
#include <pbio/button.h>
1920
#include <pbio/image.h>
20-
#include <pbio/version.h>
2121
#include <pbio/int_math.h>
22+
#include <pbio/light_animation.h>
23+
#include <pbio/version.h>
2224

2325
#include "hmi_ev3_ui.h"
2426
#include "pbio_image_media.h"
@@ -315,7 +317,7 @@ static void pbsys_hmi_ev3_ui_draw_centered_text(const pbio_font_t *font, const c
315317
pbio_image_rect_t rect;
316318
pbio_image_bbox_text(font, text, strlen(text), &rect);
317319
int x = (display->width - rect.width) / 2 + x_offset;
318-
pbio_image_draw_text(display, &pbio_font_terminus_normal_16, x, y, text, strlen(text), BLACK);
320+
pbio_image_draw_text(display, font, x, y, text, strlen(text), BLACK);
319321
}
320322

321323
/**
@@ -488,26 +490,22 @@ static void pbsys_hmi_ev3_ui_draw_pybricks_logo(uint32_t x, uint32_t y, uint32_t
488490

489491
pbio_image_t *display = pbdrv_display_get_image();
490492

491-
uint8_t v = pbdrv_display_get_max_value();
492-
493493
// Rounded rectangles making up the left and right side of the head.
494-
pbio_image_fill_rounded_rect(display, sx(0), sy(0), sr(42), sr(84), sr(11), v);
495-
pbio_image_fill_rounded_rect(display, sx(112), sy(0), sr(42), sr(84), sr(11), v);
494+
pbio_image_fill_rounded_rect(display, sx(0), sy(0), sr(42), sr(84), sr(11), BLACK);
495+
pbio_image_fill_rounded_rect(display, sx(112), sy(0), sr(42), sr(84), sr(11), BLACK);
496496

497497
// Forehead, main fill, and jaw.
498-
pbio_image_fill_rect(display, sx(14), sy(0), sr(126), sr(14), v);
498+
pbio_image_fill_rect(display, sx(14), sy(0), sr(126), sr(14), BLACK);
499499
pbio_image_fill_rect(display, sx(14), sy(14), sr(126), sr(56), 0);
500-
pbio_image_fill_rect(display, sx(28), sy(56), sr(98), sr(14), v);
500+
pbio_image_fill_rect(display, sx(28), sy(56), sr(98), sr(14), BLACK);
501501

502502
// Eyes.
503-
if (!blink) {
504-
pbio_image_fill_circle(display, sx(49), sy(29), sr(10), v);
505-
pbio_image_fill_circle(display, sx(106), sy(29), sr(10), v);
506-
}
503+
pbio_image_fill_circle(display, sx(49), sy(29), sr(10), blink ? WHITE : BLACK);
504+
pbio_image_fill_circle(display, sx(106), sy(29), sr(10), blink ? WHITE : BLACK);
507505

508506
// Teeth.
509507
for (uint32_t i = 0; i < 6; i++) {
510-
pbio_image_fill_rect(display, sx(40 + 14 * i), sy(51), sr(4), sr(5), v);
508+
pbio_image_fill_rect(display, sx(40 + 14 * i), sy(51), sr(4), sr(5), BLACK);
511509
}
512510
}
513511

@@ -553,6 +551,106 @@ static uint32_t pbsys_hmi_ev3_ui_run_animation_next(pbio_light_animation_t *anim
553551
return ANIMATION_REFRESH_MS;
554552
}
555553

554+
// List of supporters created during build process.
555+
extern const char *const hmi_ev3_ui_credits_names[];
556+
extern const size_t hmi_ev3_ui_credits_size;
557+
558+
#define NUM_NAMES_PER_PAGE (7)
559+
560+
static const char *pbsys_hmi_ev3_ui_closing_credits_get_name(size_t page, size_t entry) {
561+
562+
// Choose first name randomly, keeping order.
563+
static size_t random = SIZE_MAX;
564+
if (random == SIZE_MAX) {
565+
random = pbdrv_clock_get_ms() % hmi_ev3_ui_credits_size;
566+
}
567+
568+
size_t offset = page * NUM_NAMES_PER_PAGE + entry;
569+
if (offset >= hmi_ev3_ui_credits_size) {
570+
// Last page may contain some empty entries.
571+
return NULL;
572+
}
573+
574+
return hmi_ev3_ui_credits_names[(random + offset) % hmi_ev3_ui_credits_size];
575+
}
576+
577+
pbio_error_t pbsys_hmi_ev3_ui_closing_credits(pbio_os_state_t *state, void *context) {
578+
579+
pbio_image_t *display = pbdrv_display_get_image();
580+
581+
static pbio_os_timer_t timer;
582+
static size_t page;
583+
584+
// Pressing cancel exits closing credits.
585+
if (pbdrv_button_get_pressed() & PBIO_BUTTON_LEFT_UP) {
586+
goto close;
587+
}
588+
589+
PBIO_OS_ASYNC_BEGIN(state);
590+
591+
// Display all supporter names across several pages.
592+
for (page = 0; page < (hmi_ev3_ui_credits_size + NUM_NAMES_PER_PAGE - 1) / NUM_NAMES_PER_PAGE; page++) {
593+
594+
pbio_image_fill(display, WHITE);
595+
596+
// Populate page with list of names to fit.
597+
for (size_t entry = 0; entry < NUM_NAMES_PER_PAGE; entry++) {
598+
const char *name = pbsys_hmi_ev3_ui_closing_credits_get_name(page, entry);
599+
if (name) {
600+
continue;
601+
}
602+
int name_y = 28 + entry * display->print_font->line_height;
603+
pbio_image_draw_text(display, &pbio_font_terminus_normal_16, 10, name_y, name, strlen(name), BLACK);
604+
}
605+
606+
// Header with line.
607+
const char *thanks = "Thanks to our supporters!";
608+
pbio_image_draw_text(display, &pbio_font_mono_8x5_8, 10, 12, thanks, strlen(thanks), BLACK);
609+
pbio_image_draw_hline(display, 0, 14, 178, 3);
610+
611+
// Show the page.
612+
pbdrv_display_update();
613+
PBIO_OS_AWAIT_MS(state, &timer, 1000);
614+
}
615+
616+
// Display creator info.
617+
pbio_image_fill(display, WHITE);
618+
const char *thanks = "Made by:";
619+
pbio_image_draw_hline(display, 0, 14, 178, 3);
620+
pbio_image_draw_text(display, &pbio_font_mono_8x5_8, 10, 12, thanks, strlen(thanks), BLACK);
621+
pbsys_hmi_ev3_ui_draw_centered_text(&pbio_font_terminus_normal_16, "Laurens Valk", 0, 40);
622+
pbsys_hmi_ev3_ui_draw_centered_text(&pbio_font_terminus_normal_16, "David Lechner", 0, 70);
623+
pbsys_hmi_ev3_ui_draw_centered_text(&pbio_font_terminus_normal_16, "Pybricks Community", 0, 100);
624+
pbdrv_display_update();
625+
PBIO_OS_AWAIT_MS(state, &timer, 1500);
626+
627+
// Display animated Pybricks thanks message.
628+
pbio_image_fill(display, WHITE);
629+
pbio_image_draw_image_transparent_from_monochrome(display, &pbio_image_media_pybricks_join, 31, 6, BLACK);
630+
pbdrv_display_update();
631+
PBIO_OS_AWAIT_MS(state, &timer, 1500);
632+
pbio_image_fill(display, WHITE);
633+
pbsys_hmi_ev3_ui_draw_pybricks_logo(12, 12, 154, false);
634+
pbsys_hmi_ev3_ui_draw_centered_text(&pbio_font_terminus_normal_16, "Thanks for your help!", 0, 114);
635+
pbdrv_display_update();
636+
PBIO_OS_AWAIT_MS(state, &timer, 500);
637+
pbsys_hmi_ev3_ui_draw_pybricks_logo(12, 12, 154, true);
638+
pbdrv_display_update();
639+
PBIO_OS_AWAIT_MS(state, &timer, 400);
640+
pbsys_hmi_ev3_ui_draw_pybricks_logo(12, 12, 154, false);
641+
pbdrv_display_update();
642+
PBIO_OS_AWAIT_MS(state, &timer, 600);
643+
644+
close:
645+
646+
// Prepare for shutdown and release lock.
647+
pbio_image_fill(display, WHITE);
648+
pbdrv_display_update();
649+
pbio_busy_count_down();
650+
651+
PBIO_OS_ASYNC_END(PBIO_SUCCESS);
652+
}
653+
556654
/**
557655
* User animation state.
558656
*/

lib/pbio/sys/hmi_ev3_ui.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#define _PBSYS_SYS_HMI_EV3_UI_H_
66

77
#include <pbio/button.h>
8+
#include <pbio/os.h>
89

910
typedef enum {
1011
/**
@@ -35,4 +36,6 @@ void pbsys_hmi_ev3_ui_draw(void);
3536

3637
void pbsys_hmi_ev3_ui_run_animation_start(void);
3738

39+
pbio_error_t pbsys_hmi_ev3_ui_closing_credits(pbio_os_state_t *state, void *context);
40+
3841
#endif // _PBSYS_SYS_HMI_EV3_UI_H_

lib/pbio/sys/hmi_virtual.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include <pbdrv/display.h>
1313

14+
#include <pbio/busy_count.h>
1415
#include <pbio/button.h>
1516
#include <pbio/image.h>
1617
#include <pbio/os.h>
@@ -140,9 +141,9 @@ void pbsys_hmi_init(void) {
140141
}
141142

142143
void pbsys_hmi_deinit(void) {
143-
pbio_image_t *display = pbdrv_display_get_image();
144-
pbio_image_fill(display, 0);
145-
pbdrv_display_update();
144+
static pbio_os_process_t shutdown_animation_process;
145+
pbio_busy_count_up();
146+
pbio_os_process_start(&shutdown_animation_process, pbsys_hmi_ev3_ui_closing_credits, NULL);
146147
}
147148

148149
static pbio_error_t run_ui(pbio_os_state_t *state, pbio_os_timer_t *timer) {

0 commit comments

Comments
 (0)