Wszystko się zazębia: jeden panel, cały dom#

Ten tutorial nie dodaje nowego urządzenia. Pokazuje, że to, co już masz w vCLU, działa razem. Światła z Grentona, czujnik Zigbee, gniazdko Tasmota, rekuperator z wtyczki i pogoda z internetu trafiają na jeden Smart Panel, a każdy z nich to najwyżej kilka linijek.


Co zbudujemy#

Na Smart Panelu powstanie menu, w którym znajdą się:

  • Pokój: światła z Grentona, lampka podłączona przez Tasmotę i termometr.
  • Wykres temperatury: jak zmieniała się temperatura przez ostatnie 12 godzin, z danych zapisanych przez vCLU.
  • Rekuperator: bieg i temperatura zadana, których nie da się ustawić poza bezpiecznym zakresem, oraz odczyt nawiewu i czerpni.
  • Pogoda: własna strona panelu, czyli kilka linijek Lua, które rysują dane z wtyczki Weather.

Smart Panel WiFi z ekranem startowym: Światło, LED, Lampka, Menu i zegar

Czego potrzebujesz#

CoGdzie
Smart Panel sterowany przez vCLUtutorial Smart Panel
Model domu z expose (nazwa i pomieszczenie)tutorial Grenton
Opcjonalnie: Zigbee, Tasmota, rekuperator, pogodaZigbee, Tasmota, Pogoda

Każdy z tych klocków jest osobny. Brakuje któregoś? Panel po prostu go nie pokaże.


1. Pokój na panelu#

expose wystawia urządzenie na zewnątrz: do Home Assistanta, do Apple Home i na panel. Podajesz w nim nazwę urządzenia i pomieszczenie (area). Nie ma znaczenia, czy urządzenie jest z Grentona, z Tasmoty czy z Zigbee. Szczegóły w dokumentacji expose().

-- modules/dom.lua: Grenton
expose(_:get("CLU221000290.DOU4157"), "light", { name = "Światło", area = "Biuro" })
expose(_:get("CLU220000592.ONE2545"), "temperature", { name = "Temperatura", area = "Biuro" })

-- modules/tasmota/init.lua: gniazdko Tasmota przez MQTT
expose(lampka, "light", { path = "tasmota.biurko", name = "Lampka biurko", area = "Biuro" })

-- modules/zigbee/salon_czujnik.lua: czujnik z Zigbee2MQTT
expose(temp, "temperature", { path = "zigbee.salon.temperatura", name = "Temperatura (czujnik)", area = "Salon" })

Gniazdko z Tasmotą wystaw jako light (albo dodaj panel = true), bo przełączników pokój na panelu domyślnie nie pokazuje. Czujnik Zigbee z przykładu trafi do menu salonu.

Pokój na panelu to jedna linia:

local Panel = require("lib/panel")
local biuro = Panel.area("Biuro")

Menu pokoju pokazuje światła, ściemniacze, rolety i czujniki temperatury, które w expose mają pomieszczenie Biuro: tu światła z Grentona, lampkę z Tasmoty, ściemniacz i termometr. Światło przełączasz jednym OK.

Menu Biuro: LED z Grentona, lampka na Tasmocie i ściemniacz w jednym pokoju

Działa to w obie strony:

  • Gdy ktoś zgasi światło w Apple Home albo przyciskiem na ścianie, wiersz na panelu od razu pokazuje OFF. Zmiany z Grentona przychodzą dla obiektów z listy Grenton → Sync.
  • Każda zmiana trafia do historii vCLU razem z tym, kto ją zrobił: CLU panelu, dashboard, Home Assistant, Apple Home czy przycisk na ścianie.

2. Historia termometru#

Nic nie trzeba dopisywać. Wiersz z temperaturą w menu pokoju po OK otwiera wykres z ostatnich 12 godzin: słupek na godzinę, a przyciski góra i dół wybierają godzinę.

Wykres temperatury w biurze: słupek na godzinę, po prawej wartość wybranej godziny

Dane biorą się z historii, którą vCLU prowadzi dla każdego obiektu w rejestrze. Nie trzeba ani InfluxDB, ani własnych metryk. vCLU trzyma w pamięci ostatnią dobę i po restarcie wczytuje ją z dysku, więc wykres nie znika przy restarcie. Dłuższa historia to temat dla InfluxDB i Grafany.

3. Rekuperator z wtyczki#

