Nota

Hola, bienvenido a la comunidad de entusiastas de SunFounder Raspberry Pi & Arduino & ESP32 en Facebook. ¡Explora más a fondo Raspberry Pi, Arduino y ESP32 con otros entusiastas!

¿Por qué unirse?

  • Soporte experto: Resuelve problemas postventa y desafíos técnicos con la ayuda de nuestra comunidad y equipo.

  • Aprende y comparte: Intercambia consejos y tutoriales para mejorar tus habilidades.

  • Avances exclusivos: Obtén acceso anticipado a anuncios de nuevos productos y vistas previas.

  • Descuentos especiales: Disfruta de descuentos exclusivos en nuestros productos más recientes.

  • Promociones festivas y sorteos: Participa en sorteos y promociones de temporada.

👉 ¿Listo para explorar y crear con nosotros? Haz clic en [aquí] y únete hoy mismo.

Búsqueda del Tesoro

Organiza un laberinto en tu habitación y coloca seis tarjetas de colores diferentes en seis esquinas. Luego, controla a PiCrawler para buscar estas tarjetas de colores una por una.

Nota

Puedes descargar e imprimir las Tarjetas de Color en PDF para la detección de colores.

Ejecutar el Código

cd ~/picrawler/examples
sudo python3 8_treasure_hunt.py

Ver la Imagen

Después de ejecutar el código, la terminal mostrará el siguiente mensaje:

No desktop !
* Serving Flask app "vilib.vilib" (lazy loading)
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://0.0.0.0:9000/ (Press CTRL+C to quit)

Luego, puedes ingresar http://<tu IP>:9000/mjpg en el navegador para ver la pantalla de video. Por ejemplo: http://192.168.18.113:9000/mjpg.

../_images/display.png

Código

#!/usr/bin/env python3
from picrawler import Picrawler
from time import sleep, time
from robot_hat import Music, TTS
from vilib import Vilib
import readchar
import random
import threading

crawler = Picrawler()
music = Music()   # kept for compatibility (not used here)
tts = TTS()

MANUAL = '''
Press keys on keyboard to control Picrawler!
        w: Forward
        a: Turn left
        s: Backward
        d: Turn right
        space: Say the target again
        Ctrl+C: Quit
'''

color = "red"
color_list = ["red", "orange", "yellow", "green", "blue", "purple"]

key_dict = {
        'w': 'forward',
        's': 'backward',
        'a': 'turn_left',
        'd': 'turn_right',
}

# ----------------------------
# Thread-safe key handling
# ----------------------------
lock = threading.Lock()
key_state = None               # last key event
stop_event = threading.Event() # signal to exit cleanly

def set_key(k):
        global key_state
        with lock:
                key_state = k

def pop_key():
        """Read and clear the last key event."""
        global key_state
        with lock:
                k = key_state
                key_state = None
        return k

def key_scan_thread():
        """Keyboard input thread (quiet exit on Ctrl+C)."""
        while not stop_event.is_set():
                try:
                        k = readchar.readkey()
                except KeyboardInterrupt:
                        # Ctrl+C may raise KeyboardInterrupt inside this thread
                        stop_event.set()
                        break
                except Exception:
                        sleep(0.02)
                        continue

                if k == readchar.key.SPACE:
                        set_key('space')
                elif k == readchar.key.CTRL_C:
                        set_key('quit')
                        stop_event.set()
                        break
                else:
                        try:
                                set_key(str(k).lower())
                        except Exception:
                                pass

                sleep(0.01)

# ----------------------------
# Game logic
# ----------------------------
def renew_color_detect():
        global color
        color = random.choice(color_list)
        try:
                Vilib.color_detect(color)
        except Exception:
                pass
        try:
                tts.say("Look for " + color)
        except Exception:
                pass

def safe_camera_close():
        try:
                Vilib.color_detect("close")
        except Exception:
                pass
        try:
                Vilib.camera_close()
        except Exception:
                pass

def safe_sit():
        try:
                crawler.do_step('sit', 40)
                sleep(0.5)
        except Exception:
                pass

def stand_ready():
        """
        Stand up after startup.
        Requirement: stand at 40, then only move after WASD is pressed.
        """
        try:
                crawler.do_step('stand', 40)
                sleep(0.8)
        except Exception:
                pass

def main():
        speed = 80
        action = None

        # Start camera + web preview
        Vilib.camera_start(vflip=False, hflip=False)
        Vilib.display(local=False, web=True)
        sleep(0.8)

        print(MANUAL)

        # Start keyboard thread (daemon, so it won't block process exit)
        t = threading.Thread(target=key_scan_thread, daemon=True)
        t.start()

        # Announce and stand up to 40
        try:
                tts.say("game start")
        except Exception:
                pass
        sleep(0.05)

        stand_ready()
        renew_color_detect()

        try:
                while not stop_event.is_set():
                        # If target detected and large enough -> renew target
                        try:
                                n = Vilib.detect_obj_parameter.get('color_n', 0)
                                w = Vilib.detect_obj_parameter.get('color_w', 0)
                        except Exception:
                                n, w = 0, 0

                        if n != 0 and w > 100:
                                try:
                                        tts.say("well done")
                                except Exception:
                                        pass
                                sleep(0.05)
                                renew_color_detect()

                        # Handle key event
                        k = pop_key()

                        if k in key_dict:
                                action = key_dict[k]

                        elif k == 'space':
                                try:
                                        tts.say("Look for " + color)
                                except Exception:
                                        pass

                        elif k == 'quit':
                                stop_event.set()

                        # Move only after receiving a WASD action
                        if action is not None:
                                try:
                                        crawler.do_action(action, 1, speed)
                                except Exception:
                                        pass
                                action = None

                        sleep(0.05)

        except KeyboardInterrupt:
                stop_event.set()

        finally:
                # Clean exit
                stop_event.set()
                safe_camera_close()
                safe_sit()
                print("\nQuit")

