Skip to main content
Version: 0.14

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

btoa(string)

Encode a string of binary data to Base64-encoded ASCII string and return the result. (should be read as "binary to ASCII").

Builtin btoa()
SCRIPT:

let binary = "abc\x00\x01\xf0\xff";
let encoded = btoa(binary);
print(encoded);
____________________________________________________________________________________________________
OUTPUT:

YWJjAAHw/w==

atob(string)

Decode a Base64-encoded ASCII string to source binary string and return the result. Returns undefined if the argument is not a valid Base64-encoded string. (should be read as "ASCII to binary").

Builtin atob()
SCRIPT:

let encoded = "SGVsbG8gd29ybGQ=";
let decoded = atob(encoded);
print(decoded);
____________________________________________________________________________________________________
OUTPUT:

Hello world

Shelly APIs

Shelly.call()

To interact with the local device, JS code can invoke RPC methods using a "local" RPC channel:

Shelly.call(method, params[, callback[, userdata]]) -> undefined

PropertyTypeDescription

method

string

Name of the method to invoke

params

object or string

Parameters

callback

function or null or undefined

If it is a function, will be invoked when the call completes

PropertyTypeDescription

result

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

The passed userdata

userdata

any type

Can be used to pass data to the callback

  • This method doesn't return a value. If invoked with invalid arguments the script is aborted.

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:

Shelly.addEventHandler(callback[, userdata]) -> subscription_handle

Shelly.addStatusHandler(callback[, userdata]) -> subscription_handle

PropertyTypeDescription

callback

function

Will be invoked when the respective event occurs

PropertyTypeDescription

event_data

object

Identical in contents to what NotifyStatus and NotifyEvent emit.

userdata

any type

The passed userdata

userdata

any type

Can be used to pass data 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. If invoked with invalid arguments the script is aborted.

Shelly.removeEventHandler() and Shelly.removeStatusHandler()

Shelly.removeEventHandler(subscription_handle) -> boolean

Shelly.removeStatusHandler(subscription_handle) -> boolean

PropertyTypeDescription

subscription_handle

number

The handle returned by previously called corresponding addEventHandler or addStatusHandler

  • Return value: Returns true if the handle is found and the listener is removed, false if handle is not found or undefined if the handle is invalid.

Shelly.emitEvent()

This method creates an event which is broadcasted to all persistent RPC channels.

Shelly.emitEvent(name, data) -> undefined

PropertyTypeDescription

name

string

Name of the event

data

scalar or object or array

payload of the event. Any valid JSON value is allowed. undefined and function objects are not supported.

  • This method doesn't return a value. If invoked with invalid arguments the script is aborted.
Shelly.emitEven() example
Shelly.emitEvent("this_happened", {"what":"when", "why": 42});

The above code will trigger a notification to be emitted:

{
"component": "script:1",
"id": 1,
"event": "this_happened",
"data": {
"why": 42,
"what": "when"
},
"ts": 1657878122.44
}

Shelly.getComponentConfig()

Shelly.getComponentConfig(type_or_key, id) -> object or null

PropertyTypeDescription

type_or_key

string

Component type or key("component:id"). Component type must be in lowercase.

id

number

Numeric id of multi instance components if only type is specified or absent for single instance components

  • Return value: an object with the current configuration of the component, null if component was not found. If invoked with invalid arguments the script is aborted.

Shelly.getComponentStatus()

Shelly.getComponentStatus(type_or_key, id) -> object or null

PropertyTypeDescription

type_or_key

string

Component type or key("component:id"). Component type must be in lowercase.

id

number

Numeric id of multi instance components if only type is specified or absent for single instance components

  • Return value: an object with the current status of the component, null if component was not found. If invoked with invalid arguments the script is aborted.

Shelly.getDeviceInfo()

  • Return value: the DeviceInfo object.

Shelly.getCurrentScriptId()

  • Return value: number - the id of the current script.

Utilities

Timer

Timer global object 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

PropertyTypeDescription

period

number

In milliseconds

repeat

boolean

If true, the timer will fire periodically, otherwise the callback will be invoked only once

callback

function

