Skip to main content

Shelly Script Language Features

Since version 0.9.0

Shelly Scripts run on a modified version of mJS, which is part of the Mongoose OS framework. mJS implements a useful subset of the JavaScript language, which, while very minimal, is complete and capable. This page briefly demonstrates the supported language features with examples. If you a more complicated example, you can check our short tutorial. More information about the Shelly's scripting API can be found here.

Variables, types, scope#

Only let is supported and must be used when introducing a new variable. The language supports all basic JS types.

A semicolon is required after every statement.

typeof can be used to inspect the type of any value.

____________________________________________________________________________________________________
SCRIPT:
let a_number = 3.1415;
let a_string = "characters make up words"; // No Unicode support
let a_boolean = true;
let nothing = null;
let an_object = {answer: 42};
let an_array = [a_number, a_string, a_boolean, nothing, an_object];
for (let i in an_array) {
print(i, typeof(an_array[i]), an_array[i]);
}
____________________________________________________________________________________________________
OUTPUT:
4 object <object>
3 null null
2 boolean true
1 string characters make up words
0 number 3.141500

Comparison is strict. Only === and !==, no == or !=.

Closures are not supported.

____________________________________________________________________________________________________
SCRIPT:
function enclosing(a) {
return function(b) {
return a + b;
}
}
let ef = enclosing(3);
ef(5);
____________________________________________________________________________________________________
OUTPUT:
MJS error: [a] is not defined

Numbers#

Normal arithmetic, bitwise operators and shift operators are supported.

Numbers: basic arithmetic
SCRIPT:
let n = 2 + 2;
n = n - 2;
n = n * 2;
n = n / 2;
print(n);
____________________________________________________________________________________________________
OUTPUT:
2
Numbers: bitshift operators
SCRIPT:
let n = 2;
n = n << 2;
print(n);
n = n >> 2;
print(n);
____________________________________________________________________________________________________
OUTPUT:
8
2
Numbers: bitwise operators
SCRIPT:
// bitwise or
let n = 2;
n = n | 1;
print(n);
// bitwise and and not
n = n & (~1); // 00000011 & 11111110
print(n);
// bitwise xor
n = n ^ 1;
print(n);
____________________________________________________________________________________________________
OUTPUT:
3
2
3

Strings#

Strings in mJS are sequences of bytes and can hold arbitrary binary data. Unicode is not supported. UTF-8-encoded strings can used, but will be serialized with the non-standard \xHH hexadecimal representation.

String values have support for:

String literals and concatenation
SCRIPT:
// concatenate strings
let s = 'Shelly' + ' ' + 'Scripting';
print(s);
____________________________________________________________________________________________________
OUTPUT:
Shelly Scripting

Non-ascii symbols must be encoded in UTF-8, which will work over RPC channels.

String length, multi-byte symbols, [] operator
SCRIPT:
// length property
let s = 'Shelly' + ' ' + 'Scripting';
print(s.length)
print('Шели'.length)
print('€', '€'.length)
print('€'[2]);
____________________________________________________________________________________________________
OUTPUT:
16 // the length in bytes
8
\xe2\x82\xac 3
\xac

"".slice() can be used to extract a substring.

String.slice() demonstration
SCRIPT:
// slice - return a substring
let s = 'Shelly' + ' ' + 'Scripting';
let substr = s.slice(7, s.length);
print(substr);
____________________________________________________________________________________________________
OUTPUT:
Scripting

Values of strings can be compared with === and !==

String comparison
SCRIPT:
let s = 'Shelly' + ' ' + 'Scripting';
let substr = s.slice(7, s.length);
if (substr === 'Scripting') {
print('"Scripting" substring starts at index 7');
}
if (substr !== 'Shelly') {
print('Substring is not Shelly');
}
____________________________________________________________________________________________________
OUTPUT:
"Scripting" substring starts at index 7
Substring is not Shelly
String.indexOf() example
SCRIPT:
let s = 'Shelly' + ' ' + 'Scripting';
// indexOf - index of substring, -1 if not found
print(s.indexOf('She'));
print(s.indexOf('Allterco'));
// byte at index
let seventh = s.at(7);
print(typeof(seventh), seventh);
____________________________________________________________________________________________________
OUTPUT:
0
-1 // substring not found
string 83 // ascii code for S

Arrays#

Arrays support

Array instantiation
SCRIPT:
let a = [ "S", "h", "e", "l", "l", "y", 2];
print(a.length);
____________________________________________________________________________________________________
OUTPUT:
7

Iteration can be done sequentially or with the for (key in value) shorthand, which does not guarantee order.

Array iteration
SCRIPT:
let a = [ "S", "h", "e", "l", "l", "y", 2];
for (let i=0; i<a.length; i++) {
print(a[i]);
}
____________________________________________________________________________________________________
OUTPUT:
S
h
e
l
l
y
2
Array iteration 2
SCRIPT:
let a = [ "S", "h", "e", "l", "l", "y", 2];
for (let i in a) {
print(a[i]);
}
____________________________________________________________________________________________________
OUTPUT:
2
y
l
l
e
h
S

