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 webhook
  • headers: 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 TypeSyntaxExample
Attribute Valueattribute < <= == > >= != valuepath == '/streamPath'
location.lat > 43.535635
elems.arbitraryAttribute != false
Attribute Comparisonattribute1 < <= == > >= != attribute2path == description
elems.attribute1 >= elems.attribute2
Moduloattribute1 % X (== OR !=) Yelems.counter % 100 == 0
INattribute IN ["value1", "value2", "value3"]elems.status IN ["shipped", "delivered"]
CONTAINSattribute 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 punctu­ation 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: An encodeURI() 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.