Grenton Smart Panel#
vCLU może przejąć panel Grenton Smart Panel w całości. Panel przez swoje CLU wysyła naciśnięcia przycisków do vCLU, a cały ekran, czyli menu, strony, edycja wartości i ekran startowy, jest w module Lua na vCLU. Panel nic nie pamięta: pokazuje to, co narysuje vCLU.
Krok po kroku, na przykładzie jednego panelu: tutorial Smart Panel.
Sprawdzone na Smart Panel WiFi (moduł panelu fv05). Panel na magistrali TF-Bus ma ten sam moduł i działa tak samo, ale nie był jeszcze testowany na sprzęcie.
Jak to działa#
flowchart LR
P[Smart Panel] -- "OnClick, OnLongPress" --> C[CLU panelu]
C -- "click('PAN1085', 'btn1')" --> V[vCLU: lib/panel]
V -- "ramki: PrintText, DrawLine, DisplayContent" --> C
C --> P- Panel to kwadrat z czterema przyciskami w rogach i ekranem 128 × 64 pośrodku. Każdy ekran mówi, co robi każdy róg. Biblioteka rysuje podpowiedź przy przycisku i zapala diody tylko tych przycisków, które coś robią.
- Na stronach menu rogi znaczą zawsze to samo: w górę i w dół po lewej, wstecz w prawym górnym rogu (
<), OK w prawym dolnym (>). - Ekran idzie do CLU krótkimi wiadomościami z odstępami, tak jak CLU panelu je przyjmuje. Każda ramka rysuje cały ekran od nowa. Jeśli CLU nie potwierdzi ostatniej wiadomości, vCLU wysyła ramkę jeszcze raz. Kilka zmian naraz daje jedną ramkę.
Krok 1: import z Object Managera#
Zaimportuj 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 OM | Klasa | Obiekt w vCLU |
|---|---|---|
PANEL | 29 | RemotePanel, z przypiętymi przyciskami |
PANEL_BUTTON | 30 | RemotePanelButton (dioda, etykieta) |
PANELSENSTEMP | 20 | czujnik temperatury, jak każdy inny |
PANELSENSLIGHT | 21 | czujnik jasności |
Panel i przyciski nie trafiają do Home Assistanta ani HomeKita. Termometr i czujnik jasności panelu trafiają tam jak inne czujniki.
Identyfikator panelu (np. PAN1085) znajdziesz w om.lua zaimportowanego CLU. Nazwy, które Object Manager nadaje sam, zaczynają się od x (np. x250002119_PANEL1). Biblioteka znajduje panel po identyfikatorze i po nazwie: Panel.find("PAN1085").
Krok 2: zdarzenia w Object Managerze#
Na vCLU (w OM to CLU o nazwie, którą nadałeś vCLU) utwórz skrypt click z dwoma parametrami tekstowymi, panel i input, i zostaw go pustym. Object Manager wgrywa go do vCLU jako funkcję click, a Panel.new nadpisuje ją funkcją vCLU (RemotePanel.click). Dlatego pusty skrypt wystarczy: musi tylko istnieć, żeby dało się go wywołać z innego CLU.
Na CLU z panelem podepnij pod przyciski wywołanie skryptu click na vCLU, np. Vclu->click("PAN9852","btn1") (PAN9852 to identyfikator przycisku, pole Id w OM):

