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.
SCRIPT:
let n = 2 + 2;
n = n - 2;
n = n * 2;
n = n / 2;
print(n);
____________________________________________________________________________________________________
OUTPUT:
2
SCRIPT:
let n = 2;
n = n << 2;
print(n);
n = n >> 2;
print(n);
____________________________________________________________________________________________________
OUTPUT:
8
2
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:
length
property, in bytesat(pos)
, alias ofcharCodeAt(pos)
as String.prototype.charCodeAt()indexOf()
as String.prototype.indexOf()slice()
as String.prototype.slice()[pos]
operator which returns a 1-char string ifpos
is not out of bounds
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.
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.
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 !==
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
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
length
property, the number of elementssplice()
as Array.prototype.splice()push()
, as Array.prototype.push()
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.
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
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
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
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
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
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)
.
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.
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:
- //
- /* */
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)
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.
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.
SCRIPT:
print("hello world", {}, [], null);
____________________________________________________________________________________________________
OUTPUT:
hello world <object> <array> null
Object.create(proto)
Create an object with prototype.
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.
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").
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").
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. This method doesn't return a value. If invoked with invalid arguments the script is aborted. The signature of the method is:
Shelly.call(method, params, callback, userdata) -> boolean
Property | Type | Description | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| string | Name of the method to invoke | |||||||||||||||
| object or string | Parameters | |||||||||||||||
| function | Function, will be invoked when the call completes
|
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
Shelly.removeEventHandler(subscription_handle) -> boolean
Shelly.removeStatusHandler(subscription_handle) -> boolean
addEventHandler()
and addStatusHandler()
take the following:
Property | Type | Description | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
| function | Will be invoked when the respective event occurs
|
- Return value: On success, returns a handle which can be used to remove the listener with
Shelly.removeEventHandler(subscription_handle)
orShelly.removeStatusHandler(subscription_handle)
respectively. If invoked with invalid arguments the script is aborted.
removeEventHandler()
and addStatusHandler()
take a previously obtained subscription_handle
and return 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
Property | Type | Description |
---|---|---|
| string | Name of the event |
| scalar or object or array | payload of the event. Any valid JSON value is allowed. |
- This method doesn't return a value. If invoked with invalid arguments the script is aborted.
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)
Property | Type | Description |
---|---|---|
| string | Component type or key("component:id"). Component type must be in lowercase. |
| number | Numeric |
- 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)
Property | Type | Description |
---|---|---|
| string | Component type or key("component:id"). Component type must be in lowercase. |
| number | Numeric |
- 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
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
Property | Type | Description |
---|---|---|
| number | In milliseconds |
| boolean | If |
| function | To be invoked when the timer fires |
| 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 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
Property | Type | Description |
---|---|---|
| handle | handle previously returned by |
- Return value:
true
if the timer was armed and destroyed,false
if no such timer existed orundefined
if the giventimer_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()
- Return value:
true
if device is connected to a MQTT broker orfalse
otherwise.
MQTT.subscribe()
- Subscribe to a topic on the MQTT broker.
MQTT.subscribe(topic, callback, callback_arg)
Property | Type | Description | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| string | The topic to subscribe to | ||||||||||||
| function | Function to be called when a message is published on the topic
| ||||||||||||
| any type | User data to be passed to the callback |
- This method doesn't return a value. If invoked with invalid arguments the script is aborted.
MQTT.unsubscribe()
- Unsubscribe from a topic previously subscribed, can be called only for topics subscribed in the same script.
MQTT.unsubscribe(topic)
Property | Type | Description |
---|---|---|
| string | The topic to subscribe to |
- Return value:
true
if topic is subscribed to and unsubscribed orfalse
if subscription to the topic does not exist. The script is aborted if the argument is not valid.
MQTT.publish()
- publish a message to a topic.
MQTT.publish(topic, message, qos, retain)
Property | Type | Description |
---|---|---|
| string | The topic to publish |
| string | Тhe message to publish |
| integer | Can be |
| boolean | If |
MQTT.setConnectHandler()
- Registers a handler for the MQTT connection established event.
- This method doesn't return a value. If invoked with invalid arguments the script is aborted.
MQTT.setConnectHandler(callback, callback_arg)
Property | Type | Description | ||||||
---|---|---|---|---|---|---|---|---|
| function | Function to be called when event is received
| ||||||
| any type | User data to be passed to the callback |
MQTT.setDisconnectHandler()
- registers a handler for the MQTT connection closed event.
- This method doesn't return a value. If invoked with invalid arguments the script is aborted.
MQTT.setDisconnectHandler(callback, callback_arg)
Property | Type | Description | ||||||
---|---|---|---|---|---|---|---|---|
| function | Function to be called when event is received
| ||||||
| any type | User data to be passed to the callback |
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(callback[, callback_arg])
- Subscribe 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.
- This method doesn't return a value. If invoked with invalid arguments the script is aborted.
Property | Type | Description | ||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| function or null | Function invoked for each scan event. Specify
|
| any type | User data passed in the call | |||||||||||||||||||||||||||||||||||||||||||||
| any type | User data to be passed to the callback. Optional |
BLE.Scanner.Start(options[, callback[, callback_arg]])
- Start a scan and optionally subscribe for scan events.
- 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.
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, supply a single option: {duration_ms: BLE.Scanner.INFINITE_SCAN}
.
Property | Type | Description | ||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| object | object with scan options. All options are optional and may be omitted, substituting with defaults. Required
| ||||||||||||||||||||||||||||||||||||||||||||||||
| function | Function invoked for each scan event. If specified the call will also subscribe for scan events. Optional
|
| any type | User data passed in the call | |||||||||||||||||||||||||||||||||||||||||||||
| any type | User data to be passed to the callback. Optional |
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 orfalse
if there is an error.
BLE.Scanner.isRunning()
- Return value:
true
if currently there is a running scan orfalse
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
= 1;BLE.GAP.ADDRESS_TYPE_RANDOM_STATIC
= 2;BLE.GAP.ADDRESS_TYPE_RANDOM_NON_RESOLVABLE
= 3;BLE.GAP.ADDRESS_TYPE_RANDOM_RESOLVABLE
= 4;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;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(data)
- Parse device name from advertisement 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.
Property | Type | Description |
---|---|---|
| string | data to parse, should be either advertisment data or scan response. Required |
BLE.GAP.parseManufacturerData(data)
- Parse manufacturer data from advertisement 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.
Property | Type | Description |
---|---|---|
| string | data to parse, should be either advertisment data or scan response. Required |
BLE.GAP.ParseDataByEIRType(data, type)
- Parse data for specified EIR type (Extended Inquiry Response) from advertisement 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.
Property | Type | Description |
---|---|---|
| string | data to parse, should be either advertisment data or scan response. Required |
| number | EIR type, should be one of the defined constants. Required |
BLE.GAP.HasService(data, uuid)
- Search for specified service UUID in the advertisement data or scan response.
- Return value:
true
if advertisement data or scan response lists the specified service uuid orfalse
otherwise. If invoked with invalid arguments the script is aborted.
Property | Type | Description |
---|---|---|
| string | data to parse, should be either advertisment data or scan response. Required |
| string | service UUID to check. Required |
BLE.GAP.ParseServiceData(data, uuid)
- Parse service data string for the specified service UUID if present in the advertisement 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.
Property | Type | Description |
---|---|---|
| string | data to parse, should be either advertisment data or scan response. Required |
| string | service UUID to check. Required |
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, callback_arg)
Property | Type | Description | ||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| string | The name of the endpoint to register | ||||||||||||||||||||||||||||||||||||||||||
| function | Function to be called when a request comes on the registered endpoint
| ||||||||||||||||||||||||||||||||||||||||||
| any type | User data to be passed 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:
- No more than 5 timers used in a script;
- No more than 5 subscriptions for events used in a script;
- No more than 5 subscriptions for status changes used in a script;
- No more than 5 RPC calls used in a script.
- No more than 10 MQTT topic subscriptions used in a script.
- No more than 5 HTTP registered endpoints used in a script.
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:
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);