Skip to content

Commit 8a53118

Browse files
committed
pbio/drv/display_virtual: Animate state.
1 parent 762d58f commit 8a53118

File tree

2 files changed

+78
-40
lines changed

2 files changed

+78
-40
lines changed

lib/pbio/drv/display/display_virtual.c

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@
2929
static uint8_t pbdrv_display_user_frame[PBDRV_CONFIG_DISPLAY_NUM_ROWS][PBDRV_CONFIG_DISPLAY_NUM_COLS] __attribute__((section(".noinit"), used));
3030

3131
/**
32-
* "Hardware" buffer of the virtual display.
32+
* Image corresponding to the display.
3333
*/
34-
static uint8_t pbdrv_display_hardware_frame[PBDRV_CONFIG_DISPLAY_NUM_ROWS][PBDRV_CONFIG_DISPLAY_NUM_COLS] __attribute__((section(".noinit"), used));
34+
static pbio_image_t display_image;
3535

3636
/**
3737
* Flag to indicate that the user frame has been updated and needs to be
@@ -57,17 +57,13 @@ static pbio_error_t pbdrv_display_virtual_process_thread(pbio_os_state_t *state,
5757
for (;;) {
5858
PBIO_OS_AWAIT_UNTIL(state, pbdrv_display_user_frame_update_requested);
5959
pbdrv_display_user_frame_update_requested = false;
60-
memcpy(pbdrv_display_hardware_frame, pbdrv_display_user_frame, sizeof(pbdrv_display_hardware_frame));
60+
extern void virtual_hub_socket_send(const uint8_t *data, uint32_t size);
61+
virtual_hub_socket_send(display_image.pixels, sizeof(pbdrv_display_user_frame));
6162
}
6263

6364
PBIO_OS_ASYNC_END(PBIO_SUCCESS);
6465
}
6566

66-
/**
67-
* Image corresponding to the display.
68-
*/
69-
static pbio_image_t display_image;
70-
7167
/**
7268
* Initialize the display driver.
7369
*/

lib/pbio/platform/virtual_hub/animation.py

Lines changed: 74 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,23 @@
66
from pathlib import Path
77
import struct
88

9-
9+
IMG_PATH = Path("lib/pbio/test/animator/img")
1010
HOST = "127.0.0.1"
1111
PORT = 5002
1212
SCREEN_WIDTH = 1400
1313
SCREEN_HEIGHT = 1000
1414
FPS = 25
1515

16+
# Virtual display settings
17+
DISPLAY_WIDTH = 178
18+
DISPLAY_HEIGHT = 128
19+
DISPLAY_SCALE = 2 # Scale factor for display on screen
20+
DISPLAY_POS = (450, 50) # Position on main screen
21+
1622
PBIO_PYBRICKS_EVENT_STATUS_REPORT = 0
1723
PBIO_PYBRICKS_EVENT_WRITE_STDOUT = 1
1824
PBIO_PYBRICKS_EVENT_WRITE_APP_DATA = 2
19-
PBIO_PYBRICKS_EVENT_WRITE_PORT_VIEW = 3
25+
PBIO_PYBRICKS_EVENT_WRITE_TELEMETRY = 3
2026

2127
# Incoming events (stdout, status, app data, port view)
2228
data_queue = queue.Queue()
@@ -29,53 +35,63 @@ def socket_listener_thread():
2935
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
3036
sock.bind((HOST, PORT))
3137
print(f"Listening on {HOST}:{PORT}")
32-
3338
while True:
34-
data, _ = sock.recvfrom(1024)
39+
data, _ = sock.recvfrom(DISPLAY_WIDTH * DISPLAY_HEIGHT)
3540
data_queue.put(data)
3641

3742

38-
def blit_rotate_at_center(screen, image, position, angle):
39-
"""Rotate image around a specific center point"""
40-
rotated_image = pygame.transform.rotate(image, angle)
41-
rotated_rect = rotated_image.get_rect(center=position)
42-
screen.blit(rotated_image, rotated_rect)
43-
43+
# Hub state
44+
angles = [0] * 6
45+
virtual_display = pygame.Surface((DISPLAY_WIDTH, DISPLAY_HEIGHT))
46+
display_color = [0x90, 0xC5, 0xAD]
47+
virtual_display.fill(display_color)
4448

45-
# Define the base path for images
46-
img_path = Path("lib/pbio/test/animator/img")
4749

