TapHome

Przykłady skryptów

Kompletne skrypty TapHome: bezpieczne dzielenie, 0 zamiast nieznanej wartości, ściemnianie przez LINEAR, alarmy Modbus i atrybuty serwisowe, HTTP i JSON, listener MQTT i Wake on LAN.

Kompletne skrypty do częstych zadań. Gotowe rozwiązania dla smart rules działają w każdej instalacji; przykłady Modbus i Packet parser pochodzą z oficjalnych repozytoriów szablonów dla Modbus i Packet parser. Krótkie nazwy, takie jak Se, va1 czy St, to skróty wartości urządzeń wybranych w edytorze skryptu; nazwy takie jak StatusJson czy plugTopic to zmienne modułu.

Gotowe rozwiązania dla smart rules

Bezpieczne dzielenie dwóch wartości

Dzielenie przez wartość urządzenia równą 0 zatrzymuje skrypt błędem Line in script: 1 Message: Division by zero. Obejmij dzielenie va1/va2 funkcyjną formą IF. Oblicza ona tylko wybraną gałąź, więc dzielenie wykona się tylko wtedy, gdy dzielnik jest większy od 0:

IF(va2 > 0, va1/va2, 0);
# Result: 0 when va2 is 0, otherwise va1 divided by va2

Gdy dzielnik może być ujemny, sprawdzaj zamiast tego va2 <> 0. Więcej w Błędy.

Zapis 0 zamiast nieznanej wartości

Nieznana wartość urządzenia, na przykład z czujnika w błędzie, to NaN. Aby zamiast niej zapisać 0, sprawdź ją przez ISNAN — wzorzec to IF(ISNAN(x), 0, x):

IF(ISNAN(Se), 0, Se);
# Result: 0 while the value of Se is unknown, otherwise the value of Se

Porównanie takie jak Se = NaN to błąd parsowania. Gdy wartość może być także Null (brak wartości), najpierw sprawdź ISNULL; OR pomija swoją prawą stronę, gdy lewa strona jest TRUE:

IF(ISNULL(Se) OR ISNAN(Se), 0, Se);

Ściemnianie przez LINEAR

LINEAR przenosi zakres wartości wejściowych na zakres wartości wyjściowych, co jest zwykłym sposobem obliczania poziomu ściemnienia. Poziom ściemniacza idzie od 0 (wyłączony) do 1 (100 %). W smart rule Formuła ze ściemniaczem jako wyjściem ten skrypt ściemnia światło, gdy światło dzienne rośnie od 0 do 500 lx (Se to czujnik oświetlenia):

LINEAR(Se, 0, 1, 500, 0);
# Result: 1 at 0 lx, 0.5 at 250 lx, 0 at 500 lx and more

Poza zakresem wejściowym wynik zostaje na najbliższym końcu (BOUNDS, domyślnie). Aby zachować minimalny poziom, podnieś dolne wyjście: LINEAR(Se, 0, 1, 500, 0.1) nigdy nie spada poniżej 10 %. Szczegóły w LINEAR.

Przykłady Modbus

Błędy urządzenia z rejestru alarmów

Skrypt odczytu falownika solarnego Solax zamienia rejestr statusu i 32-bitowe słowo alarmów na błędy urządzenia:

VAR reg := MODBUSR(A, 0x09, UInt16);
IF(reg = 3, ADDERROR("Run Mode Fault"));
IF(reg = 4, ADDERROR("Run Mode Permanent Fault"));

VAR x3 := MODBUSR(A, 0x0040, Uint32);
IF(GETBIT(x3, 0) = 1, ADDERROR("TZ Protect Fault"));
IF(GETBIT(x3, 1) = 1, ADDERROR("Grid Lost Fault"));
IF(GETBIT(x3, 2) = 1, ADDERROR("Grid Volt Fault"));
IF(GETBIT(x3, 15) = 1, ADDERROR("Off-grid Overload"));

Atrybut serwisowy z pól bitowych

Atrybut serwisowy Komfovent wyświetla cztery bajty 32-bitowego rejestru jako tekst z kropkami:

VAR reg := MODBUSR(H, 22, Uint32);
VAR first := GETBITS(reg, 0, 8);
VAR second := GETBITS(reg, 8, 8);
VAR third := GETBITS(reg, 16, 8);
VAR last := GETBITS(reg, 24, 8);
RETURN(last + "." + third + "." + second + "." + first);
# Result: text such as "1.2.3.4"

Akcja serwisowa z parametrem

Parametry akcji serwisowej to zmienne nazwane według ich skrótu. Akcja Eastron SDM120 zapisuje swój parametr ID do holding register 20:

MODBUSW(H, 20, Float, ID)

Odczytanie wyliczenia

Rejestr statusu Huawei SUN2000 staje się czytelnym tekstem dzięki SWITCH:

