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 va2When 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 SeA 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 moreOutside 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 + ")");
ENDUptime 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 + ")");
ENDA 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);
ENDValidate 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);
ENDWake 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);