Cloud 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
Action Runner is built around Octave.js, our server-side JavaScript processing environment. It gives you a library under the Octave namespace. It also executes your code on our servers, ensuring that your code executes safely–both for you and for everyone else on our platform.**
Octave.js provides methods for CRUD operations on select Octave domain objects from within Actions. These methods are available in the global namespace Octave.
The Octave namespace provides find()
and get()
under the sub namespaces: Stream, Event, and Action (see Cloud JavaScript Library for more information). Event also includes aggregate(), findOne(), and findFHash() methods.
See Octave.js Reference below for a list of features.
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:
Octave.js Reference
Octave.Error
The Octave namespace houses a custom error object, Octave.Error
, which is thrown when a fatal error is encountered during the invocation of certain methods. Octave.Error
objects have two properties, message
and details
. message
is a string value that represents the general cause of error. details
is an array of strings that represent more specific information relating to the cause of error.
Webhooks
All webhooks calls return the same response object with the following keys:
- headers - (map of strings)
- status - HTTP status code (integer)
- message - HTTP status message (string)
- contentType - (string)
- content - response body (string)
Note
They also can "throw" an instance of Octave.Error.
get()
Octave.Http.get(URL, HEADERS)
url
: The URL to your webhookheaders
: Any (RFC 2616) HTTP headers. It must be in the form of:{"header":"header_value"}
.
post()
Octave.Http.post(URL, HEADERS, DATA)
Parameters
url
: The URL to your webhook.headers
: Any (RFC 2616) HTTP headers. It must be in the form of:{"header":"header_value"}
.data
: The POST body.
put()
Octave.Http.put(URL, HEADERS, DATA)
Parameters
url
: The URL to your webhook.headers
: Any (RFC 2616) HTTP headers. It must be in the form of:{"header":"header_value"}
.data
: The PUT body.
delete()
Octave.Http.delete(URL, HEADERS)
Parameters
url
: The URL to your webhook.headers
: Any (RFC 2616) HTTP headers. It must be in the form of:{"header":"header_value"}
.data
: The PUT body.
Filters
Filters can be used for explicit object matching, as well as members of Stream and Action objects, where they control the types of Event that can enter an Action or Stream.
Note that regular expressions in these filters follow the Java regex format.
Filter Operations
Comparison Type | Syntax | Example |
---|---|---|
Attribute Value | attribute < <= == > >= != value | path == '/streamPath' location.lat > 43.535635 elems.arbitraryAttribute != false |
Attribute Comparison | attribute1 < <= == > >= != attribute2 | path == description elems.attribute1 >= elems.attribute2 |
Modulo | attribute1 % X (== OR !=) Y | elems.counter % 100 == 0 |
IN | attribute IN ["value1", "value2", "value3"] | elems.status IN ["shipped", "delivered"] |
CONTAINS | attribute CONTAINS ["value1", "value2", "value3"] | elems.ingredients CONTAINS ["potato", "salad"] |
Streams
Note
All Streams functions can "throw" an instance of Octave.Error.
find()
Octave.Stream.find(options)
Parameters
options
: An optional object literal of query options:filter
: A filter string.start
: The start index of the search; (default value is 0).limit
: The maximum response size; (default value 20).sort
: The string representation of the member key to sort the results over (default value is the creation date).order
: Specifies the sort order. Can be set to ‘asc’ and ‘desc’ (default value is 'desc').
Returns
An array of JSON representations of the matching Streams.
get()
Octave.Stream.get(streamId)
Parameters
streamId
: the unique ID of the Stream.
Returns
A JSON representation of the matching Stream.
Events
Note
All Events functions can "throw" an instance of Octave.Error.
find()
Octave.Event.find(streamId, options)
Parameters
streamId
: The unique ID of the Stream.options
: An optional object literal of query options:filter
: a filter string.start
: The start index of the search (default value is 0).limit
: The maximum response size (default value is 20).sort
: String representation of the member key to sort the results over (default value is the creation date).order
: Specifies the sort order. Can be set to ‘asc’ and ‘desc’ (default value is 'desc').
Returns
An array of JSON representations of the matching Events.
findone()
Octave.Event.findOne(streamId, options)
Parameters
streamId:
The unique ID of the Stream.options:
An optional object literal of query options:filter:
A filter string.start:
The start index of the search (default value is 0).limit:
The maximum response size (default value is 20).sort:
The string representation of the member key to sort the results over (default value is the creation date).order:
Specifies the sort order. Can be set to ‘asc’ and ‘desc’ (default value is 'desc').
Returns
A JSON representation of a matching Event.
get()
Octave.Event.get(streamId, eventId)
Parameters
streamId
: The unique ID of the Stream that owns the Event.eventId
: The unique ID of the Event.
Returns
A JSON representation of the matching Event.
Actions
Note
All Actions functions can "throw" an instance of Octave.Error.
find()
Octave.Action.find(options)
Parameters
options
: An optional object literal of query options:filter
: a filter string.start
: The start index of the search (default value is 0).
*limit
: The maximum response size (default value is 20).sort
: The string representation of the member key to sort the results over (default value is creation date).order
: Specifies the sort order. Can be set to ‘asc’ and ‘desc’ (default value is 'desc').
Returns
An array of JSON representations of the matching Actions.
get()
Octave.Action.get(actionId)
Parameters
actionId
: The unique ID of the Action.
Returns
A JSON representation of the matching Action.
Lodash Functions
The JavaScript utility Lodash (version 3.10.0) is exposed to the Action Runner.
Use to call this library (e.g. ```.includes()```).
For more information, read the documentation.
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
& AND 5 & 1 (0101 & 0001) 1 (1)
| OR 5 | 1 (0101 | 0001) 5 (101)
~ NOT ~ 5 (~0101) 10 (1010)
^ XOR 5 ^ 1 (0101 ^ 0001) 4 (100)
<< left shift 5 << 1 (0101 << 1) 10 (1010)
>> right shift 5 >> 1 (0101 >> 1) 2 (10)
>>> zero fill right shift 5 >>> 1 (0101 >>> 1) 2 (10)
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)
JavaScript - Errors
try {
// block of code to try
myFunction()
} catch (err) {
// block to handle errors
console.log(err.message)
}
Throw error
throw 'Error message' // throw a text
Input validation
var x = ''
try {
if (x == '') throw 'empty' // error cases
if (isNaN(x)) throw 'not a number'
x = Number(x)
if (x > 10) throw 'too high'
} catch (err) {
// if there's an error
console.error(err) // write the error in console
}
Error examples
RangeError
: A number is out of range.ReferenceError
: An illegal reference has occurred.SyntaxError
: A syntax error has occurred.TypeError
: A type error has occurred.URIError
: AnencodeURI()
error has occurred.
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