[].splice() can remove and insert elements

Array splice() example
SCRIPT:
let a = [ "S", "h", "e", "l", "l", "y", 2];
let as = a.splice(0,3);
for (let i=0; i<a.length; i++) {
print(a[i]);
}
____________________________________________________________________________________________________
OUTPUT:
l
l
y
Array splice() example 2
SCRIPT:
let proto = {a: "A"};
let obj = Object.create(proto);
for (let i=0; i<as.length; i++) {
print(as[i]);
}
____________________________________________________________________________________________________
OUTPUT:
S
h
e
Array splice() example 3
SCRIPT:
// with splice you can remove elements and insert others at their place
let as = [4,5,6,7,8,9];
as.splice(0,3,1,2,3);
for(let i=0; i<as.length; i++) {
print(as[i]);
}
____________________________________________________________________________________________________
OUTPUT:
1
2
3
7
8
9
Array push() example
SCRIPT:
let a = [];
a.push(1);
a.push(2);
a.push(3);
for (let i=0; i<a.length; i++) {
print(a[i]);
}
____________________________________________________________________________________________________
OUTPUT:
1
2
3

Objects#

mJS does not support the new keyword. Instantiation from a prototype is supported via Object.create(proto).

Object instantiation
SCRIPT:
let o = {}; // empty object
let op = {title: 'Shelly'};
print(op.title); // Shelly
// Creating an object with prototype
let proto = {
printMe: function() {
print(this.name);
}
};
let on = Object.create(proto);
on.name = 'Shelly';
on.printMe();
____________________________________________________________________________________________________
OUTPUT:
Shelly

Properties of objects can be inspected with iteration, using the for (let prop in obj) construct.

Object key iteration
SCRIPT:
let o = {
name: 'Shelly',
model: 'Plus1PM',
generation: 2,
printMe: function() {
print(this.name, ' - ', this.model, ' Gen', this.generation);
}
};
for (let prop in o) {
print(prop, ' - ', typeof (o[i]));
}
____________________________________________________________________________________________________
OUTPUT:
printMe - function
generation - number
model - string
name - string

Comments#

Both styles of comments are supported:

  • //
  • /* */
Object key iteration
SCRIPT:
/* Here we just show some comments.
Have fun! */
let letter = "a"; // just declared a variable
print(letter, type_of(letter))
________________________________________________________________________________________________
OUTPUT:
a string

JSON support#

JSON.parse() and JSON.stringify() are available

____________________________________________________________________________________________________
SCRIPT:
let json_string = '{"id":1,"name":"Shelly"}';
let obj = JSON.parse(json_string);
print(obj.id, "-", obj.name);
let obj = { "id": 1, "name": "Shelly" };
print(JSON.stringify(obj));
____________________________________________________________________________________________________
OUTPUT:
1 - Shelly
{"name":"Shelly","id":1}

Math API#

Provides standard functionality through the following functions:

  • Math.ceil(x)
  • Math.floor(x)
  • Math.round(x)
  • Math.max(x, y)
  • Math.min(x, y)
  • Math.abs(x)
  • Math.sqrt(x)
  • Math.exp(x)
  • Math.log(x)
  • Math.pow(base, exponent)
  • Math.sin(x)
  • Math.cos(x)
  • Math.random()
____________________________________________________________________________________________________
SCRIPT:
let value = 3.45;
let floored_value = Math.floor(value);
let ceiled_value = Math.ceil(value);
print(floored_value, ceiled_value);
let square = Math.sqrt(ceiled_value);
print(square);
let power = Math.power(square, floored_value);
print(power);
let minimum = Math.min(square, power);
print(minimum);
____________________________________________________________________________________________________
OUTPUT:
3 4
2
8
2

Builtin functions#

die(message)#

Builtin die()
SCRIPT:
function do_(something) {
print("doing", something);
}
die("I'm lazy");
do_("work");
____________________________________________________________________________________________________
OUTPUT:
-3: I'm lazy

chr(num)#

Convert a number to a 1-byte string.

Builtin chr()
SCRIPT:
let a_letter = chr(65);
print(a_letter, typeof(a_letter));
____________________________________________________________________________________________________
OUTPUT:
A string

print(arg0, arg1, ...)#

Converts arguments to strings, concatenates with space as delimiter and prints to the console. Also available as Console.log() and console.log() for convenience.

Builtin print()
SCRIPT:
print("hello world", {}, [], null);
____________________________________________________________________________________________________
OUTPUT:
hello world <object> <array> null

Object.create(proto)#

Create an object with prototype.

Builtin Object.create(proto)
SCRIPT:
let proto = {a: "A"};
let obj = Object.create(proto);
print(obj.a);
____________________________________________________________________________________________________
OUTPUT:
A

The prototype of an object is accessible as the __p property. When iterating over object properties will not include the properties of the prototype.