To be invoked when the timer fires

PropertyTypeDescription

userdata

any type

The passed userdata

userdata

any type

Can be used to pass data to the callback

  • Return value: On success, returns a handle which can be used to stop the timer with Timer.clear(timer_handle). If invoked with invalid arguments the script is aborted.

Timer.clear()

To stop the execution of a timer, use:

Timer.clear(timer_handle) -> boolean or undefined

PropertyTypeDescription

timer_handle

handle

handle previously returned by Timer.set()

  • Return value: true if the timer was armed and destroyed, false if no such timer existed orundefined if the given timer_handle was not valid.

MQTT support

MQTT

MQTT global object provides MQTT functionality. JS code can monitor connection status, subscribe and publish to miltiple topics.

MQTT.isConnected()

  • Return value: true if device is connected to a MQTT broker or false otherwise.

MQTT.subscribe()

Subscribes to a topic on the MQTT broker.

MQTT.subscribe(topic, callback[, userdata]) -> undefined

PropertyTypeDescription

topic

string

The topic to subscribe to

callback

function

Function to be called when a message is published on the topic

PropertyTypeDescription

topic

string

The topic that we subscribed to

message

string

Message received on the topic

userdata

any type

The passed userdata

userdata

any type

Can be used to pass data to the callback

  • This method doesn't return a value. If invoked with invalid arguments the script is aborted.

MQTT.unsubscribe()

Unsubscribes from a topic previously subscribed, can be called only for topics subscribed in the same script.

MQTT.unsubscribe(topic) -> boolean

PropertyTypeDescription

topic

string

The topic to unsubscribe from

  • Return value: true if unsubscribed or false if subscription to the topic does not exist. The script is aborted if the argument is not valid.

MQTT.publish()

Publishes a message to a topic.

MQTT.publish(topic, message[, qos[, retain]]) -> undefined

PropertyTypeDescription

topic

string

The topic to publish

message

string

Тhe 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[, userdata])

PropertyTypeDescription

callback

function

Function to be called when event is received

PropertyTypeDescription

userdata

any type

The passed userdata

userdata

any type

Can be used to pass data to the callback

  • This method doesn't return a value. If invoked with invalid arguments the script is aborted.

MQTT.setDisconnectHandler()

Registers a handler for the MQTT connection closed event.

MQTT.setDisconnectHandler(callback[, userdata])

PropertyTypeDescription

callback

function

Function to be called when event is received

PropertyTypeDescription

userdata

any type

The passed userdata

userdata

any type

Can be used to pass data to the callback

  • This method doesn't return a value. If invoked with invalid arguments the script is aborted.

Bluetooth support

Since version 0.12.0

caution

Bluetooth support in scripting is experimental. The API is subject to change.

A global BLE object provides a namespace for for various different layers of the bluetooth protocol stack. Shelly devices currently support the scanner role.

BLE.Scanner

The Scanner object provides access to functionality related to the discovery of bluetooth devices. It allows the script to start a scan and listen for scan events. It defines the following constants:

  • Scan events passed to subscribers:

    • BLE.Scanner.SCAN_START = 0;
    • BLE.Scanner.SCAN_STOP = 1;
    • BLE.Scanner.SCAN_RESULT = 2;
  • duration_ms value for perpetual scanning:

    • BLE.Scanner.INFINITE_SCAN = -1;
note

Processing advertisement packets can be very resource intensive, especially in environments with many broadcasters and advertisers. It is recommended that scripts filter advertisement data for specific devices or device types to prevent memory and bandwidth starvation.

BLE.Scanner.Subscribe()

Subscribes for scan events and register a listener. A script must subscribe in order to receive events, can subscribe at any time, regardless of the status of the scan or previous subscription. Only one subscription can be active in a script and previous subscriptions are replaced by a new one.

BLE.Scanner.Subscribe(callback[, userdata]) -> undefined

PropertyTypeDescription

callback

function or null

Function invoked for each scan event. Specify null to unsunscribe.

PropertyTypeDescription

event

number

the scan event, can be one of BLE.Scanner.SCAN_START, BLE.Scanner.SCAN_STOP or BLE.Scanner.SCAN_RESULT