if __name__ == "__main__":
        main()

¿Cómo funciona?

  1. Qué Hace Este Programa

    Este programa es un sencillo juego de “búsqueda del tesoro” para PiCrawler:

    • La cámara transmite a una página web (sin ventana GUI local).

    • Vilib detecta un color objetivo (red/orange/yellow/green/blue/purple).

    • Controlas el robot usando WASD.

    • Cuando el objeto del color detectado es lo suficientemente grande, el programa anuncia el éxito y cambia a un nuevo color objetivo.

    • El programa termina limpiamente con Ctrl+C sin mostrar errores de hilos.

  2. La Entrada del Teclado se Ejecuta en un Hilo en Segundo Plano

    stop_event = threading.Event()
    key_state = None
    
    def key_scan_thread():
        while not stop_event.is_set():
            try:
                k = readchar.readkey()
            except KeyboardInterrupt:
                stop_event.set()
                break
    

    La lectura del teclado se ejecuta en su propio hilo. Esto evita que el bucle principal se bloquee mientras espera una tecla.

    Ctrl+C puede generar KeyboardInterrupt dentro de este hilo (comportamiento de readchar), por lo que se captura y se utiliza para iniciar una salida limpia en lugar de mostrar un error.

  3. Los Eventos de Tecla se Comparten de Forma Segura

    lock = threading.Lock()
    
    def set_key(k):
        global key_state
        with lock:
            key_state = k
    
    def pop_key():
        global key_state
        with lock:
            k = key_state
            key_state = None
        return k
    

    El programa usa un lock para proteger la variable compartida key_state. El hilo del teclado escribe eventos de tecla usando set_key(). El bucle principal lee y limpia los eventos usando pop_key().

    Esto garantiza que el robot reaccione a las teclas de forma segura sin condiciones de carrera.

  4. Cámara y Vista Previa Web

    Vilib.camera_start(vflip=False, hflip=False)
    Vilib.display(local=False, web=True)
    

    La cámara se inicia y se habilita la vista previa web. local=False evita fallos de GUI en sistemas sin entorno de escritorio.

  5. Primero Ponerse de Pie, Luego Esperar las Teclas de Movimiento

    crawler.do_step('stand', 40)
    sleep(0.8)
    

    Después de iniciar, el robot se pone de pie a velocidad 40 para estabilizar la postura. El programa no mueve el robot automáticamente. Solo se mueve cuando recibe una tecla WASD.

  6. Selección de un Color Objetivo

    color = random.choice(COLOR_LIST)
    Vilib.color_detect(color)
    tts.say("Look for " + color)
    

    Se selecciona un color aleatorio de la lista. La detección de color de Vilib se activa para ese color. TTS anuncia el objetivo actual para que el usuario sepa qué buscar.

  7. Detección de “Éxito” y Cambio de Objetivo

    n = Vilib.detect_obj_parameter.get('color_n', 0)
    w = Vilib.detect_obj_parameter.get('color_w', 0)
    
    if n != 0 and w > 100:
        tts.say("well done")
        renew_color_detect()
    

    Vilib actualiza detect_obj_parameter continuamente.

    • color_n indica si se detecta un objetivo

    • color_w es el ancho del objetivo detectado (una medida aproximada de “qué tan cerca o grande es”)

    Cuando el objetivo existe y es lo suficientemente grande, el programa anuncia el éxito y cambia inmediatamente a un nuevo color objetivo aleatorio.

  8. Control de Movimiento con WASD

    if k in key_dict:
        action = key_dict[k]
    
    if action is not None:
        crawler.do_action(action, 1, speed)
        action = None
    

    El bucle principal comprueba si hay un evento de tecla:

    • w → forward

    • s → backward

    • a → turn_left

    • d → turn_right

    Cuando se recibe una tecla de movimiento, el robot ejecuta un paso corto de acción. Este diseño mantiene el control sensible y evita movimientos continuos descontrolados.

  9. Tecla Espacio: Repetir el Mensaje del Objetivo

    elif k == 'space':
        tts.say("Look for " + color)
    

    Al presionar la barra espaciadora se repite el mensaje del objetivo actual. Esto es útil si el usuario olvidó el color objetivo.

  10. Salir y Limpieza

    finally:
        stop_event.set()
        Vilib.color_detect("close")
        Vilib.camera_close()
        crawler.do_step('sit', 40)
    

    Al salir:

    • stop_event indica al hilo del teclado que debe detenerse.

    • La detección de color de Vilib se desactiva.

    • La cámara se cierra de forma segura.

    • El robot se sienta para evitar una postura inestable después de salir.

    Este orden de limpieza ayuda a prevenir errores de recursos de cámara y garantiza que el robot termine en una posición segura.