dzVents: ask an OpenAI-compatible LLM (local or hosted) and show the answer in a Text device, fully async with openURL
Posted: Sunday 18 October 2026 11:43
I work on the APIGOTO team. APIGOTO is an independent OpenAI-compatible gateway, not affiliated with OpenAI, Anthropic or Google. The script below works with any /v1/chat/completions endpoint, including a local one (llama.cpp server, Ollama's OpenAI mode and so on). Only the base URL and model ID change.
What it does: every morning, or whenever you flip a dummy switch, it sends a few sensor values to the model and writes a one-line summary into a Text device. Everything uses domoticz.openURL with a callback, so the event system is never blocked while the model thinks.
1. Test the endpoint from the Domoticz host
2. Devices and variables
- Dummy hardware → a Text device called AI summary, and a Switch called AI ask.
- Setup → More options → User variables → a String variable llm_api_key holding your key, so it doesn't sit in the script.
3. The dzVents script
Because postData is a Lua table, dzVents encodes it as JSON and sets the JSON content type. The same script handles both the request and the callback.
Troubleshooting
- Nothing happens after the switch: check the log for the LLM marker. If no HTTP line appears, the callback name in httpResponses doesn't match CB.
- item.isJSON false but data looks like JSON: some servers send application/json; charset=utf-8. The fromJSON fallback covers that.
- HTTP 401: the user variable is empty or has a trailing space.
- HTTP 404: /v1 missing or doubled in BASE_URL.
- "model not found": MODEL doesn't match GET /v1/models exactly.
- Long answers in a Text device look bad: keep max_tokens low and say "one sentence" in the system prompt.
- Privacy: whatever you put in facts leaves the house unless you use a local server.
On the gateway side: one base URL for several model families, automatic failover on upstream errors (at most 3 retries per request on another route), a per-request log, pay-as-you-go, BYOK: www.china-aiapi.com
What would you feed it? Weather plus energy prices seems the obvious one. I'm curious whether anyone uses it for notifications rather than a dashboard line.
What it does: every morning, or whenever you flip a dummy switch, it sends a few sensor values to the model and writes a one-line summary into a Text device. Everything uses domoticz.openURL with a callback, so the event system is never blocked while the model thinks.
1. Test the endpoint from the Domoticz host
Code: Select all
curl https://api.china-aiapi.com/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{"model":"MODEL_ID","messages":[{"role":"user","content":"Say hi"}]}'- Dummy hardware → a Text device called AI summary, and a Switch called AI ask.
- Setup → More options → User variables → a String variable llm_api_key holding your key, so it doesn't sit in the script.
3. The dzVents script
Code: Select all
local BASE_URL = 'https://api.china-aiapi.com/v1' -- or http://<lan-ip>:<port>/v1
local MODEL = 'MODEL_ID'
local CB = 'llmSummary'
return {
on = {
timer = { 'at 07:00' },
devices = { 'AI ask' },
httpResponses = { CB },
},
logging = { level = domoticz.LOG_INFO, marker = 'LLM' },
execute = function(domoticz, item)
if item.isHTTPResponse then
if not item.ok then
domoticz.log('HTTP ' .. tostring(item.statusCode) .. ' ' .. tostring(item.data), domoticz.LOG_ERROR)
return
end
local body = item.isJSON and item.json or domoticz.utils.fromJSON(item.data, {})
local text = body.choices and body.choices[1] and body.choices[1].message.content
if text then
domoticz.devices('AI summary').updateText(text)
else
domoticz.log('No content in response: ' .. tostring(item.data), domoticz.LOG_ERROR)
end
return
end
if item.isDevice and item.state ~= 'On' then return end
-- build the facts; replace with your own devices
local facts = string.format('Outside %.1f C, inside %.1f C.',
domoticz.devices('Outside temp').temperature,
domoticz.devices('Living room').temperature)
domoticz.openURL({
url = BASE_URL .. '/chat/completions',
method = 'POST',
headers = { ['Authorization'] = 'Bearer ' .. domoticz.variables('llm_api_key').value },
postData = {
model = MODEL,
max_tokens = 80,
messages = {
{ role = 'system', content = 'One short, plain sentence for a home dashboard. No emojis.' },
{ role = 'user', content = facts },
},
},
callback = CB,
})
end
}Troubleshooting
- Nothing happens after the switch: check the log for the LLM marker. If no HTTP line appears, the callback name in httpResponses doesn't match CB.
- item.isJSON false but data looks like JSON: some servers send application/json; charset=utf-8. The fromJSON fallback covers that.
- HTTP 401: the user variable is empty or has a trailing space.
- HTTP 404: /v1 missing or doubled in BASE_URL.
- "model not found": MODEL doesn't match GET /v1/models exactly.
- Long answers in a Text device look bad: keep max_tokens low and say "one sentence" in the system prompt.
- Privacy: whatever you put in facts leaves the house unless you use a local server.
On the gateway side: one base URL for several model families, automatic failover on upstream errors (at most 3 retries per request on another route), a per-request log, pay-as-you-go, BYOK: www.china-aiapi.com
What would you feed it? Weather plus energy prices seems the obvious one. I'm curious whether anyone uses it for notifications rather than a dashboard line.