result

object or null

scan result object for BLE.Scanner.SCAN_RESULT, null for BLE.Scanner.SCAN_START and BLE.Scanner.SCAN_STOP

PropertyTypeDescription

addr

string

address of the advertising device

addr_type

number

type of the device address, can be one of BLE.GAP_ADDRESS_TYPE_*

advData

string

advertisement data

scanRsp

string

scan response for active scans

rssi

number

received signal strength indicator

flags

number

the flags entry of the advertising data, if present

local_name

string

long or short name of the device, if present in the advertising data

manufacturer_data

object

the manufacturer data entry of the advertising data, if present. Key is the assigned number of the manufacturer and value is the associated data

service_uuids

array

list of service UUIDs advertised by the device, if present

service_data

object

keys are service UUIDs and values contain the associated service data, if present

tx_power_level

number

transmitted power level of the packet, if present

userdata

any type

The passed userdata

userdata

any type

Can be used to pass data to the callback

  • This method doesn't return a value. If invoked with invalid arguments the script is aborted.

BLE.Scanner.Start()

Starts a scan and optionally subscribe for scan events.

Scan options allow tuning for scan timings, but some restrictions apply:

  • scan window cannot be longer than 1/3 of scan interval
  • the maximum scan window is 50 ms, but 30 ms seems to be optimal
  • duration must be at least 3 scan intervals long

If these conditions are not met scanning will not start. In the future, some of these options may not be tunable or the device may choose to modify them for performance and compatibility with other firmware features. It is best to use defaults.

To run a perpetual scan, invoke with a single option: {duration_ms: BLE.Scanner.INFINITE_SCAN}.

BLE.Scanner.Start(options[, callback[, userdata]]) -> object or null

PropertyTypeDescription

options

object

object with scan options. All options are optional and may be omitted, substituting with defaults.

PropertyTypeDescription

duration_ms

number

duration of the scan in ms, BLE.Scanner.INFINITE_SCAN for perpetual (continuous) scan

active

boolean

start an active scan if true, or passive if false

interval_ms

number

scan interval in ms

window_ms

number

scan window in ms

callback

function

Function invoked for each scan event. If specified the call will also subscribe for scan events.

PropertyTypeDescription

event

number

the scan event, can be one of BLE.Scanner.SCAN_START, BLE.Scanner.SCAN_STOP or BLE.Scanner.SCAN_RESULT

result

object or null

scan result object for BLE.Scanner.SCAN_RESULT, null for BLE.Scanner.SCAN_START and BLE.Scanner.SCAN_STOP

PropertyTypeDescription

addr

string

address of the advertising device

addr_type

number

type of the device address, can be one of BLE.GAP_ADDRESS_TYPE_*

advData

string

advertisement data

scanRsp

string

scan response for active scans; a zero-length string for passive scans or when missing

rssi

number

received signal strength indicator

flags

number

the flags entry of the advertising data, if present

local_name

string

long or short name of the device, if present in the advertising data

manufacturer_data

object

the manufacturer data entry of the advertising data, if present. Key is the assigned number of the manufacturer and value is the associated data

service_uuids

array

list of service UUIDs advertised by the device, if present

service_data

object

keys are service UUIDs and values contain the associated service data, if present

tx_power_level

number

transmitted power level of the packet, if present

userdata

any type

The passed userdata

userdata

any type

Can be used to pass data to the callback

  • Return value: object with the options of the started scan or null if start failed. Start will fail also if there is a scan in progress. If invoked with invalid arguments the script is aborted.

BLE.Scanner.Stop()

Stops a running scan. Only the script that started the scan can stop it.

  • Return value: true if scan is successfully stopped or false if there is an error.

BLE.Scanner.isRunning()

  • Return value: true if currently there is a running scan or false otherwise.

BLE.Scanner.GetScanOptions()

  • Return value: object with the options of the running scan if there is one or the default options otherwise.

BLE.GAP

