Smart Panel sterowany przez vCLU#
Grenton Smart Panel ma cztery przyciski i ekran, którym steruje się z Lua. vCLU opakowuje to w nowoczesne API Lua: panel przez swoje CLU wysyła naciśnięcia przycisków do vCLU, a ekran, menu i reakcje opisuje jeden moduł. Pokoje i urządzenia biorą się z modelu domu, tego samego, który widzą Home Assistant i HomeKit, więc nowe światło pojawia się na panelu samo. Kod napisany w ten sposób można wykorzystać ponownie: ten sam moduł obsłuży drugi panel albo inny dom, bo pokoje bierze z modelu domu, a nie z identyfikatorów obiektów.
Co zbudujemy#
- ekran startowy z zegarem i czterema przyciskami w rogach: dwa światła, roleta i wejście do menu
- menu z pokojami: światła, ściemniacze, rolety, temperatura z wykresem z 12 godzin
- bramę, przy której widać, kiedy ostatnio się otwierała
- blokadę do mycia szyby i powrót na ekran startowy, gdy nikt nie dotyka panelu
- komunikat na panelu wysyłany z automatyki
Przykład dotyczy panelu Smart Panel WiFi, który dla Object Managera jest osobnym CLU. Panel na magistrali TF-Bus ma ten sam moduł, więc moduł Lua jest ten sam. Różni się tylko tym, na którym CLU podpinasz zdarzenia.
Czego potrzebujesz#
| Co | Uwagi |
|---|---|
| vCLU w instalacji Grenton | po tutorialu Grenton, z biblioteką lib/panel |
| Smart Panel albo Smart Panel WiFi | dodany w Object Managerze do projektu |
Model domu z expose | urządzenia z nazwą i pomieszczeniem (area), jak dla HomeKit |
Jak to działa#
flowchart LR
P[Smart Panel] -- "OnClick, OnLongPress" --> C[CLU panelu]
C -- "click('PAN1085', 'btn1')" --> V[vCLU: moduł z lib/panel]
V -- "ramki ekranu" --> C
C --> PPanel nic nie pamięta i nic nie liczy. Każde naciśnięcie trafia do vCLU, które decyduje, co dalej, i odsyła cały ekran. Ekran idzie krótkimi wiadomościami w odstępach, tak jak CLU panelu je przyjmuje. Gdy CLU nie potwierdzi ekranu, vCLU wyśle go jeszcze raz.
Na stronach menu rogi znaczą zawsze to samo:
| Róg | Przycisk | Co robi |
|---|---|---|
| lewy górny | btn1 | w górę (+ przy edycji) |
| lewy dolny | btn3 | w dół (- przy edycji) |
| prawy górny | btn2 | wstecz (<) |
| prawy dolny | btn4 | OK (>) |
Biblioteka rysuje podpowiedzi przy rogach i zapala diody tylko tych przycisków, które na danym ekranie coś robią. W menu góra i dół nie mają podpowiedzi: widać je po diodach, a kursor to > przy wierszu.
Krok 1: Import panelu#
Zaimportuj w vCLU plik OM z CLU, na którym jest panel (Grenton → Import OM). Dla Smart Panel WiFi to plik samego panelu, bo panel jest swoim własnym CLU.
Import tworzy obiekt panelu (RemotePanel) z przypiętymi przyciskami, a do tego termometr i czujnik jasności panelu. Identyfikator panelu znajdziesz w pliku om.lua zaimportowanego CLU, np. PAN1085. Panel i przyciski nie trafiają do Home Assistanta ani HomeKita.
Krok 2: Zdarzenia w Object Managerze#
W Object Managerze potrzebne są dwie rzeczy.
Skrypt click na vCLU. Na vCLU (w OM to CLU o nazwie, którą mu nadałeś, u nas Vclu) utwórz skrypt click z dwoma parametrami tekstowymi: panel i input. Treść zostaw pustą.
Dlaczego pusty? Object Manager wgrywa ten pusty skrypt do vCLU jako funkcję click. Moduł panelu przy starcie (Panel.new) nadpisuje tę funkcję swoją, która przekazuje naciśnięcie do biblioteki. Dlatego pusty skrypt wystarczy. Musi tylko istnieć, żeby OM pozwolił go wywołać z innego CLU.
Zdarzenia przycisków. Na CLU z panelem, we właściwościach każdego z 4 przycisków strony 1, na zakładce Zdarzenia przypisz komendy:
- OnClick:
Vclu->click("PAN9852","btn1") - OnLongPress:
Vclu->click("PAN9852","btn1:long")