VAR state := MODBUSR(H, 32089, Uint16);
SWITCH(state,
      0, "Idle: Initializing",
      1, "Idle: Detecting ISO",
    256, "Starting",
    512, "On-Grid",
    768, "Shutdown: Fault",
    "Unknown");

Przykłady Packet parser

Żądanie HTTP z obsługą błędów

SENDHTTPREQUEST zwraca obiekt httpresponse. Ten skrypt odczytu (Forecast.solar) zachowuje treść odpowiedzi w zmiennej output do późniejszego parsowania, a nieudane żądanie zgłasza jako błąd urządzenia:

VAR response := SENDHTTPREQUEST("/estimate/" + Latitude + "/" + Longitude + "/"
    + PV_declination + "/" + PV_azimuth + "/" + PV_kWp,
    "GET", "", "Accept: application/json");

IF response.IsSuccess
    output := response.Content;
ELSE
    ADDERROR(response.StatusCode + " (" + response.ReasonPhrase + ")");
END

Czas pracy z JSON

Atrybut serwisowy Shelly 1PM formatuje czas pracy w sekundach z zapamiętanego JSON statusu:

VAR upTimeSeconds := PARSEJSON(StatusJson, "uptime");
VAR days := FLOOR(upTimeSeconds / 86400, 1);
upTimeSeconds := MOD(upTimeSeconds, 86400);
VAR hours := FLOOR(upTimeSeconds / 3600, 1);
upTimeSeconds := MOD(upTimeSeconds, 3600);
VAR minutes := FLOOR(upTimeSeconds / 60, 1);
days + " day(s) " + hours + "h " + minutes + "m";
# Result: for 191820 seconds "2 day(s) 5h 17m"

Cena spot dla bieżącego okresu

Skrypt odczytu pobiera dzisiejsze ceny z okte.sk:

VAR today := TOSTRING(NOW(), "yyyy-MM-dd");
VAR response := SENDHTTPREQUEST("/api/v1/dam/results?deliveryDayFrom=" + today
    + "&deliveryDayTo=" + today);

IF response.IsSuccess
    output := response.Content;
ELSE
    ADDERROR(response.StatusCode + " (" + response.ReasonPhrase + ")");
END

Atrybut serwisowy wybiera następnie jeden okres filtrem JSONPath:

PARSEJSON(output, "$[?(@.period == 1)].price") + " EUR"

Pętla po tablicy JSON

Z TRUE jako trzecim argumentem PARSEJSON zwraca Null po ostatnim elemencie zamiast błędu, co kończy pętlę:

VAR response := SENDHTTPREQUEST("/api/taphomeapi/v1/discovery", "GET", "",
    "Authorization: TapHome " + API_token);

IF response.IsSuccess
    VAR ret := "";
    VAR i := 0;
    VAR id := PARSEJSON(response.Content, "$.devices[0].deviceId", TRUE);

    WHILE !ISNULL(id)
        ret := ret + "(" + id + ") ";
        ret := ret + PARSEJSON(response.Content, "$.devices[" + TOSTRING(i) + "].name", TRUE);
        ret := ret + " - " + PARSEJSON(response.Content, "$.devices[" + TOSTRING(i) + "].type", TRUE);
        ret := ret + "\n";
        i := i + 1;
        id := PARSEJSON(response.Content, "$.devices[" + TOSTRING(i) + "].deviceId", TRUE);
    LOOP

    RETURN(ret);
END

Sprawdzenie zmiennej modułu w skrypcie listener

Skrypt listener MQTT Shelly najpierw sprawdza, czy zmienna modułu plugTopic została wypełniona, a potem przelicza odczyt mocy z W na kW:

IF(INDEXOF(plugTopic, "shelly1pm-deviceid") = 0)
    ADDERROR("Set correct 'plugTopic' value in module variables. "
        + "Topic format is 'shelly1pm-<deviceid>'.");
    RETURN(-1);
END

IF(RECEIVEDMSG.TOPIC = "shellies/" + plugTopic + "/relay/0/power")
    VAR valueP := TOSTRING(RECEIVEDMSG.PAYLOAD);
    Ed := IF(ISNULL(valueP), NaN, TODOUBLE(valueP) / 1000.0);
END

Wake on LAN

Przycisk UDP wysyła magiczny pakiet. Zmienna modułu MAC przechowuje adres jako tekst ("12 34 56 78 9a bc"); BYTECOLLECTION ignoruje spacje:

VAR magicPacket := BYTECOLLECTION("FF FF FF FF FF FF " + MAC + MAC + MAC + MAC
    + MAC + MAC + MAC + MAC + MAC + MAC + MAC + MAC + MAC + MAC + MAC + MAC);
SENDDATA(magicPacket);