Skip to content

Netsocs Template Language

The Netsocs Template Language is the Netsocs expression engine that lets you build dynamic values inside any text field. It is used in action configurations, event payloads, dashboard filters, and any field that supports templates.


Basic syntax

Expressions are written between double curly braces {{ }}. Anything outside the braces is treated as literal text.

{{expression}}

A single string can contain several expressions:

"Sensor {{sensor_id}} reports {{get_state(sensor_id)}}"

Key rule: A template with a single expression returns the native type of the result (number, boolean, object, array). A template with mixed text or multiple expressions always returns a string.


Testing templates

In the playground you write the template directly in the TEMPLATE field (plain text, without wrapping it in JSON). Below, in CONTEXT, you define in JSON format the variables available to the template. The OUTPUT panel shows the result and its type in real time.

Template Language Playground

In the example shown in the image:

  • TEMPLATE:
    Hello, {{name}}! The sum is {{n_sum(1, 2, 3)}}.
    
  • CONTEXT:
    {
      "name": "World"
    }
    
  • OUTPUT: Hello, World! The sum is 6. (type string)

The context injects the variables available inside {{ }}. Built-in functions —both the Netsocs ones and the language-native ones— are always available without declaring anything in the context.

In the examples in this document the template is shown exactly as you write it in TEMPLATE; when an example needs variables, its JSON corresponds to the CONTEXT field.


Context variables

Variables passed in context are referenced directly by name inside {{ }}.

{
  "template": "{{name}} is {{age}} years old",
  "context": {
    "name": "Ana",
    "age": 30
  }
}
Result: "Ana is 30 years old"

To access nested properties use a dot .:

{
  "template": "{{device.brand}}",
  "context": {
    "device": { "brand": "Hanwha", "model": "XNV-8080R" }
  }
}
Result: "Hanwha"


Result types

Type Description Template example
string Text, or a mix of text + expression "Hi {{name}}", "text"
number Integer or decimal number {{42}}, {{1 + 2.5}}
boolean true or false {{5 > 3}}
object JSON object {{{ "k": "v" }}}
array JSON array {{[1, 2, 3]}}
null Explicit null (returned by empty functions)

Operators

Arithmetic

Operator Description Example Result
+ Addition {{10 + 5}} 15
- Subtraction {{10 - 3}} 7
* Multiplication {{4 * 3}} 12
/ Division {{10 / 4}} 2.5
% Modulo {{10 % 3}} 1

Comparison

Operator Example Result
== {{3 == 3}} true
!= {{3 != 4}} true
> {{5 > 3}} true
< {{2 < 4}} true
>= {{5 >= 5}} true
<= {{4 <= 4}} true

Logical

Operator Example Result
&& {{true && false}} false
\|\| {{true \|\| false}} true
! {{!true}} false

Ternary

{{condition ? "value_if_true" : "value_if_false"}}

Example:

{{"Temperature " + (temperature > 30 ? "HIGH" : "normal")}}

With temperature = 35"Temperature HIGH"


Math functions

n_sum(...values) / n_suma(...values)

Sums all the arguments. Accepts numbers, numeric strings, and arrays.

{{n_sum(1, 2, 3)}}           → 6
{{n_sum(1, '2.5', 3)}}       → 6.5
{{n_sum([10, 20, 30])}}      → 60
{{n_suma(5, 10)}}            → 15   (Spanish alias)

Using context:

{
  "template": "{{n_sum(indoor_temp, outdoor_temp) / 2}}",
  "context": { "indoor_temp": 22, "outdoor_temp": 18 }
}
Result: 20


n_multiply(...values) / n_multiplicar(...values)

Multiplies all the arguments. Accepts numbers, numeric strings, and arrays.

{{n_multiply(2, 3, 4)}}      → 24
{{n_multiply(1.5, 2.0)}}     → 3
{{n_multiply([2, 5])}}       → 10

Unit conversion:

{
  "template": "{{n_multiply(voltage, current)}} W",
  "context": { "voltage": 220, "current": 2.5 }
}
Result: "550 W"


n_abs(value)

Returns the absolute value.

{{n_abs(-15)}}    → 15
{{n_abs(7.3)}}    → 7.3
{{n_abs(-0.5)}}   → 0.5

