Z-Wave : Lessons Learned – Python OpenZwave

This article explains a few missing pieces of the puzzle I had when setting up a home automation network using ZWave. Most of this information is available publicly but it took a while to find or to actually make the connection between what I wanted and what I needed to look for in the documentation.  

Initial Setup

Hardware:

Software:

Things UI Screenshot

Though being relatively new, Things Gateway was impressive. Easy to setup, automatic discovery, worked with all my devices and very extensible. 

Digging deeper…

While the documented setup for Things Gateway got me up and running quickly, there were a few things I wanted to fix, mainly the reporting interval of the sensor (read on for why I wanted to change this). Getting there was an interesting journey since the Things Gateway didn’t seem to have a UI that caters for this (yet)

Enter OpenZwave

The Things Gateway backend uses the OpenZwave project, which comes with handy libraries. The one I used was Python OpenZwave. After installing this on the RPi, I attempted to run one of the examples api_demo.py, and ran into my first couple of newbie mistakes:

Lesson 1: The Aeotec USB stick emulates a serial device, which means only one process can use it at a time. You have to disable the Things Gateway ZWave adapter before Python OpenZWave can use the stick. 

Lesson 2: If some of your ZWave devices are “asleep”, your network will never go into “ready” state. Make sure all your ZWave devices are awake. 

The second lesson bears some expanding especially for newbies like me. Most battery powered ZWave devices go into “sleep mode” for extended periods of time to extend battery life, in which period they do not respond to active polls. Instead – depending on the type of device – they send events (like motion detection) or reports (like sensor values) at periodic intervals. In addition, when asleep the devices will not respond to negotiation requests from your ZWave controller. The longer these sleep periods, the better for battery life – at the cost of accuracy of course. Samsung SmartThings has some good documentation expanding on this: https://docs.smartthings.com/en/latest/device-type-developers-guide/z-wave-primer.html

So you may end up in situations like:

  • The UI of your gateway seems to never refresh it’s values. This is what initially made me want to change the reporting intervals
  • When running the Python API_DEMO example, the “network.STATE_READY’ was never attained, so I got unexpected results. I had to manually wake up the Fibaro Motion sensor (by manually tapping the action button once). 

Once past these details, the API_DEMO script should return a whole lot of information, such as:

Advanced Configuration

Changing the sensor configuration is a bit more complicated. We need to interact directly with the ZWave device. Open a python shell and copy/paste:

import logging
import sys, os
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('openzwave')
import openzwave
from openzwave.node import ZWaveNode
from openzwave.value import ZWaveValue
from openzwave.scene import ZWaveScene
from openzwave.controller import ZWaveController
from openzwave.network import ZWaveNetwork
from openzwave.option import ZWaveOption
import time
import six
if six.PY3:
from pydispatch import dispatcher
else:
from louie import dispatcher
device="/dev/ttyACM0"
log="None"
#Define some manager options
options = ZWaveOption(device, \
config_path="/usr/etc/openzwave/", \
user_path=".", cmd_line="")
options.set_log_file("OZW_Log.log")
options.set_append_log_file(False)
options.set_console_output(False)
options.set_save_log_level(log)
options.set_logging(True)
options.lock()
def louie_network_started(network):
print("Hello from network : I'm started : homeid {:08x} – {} nodes were found.".format(network.home_id, network.nodes_count))
def louie_network_failed(network):
print("Hello from network : can't load :(.")
def louie_network_ready(network):
print("Hello from network : I'm ready : {} nodes were found.".format(network.nodes_count))
print("Hello from network : my controller is : {}".format(network.controller))
dispatcher.connect(louie_node_update, ZWaveNetwork.SIGNAL_NODE)
dispatcher.connect(louie_node_event, ZWaveNetwork.SIGNAL_NODE_EVENT)
dispatcher.connect(louie_value_update, ZWaveNetwork.SIGNAL_VALUE)
def louie_network_awake(network):
print("Hello from network : I'm awake")
def louie_node_update(network, node):
print("Hello from node : {}.".format(node))
def louie_value_update(network, node, value):
print("Hello from value : {}.".format( value ))
def louie_node_event(**kwargs):
print("Hello from node event : {}.".format( kwargs ))
#Create a network object
network = ZWaveNetwork(options, autostart=False)
#We connect to the louie dispatcher
dispatcher.connect(louie_network_started, ZWaveNetwork.SIGNAL_NETWORK_STARTED)
dispatcher.connect(louie_network_failed, ZWaveNetwork.SIGNAL_NETWORK_FAILED)
dispatcher.connect(louie_network_ready, ZWaveNetwork.SIGNAL_NETWORK_READY)
dispatcher.connect(louie_network_awake, ZWaveNetwork.SIGNAL_NETWORK_AWAKED)
network.start()
#We wait for the network.
print("***** Waiting for network to become ready : ")
for i in range(0,90):
if network.state>=network.STATE_READY:
print("***** Network is ready")
break
else:
sys.stdout.write(".")
sys.stdout.flush()
time.sleep(1.0)
Most of the above is a modified version from the python-openzwave examples folder