The GAP object is responsible for the GAP layer of the bluetooth protocol. It provides helper functions for parsing advertisement data. It defines the following constants:

  • BLE.GAP.ADDRESS_TYPE_PUBLIC = 0x1;
  • BLE.GAP.ADDRESS_TYPE_RANDOM_STATIC = 0x2;
  • BLE.GAP.ADDRESS_TYPE_RANDOM_NON_RESOLVABLE = 0x3;
  • BLE.GAP.ADDRESS_TYPE_RANDOM_RESOLVABLE = 0x4;
  • BLE.GAP.EIR_FLAGS = 0x1;
  • BLE.GAP.EIR_SERVICE_16_INCOMPLETE = 0x2;
  • BLE.GAP.EIR_SERVICE_16 = 0x3;
  • BLE.GAP.EIR_SERVICE_32_INCOMPLETE = 0x4;
  • BLE.GAP.EIR_SERVICE_32 = 0x5;
  • BLE.GAP.EIR_SERVICE_128_INCOMPLETE = 0x6;
  • BLE.GAP.EIR_SERVICE_128 = 0x7;
  • BLE.GAP.EIR_SHORT_NAME = 0x8;
  • BLE.GAP.EIR_FULL_NAME = 0x9;
  • BLE.GAP.EIR_TX_POWER_LEVEL = 0xA;
  • BLE.GAP.EIR_DEVICE_ID = 0x10;
  • BLE.GAP.EIR_SERVICE_DATA_16 = 0x16;
  • BLE.GAP.EIR_SERVICE_DATA_32 = 0x20;
  • BLE.GAP.EIR_SERVICE_DATA_128 = 0x21;
  • BLE.GAP.EIR_URL = 0x24;
  • BLE.GAP.EIR_MANUFACTURER_SPECIFIC_DATA = 0xff;

BLE.GAP.parseName()

Parses device name from advertisement data or scan response.

BLE.GAP.parseName(data) -> string

PropertyTypeDescription

data

string

data to parse, should be either advertisment data or scan response.

  • Return value: extracted name, may be empty string if data is not available. If invoked with invalid arguments the script is aborted.

BLE.GAP.parseManufacturerData()

Parses manufacturer data from advertisement data or scan response.

BLE.GAP.parseManufacturerData(data) -> string

PropertyTypeDescription

data

string

data to parse, should be either advertisment data or scan response.

  • Return value: extracted data, may be empty string if data is not available. If invoked with invalid arguments the script is aborted.

BLE.GAP.ParseDataByEIRType()

Parses data for specified EIR type (Extended Inquiry Response) from advertisement data or scan response.

BLE.GAP.ParseDataByEIRType(data, type) -> string

PropertyTypeDescription

data

string

data to parse, should be either advertisment data or scan response.

type

number

EIR type, should be one of the defined constants.

  • Return value: extracted data, may be empty string if data is not available. If invoked with invalid arguments the script is aborted.

BLE.GAP.HasService()

Searches for specified service UUID in the advertisement data or scan response.

BLE.GAP.HasService(data, uuid) -> boolean

PropertyTypeDescription

data

string

data to parse, should be either advertisment data or scan response.

uuid

string

service UUID to check.

  • Return value: true if advertisement data or scan response lists the specified service uuid or false otherwise. If invoked with invalid arguments the script is aborted.

BLE.GAP.ParseServiceData()

Parses service data string for the specified service UUID if present in the advertisement data or scan response.

BLE.GAP.ParseServiceData(data, uuid) -> string

PropertyTypeDescription

data

string

data to parse, should be either advertisment data or scan response.

uuid

string

service UUID to check.

  • Return value: extracted data, may be empty string if data is not available. If invoked with invalid arguments the script is aborted.
note

UUIDs can be 16, 32 or 128 bits. They are represented by hexadecimal strings of the correponding length with lowercase hexadecimal digits. 128-bit UUIDS are represented in format XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.

HTTP handlers

HTTPServer

HTTPServer is a global object which allows a script to register handlers to incoming HTTP requests on specific to the script endpoints. The URL of the endpoint follows the format http://<SHELLY_IP>/script/<script_id>/<endpoint_name>.

HTTPServer.registerEndpoint()

