vclu

programmable device runtime

vclu-agent

Inspect. Execute. Control.

scroll

Jedno polecenie

Raspberry Pi. Docker. Linux arm64/amd64.

$ curl -sSL https://vclu.pl/install.sh | bash

Wymaga bash i curl. Wykrywa platforme automatycznie.

Dokumentacja →

Nie hub. Nie bridge.

vclu to programowalny runtime do sterowania urzadzeniami. Dziala lokalnie, laczy sie z czym chcesz, i daje pelna kontrole nad tym co jest widoczne na zewnatrz.

vclu to nie jest:

  • serwis chmurowy
  • zamiennik firmware'u
  • wrapper nad Object Managerem
  • kolejna apka do "madrego domu"

vclu to jest:

  • lokalny runtime
  • programowalny control plane
  • graf urzadzen z jawnym expose
  • MCP server dla agentow AI

Piszesz logike w Lua. Decydujesz co wystawic przez MQTT, REST, WebSocket albo MCP. Reszta zostaje w srodku. Bez clouda, bez konta, bez subskrypcji.

Dla developerow

Runtime z nowoczesnym API, event-driven architektura i natywnym wsparciem dla AI.

MQTT broker + client

  • embedded broker (optional)
  • external broker support
  • Lua publish/subscribe
  • HA auto-discovery

Runtime Lua VM

  • event-driven architecture
  • hot reload
  • timer + scheduler
  • object registry
  • pattern-based event bus

API REST + WS

  • REST API
  • WebSocket stream
  • device registry introspection
  • real-time state changes

Plugins sandbox

  • isolated Lua states
  • permission manifest
  • lifecycle hooks
  • sandboxed HTTP / storage

Integrations ready

  • Home Assistant
  • MQTT any broker
  • Tasmota / Shelly webhooks
  • InfluxDB + Grafana

Hardware GPIO

  • DOUT, DIN, Dimmer, Timer
  • GPIO passthrough (RPi)
  • mock GPIO (testing)
  • Docker i bare metal

Model Context Protocol

vclu wystawia standardowy MCP server. Kazdy LLM — Claude, GPT, Gemini — widzi urzadzenia jako structured tools i moze nimi sterowac.

Dlaczego MCP?

Zamiast pisac custom integracje per-model, wystawiasz jedno API ktore rozumie kazdy agent AI. Structured tool definitions, input schema, per-device ACL — AI dostaje dokladnie tyle, ile mu dasz.

tool schema MCP
{
  "name": "vclu_device_set",
  "description": "Set device state",
  "inputSchema": {
    "type": "object",
    "properties": {
      "path": {
        "type": "string",
        "description": "Device registry path"
      },
      "value": {
        "type": "number",
        "description": "Target state"
      }
    },
    "required": ["path", "value"]
  }
}
expose z ACL Lua
-- readonly: AI widzi stan, ale nie steruje
expose(lamp, "switch"):readonly()

-- full access: AI moze sterowac
expose(biuro, "switch", {
    name = "Biuro",
    readonly = false
})

-- tylko MQTT, nie MCP
expose(temp, "temperature"):mqttOnly()
expose registry Lua
-- expose wszystko z domyslnym ACL
exposeRegistry()

-- albo selektywnie
expose(lamp, "switch", {
    name = "Salon Kanapa",
    area = "Salon"
})

expose(temp, "temperature", {
    name = "Czujnik Salon",
    unit = "°C"
})

Kod mowi sam za siebie

Lua, kilka linijek, zero boilerplate'u.

steruj urzadzeniem basics
local lamp = _:get("CLU.DOUT1")

lamp:execute(DOUT.METHOD_SWITCH)

-- wlacz na 5 sekund, potem zgasn
lamp:execute(DOUT.METHOD_SWITCH_ON, 5000)
eventy + button events
local btn = GPIO_DIN:new("BTN1", 27)

btn:add_event(DIN.EVENT_ON_CLICK, function()
    lamp:execute(DOUT.METHOD_SWITCH)
end)

btn:add_event(DIN.EVENT_ON_LONG_PRESS, function()
    _:byTag("lights"):off()
end)
HTTP webhook api
HTTPServer:on("/webhook", "POST", function(req)
    local data = JSON.decode(req.body)

    if data.command == "lights_on" then
        runScene("lights_on")
    end

    return {status = 200, body = "OK"}
end)
metryki do Grafany plugin
local m = Plugin.get("@vclu/influx-metrics")
    :create({
        url  = "http://influxdb:8086/write?db=home",
        interval = 60,
        tags = {host = "vclu"}
    })

every(30000, function()
    local t = _:get("CLU.ANA1"):get(0)
    m:gauge("temperature", t, {room="salon"})
end)
async HTTP call http
local api = HttpClient:new()
api:setBaseUrl("https://api.example.com")
   :setBearerToken("token123")

api:asyncPOST("/sensors", {
    device = "vclu-1",
    value  = temp,
    ts     = os.time()
}, function(resp, err)
    if resp.ok then log.info("sent") end
end)
sceny + sekwencje runtime
scene("evening", function()
    _:byTag("lights"):off()
    _:get("CLU.DIMM1"):set(0, 30)
end)

expose(getScene("evening"), "scene", {
    name = "Wieczor"
})

expose to co chcesz. Reszta zostaje w srodku.

Kazde urzadzenie, kazdy plugin, kazdy endpoint — jawnie zadeklarowane uprawnienia. Zero implicit access.

sandbox

Kazdy plugin dziala w izolowanym Lua state. Brak dostepu do FS ani sieci bez jawnej deklaracji.

permission manifest

Plugin deklaruje czego potrzebuje w plugin.json. Uzytkownik akceptuje przed instalacja.

per-device ACL

MCP i REST widza tylko to co jawnie expose'ujesz. Kazde urzadzenie: osobny readonly/write.

auto-cleanup

Timery, eventy, MQTT sub — automatycznie zwalniane przy unload pluginu. Zero leakow.

Security docs →