Z-Way Manual


The JavaScript Engine

The Z-Way core function engine is the so called JavaScript (JS) automation system. It uses the APIs of the technology-dependent 'drivers' and delivers all the functions and interface for running a Smart Home controller:

This chapter explains the different building blocks of the JS Engine:

JavaScript API

Z-Way uses the JavaScript engine provided by Google referred to as V8. You find more information about this JavaScript implementation on

https://code.google.com/p/v8/.

V8 implements JavaScript according to the specification ECMA 5 [*].

Please note that this V8 core engine only implements the very basic JS functions and need to be extended to be usable in a Smart Home environment.

Z-Way extends the basic functionality provided by V8 with plenty of application-specific functions.

Javascript code can be executed on the server side and certain functions of the JS core are available on the client side as well since most modern web browsers have a built-in Javascript engine as well. The bridge between the server side JS and the web browser client is a built-in web server. This is the same embedded web server serving all the web browsers HTML pages etc.

There are three ways to run JavaScript code in Z-Way backend.

All options have their pros and cons. Running JS code via the browser is a very nice and convenient way to test things but the code is not persistent across Z-Way restarts.

Storing the it in a file allows to run it on Z-Way start (if 'executeFile("myfile.js")' is placed in main.js) but is not really convenient to distribute.

Writing a module requires more knowledge, but includes a nice graphical interface for App configuration. Upload your App in the Z-Way App Store for easy deployment and distribution of your App.

Check Z-Way App Store on

https://developer.z-wave.me/
for more information. There are many other open source Apps made by the community.

Accessing a server side JS function from the web browsers client side is easy. Just call

http://YOURIP:8083/JS/Run/<any JS code>

Please note that all accesses using the embedded webserver require an authentication of the web browsers instance. Please refer to chapter 13.1 for details how to authenticate in the Z-Way web server.

Z-Way offers one central object with the name 'zway' . This object encapsulates all the Z-Wave variables and functions known from the Z-Wave Device API describes in chapter 11.3.

Hence its possible to use the very same functions of the Z-Wave Device API using the JS engine. The zway objects internal structure is shown in figure 11.2 and the data elements are describes in Annex 11.3.1.

The functions can be accessed using the web browsers function like

http://YOURIP:8083/JS/Run/zway.devices[x].*

Due to the scripting nature of JavaScript it is possible to 'inject' code at run time using the interface. Here a nice example how to use the Java Script setInterval function:

