TTs whisper
This commit is contained in:
273
raspi/eyes.py
Normal file
273
raspi/eyes.py
Normal file
@@ -0,0 +1,273 @@
|
||||
"""
|
||||
eyes.py — Control de les matrius LED 8x8 RGB WS2811 (ulls del robot).
|
||||
Equivalent a eyes.cpp del codi Arduino/ESP32.
|
||||
|
||||
FastLED → pigpio waveforms (GPIO26, qualsevol pin).
|
||||
|
||||
REQUISIT: iniciar el dimoni pigpio amb resolució d'1µs:
|
||||
sudo pigpiod -s 1
|
||||
Si s'inicia sense -s 1 (defecte 5µs), els LEDs no funcionaran correctament.
|
||||
|
||||
Si en el futur es fa una modificació hardware (GPIO26 → GPIO18 o GPIO21),
|
||||
es pot substituir _send_ws2811() per rpi_ws281x sense canviar cap altra funció.
|
||||
"""
|
||||
|
||||
import time
|
||||
import threading
|
||||
import pigpio
|
||||
|
||||
from pins import LED_DATA
|
||||
|
||||
# ==================
|
||||
# Constants
|
||||
# ==================
|
||||
|
||||
ROW_NUM = 8
|
||||
COL_NUM = 8
|
||||
NUM_LEDS = ROW_NUM * COL_NUM * 2 # 128 LEDs (2 matrius 8x8)
|
||||
|
||||
MAX_BR = 170 # Brillantor màxima del parpelleig (0–255)
|
||||
MIN_BR = 80 # Brillantor mínima del parpelleig
|
||||
|
||||
# Colors predefinits (R, G, B)
|
||||
WHITE = (255, 255, 255)
|
||||
RED = (255, 0, 0)
|
||||
GREEN = ( 0, 255, 0)
|
||||
BLUE = ( 0, 0, 255)
|
||||
YELLOW = (255, 200, 0)
|
||||
ORANGE = (255, 80, 0)
|
||||
PURPLE = (180, 0, 255)
|
||||
CYAN = ( 0, 255, 255) # Color del mode gestos
|
||||
BLACK = ( 0, 0, 0)
|
||||
|
||||
# ==================
|
||||
# Formes dels ulls (índexs dels LEDs actius)
|
||||
# ==================
|
||||
|
||||
class EyeShape:
|
||||
"""Conjunt de LEDs que formen una expressió dels ulls."""
|
||||
def __init__(self, leds: list):
|
||||
self.leds = leds
|
||||
self.len = len(leds)
|
||||
|
||||
|
||||
EYES_OPEN = EyeShape([
|
||||
102, 89, 38, 25, 106, 101, 90, 85, 42, 37, 26, 21,
|
||||
107, 100, 91, 84, 43, 36, 27, 20, 108, 99, 92, 83,
|
||||
44, 35, 28, 19, 109, 98, 93, 82, 45, 34, 29, 18,
|
||||
97, 94, 33, 30
|
||||
])
|
||||
|
||||
EYES_FW = EyeShape([
|
||||
103, 88, 39, 24, 105, 102, 89, 86, 41, 38, 25, 22,
|
||||
117, 106, 101, 90, 85, 74, 53, 42, 37, 26, 21, 10,
|
||||
123, 116, 100, 91, 75, 68, 59, 52, 36, 27, 11, 4,
|
||||
99, 92, 35, 28, 98, 93, 34, 29, 97, 94, 33, 30, 96,
|
||||
95, 32, 31
|
||||
])
|
||||
|
||||
EYES_DOWN = EyeShape([97, 94, 33, 30])
|
||||
|
||||
# Nova forma per al mode gestos: marc extern dels dos ulls (expressió "atenta")
|
||||
EYES_GESTURE = EyeShape([
|
||||
96, 97, 98, 99, 100, 101, 102, 103,
|
||||
104, 111, 112, 119, 120, 127,
|
||||
31, 32, 33, 34, 35, 36, 37, 38, 39,
|
||||
0, 7, 8, 15, 16, 23
|
||||
])
|
||||
|
||||
# ==================
|
||||
# Estat global
|
||||
# ==================
|
||||
|
||||
_pi: pigpio.pi = None
|
||||
_leds = [[0, 0, 0] for _ in range(NUM_LEDS)]
|
||||
_brightness = MAX_BR
|
||||
_leds_lock = threading.Lock()
|
||||
|
||||
_update_stop = threading.Event()
|
||||
_update_thread: threading.Thread = None
|
||||
|
||||
# Màscara GPIO per a les waveforms de pigpio
|
||||
_GPIO_MASK: int = 0
|
||||
|
||||
|
||||
# ==================
|
||||
# WS2811 via pigpio waveforms
|
||||
# ==================
|
||||
|
||||
def _send_ws2811(data: bytes):
|
||||
"""
|
||||
Envia dades RGB als LEDs WS2811 via pigpio waveforms.
|
||||
Ordre de color: GRB (igual que FastLED amb WS2811, GRB).
|
||||
Timing a 1µs de resolució (requereix sudo pigpiod -s 1):
|
||||
- Bit 0: 1µs HIGH + 2µs LOW (spec: 0.5µs + 2.0µs)
|
||||
- Bit 1: 2µs HIGH + 1µs LOW (spec: 1.2µs + 1.3µs)
|
||||
- Reset: 80µs LOW
|
||||
"""
|
||||
pulses = []
|
||||
for byte_val in data:
|
||||
for bit in range(7, -1, -1):
|
||||
if byte_val & (1 << bit):
|
||||
pulses.append(pigpio.pulse(_GPIO_MASK, 0, 2))
|
||||
pulses.append(pigpio.pulse(0, _GPIO_MASK, 1))
|
||||
else:
|
||||
pulses.append(pigpio.pulse(_GPIO_MASK, 0, 1))
|
||||
pulses.append(pigpio.pulse(0, _GPIO_MASK, 2))
|
||||
pulses.append(pigpio.pulse(0, _GPIO_MASK, 80)) # reset
|
||||
|
||||
_pi.wave_add_new()
|
||||
_pi.wave_add_generic(pulses)
|
||||
wid = _pi.wave_create()
|
||||
if wid >= 0:
|
||||
_pi.wave_send_once(wid)
|
||||
while _pi.wave_tx_busy():
|
||||
pass
|
||||
_pi.wave_delete(wid)
|
||||
|
||||
|
||||
def _eyes_show(brightness: int):
|
||||
"""Renderitza l'estat actual de _leds amb la brillantor indicada."""
|
||||
data = bytearray(NUM_LEDS * 3)
|
||||
scale = brightness / 255
|
||||
for i, (r, g, b) in enumerate(_leds):
|
||||
data[i * 3 + 0] = int(g * scale) # WS2811 GRB: primer G
|
||||
data[i * 3 + 1] = int(r * scale)
|
||||
data[i * 3 + 2] = int(b * scale)
|
||||
_send_ws2811(bytes(data))
|
||||
|
||||
|
||||
# ==================
|
||||
# Setup i cleanup
|
||||
# ==================
|
||||
|
||||
def eyes_setup(pi: pigpio.pi):
|
||||
"""Inicialitza el GPIO i arrenca el thread de parpelleig."""
|
||||
global _pi, _GPIO_MASK, _update_thread
|
||||
|
||||
_pi = pi
|
||||
_GPIO_MASK = 1 << LED_DATA
|
||||
|
||||
pi.set_mode(LED_DATA, pigpio.OUTPUT)
|
||||
pi.write(LED_DATA, 0)
|
||||
|
||||
_update_stop.clear()
|
||||
_update_thread = threading.Thread(
|
||||
target=_task_update_leds, daemon=True, name="eyes"
|
||||
)
|
||||
_update_thread.start()
|
||||
|
||||
|
||||
def eyes_cleanup():
|
||||
"""Atura el thread de parpelleig i apaga els LEDs."""
|
||||
_update_stop.set()
|
||||
if _update_thread:
|
||||
_update_thread.join(timeout=1.0)
|
||||
with _leds_lock:
|
||||
for i in range(NUM_LEDS):
|
||||
_leds[i] = [0, 0, 0]
|
||||
_eyes_show(255)
|
||||
|
||||
|
||||
# ==================
|
||||
# Thread de parpelleig (equivalent a task_update_leds del FreeRTOS)
|
||||
# ==================
|
||||
|
||||
def _task_update_leds():
|
||||
"""
|
||||
Bucle continu que fa respirar la brillantor dels ulls.
|
||||
Equivalent a task_update_leds() del FreeRTOS.
|
||||
"""
|
||||
global _brightness
|
||||
going_up = False # Al C++ comença a MAX_BR i baixa
|
||||
_brightness = MAX_BR
|
||||
|
||||
while not _update_stop.is_set():
|
||||
if going_up:
|
||||
if _brightness < MAX_BR:
|
||||
_brightness += 2
|
||||
else:
|
||||
going_up = False
|
||||
else:
|
||||
if _brightness > MIN_BR:
|
||||
_brightness -= 2
|
||||
else:
|
||||
going_up = True
|
||||
|
||||
with _leds_lock:
|
||||
_eyes_show(_brightness)
|
||||
time.sleep(0.05)
|
||||
|
||||
|
||||
# ==================
|
||||
# Animacions (equivalent a les funcions de eyes.cpp)
|
||||
# ==================
|
||||
|
||||
def eyes_turn_off():
|
||||
"""Apaga tots els LEDs amb un fos progressiu."""
|
||||
for _ in range(50):
|
||||
with _leds_lock:
|
||||
for i in range(NUM_LEDS):
|
||||
r, g, b = _leds[i]
|
||||
_leds[i] = [int(r * 245 / 255),
|
||||
int(g * 245 / 255),
|
||||
int(b * 245 / 255)]
|
||||
time.sleep(0.01)
|
||||
with _leds_lock:
|
||||
for i in range(NUM_LEDS):
|
||||
_leds[i] = [0, 0, 0]
|
||||
|
||||
|
||||
def eyes_turn_on(shape: EyeShape, color: tuple,
|
||||
repeat: int = 1, forward: bool = True):
|
||||
"""
|
||||
Encén els LEDs d'una forma un per un, amb animació.
|
||||
shape: forma a dibuixar (EYES_OPEN, EYES_FW, EYES_DOWN…)
|
||||
color: color RGB com a tupla (r, g, b)
|
||||
repeat: nombre de vegades que es repeteix l'animació
|
||||
forward: True = ordre normal, False = ordre invers
|
||||
"""
|
||||
r, g, b = color
|
||||
for rep in range(repeat):
|
||||
eyes_turn_off()
|
||||
for i in range(shape.len):
|
||||
idx = shape.leds[i if forward else (shape.len - 1 - i)]
|
||||
with _leds_lock:
|
||||
_leds[idx] = [r, g, b]
|
||||
time.sleep(0.008)
|
||||
|
||||
if rep < repeat - 1:
|
||||
for i in range(shape.len):
|
||||
idx = shape.leds[i if forward else (shape.len - 1 - i)]
|
||||
with _leds_lock:
|
||||
_leds[idx] = [0, 0, 0]
|
||||
time.sleep(0.008)
|
||||
|
||||
|
||||
# ==================
|
||||
# Animacions noves — mode gestos (TFG)
|
||||
# ==================
|
||||
|
||||
def eyes_gesture_mode_on():
|
||||
"""
|
||||
Animació d'activació del mode gestos.
|
||||
Parpelleig doble en cian per indicar que el robot escolta gestos.
|
||||
"""
|
||||
eyes_turn_on(EYES_OPEN, CYAN, repeat=2)
|
||||
|
||||
|
||||
def eyes_gesture_mode_off():
|
||||
"""
|
||||
Animació de desactivació del mode gestos.
|
||||
Torna als ulls oberts en blanc.
|
||||
"""
|
||||
eyes_turn_off()
|
||||
eyes_turn_on(EYES_OPEN, WHITE)
|
||||
|
||||
|
||||
def eyes_listening():
|
||||
"""
|
||||
Expressió "escoltant": marc extern dels ulls en cian.
|
||||
Es mostra mentre el robot espera un gest.
|
||||
"""
|
||||
eyes_turn_on(EYES_GESTURE, CYAN)
|
||||
Reference in New Issue
Block a user