programmable device runtime
Inspect. Execute. Control.
Raspberry Pi. Docker. Linux arm64/amd64.
curl -sSL https://vclu.pl/install.sh | bash
Wymaga bash i curl. Wykrywa platforme automatycznie.
Dokumentacja →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.
Piszesz logike w Lua. Decydujesz co wystawic przez MQTT, REST, WebSocket albo MCP. Reszta zostaje w srodku. Bez clouda, bez konta, bez subskrypcji.
Runtime z nowoczesnym API, event-driven architektura i natywnym wsparciem dla AI.
vclu wystawia standardowy MCP server. Kazdy LLM — Claude, GPT, Gemini — widzi urzadzenia jako structured tools i moze nimi sterowac.
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.
{
"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"]
}
}
-- 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 wszystko z domyslnym ACL exposeRegistry() -- albo selektywnie expose(lamp, "switch", { name = "Salon Kanapa", area = "Salon" }) expose(temp, "temperature", { name = "Czujnik Salon", unit = "°C" })
Lua, kilka linijek, zero boilerplate'u.
local lamp = _:get("CLU.DOUT1") lamp:execute(DOUT.METHOD_SWITCH) -- wlacz na 5 sekund, potem zgasn lamp:execute(DOUT.METHOD_SWITCH_ON, 5000)
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)
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)
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)
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)
scene("evening", function() _:byTag("lights"):off() _:get("CLU.DIMM1"):set(0, 30) end) expose(getScene("evening"), "scene", { name = "Wieczor" })
Kazde urzadzenie, kazdy plugin, kazdy endpoint — jawnie zadeklarowane uprawnienia. Zero implicit access.
Kazdy plugin dziala w izolowanym Lua state. Brak dostepu do FS ani sieci bez jawnej deklaracji.
Plugin deklaruje czego potrzebuje w plugin.json. Uzytkownik akceptuje przed instalacja.
MCP i REST widza tylko to co jawnie expose'ujesz. Kazde urzadzenie: osobny readonly/write.
Timery, eventy, MQTT sub — automatycznie zwalniane przy unload pluginu. Zero leakow.