Overview
The Scripting data source runs one script, owned by the data source, on a schedule or whenever a chosen point updates, and the script writes values into points by calling set() on them. That is the opposite of a Meta data source, where each point has its own script and its value is whatever that script returns. Here the script is the producer: one run can set several points at once, which makes it the natural home for pulling data from an external API, combining several sources in one calculation, or generating test data.
Scripts on this data source run on Mango by Radix IoT’s JavaScript engine, Nashorn, which Mango starts without its ES6 option. Write them as ECMAScript 5.1: var and function, not let, const, arrow functions or template literals. (The engine choice, Graal.js or Groovy, exists only on the Script event handler.)
Prerequisites
- Mango with the Scripting data source module
- Permission to create and edit data sources; the Script roles you give the script must be roles you hold
- Basic JavaScript
Step 1: Create the Data Source and Set Its Schedule
- Go to Data integration > Data sources, create a data source, and choose Data source type Scripting
- Enter a Name; the Export ID (XID) is generated for you
- Decide what runs the script:
- On a schedule: switch on Polling. Set an Update period (a number and a unit, optionally Quantized to the start of the period), or tick Use cron and enter a Cron pattern
- On a point change: leave Polling off and tick Updates context on at least one point (Steps 2 and 3)
- Leave Execution delay(seconds) at 0 unless context points need time to update first. The run still uses the time the schedule fired, not the delayed time
- Script roles (permission groups) are the roles the script runs with. It can read and set only points those roles can. A new data source starts on superadmin
- Context update event (Update, Change or Logged) sets which kind of event on an Updates context point triggers a run, for the whole data source
- Tick Sets historical only if the script must write values older than a point’s latest one
Cron patterns are six fields, seconds first:
| Pattern | Meaning |
|---|---|
0 0/5 * * * ? |
Every 5 minutes |
0 0 * * * ? |
Top of every hour |
0 30 6 * * ? |
Daily at 06:30 |
0 0 0 1 * ? |
First day of each month at midnight |
Warning
When you tick Use cron, the Cron pattern field is already filled with * * * * * ?, which runs the script every second. Replace it before you save.
Warning
A Scripting data source must be Polling or have at least one point with Updates context. With neither, Mango refuses to save it: “Must have either be polling or have a point updating context”.
Step 2: Add External Context Points
External context points bring points from other data sources into the script.
- Open External context points (No data points selected) and choose the points
- Each row gets a Key: the variable name the script uses for that point. Keys must be valid JavaScript names and unique
- Tick Updates context on a row if a change on that point should run the script
In the script, a context point with the Key outsideTemp offers:
outsideTemp.value: its current valueoutsideTemp.time: the timestamp of that value (milliseconds since epoch)outsideTemp.ago(MINUTE, 10): its value 10 minutes agooutsideTemp.set(value): sets it, if the point is settable (the value is annotated as coming from the script)
Step 3: Add the Data Source’s Own Points
Save the data source first; its Data points tab then lets you Add points. On a new point’s Scripting Point tab:
- Variable name: how the script refers to this point
- Data type: Numeric, Binary, Multistate or Alphanumeric. Values the script sets are converted to it where possible
- Settable (starts ticked): whether the point can also be set from outside the script, for example from a dashboard
- Updates context: a value set on this point from outside runs the script. Do not set such a point from inside the script
Step 4: Write the Script
The script sets points; it does not return a value. Mango compiles it as-is, not wrapped in a function, so a top-level return is a syntax error and the data source will not save. Write each result with set():
| Data type | Pass to set() | Example |
|---|---|---|
| Numeric | A number | tempC.set(21.5); |
| Binary | true or false |
running.set(speed > 0); |
| Multistate | An integer state | motorMode.set(2); |
| Alphanumeric | A string | status.set("OK"); |
set(value, timestamp) stores the value at a time you choose (milliseconds since epoch); without a timestamp, the run’s time is used.
A Calculation
// outsideTemp: an external context point (Key). tempC: a point on this data source.
var celsius = (outsideTemp.value - 32) * 5 / 9;
tempC.set(Math.round(celsius * 100) / 100);
Several Points from One Run
// motorSpeed: context point. running (Binary) and motorMode (Multistate): points on this data source.
var speed = motorSpeed.value;
running.set(speed > 0);
var mode = 0; // Off
if (speed > 0) mode = 1; // Low
if (speed >= 500) mode = 2; // Medium
if (speed >= 1500) mode = 3; // High
motorMode.set(mode);
An External REST API
HttpBuilder is already available in the script; do not construct it. get(url, headers, parameters) takes three arguments, and the request runs, synchronously, when you call .execute(). A 200 response goes to resp, any other status to err, and a local exception to excp:
// weatherTemp: a Numeric point on this data source.
HttpBuilder.get(
"https://api.open-meteo.com/v1/forecast?latitude=40.7&longitude=-74.0¤t_weather=true",
{}, {})
.resp(function(status, headers, content) {
weatherTemp.set(JSON.parse(content).current_weather.temperature);
return true;
})
.err(function(status, headers, content) {
LOG.warn("Weather API returned status " + status);
return false;
})
.excp(function(exception) {
LOG.error("Weather API call failed: " + exception.getMessage());
return false;
})
.execute();
If the call fails, nothing is set and the point keeps its last value, which is better than setting a sentinel such as -1 that would be stored as real data. HttpBuilder.request({...}) is a second form that takes the path, method, headers, parameters and callbacks as one object.
Keeping State Between Runs
Variables the script defines persist from one run to the next until the data source restarts, so a typeof check initialises them once:
// flowRate: context point. totalFlow: a point on this data source.
if (typeof runningTotal === "undefined") {
runningTotal = 0;
}
runningTotal += flowRate.value;
totalFlow.set(runningTotal);
Step 5: Use the Script Utilities
| Name | Purpose |
|---|---|
LOG.trace … LOG.fatal |
Write to this data source’s own log, scripting-<id>.log in Mango’s logs directory, at or above its Log Level |
POINTS |
The variable names of every point in the context |
CONTEXT_POINTS |
A map of variable names to those points |
CONTEXT.getTimestamp() |
The time the run was triggered |
DataPointQuery.query(rql), .byXid(xid) |
Find points; query returns a Java list (use size() and get(i)), 100 results by default |
PointValueQuery |
Read stored point values |
RuntimeManager |
Refresh, enable or disable data sources and points; it does not set values |
JsonEmport |
Import or export Mango configuration as JSON |
DateTimeUtility |
Time zone offsets and date formatting |
print() |
Output shown only when you Validate; discarded while the data source runs |
Step 6: Handle Errors
- An exception the script does not catch raises a Script error event on the data source. It returns to normal after the next successful run
- A value that cannot be converted to the point’s data type raises a Result type error event
- A context point that is disabled raises Context point disabled, which is set to Ignore by default
- For HTTP work, handle failures in the
errandexcpcallbacks as above; usetry/catcharound anything else that can throw - To leave a point unchanged, simply do not call
set()on it
Step 7: Validate, Log and Enable
- Click Validate under the script editor. The script runs once but sets no points; the values it would have set appear in the messages below the editor
- Set Log Level above None (Trace, Debug, Info, Warning, Error, Fatal) before you rely on
LOG. At None nothing is written. Log file size (MB) and Log files to keep set how much of it is kept - Save, then tick Enabled on the data source (it does not take effect until saved)
- Check the points on a watch list, and the Events page (a top-level menu item) for script errors
- Read
scripting-<data source id>.login Mango’s logs directory. ScriptLOGoutput does not go toma.log
Verification
- The data source saves: no top-level
return, and it is Polling or has an Updates context point - If it uses cron, the Cron pattern is what you intended, not the every-second
* * * * * ? - Each point is set by
set()on its Variable name, and Validate shows the values you expect - The Script roles can read the context points and set the points
- No Script error or Result type error events on the Events page
-
LOGoutput appears inscripting-<id>.logat the chosen Log Level
Troubleshooting
The data source will not save, with a syntax error at a return
The script is compiled unwrapped, so return is not allowed at the top level. Replace each return value; with pointVariable.set(value);.
A syntax error at let, const or =>
The engine runs ECMAScript 5.1. Use var and function(...) { ... }.
“Must have either be polling or have a point updating context”
Switch on Polling, or tick Updates context on a context point or on one of the data source’s points.
The script runs every second
The Cron pattern was left at its starting value, * * * * * ?. Seconds come first: 0 0/5 * * * ? is every 5 minutes.
A point never changes
The script never calls set() on that point’s Variable name, or the call fails before reaching it. Check the Script error events and the script log.
A context point reads nothing, or a set on it is refused
The Script roles cannot read it, or it is not settable. A set on an external point needs the point to be settable and the roles to be allowed to set it (“Script does not have set permission to point with xid …”).
HTTP requests fail
The Mango server may block outbound HTTP. Test from the server with curl, and check the err and excp messages in the script log.
LOG writes nothing
Log Level is None, the default. Raise it, and read scripting-<id>.log, not ma.log.