add(a, b)

Adds two integers.

{{add(5, 3)}}    → 8

To add more than two values or work with decimals, prefer n_sum.


Numeric comparison functions

These functions are useful when values arrive as strings from an object's state (where "25.5" > "9" would fail with native operators).

n_less_than(a, b) / n_menor_que(a, b)

{{n_less_than(3, 5)}}         → true
{{n_less_than('9', '25.5')}}  → true   (compares numeric strings correctly)
{{n_less_than(5, 5)}}         → false

n_greater_than(a, b) / n_mayor_que(a, b)

{{n_greater_than(10, 3)}}     → true
{{n_greater_than(3, 10)}}     → false

n_less_equal_than(a, b) / n_menor_igual_que(a, b)

{{n_less_equal_than(5, 5)}}   → true
{{n_less_equal_than(6, 5)}}   → false

n_greater_equal_than(a, b) / n_mayor_igual_que(a, b)

{{n_greater_equal_than(5, 5)}}  → true
{{n_greater_equal_than(4, 5)}}  → false

n_equal(a, b) / n_igual(a, b)

Strict numeric comparison. Accepts numeric strings.

{{n_equal(1, 1)}}           → true
{{n_equal('1.5', 1.5)}}     → true
{{n_equal(1, 2)}}           → false

n_equal_epsilon(a, b, epsilon) / n_igual_epsilon(a, b, epsilon)

Compares whether two values are equal within a tolerance margin. Useful for noisy sensors.

{{n_equal_epsilon(25.001, 25.002, 0.01)}}   → true
{{n_equal_epsilon(25.0, 25.1, 0.01)}}       → false

Real example: Alert if the temperature deviates more than 0.5°C from the setpoint:

{
  "template": "{{n_not(n_equal_epsilon(current_temp, setpoint_temp, 0.5))}}",
  "context": { "current_temp": 22.8, "setpoint_temp": 22.0 }
}
Result: true (deviation is greater than 0.5°C)


Logical functions

n_and(a, b)

Logical AND. Accepts booleans, strings ("true"/"false"/"1"/"0"/"on"/"off") and numbers.

{{n_and(true, true)}}       → true
{{n_and(true, false)}}      → false
{{n_and("1", "on")}}        → true
{{n_and(1, 0)}}             → false

n_or(a, b)

Logical OR with the same types as n_and.

{{n_or(true, false)}}       → true
{{n_or(false, false)}}      → false
{{n_or("0", "off")}}        → false

n_not(a)

Logical negation.

{{n_not(true)}}             → false
{{n_not(false)}}            → true
{{n_not("on")}}             → false
{{n_not(0)}}                → true

Combined example: Verify that a door is closed AND the alarm is armed:

{
  "template": "{{n_and(get_state_property('door.main', 'status'), get_state_property('alarm.zone1', 'armed'))}}",
  "context": {}
}


String functions

netconcat(a, b)

Concatenates two strings.

{{netconcat("sensor.", "001")}}    → "sensor.001"

For concatenation with the native operator, you can use + directly in the expression when both values are context strings.


letter_add(letter, amount) / sumar_letra(letter, amount)

Adds an integer to a letter, advancing in English alphabetical order, treating letters as a number in spreadsheet-column-style base 26 (A=1, B=2, …, Z=26, AA=27, AB=28, …). When passing Z, a new letter is prepended.

Preserves the case of the input: if the letter comes in lowercase, the result is returned in lowercase. amount accepts a number or a numeric string. If the result falls below A, it returns "".

{{letter_add("A", 2)}}      → "C"
{{letter_add("Z", 3)}}      → "AC"
{{letter_add("Z", 1)}}      → "AA"
{{letter_add("AZ", 1)}}     → "BA"
{{letter_add("a", 2)}}      → "c"     (lowercase in, lowercase out)
{{letter_add("A", "2")}}    → "C"     (amount as a string)
{{sumar_letra("A", 2)}}     → "C"     (Spanish alias)

Example - generate sequential column labels:

{
  "template": "Column: {{letter_add(col, offset)}}",
  "context": { "col": "A", "offset": 4 }
}
Result: "Column: E"