Registers an endpoint with a corresponding handler

HTTPServer.registerEndpoint(endpoint_name, callback[, userdata]) -> string

PropertyTypeDescription

endpoint_name

string

The name of the endpoint to register

callback

function

Function to be called when a request comes on the registered endpoint

PropertyTypeDescription

request

object

request object created by the API containing parameters of the request

PropertyTypeDescription

method

string

The HTTP method of the request (see note)

query

string

Query string of the request Optional, the part of the request URL following the ? if present

headers

array

Array of two element arrays each containing the header name at index 0 and header value at index 1, corresponding to the request headers

body

string

The body of the request Optional (see note)

response

object

response object created by the API containing parameters of the response to be sent to the client which should be set by the script

PropertyTypeDescription

code

number

The HTTP response code to be sent to the client Optional

body

string

The response body to be sent to the client Optional

headers

array

Array of two element arrays each containing the header name at index 0 and header value at index 1, corresponding to the response headers to be sent to the client Optional (see note)

send

function

method supplied by the API which should be called to send the repsonse. Returns true if successful or false otherwise (see note)

userdata

any type

The passed userdata

userdata

any type

Can be used to pass data to the callback

  • Return value: the part of the endpoint URL following the SHELLY_IP. The script is aborted if the arguments are not valid.
note

Handling of POST and PUT requests with payload of type multipart/* are not supported. Response to such requests is HTTP status code 415 (Unsupported Media Type).

note

The HTTP server cannot process requests of total size greater than 3072 bytes. This includes the HTTP request line, headers and body. In this case connection is reset and no response is sent.

note

Content-Length and Connection: close headers are automatically set in the response and and override any such headers set by the script. Content-Type header defaults to text/plain if not set by the script.

note

HTTP transaction (request and response) times out after 10 seconds if repsonse is not sent. If timeout is reached response of HTTP status code 504 (Gateway Timeout) is automatically sent and transaction is cancelled (send method will return false). No more than 5 concurrent transactions are allowed. If this limit reached further requests are not processed and response of HTTP status code 503 (Service Unavailable) is sent to the client.

note

HTTP endpoints exposed through scripts will require authentication if enabled on the device

Resource Limits

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

Error Handling

When the script contains errors (either a javascript error or parameters error) its execution is aborted. An error message is printed on the console and a status change event is issued with information of the type of error. This information is also avalibale in the Script.GetStatus RPC call. When the error affects the behavior of script API this is reflected in the documentation.

A special case of error is when a script causes a device crash. The script causing the crash is detected during the reboot after the crash and is disabled. The error is reported in a status change event and also in the Script.GetStatus RPC call.

Known Issues

Non-blocking execution

Shelly scripts are executed in an environment which shares CPU time with the rest of the firmware. Code in the scripts runs on the main system task and is not allowed to block for long. This is why any APIs which can potentially take a long while are callback-based. Still, it is possible to write code which will hoard the CPU for longer then acceptable. Such code may cause issues with other firmware features, communication or even cause the device to crash. One obvious example is an infinite (or near infinite) loop:

Loop for too long
let n = 0;
while (n < 500000) {
n = n + 1;
}

If a script manages to crash the device the system will detect this and disable the script at the next boot.

Limited levels of nested anonymous functions

A limitation of the javascript engine that it cannot parse too many levels of nested anonymous functions. With more than 2 or 3 levels the device crashes when attempting to execute the code. To avoid this problem it is recommended that asynchronous callback functions are defined at the top level and passed as a named reference. Also, where possible prefer synchronous calls like Shelly.getComponentStatus and Shelly.getComponentConfig to avoid the need for async callbacks altogether.

For example, instead of using an anonymous function for a callback:

Shelly.call(
"HTTP.GET",
{url: "http://example.com/"},
function(result, error_code, error_message) {
if (error_code != 0) {
// process error
} else {
// process result
}
});

Prefer a named function:

function processHttpResponse(result, error_code, error) {
if (error_code != 0) {
// process error
} else {
// process result
}
}

Shelly.call("HTTP.GET", {url: "http://example.com/"}, processHttpResponse);