20,919,685Total Points24,106Total Sites23,382Cell Towers69,500Racks Monitored56,753HVAC Units Monitored31,322UPS Units Monitored24,788Generators Monitored20,919,685Total Points24,106Total Sites23,382Cell Towers69,500Racks Monitored56,753HVAC Units Monitored31,322UPS Units Monitored24,788Generators Monitored20,919,685Total Points24,106Total Sites23,382Cell Towers69,500Racks Monitored56,753HVAC Units Monitored31,322UPS Units Monitored24,788Generators Monitored

Chapter 8: Scripting & AutomationLesson 46 of 65

How to Build Data Validation Scripts

Create scripts that validate, clean, and transform incoming data point values before they are stored, catching sensor errors, out-of-range readings, and data quality issues.

  • 10 min read
  • Intermediate
  • Mango 4.x / 5.x

Overview

Bad data from sensors—spikes, dropouts, error codes, stuck values—can corrupt historical records and trigger false alarms. Data validation scripts catch these problems before values are stored, keeping time-series data clean and alarms reliable.

Issue Example Impact
Spike Temperature jumps from 72 to 9999 for one poll Skews averages and triggers false alarms
Dropout Value reads 0 or -1 when a sensor loses communication Causes false low alarms and incorrect calculations
Stuck value Temperature reads exactly 72.0 for 24 hours Hides real changes and may indicate sensor failure
Error code Device returns -999 or 65535 to indicate a fault Pollutes data if logged as a real value
Out of range Humidity reads 150% or pressure is negative Indicates a physically impossible reading or sensor fault

Prerequisites

  • A running Mango by Radix IoT v4.x or v5.x instance
  • Access to edit data points, create meta points, and configure global scripts
  • Known valid ranges and fault codes for the sensors being monitored
  • Enough historical data to choose realistic validation thresholds

Approach 1: Point Configuration Without Scripting

Mango’s built-in Discard extreme values setting handles simple range validation:

  1. Open the data point’s edit page.
  2. Enable Discard extreme values.
  3. Set the Discard low limit and Discard high limit.
  4. Save the point.

Values outside the configured range are silently discarded: they are not logged or displayed. This is the simplest approach and should be the first line of defense.

Note

Do not confuse Discard extreme values with Prevent extreme sets. They are separate settings. Discard extreme values silently drops out-of-range logged readings; Prevent extreme sets controls attempts to set a point to an extreme value.

Approach 2: Meta Point Validation

For logic more complex than range checks, create a meta point that reads the raw source point and produces a validated value.

Clamp Values to a Valid Range

// Context: raw = raw temperature point
var value = raw.value;

if (value < -40) value = -40;
if (value > 300) value = 300;

return value;

Filter Known Error Codes

// Context: raw = source point
var errorCodes = [-999, -1, 65535, 32767];

if (errorCodes.indexOf(raw.value) >= 0) {
    return UNCHANGED; // Leave the meta point's current value in place
}

return raw.value;

Warning

Do not use return null to skip an update. For Numeric, Binary, Multistate, and Alphanumeric meta points, Mango can coerce null to a type default such as 0, false, or an empty string and log it as a real value. Use return UNCHANGED to leave the current value untouched.

Detect Spikes

// Reject values that change more than 50 units in one poll
// Context: raw = source point
var current = raw.value;
var previous = raw.ago(SECOND, 1);

if (previous !== null) {
    var diff = Math.abs(current - previous);

    if (diff > 50) {
        LOG.warn(
            "Spike detected on " +
            raw.dataPointWrapper.name +
            ": " +
            current
        );
        return UNCHANGED;
    }
}

return current;

Warning

raw.past(periodType, count) returns one aggregate statistics object containing values such as average, minimum, maximum, and count. It does not return an array of samples. Use raw.ago(periodType, count) when you need one prior value; it returns the number directly.

Detect a Stuck Value

// Context: raw = source point
var to = raw.time;
var from = to - (2 * 60 * 60 * 1000);
var history = raw.pointValuesBetween(from, to);