Date and time functions

All date functions return strings in RFC3339 format ("2026-05-27T14:30:00Z"). The start_of_* and end_of_* functions use the server's local time.

n_now()

Current UTC timestamp.

{{n_now()}}   → "2026-05-27T14:30:00Z"

minutes_ago(minutes)

Timestamp from N minutes ago (UTC).

{{minutes_ago(30)}}    → "2026-05-27T14:00:00Z"
{{minutes_ago(1440)}}  → (yesterday at the same time)
{{minutes_ago(-60)}}   → (in 60 minutes, a future value)

Use in an event filter:

{
  "template": "{{minutes_ago(60)}}",
  "context": {}
}
Returns the timestamp from one hour ago to use as start_time in queries.


time_in_range(time, start, end [, offset]) / hora_en_rango(...)

Indicates (true/false) whether a time falls within the range [start, end], with inclusive bounds. Times are written as "HH:MM" (it also accepts "HH:MM:SS"; seconds are ignored).

  • If time is "" or "now", it uses the current time (the server works in UTC) adding the given offset, which corresponds to the country's time zone (e.g. -4, +2, -5; fractions such as -4.5 are allowed).
  • The range supports midnight crossing: if start > end (e.g. 22:0006:00), it is interpreted as spanning midnight.
{{time_in_range("15:32", "12:00", "16:00")}}   → true
{{time_in_range("16:01", "12:00", "16:00")}}   → false
{{time_in_range("12:00", "12:00", "16:00")}}   → true   (start inclusive)
{{time_in_range("16:00", "12:00", "16:00")}}   → true   (end inclusive)
{{time_in_range("23:00", "22:00", "06:00")}}   → true   (crosses midnight)
{{time_in_range("12:00", "22:00", "06:00")}}   → false
{{time_in_range("now", "08:00", "18:00", -4)}} → is the current time (UTC-4) within business hours?
{{hora_en_rango("15:32", "12:00", "16:00")}}   → true   (Spanish alias)

Example - enable an action only during the country's business hours (UTC-5):

{
  "template": "{{time_in_range(\"now\", \"08:00\", \"17:00\", -5)}}",
  "context": {}
}


Start-of-period functions

Function Description
start_of_minute() Start of the current minute (seconds = 0)
start_of_hour() Start of the current hour
start_of_day() 00:00:00 of the current day
start_of_week() Monday 00:00:00 of the current week
start_of_month() Day 1, 00:00:00 of the current month
start_of_quarter() Start of the quarter (Jan/Apr/Jul/Oct)
start_of_year() January 1, 00:00:00 of the current year
{{start_of_day()}}      → "2026-05-27T00:00:00Z"
{{start_of_week()}}     → "2026-05-25T00:00:00Z"  (Monday)
{{start_of_month()}}    → "2026-05-01T00:00:00Z"
{{start_of_quarter()}}  → "2026-04-01T00:00:00Z"
{{start_of_year()}}     → "2026-01-01T00:00:00Z"

End-of-period functions

Function Description
end_of_minute() End of the current minute (sec = 59.999…)
end_of_hour() End of the current hour
end_of_day() 23:59:59.999… of the current day
end_of_week() Sunday 23:59:59.999… of the current week
end_of_month() Last second of the current month
end_of_quarter() End of the quarter (Mar/Jun/Sep/Dec)
end_of_year() December 31 23:59:59.999… of the current year
{{end_of_day()}}      → "2026-05-27T23:59:59.999999999Z"
{{end_of_week()}}     → "2026-06-01T23:59:59.999999999Z"  (Sunday)
{{end_of_month()}}    → "2026-05-31T23:59:59.999999999Z"
{{end_of_year()}}     → "2026-12-31T23:59:59.999999999Z"

Example - current month range:

{
  "template": "{{[start_of_month(), end_of_month()]}}",
  "context": {}
}
Result: ["2026-05-01T00:00:00Z", "2026-05-31T23:59:59.999999999Z"] (type array)


Object access functions

These functions query the state of objects on the Netsocs platform in real time.

get_state(objectId)

Returns the object's main state as a string.

{{get_state("hikvision.camera.ch1")}}   → "online"
{{get_state("door.main.lock")}}         → "locked"

