""" Copy this file, rename it, change the id - you have a new dashboard panel. Every control below shows one widget type. Delete what you do not need. After editing, press "Reload extensions" in the Extensions tab. Files starting with an underscore are skipped by the loader, so this template never appears in the UI until you copy it to a normal name. """ from backend.plugin_api import Plugin plugin = Plugin( id="template", name="Template", description="Every control type, as a starting point.", icon="\U0001F9E9", order=900, ) # A button. style: default | primary | warn | danger @plugin.action("hello", label="Say hello", style="primary", icon="\U0001F44B") async def hello(ctx): await ctx.bridge.speak("Hello from a dashboard extension") return "Spoke" # A destructive button asks for confirmation first. @plugin.action("reset", label="Reset counter", style="danger", confirm="Reset the stored counter to zero?") async def reset(ctx): ctx.storage["count"] = 0 await ctx.push("count", 0) return "Counter reset" # A slider. live=True fires continuously while dragging; leave it False to fire # only on release, which is what you want for anything that moves the robot. @plugin.slider("speed", label="Cruise speed", min=0.0, max=0.6, step=0.05, default=0.2, unit="m/s", help="Applied when the routine below starts.") async def speed(ctx, value): ctx.storage["speed"] = value return f"Speed set to {value:.2f} m/s" @plugin.toggle("verbose", label="Verbose logging", default=False) async def verbose(ctx, value): ctx.storage["verbose"] = value return "Verbose on" if value else "Verbose off" @plugin.select("gait", label="Gait", default="normal", options=[ {"value": "slow", "label": "Slow and steady"}, {"value": "normal", "label": "Normal"}, {"value": "brisk", "label": "Brisk"}, ]) async def gait(ctx, value): ctx.storage["gait"] = value return f"Gait: {value}" @plugin.text("announce", label="Announcement", placeholder="Type something to say", submit_label="Speak") async def announce(ctx, value): result = await ctx.bridge.speak(value, priority=6) return result.message @plugin.number("repeats", label="Repeat count", min=1, max=10, step=1, default=3) async def repeats(ctx, value): ctx.storage["repeats"] = int(value) return f"Will repeat {int(value)}x" @plugin.color("tint", label="Strip colour", default="#3987e5") async def tint(ctx, value): value = value.lstrip("#") r, g, b = (int(value[i:i + 2], 16) for i in (0, 2, 4)) result = await ctx.bridge.set_led(mode=0, r=r, g=g, b=b) return result.message # Declared readouts appear as stat tiles. chart=True adds a sparkline fed by # ctx.record(). plugin.readout("count", "Ticks", chart=True, precision=0) plugin.readout("battery", "Battery seen", unit="%", precision=1) @plugin.on_start async def start(ctx): ctx.storage.setdefault("count", 0) await ctx.log("Template extension loaded") @plugin.on_tick(interval=2.0) async def tick(ctx): ctx.storage["count"] = ctx.storage.get("count", 0) + 1 await ctx.push("count", ctx.storage["count"]) ctx.record("count", ctx.storage["count"]) battery = ctx.state.battery_pct if battery is not None: await ctx.push("battery", round(battery, 1))