2026-08-13 16:23:18 +04:00

106 lines
3.3 KiB
Python

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