Example: Generate a dynamic message:

{
  "template": "The camera is: {{get_state('hikvision.camera.ch1')}}",
  "context": {}
}


get_state_property(objectId, property)

Returns the value of a specific property from the object's additional state.

{{get_state_property("sensor.temp.001", "temperature")}}  → "23.5"
{{get_state_property("camera.001", "recording")}}         → "true"

Example - compute a temperature difference:

{
  "template": "{{n_sum(get_state_property('sensor.interior', 'temp'), '-', get_state_property('sensor.exterior', 'temp'))}}",
  "context": {}
}


get_object_state(objectId)

Returns the full StateChangeRecord object with all the data from the latest state.

{{get_object_state("sensor.001")}}

Allows access to record fields:

{{get_object_state("sensor.001").State}}
{{get_object_state("sensor.001").CreatedAt}}


get_object_by_id(objectId)

Returns the full object (metadata + configuration).

{{get_object_by_id("camera.001").Name}}
{{get_object_by_id("camera.001").Domain}}

get_objects_by_ids(ids)

Returns an array of objects. Accepts a comma-separated list, an array, or an array from the context. Maximum 1000 IDs.

{
  "template": "{{get_objects_by_ids(camera_ids)}}",
  "context": {
    "camera_ids": ["camera.001", "camera.002", "camera.003"]
  }
}

It also accepts a string with comma-separated IDs:

{{get_objects_by_ids("camera.001,camera.002")}}


get_objectids_where_state(state)

Returns an array of IDs of all objects that have the given state.

{{get_objectids_where_state("online")}}   → ["camera.001", "camera.003", "sensor.002"]
{{get_objectids_where_state("alarm")}}    → ["zone.1", "zone.3"]

Example - count online cameras:

{
  "template": "{{n_sum(get_objectids_where_state('online'))}}",
  "context": {}
}

Note: n_sum over an array of non-numeric strings returns 0. To count, prefer count_objects_by_prop.


get_objectids_where_property(property, value)

Returns an array of IDs of objects where a state property has exactly the given value.

{{get_objectids_where_property("zone", "A")}}
{{get_objectids_where_property("model", "XNV-8080R")}}

get_first_objectid_where_property(property, value)

Returns the ID of the first object that meets the condition. Useful when a single result is expected.

{{get_first_objectid_where_property("serial_number", "SN-123456")}}
→ "hanwha.camera.main"

count_objects_by_prop(property, condition, value [, domain])

Counts objects whose additional state meets a condition. The only condition currently supported is "eq" (equal).

count_objects_by_prop(property, "eq", value)
count_objects_by_prop(property, "eq", value, domain)

Examples:

{{count_objects_by_prop("status", "eq", "open")}}
→ 5   (5 objects have status = "open")

{{count_objects_by_prop("recording", "eq", "true", "cameras")}}
→ 3   (3 objects in the "cameras" domain have recording = "true")

Dashboard example:

{
  "template": "Open doors: {{count_objects_by_prop('status', 'eq', 'open', 'access.doors')}}",
  "context": {}
}


Event function

get_total_events(eventTypes [, objectsId [, domains [, startTime, endTime]]])

Counts events that match the given filters. All parameters are comma-separated strings when there are multiple values.

Parameters:

Parameter Type Description
eventTypes string Comma-separated event types
objectsId string Comma-separated object IDs (optional)
domains string Comma-separated domains (optional)
startTime string Start date RFC3339 or YYYY-MM-DD:HH:MM:SS
endTime string End date RFC3339 or YYYY-MM-DD:HH:MM:SS

Examples:

Count all motion events for the day:

{
  "template": "{{get_total_events('motion_detected', '', '', start_of_day(), end_of_day())}}",
  "context": {}
}

Count events from a specific camera in the last hour:

{
  "template": "{{get_total_events('motion_detected,tamper', 'hanwha.camera.001', '', minutes_ago(60), n_now())}}",
  "context": {}
}

Count this month's alarms in a domain:

{
  "template": "{{get_total_events('alarm_triggered', '', 'security.zones', start_of_month(), end_of_month())}}",
  "context": {}
}


Data types in expressions