[Polling device \#2]
/JS/Run/setInterval(function() { 
	zway.devices[2].Basic.Get();
}, 300*1000);

This code will, once 'executed' as a URL within a web browser, calls the Get() command of the command class Basic of Node ID 2 every 300 seconds.

A very powerful function of the JS API is the ability to bind functions to certain values of the device tree. they get then executed when the value changes. Here an example for this binding. The device No. 3 has a command class SensorMultilevel that offers the variable level. The following call - both available on the client side and on the server side - will bind a simple alert function to the change of the variable.

[Bind a function]
zway.devices[3].SensorMultilevel.data[1].val.bind(function() { 
	debugPrint('CHANGED TO: ' + this.value + '\n'); 
});

Z-Way extensions to the JavaScript Core

Z-Way provides some extensions to the JS core that are not part of the ECMA functionality mentioned above.

HTTP Access

The JavaScript implementation of Z-Way allows directly accessing HTTP objects.

The http request is much like jQuery.ajax(): r = http.request(options);

Here's the list of options:

Response (as stated above) is delivered either as function return value, or as callback parameter. It is always an object containing the following members:

Response data is handled differently depending on content type (if contentType on request is set, it takes priority over server content type):

In case data cannot be parsed as valid JSON/XML, it is still returned as string, and additional parseError member is present.

http.request({
	url: "http://server.com" (string, required),
	method: "GET" (GET/POST/HEAD, optional, default "GET"),
	
	headers: (object, optional)
	{
		"name": "value",
		...
	},
	
	auth: (object, optional)
	{
		"login": "xxx" (string, required),
		"password": "***" (string, required)
	},
	
	data: (object, optional, for POST only)
	{
		"name": "value",
		...
	}
	-- OR --
	data: "name=value&..." (string, optional, for POST only),

	async: true (boolean, optional, default false),

	timeout: (number, optional, default 20000)
	
	success: function(rsp) {} (function, optional, for async only),
	error: function(rsp) {} (function, optional, for async only),
	complete: function(rsp) {} (function, optional, for async only)
});


response:
{
	status: 200 (integer, -1 for non-http errors),
	statusText: "OK" (string),
	url: "http://server.com" (string),
	contentType: "text/html" (string),
	headers: (object)
	{
		"name": "value"
	},
	data: result (object or string, depending on content type)
}

XML parser

ZXmlDocument object allows converting any valid XML document into a JSON object and vice versa.

var x = new ZXmlDocument()

Create new empty XML document

x = new ZXmlDocument("xml content")

Create new XML document from a string

x.root

Get/set document root element. Elements are got/set in form of JS objects:

\{
    name: "node_name", - mandatory
    text: "value", - optional, for text nodes
    attributes: { - optional
    	name: "value",
    	...
    },
    children: [ - optional, should contain a valid object of same type
    	{ ... }
    ]
}

For example:

(new ZXmlDocument('<weather><city id="1"><name>Zwickau</name>
	<temp>2.6</temp></city> 
	<city id="2"><name>Moscow</name><temp>-23.4</temp></city>
	</weather>')).root =
{  
   "children":[  
      {  
         "children":[  
            {  
               "text":"Zwickau",
               "name":"name"
            },
            {  
               "text":"2.6",
               "name":"temp"
            }
         ],
         "attributes":{  
            "id":"1"
         },
         "name":"city"
      },
      {  
         "children":[  
            {  
               "text":"Moscow",
               "name":"name"
            },
            {  
               "text":"-23.4",
               "name":"temp"
            }
         ],
         "attributes":{  
            "id":"2"
         },
         "name":"city"
      }
   ],
   "name":"weather"
}

x.isXML

This hidden read-only property allows detecting if the object is an XML object or not (it is always true).

x.toString()

Converts XML object into a string with valid XML content.

x.findOne(XPathString)

Returns first matching to XPathString element or null if not found.

x.findOne('/weather/city[@id="2"]') // returns only city tag for Moscow
x.findOne('/weather/city[name="Moscow"]/temp/text()') // returns temperature in Moscow

x.findAll(XPathString)

Returns array of all matching to XPathString elements or empty array if not found.

x.findAll('/weather/city') // returns all city tags
x.findAll('/weather/city/name/text()') // returns all city names

x.document

A hidden property that refers to the document root.

XML elements

Each XML element (tag) in addition to properties described above (text, attributes, children) have hidden read-only property parent pointing to parent object and the following methods:

ZXmlDocument is returned from http.request() when content type is 'application/xml', 'text/xml' or any other ending with '+xml'. Namespaces are not yet supported.

Cryptographic functions

crypto object provides access to some popular cryptographic functions such as SHA1, SHA256, SHA512, MD5, HMAC, and provides good random numbers.

var guid = crypto.guid()

Provides standard GUID in string format.

var rnd = crypto.random(n)

Generates n random bytes. Returned value is of type ArrayBuffer. To convert it into array, use this trick:

	rnd = (new Uint8Array(crypto.random(10)));

var dgst = crypto.digest(hash, data, ...)

Returns digest calculated using selected hash algorithm. It supports virtually all the algorithms available in OpenSSL (md4, md5, mdc2, sha, sha1, sha224, sha256, sha384, sha512, ripemd160). If no data parameters specified, it returns a digest of an empty value. If more than one data parameters are specified, they're all used to calculate the result. Data parameters may be of several types: strings, arrays, ArrayBuffers. Return value is of type ArrayBuffer.

There are also a few shortcut functions for popular algorithms: 'md5', 'sha1', 'sha256', 'sha512'. For example, these calls are equivalent:

	dgst = crypto.digest('sha256', data);
	dgst = crypto.sha256(data);

var hmac = crypto.hmac(cipher, key, data, ...)

Returns hmac calculated using selected hash algorithm. Hash algorithms are the same as for digest() function.

Key parameter is required.

If no data parameters specified, it returns a HMAC of an empty value. If more than one data parameter is specified, they're all used to calculate the result. Key and data parameters may be of different types (strings, arrays, ArrayBuffers). Return value is of type ArrayBuffer.

There are also a few shortcut functions for popular algorithms: 'hmac256', 'hmac512'. For example, these calls are equivalent:

  dgst = crypto.hmac('sha256', key, data);
  dgst = crypto.hmac256(key, data);

Sockets functions

Socket module allows easy access to TCP and UDP sockets from JavaScript. Both connection to distant ports and listening on local are available. This API fully mirrors into JavaScript POSIX TCP/IP sockets. This can be used to control third party devices like Global Cache or Sonos as well as emulating third party services.

To start communication, one needs to create socket and either connect it or listen it. onrecv method is called on data receive from remote, while send is used to send data to remote side.

The example below dumps to log file response to http://ya.ru:80/ (raw HTTP protocol is used as an example).

var sock = new sockets.tcp();

sock.onrecv = function(data) {
    debugPrint(data.byteLength);
};

sock.connect('ya.ru', 80);

sock.send("GET / HTTP/1.0\r\n\r\n");

Here is an example of TCP echo server on port 8888:

var sock = new sockets.tcp();

sock.bind(8888);

sock.onrecv = function(data) {
    this.send(data);
};

sock.listen();

And echo server for UDP:

var sock = new sockets.udp();

sock.bind(8888);

sock.onrecv = function(data, host, port) {
    this.sendto(data, host, port);
};

sock.listen();

Important! Callbacks can only be specified before the connection is established.

“this“ inside callbacks refers to the socket object itself.

Detailed description of Socket API:

WebSockets functions

Socket module also implements WebSockets (RFC 6455). WebSocket API is made to be compatible with browser implementations (some rarely used functions are not implemented, see below).

The example below implements basic application using the WebSockets client:

var sock = new sockets.websocket("ws://echo.websocket.org");

sock.onopen = function () {
    debugPrint('connected, sending ping');
    sock.send('ping');
}

sock.onmessage = function(ev) {
    debugPrint('recv', ev.data);
}

sock.onclose = function() {
    debugPrint('closed');
}

sock.onerror = function(ev) {
    debugPrint('error', ev.data);
}

Next example shows basic application using WebSockets server:

var sock = new sockets.websocket(9009);

sock.onconnect = function () {
    debugPrint('client connected, sending ping');
}

sock.onmessage = function(ev) {
    debugPrint('recv', ev.data);
    sock.send('pong');
}

sock.onclose = function() {
    if (this === sock) {
        debugPrint('server websocket closed');
    } else {
        debugPrint('client disconnected');
    }
}

sock.onerror = function(ev) {
    debugPrint('error', ev.data);
}

Detailed description of WebSocket API:

MQTT functions

MQTT module allows to connect to MQTT broker from JavaScript. Both subscribtion to remote to topics and publishing own topics are possible. Unencrupted or TLS-encrypted TCP transport is supported (TLS requires libmosquitto 2.1.0 or upper).

The example below connects to a server using and publishes a topic.

var m = new mqtt("broker.emqx.io", 1883);

m.onconnect = function() {
	debugPrint("Connected");
};

m.ondisconnect = function() {
	debugPrint("Disconnected");
};

m.onpublish = function() {
	debugPrint("Published");
};
it will work when the sent message is published;

m.onsubscribe = function () {
	debugPrint("Subscribed");
};

m.onmessage = function (topic, message) {
	debugPrint("New topic " + topic + ": " + message);
};

m.connect();

Important! Callbacks can only be specified before the connection is established.

“this“ inside callbacks refers to the mqtt object itself.

Detailed description of MQTT API:

You can to set the TSL settings before the connection is established. Configure the client for certificate based SSL/TLS support:

The MQTT object provides the following methods:

For debugging purposes there is an additional method:

Other JavaScript Extensions

fs.list(folder)

This returns the list of items in the folder or undefined if the folder does not exist.

fs.stat(file)

This returns one of the following values:

fs.loadJSON(filename)

This function reads a file from the file system and loads it into the memory. The file must contain a valid JSON object. The only argument is the name of the file including relative pathname to the automation folder. Returns the full JSON object or null in case of error.

fs.load(filename)

This function reads a file from the file system and returns its content as a string. The only argument is the name of the file including relative pathname to the automation folder. Returns null in case of error.

executeFile(filename) and executeJS(string)

Loads and executes a particular JavaScript file from the local filesystem or executes JavaScript code represented in string (like eval in browsers).

The script is executed within the global namespace.

Remark: If an error occurrs during the execution, it won't stop from further execution, but erroneous scripts will not be executed completely. It will stop at the first error. Exceptions in the executed code can be trapped in the caller using standard try-catch mechanism.

system(command)

The command system() allows executing any shell level command available on the operating system. It will return the shell output of the command. By default the execution of system commands is forbidden. Each command executed need to be permitted by putting one line with the starting commands in the file automation/.syscommands or in an different automation folder as specified in config.xml.

Timers

Timers are implemented exactly as they are used in browsers. They are very helpful for periodical and delayed operations. Timeout/period is defined in milliseconds.

loadObject(object_name) and saveObject(object_name, object)

Loads and saves JSON object from/to storage. These functions implement flat storage for application with access to the object by its name. No folders are available.

Data is saved in automation/storage folder. Filenames are made from object names by stripping characters but [a-ZA-Z0-9] and adding checksum from original name (to avoid name conflicts).

exit()

Stops JavaScript engine and shuts down Z-Way server

allowExternalAccess(handlerName) and listExternalAccess()

allowExternalAccess allows registering HTTP handler. handlerName can contain strings like aaa.bbb.ccc.ddd — in that case any HTTP request starting by /aaa/bbb/ccc/ddd will be handled by a function aaa.bbb.ccc.ddd() if present, otherwise aaa.bbb.ccc(), ... up to aaa(). The handler should return object with at least property status and body (one can also specify headers like it was in http.request module).

listExternalAccess returns array with names of all registered HTTP handlers.

Here is an example how to attach handlers for /what/timeisit and /what:

what = function() {
  return { status: 500, body: 'What do you want to know' };
};

what.timeisit = function() {
  return { status: 200, body: (new Date()).toString() }
};

allowExternalAccess("what");
allowExternalAccess("what.timeisit");

debugPrint(object, object, ...)

Prints arguments converted to string to Z-Way console. Very useful for debugging. For convenience, one can map 'console.log()' to debugPrint().

This is how it was done in automation/main.js in Z-Way Home Automation engine:

var console = {
    log: debugPrint,
    warn: debugPrint,
    error: debugPrint,
    debug: debugPrint,
    logJS: function() {
        var arr = [];
        for (var key in arguments)
            arr.push(JSON.stringify(arguments[key]));
        debugPrint(arr);
    }
};

Debugging JavaScript code

Change in config.xml debug-port to 8183 (or some other) turn on V8 debugger capability on Z-Way start.

<config>
    ...
    <debug-port>8183</debug-port>
    ....
</config>

node-inspector debugger tool is required. It provides web-based UI for debugging similar to Google Chrome debug console.

You might want to run debugger tool on another machine (for example if it is not possible to install it on the same box as Z-Way is running on).

Use the following command to forward debugger port defined in config.xml to your local machine:

ssh -N USER@IP_OF_Z-WAY_MACHINE -L 8183:127.0.0.1:8183 (for RaZberry USER is pi)

Install node-inspector debugger tool and run it:

npm install -g node-inspector node-inspector –debug-port 8183

Then you can connect to

http://IP_OF_MACHINE_WITH_NODE_INSPECTOR:8080/debug?port=8183

If debugging is turned on, Z-Way gives you five seconds during startup to reconnect debugger to Z-Way (refresh the page of debugger Web UI within these five seconds). This allows you to debug startup code of Z-Way JavaScript engine from the very first line of code.


The virtual device concept (vDev)

A virtual device is a data object within the JS engine. Virtual devices have properties and functions. Most virtual devices represent a physical device or a part of a physical device but virtual devices are not limited to this. Virtual devices can be pure dummy device doing nothing but pretenting to be a device (There is an app called 'DUMMY DEVICE' that works exactly like this). Virtual devices can also connect to services via TCP/IP.

The purpose of virtual devices is to unify the appearance on a graphical user interface and to unify the communication between them. At the level of virtual devices and EnOcean controller can switch a Z-Wave switch and trigger a rule in a cloud service.

Names and Ids

Every virtual device is identified by a simple string type id. For all virtual devices that are related to physical Z-Wave devices the device name is auto-generated by the module (app) 'Z-Wave' following this logic:

ZWayVDev_[Node ID]:[Instance ID]:[Command Class ID]:[Scale ID]

The Node Id is the node id of the physical device, the Instance ID is the instance id of the device or '0' if there is only one instance. The command class ID refers to the command class the function is embedded in. The scale id is usually '0' unless the virtual device is generated from a Z-Wave device that supports multiple sensors with different scales in one single command class.

Virtual devices not generated by a Z-Wave device may have other Ids. They are either created by other physical device subsystems such as 433MHz or EnOcean or they are generated by a module (app).

Device Type

Virtual devices can have a certain types. Table shows the different types plus the defines commands. Table 12.1 shows the list of current device types with their metrics and defines commands.


Table 12.1: vDev device types with metrics and commands
deviceType Metrics Commands Examples
battery probeTitle,scaleTitle, level, icon, title - -
doorlock level, icon, title open or close apiURL/devices/:deviceId/command/open
thermostat scaleTitle, min, max, level, icon, title exact with get-param level apiURL/devices/:deviceId/command/exact? level=22.5
switchBinary (Thermostat) level, icon, title on, off or update apiURL/devices/:deviceId/command/on
switchBinary level, icon, title on, off or update apiURL/devices/:deviceId/command/on
switchMultilevel level, icon, title on Set(255), off Set(0), min Set(10), max Set(99), increase Set(l+10), decrease Set(l-10), update, exact + get params level apiURL/devices/:deviceId/command/exact? level=40
switchMultilevel (Blinds) level, icon, title up Set(255), down Set(0), upMax Set(99), increase Set(l+10), decrease Set(l-10), startUp StartLevelChange(0), startDown StartLevelChange(1), stop StopLevelChange(), update, excactSmooth + get params level apiURL/devices/:deviceId/command/stop
sensorBinary probeTitle, level, icon, title update apiURL/devices/:deviceId/command/update
sensorMultilevel probeTitle, scaleTitle, level, icon, title update apiURL/devices/:deviceId/command/update
toggleButton level, icon, title on apiURL/devices/:deviceId/command/on
camera icon, title depends on installed camera - could be: zoomIn, zoomOut, up, down, left, right, close, open apiURL/devices/:deviceId/zoomIn
switchControl level, icon, title, change on, off, upstart, upstop, downstart, downstop, exact with get-param level apiURL/devices/:deviceId/command/on
text title, text, icon - -
sensorMultiline multilineType, title, icon, level, (scaleTitle, ...) depends on apps apiURL/devices/:deviceId/command/:cmd
switchRGB icon, title, color: r:255,g:255,b:255, level on, off, exact with get-params: red, green and blue apiURL/devices/:deviceId/command/exact? red=20&green=240&blue=0


Using WebSocket API

WebSocket API provides instant control and is similar to the HTTP API.

Before connecting, make sure to authenticate or save the token in Cookies.

[Connecting using WebSockets]
var ws = new WebSocket("ws://localhost:8083");

ws.onopen = function() {
	console.log("connected");
};
ws.onclose = function() {
	console.log("disconnected");
};
ws.onmessage = function(event) {
	console.log(event.data);
};
ws.onerror = function(event) {
	console.log(event);
};

Once connected, the send command allows to send commands to the remote site. Convert the parameter object to a string first.

[Sending commands via WebSockets]
ws.send(JSON.stringify({"event": "httpEncapsulatedRequest", "data": {"url": "/ZAutomation/api/v1/devices/DummyDevice_18/command/on", "method": "GET"}}))

Events are sent in the following format:

[Events format from WebSockets connection]
{"type":"ws-reply","data":{"status":200,"body":"{\"data\":null,\"code\":200,\"message\":\"200 OK\",\"error\":null}","headers":{"Content-Type":"application/json; charset=utf-8","X-API-VERSION":"2.0.1"}}}
{"type":"me.z-wave.devices.level","data":{"creationTime":1710555717,"creatorId":18,"customIcons":{},"deviceType":"switchBinary","firmware":"v4.1.2","h":-1669838584,"hasHistory":false,"id":"DummyDevice_18","location":0,"locationName":"globalRoom","manufacturer":"Z-Wave.Me","metrics":{"title":"Dummy Device Binary","icon":"switch","level"

Access to Virtual Devices

Virtual devices can be access both on the server side using JS modules and on the client side using the JSON API. On the client they are encoded into a URL style for easier handling in AJAX code. A typical client side command in the vDev API looks like

http://YOURIP:8083/ZAutomation/api/v1/devices/ZWayVDev_6:0:37/command/off

'api' points to the vDev API function, 'v1' is just a constant to allow future extensions. The devices are referred by a name that is automatically generated from the Z-Wave Device API. The vDev also unifies the commands 'command' and the parameters, here 'off'.

On the server side the very same command would be encoded in a JavaScript style.

[Access vDevs]

vdevId = vdev.id;

vDev = this.controller.devices.get(vdevId);

vDevList = this.controller.devices.filter(function(x) { 
	return x.get("deviceType") === "switchBinary"; }); 

vDevTypes = this.controller.devices.map(function(x) { 
	return x.get("deviceType"); });

Virtual Device Usage / Commands

In case the virtual device is an actor it will accept and execute a command using the syntax:

Vdev.performCommand(„name of the command“)

The name of the accepted command should depend on the device type and can again be defined free of restrictions when implementing the virtual device. For auto-generated devices derived from Z-Wave the following commands are typically implemented.

  1. 'update': updates a sensor value
  2. 'on': turns a device on. Only valid for binary commands
  3. 'off': turns a device off. Only valid for binary commands
  4. 'exact': sets the device to an exact value. This will be a temperature for thermostats or a percentage value of motor controls or dimmers

Virtual Device Usage / Values

Virtual devices have inner values. They are called metrics. A metric can be set and get. Each virtual device can define its own metrics. Metrics can be level, title icon and other device specific values like scale (%, kWh, ...)

vDev.set("metrics:...", ...);  
vDev.get("metrics:...");

How to create your own virtual devices

A Virtual Device (Vdev) is an instance of a VirtualDevice class' descendant which exposes set of metrics and commands (according to it's type/subtype). Virtual devices are the only runtime instances which is controllable and observable through the JS API.

Technically, VDev is a VirtualDevice subclass which concretize, overrides or extends superclass' methods.

Step 1. Define a VirtualDevice subclass

// Important: constructor SHOULD always be successful
BatteryPollingDevice = function (id, controller) {
    // Always call superconstructor first
    BatteryPollingDevice.super_.call(this, id, controller);

    // Define VDevs properties
    this.deviceType = "virtual";
    this.deviceSubType = "batteryPolling";
    this.widgetClass = "BatteryStatusWidget";

    // Setup some additional metrics (many of them is setted up in a base class)
    this.setMetricValue("someMetric", "someValue");
}
inherits(BatteryPollingDevice, VirtualDevice);

VDev class should always fill in the deviceType property and often fill in the deviceSubType property.

If the particular VDev class can be controller by the client-side widget, it should define widget's class name in the widgetClass property.

Step 2. Override performCommand() method

BatteryPollingDevice.prototype.performCommand = function (command) {
    var handled = true;

    if ("update" === command) {
        for (var id in zway.devices) {
            zway.devices[id].Battery && zway.devices[id].Battery.Get();
        }
    } else {
        handled = false;
    }

    return handled ? true : 
    BatteryPollingDevice.super_.prototype.performCommand.call(this, command);
}

VDev itself mostly needed to handle commands, triggered by the events, system or the API.

In the example above you could see, that this VDev is capable of performing "update" command. But base class can be capable of performing some other commands, so the last l ine calls superclass' performCommand() method if the particular command wasn't handled by the VDev itself.

This extensibility provides the possibility to create a VDev class tree. Take a look at ZWaveGate module as an example of such tree.

Step 3. Instantiate your VDev by the module

// ...part of the BatteryPolling.init() method
executeFile(this.moduleBasePath()+"/BatteryPollingDevice.js");
this.vdev = new BatteryPollingDevice("BatteryPolling", this.controller);

First line of code is loads and executes apropriate .js-file which provides BatteryPollingDevice class.

Secnd line instantiates this class.

The last line calls controller's registerDevice method to register and VDev instance.

Step 4. Register device

[Register Device]
        vDev = this.controller.devices.create(vDevId, {
            deviceType: "deviceType",
            metrics: {
                level: "level",
                icon: "icon from lib or url"
                title: "Default title"
            }
        }, function (command, ...) {
                // handles actions with the widget
        });

Step 5: Unregister device

Devices can be deleted or unregistered using the following command:

this.controller.devices.remove(vDevId)

Binding to metric changes

The metric - the inner variables of the vDev a changed by the system automatically. In order to perform certain functions on these changes the function needs to be bound to the change to the vdev. The syntax for this is

vDev.on('change:metrics:...", function (vDev) ... );

Unbinding then works as one can expect:

vDev.off(’change:metrics:...”, function (vDev) ... )


The event bus

All communication from and to the automation modules is handled by events. An event is a structure containing certain information that is exchanged using a central distribution place, the event bus. This means that all modules can send events to the event bus and can listen to event in order to execute commands on them. All modules can 'see' all events but need to filter out their events of relevance. The core objects of the automation are written in JS and they are available as source code in the sub folder 'classes':

The file main.js is the startup file for the automation system and it is loading the three classes just mentioned. The subfolder /lib contains the key JS script for the Event handling: eventemitter.js.

Emitting events

The 'Event emitter' emits events into the central event bus. The event emitter can be called from all modules and scripts of the automation system. The syntax is:

controller.emit(eventName, args1, arg2,...argn)

The event name 'eventName' has to be noted in the form of 'XXX.YYY' where 'XXX' is the name of the event source (e.g. the name of the module issuing the event or the name of the module using the event) and 'YYY' is the name of the event itself. To allow a scalable system it makes sense to name the events by the name of the module that is supposed to receive and to manage events. This simplifies the filtering of these events by the receiver module(s).

Certain event names are forbidden for general use because they are already used in the existing modules. One example are events with the name cron.XXXX that are used by the cron module handling all timer related events.

Every event can have a list of arguments developers can decide on. For the events used by preloaded modules (first and foremost the cron module) this argument structure is predefined. For all other modules the developer is free to decide on structure and content. It is also possible to have list fields and or any other structure as argument for the event

One example of an issued event can be

emit(“mymodule.testevent”,”Test”,[“event1”,”event2”])

Catching (binding to) events

The controller object, part of every module, offers a function called 'on()' to catch events. The 'on(name, function())' function subscribes to events of a certain name type. If not all events of a certain name tag shall be processed a further filtering needs to be implemented processing the further arguments of the event. The function argument contains a reference to the implementation using the event to perform certain actions. The argument list of the event is handed over to this function in its order but need to be declared in the function call statement.

this.controller.on(“mymodule.testevent”, function (name,eventarray) )

The same way objects can unbind from events:

this.controller.off(“mymodule.testevent”, function (name,eventarray));

Notification and Severity

Notifications are a special kind of event to inform the user on the graphical user interface or out-of-band.. This means that normal events are typically describes with numbers or ids while notifications contain a human readable message.

The UI can be notified on the certain events.

this.controller.addNotification("....severity....", "....message....", "....origin....");

The parameters define

The controller can act on notifications or disable them.

this.controller.on('notifications.push', this.handler);

this.controller.off('notifications.push', this.handler);


Modules (for users called 'Apps')

Beside the core functions encoded into the JS core there are extensions to this code called modules. Modules extend the JS core by providing internal or external ( visible to the user) functions.

Each modules code is located in a sub directory of the sub folder module as described in chapter 11.4.1. The name of the subfolder equals the name of the module. The sub folder contains files to define the behavior of the module.
\begin{forest}
for tree={
font=\ttfamily ,
grow'=0,
child anchor=west,
pare...
...ible by the web server]
[lang: translation into local languages]
]
\end{forest}

Module.json

This file contains the module meta-definition used by the AutomationController. It must be a valid JSON object with the following fields (all of them are required):

All configuration fields are required. Types of the object must be equal in every definition in every case. For instance, if module doesn't export any metric corresponding key value should be and empty object “”.

index.js

This script defines an automation module class which is descendant of AutomationModule base class. During initialization the module script must define the variable '_module' containing the particular module class.

Example of a minimal automation module:

[Minimal Module]

function SampleModule (id, controller) {
    SampleModule.super_.call.init(this, id, controller);
    
    this.greeting = "Hello, World!";
}

inherits(SampleModule, AutomationModule);
_module = SampleModule;

SampleModule.prototype.init = function () {
    this.sayHello();
    
    // subscriptions and initializations
};

SampleModule.prototype.stop = function () {
    // unsubscriptions and cleanup of allocated obkects
};

SampleModule.prototype.sayHello = function () {
    debugPrint(this.greeting);
};

The first part of the code illustrates how to define a class function named SampleModule that calls the superclass' constructor. Its highly recommended not to do further instantiations in the constructur. Initializations should be implemented within the 'init' function.

The second part of the code is almost immutable for any module. It calls prototypal inheritance support routine and it fills in _module variable.

The third part of the sample code defines module's init() method which is an instance initializer. This initializer must call the superclass's initializer prior to all other tasks. In the initializer module can setup it's private environment, subscribe to the events and do any other stuff. Sometimes, whole module code can be placed withing the initializer without creation of any other class's methods. As the reference of such approach you can examine AutoOff module source code.

After the init function a module may contain other functions. The 'sayHello' function of the Sample Module shows this as example.

Available Core Modules

All modules in Z-Way are designed the same way using the same file structure but they serve different purposes and they are of different importance:

The two core modules are worth to be explained in detail:

Cron, the timer module

All time driven actions need a timer. The Z-Way automation engine implement a cron-type timer system as a module as well. The basic function of the cron module is

The registration and deregistration of events is also handled using the event mechanism. The cron module is listening for events with the tags 'cron.addTask' and 'cron.removeTask'. The first argument of these events are the name of the event fired by the cron module. The second argument of the 'addTask' event is an array desricing the times when this event shall be issued. It has the format:

The argument for the different time parameters has one of three formats

The object

{minute : null, hour : null, weekDay : null, day : null, month : null}

will fire every minute within every hour within every weekday on every day of the month every month. Another example of an event emitted towards the cron module for registering an timer event can be found in the Battery Polling Module:

[Registering a Battery Polling Command]
    this.controller.emit("cron.addTask", "batteryPolling.poll", {
        minute: 0,
        hour: 0,
        weekDay: this.config.launchWeekDay,
        day: null,
        month: null        
    });

This call will cause the cron module to emit an event at night (00:00) on a day that is defined in the configuration variable this.config.launchWeekDay, e.g. 0 = Sunday.

The 'cron.removeTask' only needs the name of the registered event to deregister.

Z-Wave

The whole mapping of Z-Wave devices into virtual devices is handled by a module called 'ZWAVE'. This module is quite powerful. It does not only manage the mapping but handles various Z-Wave specific functions such as timing recording, etc.


Footnotes

... 5[*]
http://www.ecma-international.org/publications/standards/Ecma-262.htm