50+
def update_display(data):
51+
virtual_display.fill(display_color)
52+
for y in range(DISPLAY_HEIGHT):
53+
for x in range(DISPLAY_WIDTH):
54+
value = data[y * DISPLAY_WIDTH + x]
55+
if value:
56+
brightness = (100, 75, 25, 0)[value]
57+
color = [c * brightness // 100 for c in display_color]
58+
virtual_display.set_at((x, y), color)
4859

49-
def load_and_scale_image(filename, scale=0.25):
50-
"""Load an image and scale it by the given scale factor."""
51-
image = pygame.image.load(img_path / filename)
52-
original_size = image.get_size()
53-
scaled_size = (int(original_size[0] * scale), int(original_size[1] * scale))
54-
return pygame.transform.smoothscale(image, scaled_size)
5560

56-
57-
# Hub state
58-
angles = [0] * 6
61+
def process_telemetry(payload):
62+
# Only supports motors for now.
63+
if len(payload) == 6:
64+
type_id, index, angle = struct.unpack("<bbi", payload[0:6])
65+
if type_id == 96:
66+
angles[index] = angle
5967

6068

6169
def update_state():
6270
while not data_queue.empty():
6371
data = data_queue.get_nowait()
72+
73+
# Revisit: we should use the telemetry protocol for the display, but
74+
# this is not hooked up yet. For now, just assume that a huge payload
75+
# is a display buffer.
76+
if len(data) > 6000:
77+
update_display(data)
78+
continue
79+
80+
# Expecting a notification event code with payload
6481
if not isinstance(data, bytes) or len(data) < 2:
6582
continue
6683
event = data[0]
6784
payload = data[1:]
6885

86+
# Stdout goes to animation terminal.
6987
if event == PBIO_PYBRICKS_EVENT_WRITE_STDOUT:
7088
try:
7189
print(payload.decode(), end="")
7290
except UnicodeDecodeError:
7391
print(payload)
74-
elif event == PBIO_PYBRICKS_EVENT_WRITE_PORT_VIEW:
75-
if len(payload) == 6:
76-
type_id, index, angle = struct.unpack("<bbi", payload[0:6])
77-
if type_id == 96:
78-
angles[index] = angle
92+
# Telemetry is used to update the visual state.
93+
elif event == PBIO_PYBRICKS_EVENT_WRITE_TELEMETRY:
94+
process_telemetry(payload)
7995

8096

8197
def main():
@@ -85,6 +101,17 @@ def main():
85101
pygame.display.set_caption("Virtual Hub Animator")
86102
clock = pygame.time.Clock()
87103

104+
def blit_rotate_at_center(image, position, angle):
105+
rotated_image = pygame.transform.rotate(image, angle)
106+
rotated_rect = rotated_image.get_rect(center=position)
107+
screen.blit(rotated_image, rotated_rect)
108+
109+
def load_and_scale_image(filename, scale=0.25):
110+
image = pygame.image.load(IMG_PATH / filename)
111+
original_size = image.get_size()
112+
scaled_size = (int(original_size[0] * scale), int(original_size[1] * scale))
113+
return pygame.transform.smoothscale(image, scaled_size)
114+
88115
# Load images
89116
hub = load_and_scale_image("main-model.png")
90117
wheel_left = load_and_scale_image("wheel-left.png")
@@ -94,6 +121,13 @@ def main():
94121
gear_drive = load_and_scale_image("gear-drive.png")
95122
gear_follow = load_and_scale_image("gear-follow.png")
96123

124+
display_border_rect = pygame.Rect(
125+
DISPLAY_POS[0] - 2,
126+
DISPLAY_POS[1] - 2,
127+
DISPLAY_WIDTH * DISPLAY_SCALE + 4,
128+
DISPLAY_HEIGHT * DISPLAY_SCALE + 4,
129+
)
130+
97131
# Start the socket listener thread
98132
threading.Thread(target=socket_listener_thread, daemon=True).start()
99133

@@ -110,13 +144,21 @@ def main():
110144
screen.fill((161, 168, 175))
111145

112146
# Draw and rotate components, with left wheel behind hub.
113-
blit_rotate_at_center(screen, wheel_left, (1242, 156), -angles[0])
147+
blit_rotate_at_center(wheel_left, (1242, 156), -angles[0])
114148
screen.blit(hub, (0, 0))
115-
blit_rotate_at_center(screen, wheel_right, (1067, 331), angles[1])
116-
blit_rotate_at_center(screen, shaft, (247, 712), angles[4])
117-
blit_rotate_at_center(screen, stall, (245, 189), angles[2])
118-
blit_rotate_at_center(screen, gear_drive, (943, 694), -angles[5])
119-
blit_rotate_at_center(screen, gear_follow, (1037, 694), angles[5] / 3)
149+
blit_rotate_at_center(wheel_right, (1067, 331), angles[1])
150+
blit_rotate_at_center(shaft, (247, 712), angles[4])
151+
blit_rotate_at_center(stall, (245, 189), angles[2])
152+
blit_rotate_at_center(gear_drive, (943, 694), -angles[5])
153+
blit_rotate_at_center(gear_follow, (1037, 694), angles[5] / 3)
154+
155+
# Show virtual display.
156+
pygame.draw.rect(screen, (50, 50, 50), display_border_rect)
157+
scaled_surface = pygame.transform.scale(
158+
virtual_display,
159+
(DISPLAY_WIDTH * DISPLAY_SCALE, DISPLAY_HEIGHT * DISPLAY_SCALE),
160+
)
161+
screen.blit(scaled_surface, DISPLAY_POS)
120162

121163
# Update display
122164
pygame.display.flip()

0 commit comments

Comments
 (0)