if (history.size() > 10) {
    var allSame = true;
    var first = history.get(0).doubleValue;

    for (var i = 1; i < history.size(); i++) {
        if (history.get(i).doubleValue !== first) {
            allSame = false;
            break;
        }
    }

    if (allSame) {
        LOG.warn(
            "Stuck value detected on " +
            raw.dataPointWrapper.name +
            ": " +
            raw.value
        );
        return -1; // Multistate code for "suspect"
    }
}

return 0; // Multistate code for "normal"

Warning

pointValuesBetween() returns a Java list, not a JavaScript array. Use .size() and
.get(i), which is the idiom in Mango’s own scripting help. .length is engine-dependent:
it is undefined on Nashorn, so a loop written as i < history.length simply never runs
and reports nothing — no error, no detection. The same applies to the list returned by
DataPointQuery.query().

Note

This pattern returns 0 and -1 as status codes rather than returning the temperature. Configure the meta point as Multistate, not Numeric, so these values have a defined meaning on dashboards.

Approach 3: Global Script for Batch Validation

A scheduled global script can review recently logged values across many points and flag anomalies:

// Nightly validation: check all temperature points for spikes
var points = DataPointQuery.query("eq(tags.pointType,temperature)");
var to = new Date().getTime();
var from = to - (24 * 60 * 60 * 1000);

for (var i = 0; i < points.size(); i++) {
    var pt = points.get(i);

    if (pt.runtime === null) continue;

    var values = pt.runtime.pointValuesBetween(from, to);
    var min = Infinity;
    var max = -Infinity;

    for (var j = 0; j < values.size(); j++) {
        var value = values.get(j).doubleValue;
        if (value < min) min = value;
        if (value > max) max = value;
    }

    if (max - min > 100) {
        LOG.warn(
            "Possible spike on " +
            pt.name +
            ": range = " +
            min +
            " to " +
            max
        );
    }
}

Use DataPointQuery.query(rql) with an RQL expression such as eq(tags.pointType,temperature). Each result is a data point wrapper. Its .runtime property is null when the point is not enabled; otherwise, it exposes pointValuesBetween(from, to) for retrieving history.

Building a Validation Strategy

  • Enable Discard extreme values on numeric points as the first line of defense.
  • Add meta point validation where you need spike detection, error-code filtering, or more complex logic.
  • Schedule a global script for periodic batch validation.
  • Log validation events so recurring data-quality problems can be traced to their source.

Verification

  • Confirm an in-range source value reaches the validated point unchanged.
  • Submit a known error code and verify the meta point retains its previous value.
  • Submit an artificial spike and confirm the script logs a warning containing raw.dataPointWrapper.name.
  • Confirm stuck-value detection uses samples returned by raw.pointValuesBetween(from, to).
  • Review historical data to ensure rejected values were not stored as 0, false, or an empty string.

Troubleshooting

Rejected Values Become Zero or False

Replace return null with return UNCHANGED. A null result can be coerced to the point data type’s default value.

Spike Detection Fails or Reports Undefined

  • Use raw.ago(periodType, count) for one earlier value.
  • Use raw.dataPointWrapper.name for the point name.
  • Do not treat raw.past() as an array.

Stuck-Value Detection Throws an Error

  • Retrieve samples with raw.pointValuesBetween(from, to).
  • Iterate with .size() and .get(i) — the result is a Java list, and .length is
    undefined on Nashorn, which makes the loop silently skip.
  • Compare each sample’s .doubleValue, not its boxed .value.
  • Confirm the time range contains enough logged samples.

Global Script Returns No Points

  • Confirm the points have the expected tags.
  • Use DataPointQuery.query("eq(tags.pointType,temperature)").
  • Iterate the result with .size() and .get(i). It is a Java list, so points.length
    can be undefined and the loop will report zero points without raising an error.
  • Skip results whose .runtime property is null.
  • Guide to Scripting Locations in Mango
  • How to Create Unit Conversions with Meta Points
  • How to Use Global Scripts for Reusable Functions
  • How to Configure Point Logging for Optimal Data Collection

Get free access to all 65 Mango by Radix IoT training lessons

One short form unlocks every lesson and its video in this browser.

This form is hosted by Zoho. It loads only after you enable support cookies or choose to load it below.

We honored your browser’s Global Privacy Control signal — optional cookies are off and nothing is tracked.