""" 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"])