Note
Hello, welcome to the SunFounder Raspberry Pi & Arduino & ESP32 Enthusiasts Community on Facebook! Dive deeper into Raspberry Pi, Arduino, and ESP32 with fellow enthusiasts.
Why Join?
Expert Support: Solve post-sale issues and technical challenges with help from our community and team.
Learn & Share: Exchange tips and tutorials to enhance your skills.
Exclusive Previews: Get early access to new product announcements and sneak peeks.
Special Discounts: Enjoy exclusive discounts on our newest products.
Festive Promotions and Giveaways: Take part in giveaways and holiday promotions.
👉 Ready to explore and create with us? Click [here] and join today!
8. Treasure Hunt
Arrange a maze in your room and place six different color cards in six corners. Then control PiCrawler to search for these color cards one by one!
Note
You can download and print the PDF Color Cards for color detection.
Run the Code
cd ~/picrawler/examples
sudo python3 8_treasure_hunt.py
View the Image
After the code runs, the terminal will display the following prompt:
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)
Then you can enter http://<your IP>:9000/mjpg in the browser to view the video screen. such as: http://192.168.18.113:9000/mjpg
Code
#!/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()
How it works?
What This Program Does
This program is a simple “treasure hunt” game for PiCrawler:
The camera streams to a web page (no local GUI window).
Vilib detects a target color (red/orange/yellow/green/blue/purple).
You control the robot with WASD.
When the detected color object is big enough, the program announces success and switches to a new target color.
The program exits cleanly on Ctrl+C without thread traceback.
Keyboard Input Runs in a Background Thread
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
The keyboard reading is separated into its own thread. This prevents the main loop from blocking while waiting for a key press.
Ctrl+C may raise KeyboardInterrupt inside this thread (readchar behavior), so it is caught and used to trigger a clean exit instead of printing an error.
Key Events Are Shared Safely
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
The program uses a lock to protect the shared variable key_state. The keyboard thread writes key events using set_key(). The main loop reads and clears events using pop_key().
This ensures the robot reacts to keys safely without race conditions.
Camera and Web Preview
Vilib.camera_start(vflip=False, hflip=False) Vilib.display(local=False, web=True)
The camera is started and the web preview is enabled. local=False avoids GUI crashes on systems without a desktop environment.
Stand Up First, Then Wait for Movement Keys
crawler.do_step('stand', 40) sleep(0.8)
After startup, the robot stands at speed 40 to stabilize posture. The program does not move the robot automatically. It only moves when a WASD key is received.
Selecting a Target Color
color = random.choice(COLOR_LIST) Vilib.color_detect(color) tts.say("Look for " + color)
A random color is selected from the list. Vilib color detection is enabled for that color. TTS announces the current target so the user knows what to find.
Detecting “Success” and Switching Targets
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 updates detect_obj_parameter continuously.
color_n indicates whether a target is detected
color_w is the detected target width (a rough “how close/big” measure)
When the target exists and is large enough, the program announces success and immediately switches to a new random target color.
Movement Control With WASD
if k in key_dict: action = key_dict[k] if action is not None: crawler.do_action(action, 1, speed) action = None
The main loop checks for a key event:
w → forward
s → backward
a → turn_left
d → turn_right
When a movement key is received, the robot executes one short action step. This design keeps control responsive and avoids continuous runaway movement.
Space Key: Repeat the Target Prompt
elif k == 'space': tts.say("Look for " + color)
Pressing Space repeats the current target message. This is useful if the user forgot the target color.
Quit and Cleanup
finally: stop_event.set() Vilib.color_detect("close") Vilib.camera_close() crawler.do_step('sit', 40)
When quitting:
stop_event tells the keyboard thread to stop.
Vilib color detection is disabled.
The camera is closed safely.
The robot sits down to avoid an unstable posture after exit.
This cleanup order helps prevent camera resource errors and ensures the robot ends in a safe position.