96 lines
3.2 KiB
Python
96 lines
3.2 KiB
Python
"""
|
|
Greeter routine - a worked example of sequencing several subsystems.
|
|
|
|
Shows how one button can drive mode, motion, speech, screen and lights together,
|
|
which is the usual reason to write an extension rather than clicking four tabs.
|
|
"""
|
|
|
|
import asyncio
|
|
|
|
from backend.plugin_api import Plugin
|
|
|
|
plugin = Plugin(
|
|
id="greeter",
|
|
name="Greeter routine",
|
|
description="A one-button welcome: stand, wave, speak, smile, glow.",
|
|
icon="\U0001F44B",
|
|
order=10,
|
|
)
|
|
|
|
GREETINGS = {
|
|
"en": "Hello, I am X2. Nice to meet you.",
|
|
"formal": "Good day. Welcome. How may I assist you?",
|
|
"short": "Hi there.",
|
|
}
|
|
|
|
|
|
@plugin.select("phrase", label="Greeting", default="en", options=[
|
|
{"value": "en", "label": "Friendly"},
|
|
{"value": "formal", "label": "Formal"},
|
|
{"value": "short", "label": "Short"},
|
|
])
|
|
async def phrase(ctx, value):
|
|
ctx.storage["phrase"] = value
|
|
return f"Greeting set to '{value}'"
|
|
|
|
|
|
@plugin.toggle("with_lights", label="Include light cue", default=True)
|
|
async def with_lights(ctx, value):
|
|
ctx.storage["with_lights"] = value
|
|
return "Light cue on" if value else "Light cue off"
|
|
|
|
|
|
@plugin.action("run", label="Run greeting", style="primary", icon="▶",
|
|
help="Requires clear space around the robot.")
|
|
async def run(ctx):
|
|
if ctx.storage.get("running"):
|
|
return {"ok": False, "message": "Greeting already running"}
|
|
|
|
ctx.storage["running"] = True
|
|
try:
|
|
# Preset motions need Stable stand, so get there first if we are not.
|
|
if ctx.state.mode != "STAND_DEFAULT":
|
|
outcome = await ctx.bridge.set_mode("STAND_DEFAULT")
|
|
if not outcome.ok:
|
|
return {"ok": False, "message": f"Could not enter Stable stand: {outcome.message}"}
|
|
await asyncio.sleep(1.5)
|
|
|
|
if ctx.storage.get("with_lights", True):
|
|
await ctx.bridge.set_led(mode=1, r=57, g=135, b=229)
|
|
|
|
await ctx.bridge.play_emoji(90, mode=1) # Happy
|
|
await ctx.bridge.play_preset(motion=1002, area=2) # Right-hand wave
|
|
|
|
text = GREETINGS.get(ctx.storage.get("phrase", "en"), GREETINGS["en"])
|
|
await ctx.bridge.speak(text, priority=6)
|
|
|
|
ctx.storage["count"] = ctx.storage.get("count", 0) + 1
|
|
await ctx.push("count", ctx.storage["count"])
|
|
await ctx.log(f"Greeting played ({ctx.storage['count']} total)")
|
|
return "Greeting played"
|
|
finally:
|
|
ctx.storage["running"] = False
|
|
|
|
|
|
@plugin.action("goodbye", label="Say goodbye", style="default", icon="\U0001F44B")
|
|
async def goodbye(ctx):
|
|
if ctx.state.mode != "STAND_DEFAULT":
|
|
return {"ok": False, "message": "Switch to Stable stand first"}
|
|
await ctx.bridge.play_emoji(110, mode=1) # Sad
|
|
# TURN_WAVE_HAND (2001), whole body. The documented "wave goodbye" id 3031
|
|
# does not exist in this firmware's McPresetMotion enum.
|
|
await ctx.bridge.play_preset(motion=2001, area=11)
|
|
await ctx.bridge.speak("Goodbye. See you soon.", priority=6)
|
|
return "Goodbye played"
|
|
|
|
|
|
plugin.readout("count", "Greetings played", precision=0)
|
|
|
|
|
|
@plugin.on_start
|
|
async def start(ctx):
|
|
ctx.storage.setdefault("count", 0)
|
|
ctx.storage.setdefault("phrase", "en")
|
|
ctx.storage.setdefault("with_lights", True)
|
|
await ctx.push("count", ctx.storage["count"])
|