Edge Action Runner Reference
This topics provides details about the Action Runner scripting language that you can use to run scripts at the edge.
JavaScript Environment
The Action Runner runs the appropriate Actions when their configured triggers are received from the Data Hub.
ECMAScript 5
The environment that executes your code is in strict mode, a restricted subset of JavaScript 5.1. For more information, read the documentation.
With this version you should avoid declaring global variables (use var x = 1; instead of just simply x = 1;). For more information about strict mode, read the Mozilla Developer Network article.
We do not support most features of ECMAScript 6 and we also disallow the use of eval. If you are not familiar with JavaScript, get started in a few minutes with the following resources:
Format
Structure
The Action is a standard ECMAScript 5.1 function, with arity-1:
function ( payload ) {
}
Valid input payload consists of any valid JSON element:
- JSON Boolean
- JSON Number
- JSON String
- JSON Object
- JSON Array
Output
Each Action should return a value. If no value is returned, no further work needs to be done. If a value is returned, Action Runner validates it to match the following JSON format:
result = {
key1 : [ value1, value2, … , valueN],
key2 : [ … ],
…
keyN : [ … ]
}
Each value must be in an array of key/value pairs.
The “keys” specify the target to deliver the payloads. Each key is a URI, comprised of a scheme (), a separator (://), and a path () - i.e. ://. The available key types are:
Name | Format (<destination>://<path> ) |
---|---|
Datahub Resource | dh://<resource_value_path> |
Virtual Resource | vr://<virtual_resource_name> |
Cloud (Immediate) | cl:// |
Cloud (Store and Forward) | st:// |
The “values” are the data to output. Each “value” can be any JSON type (boolean, number, string, object, array).
Note
Events sent from an Edge Action cannot be sent to a nammed Sream on the Cloud, they will be stored in the stream :/default of the device.
Each
<destination>://<path>
entry must be unique.
Errors and Failures
- If an Action fails validation at compilation (load) time, it cannot be loaded. If it is the sole Action to specify that topic, Octave remove the callback handler for that topic.
- If an Action fails while executing, an error should be logged, and a value should be written to the Data Hub.
- If a single output “key” or “value” fails validation, Octave proceeds with the other keys and values and continue to process those which are valid.
Octave Functions
The following JavaScript functions are provided within the environment:
- Datahub.read(): Performs a read operation on the Data Hub (e.g. to read the value of a resource).
- Datahub.query(): Performs a query operation which reads the min, max, mean, or standard deviation of a value on the Data Hub.
Datahub.read()
The Datahub.read()
function accepts a path
and a timeout
parameter, and proxy to the following Data Hub API function:
data = Datahub.read(path, 0)
path:
The path is checked to be a valid resource in the Data Hub and may one of two types:
- Regular Resources:
- Actual resources; must begin with a
/
. - These paths can be either absolute or relative. Absolute paths must begin with
/app
and are used without modification. - Relative paths are assumed to be with respect to
/app
and have/app
prepended to them internally before use (e.g. The relative path argument:/myapp/mysensor/period
is converted to the full path:/app/myapp/mysensor/period
). - Virtual Resources: Virtual Resources are user-defined resources created from within an Action or from the Cloud via /virtual/config Resource. These are read using
Datahub.read()
and are written using thevr://
output tag in the return object. Each Virtual resource behaves in a similar way to that of a global variable, and is accessible from all actions which belong to a particular device. These resources are not visible between devices. - Variable names which do not contain any slashes
/
(i.e. no sub-directories). - When read, the terminating node
/value
is appended to the path.
timeout:
The timeout parameter has been deprecated and is always 0. Immediately upon being called, the read function will attempt to read from the resource.
The value read will be returned or NULL will be returned if the read failed.
data:
The resulting data will be in JSON format, with two field names:
{ value : <data value>, timestamp : <seconds> }
For regular resources, <data name>
is the terminating node of the path provided. For example:
var result = Datahub.read('/io/ADC0/period', 0)
Will return:
{
"period" : 300,
timestamp : 1292947293
}
For user-defined resources, the terminating node of the path is always "value". For example:
var result = Datahub.read("/redSensor/light/value",0);
Will return:
{
"value" : 254,
timestamp : 1234567890
}
Datahub.query()
The Datahub.query()
function accepts a path, query-type, and window size (in seconds) parameters:
Datahub.query(path, query - type, window - size)
path:
The path is validated to be a valid path in the Data Hub and may be either absolute or relative. Absolute paths must begin with /obs
. Relative paths are assumed to be with respect to /obs
and must begin with a /
.
queryType:
The query type is a (case-insensitive) string matching one of: min
, max
, mean
, or stddev
.
timeWindowS:
The time window, in seconds, is the time from which to begin analyzing the data.
data:
The resulting data will be a scalar. For example:
var queryResult = Datahub.query('my_observation', 'mean', 10000)
Will return a scalar:
32
Octave.js Reference
JavaScript - Variables and Operators
var limit = 100 // number
var user = 'John' // string
var user = { first: 'John', last: 'Doe' } // object
var state = false // boolean
var measures = [2, 5, 10] // array
var a
typeof a // undefined
var a = null // value null
Operators
a = b + c - d // addition, substraction
a = b * (c / d) // multiplication, division
x = 100 % 48 // modulo. 100 / 48 remainder = 4
a++
b-- // postfix increment and decrement
Bitwise operators
Arithmetic
a * (b + c) // grouping
person.age // member
person[age] // member
!(a == b) // logical not
a != b // not equal
typeof a // type (number, object, function...)
x << 2 x >> 3 // minary shifting
a = b // assignment
a == b // equals
a != b // unequal
a === b // strict equal
a !== b // strict unequal
a < b a > b // less and greater than
a <= b a >= b // less or equal, greater or eq
a += b // a = a + b (works with - * %...)
a && b // logical and
a || b // logical or
Objects
var sensor = {
// object name
serialNumber: 'XXX-0001', // list of properties and values
displayName: 'Device of John',
temperature: 128,
light: 170,
report: function() {
// object function
return this.serialNumber + ' t:' + this.temperature + ' l:' + this.light
},
}
sensor.light = 157 // setting value
sensor[light]++ // incrementing
measure = sensor.report() // call object function
Strings
var lower = 'abcdefghijklmnopqrstuvwxyz'
var escape = "I don't know" //\n new line
var len = lower.length // string length
lower.indexOf('lmno') // find substring, -1 if does not contain
lower.lastIndexOf('lmno') // last occurance
lower.slice(3, 6) // cuts out "def", negative values count from behind
lower.replace('lower', '123') // find and replace, takes regular expressions
lower.toUpperCase() // convert to upper case
lower.toLowerCase() // convert to lower case
lower.concat(' ', str2) // lower + " " + str2
lower.charAt(2) // character at index: "c"
lower[2] // unsafe, lower[2] = "C" doesn't work
lower.charCodeAt(2) // character code at index: "c" -> 99
lower.split(',') // splitting a string on commas gives an array
lower.split('') // splitting on characters
;(551).toString(16) // number to hex(16), octal (8) or binary (2)
Numbers
var pi = 3.141
pi.toFixed(0) // returns 3
pi.toFixed(2) // returns 3.14 - for working with money
pi.toPrecision(2) // returns 3.1
pi.valueOf() // returns number
Number(true) // converts to number
Number(new Date()) // number of milliseconds since 1970
parseInt('3 liters') // returns the first number: 3
parseFloat('3.5 liters') // returns 3.5
JavaScript - If/Else
if ((light >= 10) && (light < 200)) { // logical condition
status = "doorOpened."; // executed if condition is true
} else { // else block is optional
status = "doorClosed"; // executed if condition is false
}
Switch Statement
switch (new Date().getDay()) { // input is current day
case 6: // if (day == 6)
text = "Saturday";
break;
case 0: // if (day == 0)
text = "Sunday";
break;
default: // else...
text = "Week day";
}
JavaScript - Loops
For Loop
var sum = 0
for (var i = 0; i < measures.length; i++) {
sum += measures[i]
} // parsing an array
While Loop
var light = []
var i = 1 // initialize
while (i < 100) {
// enters the cycle if statement is true
i++
light[i] = measures[i] // increment to avoid infinite loop
}
Do While Loop
var i = 1 // initialize
do {
// enters cycle at least once
i++ // increment to avoid infinite loop
light[i] = measures[i] // output
} while (i < 100) // repeats cycle if statement is true at the end
Break
for (var i = 0; i < 100; i++) {
if (measures[i] > 100) {
break
} // stops and exits the cycle
}
light = measures[i]
Continue
for (var i = 0; i < 100; i++) {
if (measures[i] > 100) {
continue
} // skips the rest of the cycle
light[i] = measures[i]
}
JavaScript - JSON
var str =
'{"names":[' + // crate JSON object
'{"first":"Hakuna","lastN":"Matata" },' +
'{"first":"Jane","lastN":"Doe" },' +
'{"first":"Air","last":"Jordan" }]}'
var obj = JSON.parse(str) // parse
console.log(obj.names[1].first) // access
Send
var myObj = { name: 'John', age: 18, city: 'New York' } // create object
var myJSON = JSON.stringify(myObj) // stringify
userID = 'User ID' + myJSON
JavaScript - Regular Expressions
var a = str.search(/pattern/i)
Modifiers
i Perform case-insensitive matching
g Perform a global match
m Perform multiline matching
Patterns
\ Escape character
d Find a digit
s Find a whitespace character
Find match at beginning or end of a word
n+ Contains at least one n
n* Contains zero or more occurrences of n
n? Contains zero or one occurrences of n
^ Start of string
$ End of string
. Any single character
(a|b) a or b
(...) Group section
[lower] In range (a, b or c)
[0-9] Any of the digits between the brackets
[^lower] Not in range
s White space
a? Zero or one of a
a* Zero or more of a
a*? Zero or more, ungreedy
a+ One or more of a
a+? One or more, ungreedy
a{2} Exactly 2 of a
a{2,} 2 or more of a
a{,5} Up to 5 of a
a{2,5}2 to 5 of a
a{2,5}?2 to 5 of a, ungreedy
[:punct:] Any punctuation symbol
[:space:] Any space character
[:blank:] Space or tab
JavaScript - Global Functions
eval() // executes a string as if it was a block of javascript
String(51) // return string from number
;(51).toString() // return string from number
Number('51') // return number from string
var sourceURI = 'https://mozilla.org/?x=шеллы'
// encode URI. Result: "https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B"
var encodedURI = encodeURI(sourceURI)
decodeURI(encodedURI) // decode URI. Result: "https://mozilla.org/?x=шеллы"
decodeURIComponent(encodedURI) // decode a URI component
encodeURIComponent(sourceURI) // encode a URI component
isFinite() // is variable a finite, legal number
isNaN() // is variable an illegal number
parseFloat() // returns floating point number of string
parseInt() // parses a string and returns an integer
JavaScript - Dates
//Mon Mar 18 2019 10:39:27 GMT+0100 (Central European Standard Time)
var d = new Date();
1552901967684 miliseconds passed since 1970
Number(d)
Date("2019-03-18"); // create a date
Date("2019"); // is set to Jan 01
Date("2019-03-18T10:39:27-09:45"); // date - time YYYY-MM-DDTHH:MM:SSZ
Date("March 18 2019"); // long date format
Date("Mar 18 2019 10:39:27 GMT+0100 (Central European Standard Time)"); // time zone
Get Times
var d = new Date()
a = d.getDay() // getting the weekday
d.getDate() // day as a number (1-31)
d.getDay() // weekday as a number (0-6)
d.getFullYear() // four digit year (yyyy)
d.getHours() // hour (0-23)
d.getMilliseconds() // milliseconds (0-999)
d.getMinutes() // minutes (0-59)
d.getMonth() // month (0-11)
d.getSeconds() // seconds (0-59)
d.getTime() // milliseconds since 1970
Setting Part of a Date
var d = new Date()
d.setDate(d.getDate() + 7) // adds a week to a date
d.setDate() // day as a number (1-31)
d.setFullYear() // year (optionally month and day)
d.setHours() // hour (0-23)
d.setMilliseconds() // milliseconds (0-999)
d.setMinutes() // minutes (0-59)
d.setMonth() // month (0-11)
d.setSeconds() // seconds (0-59)
d.setTime() // milliseconds since 1970)
JavaScripit - Maths
Other constants like Math.PI: E, SQRT2, SQRT1_2, LN2, LN10, LOG2E, Log10E.
var pi = Math.PI // 3.141592653589793
Math.round(4.4) // = 4 - rounded
Math.round(4.5) // = 5
Math.pow(2, 8) // = 256 - 2 to the power of 8
Math.sqrt(49) // = 7 - square root
Math.abs(-3.14) // = 3.14 - absolute, positive value
Math.ceil(3.14) // = 4 - rounded up
Math.floor(3.99) // = 3 - rounded down
Math.sin(0) // = 0 - sine
Math.cos(Math.PI) // OTHERS: tan,atan,asin,acos,
Math.min(0, 3, -2, 2) // = -2 - the lowest value
Math.max(0, 3, -2, 2) // = 3 - the highest value
Math.log(1) // = 0 natural logarithm
Math.exp(1) // = 2.7182pow(E,x)
Math.random() // random number between 0 and 1
Math.floor(Math.random() * 5) + 1 // random integer, from 1 to 5
JavaScript - Arrays
var lightStates = ['Low', 'Dim', 'High']
var lightStates = new Array('Low', 'Dim', 'High') // declaration
// access value at index, first item being [0]
var light = lightStates[1]
// change the second item, first item being [0]
lightStates[1] = 'Medium'
for (var i = 0; i < lightStates.length; i++) {
// parsing with array.length
console.log(lightStates[i])
}
Methods on Arrays
lightStates.toString() // convert to string: results "Low,Dim,High"
lightStates.join(' * ') // join: "Low * Dim * High"
lightStates.pop() // remove last element
lightStates.push('Bright') // add new element to the end
lightStates[lightStates.length] = 'Bright' // the same as push
lightStates.shift() // remove first element
lightStates.unshift('Dark') // add new element to the beginning
// change element to undefined (not recommended)
delete lightStates[0]
// add elements (where, how many to remove, element list)
lightStates.splice(2, 0, 'Bright', 'Extra bright')
// join two arrays (lightStates followed by tempStates and pressureStates)
var sensorStates = lightStates.concat(tempStates, pressureStates)
lightStates.slice(1, 4) // elements from [1] to [4-1]
lightStates.sort() // sort string alphabetically
lightStates.reverse() // sort string in descending order
x.sort(function(a, b) {
return a - b
}) // numeric sort
x.sort(function(a, b) {
return b - a
}) // numeric descending sort
// first item in sorted array is the lowest (or highest) value
highest = x[0]
// It's a random sort
x.sort(function(a, b) {
return 0.5 - Math.random()
})
Manipulating Binary Data
Often times it necessary to manipulate data such as that read from Modbus (e.g., to change endianness). This is can be done in an Edge Action or, less commonly, in a Cloud Action, using JavaScript's support for bitwise operators.
For more information about bitwise operator support in JavaScript, see: Bitwise Operators.
Updated almost 5 years ago