Iterating properties of object with prototype
SCRIPT:
for (let prop in obj) {
print(prop, typeof(prop));
}
____________________________________________________________________________________________________
OUTPUT:
__p string

Shelly APIs#

Shelly.call()#

To interact with the local device, JS code can invoke RPC methods using a "local" RPC channel. The signature of the method is:

Shelly.call(method, params, callback, userdata) -> boolean

  • method: string, name of the method to invoke
  • params: JSON object or a valid JSON string
  • callback(result, error_code, error_message, userdata): function, will be invoked when the call completes
    • result: JSON object or null, or undefined, result from the callback (null if the method does not return any data, undefined if the call resulted in an error)
    • error_code: number, 0 if there was no error or non-zero if an error occurred
    • error_message: string, more information on the error if one occurred
    • userdata: any type, can be used to pass data from the invoker to the callback
  • Return value: boolean, true if parameters given to Shelly.call() were valid and the RPC call is scheduled, false otherwise.

Shelly.addEventHandler() and Shelly.addStatusHandler()#

These methods allow JS code to react to internal events. These are identical to the events reported through RPC notifications as NotifyStatus and NotifyEvent. The signatures are identical:

JS equivalents of NotifyEvent and NotifyStatus
Shelly.addEventHandler(callback, userdata) -> subscription_handle or undefined
Shelly.addStatusHandler(callback, userdata) -> subscription_handle or undefined
Shelly.removeEventHandler(subscription_handle) -> boolean
Shelly.removeStatusHandler(subscription_handle) -> boolean

addEventHandler() and addStatusHandler() take the following:

  • callback(event_data, userdata): function, will be invoked when the respective event occurs.
  • event_data: JSON object, identical in contents to what NotifyStatus and NotifyEvent emit.
  • userdata: any type, can be used to pass data from the invoker to the callback
  • Return value: On success, returns a handle which can be used to remove the listener with Shelly.removeEventHandler(subscription_handle) or Shelly.removeStatusHandler(subscription_handle) respectively. undefined will be returned if invoked with invalid arguments.

Utilities#

Timer#

Timers can be used for one-shot delayed code execution, or to run some code periodically.

Timer.set()#

To arm a timer, use:

Timer.set(period, repeat, callback, userdata) -> timer_handle or undefined

  • period: number, in milliseconds
  • repeat: boolean, if true, the timer will fire periodically, otherwise the callback will be invoked only once.
  • callback(userdata): function, to be invoked when the timer fires
  • userdata: any type, can be used to pass data from the invoker to the callback
  • Return value: timer_handle if parameters were valid and the timer was created, undefined otherwise.

Timer.clear()#

To stop the execution of a timer, use:

Timer.clear(timer_handle) -> boolean or undefined

  • timer_handle: a handle previously returned by Timer.set()
  • Return value: true if the timer was armed, false if no such timer existed. undefined will be returned if the given timer_handle was not valid.

MQTT support#

MQTT#

MQTT is a global object which allows to access mqtt functionality. JS code can monitor connection status, subscribe to miltiple topics and publish to topics.

MQTT.isConnected()#

  • returns true if device is connected to a MQTT broker or false otherwise.

MQTT.subscribe()#

  • Subscribe to a topic on the MQTT broker.

MQTT.subscribe(topic, callback, callback_arg)

  • topic - string, the topic to subscribe to.
  • callback(topic, message, callback_arg) - function to be called when a message is published on the topic.
    • topic - string, the topic that we subscribed to.
    • message - string, message received on the topic.
    • callback_arg - any type, the user data passed in the call.
  • callback_arg - any type, user data to be passed to the callback.

MQTT.unsubscribe()#

  • Unsubscribe from a topic previously subscribed, can be called only for topics subscribed in the same script. Returns true if topic is subscribed to and unsubscribed or false otherwise.

MQTT.unsubscribe(topic)

  • topic - string, the topic to subscribe to.

MQTT.publish()#

  • publish a message to a topic. Returns non-zero if successfull or zero otherwise.

MQTT.publish(topic, message, qos, retain)

  • topic - string, the topic to publish.
  • message - string, the message to publish.
  • qos - integer, can be 0 - at most once, 1 - at least once or 2 exactly once. Default is 0.
  • retain - boolean, if true the message is retained by the broker and delivered when subscribers are activated. Default is false.

MQTT.setConnectHandler()#

  • registers a handler for the MQTT connection established event.

MQTT.setConnectHandler(callback, callback_arg)

  • callback(callback_arg) - function to be called when event is received.
    • callback_arg - any type, user data passed in the call.
  • callback_arg - any type, user data to be passed to the callback.

MQTT.setDisconnectHandler()#

  • registers a handler for the MQTT connection closed event.

MQTT.setDisconnectHandler(callback, callback_arg)

  • callback - function to be called when event is received.
    • callback_arg - any type, user data passed in the call.
  • callback_arg - any type, user data to be passed to the callback.

Limitations#

There are some limitations of the resources used in a script. At the moment, these are as follows: