Skip to content
Stand with Ukraine flag

Script Calculated Field

Script calculated fields use TBEL (ThingsBoard Expression Language) to perform advanced, real-time computations on telemetry and attributes. Unlike Simple fields, Script fields support conditional logic, iteration over rolling time-series windows, and returning multiple results in a single execution.

Use Script when you need:

  • Multi-step calculations (e.g., dew point, air density, efficiency metrics)
  • Conditional rules (e.g., generate status flags based on multiple inputs)
  • Multiple output keys computed in a single pass
  • Event-like telemetry with explicit timestamps
  • Rolling analytics (rolling average, trend detection, anomaly detection)
  1. Open the Calculated fields page.
  2. Click the + Add calculated field ⇾ Create new calculated field in the top-right corner.
  3. In General, enter a title, specify the entity the field applies to, and select the Script type.
  4. Add Arguments, write the Script function, and configure Output.
  5. Click Add to save.

Script fields support three argument types. The data source can be the current entity, another Device/Asset, the Customer, the Current tenant, or the Current owner.

  • Attribute — a single attribute value (Server, Client, or Shared scope) with an optional default fallback.
  • Latest telemetry — the most recent value of a telemetry key, with an optional default fallback.
  • Time series rolling — a sliding historical window of telemetry values. Exposes aggregation methods and merge utilities — see Rolling Argument API below.

The script must implement:

function calculate(ctx, arg1, arg2, ...): object | object[]

The function returns a single JSON object (multiple output keys at server time) or an array of JSON objects (multiple time series records with explicit timestamps).

Context object (ctx):

PropertyDescription
ctx.latestTsMost recent timestamp (ms) from telemetry-based arguments
ctx.args.<arg>.tsTimestamp of a single-value argument
ctx.args.<arg>.valueValue of a single-value argument
ctx.args.<arg>.timeWindow{ startTs, endTs } of a rolling argument
ctx.args.<arg>.valuesArray of { ts, value } for a rolling argument
ctx.args.<arg>.<method>()Built-in methods: mean(), sum(), min(), max(), first(), last(), etc.

Arguments are also passed as direct function parameters, so you can use temperature directly instead of ctx.args.temperature.value.

Example — dew point:

function calculate(ctx, temperature, humidity) {
var a = 17.625;
var b = 243.04;
var alpha = ((a * temperature) / (b + temperature)) + Math.log(humidity / 100.0);
var dewPoint = toFixed((b * alpha) / (a - alpha), 1);
return { "dewPoint": dewPoint };
}

Example — time series with explicit timestamp:

function calculate(ctx, temperatureF) {
var temperatureC = (temperatureF - 32) / 1.8;
return {
"ts": ctx.latestTs,
"values": { "temperatureC": toFixed(temperatureC, 2) }
};
}

Example — array output (one record per time window entry):

function calculate(ctx, temperature, defrost) {
var merged = temperature.merge(defrost);
var result = [];
foreach(item: merged) {
if (item.v1 > -5.0 && item.v2 == 0) {
result.add({ ts: item.ts, values: { issue: { temperature: item.v1, defrostState: false } } });
}
}
return result;
}

Stores results as time series or attributes. For array returns, each element becomes a separate time series record.

For output strategy options, see Calculated Fields — Output Strategy.

When an argument uses the Time series rolling type, it exposes a rich API for aggregating and aligning historical telemetry. The argument is passed directly as a function parameter (e.g., temperature) and provides:

  • .timeWindow{ startTs, endTs } of the configured window
  • .values — array of { ts, value } records within the window
  • Aggregation methods and merge utilities described below

All methods accept an optional ignoreNaN boolean (default true):

MethodignoreNaN = true (default)ignoreNaN = false
max()Highest value, ignoring NaNNaN if any NaN exists
min()Lowest value, ignoring NaNNaN if any NaN exists
mean(), avg()Average, ignoring NaNNaN if any NaN exists
std()Standard deviation, ignoring NaNNaN if any NaN exists
median()Median, ignoring NaNNaN if any NaN exists
count()Count of values, ignoring NaNCount including NaN
last()Most recent non-NaN valueLast value even if NaN
first()Oldest non-NaN valueFirst value even if NaN
sum()Total sum, ignoring NaNNaN if any NaN exists

Given this rolling argument:

{
"temperature": {
"timeWindow": { "startTs": 1740643762896, "endTs": 1740644662896 },
"values": [
{ "ts": 1740644350000, "value": 72.32 },
{ "ts": 1740644360000, "value": 72.86 },
{ "ts": 1740644370000, "value": 73.58 },
{ "ts": 1740644380000, "value": "NaN" }
]
}
}

Result:

var avgTemp = temperature.mean(); // 72.92
var tempMax = temperature.max(); // 73.58
var valueCount = temperature.count(); // 3
var avgTempNaN = temperature.mean(false); // NaN
var tempMaxNaN = temperature.max(false); // NaN
var valueCountNaN = temperature.count(false); // 4

Use merge and mergeAll to align timestamps across multiple telemetry streams. Missing values at a given timestamp are filled with the most recent known value; if no previous value exists, NaN is used.

