Page 1 of 1

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
by apigoto
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

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"}]}'
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

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
}
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.