JSON objects

You can build an object directly in the expression:

{{{"name": "sensor1", "value": 42}}}
Result: {"name":"sensor1","value":42} (type object)

Access properties of inline objects:

{{{"greeting": "hi"}.greeting}}   → "hi"

JSON arrays

{{[1, 2, 3, 4, 5]}}   → [1,2,3,4,5] (type `array`)
{{["cam1", "cam2"]}}  → ["cam1","cam2"]

Dates with date() and duration()

The underlying library (expr-lang) provides native functions to compare dates:

{{date("2026-01-01T00:00:00Z") < date("2026-12-31T00:00:00Z")}}   → true

{{date(start_date) - date(end_date) < duration("24h")}}

With context:

{
  "template": "{{(date(request.Time) - date(resource.Age) < duration(\"24h\")) ? \"recent\" : \"old\"}}",
  "context": {
    "request": { "Time": "2026-05-27T23:59:00Z" },
    "resource": { "Age": "2026-05-27T00:00:00Z" }
  }
}
Result: "recent"


Base language (expr-lang)

In addition to the Netsocs functions described above, the entire base language is available inside {{ }}, with its operators and a broad set of built-in functions. Everything in this section is written the same way: inside double braces in the playground.

Literals

Type Examples
Comment /* ... */ or // ...
Boolean true, false
Integer 42, 0x2A (hex), 0o52 (octal), 0b101010 (binary)
Decimal 0.5, .5
String "foo", 'bar'
Multiline string `text on\nmultiple lines` (backticks, no escapes)
Array [1, 2, 3]
Map (object) {a: 1, b: 2}
Null nil

Strings with double/single quotes support escapes: \n, \t, \uXXXX.

Operators

Category Operators
Arithmetic +, -, *, /, % (modulo), ^ or ** (power)
Comparison ==, !=, <, >, <=, >=
Logical not or !, and or &&, or or \|\|
Conditional ?: (ternary), ?? (null coalescing)
Membership [], ., ?., in
String + (concatenate), contains, startsWith, endsWith
Regex matches
Range ..
Slice [:]
Pipe \|
{{2 ^ 10}}                          → 1024
{{"a" in ["a", "b"]}}               → true
{{"name" in {"name": "x"}}}         → true
{{"hello" contains "ell"}}          → true
{{"camera.001" startsWith "cam"}}   → true
{{"sensor.temp" endsWith ".temp"}}  → true
{{"abc123" matches "[a-z]+[0-9]+"}} → true
{{1..3}}                            → [1, 2, 3]

Access and indices

Fields are accessed with . or [] (equivalent). Negative indices count from the end.

{{device.brand}}        equivalent to   {{device["brand"]}}
{{array[0]}}            → first element
{{array[-1]}}           → last element

Slices [:]

With array = [1, 2, 3, 4, 5]:

{{array[1:4]}}   → [2, 3, 4]
{{array[:3]}}    → [1, 2, 3]
{{array[3:]}}    → [4, 5]

Optional chaining ?. and coalescing ??

?. avoids errors if an intermediate value is nil (returns nil); ?? returns the right-hand side when the left is nil.

{{person?.address?.city}}
{{person?.name ?? "Anonymous"}}

Pipe operator |

Passes the result of the left side as the first argument of the right side.

{{name | lower() | split(" ")}}   equivalent to   {{split(lower(name), " ")}}

Variables (let)

You can declare temporary variables with let, separating statements with ;.

{{let x = 42; x * 2}}                → 84
{{let a = 10; let b = 5; a + b}}     → 15

$env

$env is a map with all the context variables. Useful for names with spaces or to check whether a variable exists.

{{'temperature' in $env}}            → true if the context carries "temperature"
{{$env["var with spaces"]}}

Predicates

Functions like filter, map, all, any, etc. take a predicate: an expression where # is the current element. If the element is an object, you can omit # and use .Field directly.

