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 OMKlasaObiekt w vCLU
PANEL29RemotePanel, z przypiętymi przyciskami
PANEL_BUTTON30RemotePanelButton (dioda, etykieta)
PANELSENSTEMP20czujnik temperatury, jak każdy inny
PANELSENSLIGHT21czujnik 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):

Object Manager: zdarzenia OnClick i OnLongPress przycisku panelu wywołują skrypt click na vCLU

ObiektZdarzenieinput
przycisk 1-4 (strona 1)OnClickbtn1 … btn4
przycisk 1-4OnLongPressbtn1:long … btn4:long
przycisk 1-4OnHoldbtn1:hold … (opcjonalnie: szybka zmiana wartości przy przytrzymaniu)
panelOnDisplayOnwake (opcjonalnie: przerysowanie po włączeniu ekranu)
panelOnProximityDetectproximity (opcjonalnie)
panelOnGestureUp/Down/Left/Rightswipe: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 OM

i 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 M

Katalog 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:

MetodaCo 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 })
end

Ekran gaśnie po czasie ustawionym w OM (ProximityTimeout) i włącza się, gdy ktoś podejdzie.

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.

WierszCo 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.

RodzajWierszOK
light, switch, lockON/OFFprzełącza
dimmer (i światło z jasnością)jasność w %edycja co 10%
coverpozycjastrona rolety: góra otwiera, dół zamyka, OK zatrzymuje
termostat Grentonatemperaturastrona termostatu
czujnik dwustanowyON/OFFostatnie zmiany
temperature, humidity, illuminance, liczbawartośćwykres z 12 godzin
Opcja :addOpis
kindrodzaj, gdy ma być inny niż z expose
formatformat wartości
chart = false, history = falsewartość bez wykresu, stan bez listy zmian
readonly = truesam 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 = true strona 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. min i max ograniczają, 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 RemoteThermostatZwraca
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 })
OpcjaDomyślnieOpis
titlenazwa pokojutytuł menu
kindslight, dimmer, cover, temperature, humidityrodzaje 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 rodzaju

Home 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" } })
OpcjaOpis
titletytuł menu
orderpierwsze pokoje, w tej kolejności; reszta alfabetycznie
titlestytuły pokoi, np. krótsze niż w modelu domu
kinds, skipjak w Panel.area

Strony wbudowane#

StronaOpis
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 bram

Etykiety 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 stronyOpis
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)
watchobiekty (albo funkcja), których zmiana przerysowuje stronę
every, alignprzerysowanie co N sekund (align = równo z zegarem)
modalstrona 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ę:

MetodaPanel
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 x albo textRight, 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 .bmp na 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#

UstawienieDomyślnieOpis
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.stringsangielskieteksty biblioteki; Panel.lang("pl")
Panel.REDRAW_MS20zmiany w tym czasie dają jedną ramkę
Panel.pageControlOfftruewyłącza przełączanie wbudowanych stron Grentona gestami
RemotePanel.MAX_MESSAGE300najdłuższa wiadomość do CLU (znaki); panel przyjął 337
RemotePanel.SPACING_MS60odstęp między wiadomościami
RemotePanel.RETRIES2ile 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:

  1. Zaimportuj plik OM samego panelu WiFi. Import tworzy dla niego zdalne CLU i RemotePanel z przyciskami i czujnikami.
  2. Zdarzenia przycisków podepnij na panelu WiFi, bo to on jest CLU.
  3. 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.