97 lines
3.4 KiB
Python
97 lines
3.4 KiB
Python
# Standalone MicroPython Voice Animation: Wake Attention
|
|
# Exposes run(text="", frames=...) and runs automatically if executed directly.
|
|
import time
|
|
import math
|
|
|
|
try:
|
|
import board_config
|
|
display = getattr(board_config, 'display_instance', None)
|
|
except Exception:
|
|
board_config = None
|
|
display = None
|
|
|
|
def draw_center_text(d, msg, y, w, scale=1, c=1):
|
|
char_w = 8 * scale
|
|
x = max(0, (w - len(msg) * char_w) // 2)
|
|
if hasattr(d, 'text_large') and scale > 1:
|
|
try:
|
|
d.text_large(msg, x, y, scale, c)
|
|
return
|
|
except Exception:
|
|
pass
|
|
d.text(msg, x, y, c)
|
|
|
|
def run(text="", frames=100):
|
|
if not display:
|
|
print("board_config.display_instance is not available. Skipping animation.")
|
|
return
|
|
|
|
width = getattr(display, 'width', 400)
|
|
height = getattr(display, 'height', 300)
|
|
cx, cy = width // 2, height // 2
|
|
|
|
label = text if text else "HI, I'M LISTENING"
|
|
frame = 0
|
|
|
|
try:
|
|
while True:
|
|
if frames is not None and frames > 0 and frame >= frames:
|
|
break
|
|
|
|
display.clear(0)
|
|
|
|
# Left and Right eye center X coordinates
|
|
left_cx = cx - 55
|
|
right_cx = cx + 55
|
|
eye_y = cy - 10
|
|
|
|
# State cycle to control pupil gaze offset (look center, look left, look right)
|
|
gaze_state = (frame // 16) % 4
|
|
gaze_offset = 0
|
|
if gaze_state == 1:
|
|
gaze_offset = -12 # Look left
|
|
elif gaze_state == 3:
|
|
gaze_offset = 12 # Look right
|
|
|
|
# Blink cycle: close eyes for a few frames periodically
|
|
is_blinking = (frame % 22) in (0, 1, 2)
|
|
|
|
if is_blinking:
|
|
# Closed eyes: draw simple horizontal lines across the eye spaces
|
|
display.line(left_cx - 28, eye_y, left_cx + 28, eye_y, 1)
|
|
display.line(left_cx - 28, eye_y + 1, left_cx + 28, eye_y + 1, 1) # thicker
|
|
display.line(right_cx - 28, eye_y, right_cx + 28, eye_y, 1)
|
|
display.line(right_cx - 28, eye_y + 1, right_cx + 28, eye_y + 1, 1) # thicker
|
|
else:
|
|
# Open eyes: draw outer rounded rectangular bounding frames
|
|
# Left eye border
|
|
display.rect(left_cx - 32, eye_y - 22, 64, 44, 1)
|
|
display.rect(left_cx - 31, eye_y - 21, 62, 42, 1)
|
|
# Right eye border
|
|
display.rect(right_cx - 32, eye_y - 22, 64, 44, 1)
|
|
display.rect(right_cx - 31, eye_y - 21, 62, 42, 1)
|
|
|
|
# Draw pupils inside, shifted by the gaze offset
|
|
display.fill_rect(left_cx - 10 + gaze_offset, eye_y - 10, 20, 20, 1)
|
|
display.fill_rect(right_cx - 10 + gaze_offset, eye_y - 10, 20, 20, 1)
|
|
|
|
# Draw small cute highlights inside pupils
|
|
display.fill_rect(left_cx - 6 + gaze_offset, eye_y - 6, 6, 6, 0)
|
|
display.fill_rect(right_cx - 6 + gaze_offset, eye_y - 6, 6, 6, 0)
|
|
|
|
# Draw centered text label at the bottom
|
|
draw_center_text(display, label, height - 30, width, scale=1, c=1)
|
|
|
|
display.show()
|
|
frame += 1
|
|
|
|
try:
|
|
time.sleep_ms(60)
|
|
except AttributeError:
|
|
time.sleep(0.06)
|
|
except KeyboardInterrupt:
|
|
print("Animation interrupted by user.")
|
|
|
|
if __name__ in ('__main__', '__name__'):
|
|
run()
|