{{filter(0..9, # % 2 == 0)}}             → [0, 2, 4, 6, 8]
{{filter(cameras, .state == "online")}}
{{map(sensors, .temperature)}}

String functions (base)

{{trim("  Hi  ")}}                         → "Hi"
{{trim("__Hi__", "_")}}                    → "Hi"
{{trimPrefix("HelloWorld", "Hello")}}      → "World"
{{trimSuffix("HelloWorld", "World")}}      → "Hello"
{{upper("hi")}}                            → "HI"
{{lower("HI")}}                            → "hi"
{{split("a,b,c", ",")}}                    → ["a", "b", "c"]
{{splitAfter("a,b,c", ",")}}               → ["a,", "b,", "c"]
{{replace("Hello World", "World", "All")}} → "Hello All"
{{repeat("ab", 3)}}                        → "ababab"
{{indexOf("sensor.001", ".")}}             → 6
{{lastIndexOf("a.b.c", ".")}}              → 3
{{hasPrefix("cam.1", "cam")}}              → true
{{hasSuffix("cam.1", ".1")}}               → true

Numeric functions (base)

{{max(5, 7)}}      → 7
{{min(5, 7)}}      → 5
{{abs(-5)}}        → 5
{{ceil(1.5)}}      → 2
{{floor(1.5)}}     → 1
{{round(1.5)}}     → 2

For sums/multiplications/comparisons that tolerate numeric strings from state, prefer the Netsocs functions n_sum, n_multiply, n_greater_than, etc.

Array functions (base)

{{all([2, 4, 6], # % 2 == 0)}}        → true
{{any([1, 2, 3], # > 2)}}             → true
{{one([1, 2, 3], # == 2)}}            → true
{{none([1, 2, 3], # > 5)}}            → true
{{map([1, 2, 3], # * 2)}}             → [2, 4, 6]
{{filter([1, 2, 3, 4], # > 2)}}       → [3, 4]
{{find([1, 2, 3, 4], # > 2)}}         → 3
{{findIndex([1, 2, 3, 4], # > 2)}}    → 2
{{findLast([1, 2, 3, 4], # > 2)}}     → 4
{{findLastIndex([1, 2, 3, 4], # > 2)}}→ 3
{{count([1, 2, 3, 4], # > 2)}}        → 2
{{count([true, false, true])}}        → 2
{{concat([1, 2], [3, 4])}}            → [1, 2, 3, 4]
{{flatten([1, [2, [3]]])}}            → [1, 2, 3]
{{uniq([1, 2, 2, 3])}}                → [1, 2, 3]
{{join(["a", "b", "c"], ",")}}        → "a,b,c"
{{reduce(1..4, #acc + #, 0)}}         → 10
{{sum([1, 2, 3])}}                    → 6
{{mean([1, 2, 3])}}                   → 2
{{median([1, 2, 3])}}                 → 2
{{first([1, 2, 3])}}                  → 1
{{last([1, 2, 3])}}                   → 3
{{take([1, 2, 3, 4], 2)}}             → [1, 2]
{{reverse([3, 1, 4])}}                → [4, 1, 3]
{{sort([3, 1, 4])}}                   → [1, 3, 4]
{{sort([3, 1, 4], "desc")}}           → [4, 3, 1]
{{sortBy(users, .age)}}
{{sortBy(users, .age, "desc")}}
{{groupBy(users, .age)}}

In reduce, the predicate has access to # (current element), #acc (accumulator) and #index (index).

Map functions (base)

{{keys({"name": "John", "age": 30})}}    → ["name", "age"]
{{values({"name": "John", "age": 30})}}  → ["John", 30]
{{toPairs({"a": 1, "b": 2})}}            → [["a", 1], ["b", 2]]
{{fromPairs([["a", 1], ["b", 2]])}}      → {"a": 1, "b": 2}

Type conversion (base)

{{type(42)}}                  → "int"
{{type("hi")}}               → "string"
{{int("123")}}               → 123
{{float("123.45")}}          → 123.45
{{string(123)}}              → "123"
{{toJSON({"a": 1})}}         → "{\"a\":1}"
{{fromJSON("{\"a\":1}")}}    → {"a": 1}
{{toBase64("Hi")}}           → "SGk="
{{fromBase64("SGk=")}}       → "Hi"

Miscellaneous (base)

{{len([1, 2, 3])}}                       → 3
{{len("Hi")}}                            → 2
{{get([10, 20, 30], 1)}}                 → 20
{{get({"name": "John"}, "name")}}        → "John"

Bit functions (base)

{{bitand(0b1010, 0b1100)}}   → 8     (0b1000)
{{bitor(0b1010, 0b1100)}}    → 14    (0b1110)
{{bitxor(0b1010, 0b1100)}}   → 6     (0b110)
{{bitnand(0b1010, 0b1100)}}  → 2
{{bitnot(0b1010)}}           → -11
{{bitshl(0b1011, 2)}}        → 44
{{bitshr(0b1011, 2)}}        → 2

Complete examples

1. Camera status with a state label

{
  "template": "Camera {{cam_id}}: {{get_state(cam_id) == 'online' ? 'ACTIVE' : 'INACTIVE'}}",
  "context": { "cam_id": "hanwha.camera.lobby" }
}

2. Check whether a temperature is within range

{
  "template": "{{n_and(n_greater_equal_than(get_state_property('sensor.temp', 'value'), '18'), n_less_equal_than(get_state_property('sensor.temp', 'value'), '26'))}}",
  "context": {}
}
Returns true if the temperature is between 18 and 26.

3. Dynamic payload for a driver action

{
  "template": "{\"camera_id\": \"{{camera_id}}\", \"recording\": true, \"start_time\": \"{{start_of_day()}}\", \"end_time\": \"{{end_of_day()}}\"}",
  "context": { "camera_id": "cam.001" }
}

4. Daily event summary

{
  "template": "Today: {{get_total_events('motion_detected', '', 'cameras', start_of_day(), end_of_day())}} motions, {{get_total_events('alarm_triggered', '', '', start_of_day(), end_of_day())}} alarms",
  "context": {}
}

5. Get the IDs of cameras in alarm and build a payload

{
  "template": "{{get_objectids_where_state('alarm')}}",
  "context": {}
}
Returns an array of IDs (type array) ready to use in another field.

6. Compute the average of two sensors

{
  "template": "{{n_sum(get_state_property('sensor.north', 'temp'), get_state_property('sensor.south', 'temp')) / 2}} °C",
  "context": {}
}

7. Number of online devices in a specific domain

{
  "template": "Active cameras: {{count_objects_by_prop('connection_status', 'eq', 'connected', 'cameras')}}",
  "context": {}
}

8. Check whether a device reported recently

{
  "template": "{{date(get_state_property('sensor.001', 'last_seen')) > date(minutes_ago(10))}}",
  "context": {}
}
Returns true if the sensor reported in the last 10 minutes.


Access control functions

These functions query, in real time, the data of people registered in the access control module.

get_person(personId)

Returns the full object for a person given their ID. Returns an empty object if the ID does not exist or is blank.

Available fields:

Field Type Description
ID string The person's UUID
ExternalID string External ID (e.g. employee number)
Name string Full name
Photo string Photo URL
PersonType string "employee", "visitor", "contractor"
ActivationDate string / null Activation date (RFC3339)
ExpirationDate string / null Expiration date (RFC3339)
Timezone string Time zone (default "UTC")
Enabled bool Whether the person is enabled
CustomFields object Custom fields {"key": "value"}
APBExempt bool Exempt from anti-passback
ExtendedUnlock bool Extended unlock time
EscortRequired bool Requires an escort
TenantID string Tenant ID
CreatedAt string Creation date (RFC3339)
UpdatedAt string Last update date (RFC3339)

Examples:

Get a person's name:

{{get_person("abc-123-uuid").Name}}   → "María García"

Check whether a person is enabled:

{{get_person("abc-123-uuid").Enabled}}   → true

Use the person type in conditional logic:

{
  "template": "{{get_person(person_id).PersonType == 'visitor' ? 'Limited access' : 'Full access'}}",
  "context": { "person_id": "abc-123-uuid" }
}

Build a payload enriched with cardholder data:

{
  "template": "{\"name\": \"{{get_person(person_id).Name}}\", \"type\": \"{{get_person(person_id).PersonType}}\", \"enabled\": {{get_person(person_id).Enabled}}}",
  "context": { "person_id": "abc-123-uuid" }
}

Access a custom field:

{
  "template": "Department: {{get_person(person_id).CustomFields.department}}",
  "context": { "person_id": "abc-123-uuid" }
}


get_person_location(personId)

Returns a person's current location: whether they are inside, in which area, when they were last seen, and through which reader.

Available fields:

Field Type Description
PersonID string The person's ID
IsInside bool true if the person is inside the area
AreaID string ID of the area where they were last seen
LastReaderID string ID of the reader they passed through
LastSeenAt string / null Timestamp of last sighting (RFC3339)
LastDirection string "entry" or "exit"
TenantID string Tenant ID
UpdatedAt string Last update of the record

Examples:

Check whether a person is inside:

{{get_person_location("abc-123-uuid").IsInside}}   → true

Get the area where a person is:

{{get_person_location("abc-123-uuid").AreaID}}   → "building.entrance.lobby"

Dynamic message with presence status:

{
  "template": "{{get_person(person_id).Name}} is {{get_person_location(person_id).IsInside ? 'inside' : 'outside'}} the building",
  "context": { "person_id": "abc-123-uuid" }
}
Result: "María García is inside the building"

Check the last direction of passage:

{
  "template": "{{get_person_location(person_id).LastDirection == 'entry' ? 'Entered' : 'Exited'}} through {{get_person_location(person_id).LastReaderID}}",
  "context": { "person_id": "abc-123-uuid" }
}


Quick function reference

Function Returns Description
n_sum(...) number Sum of values/array
n_suma(...) number Spanish alias of n_sum
n_multiply(...) number Product of values/array
n_multiplicar(...) number Spanish alias of n_multiply
n_abs(a) number Absolute value
add(a, b) number Sum of two integers
n_less_than(a, b) boolean a < b numeric
n_menor_que(a, b) boolean Alias of n_less_than
n_greater_than(a, b) boolean a > b numeric
n_mayor_que(a, b) boolean Alias of n_greater_than
n_less_equal_than(a, b) boolean a <= b numeric
n_menor_igual_que(a, b) boolean Alias of n_less_equal_than
n_greater_equal_than(a, b) boolean a >= b numeric
n_mayor_igual_que(a, b) boolean Alias of n_greater_equal_than
n_equal(a, b) boolean a == b numeric
n_igual(a, b) boolean Alias of n_equal
n_equal_epsilon(a, b, ε) boolean \|a - b\| <= ε
n_igual_epsilon(a, b, ε) boolean Alias of n_equal_epsilon
n_and(a, b) boolean Logical AND
n_or(a, b) boolean Logical OR
n_not(a) boolean Logical NOT
netconcat(a, b) string Concatenates two strings
letter_add(letter, n) string Adds n to a letter in base-26 (A+2=C, Z+3=AC)
sumar_letra(letter, n) string Spanish alias of letter_add
n_now() string Current UTC timestamp
minutes_ago(n) string Timestamp from N minutes ago
time_in_range(time, start, end [, offset]) boolean Is time within [start, end]? ("now"+offset = current time; supports midnight crossing)
hora_en_rango(...) boolean Spanish alias of time_in_range
start_of_minute() string Start of the current minute
start_of_hour() string Start of the current hour
start_of_day() string Start of the current day
start_of_week() string Start of the week (Monday)
start_of_month() string Start of the current month
start_of_quarter() string Start of the quarter
start_of_year() string Start of the current year
end_of_minute() string End of the current minute
end_of_hour() string End of the current hour
end_of_day() string End of the current day
end_of_week() string End of the week (Sunday)
end_of_month() string End of the current month
end_of_quarter() string End of the quarter
end_of_year() string End of the current year
get_state(id) string Object's main state
get_state_property(id, prop) string Property of the additional state
get_object_state(id) object Full state record
get_object_by_id(id) object Full object
get_objects_by_ids(ids) array List of objects by IDs
get_objectids_where_state(state) array IDs with that state
get_objectids_where_property(prop, val) array IDs where property = value
get_first_objectid_where_property(prop, val) string First matching ID
count_objects_by_prop(prop, cond, val [, domain]) number Counts objects
get_total_events(types [, ids [, domains [, start, end]]]) number Counts events
get_person(personId) object Full data of a person by ID
get_person_location(personId) object Current location of a person