TapHome

Script examples

Complete TapHome scripts: safe division, 0 instead of an unknown value, dimming with LINEAR, Modbus alarms and service attributes, HTTP and JSON, MQTT listener and Wake on LAN.

Complete scripts for common tasks. The smart rule recipes work in any installation; the Modbus and Packet parser examples come from the official template repositories for Modbus and Packet parser. Short names such as Se, va1 or St are abbreviations of device values picked in the script editor; names such as StatusJson or plugTopic are module variables.

Smart rule recipes

Divide two values safely

Dividing by a device value that is 0 stops the script with Line in script: 1 Message: Division by zero. Wrap the division va1/va2 in the function form of IF. It evaluates only the chosen branch, so the division runs only when the divisor is greater than 0:

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

When the divisor can be negative, test va2 <> 0 instead. More in Errors.

Write 0 instead of an unknown value

An unknown device value, for example from a sensor in error, is NaN. To write 0 instead, test it with ISNAN — the pattern is IF(ISNAN(x), 0, x):

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

A comparison such as Se = NaN is a parse error. When the value can also be Null (no value), test ISNULL first; OR skips its right side once the left side is TRUE:

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

Dimming with LINEAR

LINEAR maps a range of input values to a range of output values, which is the usual way to compute a dimming level. A dimmer level goes from 0 (off) to 1 (100 %). In a Formula smart rule with a dimmer as the output, this script dims the light as daylight rises from 0 to 500 lx (Se is the light sensor):

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

Outside the input range the result stays at the nearest end (BOUNDS, the default). To keep a minimum level, raise the lower output: LINEAR(Se, 0, 1, 500, 0.1) never goes below 10 %. Details in LINEAR.

Modbus examples

Device errors from an alarm register

A read script of a Solax inverter turns a status register and a 32-bit alarm word into device errors:

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"));

Service attribute from bit fields

A Komfovent service attribute shows the four bytes of a 32-bit register as dotted text:

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"

Service action with a parameter

Parameters of a service action are variables named by their abbreviation. An Eastron SDM120 action writes its parameter ID to holding register 20:

MODBUSW(H, 20, Float, ID)

Decode an enumeration

A Huawei SUN2000 status register becomes readable text with 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");

Packet parser examples

HTTP request with error handling

SENDHTTPREQUEST returns an httpresponse object. This read script (Forecast.solar) keeps the response body in the variable output for later parsing and reports a failed request as a device error:

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

Uptime from JSON

A Shelly 1PM service attribute formats the uptime in seconds from the cached status JSON:

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"

Spot price for the current period

A read script downloads today’s prices from 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

A service attribute then picks one period with a JSONPath filter:

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

Loop over a JSON array

With TRUE as the third argument, PARSEJSON returns Null after the last element instead of failing, which ends the loop:

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

Validate a module variable in a listener script

A Shelly MQTT listener script first checks that the module variable plugTopic was filled in, then converts the power reading from W to 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

A UDP push button sends a magic packet. The module variable MAC holds the address as text ("12 34 56 78 9a bc"); BYTECOLLECTION ignores the spaces:

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);