Skip to main content

Script Examples

This page provides practical script examples that demonstrate common patterns for Mango's scripting engine. These examples can be used in meta data points, scripting data sources, or adapted for event handler scripts.

Smoothness Calculation

This script calculates the same smoothness value as the built-in smoothness event detector. You can use it to determine what threshold to set for the smoothness detector by observing the output over time.

To use this script, create a meta data point with a Numeric context variable named signal set to context update mode.

var boxcarLength = 60;
var boxcar = signal.last(boxcarLength);

// As in the detector, if there are fewer than 3 values it is considered smooth
if (boxcar.size() < 3)
return 1;

var prev = Number.NaN;
var lastAngle = Number.NaN;
var sumErr = 0;
var count = 0;

// Walk backwards across the list to perform calculation in time order
for (var i = boxcar.size() - 1; i >= 0; i--) {
var value = boxcar.get(i).value;
if (!isNaN(prev)) {
var opp = value - prev;
var hyp = Math.sqrt(0.1 + opp * opp);
var angle = Math.asin(opp / hyp);
if (!isNaN(lastAngle)) {
var diff = angle - lastAngle;
var norm = diff / Math.PI;
sumErr += norm < 0 ? -norm : norm;
count++;
}
lastAngle = angle;
}
prev = value;
}

var err = sumErr / count;
return (1.0 - err);

How it works: The script retrieves the last 60 values from the signal point, then walks through them calculating the angle change between consecutive values. The sum of normalized angle changes produces an error metric, and 1.0 - error gives the smoothness value (1.0 = perfectly smooth, lower values = more erratic).

Note: Generate history may not produce the same results as real-time operation because the history generator uses stored values rather than live updates for the boxcar.

JsonEmport: Bulk Event Detector Creation

This script demonstrates how to use the JsonEmport API to programmatically add a binary state event detector to all data points identified by a watch list XID. This is useful when you need to add the same detector configuration to many points without manual work.

var xidPrefix = "ED_15m_status_";
var watchListXid = "WL_5275e48f-5819-4971-8462-0d1ed8ea8264";

var dataPoints = JSON.parse(
JsonEmport.getConfiguration("dataPoints")
).dataPoints;
var watchLists = JSON.parse(
JsonEmport.getConfiguration("watchLists")
).watchLists;

function findByXid(list, xid) {
for (var k = 0; k < list.length; k += 1)
if (list[k].xid === xid)
return list[k];
return undefined;
}

var wl = findByXid(watchLists, watchListXid);
if (typeof wl === 'undefined')
throw "Couldn't find watchlist with XID " + watchListXid;

var importPoints = [];

function addDetectorToDataPoint(dp) {
// Check if detector already exists (prevent duplicates)
for (var l = 0; l < dp.eventDetectors.length; l += 1) {
if (dp.eventDetectors[l].xid.startsWith(xidPrefix))
return;
}

var ed = {
"type": "BINARY_STATE",
"sourceType": "DATA_POINT",
"xid": com.serotonin.m2m2.Common.generateXid(xidPrefix),
"name": "",
"alarmLevel": "URGENT",
"durationType": "MINUTES",
"duration": 15,
"state": (dp.textRenderer.type === "BINARY"
&& dp.textRenderer.oneLabel === "Device Ok")
};

dp.eventDetectors.push(ed);
importPoints.push(dp);
}

if (wl.type === 'static') {
for (var j = 0; j < wl.dataPoints.length; j += 1) {
var dp = findByXid(dataPoints, wl.dataPoints[j]);
addDetectorToDataPoint(dp);
}
} else if (wl.type === 'query') {
var addToPoints = DataPointQuery.query(wl.query);
for (var j = 0; j < addToPoints.length; j += 1) {
var dp = findByXid(dataPoints, addToPoints[j].getXid());
addDetectorToDataPoint(dp);
}
}

JsonEmport.doImport(JSON.stringify({"dataPoints": importPoints}));

How it works:

  1. Exports the full configuration of all data points and watch lists as JSON.
  2. Finds the specified watch list by XID.
  3. Iterates over the watch list's data points.
  4. For each point, checks whether the detector already exists (using the XID prefix) to prevent duplicates.
  5. Creates a new binary state event detector with URGENT alarm level and 15-minute duration.
  6. Imports the modified data points back into Mango using JsonEmport.doImport().

Lorenz Equations (Scripting Data Source)

This example implements the Lorenz attractor equations using a scripting data source. It demonstrates how multiple points can be set from a single script and how variables persist between executions.

Create three Numeric data points with variable names x, y, and z on the scripting data source. Set the cron pattern to 0/2 * * * * ? (every 2 seconds) to produce a steady stream of values.

if (x.value == 0 && y.value == 0 && z.value == 0) {
// Initial point values
y.set(1);
}

if (typeof(rho) == "undefined") {
rho = 28;
sigma = 10;
beta = 8 / 3;
dt = 0.01;
}

dx = sigma * (y.value - x.value);
dy = x.value * (rho - z.value) - y.value;
dz = x.value * y.value - beta * z.value;

x.set(x.value + dx * dt);
y.set(y.value + dy * dt);
z.set(z.value + dz * dt);

How it works: The script uses the typeof check to initialize constants (rho, sigma, beta, dt) only on the first execution. Because scripting data source variables persist between executions, these constants are defined once and reused. The Lorenz equations are then applied to compute the next values of x, y, and z, which are set using the .set() method.

When plotted on a chart, the three points trace out the characteristic butterfly-shaped Lorenz attractor.

Simple Calculations

Running Average

Calculate the average of the last N values from a sensor:

var count = 20;
var values = sensor.last(count);
var sum = 0;
for (var i = 0; i < values.size(); i++) {
sum += values.get(i).value;
}
return sum / values.size();

Delta from Previous Value

Return the difference between the current and previous value:

var prev = sensor.lastValue(1);
if (prev != null) {
return sensor.value - prev.value;
}
return 0;

Time Since Last Change

Return the number of seconds since the point last changed value:

var prev = sensor.lastValue(1);
if (prev != null) {
return (sensor.time - prev.time) / 1000;
}
return 0;

Conditional Logic

Return different values based on input conditions:

if (mode.value === 1) {
return setpoint.value + offset.value;
} else if (mode.value === 2) {
return setpoint.value - offset.value;
} else {
return setpoint.value;
}

Unit Conversion (PSI to Bar)

return pressurePsi.value * 0.0689476;

Dewpoint Calculation

Calculate the dewpoint from temperature and relative humidity using the Magnus formula:

var T = temperature.value;  // Celsius
var RH = humidity.value; // Percent

var a = 17.27;
var b = 237.7;
var alpha = ((a * T) / (b + T)) + Math.log(RH / 100);
var dewpoint = (b * alpha) / (a - alpha);

return dewpoint;