Historia zmian#

vCLU zapisuje każdą zmianę stanu w domu: co się zmieniło, z jakiej wartości na jaką, kiedy i kto to zrobił. Dzięki temu pytanie „czemu światło zapaliło się o 3 w nocy?” ma odpowiedź, a automatyka może odróżnić swoje działanie od ręcznego.

Co jest zapisywane#

Każdy wpis to jedna zmiana:

PoleOpis
pathObiekt, np. CLU220000592.DOU4992 albo satel.zone.2
old, valueWartość przed i po zmianie
atMsCzas zmiany (Unix, milisekundy)
originKtórędy przyszła zmiana: grenton, physical (np. Satel), web, mcp, mqtt, homekit, api, lua, system
actorKto ją zrobił (tabela niżej)
correlationIdWspólny identyfikator zmian wywołanych jedną akcją, np. sceną

Jedna prawdziwa zmiana daje jeden wpis, a ustawienie tej samej wartości nie jest zmianą. Po starcie albo reloadzie pierwszy odczyt równy ostatniej znanej wartości też nie jest zmianą. Jeśli wartość jest inna (np. ktoś przełączył światło, gdy vCLU nie działało), zapisuje się raz jako zmiana z origin = system i sprawcą system:startup.

Kto to zrobił: actor#

Źródło zmianyactor
Raport z CLU (włącznik, wejście, logika CLU)device:<CLU>
Skrypt wysłany przez CLU do vCLUdevice:<nazwa CLU>
Satel (czujka, klawiatura)device:satel
Wejście GPIOdevice:gpio
Panel vCLU (jedno konto administratora)user:<login>, user:local przy wyłączonym logowaniu
Dashboard otwarty publicznie lub przez PIN, bez logowaniadashboard:<id> (z zalogowanej przeglądarki: user:<login>)
Agent AI przez MCPagent:<id bota>
Klucz APIservice:web-api
Endpoint HTTP napisany w Luaservice:http
Home Assistantservice:home-assistant
Inne komendy MQTTservice:mqtt
HomeKitservice:homekit
Automatyka z runAsautomation:<id>
Kod uruchamiany przy ładowaniu modułu (i jego timery)automation:module:<plik>
Handler bez runAssprawca zdarzenia, które go wywołało (np. agent:…), w innym wypadku automation:unknown
Kod uruchamiany przy ładowaniu pluginuplugin:<id>
Zmiana wykryta po starciesystem:startup

Polecenie switchOn(ms) na wyjściu Grentona przypisuje także samoczynne wyłączenie przez CLU temu, kto je wydał. Automatyka, która zapaliła światło na minutę, rozpozna więc zgaszenie po tej minucie jako swoje, a nie jako ręczne.

Gdzie leży#

  • W pamięci: domyślnie ostatnie 24 h, do 10 000 zmian na obiekt i 50 000 łącznie (około 11 MB przy pełnym limicie).
  • Na dysku: jeden plik JSONL na dzień w katalogu danych, zapis co 5 minut i przy wyłączaniu, żeby oszczędzać kartę SD. Domyślnie 90 dni albo 1 GB.
  • Po restarcie: ostatnie 24 h wczytują się z dysku, więc lastChange() działa od razu, bez fałszywych zmian 0 → wartość.

Limity zmienisz na karcie Ustawienia na stronie Narzędzia → Historia i audyt. Działają od razu. Włączenie lub wyłączenie samej historii albo zapisu na dysk wymaga restartu.

W Lua#

local light = _:get("CLU220000592.DOU4992")

local last = light:lastChange()   -- {value, old, atMs, origin, actor, correlationId} albo nil
print(light:since())              -- ile sekund w obecnym stanie
for i, e in ipairs(light:history(5)) do   -- 5 ostatnich, najnowsza pierwsza
    print(e.value, e.actor)
end

Odróżnienie własnych zmian od cudzych i działanie pod własnym imieniem:

-- Ktoś (nie my) zgasił światło: przerwa na 10 minut
StateBus:watch("CLU220000592.DOU4992", function(e)
    if e.actor == "automation:wiatrolap" then return end
    pauseUntil = os.time() + 600
end, { from = 1, to = 0 })

-- Zmiany zrobione w tej funkcji mają sprawcę automation:wiatrolap
StateBus:watch("satel.zone.2", function()
    _:get("CLU220000592.DOU4992"):switchOn(60 * 1000)
end, { to = 1, runAs = "automation:wiatrolap" })

runAs zmienia tylko sprawcę (actor). origin zostaje z wywołującego zdarzenia, np. physical dla czujki Satela.

Obiekt, którego zmian nie chcesz zapisywać, wystawiasz z history = false. Czujnik, który szumi, z filter. Oba opisane są w expose API.

W panelu#

Narzędzia → Historia i audyt: przeglądanie zmian z filtrami po obiekcie, źródle, sprawcy, tekście i czasie. Na tej samej stronie widać zużycie pamięci i dysku, błędy zapisu i ustawienia limitów.

REST API#

GET /api/history?path=CLU220000592.DOU4992&since=2026-10-08T21:00:00%2B02:00&limit=50

Filtry: path, origin, actor, q (tekst), since, until (RFC 3339 albo Unix w ms), limit (domyślnie 100, maks. 1000), cursor (wartość next z poprzedniej odpowiedzi). Status i ustawienia: GET /api/history/status, GET/PUT /api/history/settings. Zmiana ustawień wymaga zalogowania w panelu, klucz API dostaje 403.

Agent AI: jak żyje dom#

Boty MCP czytają historię narzędziem vclu_device_history z tymi samymi filtrami (path, origin, actor, query, since, until, limit - domyślnie 50, cursor). Bot widzi tylko obiekty, na które pozwala mu jego profil dostępu. Dzięki temu można zapytać dom, co się w nim działo:

Ty:  Jak wyglądał wczorajszy wieczór w domu?

AI:  → vclu_device_history(since="2026-10-08T17:00:00+02:00", until="2026-10-09T01:00:00+02:00")

     Wczoraj wieczorem:
     • 17:41 - alarm rozbrojony (Satel), minutę później ruch w wiatrołapie:
       ktoś wrócił do domu.
     • 17:44-23:10 - kuchnia i salon: światła przełączane na CLU (włączniki),
       najdłużej świeciła sofa (4 h 20 min).
     • 19:30 - trzy światła w kuchni zmienione jedną akcją z tabletu
       (dashboard:kuchnia).
     • 22:05 - tryb nocny włączony przez automatykę.
     • 23:12 - ostatnie światło zgaszone, 23:15 alarm uzbrojony.
     • W nocy: dwa razy ruch w wiatrołapie (01:20, 03:05), światło zapaliła
       automatyka na minutę i samo zgasło.

To przykład. Agent widzi tylko obiekty, wartości i sprawców w formie z tabeli wyżej. Imion domowników historia nie zapisuje, a „ktoś wrócił do domu” to wniosek agenta z kolejności zdarzeń.