At this stage, the network object will allow you to interact with your network. First step, identify the node you’d like to communicate with:

>> network.nodes
{1: <openzwave.node.ZWaveNode object at 0x76c659d0>, 6: <openzwave.node.ZWaveNode object at 0x764fc610>}
>> network.nodes[6].manufacturer_name
'FIBARO System'

So node number 6 is what I’m after. Now to get to the configuration values. Looking at the documentation for the Fibaro device shows the different configuration parameters:

So for example:

I’d like to change parameter number “64”. But:

>> network.nodes[6].get_configs()
{}

An empty result. Seems like i’m not the only one to get to this point, but devs say it’s expected…. This gave a clue:

>> network.nodes[6].product_name
Unknown: type=0801, id=1002

It turns out that the ZWave protocol on it’s own does not allow you to “auto-discover” the configuration options available of a device. It needs to know which device it’s talking to in order to know which configuration options that device supports, and it figured out which device it’s talking to from the above “type” and “id” numbers.

Lesson 3: If you see an “Unknown” node product name like the above exmaple, the type and ID numbers don’t match anything OpenZwave have in their database, and you need to add it manually. Read https://github.com/OpenZWave/open-zwave/wiki/Adding-Devices

Configuring OpenZwave

After going through the above link, first stop is to check the manufacturer_specific.xml file under /usr/etc/openwave for our product type and id. In my case Fibaro seems to have updated their product but OpenZwave hasn’t yet caught up. There was an entry in the XML file that was very close to what I was looking for:

< Product type="0801" id="1001" name="FGMS001-ZW5 Motion Sensor" config="fibaro/fgmszw5.xml"/ >

Double check the fgmszw5.xml file, but in my case it matched the documentation and I was able to simply add a new line in the manufacturer_specific.xml file with the correct type and id I required:

< Product type="0801" id="1002" name="FGMS001-ZW5 Motion Sensor" config="fibaro/fgmszw5.xml"/ >

I restarted my interactive python script ran network.nodes[6].get_configs()…. and got an empty dictionary again. 

Lesson 4: Remove any zwcfg_*.xml files you have – otherwise OpenZwave wont update it’s configuration

After doing that, and restarting the script, things look a lot more promising:

>>> network.nodes[6].product_name
'FGMS001-ZW5 Motion Sensor'
>>> network.nodes[6].get_configs()
{ 72057594148814916L: < openzwave.value.ZWaveValue object at 0x7602c9d0 >, … , 72057594148815846L: < openzwave.value.ZWaveValue object at 0x7602cbd0 >}

Let’s make the output of get_configs a bit easier to understand:

>>> configs = network.nodes[6].get_configs()
>>> for c in configs:
>>> print configs[c]
...
home_id: [0xfd7d0579] id: [72057594148815878] parent_id: [6] label: [Temperature report - interval] data: [0]
...

Right, now we have the id for the configuration option that we’d like to change. So we set it to send temperature report intervals every minute using:

>>> network.nodes[6].set_config(72057594148815878, 60)

… and nothing happens. Waiting for a minute doesn’t show any temperature reports as expected.

Lesson 5: ZWave devices only update their config when they’re awake. Make sure the device you are configuring is awake!

After that’s done, results come in as expected:

All done.. stop the python openzwave network:

>>> network.stop()

… and don’t forget to re-enable the ZWave adapter from the Things Gateway UI!

Advertisement

One thought on “Z-Wave : Lessons Learned – Python OpenZwave

Comments are closed.