Lights
“Turn on the kitchen light.”
“Dim it to twenty percent.”
Climate
“Set the thermostat to seventy.”
Locks & doors
“Lock the front door.”
Media & scenes
“Start movie night.”
How it works
Connect the device in Home Assistant
Add it from the dashboard, as covered in Add integrations.
Build a Local Ability for it
Create a small Ability that reaches the device through Home Assistant on your DevKit.
This has to be a Local Ability. The action happens on your DevKit, where Home Assistant runs, so only the Local category can reach it.
Example: voice-control Tasmota lights
This walkthrough builds a working voice integration end to end: a Local Ability that controls Tasmota smart lights through the Home Assistant instance on your device. Once it’s set up you can say “turn on the kitchen light”, “make it warm”, or “set it to red” and your agent does the rest. It comes in two files, which is the standard Local Ability split:| File | Runs on | Responsibility |
|---|---|---|
main.py | Standard Ability runtime | Listens to the user, turns speech into an intent with the LLM, and calls the DevKit side. |
devkit_functions.py | OpenHome DevKit | Talks to Home Assistant’s REST API on the device and performs the actual light action. |
This is a Local Ability, so it only runs on real OpenHome DevKit hardware, the device where Home Assistant is installed. See Local Abilities for the full reference on how the two files work together.
What you’ll need
- Home Assistant installed on your DevKit (see Install & manage). The MQTT integration is set up automatically during install.
- One or more Tasmota smart bulbs or strips on the same network as your DevKit.
- The OpenHome Live Editor to create the Ability under the Local category.
Step 1: Connecting to Home Assistant (automatic)
You don’t set up authentication yourself. When you install Home Assistant through OpenHome, the credentials your Ability needs are provisioned on the device, and the DevKit side authenticates on its own. There’s no file to create, no token to paste, and nothing to configure. For this Tasmota example, it just works.Reuse the connection in your own Ability
Building a different Ability that talks to Home Assistant? Reuse OpenHome’s connection logic directly. Drop this into yourdevkit_functions.py and call _ensure_auth() before any Home Assistant request. It finds the saved credentials, exchanges them for a short-lived access token at Home Assistant’s /auth/token endpoint, and caches the token until just before it expires:
_ensure_auth() first, so the access token is fetched and refreshed for you automatically. If a token expires mid-session, the next call refreshes it. (See _ha_request() in the full devkit_functions.py below for how each call uses it.)
Where the credentials live (reference)
You normally never touch this, but for advanced or custom setups it helps to know how the connection is stored._ensure_auth() reads a small file containing:
| Key | Value |
|---|---|
HA_URL | The Home Assistant address as seen from the DevKit. Because Home Assistant runs on the same device, http://localhost:8123 works. |
HA_REFRESH_TOKEN | A refresh token issued by Home Assistant for user (openhome_devkit_user, created during install). |
HA_CLIENT_ID | The OAuth client id the token was issued for. Defaults to HA_URL + / if you leave it out. |
- The path in the
OPENHOME_HA_AUTH_PATHenvironment variable ~/.ha_refresh_token/home/openhome/.ha_refresh_token
OpenHome saves these credentials during install, so this file already exists at one of the paths above, and this is simply where the code looks. For a custom location, point
OPENHOME_HA_AUTH_PATH at your own file.Step 2: Connect your Tasmota device to Home Assistant
Now pair the physical light with Home Assistant. Tasmota talks to the same MQTT broker your DevKit’s Home Assistant already uses, so once it connects, Home Assistant discovers it on its own.Find your Tasmota device's IP
Check your router’s list of connected devices, or open
tasmota-XXXXX.local in a browser.Open the Tasmota web UI → Configuration → Configure MQTT
Set these values:
The Host is your DevKit’s IP address, the same one you use to open the Home Assistant dashboard. You can find it in the DevKit section under MQTT, shown as Device IP.
| Field | Value |
|---|---|
| Host | Your DevKit’s IP (for example 192.168.18.106) |
| Port | 1883 |
| User | openhome_devkit_user |
| Password | admin123 |
| Topic | leave default |
| Full Topic | leave default (%prefix%/%topic%/) |
Home Assistant auto-discovers it
The MQTT integration is already loaded in Home Assistant. Once Tasmota connects, it publishes to the discovery topic and Home Assistant’s Tasmota integration picks it up automatically, with no manual configuration needed. The light then shows up as a
light.* entity, which is exactly what the Ability controls.Step 3: Build the Ability
In the OpenHome Live Editor, create a new Ability and select the Local category (Local Abilities are the only type that can reach Home Assistant on the device; see Local Abilities). Give it trigger words so users can launch it by voice, such as “smart home” or “lights”. A Local Ability is two files. This example needs no third-party packages, sorequirements.txt can stay empty, since everything uses the Python standard library.
main.py: voice and intent
main.py runs in the standard Ability runtime. It greets the user, runs a conversation loop, and uses the LLM to convert each spoken request into a structured intent (which integration, which action, which device). It then hands that intent to the DevKit side and speaks a short confirmation.
devkit_functions.py: Home Assistant on the device
devkit_functions.py runs on the DevKit. It authenticates with the credentials file from Step 1, finds the matching Tasmota light entity, and calls Home Assistant’s light services to perform the action. ha_startup is a lightweight ping that also reports which integrations and how many entities are available.
Step 4: Talk to it
Launch the Ability with one of your trigger words, then speak naturally. The conversation loop keeps listening until you say you’re done.- “Turn on the kitchen light.”
- “Make it warm.” (reuses the last light you mentioned)
- “Set the lamp to red.”
- “Dim the bedroom to twenty percent.”
- “That’s all, thanks.” (ends the session)
Extending it
This template is built around an integration registry. To support more than Tasmota lights, add another entry toINTEGRATION_REGISTRY in main.py (a context function describing its actions, and an execute function that maps an intent to a DevKit call) and a matching handler in FUNCTION_REGISTRY in devkit_functions.py. The voice loop, prompt builder, and dispatch logic stay the same.
Edge cases
Home Assistant can't be reached
Home Assistant can't be reached
The Ability checks for a running Home Assistant on the device when it starts. If Home Assistant isn’t installed, or is still starting up, the Ability can’t control anything. Make sure it’s installed and running (see Install & manage), then relaunch.
No lights are found
No lights are found
If the Ability says it can’t find a light, the Tasmota device probably isn’t connected to Home Assistant yet. Recheck Step 2. Once Tasmota connects to the broker, Home Assistant discovers it as a light and the Ability can control it.
Only Tasmota lights are controlled
Only Tasmota lights are controlled
This example deliberately ignores lights from other ecosystems (such as Hue, LIFX, Shelly, WLED, Yeelight, and TP-Link) so it only acts on your Tasmota bulbs. To control other brands, extend the Ability as described above.
You have more than one light
You have more than one light
Add a short descriptor to your command, like “kitchen” or “bedroom lamp”. The DevKit side matches it against your Home Assistant light names. If it can’t match your words to a specific light, it falls back to the first one it finds, so naming your lights clearly in Home Assistant helps.
A light is powered off at the wall
A light is powered off at the wall
A bulb that’s cut off from power shows as unavailable in Home Assistant and is skipped. Restore power so it reconnects, then try your command again.
It can't tell you whether a light is already on or off
It can't tell you whether a light is already on or off
The Ability sends actions; it doesn’t read a light’s current state. It will never claim a light is on or off without performing an action, so ask it to turn things on, off, or toggle rather than asking for status.
It didn't catch what you said
It didn't catch what you said
If your speech isn’t recognized, the Ability asks you to repeat. After a few unclear tries in a row it ends the session politely. Just relaunch it with your trigger word.
See also
- Local Abilities: full reference for the
main.py+devkit_functions.pysplit - Local Ability quickstart: get a Local Ability running fast
- Voice-First Best Practices: the UX rules behind the spoken responses