MethodDescriptionReturns
merge(other, settings)Merges with another rolling argument. Aligns timestamps and fills missing values with the previous available value.Merged object with timeWindow and aligned values
mergeAll(others, settings)Merges multiple rolling arguments. Aligns timestamps and fills missing values with the previous available value.Merged object with timeWindow and aligned values

Parameters:

ParameterTypeDescription
otherRolling argumentSingle rolling argument to combine with
othersRolling argument[]Array of rolling arguments to combine with
settings.ignoreNaNbooleanWhether to skip NaN values when filling gaps (default true)
settings.timeWindow{ startTs, endTs }Override the time window for the merge operation

Each merged record has the shape { ts, v1, v2, ... } where v1 is the value from the calling argument and v2, v3, … are values from the merged arguments. Missing values at a timestamp are forward-filled from the most recent known reading.

Merge two arguments:

var merged = temperature.merge(humidity, { ignoreNaN: false });
// merged.values[i] = { ts, v1: tempValue, v2: humidityValue }

Merge three or more arguments:

var merged = temperature.mergeAll([humidity, pressure], { ignoreNaN: true });
// merged.values[i] = { ts, v1: tempValue, v2: humidityValue, v3: pressureValue }

Common use cases: sensor data synchronization, anomaly detection across multiple streams, predictive maintenance combining vibration, temperature, and operational status.

A device sending temperature in Fahrenheit under the temperatureF key.
Goal: convert to Celsius, round to two decimal places, and store as temperatureC using the same timestamp.

  1. Open the Calculated fields page.
  2. Click the + Add calculated field ⇾ Create new calculated field in the top-right corner.
  3. In General:
    • Title: F to C
    • Entity: a device sending temperature (or its device profile)
    • Type: Script.
  4. Add argument:
    • Argument type: Latest telemetry
    • Time series key: temperatureF
    • Argument name: temperatureF
  5. Write the script:
    function calculate(ctx, temperatureF) {
    var temperatureC = (temperatureF - 32) / 1.8;
    return { "ts": ctx.latestTs, "values": { "temperatureC": toFixed(temperatureC, 2) } };
    }
  6. Output type: Time series.
  7. Click Add.

Result: temperatureC appears on the device’s Latest telemetry tab whenever temperatureF updates.

Example 2: Air Density from Cross-Entity Data

Section titled “Example 2: Air Density from Cross-Entity Data”

The Building A asset has two related devices:

  • Smart Device — publishes temperature (°F) as telemetry
  • Altimeter — provides altitude as a server attribute

Goal: compute air density and store it as airDensity telemetry on the Building A asset.

  1. Open the Calculated fields page.
  2. Click the + Add calculated field ⇾ Create new calculated field in the top-right corner.
  3. In General:
    • Title: Air density calculation
    • Apply the calculated field to the building asset profile.
    • Type: Script.
  4. Add two arguments:
    • Argument 1:
      • Entity: Altimeter
      • Type: Attribute (Server),
      • Key: altitude
      • Name: altitude
    • Argument 2:
      • Entity: Smart Device
      • Type: Time series rolling
      • Key: temperature
      • Name: temperature
      • Time window: 15 minutes
  5. Write the script:
    function calculate(ctx, altitude, temperature) {
    var avgTemperature = temperature.mean(); // Get average temperature
    var temperatureK = (avgTemperature - 32) * (5 / 9) + 273.15; // Convert Fahrenheit to Kelvin
    var pressure = 101325 * Math.pow((1 - 2.25577e-5 * altitude), 5.25588); // Estimate air pressure based on altitude
    var airDensity = pressure / (287.05 * temperatureK); // Air density formula
    return { "airDensity": toFixed(airDensity, 2) };
    }
  6. Output type: Time series.
  7. Click Add.

Result: airDensity appears on Building A’s Latest telemetry tab.

A freezer device publishes temperature (°C) and defrost (0 = OFF, 1 = ON). Goal: generate an issue telemetry record whenever defrost = 0 and temperature > -5°C.

  1. Open the Calculated fields page.
  2. Click the + Add calculated field ⇾ Create new calculated field in the top-right corner.
  3. In General:
    • Title: Air density calculation
    • Apply the calculated field to the freezer device profile.
    • Type: Script.
  4. Add two rolling arguments (15-minute window each):
    • Argument 1:
      • Entity: Current entity (freezer device)
      • Type: Time series rolling
      • Key: defrost
      • Name: defrost
      • Time window: 15 minutes
    • Argument 2:
      • Entity: Current entity (freezer device)
      • Type: Time series rolling
      • Key: temperature
      • Name: temperature
      • Time window: 15 minutes
  5. Write the script:
    function calculate(ctx, temperature, defrost) {
    var merged = temperature.merge(defrost);
    var result = [];
    foreach(item: merged) {
    if (item.v1 > -5.0 && item.v2 == 0) {
    result.add({ ts: item.ts, values: { issue: { temperature: item.v1, defrostState: false } } });
    }
    }
    return result;
    }
  6. Output type: Time series.
  7. Click Add.

Result: When the condition is met, an issue appears on the device with values such as { "temperature": 0.8, "defrostState": false }, indicating a potentially critical state.