This is a template ability that controls the onboard NeoPixel LED ring on an OpenHome DevKit using natural-language voice commands.
- Ambient mood lighting controller
- Notification ring (flash on incoming events)
- Build / oncall status indicator
- Music visualiser with custom palettes
- Sleep-wake routines with sunrise-sunset fades
- Pomodoro timer with phase-coloured transitions
This template uses generic triggers — customize these for your specific ability:
- "control the lights" / "lights control" / "change the lights"
- Configure your own trigger words in the OpenHome dashboard
- An OpenHome DevKit (Raspberry Pi) with the onboard NeoPixel ring
- NeoPixel ring: 24 pixels on GPIO 12, driven via PWM/DMA
rpi-ws281x(declared inrequirements.txt)- DevKit-side bridge functions in
devkit_functions.py(shipped with this template)
⚠️ Local abilities cannot be tested in the Live Editor. They must run on a connected DevKit device.
- Get the template from the OpenHome dashboard or GitHub and add it to your agent.
- Configure trigger words in the dashboard.
- Power on the DevKit, connect it to your agent, and say a trigger word.
- User speaks a request
- LLM routes the request to one of the registered NeoPixel commands and returns a JSON object
- The ability speaks the response and dispatches the command to the DevKit
- Effects run continuously by default until the user changes them or exits
User: "make it red" → AI: "Going red." (ring turns red)
User: "now do a candle flicker" → AI: "Lighting a warm candle." (ring flickers)
User: "stop" → AI: "Closing lights control." (ring resets, ability exits)
Dispatches a NeoPixel command from main.py to a function registered in devkit_functions.py.
await self.capability_worker.send_devkit_capability_action(
"neopixel_solid", ["ff0000", "180"], 8
)Used for the system-level toggles that surround the ability lifecycle.
await self.capability_worker.send_devkit_action("automatic_leds_off")
# ... ability runs ...
await self.capability_worker.send_devkit_action("automatic_leds_on")Implements every neopixel_* function the LLM can route to. Long-running effects supersede previous ones cleanly when a new command arrives. The file also exposes general DevKit utilities (GPIO, system stats, camera, network, services) — useful when extending this template into a broader hardware-control ability.
- Voice input is sent to the LLM with a system prompt enumerating every available NeoPixel function
- The LLM returns
{ "function_name", "args", "spoken_response" } - The spoken response is queued in parallel with the bridge call so the user hears confirmation while the ring is already changing
- If a duration was given, a session task drops a soft fallback gradient onto the ring when the effect ends
- The loop continues until the user says an exit phrase ("stop", "bye", "thanks", "I'm done")
Edit the NEOPIXEL_COMMANDS dict at the top of main.py. The system prompt is generated from this dict automatically.
Edit FALLBACK_FUNCTION and FALLBACK_ARGS to change what users see between timed effects.
By default, effects run continuously (999999 seconds). To make effects time-limited by default, edit rule 5 in SYSTEM_PROMPT.
- Speak and dispatch in parallel — keep
session_tasks.create(self.capability_worker.speak(...))so confirmation and effect happen together. - Always restore automatic LED behaviour on exit — the
finallyblock callsneopixel_off,automatic_leds_on, andresume_normal_flow(). Local abilities holding hardware must clean up. - Use the command counter for stale tasks —
_command_countersupersedes pending fallback timers when a newer command arrives. Capture the counter at task start and bail if it has advanced.
Check the DevKit is connected, automatic_leds_off is being sent, and review [Lights] bridge result: in the editor logs.
The LLM has misrouted "stop" to neopixel_off. Re-emphasise rule 14 in SYSTEM_PROMPT and add the failing phrasing to the explicit examples list.