Pierwszy argument to identyfikator przycisku albo panelu (tu PAN9852: przycisk 1, pole Id w OM), drugi to nazwa wejścia: btn1 … btn4, a dla długiego naciśnięcia btn1:long … btn4:long. vCLU sam znajdzie panel po identyfikatorze przycisku.
Wystarczą OnClick i OnLongPress. OnClick nie przychodzi po długim naciśnięciu, więc oba zdarzenia się nie mieszają. Pozostałe 12 obiektów przycisków należy do wbudowanych stron Grentona, których vCLU nie używa.
Kilka paneli w domu. Jeden pusty skrypt click na vCLU obsługuje wszystkie panele. Przyciski każdego panelu wołają tę samą komendę, ale z identyfikatorem swojego przycisku jako pierwszym argumentem, a vCLU po nim znajduje właściwy panel. Na vCLU każdy panel ma swój moduł ze swoim Panel.new("PAN…") i własnym stanem (otwarte menu, kursor), a wspólne menu, np. pokoje, możesz trzymać w jednym miejscu.
Wyślij konfigurację do CLU. Od tej pory każde naciśnięcie widać w logach vCLU jako skrypt Lua od CLU panelu.
Krok 3: Pokoje w modelu domu#
Menu pokoi biblioteka buduje z expose, tak jak Home Assistant i HomeKit. Nazwa (name) i pomieszczenie (area) zwykle już są:
expose(_:get("CLU220000592.DOU0789"), "light", { name = "Kanapa", area = "Salon" })
expose(_:get("CLU220000592.DOU5834"), "light", { name = "Stół", area = "Salon" })
expose(_:get("CLU220000592.ROL4101"), "cover", { name = "Roleta", area = "Salon" })
expose(_:get("CLU220000592.ONE6305"), "temperature", { name = "Temperatura", area = "Salon" })Nie wszystko z domu musi trafić na panel. Domyślnie pokój na panelu pokazuje światła, ściemniacze, rolety, temperaturę i wilgotność. Przełączniki (pompy, domofon, otwieranie drzwi) i czujniki alarmu zostają poza panelem. Wyjątki ustawiasz opcją panel przy expose, tak jak homekit i mqtt:
-- druga temperatura w salonie: w Home Assistant tak, na panelu nie
expose(_:get("zigbee.salon.temperatura"), "temperature", { name = "Temp. Zigbee", area = "Salon", panel = false })
-- przełącznik, który jednak ma być na panelu
expose(_:get("CLU220000592.DOU7823"), "switch", { name = "Domofon", area = "Wiatrołap", panel = true })Home Assistant i HomeKit opcji panel nie widzą.
Dla zaawansowanych: zamiast całego pokoju z
Panel.areamożesz wybrać dokładnie te urządzenia, które mają być na panelu.:addsam dobiera wiersz do rodzaju urządzenia (światło przełącza, roleta otwiera swoją stronę, temperatura wykres), a bez etykiety bierze nazwę zexpose. Urządzenia, którego nie ma w rejestrze, po prostu nie doda.local salon = Panel.menu("Salon") :add("CLU220000592.DOU0789") -- nazwa z expose („Kanapa”) :add("Stół", "CLU220000592.DOU5834") -- własna etykieta :add("CLU220000592.ONE6305") -- temperatura, OK otwiera wykresMożna też zawęzić pokój, np.
Panel.area("Salon", { kinds = { "light" } })(tylko światła) alboPanel.area("Salon", { skip = { "zigbee.salon.temperatura" } })(bez jednego urządzenia), i dopisać do niego własne wiersze:Panel.area("Biuro"):add("Lampka", "tasmota.biurko"). Przy kilku panelach, z których każdy pokazuje co innego, ręczna lista w module panelu jest czytelniejsza niż opcjapanelwexpose.
Krok 4: Moduł panelu#
Jeden panel to jeden moduł. W edytorze vCLU utwórz modules/panel_salon/init.lua:
-- modules/panel_salon/init.lua
-- Smart Panel w salonie (PAN1085), rysowany przez vCLU.
local Panel = require("lib/panel")
-- Bez importu panelu moduł nic nie robi, a reszta domu działa dalej.
if not Panel.find("PAN1085") then
print("[PANEL-SALON] brak panelu PAN1085 (import OM?), moduł pominięty")
return nil
end
Panel.lang("pl") -- teksty biblioteki po polsku: Otworz, Zamknij, Czyszczenie, brak zmian...
local M = {}
M.panel = Panel.new("PAN1085")
:home(Panel.menu("Salon", { Panel.area("Salon") }))
:sleepAfter(30)
return MI dopisz go w modules/init.lua, bo katalogi w modules/ nie ładują się same:
require("panel_salon") -- modules/panel_salon/init.luaZapisz i kliknij Reload. Panel pokazuje menu z jednym pokojem. OK otwiera pokój: światła, roletę i temperaturę, z nazwami jak w Apple Home.
┌────────────────────────┐
│ Salon <│
│ Kanapa ON │
│>Stol OFF │
│ Roleta 70% │
│ >│
└────────────────────────┘Każde urządzenie dostaje wiersz według tego, czym jest:
| Urządzenie | Wiersz | OK |
|---|---|---|
| światło, przełącznik | ON/OFF | przełącza |
| ściemniacz | jasność w % | edycja: +/- co 10%, OK zapisuje |
| roleta | pozycja, ^ albo v w ruchu | strona rolety: góra otwiera, dół zamyka, OK zatrzymuje |
| temperatura, wilgotność | wartość | wykres z ostatnich 12 godzin |
| czujnik dwustanowy | stan | lista ostatnich zmian |
Wiersze same się odświeżają: gdy ktoś zgasi Kanapę w HomeKit, na panelu od razu widać OFF. Pokój jest budowany przy każdym otwarciu, więc nowe światło dopisane w modelu domu pojawi się na panelu bez zmian w module.
Drugi argument Panel.menu to lista podmenu. Pokój bez niczego dla panelu menu pomija.
Krok 5: Ekran startowy z przyciskami#
Z menu do światła są co najmniej dwa naciśnięcia. Na co dzień wygodniej, gdy każdy róg od razu coś włącza. Panel.corners robi z ekranu cztery przyciski:
local menu = Panel.menu("Dom", { Panel.area("Salon"), Panel.area("Kuchnia") })
M.panel = Panel.new("PAN1085")
:home(Panel.corners{
tl = { "Kanapa", "CLU220000592.DOU0789" },
tr = { "Stol", "CLU220000592.DOU5834" },
bl = { "Roleta", "CLU220000592.ROL4101" },
br = { "Menu", function(p) p:open(menu) end },
clock = true,
})
:sleepAfter(30)┌────────────────────────┐
│Kanapa Stol│
│ │
│ 18:42 │
│ │
│Roleta Menu│
└────────────────────────┘Róg z urządzeniem robi to, co jego wiersz w menu: światło przełącza, roleta otwiera się albo zamyka. Róg z funkcją wywołuje fn(panel). Dioda przy przycisku świeci, gdy róg coś robi. Ścieżka, której nie ma w rejestrze, zostawia sam napis i zgaszoną diodę. Róg zacznie działać po przeładowaniu (Reload), gdy urządzenie będzie już w rejestrze.
clock = true rysuje godzinę na środku i odświeża ją co minutę. sleepAfter(30) wraca na ekran startowy po 30 sekundach bez dotyku. Sam ekran panel wygasza po swoim czasie zbliżeniowym (ProximityTimeout w OM) i włącza, gdy ktoś podejdzie.
Krok 6: Brama, piętra i rzeczy dodane ręcznie#
Do menu z pokoi możesz dokładać wiersze ręcznie. Bramę pokazuje wiersz :gate: czy jest otwarta i kiedy się zmieniała. Źródłem jest wejście dwustanowe, np. krańcówka:
local bramy = Panel.menu("Bramy")
:gate("Brama", "CLU220000592.DIN0095", { open = 0 }) -- krańcówka „zamknięta”: 0 = otwarta
:gate("Furtka", "satel.zone.11") -- strefa alarmu: 1 = otwartaWiersz pokazuje OTWARTA albo zamk., a OK otwiera listę ostatnich zmian, od najnowszej. Z panelu w kuchni widać, że brama otwierała się o 7:40 i zamknęła o 7:41.
Gdy pokoi jest dużo, pogrupuj je piętrami. Najprościej ręcznie:
local parter = Panel.menu("Parter", {
Panel.area("Hol"),
Panel.area("Salon"),
Panel.area("Kuchnia"),
Panel.area("Łazienka dół", { title = "Lazienka" }),
})Albo z modelu domu: dopisz przy expose piętro, np. { name = "Kanapa", area = "Salon", floor = "Parter" }, a menu piętra złoży się samo:
local parter = Panel.floor("Parter", { order = { "Hol", "Salon", "Kuchnia" } })order ustala kolejność pierwszych pokoi, reszta idzie alfabetycznie. Jeden pokój pod dwiema nazwami w modelu domu (np. światła w „Pokój środkowy”, a temperatura w „Pokój Tadeusza”) łączysz w Panel.area({ "Pokój środkowy", "Pokój Tadeusza" }, { title = "Pokoj srodek" }).
Swoje wartości dopisujesz tak jak w każdym menu:
local zadana = VirtualNumber:new("zadana_salon", { initial = 21, persist = true })
local salon = Panel.area("Salon")
:number("Zadana", function() return zadana:getValue() end,
function(v) zadana:setValue(v) end, { step = 0.5, min = 19, max = 23, format = "%.1f" })min i max trzymają wartość w bezpiecznym zakresie, a zapis idzie raz, po OK, a nie przy każdym +.
Krok 7: Całość#
-- modules/panel_salon/init.lua
local Panel = require("lib/panel")
if not Panel.find("PAN1085") then
print("[PANEL-SALON] brak panelu PAN1085 (import OM?), moduł pominięty")
return nil
end
Panel.lang("pl")
local bramy = Panel.menu("Bramy")
:gate("Brama", "CLU220000592.DIN0095", { open = 0 })
:gate("Furtka", "satel.zone.11")
local menu = Panel.menu("Dom", {
Panel.area("Salon"),
bramy,
Panel.floor("Parter", { order = { "Hol", "Salon", "Kuchnia" } }),
Panel.floor("Piętro"),
Panel.area("Ogród"),
})
menu:action("Czyszczenie", Panel.cleaning(30))
local M = {}
M.panel = Panel.new("PAN1085")
:home(Panel.corners{
tl = { "Kanapa", "CLU220000592.DOU0789" },
tr = { "Stol", "CLU220000592.DOU5834" },
bl = { "Roleta", "CLU220000592.ROL4101" },
br = { "Menu", function(p) p:open(menu) end },
clock = true,
})
:sleepAfter(30)
return MCzyszczenie blokuje panel na 30 sekund: odlicza czas, wyłącza buzzer i ignoruje dotyk, żeby mycie szyby niczego nie przełączyło. Po odliczeniu wraca poprzedni ekran.
Dalej: komunikat z automatyki#
Moduł zwraca panel, więc każda automatyka może coś na nim wyświetlić. Komunikat przykrywa bieżący ekran, a OK, wstecz albo upływ czasu go zamyka:
local salon = require("panel_salon")
EventBus:on("pralka:koniec", function()
-- bez panelu moduł zwraca nil, a require oddaje wtedy true
if type(salon) == "table" then
salon.panel:notify("Pranie gotowe", { beep = true, seconds = 60 })
end
end)Dalej: agent pisze panel#
Agent AI w vCLU zna lib/panel (narzędzie vclu_runtime_schema) i może sam zbudować moduł panelu z Twojego modelu domu. Zanim go wdroży, sprawdza go w sandboxie: w scenariuszu vclu_draft_test kroki {"click": "PAN1085", "input": "btn4"} naciskają przyciski, a ekrany, które poszłyby do panelu, widać w clu_calls. Dopiero potem wdraża.
Ograniczenia panelu#
- Ekran ma 128 × 64 punkty, jest czarno-biały, a czcionki mają 10, 14 i 28 punktów wysokości.
- Jeden napis to najwyżej 15 znaków. Polskie litery zamieniają się na łacińskie (
ł→l), bo panel ich nie ma. - Panel nie wypełnia prostokątów, więc tekstu nie da się odwrócić. Kursor w menu to
>. - Rysuj tylko przez
lib/panel. Biblioteka wysyła ekran krótkimi wiadomościami z odstępami, tak jak CLU panelu je przyjmuje. Nie wysyłaj do panelu własnych skryptów w pętli.
Pełny opis biblioteki, własne strony i ustawienia znajdziesz na stronie Smart Panel.