Introduction#
What is TapHome Script?#
TapHome Script is a custom scripting language designed specifically for IoT and smart home automation. It allows you to:
- Read and write data from Modbus devices
- Communicate with web services via HTTP/REST APIs
- Process MQTT messages
- Parse JSON and XML data
- Implement complex automation logic
- Transform and calculate sensor values
Where is TapHome Script Used?#
- Smart Rules - Automation triggers and conditions
- Equations - Mathematical formulas for sensor values
- Formulas - Data transformations
- Modbus Communication - Reading/writing Modbus registers
- Packet Parser - Custom protocol parsing for TCP/UDP/HTTP/MQTT
Key Features#
✅ Case-insensitive - You can write IF or if
✅ Multi-line scripts - Write complex logic across multiple lines
✅ Rich data types - Numbers, strings, dates, colors, collections
✅ Built-in functions - 70+ functions for common tasks
✅ Network protocols - HTTP, MQTT, FTP, Modbus support
✅ Data parsing - JSON, XML, text parsing built-in
Getting Started#
Your First Script#
Let’s start with a simple example:
# Read temperature from a Modbus sensor
temperature := MODBUSR(H, 100, BigEndianInt16)
# Check if it's too hot
IF temperature > 25
RETURN(1) # Turn on cooling
ELSE
RETURN(0) # Turn off cooling
ENDWhat this does:
- Reads a 16-bit integer from Modbus holding register 100
- Compares the temperature to 25
- Returns 1 (on) or 0 (off) based on the condition
Script Execution Flow#
- Parse - TapHome reads your script and checks syntax
- Evaluate - The script runs line by line
- Return - The final result is returned to the system
Language Syntax#
Comments#
Comments help document your code. They start with # and continue to the end of the line.
# This is a comment
temperature := 22 # This is also a comment
# Comments must start on a new line
# Inline comments after code are NOT supported on the same line without #Important: Comments must be on their own line or after # on any line.
Variables and Data Types#
Declaring Variables#
Automatic Type (VAR):
VAR temperature := 22.5
VAR deviceName := "Living Room"
VAR isActive := TRUEExplicit Type:
int counter := 0
float voltage := 3.3
string message := "Hello"
bool enabled := FALSESupported Types:
- Numbers:
int,uint,float,double,int8,int16,int32,int64,uint8,uint16,uint32,uint64 - Text:
string,char - Logic:
bool - Date/Time:
datetime,timespan - Network:
httprequest,httpresponse,receivedmessage - Binary:
blob - Colors:
rgbcolor,hsvcolor - Collections:
collection<TYPE>
Variable Assignment#
# Simple assignment
x := 10
# Compound assignment
x += 5 # x = x + 5
x -= 3 # x = x - 3
x *= 2 # x = x * 2
x /= 4 # x = x / 4Collections (Arrays)#
# Create collection
numbers := {1, 2, 3, 4, 5}
names := {"Alice", "Bob", "Charlie"}
# Access elements (0-based index)
first := GETAT(numbers, 0) # Gets 1
last := GETAT(numbers, 4) # Gets 5
# Modify elements
SETAT(numbers, 0, 10) # Sets first element to 10
# Add elements
APPEND(numbers, 6) # Adds 6 to the end
# Collection size
count := numbers.LengthOperators#
Arithmetic Operators#
a := 10 + 5 # Addition: 15
b := 10 - 5 # Subtraction: 5
c := 10 * 5 # Multiplication: 50
d := 10 / 5 # Division: 2
e := 10 MOD 3 # Modulo (remainder): 1Comparison Operators#
x = 5 # Equal to
x != 5 # Not equal to
x <> 5 # Not equal to (alternative)
x > 5 # Greater than
x < 5 # Less than
x >= 5 # Greater than or equal to
x <= 5 # Less than or equal toLogical Operators#
# AND - both conditions must be true
IF temperature > 20 AND humidity < 80
# Do something
END
# OR - at least one condition must be true
IF error = 1 OR warning = 1
# Handle issue
END
# NOT - negation
IF !isActive
# Device is not active
END
# XOR - exactly one must be true
IF sensor1Active XOR sensor2Active
# Only one sensor is active
ENDBitwise Operators#
# Bitwise AND
result := 0xFF & 0x0F # Result: 0x0F
# Bitwise OR
result := 0x10 | 0x01 # Result: 0x11
# Bitwise XOR
result := 0xFF ^ 0x0F # Result: 0xF0
# Bit shift left
result := 1 << 4 # Result: 16 (binary: 00010000)
# Bit shift right
result := 16 >> 2 # Result: 4 (binary: 00000100)Control Structures#
IF/ELSEIF/ELSE/END#
IF temperature > 30
RETURN("Too hot")
ELSEIF temperature > 20
RETURN("Comfortable")
ELSEIF temperature > 10
RETURN("Cool")
ELSE
RETURN("Cold")
ENDNested IF:
IF temperature > 25
IF humidity > 70
RETURN("Hot and humid")
ELSE
RETURN("Hot and dry")
END
ENDWHILE Loop#
Repeats while condition is true (checks condition BEFORE execution):
counter := 0
WHILE counter < 5
counter := counter + 1
LOOP
# counter is now 5DO-WHILE Loop#
Repeats while condition is true (checks condition AFTER execution):
counter := 0
DO
counter := counter + 1
LOOP WHILE counter < 5
# counter is now 5
# Executes at least once even if condition is initially falseBREAK - Exit Loop#
counter := 0
WHILE TRUE
counter := counter + 1
IF counter >= 10
BREAK # Exit the loop
END
LOOPCONTINUE - Skip to Next Iteration#
sum := 0
counter := 0
WHILE counter < 10
counter := counter + 1
# Skip odd numbers
IF counter MOD 2 = 1
CONTINUE
END
sum := sum + counter
LOOP
# sum contains only even numbers: 2+4+6+8+10 = 30RETURN - Exit Script#
# Exit immediately and return a value
IF error = 1
RETURN(NaN) # Return "Not a Number" to indicate error
END
# Normal processing
result := temperature * 2
RETURN(result)Data Types Reference#
DateTime#
Represents date and time with timezone support.
Creating DateTime:
# Current time (local)
now := NOW()
# Current time (UTC)
nowUtc := NOW(DateTimeKind.Utc)
# Specific date/time
dt := DATETIME(2024, 1, 15, 10, 30, 0, 0, DateTimeKind.Utc)
# year mon day hr min sec ms kind
# Parse from string
dt := DATETIME("2024-01-15 10:30:00")DateTime Properties:
dt := NOW()
year := dt.Year # 2024
month := dt.Month # 1-12
day := dt.Day # 1-31
hour := dt.Hour # 0-23
minute := dt.Minute # 0-59
second := dt.Second # 0-59
dayOfWeek := dt.DayOfWeek # 0=Sunday, 6=SaturdayDateTime Operations:
# Add time
future := DATETIMEADD(NOW(), 0, 1, 5, 2, 30, 0, 0)
# yr mon day hr min sec ms
# Adds: 1 month, 5 days, 2 hours, 30 minutes
# Compare dates
IF dt1 > dt2
# dt1 is later than dt2
ENDTimeSpan#
Represents duration or time interval.
# Create from milliseconds
duration := TIMESPAN(5000) # 5 seconds
# Create from components
duration := TIMESPAN(1, 2, 30, 45, 500)
# days hrs min sec ms
# Properties
days := duration.Days
hours := duration.Hours
totalSeconds := duration.TotalSecondsHttpRequest & HttpResponse#
Creating HTTP Request:
# Simple GET
req := HTTPREQUEST("/api/data")
# POST with body
req := HTTPREQUEST("/api/update", "POST", "{\"value\": 25}")
# Access properties
path := req.Path
method := req.Method
body := req.BodyWorking with Response:
response := SENDHTTPREQUEST(req)
IF response.IsSuccess
content := response.Content
statusCode := response.StatusCode
ELSE
error := response.ReasonPhrase
ENDRGBColor & HSVColor#
RGB Color (0-255 per channel):
# Convert from HSV
rgb := HSVTORGB(180, 1.0, 1.0) # Cyan
red := rgb.Red # 0-255
green := rgb.Green # 0-255
blue := rgb.Blue # 0-255HSV Color (Hue: 0-360, Saturation: 0-1, Value: 0-1):
# Convert from RGB
hsv := RGBTOHSV(255, 0, 0) # Red
hue := hsv.Hue # 0-360 degrees
saturation := hsv.Saturation # 0.0-1.0
value := hsv.Value # 0.0-1.0 (brightness)Collections#
Creating Collections:
# From literals
numbers := {1, 2, 3, 4, 5}
# Byte collection from hex
bytes := BYTECOLLECTION("01FF23A4")
# Empty byte collection
emptyBytes := BYTECOLLECTION(10) # 10 bytesCollection Operations:
# Get element
value := GETAT(numbers, 0)
# Set element
SETAT(numbers, 0, 100)
# Add element
APPEND(numbers, 6)
# Insert element
INSERT(numbers, 2, 999) # Insert 999 at index 2
# Remove elements
REMOVEAT(numbers, 0, 2) # Remove 2 elements starting at index 0
# Find element
index := INDEXOF(numbers, 3) # Returns index or -1
# Copy sub-collection
sub := COPY(numbers, 1, 3) # Copy 3 elements starting at index 1
# Sort
sorted := ORDER(5, 2, 8, 1, 9) # Returns {1, 2, 5, 8, 9}Built-in Functions#
Mathematical Functions#
MIN / MAX - Minimum and Maximum#
# Find minimum
min := MIN(5, 10, 3, 8) # Returns 3
# From collection
temperatures := {22.5, 23.1, 21.8, 24.0}
lowest := MIN(temperatures) # Returns 21.8
# Find maximum
max := MAX(5, 10, 3, 8) # Returns 10
highest := MAX(temperatures) # Returns 24.0
# Strict versions (return NaN if any value is NaN)
result := MINSTRICT(5, NaN, 10) # Returns NaN
result := MAXSTRICT(5, NaN, 10) # Returns NaNUse Case - Temperature Monitoring:
# Read temperatures from 3 sensors
t1 := MODBUSR(H, 100, BigEndianInt16)
t2 := MODBUSR(H, 102, BigEndianInt16)
t3 := MODBUSR(H, 104, BigEndianInt16)
# Get average and extremes
average := AVG(t1, t2, t3)
highest := MAX(t1, t2, t3)
lowest := MIN(t1, t2, t3)
# Return average
RETURN(average)AVG - Average#
# Calculate average
avg := AVG(10, 20, 30) # Returns 20
# From collection
readings := {22.5, 23.1, 21.8, 24.0}
average := AVG(readings) # Returns 22.85
# Strict version
result := AVGSTRICT(10, NaN, 30) # Returns NaNROUND, CEIL, FLOOR - Rounding#
# Round to nearest integer
rounded := ROUND(3.7) # Returns 4
rounded := ROUND(3.2) # Returns 3
# Round up (ceiling)
up := CEIL(3.2) # Returns 4
# Round down (floor)
down := FLOOR(3.7) # Returns 3ABS - Absolute Value#
# Get absolute value
abs := ABS(-15) # Returns 15
abs := ABS(10) # Returns 10POWER, SQRT - Power and Square Root#
# Power
result := POWER(2, 8) # 2^8 = 256
result := POWER(10, 2) # 10^2 = 100
# Square root
result := SQRT(16) # Returns 4
result := SQRT(2) # Returns 1.414...MOD - Modulo (Remainder)#
# Get remainder
remainder := MOD(10, 3) # Returns 1 (10 / 3 = 3 remainder 1)
remainder := MOD(17, 5) # Returns 2
# Check if number is even
IF MOD(number, 2) = 0
# Number is even
ENDSIGN - Sign of Number#
sign := SIGN(-10) # Returns -1
sign := SIGN(0) # Returns 0
sign := SIGN(10) # Returns 1LN, LOG - Logarithms#
# Natural logarithm (base e)
ln := LN(2.71828) # Returns ~1
# Logarithm base 10
log := LOG(100) # Returns 2 (10^2 = 100)
# Logarithm custom base
log := LOG(8, 2) # Returns 3 (2^3 = 8)RAND, RANDINT - Random Numbers#
# Random decimal 0.0 to 1.0
random := RAND() # Returns e.g., 0.7234
# Random integer (min inclusive, max exclusive)
dice := RANDINT(1, 7) # Returns 1-6
number := RANDINT(1, 101) # Returns 1-100Logical Functions#
IF - Ternary Conditional#
# IF(condition, valueIfTrue, valueIfFalse)
result := IF(temperature > 25, 1, 0)
# Without else (returns null if false)
result := IF(temperature > 25, 1)
# Nested IF
result := IF(temp > 30, "hot", IF(temp > 20, "warm", "cold"))SWITCH - Multi-way Branch#
# SWITCH(variable, value1, result1, value2, result2, ..., default)
state := 2
message := SWITCH(state,
1, "On",
2, "Off",
3, "Auto",
"Unknown")
# Returns "Off"Use Case - Modbus State Decoding:
# Read state register
stateRegister := MODBUSR(H, 200, UInt16)
# Decode state
stateText := SWITCH(stateRegister,
0x0001, "Heating",
0x0002, "Cooling",
0x0004, "Fan Only",
0x0008, "Off",
"Error")
RETURN(stateText)HYSTERESIS - Prevent Oscillation#
# HYSTERESIS(value, upperThreshold, lowerThreshold,
# upperState, lowerState, previousState)
# Example: Thermostat control
# Turn ON when temp drops below 20°C
# Turn OFF when temp rises above 22°C
# Keep previous state between 20-22°C
heater := HYSTERESIS(temperature, 22, 20, 0, 1, heater)
# upper low off on prev
RETURN(heater)Explanation:
- If temp > 22°C → returns 0 (off)
- If temp < 20°C → returns 1 (on)
- If 20°C ≤ temp ≤ 22°C → returns previous state (prevents rapid switching)
EQUALS - Compare Numbers with Tolerance#
# EQUALS(number1, number2, epsilon)
# Default epsilon: 0.000001
result := EQUALS(3.14159, 3.14160, 0.001) # Returns TRUE
# Useful for floating point comparisons
IF EQUALS(sensorValue, 25.0, 0.1)
# Sensor reads approximately 25.0
ENDISNAN, ISNULL - Check for Special Values#
# Check if value is NaN (Not a Number)
IF ISNAN(result)
# Handle error - result is not a valid number
END
# Check if value is null
IF ISNULL(optionalValue)
# Value was not provided
ENDString and Text Functions#
LENGTH - Get String/Collection Length#
# String length
len := LENGTH("Hello") # Returns 5
# Collection length
numbers := {1, 2, 3, 4}
count := LENGTH(numbers) # Returns 4
# Also available as property
len := "Hello".Length # Returns 5
count := numbers.Length # Returns 4TOSTRING - Convert to String#
# Convert number
str := TOSTRING(123) # Returns "123"
# With formatting
str := TOSTRING(3.14159, "F2") # Returns "3.14" (2 decimal places)
# Convert boolean
str := TOSTRING(TRUE) # Returns "True"TODOUBLE - Parse Number from String#
# Convert string to number
num := TODOUBLE("123.45") # Returns 123.45
# Returns NaN if conversion fails
num := TODOUBLE("abc") # Returns NaN
# Check for errors
num := TODOUBLE(textValue)
IF ISNAN(num)
# Conversion failed
ENDSPLIT - Split String#
# Split by comma
parts := SPLIT("apple,banana,orange", ",")
# Returns collection: {"apple", "banana", "orange"}
# Get individual parts
first := GETAT(parts, 0) # "apple"
# Split CSV line
data := SPLIT("25.5,60,1013", ",")
temperature := TODOUBLE(GETAT(data, 0)) # 25.5
humidity := TODOUBLE(GETAT(data, 1)) # 60INDEXOF - Find Substring#
# Find position of substring
pos := INDEXOF("Hello World", "World") # Returns 6
# Returns -1 if not found
pos := INDEXOF("Hello", "xyz") # Returns -1
# Check if string contains substring
IF INDEXOF(message, "ERROR") != -1
# Message contains "ERROR"
ENDREPLACE - Replace Substring#
# Replace all occurrences
result := REPLACE("Hello World", "World", "Universe")
# Returns "Hello Universe"
# Replace multiple times
result := REPLACE("foo foo foo", "foo", "bar")
# Returns "bar bar bar"COPY - Extract Substring#
# COPY(string, start, length)
sub := COPY("Hello World", 0, 5) # Returns "Hello"
sub := COPY("Hello World", 6, 5) # Returns "World"
# Copy to end
sub := COPY("Hello World", 6) # Returns "World"APPEND, INSERT, REMOVEAT - String Manipulation#
# Append
result := APPEND("Hello", " World") # Returns "Hello World"
# Insert
result := INSERT("Hello World", 5, " Beautiful")
# Returns "Hello Beautiful World"
# Remove
result := REMOVEAT("Hello World", 5, 6) # Remove " World"
# Returns "Hello"ENCODE, DECODE - Encoding#
# XML encoding
encoded := ENCODE("<tag>", "xml") # Returns "<tag>"
decoded := DECODE("<tag>", "xml") # Returns "<tag>"
# Base64 encoding
encoded := ENCODE("Hello", "base64") # Returns "SGVsbG8="
decoded := DECODE("SGVsbG8=", "base64") # Returns "Hello"TOBYTES - Convert String to Bytes#
# Convert to bytes (default: ISO-8859-1)
bytes := TOBYTES("Hello")
# With encoding
bytes := TOBYTES("Hello", "utf-8")
# Use for binary protocols
data := TOBYTES("AT+CMD\r\n")
SENDDATA(data)Collection Functions#
ORDER - Sort Collection#
# Sort ascending
sorted := ORDER(5, 2, 8, 1, 9)
# Returns {1, 2, 5, 8, 9}
# Sort descending
sorted := ORDERDESC(5, 2, 8, 1, 9)
# Returns {9, 8, 5, 2, 1}
# Strict versions (fail if NaN present)
sorted := ORDERSTRICT(5, NaN, 2) # Returns NaN
sorted := ORDERDESCSTRICT(5, NaN, 2) # Returns NaNORDERINDEX - Get Sort Indices#
# Get indices that would sort the data
values := {50, 20, 80, 10}
indices := ORDERINDEX(values)
# Returns {3, 1, 0, 2}
# Meaning: element at index 3 is smallest (10),
# element at index 1 is next (20), etc.
# Access sorted values
first := GETAT(values, GETAT(indices, 0)) # Gets 10Date and Time Functions#
NOW - Current Date/Time#
# Current local time
now := NOW()
# Current UTC time
nowUtc := NOW(DateTimeKind.Utc)
# Get components
hour := now.Hour
minute := now.Minute
dayOfWeek := now.DayOfWeekDATETIME - Create Date/Time#
# From components (year, month, day, hour, min, sec, ms, kind)
dt := DATETIME(2024, 1, 15, 10, 30, 0, 0, DateTimeKind.Utc)
# Parse from string
dt := DATETIME("2024-01-15 10:30:00")
# From ticks
dt := DATETIME(638400000000000000)DATETIMEADD - Add Time#
# DATETIMEADD(datetime, years, months, days, hours, mins, secs, ms)
# Add 1 day
tomorrow := DATETIMEADD(NOW(), 0, 0, 1, 0, 0, 0, 0)
# Add 2 hours and 30 minutes
later := DATETIMEADD(NOW(), 0, 0, 0, 2, 30, 0, 0)
# Subtract (use negative numbers)
yesterday := DATETIMEADD(NOW(), 0, 0, -1, 0, 0, 0, 0)TIMESPAN - Duration#
# From milliseconds
duration := TIMESPAN(5000) # 5 seconds
# From components (days, hours, minutes, seconds, milliseconds)
duration := TIMESPAN(0, 2, 30, 0, 0) # 2 hours 30 minutes
# Access components
hours := duration.Hours
minutes := duration.Minutes
totalSeconds := duration.TotalSecondsBit and Byte Operations#
GETBIT, SETBIT - Bit Manipulation#
# Get bit at position (0-31)
bit := GETBIT(5, 0) # Returns 1 (binary: 0101, bit 0 is 1)
bit := GETBIT(5, 1) # Returns 0 (binary: 0101, bit 1 is 0)
# Set bit at position
result := SETBIT(0, 2, 1) # Returns 4 (binary: 0100)
result := SETBIT(7, 1, 0) # Returns 5 (binary: 0101)Use Case - Read Multiple Flags:
# Read status register
status := MODBUSR(H, 300, UInt16)
# Extract individual flags
alarm := GETBIT(status, 0) # Bit 0: Alarm
warning := GETBIT(status, 1) # Bit 1: Warning
fault := GETBIT(status, 2) # Bit 2: Fault
running := GETBIT(status, 3) # Bit 3: Running
IF alarm = 1
# Handle alarm
ENDGETBITS, SETBITS - Multiple Bits#
# Get multiple bits
# GETBITS(number, startBit, numberOfBits)
value := GETBITS(0xFF, 4, 4) # Returns 0x0F (bits 4-7)
# Set multiple bits
# SETBITS(number, startBit, numberOfBits, value)
result := SETBITS(0, 4, 4, 0x0F) # Returns 0xF0GETBYTE, SETBYTE - Byte Manipulation#
# Get byte at index (0-3 for 32-bit)
byte := GETBYTE(0x12345678, 0) # Returns 0x78 (low byte)
byte := GETBYTE(0x12345678, 3) # Returns 0x12 (high byte)
# Set byte at index
result := SETBYTE(0, 1, 0xFF) # Returns 0xFF00FROMBCD, TOBCD - BCD Conversion#
# Convert from BCD (Binary Coded Decimal)
decimal := FROMBCD(0x1234) # Returns 1234
# Convert to BCD
bcd := TOBCD(1234) # Returns 0x1234Use Case - BCD Encoded Modbus:
# Some devices store numbers in BCD format
bcdValue := MODBUSR(H, 500, UInt16)
actualValue := FROMBCD(bcdValue)
RETURN(actualValue)Color Functions#
HSVTORGB - Convert HSV to RGB#
# HSVTORGB(hue, saturation, value)
# Hue: 0-360 degrees
# Saturation: 0.0-1.0
# Value (brightness): 0.0-1.0
rgb := HSVTORGB(0, 1.0, 1.0) # Red
rgb := HSVTORGB(120, 1.0, 1.0) # Green
rgb := HSVTORGB(240, 1.0, 1.0) # Blue
red := rgb.Red # 0-255
green := rgb.Green # 0-255
blue := rgb.Blue # 0-255Use Case - Color Temperature to RGB:
# User sets color wheel position (0-360)
hue := colorWheelPosition
# Convert to RGB for LED strip
rgb := HSVTORGB(hue, 1.0, 1.0)
# Write to Modbus RGB controller
MODBUSW(H, 1000, UInt8, rgb.Red, rgb.Green, rgb.Blue)RGBTOHSV - Convert RGB to HSV#
# RGBTOHSV(red, green, blue)
# Red, Green, Blue: 0-255
hsv := RGBTOHSV(255, 0, 0) # Red
hue := hsv.Hue # 0-360
saturation := hsv.Saturation # 0.0-1.0
value := hsv.Value # 0.0-1.0Utility Functions#
DEWPOINT - Dew Point Calculation#
# Calculate dew point from temperature and humidity
# DEWPOINT(temperature_celsius, humidity_percent)
dewpoint := DEWPOINT(25, 60)
# Returns dew point temperature in CelsiusLINEAR - Linear Interpolation#
# LINEAR(input, x1, y1, x2, y2, [type])
# Maps input from range [x1, x2] to range [y1, y2]
# Convert 0-100% to 4-20mA
current := LINEAR(percent, 0, 4, 100, 20)
# Examples:
# percent=0 → 4mA
# percent=50 → 12mA
# percent=100 → 20mATypes:
LinearType.DEFAULT- Clamp to boundsLinearType.INFINITE- Extrapolate beyond bounds
ADDERROR, ADDWARNING, ADDINFO - Logging#
# Add error message
ADDERROR("Sensor offline")
ADDERROR(100, "Critical temperature")
# Add warning
ADDWARNING("Low battery")
# Add info
ADDINFO("System started")Modbus Communication#
Overview#
Modbus is a widely used industrial protocol. TapHome Script provides three functions:
- MODBUSR - Read from Modbus registers
- MODBUSW - Write to Modbus registers
- MODBUSWNE - Write only if value changed (optimization)
Register Types#
| Code | Name | Description | Read/Write |
|---|---|---|---|
SC | Single Coil | Single on/off bit | Write only |
C | Coil | On/off bits (discrete outputs) | Read/Write |
SH | Single Holding | Single 16-bit register | Write only |
H | Holding Register | 16-bit read/write registers | Read/Write |
D | Discrete Input | On/off bits (read-only) | Read only |
A | Analog Input | 16-bit registers (read-only) | Read only |
Data Types#
| Type | Size | Range | Description |
|---|---|---|---|
Int16 | 16-bit | -32,768 to 32,767 | Signed integer |
UInt16 | 16-bit | 0 to 65,535 | Unsigned integer |
Int32 | 32-bit | -2,147,483,648 to 2,147,483,647 | Signed integer |
UInt32 | 32-bit | 0 to 4,294,967,295 | Unsigned integer |
Float | 32-bit | IEEE 754 | Floating point |
Bool | 16-bit | 0 or 1 | Boolean |
String | Variable | Text | Character string |
Endianness#
Big Endian (most significant byte first) - Default for most devices:
BigEndianInt16,BigEndianUint16BigEndianInt32,BigEndianUint32BigEndianFloat
Little Endian (least significant byte first):
LittleEndianInt16,LittleEndianUint16LittleEndianInt32,LittleEndianUint32LittleEndianFloat
Byte Swap (swap bytes within each 16-bit register):
BigEndianInt32ByteSwap,LittleEndianInt32ByteSwapBigEndianUint32ByteSwap,LittleEndianUint32ByteSwapBigEndianFloatByteSwap,LittleEndianFloatByteSwap
MODBUSR - Read from Modbus#
Syntax:
MODBUSR(registerType, registerNumber, dataType)
MODBUSR(registerType, registerNumber, dataType, numberOfRegisters) # For stringsExamples:
# Read 16-bit signed integer
temperature := MODBUSR(H, 100, BigEndianInt16)
# Read 16-bit unsigned integer
pressure := MODBUSR(H, 200, UInt16)
# Read 32-bit integer (uses 2 registers)
energy := MODBUSR(H, 300, BigEndianInt32)
# Read float (uses 2 registers)
voltage := MODBUSR(H, 400, LittleEndianFloat)
# Read boolean from coil
pump := MODBUSR(C, 50, Bool)
# Read string (10 registers = 20 characters max)
deviceName := MODBUSR(H, 1000, String, 10)Real-World Example - Energy Meter:
# Read power (Float, registers 100-101)
power := MODBUSR(H, 100, BigEndianFloat)
# Read energy (UInt32, registers 200-201)
energy := MODBUSR(H, 200, BigEndianUint32)
# Read voltage (Int16, register 300)
voltage := MODBUSR(H, 300, Int16)
# Calculate total cost
cost := energy * 0.12 # $0.12 per kWh
RETURN(cost)MODBUSW - Write to Modbus#
Syntax:
MODBUSW(registerType, registerNumber, dataType, value)
MODBUSW(registerType, registerNumber, dataType, value1, value2, ...) # Multiple valuesExamples:
# Write 16-bit integer
MODBUSW(H, 100, BigEndianInt16, 1234)
# Write 32-bit integer
MODBUSW(H, 200, BigEndianInt32, 1000000)
# Write float
MODBUSW(H, 300, BigEndianFloat, 25.5)
# Write boolean to coil
MODBUSW(C, 50, Bool, TRUE)
# Write multiple values (auto-increment registers)
MODBUSW(H, 1000, UInt16, 100, 200, 300, 400)
# Writes: register 1000=100, 1001=200, 1002=300, 1003=400Real-World Example - Thermostat Control:
# Read current temperature
currentTemp := MODBUSR(H, 100, BigEndianInt16)
# Read setpoint
setpoint := MODBUSR(H, 200, BigEndianInt16)
# Calculate heating state (0=off, 1=on)
heating := HYSTERESIS(currentTemp, setpoint + 1, setpoint - 1, 0, 1, heating)
# Write heating command
MODBUSW(C, 50, Bool, heating)
RETURN(heating)MODBUSWNE - Write if Not Equal#
Writes value only if it differs from the current register value. This reduces Modbus traffic.
Syntax:
MODBUSWNE(registerType, registerNumber, dataType, value)Example:
# Only write if value changed
targetTemp := 22
MODBUSWNE(H, 200, Int16, targetTemp)
# This is more efficient than:
# MODBUSW(H, 200, Int16, targetTemp) # Writes every timeCommon Modbus Patterns#
Pattern 1: Read-Modify-Write#
# Read current value
current := MODBUSR(H, 100, UInt16)
# Modify
current := current + 10
# Write back
MODBUSW(H, 100, UInt16, current)Pattern 2: Multi-Register Status#
# Read status register
status := MODBUSR(H, 300, UInt16)
# Extract individual flags using bit operations
alarm := GETBIT(status, 0)
warning := GETBIT(status, 1)
fault := GETBIT(status, 2)
running := GETBIT(status, 3)
# Return combined state
IF alarm = 1
RETURN(-1) # Alarm
ELSEIF warning = 1
RETURN(0) # Warning
ELSEIF running = 1
RETURN(1) # Running
ELSE
RETURN(2) # Stopped
ENDPattern 3: Batch Read#
# Read multiple sequential registers efficiently
temp1 := MODBUSR(H, 100, Int16)
temp2 := MODBUSR(H, 101, Int16)
temp3 := MODBUSR(H, 102, Int16)
# Calculate average
average := AVG(temp1, temp2, temp3)
RETURN(average)Network Communication#
HTTP/REST API#
SENDHTTPREQUEST - Send HTTP Request#
Syntax:
SENDHTTPREQUEST(path)
SENDHTTPREQUEST(path, method)
SENDHTTPREQUEST(path, method, body)
SENDHTTPREQUEST(path, method, body, header1, header2, ...)
SENDHTTPREQUEST(httpRequestObject)Examples:
# Simple GET request
response := SENDHTTPREQUEST("/api/data")
# POST with JSON body
response := SENDHTTPREQUEST("/api/update", "POST", "{\"value\": 25}")
# With headers
response := SENDHTTPREQUEST("/api/data", "GET", "",
"Authorization: Bearer token123",
"Content-Type: application/json")
# Using HttpRequest object
req := HTTPREQUEST("/api/data", "POST", "{\"temp\": 22}")
response := SENDHTTPREQUEST(req)Working with Response:
response := SENDHTTPREQUEST("/api/temperature")
IF response.IsSuccess
# Parse JSON response
temperature := PARSEJSON(response.Content, "$.temperature")
RETURN(temperature)
ELSE
# Handle error
ADDERROR("HTTP Error: " + TOSTRING(response.StatusCode))
RETURN(NaN)
ENDReal-World Example - Weather API:
# Get weather from API
response := SENDHTTPREQUEST("/api/weather?city=London", "GET")
IF response.IsSuccess
# Parse JSON
temp := PARSEJSON(response.Content, "$.main.temp")
humidity := PARSEJSON(response.Content, "$.main.humidity")
# Convert Kelvin to Celsius
tempC := temp - 273.15
RETURN(tempC)
ELSE
RETURN(NaN)
ENDHTTPREQUEST - Create Request Object#
# Create reusable request
req := HTTPREQUEST("/api/data", "POST", "{\"value\": 25}")
# Modify request
req.Method := "PUT"
req.Body := "{\"value\": 30}"
# Send request
response := SENDHTTPREQUEST(req)MQTT#
MQTTPUBLISH - Publish MQTT Message#
Syntax:
MQTTPUBLISH(topic, data)Examples:
# Publish string
MQTTPUBLISH("home/sensor/temperature", "22.5")
# Publish JSON
json := "{\"temp\": " + TOSTRING(temperature) + "}"
MQTTPUBLISH("home/sensor/data", json)
# Publish bytes
bytes := TOBYTES("binary data")
MQTTPUBLISH("device/raw/data", bytes)Real-World Example - MQTT Bridge:
# Read from Modbus
temperature := MODBUSR(H, 100, BigEndianInt16)
humidity := MODBUSR(H, 102, BigEndianInt16)
# Create JSON payload
json := "{\"temperature\": " + TOSTRING(temperature) +
", \"humidity\": " + TOSTRING(humidity) + "}"
# Publish to MQTT
MQTTPUBLISH("sensors/room1/data", json)
RETURN(temperature)FTP#
FTPDOWNLOAD - Download File#
# Download file as bytes
data := FTPDOWNLOAD("/config/settings.txt")
# Convert to string and parse
text := TOSTRING(data)
value := PARSETEXT(text, "Temperature=", "\n")
RETURN(value)FTPUPLOAD - Upload File#
# Upload text file
data := "Temperature: 22.5\nHumidity: 60"
FTPUPLOAD("/logs/sensor.log", data, "write")
# Append to log file
timestamp := TOSTRING(NOW())
logEntry := timestamp + ": Temperature = 22.5\n"
FTPUPLOAD("/logs/sensor.log", logEntry, "append")Real-World Example - Data Logger:
# Read sensors
temp := MODBUSR(H, 100, BigEndianInt16)
humidity := MODBUSR(H, 102, BigEndianInt16)
# Create CSV line
timestamp := TOSTRING(NOW())
csvLine := timestamp + "," + TOSTRING(temp) + "," + TOSTRING(humidity) + "\n"
# Append to FTP log file
FTPUPLOAD("/logs/data.csv", csvLine, "append")
RETURN(temp)Parsing and Data Extraction#
PARSETEXT - Extract Text#
Syntax:
PARSETEXT(source, leftPattern)
PARSETEXT(source, leftPattern, rightPattern)Examples:
# Extract number after pattern
text := "Temperature: 22.5°C"
temp := PARSETEXT(text, "Temperature: ", "°")
# Returns: 22.5 (as number)
# Extract without right pattern (reads until non-alphanumeric)
text := "Value=123 Units"
value := PARSETEXT(text, "Value=")
# Returns: 123
# Legacy format with multiple patterns
html := "<div>Temperature<span>22.5</span></div>"
temp := PARSETEXT(html, "<div>...Temperature...<span>", "</span>")
# Returns: 22.5Real-World Example - Parse HTTP Response:
response := SENDHTTPREQUEST("/api/status")
# Extract value from plain text response
# Response: "Status: OK, Temperature: 22.5, Humidity: 60"
temp := PARSETEXT(response.Content, "Temperature: ", ",")
humidity := PARSETEXT(response.Content, "Humidity: ")
RETURN(temp)PARSEJSON - Parse JSON#
Syntax:
PARSEJSON(jsonData, jsonPath)
PARSEJSON(jsonData, jsonPath, ignoreError)JSONPath Examples:
$.temperature- Get top-level property$.sensor.value- Get nested property$.sensors[0].temp- Get array element$.sensors[*].temp- Get all temps from array
Examples:
# Simple property
json := "{\"temperature\": 22.5}"
temp := PARSEJSON(json, "$.temperature")
# Returns: 22.5
# Nested object
json := "{\"sensor\": {\"name\": \"Room1\", \"value\": 22}}"
value := PARSEJSON(json, "$.sensor.value")
# Returns: 22
# Array element
json := "{\"sensors\": [{\"id\": 1, \"temp\": 20}, {\"id\": 2, \"temp\": 22}]}"
temp := PARSEJSON(json, "$.sensors[1].temp")
# Returns: 22
# With error handling
temp := PARSEJSON(json, "$.missing", true)
IF ISNULL(temp)
# Property not found
temp := 0
ENDReal-World Example - Weather API:
# Get weather data
response := SENDHTTPREQUEST("/api/weather?city=London")
IF response.IsSuccess
# Parse nested JSON
# Response: {"main": {"temp": 295.15, "humidity": 60}, "weather": [{"description": "clear sky"}]}
temp := PARSEJSON(response.Content, "$.main.temp")
humidity := PARSEJSON(response.Content, "$.main.humidity")
description := PARSEJSON(response.Content, "$.weather[0].description")
# Convert Kelvin to Celsius
tempC := temp - 273.15
RETURN(tempC)
ENDPARSEXML - Parse XML#
Syntax:
PARSEXML(xmlData, xpathExpression)XPath Examples:
//temperature- Find temperature element anywhere/root/sensor/value- Specific path from root//sensor[@id='1']/value- Element with attribute//sensor/@value- Get attribute value
Examples:
# Simple element
xml := "<root><temperature>22.5</temperature></root>"
temp := PARSEXML(xml, "//temperature")
# Returns: 22.5
# With attribute
xml := "<sensors><sensor id='1' value='20'/><sensor id='2' value='22'/></sensors>"
value := PARSEXML(xml, "//sensor[@id='2']/@value")
# Returns: 22
# Nested path
xml := "<root><device><sensor><value>100</value></sensor></device></root>"
value := PARSEXML(xml, "/root/device/sensor/value")
# Returns: 100Real-World Example - SOAP API:
# Call SOAP service
soapBody := "<soap:Envelope><soap:Body><GetTemperature/></soap:Body></soap:Envelope>"
response := SENDHTTPREQUEST("/api/soap", "POST", soapBody,
"Content-Type: text/xml")
IF response.IsSuccess
# Parse SOAP response
# Response: <soap:Envelope><soap:Body><GetTemperatureResponse><Temperature>22.5</Temperature></GetTemperatureResponse></soap:Body></soap:Envelope>
temp := PARSEXML(response.Content, "//Temperature")
RETURN(temp)
ENDCommon Integration Examples#
Example 1: Temperature Monitoring with Alerts#
# Read temperature from Modbus sensor
temperature := MODBUSR(H, 100, BigEndianInt16)
# Define thresholds
warningTemp := 30
criticalTemp := 35
# Check thresholds
IF temperature >= criticalTemp
ADDERROR(101, "Critical temperature: " + TOSTRING(temperature) + "°C")
RETURN(2) # Critical state
ELSEIF temperature >= warningTemp
ADDWARNING(100, "High temperature: " + TOSTRING(temperature) + "°C")
RETURN(1) # Warning state
ELSE
RETURN(0) # Normal state
ENDExample 2: Multi-Sensor Average with Error Handling#
# Read three temperature sensors
t1 := MODBUSR(H, 100, BigEndianInt16)
t2 := MODBUSR(H, 102, BigEndianInt16)
t3 := MODBUSR(H, 104, BigEndianInt16)
# Check for errors (some Modbus devices return -32768 for errors)
IF t1 = -32768 OR ISNAN(t1)
ADDERROR("Sensor 1 offline")
t1 := NaN
END
IF t2 = -32768 OR ISNAN(t2)
ADDERROR("Sensor 2 offline")
t2 := NaN
END
IF t3 = -32768 OR ISNAN(t3)
ADDERROR("Sensor 3 offline")
t3 := NaN
END
# Calculate average (AVG ignores NaN values)
average := AVG(t1, t2, t3)
IF ISNAN(average)
ADDERROR("All sensors offline")
RETURN(NaN)
END
RETURN(average)Example 3: HVAC Control with Hysteresis#
# Read current temperature
temperature := MODBUSR(H, 100, BigEndianInt16)
# Read setpoint (target temperature)
setpoint := MODBUSR(H, 200, BigEndianInt16)
# Calculate deadband (±1°C around setpoint)
upperLimit := setpoint + 1
lowerLimit := setpoint - 1
# Heating control (on below lower, off above upper)
heating := HYSTERESIS(temperature, upperLimit, lowerLimit, 0, 1, heating)
# Cooling control (on above upper, off below lower)
cooling := HYSTERESIS(temperature, upperLimit, lowerLimit, 1, 0, cooling)
# Write outputs to Modbus
MODBUSW(C, 50, Bool, heating) # Heating coil
MODBUSW(C, 51, Bool, cooling) # Cooling coil
# Return status: -1=cooling, 0=idle, 1=heating
IF cooling = 1
RETURN(-1)
ELSEIF heating = 1
RETURN(1)
ELSE
RETURN(0)
ENDExample 4: Energy Meter with Cost Calculation#
# Read energy (kWh) from meter
energy := MODBUSR(H, 100, BigEndianFloat)
# Read current power (W)
power := MODBUSR(H, 200, BigEndianFloat)
# Define electricity rates ($/kWh)
peakRate := 0.25
offPeakRate := 0.12
# Get current hour
now := NOW()
hour := now.Hour
# Determine rate (peak: 7am-11pm, off-peak: 11pm-7am)
rate := IF(hour >= 7 AND hour < 23, peakRate, offPeakRate)
# Calculate cost
cost := energy * rate
# Store cost back to Modbus (for display)
MODBUSW(H, 300, BigEndianFloat, cost)
# Log to MQTT
json := "{\"energy\": " + TOSTRING(energy) +
", \"power\": " + TOSTRING(power) +
", \"cost\": " + TOSTRING(cost) + "}"
MQTTPUBLISH("meter/data", json)
RETURN(cost)Example 5: REST API Integration with Retry#
# Try to get data from REST API
maxRetries := 3
retry := 0
success := FALSE
WHILE retry < maxRetries AND !success
response := SENDHTTPREQUEST("/api/sensor/123")
IF response.IsSuccess
# Parse temperature from JSON
temperature := PARSEJSON(response.Content, "$.temperature", true)
IF !ISNULL(temperature)
success := TRUE
RETURN(temperature)
END
END
retry := retry + 1
IF !success
# Wait before retry (not real sleep, just example)
ADDWARNING("API retry " + TOSTRING(retry))
END
LOOP
# All retries failed
ADDERROR("Failed to get temperature from API")
RETURN(NaN)Example 6: Data Logger to FTP#
# Read sensors
temperature := MODBUSR(H, 100, BigEndianInt16)
humidity := MODBUSR(H, 102, BigEndianInt16)
pressure := MODBUSR(H, 104, BigEndianInt16)
# Get timestamp
now := NOW()
timestamp := TOSTRING(now.Year) + "-" +
TOSTRING(now.Month) + "-" +
TOSTRING(now.Day) + " " +
TOSTRING(now.Hour) + ":" +
TOSTRING(now.Minute)
# Create CSV line
csvLine := timestamp + "," +
TOSTRING(temperature) + "," +
TOSTRING(humidity) + "," +
TOSTRING(pressure) + "\n"
# Upload to FTP (append mode)
FTPUPLOAD("/logs/sensor_data.csv", csvLine, "append")
RETURN(temperature)Example 7: Modbus to MQTT Bridge#
# Read all sensors from Modbus
temp := MODBUSR(H, 100, BigEndianInt16)
humidity := MODBUSR(H, 102, BigEndianInt16)
co2 := MODBUSR(H, 104, UInt16)
pressure := MODBUSR(H, 106, BigEndianFloat)
# Read device status
statusReg := MODBUSR(H, 200, UInt16)
alarm := GETBIT(statusReg, 0)
warning := GETBIT(statusReg, 1)
fault := GETBIT(statusReg, 2)
# Build JSON payload
json := "{" +
"\"temperature\": " + TOSTRING(temp) + "," +
"\"humidity\": " + TOSTRING(humidity) + "," +
"\"co2\": " + TOSTRING(co2) + "," +
"\"pressure\": " + TOSTRING(pressure) + "," +
"\"alarm\": " + TOSTRING(alarm) + "," +
"\"warning\": " + TOSTRING(warning) + "," +
"\"fault\": " + TOSTRING(fault) +
"}"
# Publish to MQTT
MQTTPUBLISH("building/room1/sensors", json)
RETURN(temp)Example 8: Linear Scaling (4-20mA to Engineering Units)#
# Read 4-20mA signal from analog input
# (Assuming Modbus device returns mA as integer, e.g., 40-200 for 4.0-20.0mA)
analogInput := MODBUSR(A, 0, UInt16)
# Convert to mA
currentMA := analogInput / 10.0
# Validate 4-20mA range
IF currentMA < 3.5 OR currentMA > 20.5
ADDERROR("Analog input out of range: " + TOSTRING(currentMA) + "mA")
RETURN(NaN)
END
# Scale to engineering units
# Example: 4-20mA represents 0-100°C
temperature := LINEAR(currentMA, 4, 0, 20, 100)
RETURN(temperature)Best Practices#
1. Error Handling#
Always handle potential errors:
# Check for NaN
value := MODBUSR(H, 100, Int16)
IF ISNAN(value)
ADDERROR("Failed to read register")
RETURN(NaN)
END
# Check HTTP success
response := SENDHTTPREQUEST("/api/data")
IF !response.IsSuccess
ADDERROR("HTTP error: " + TOSTRING(response.StatusCode))
RETURN(NaN)
END
# Check JSON parsing
temp := PARSEJSON(json, "$.temperature", true)
IF ISNULL(temp)
ADDWARNING("Temperature not found in JSON")
temp := 0 # Default value
END2. Use Comments#
Document your logic:
# Read temperature from Modbus sensor on holding register 100
temperature := MODBUSR(H, 100, BigEndianInt16)
# Convert from 0.1°C resolution to 1°C
temperature := temperature / 10
# Apply hysteresis to prevent oscillation
# ON when temp drops below 20°C, OFF when rises above 22°C
heating := HYSTERESIS(temperature, 22, 20, 0, 1, heating)3. Validate Input Ranges#
# Read sensor value
value := MODBUSR(H, 100, Int16)
# Validate range (sensor spec: -40 to 125°C)
IF value < -40 OR value > 125
ADDERROR("Sensor reading out of range: " + TOSTRING(value))
RETURN(NaN)
END4. Use Constants#
Define constants at the top for easy maintenance:
# Configuration
tempRegister := 100
setpointRegister := 200
heatingCoil := 50
upperDeadband := 1
lowerDeadband := 1
# Read values
temperature := MODBUSR(H, tempRegister, BigEndianInt16)
setpoint := MODBUSR(H, setpointRegister, BigEndianInt16)
# Control logic
heating := HYSTERESIS(temperature,
setpoint + upperDeadband,
setpoint - lowerDeadband,
0, 1, heating)
# Write output
MODBUSW(C, heatingCoil, Bool, heating)5. Minimize Modbus Traffic#
Use MODBUSWNE for values that don’t change often:
# Bad - writes every time
setpoint := 22
MODBUSW(H, 200, Int16, setpoint) # Writes even if already 22
# Good - writes only if changed
setpoint := 22
MODBUSWNE(H, 200, Int16, setpoint) # Writes only if different6. Use Meaningful Variable Names#
# Bad
x := MODBUSR(H, 100, Int16)
y := MODBUSR(H, 200, Int16)
z := HYSTERESIS(x, y + 1, y - 1, 0, 1, z)
# Good
temperature := MODBUSR(H, 100, Int16)
setpoint := MODBUSR(H, 200, Int16)
heating := HYSTERESIS(temperature, setpoint + 1, setpoint - 1, 0, 1, heating)7. Keep Scripts Simple#
Break complex logic into smaller Smart Rules:
# Instead of one huge script handling everything,
# create separate Smart Rules for:
# - Temperature monitoring (one rule)
# - Alarm checking (one rule)
# - Data logging (one rule)
# - MQTT publishing (one rule)8. Test Thoroughly#
Test edge cases:
# Test with:
# - Normal values
# - Out of range values
# - NaN / null values
# - Network failures
# - Device offline scenariosTroubleshooting#
Common Issues#
Issue 1: NaN Results#
Problem: Script returns NaN unexpectedly
Causes:
- Modbus read failed
- Division by zero
- Invalid data type conversion
- Math operation on NaN
Solution:
value := MODBUSR(H, 100, Int16)
IF ISNAN(value)
ADDERROR("Modbus read failed")
RETURN(0) # or appropriate default
ENDIssue 2: Modbus Data Type Mismatch#
Problem: Wrong values from Modbus
Cause: Incorrect endianness or data type
Solution: Try different combinations:
# Try Big Endian first (most common)
value := MODBUSR(H, 100, BigEndianInt32)
# If wrong, try Little Endian
value := MODBUSR(H, 100, LittleEndianInt32)
# If still wrong, try ByteSwap
value := MODBUSR(H, 100, BigEndianInt32ByteSwap)Issue 3: JSON Parsing Fails#
Problem: PARSEJSON returns null
Causes:
- Invalid JSON
- Wrong JSONPath
- Element doesn’t exist
Solution:
# Use ignore error flag
value := PARSEJSON(json, "$.temperature", true)
IF ISNULL(value)
ADDWARNING("Temperature not found in JSON")
# Log actual JSON for debugging
ADDINFO("JSON: " + json)
value := 0
ENDIssue 4: HTTP Request Fails#
Problem: HTTP requests timeout or fail
Causes:
- Network connectivity
- Wrong URL
- Server down
- Firewall blocking
Solution:
response := SENDHTTPREQUEST("/api/data")
IF !response.IsSuccess
ADDERROR("HTTP " + TOSTRING(response.StatusCode) + ": " + response.ReasonPhrase)
# Check specific errors
IF response.StatusCode = 404
ADDERROR("API endpoint not found")
ELSEIF response.StatusCode = 401
ADDERROR("Authentication required")
ELSEIF response.StatusCode = 500
ADDERROR("Server error")
END
RETURN(NaN)
ENDIssue 5: Infinite Loop#
Problem: Script hangs
Cause: WHILE loop never exits
Solution: Always have exit condition and counter:
maxIterations := 100
counter := 0
WHILE condition AND counter < maxIterations
# ... your code ...
counter := counter + 1
LOOP
IF counter >= maxIterations
ADDERROR("Loop exceeded maximum iterations")
ENDDebugging Tips#
- Use logging functions:
ADDINFO("Temperature: " + TOSTRING(temperature))
ADDINFO("Setpoint: " + TOSTRING(setpoint))- Check intermediate values:
value := MODBUSR(H, 100, Int16)
ADDINFO("Raw Modbus value: " + TOSTRING(value))
scaled := value / 10
ADDINFO("Scaled value: " + TOSTRING(scaled))- Validate data types:
# Check if value is number
IF ISNAN(value)
ADDERROR("Value is NaN")
END
# Check if value is null
IF ISNULL(value)
ADDERROR("Value is null")
END- Test small parts first:
# Comment out complex logic
# Test Modbus read first
temp := MODBUSR(H, 100, Int16)
RETURN(temp)
# Then add logic incrementallyAppendix A: Quick Reference#
Operators#
| Category | Operators |
|---|---|
| Arithmetic | + - * / MOD |
| Comparison | = != <> < > <= >= |
| Logical | AND OR XOR ! |
| Bitwise | & ` |
| Assignment | := += -= *= /= &= ` |
Control Structures#
| Structure | Syntax |
|---|---|
| IF | IF condition ... ELSEIF ... ELSE ... END |
| WHILE | WHILE condition ... LOOP |
| DO-WHILE | DO ... LOOP WHILE condition |
| BREAK | BREAK |
| CONTINUE | CONTINUE |
| RETURN | RETURN(value) |
Common Functions#
| Function | Purpose |
|---|---|
MIN(...) | Minimum value |
MAX(...) | Maximum value |
AVG(...) | Average |
ROUND(x) | Round to integer |
ABS(x) | Absolute value |
HYSTERESIS(...) | Prevent oscillation |
IF(cond, t, f) | Ternary if |
SWITCH(...) | Multi-way branch |
LENGTH(x) | String/collection length |
TOSTRING(x) | Convert to string |
TODOUBLE(x) | Parse number |
Modbus Functions#
| Function | Purpose |
|---|---|
MODBUSR(type, reg, dataType) | Read Modbus |
MODBUSW(type, reg, dataType, val) | Write Modbus |
MODBUSWNE(type, reg, dataType, val) | Write if changed |
Network Functions#
| Function | Purpose |
|---|---|
SENDHTTPREQUEST(...) | HTTP request |
MQTTPUBLISH(topic, data) | MQTT publish |
FTPDOWNLOAD(path) | FTP download |
FTPUPLOAD(path, data, mode) | FTP upload |
PARSEJSON(json, path) | Parse JSON |
PARSEXML(xml, xpath) | Parse XML |
PARSETEXT(text, left, right) | Extract text |
Appendix B: Data Type Quick Reference#
| Type | Description | Example |
|---|---|---|
int | 32-bit signed integer | 42 |
float | 32-bit floating point | 3.14 |
double | 64-bit floating point | 3.14159 |
bool | Boolean | TRUE, FALSE |
string | Text string | "Hello" |
datetime | Date and time | DATETIME(2024, 1, 15, 10, 30, 0) |
timespan | Duration | TIMESPAN(0, 2, 30, 0, 0) |
collection | Array | {1, 2, 3} |
Support and Resources#
For more information:
- Check the TapHome documentation
- Contact TapHome support
- Visit the TapHome community forum