Rekuperator Salda łączy się z vCLU przez wtyczkę. Moduł rekuperator jako jedyny korzysta z wtyczki i pilnuje bezpiecznego zakresu (biegi 1-3, temperatura 19-22 °C). Panel bierze funkcje z modułu:

local reku = require("rekuperator")

local rekuperator = Panel.menu("Rekuperator")
    :number("Bieg", reku.bieg, reku.setBieg,
        { step = 1, min = reku.BIEGI[1], max = reku.BIEGI[#reku.BIEGI], format = "%d" })
    :number("Zadana", reku.zadana, reku.setZadana,
        { step = 1, min = reku.ZADANA_MIN, max = reku.ZADANA_MAX, format = "%d C" })
    :value("Nawiew", reku.nawiew, "%.1f C")
    :value("Czerpnia", reku.zewn, "%.1f C")

Menu Rekuperator: bieg, temperatura zadana i nawiew

  • min i max biorą zakres z modułu, więc z panelu też nie da się go przekroczyć.
  • Wartość jest zapisywana raz, po OK, a nie przy każdym +.
  • Rekuperator odpowiada przez HTTP z opóźnieniem. Panel do tego czasu (najwyżej 90 s) pokazuje ustawioną wartość, a nie starą.

Te same funkcje modułu zasilają dashboard rekuperatora i eksport do InfluxDB. Panel jest kolejnym odbiorcą, nic nie jest liczone dwa razy.

4. Własna strona z pogodą#

Strona panelu to tabela z funkcją draw. Moduł pogoda z tutorialu pogody daje odczyty, a strona je rysuje:

local pogoda = require("pogoda")

local pogodaPage = { every = 300 }   -- odświeżenie co 5 minut
pogodaPage.draw = function(s)
    Panel.drawTitle(s, "Pogoda")
    local t = pogoda.temp()
    s:textCenter(14, pogoda.condition() or "--")
    s:textCenter(26, t and string.format("%.1f C", t) or "--", 2)   -- czcionka 2: większa
    s:textCenter(44, pogoda.windText() or "")
end

Własna strona z pogodą: warunki, temperatura dużą czcionką i wiatr

Na stronach menu prawy górny przycisk to zawsze „wstecz”.

5. Całość#

-- modules/panel_biuro/init.lua
local Panel = require("lib/panel")
local reku = require("rekuperator")
if type(reku) ~= "table" then reku = nil end      -- bez wtyczki moduł zwraca nil
local pogoda = require("pogoda")
if type(pogoda) ~= "table" then pogoda = nil end

if not Panel.find("PAN1085") then return nil end
Panel.lang("pl")

local menu = Panel.menu("Menu", { Panel.area("Biuro") })

if pogoda then
    local page = { every = 300 }
    page.draw = function(s)
        Panel.drawTitle(s, "Pogoda")
        local t = pogoda.temp()
        s:textCenter(14, pogoda.condition() or "--")
        s:textCenter(26, t and string.format("%.1f C", t) or "--", 2)
        s:textCenter(44, pogoda.windText() or "")
    end
    menu:item("Pogoda", page)
end

if reku then
    menu:submenu(Panel.menu("Rekuperator")
        :number("Bieg", reku.bieg, reku.setBieg,
            { step = 1, min = reku.BIEGI[1], max = reku.BIEGI[#reku.BIEGI], format = "%d" })
        :number("Zadana", reku.zadana, reku.setZadana,
            { step = 1, min = reku.ZADANA_MIN, max = reku.ZADANA_MAX, format = "%d C" })
        :value("Nawiew", reku.nawiew, "%.1f C")
        :value("Czerpnia", reku.zewn, "%.1f C"))
end

local M = {}
M.panel = Panel.new("PAN1085")
    :home(Panel.corners{
        tl = { "Swiatlo", "CLU221000290.DOU4157" },
        tr = { "LED", "CLU221000290.DOU3328" },
        bl = { "Lampka", "tasmota.biurko" },
        br = { "Menu", function(p) p:open(menu) end },
        clock = true,
    })
    :sleepAfter(30)
return M

Róg sprawdza ścieżkę urządzenia przy starcie modułu, więc moduł tasmota musi się załadować wcześniej: w modules/init.lua require("tasmota") stoi wyżej niż require("panel_biuro").

Moduł panelu nie wie, skąd pochodzą urządzenia. Grenton, Tasmota, Zigbee, rekuperator i pogoda spotykają się w rejestrze, a panel tylko je pokazuje. Na tych samych danych zbudujesz dashboard albo automatykę, a agent AI może o nie zapytać.