| Obiekt | Zdarzenie | input |
|---|---|---|
| przycisk 1-4 (strona 1) | OnClick | btn1 … btn4 |
| przycisk 1-4 | OnLongPress | btn1:long … btn4:long |
| przycisk 1-4 | OnHold | btn1:hold … (opcjonalnie: szybka zmiana wartości przy przytrzymaniu) |
| panel | OnDisplayOn | wake (opcjonalnie: przerysowanie po włączeniu ekranu) |
| panel | OnProximityDetect | proximity (opcjonalnie) |
| panel | OnGestureUp/Down/Left/Right | swipe:up … (opcjonalnie) |
Jako panel podaj identyfikator lub nazwę panelu albo przycisku. vCLU znajdzie panel po każdym z nich. Dlatego jeden skrypt click obsługuje wszystkie panele w domu: każdy panel ma na vCLU swój moduł z Panel.new("PAN…") i własny stan.
Wystarczą 4 przyciski strony 1. Pozostałe 12 obiektów przycisków należy do wbudowanych stron Grentona, których vCLU nie używa. OnClick nie przychodzi po długim naciśnięciu, więc OnClick i OnLongPress się nie mieszają. OnShortPress nie jest potrzebne.
Żeby nie powtarzać nazwy panelu w każdym zdarzeniu, możesz założyć na CLU panelu skrypt pomocniczy:
-- skrypt na CLU z panelem: sp(input)
CLU221064687->click("PAN1085", input) -- CLU221064687: identyfikator vCLU w Twoim projekcie OMi w zdarzeniach wołać sp("btn1").
Krok 3: moduł Lua#
Jeden panel to jeden moduł, np. 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 M = {}
M.panel = Panel.new("PAN1085")
:home(Panel.menu("Dom", {
Panel.area("Salon"),
Panel.area("Kuchnia"),
}):action("Czyszczenie", Panel.cleaning(30)))
:sleepAfter(30)
return MKatalog z modułem dopisz w modules/init.lua: require("panel_salon").
Menu na ekranie:
┌────────────────────────┐
│ Dom <│ prawy górny: wstecz
│>Salon >│
│ Kuchnia >│
│ Czyszczenie │
│ >│ prawy dolny: OK
└────────────────────────┘Przyciski po lewej przesuwają kursor (>). Nie mają podpowiedzi, za to świecą ich diody.
Panel#
Panel.new("PAN1085") -- przejmuje panel z importu OM (identyfikator, nazwa albo obiekt)
:buttons{ tl = "btn1", tr = "btn2", bl = "btn3", br = "btn4" } -- który przycisk w którym rogu (to jest domyślne)
:back("tr") -- róg, który zawsze cofa (domyślnie prawy górny)
:home(page) -- strona startowa
:idle(page) -- ekran spoczynkowy (po sleepAfter i po „wstecz” na stronie startowej)
:sleepAfter(sekundy) -- bez dotyku przez tyle sekund: ekran spoczynkowy, a bez niego startowy
:name("bramy", page) -- strona pod nazwą, dla panel:open("bramy")
:onGesture("swipe:right", fn) -- gest: fn(panel)
:onProximity(fn) -- ktoś podszedł: fn(panel)Zostaw wstecz w prawym górnym rogu: wtedy każdy panel w domu działa tak samo. Gdy zmienisz róg wstecz, biblioteka sama przestawi pozostałe: druga kolumna to góra i dół, ostatni róg to OK.
Metody w trakcie działania, np. z automatyki:
| Metoda | Co robi |
|---|---|
panel:open(page) | otwiera stronę na wierzchu (albo stronę nazwaną przez :name) |
panel:back() | zamyka bieżącą stronę |
panel:home() | zamyka wszystko i wraca na stronę startową |
panel:notify(tekst, {beep, seconds, title}) | komunikat na ekranie; OK, wstecz albo upływ czasu go zamyka |
panel:redraw() | przerysowanie ekranu |
panel:debounce(klucz, sekundy, fn) | fn raz, tyle sekund po ostatnim wywołaniu |
-- z dowolnej automatyki
local salon = require("panel_salon")
if type(salon) == "table" then
salon.panel:notify("Pranie gotowe", { beep = true, seconds = 60 })
endEkran gaśnie po czasie ustawionym w OM (ProximityTimeout) i włącza się, gdy ktoś podejdzie.
Menu#
Panel.menu(tytuł, podmenu) to lista wierszy. Góra i dół przesuwają kursor, OK otwiera albo edytuje wiersz, wstecz wychodzi z menu lub anuluje edycję. Drugi argument to lista menu, które stają się podmenu. Menu bez wierszy jest pomijane.
| Wiersz | Co robi |
|---|---|
:add(etykieta, obiekt|ścieżka, opcje) | wiersz dobrany do urządzenia, patrz Urządzenia |
:toggle(etykieta, obiekt|ścieżka) | ON/OFF; OK przełącza |
:dimmer(etykieta, obiekt|ścieżka, {step}) | jasność w %, 0 to OFF; edycja co 10% |
:cover(etykieta, obiekt|ścieżka) | pozycja rolety, ^/v w ruchu; OK otwiera stronę rolety |
:gate(etykieta, obiekt|ścieżka, {open}) | brama, drzwi, okno: OTWARTA/zamk.; OK pokazuje ostatnie zmiany |
:thermostat(etykieta, obiekt|ścieżka, {readonly, step, min, max}) | termostat Grentona: temperatura; OK otwiera stronę z zadaną, patrz Termostat |
:value(etykieta, obiekt|fn, format, strona) | tylko odczyt; ze stroną (np. Panel.chart) OK ją otwiera |
:number(etykieta, get, set, {step, min, max, format, watch}) | edycja w miejscu: OK, +/-, OK zapisuje raz |
:choice(etykieta, get, set, {options, watch}) | wybór z listy, jak number |
:item(etykieta, strona) / :submenu(etykieta, menu) | otwiera stronę lub podmenu; :submenu(menu) bierze etykietę z nazwy menu |
:action(etykieta, fn|strona, {confirm}) | uruchamia fn(panel) (albo otwiera stronę); confirm najpierw pyta |
Ścieżka to adres z rejestru, np. "CLU220000592.DOU0789" albo "satel.zone.11". Ścieżka, której nie ma w rejestrze, nie dodaje wiersza i zostawia ostrzeżenie w logach. Moduł nie wywraca się, gdy urządzenie zniknie z importu.
min i max ograniczają to, co da się wybrać na panelu, więc wiersz menu wystarczy, żeby trzymać urządzenie w bezpiecznym zakresie (np. rekuperator: bieg 1-3, temperatura 19-22 °C). Wartość jest zapisywana raz, po OK, a nie przy każdym +.
format to wzór string.format ("%.1f", "%d%%") albo funkcja fn(v) zwracająca tekst. Długa etykieta jest przycinana tak, żeby nie wchodziła na wartość.
Wiersze z obiektem odświeżają się same, gdy obiekt się zmieni, np. gdy ktoś zgasi światło w HomeKit. Roleta odświeża się też w trakcie ruchu.
Urządzenia#
Wiersz według rodzaju urządzenia#
:add dobiera wiersz do tego, czym jest obiekt: według typu z expose, a bez expose według klasy Grentona. Bez etykiety bierze nazwę z expose.
| Rodzaj | Wiersz | OK |
|---|---|---|
light, switch, lock | ON/OFF | przełącza |
dimmer (i światło z jasnością) | jasność w % | edycja co 10% |
cover | pozycja | strona rolety: góra otwiera, dół zamyka, OK zatrzymuje |
| termostat Grentona | temperatura | strona termostatu |
| czujnik dwustanowy | ON/OFF | ostatnie zmiany |
temperature, humidity, illuminance, liczba | wartość | wykres z 12 godzin |
Opcja :add | Opis |
|---|---|
kind | rodzaj, gdy ma być inny niż z expose |
format | format wartości |
chart = false, history = false | wartość bez wykresu, stan bez listy zmian |
readonly = true | sam stan, bez przełączania; expose(..., { readonly = true }) ustawia to samo |
Wykres i lista zmian biorą dane z historii vCLU w pamięci, czyli mniej więcej z ostatniej doby.
Termostat Grentona#
Termostat to obiekt samego CLU (THE… w Object Managerze). Import OM tworzy go jako RemoteThermostat: wartością jest temperatura na jego czujniku, a obok są temperatura zadana, nastawa ręczna, stan i tryb. Termostat trafia do rejestru, ale nie do Home Assistanta ani HomeKita. Żeby vCLU dostawało jego wartości, dodaj go do listy w Grenton → Sync.
menu:thermostat("Salon", "CLU220000592.THE0855", { readonly = true })
menu:thermostat("Sypialnia", "CLU220000592.THE4346", { step = 0.5, min = 18, max = 23 })Wiersz pokazuje temperaturę, a OK otwiera stronę z temperaturą teraz i zadaną. Zadana to ta, która obowiązuje, także z harmonogramu w trybie automatycznym albo w trybie wakacyjnym.
- Z
readonly = truestrona tylko pokazuje: nie ma+i-, a przyciski nic nie zmieniają. Tak zostaw termostat, którego nikt nie powinien zmieniać z panelu. - Bez
readonly+i-zmieniają nastawę ręczną (PointValue), zapisywaną sekundę po ostatnim naciśnięciu.minimaxograniczają, dokąd da się dojść. Działa to tylko w trybie ręcznym: w trybie automatycznym i wakacyjnym termostat bierze zadaną z harmonogramu albo z wartości wakacyjnej, więc strona tylko pokazuje.
Metoda RemoteThermostat | Zwraca |
|---|---|
getValue() | temperatura na czujniku |
getTarget() | zadana, która obowiązuje |
getSetpoint() / setSetpoint(v) | nastawa ręczna |
getState() | 1 pracuje, 0 zatrzymany |
getMode() | 0 ręczny, 1 wakacyjny, 2 automatyczny, 3 podgrzewanie |
Termostaty na starym firmware CLU (197 i 1f5) nie podają temperatury bieżącej: wiersz pokazuje --.
Każda nastawa ustawiona z panelu zostaje na ekranie, aż urządzenie zgłosi nową wartość (tę albo inną, np. przyciętą), najwyżej 90 s (Panel.HOLD_S). Dotyczy to też wierszy :number: urządzenie za HTTP, np. rekuperator, przez chwilę oddaje starą wartość.
Na CLU v1.0 (firmware 4.x) dodanie termostatu do listy Sync resetuje CLU przy rejestracji subskrypcji. Do czasu poprawki (#67) nie dodawaj tam termostatów z takiego CLU.
Pokoje z modelu domu#
Panel.area(pokój, opcje) to menu z urządzeń, które expose umieściło w pomieszczeniu (area), z nazwami jak w Home Assistant i HomeKit. Pokój jest budowany przy każdym otwarciu, więc urządzenie dopisane później pojawi się na panelu samo. Wiersze dodane ręcznie idą po urządzeniach.
Panel.area("Salon")
Panel.area("Łazienka dół", { title = "Lazienka" })
Panel.area({ "Pokój środkowy", "Pokój Tadeusza" }, { title = "Pokoj srodek" }) -- jeden pokój pod dwiema nazwami
Panel.area("Salon"):number("Zadana", get, set, { step = 0.5, min = 19, max = 23 })| Opcja | Domyślnie | Opis |
|---|---|---|
title | nazwa pokoju | tytuł menu |
kinds | light, dimmer, cover, temperature, humidity | rodzaje urządzeń na panelu |
skip | — | ścieżki do pominięcia |
Kolejność: światła, przełączniki i ściemniacze, potem rolety, potem reszta, w każdej grupie alfabetycznie.
Nie wszystko z domu musi trafić na panel. Przełączniki (pompy, domofon, otwieranie drzwi) i czujniki alarmu domyślnie zostają poza nim. Decydujesz opcją panel przy expose, tak jak opcjami homekit i mqtt:
expose(_:get("zigbee.salon_temp"), "temperature", { name = "Temp. Zigbee", area = "Salon", panel = false }) -- nigdy na panelu
expose(_:get("CLU220000592.DOU7823"), "switch", { name = "Domofon", area = "Wiatrołap", panel = true }) -- na panelu mimo rodzajuHome Assistant i HomeKit opcji panel nie widzą.
Piętra#
Panel.floor(piętro, opcje) to menu z pokoi piętra: tych, które w expose mają floor, np. { name = "Kanapa", area = "Salon", floor = "Parter" }. Każdy pokój to Panel.area. Pokój bez niczego dla panelu jest pomijany.
Panel.floor("Parter", { order = { "Hol", "Salon", "Kuchnia" }, titles = { ["Łazienka dół"] = "Lazienka" } })| Opcja | Opis |
|---|---|
title | tytuł menu |
order | pierwsze pokoje, w tej kolejności; reszta alfabetycznie |
titles | tytuły pokoi, np. krótsze niż w modelu domu |
kinds, skip | jak w Panel.area |
Strony wbudowane#
| Strona | Opis |
|---|---|
Panel.menu(tytuł, podmenu) | menu, jak wyżej |
Panel.corners{ tl, tr, bl, br, clock, draw, every, watch } | ekran, na którym każdy róg to jeden przycisk; patrz niżej |
Panel.area(pokój, opcje), Panel.floor(piętro, opcje) | menu z modelu domu |
Panel.cover(tytuł, obiekt) | roleta: góra otwiera, dół zamyka, OK zatrzymuje; pozycja dużą czcionką |
Panel.chart(tytuł, obiekt|fn, {hours, format}) | słupki godzinowe z historii (domyślnie 12 h); góra i dół wybierają godzinę, obok jej wartość |
Panel.history(tytuł, obiekt|fn, {count, format}) | ostatnie zmiany, od najnowszej: godzina, data i stan |
Panel.toggle(obiekt, tytuł) | jeden włącznik na całym ekranie |
Panel.value(tytuł, obiekt|fn, format) | jedna wartość, duża czcionka |
Panel.thermostat{ name, current, get, set, step, min, max, format } | temperatura bieżąca i zadana z dowolnego źródła; +/- zmieniają zadaną, zapis 1 s po ostatnim naciśnięciu; bez set tylko pokazuje |
Panel.idle{ {etykieta, obiekt|fn, format}, ... } | godzina, data i do trzech wartości; dowolny przycisk otwiera stronę startową |
Panel.cleaning(sekundy) | blokada do mycia szyby: odliczanie, dotyk ignorowany, buzzer wyłączony |
Panel.confirm(tekst, fn, tytuł) | pytanie: OK uruchamia fn(panel) |
Panel.chart i Panel.history zwracają nil, gdy obiekt nie ma historii. Wiersz :value z taką stroną po prostu nie ma OK.
Ekran startowy z przyciskami#
Panel.corners{
tl = { "Kanapa", "CLU220000592.DOU0789" }, -- urządzenie: róg robi to, co jego wiersz
tr = { "Roleta", "CLU220000592.ROL4101" },
bl = { "Lampka" }, -- sam napis: dioda zgaszona
br = { "Menu", function(p) p:open(menu) end }, -- funkcja: fn(panel)
clock = true, -- godzina na środku, co minutę
}Róg ze światłem albo ściemniaczem przełącza je, róg z roletą otwiera ją albo zamyka (gdy jest choć trochę otwarta). Ścieżka, której nie ma w rejestrze, zostawia sam napis. draw = function(s, panel) ... end rysuje coś innego na środku, a every i watch mówią, kiedy to odświeżyć.
Ekran z rogami nie ma wstecz: każdy róg jest przyciskiem. Dlatego nadaje się na stronę startową, a nie na stronę w głębi menu.
Teksty i język#
Teksty biblioteki (ON/OFF, Czyszczenie, podpowiedzi rolety, stan bramy) są domyślnie po angielsku. Panel.lang("pl") przełącza je na polski. Pojedynczy tekst zmienisz w Panel.strings:
Panel.lang("pl")
Panel.strings.open = "OTWARTE" -- np. dla okien zamiast bramEtykiety i tytuły możesz pisać z polskimi literami. Panel ich nie ma, więc biblioteka zamienia je na łacińskie (ł → l, tabela Panel.charMap).
Własna strona#
Strona to tabela z funkcjami. Rejestrujesz ją przez Panel.register i używasz jak wbudowanej:
Panel.register("rekuperator", function(rek)
local page = { every = 60 } -- odczyt z centrali co minutę
page.keys = { up = { "+" }, down = { "-" }, ok = { ">" } }
local st = { pole = 1 } -- stan w tabeli; bieg/zadana: wartość przed zapisem
page.draw = function(s)
local L = Panel.layout
Panel.drawTitle(s, "Rekuperator")
s:text(0, L.rows[1], (st.pole == 1 and ">" or " ") .. "Bieg")
:textRight(L.rows[1], tostring(st.bieg or rek.bieg()))
s:text(0, L.rows[2], (st.pole == 2 and ">" or " ") .. "Zadana")
:textRight(L.rows[2], string.format("%.1f", st.zadana or rek.zadana()))
end
page.input = function(name, panel)
if name == "ok" then st.pole = st.pole % 2 + 1 return end
local d = (name == "up") and 1 or (name == "down") and -1 or 0
if st.pole == 1 then
st.bieg = math.max(1, math.min(3, (st.bieg or rek.bieg()) + d))
panel:debounce("bieg", 1, function() rek.setBieg(st.bieg); st.bieg = nil end)
else
st.zadana = math.max(19, math.min(22, (st.zadana or rek.zadana()) + d * 0.5))
panel:debounce("zadana", 1, function() rek.setZadana(st.zadana); st.zadana = nil end)
end
end
return page
end)
-- w menu:
-- :item("Rekuperator", Panel.rekuperator(require("rekuperator")))| Pole strony | Opis |
|---|---|
draw(screen, panel) | rysuje stronę |
keys | { up = {etykieta, fn}, down = …, ok = …, back = {etykieta} } albo funkcja (panel) zwracająca taką tabelę; tylko rogi z kluczem mają podpowiedź i zapaloną diodę |
input(nazwa, panel) | up, down, ok (także z :long, :hold); bez input biblioteka wywołuje fn z keys |
back(panel) | zwraca true, jeśli strona sama obsłużyła „wstecz” (np. anulowanie edycji) |
watch | obiekty (albo funkcja), których zmiana przerysowuje stronę |
every, align | przerysowanie co N sekund (align = równo z zegarem) |
modal | strona dostaje każdy przycisk, bez podpowiedzi i bez „wstecz” (tak działa czyszczenie) |
open(panel), close(panel) | wywoływane przy otwarciu i zamknięciu |
Strona używa ról up, down i ok, a nie rogów, więc działa przy każdym ustawieniu :back().
Rysowanie#
screen zbiera wywołania i robi z nich jedną ramkę:
| Metoda | Panel |
|---|---|
s:text(x, y, tekst, czcionka) | PrintText; y to góra tekstu, czcionka 1-3 |
s:textRight(y, tekst, czcionka), s:textCenter(y, tekst, czcionka) | tekst do prawej krawędzi / na środku |
s:float(x, y, liczba, precyzja, czcionka) | PrintFloat, precyzja 0-2 |
s:line(x, y, xe, ye), s:rule(y), s:point(x, y) | linia, pozioma kreska przez cały ekran, punkt |
s:icon(x, y, plik) | DrawIcon: plik .bmp z karty panelu, nazwa bez rozszerzenia |
Panel.drawTitle(s, tekst) | tytuł strony, między podpowiedziami górnych rogów |
Co umie ekran (zmierzone na Smart Panel WiFi):
- 128 × 64 punkty, czarno-biały. Czcionki 1, 2 i 3 mają 10, 14 i 28 punktów wysokości.
- Czcionki są proporcjonalne. W wierszu mieści się 16 cyfr czcionką 1 i 13 czcionką 2. Kolumny ustawiaj przez
xalbotextRight, nie spacjami. - Jeden napis to najwyżej 15 znaków. Dłuższy biblioteka przycina.
- PrintText maluje czarne tło pod każdą literą, a DrawBox nic nie rysuje. Tekstu nie da się więc odwrócić ani podświetlić, a każda ramka rysuje cały ekran od nowa.
- Ikony to pliki
.bmpna karcie panelu (temp,fan,sun,lamp1on…, 64 × 32;arrups,arrdowns, 16 × 16). Paleta pliku jest ignorowana.
Wiersze treści są na y = 14, 27 i 40 (Panel.layout.rows), podpowiedzi rogów na górze (y = 0) i na dole (y = 54, dłuższe na 48, bo małe litery na 54 wychodzą poza ekran).
Ustawienia#
| Ustawienie | Domyślnie | Opis |
|---|---|---|
Panel.layout | { w=128, h=64, rows={14,27,40}, top=0, bottom=54, bottomText=48, indent=8, ... } | położenie wierszy, szerokości znaków (cw, narrow) |
Panel.strings | angielskie | teksty biblioteki; Panel.lang("pl") |
Panel.REDRAW_MS | 20 | zmiany w tym czasie dają jedną ramkę |
Panel.pageControlOff | true | wyłącza przełączanie wbudowanych stron Grentona gestami |
RemotePanel.MAX_MESSAGE | 300 | najdłuższa wiadomość do CLU (znaki); panel przyjął 337 |
RemotePanel.SPACING_MS | 60 | odstęp między wiadomościami |
RemotePanel.RETRIES | 2 | ile razy wysłać ramkę ponownie, gdy CLU nie odpowie |
Każda wiadomość jest jednym wyrażeniem, (function() ... end)(): CLU panelu nie wykona wiadomości z dwiema instrukcjami obok siebie.
Smart Panel WiFi#
Panel WiFi jest dla Object Managera osobnym CLU, z własnym IP, skryptami i plikiem OM, a w środku ma ten sam moduł panelu. Dla vCLU to zwykłe zdalne CLU, więc wszystko działa tak samo:
- Zaimportuj plik OM samego panelu WiFi. Import tworzy dla niego zdalne CLU i
RemotePanelz przyciskami i czujnikami. - Zdarzenia przycisków podepnij na panelu WiFi, bo to on jest CLU.
- Moduł Lua jest taki sam jak dla panelu na magistrali.
Panel WiFi wysyła wywołanie click na zmianę broadcastem i unicastem. Dochodzi tak czy inaczej, ale broadcast widzą też inne CLU w sieci. Dla nich click to nieznana funkcja, więc tylko go odrzucą.
Jeśli ekran rysuje się niepełny, zwiększ odstęp i zmniejsz wiadomości, np. RemotePanel.SPACING_MS = 120 i RemotePanel.MAX_MESSAGE = 150. Brakujące ramki i tak są wysyłane ponownie.
Stan panelu#
Stan każdego panelu trzyma vCLU w bibliotece: otwarte strony, kursor, edytowaną wartość i licznik bezczynności. Stan nie jest zapisywany. Po restarcie lub przeładowaniu vCLU każdy panel rysuje stronę startową. Gdy panel straci obraz (restart CLU, ponowne włączenie ekranu), zdarzenie wake przerysowuje bieżącą stronę.
Po imporcie OM na działającym vCLU obiekt panelu jest tworzony od nowa. Przeładuj wtedy Lua (Reload), żeby moduł przejął nowy obiekt.
Testy w sandboxie#
Agent AI (i Ty, przez narzędzia MCP) może sprawdzić moduł panelu przed wdrożeniem. W scenariuszu vclu_draft_test kroki click naciskają przyciski, a ramki, które poszłyby do panelu, są w clu_calls:
[{"advance": 1}, {"click": "PAN1085", "input": "btn4"}, {"advance": 1}]Bezpośrednie sterowanie#
Bez biblioteki też można rysować, wywołując metody obiektu z importu:
local p = require("lib/panel").find("PAN1085") -- obiekt RemotePanel
p:beep(2000, 100, 8)
p:send({ p:op(RemotePanel.CLEAR), p:op(RemotePanel.PRINT_TEXT, 0, 0, "Czesc", 2), p:op(RemotePanel.DISPLAY) })
p:getButton(0):ledOn()send dzieli ramkę na wiadomości i wysyła je z odstępami, jak biblioteka. Nie wysyłaj do panelu własnych skryptów w pętli.