Now we have a working Raspberry Pi 3 with Z-Way it's time to connect it to the backbone of Thuis: MQTT. For this I developed a custom userModule/app. In this post I'll explain what it does, how to use it and how it works.
Goal
The app enables Z-Way to send and receive MQTT messages. Whenever the status of one of the selected devices changes it will be published to a topic. Based on these topics some other topics are available to change the status of the devices or request a status update.
How to use
Install
Z-Way allows developers to create userModules and publish them to their App Store. Unfortunately they still didn't reply to my submit, so it's available straight away. Luckily they do have a beta-token mechanism, so it's still possible to install it. I've used BaseModule by @maros as basis, so you'll need to install this as well. To install it follow these steps:
- Go to the Management page of your Z-Way
- Open the App Store Access tab
- Add
mqtt_beta
as token - Now go to Apps, and then Online Apps
- Search for Base Module and install it
- Search for MQTT and install it
Configure
When you go to the settings of the app you'll find quite some options. The first section are the basic settings needed for MQTT: the client ID and hostname/port/username/password of the MQTT broker. Next are the common topic prefix (we'll use Thuis/device
for now) and postfixes for requesting a status update and for setting the status of a device.
More interesting is the second section, which are the publications. When you add a publication you first select a type: a single device or tagged devices. We'll use the latter for now. Now you will add some tags to select which devices will be part of this publication.
Next is deciding on the topic for the publication. You can use two placeholders: %deviceName%
and %roomName%
. They will be replaced by the actual values for the corresponding device. In this example we'll use the topic %roomName%/%deviceName%
.
The last option is wether or not to publish status updates as retained messages, we'll turn it on.
Receive status updates
Now you've configured the Z-Way MQTT app so we can use it to receive updates on our devices. We'll use mosquitto_sub
to demonstrate this:
robin@thuis-server-core:~# mosquitto_sub -v -t Thuis/# Thuis/device/kitchen/counterSwitch on Thuis/device/kitchen/counterSwitch off Thuis/device/office/dimmer 0 Thuis/device/kitchen/luminescence 39
As you can see a devices called 'Counter Switch' in the room 'Kitchen' was turned on and off, the dimmer in the office was turned down, and the luminescence sensor in the kitchen gave an updated status.
Interact
There are two interactions available: requesting a status update and setting a value. Both can be demonstrated using mosquitto_pub
. To request the status of the dimmer in the office publish a message to Thuis/device/office/dimmer/status
with an empty message:
robin@thuis-server-core:~# mosquitto_pub -t Thuis/device/office/dimmer/status -m ""
When you subscribe to the dimmer's topic in the meanwhile you receive the new status:
robin@thuis-server-core:~# mosquitto_sub -v -t Thuis/device/office/dimmer Thuis/device/office/dimmer 0
Then to set the dimmer to 60% you send the message 60
to Thuis/device/office/dimmer/set
:
robin@thuis-server-core:~# mosquitto_pub -t Thuis/device/office/dimmer/set -m "60"
The light will turn on and you'll see the status changed:
robin@thuis-server-core:~# mosquitto_sub -v -t Thuis/device/office/dimmer Thuis/device/office/dimmer 60
Currently values between 0 and 100 are supported for dimmers (devices supporting the SwitchMultiLevel command class) and on
/off
for switches (devices supporting the SwitchBinary command class).
How does it work
Z-Way apps are made in JavaScript. Their basic structure is defined in the developer manual. As said before I'm using the BaseModule as basis as it provides some useful functions for interacting with devices, for example filtering status updates to includes only updates with actual changes. The full project can be found on my GitHub as Zway-MQTT. Here I'll explain some interesting parts of the code.
MQTT client
From a userModule you can call out to external utilities, but only when you give explicit permission. I would like to avoid this, so I searched for a pure JavaScript solution. Most JavaScript-based MQTT libraries use WebSockets, which is unfortunately not available in Z-Way. Luckily I found a module by @goodfield. He found and modified a MQTT client for use within Z-Way. I cleaned it up and started using it:
self.client = new MQTTClient(self.config.host, parseInt(self.config.port), {client_id: self.config.clientId});
Publishing status updates
To receive updates we have to subscribe to updates sent by the Z-Way core. I'm using the modify:metrics:level
event, which is the filtered version from BaseModule.
self.callback = _.bind(self.updateDevice, self); self.controller.devices.on("modify:metrics:level", self.callback);
In updateDevice
I retrieve the new value from the device and transform it if needed. Then I'll look up all publications matching this device (for example because they are tagged with the configured tag) and execute a MQTT publish for them.
MQTT.prototype.updateDevice = function (device) { var self = this; var value = device.get("metrics:level"); if (device.get("deviceType") == "switchBinary" || device.get("deviceType") == "sensorBinary") { if (value == 0) { value = "off"; } else if (value == 255) { value = "on"; } } self.processPublicationsForDevice(device, function (device, publication) { var topic = self.createTopic(publication.topic, device); self.publish(topic, value, publication.retained); }); };
You'll notice the createTopic
call, this call takes care of merging the prefix and configured topics plus it replaces the placeholders. Device and room names are camel cased to have nice and valid topics.
MQTT.prototype.createTopic = function (pattern, device) { var self = this; var topicParts = [].concat(self.config.topicPrefix.split("/")) .concat(pattern.split("/")); if (device != undefined) { topicParts = topicParts.map(function (part) { return part.replace("%roomName%", self.findRoom(device.get("location")).title.toCamelCase()) .replace("%deviceName%", device.get("metrics:title").toCamelCase()); }); } return topicParts.filter(function (part) { return part !== undefined && part.length > 0; }).join("/"); };
Reacting on interaction
To react on status requests and new values the app will subscribe to all topics starting with the prefix. It will then filter out the actions. If it receives an action message it will try to find the corresponding publication and device bases on the topic. It will then take the requested action.
self.client.subscribe(self.createTopic("/#"), {}, function (topic, payload) { var topic = topic.toString(); if (!topic.endsWith(self.config.topicPostfixStatus) && !topic.endsWith(self.config.topicPostfixSet)) { return; } self.controller.devices.each(function (device) { self.processPublicationsForDevice(device, function (device, publication) { var deviceTopic = self.createTopic(publication.topic, device); if (topic == deviceTopic + "/" + self.config.topicPostfixStatus) { self.updateDevice(device); } if (topic == deviceTopic + "/" + self.config.topicPostfixSet) { var deviceType = device.get('deviceType'); if (deviceType.startsWith("sensor")) { self.error("Can't perform action on sensor " + device.get("metrics:title")); return; } if (deviceType === "switchMultilevel" && payload !== "on" && payload !== "off") { device.performCommand("exact", {level: payload + "%"}); } else { device.performCommand(payload); } } }); }); });
We can now easily talk to Z-Wave devices through MQTT messages. Later I'll add support for more command classes, like thermostat and power usage. Someone already requested battery level updates through GitHub, which is an interesting addition as well.