Using Zwave Configure in an Edge Driver

Until the next application time, the commands will just remain queued up on the hub, waiting until they can be delivered.

Do you know how this is implemented from a driver perspective? I have a z-wave motion sensor that wakes periodically (I think every 6 minutes) but isn’t applying the parameter change.

The driver has an info_changed event function that gets called and sends any parameters that have changed via

device:send(Configuration:Set({parameter_number = preferences[id].parameter_number, size = preferences[id].size, configuration_value = new_parameter_value}))

…I had assumed if the device was asleep this message would just not be delivered though? Do you need to do something explicitly to test whether the device is awake before sending, or handle a wake_up event and then send?

It’s up to the manufacturer, but typically, as described above, for zwave devices configuration changes for sleepy devices remain queued on the hub until the device requests that the queue is sent, at which point they will be applied in the order in which they were initially received by the hub.

The default request interval is totally up to the manufacturer, because it will use up battery life to send the queue request and then process the individual configuration changes. Some devices do it every eight hours, some do it once a day. A few safety devices like smoke alarms may only do it once a week. And still other devices might never request the Queue unless you specifically wake them up to do so. And there are even some who just won’t accept configuration changes except at the time of joining a network. Again, all of this is up to the device manufacturer.

Some devices, but not all, allow you to change the request interval, but only within the manufacturer-specified values. If the device supports “wakeup_command_class V2” then you can query it for the following values:

minimum
maximum
default
supported wake-up interval step, all in seconds

(If the minimum, maximum, and default are all the same value, then you are not allowed to change the interval)

Waking up to report is not necessarily the same interval as waking up to process queued configuration change requests. Aeotec is a good example. They might have a temperature sensor which reports every 15 minutes, but only processes queued configuration change requests every eight hours unless you specifically put it into configuration change processing mode with a physical manipulation of the sensor. Again, this is all done to save battery life.

The instructions for waking up a battery powered device by using a physical tap pattern should be in the user manual for that device.

A sleepy z-wave device will send a wake up notification when it’s awake and ready to receive commands. The device might “wake up” more frequently - such as to send regular temperature or humidity reports - but if it doesn’t send the wake up notification then it’s likely going back to sleep immediately after sending the report.

Configuration parameter changes need to be sent immediately after the wakeup notification, before the device goes back to sleep. You can define your own zwave handler for WakeUp:Notification or use the structure set up in the default handlers by defining an update_preferences function, which is described at the link below:

https://developer.smartthings.com/docs/edge-device-drivers/zwave/device.html#sleepy-devices

1 Like

Thanks, this is the info I was looking for. I wired up the z-wave wake-up notification handler and saw that trigger, but it wasn’t then easy to send the changed parameters. The update_preferences function is obviously designed for doing this as per the example provided, so I’ll give that a go now.

1 Like

Hi @sipuncher

Almost all battery powered zwave devices that sleep between status reports have a manual mechanism to wake them up, by pressing a button, … in the manuals it is usually specified.
For example in the fibaro manuals.

The KeyFob allows to customize its operation to user’s needs. The settings are available in the FIBARO interface as simple options that may be chosen by selecting the appropriate box. In order to configure the KeyFob (using the home Center controller):

  1. Go to the device options by clicking the icon:
  2. Select the „Advanced” tab.
  3. Modify values of chosen parameters.
  4. Save the changes.
  5. Click and simultaneously O and + to wake up the device.

The driver normally always sends the parameter value regardless of whether it is asleep or not. You simply have to wake it up at the time of sending it.

In the case of the driver that you are using stock Z-Wave Sensor, I removed a restriction in my driver, which smartthings has put in the infoChanged lifecycle function, which prevents sending parameter changes if the device supports c.c. wakeUp, it seems to me that this restriction does not make sense if I wake up the device.

Maybe I misunderstand the code, but I think not.

I recommend that you delete the if not device:is_cc_supported(cc.WAKE_UP) then line and try

local function info_changed(self, device, event, args)
   if not device:is_cc_supported(cc.WAKE_UP) then
     preferences.update_preferences(self, device, args)
   end
end
1 Like

The stock drivers/handlers send configuration parameter changes once and assume they worked. They never retry if the changes failed to take. That code is in there to prevent the driver from sending to a sleepy device immediately while the device is asleep. The intent is for you to make the change in the app, then wake up the device. Deleting that line will cause configuration changes to be missed unless other changes are made to the code.

I’d rather not have to wake up the device to update the preferences so am interested in the sample @philh30 provided that apparently is triggered when a devices wakes up.

I assume from the code that the following registers the update_preferences function to be called when the device wakes up. I’ll give this a go. Does anyone know when the device_init lifecycle event is called? Would that be when I apply the driver to the device (eg each time I make a code change and try to test it) or when the device is first paired to the hub?

local function device_init(self, device)
  device:set_update_preferences_fn(update_preferences)
end

Again with the understanding that “worked” means “were accepted into the hub queue to be processed the next time the device requests its queue.” Which doesn’t mean “updated immediately.“

I know you know that, I just want to emphasize it because we get so many forum questions about why a configurations change “isn’t doing anything“ when it’s probably just still waiting in the queue. :sunglasses:

Is that how it works, that the device requests its queue? I had assumed the hub sends messages out and they either get accepted by the device and therefore an acknowledgement is returned, or they don’t as it is asleep, and no acknowledgement is returned.

Under the independent third-party standard For Z wave, it is supposed to be the sleepy end device which tells the hub it is ready for the messages from the queue.

The hub shouldn’t send the messages unless the sleepy device says it’s ready. And that interval is up to the end device.

The idea is that There’s no point in clogging up the network with messages that aren’t going to be processed, so the hub is supposed to hold onto them until it knows the sleepy device is awake and ready because the sleepy device told it so.

However, based on what @philh30 says below, it appears that smartthings may not be following the standard in this regard. So I will leave it up to Phil and other developers to discuss further details on your question. :sunglasses:

And here’s the official specification. It goes into all the details, including discussion of the various subclasses:

No. Worked meaning the device accepted the configuration change.

It’s the driver’s responsibility to add commands to the z-wave radio queue at the right time. If the driver adds a command to the z-wave radio’s queue while the device is asleep, the radio tries to transmit fairly immediately (and will try a couple times in a short period) and fails. There’s no follow up attempt included in the stock drivers/handlers.

@Mariano_Colmenarejo is suggesting to disable part of the logic that makes the driver wait until the right time to send configuration changes. He may have other logic in his drivers to check whether the changes took effect so that what he’s suggesting works in his case, but the stock drivers don’t have any such check.

You may be speaking generally of a “z-wave hub”, but in the ST ecosystem the z-wave radio will send messages from its queue as soon as it can.

1 Like

@sipuncher @JDRoberts I think there’s some confusion here in that this topic was a FAQ intended for general users, while Simon is asking questions in relation to his development of a driver. This conversation should probably move to its own thread so that the technical details of writing an edge driver are separated from the FAQ.

1 Like

Good point. Done. :sunglasses:

1 Like

Every time the driver begins running for that device. That will include when the device is joined, when the hub is rebooted, when the driver restarts following a code update, and when the hub is running out of memory and begins restarting all of your drivers. Others have tested to determine when exactly init and other lifecycles happen, but init is at least at every start up.

2 Likes

I would agree with you if I was able to understand how the stock driver sends preference changes when the device wakes up. Maybe I’m missing something, which I don’t see in the default libraries, that calls the update_preferences function when the device wakes up.

In the driver I don’t see anywhere that the update_preferences function is called to send the changes that have been produced by the user.

The only time update_preferences function is called for devices that support c.c.wakeUp is in the init lifecycle, with the function:

local function device_init(self, device)
    device:set_update_preferences_fn(preferences.update_preferences)
end

This function from the default libraries would execute preferences.update_preferences for the device only in the init lifecycle.

This means that the changes in preferences in devices with wakeUp will never be updated, except during the installation or when the driver or the hub is restarted…, but if the driver or the hub is restarted I do not think it guarantees that the device will wake up, but I may be wrong.

The function preferences.update_preferences already has a filter to only send parameter values that have been changed in the preferences from the last value saved in permanent memory, but the driver never really knows what value the device has for each parameter since driver is not read device parameters to save them in permanent memory. Well, except in the Fibaro door-window sensor subdriver, which has a specific infoChanged function to synchronize the value of the parameter saved in the device and the value in user preferences.

That’s why I think the best way to do it is to wake up the device and try to send the new value of the parameter as fast as possible.

This is how I do it with my fibaro button and the Z-Wave Device Config Mc driver and it works perfectly.

UPDATE:
I have retested with CLI the function that is saved in the lifecycle device_init function and now I see that it calls the update.preferences function when the WAKE_UP NOTIFICATION is received from the device.
At the time I changed the infoChanged function code I didn’t see that it worked well and that’s why I changed it.

I will put the preferences an option for the user to choose to send the new parameters values immediately or automatically when he wakes up

Thank you @philh30

3 Likes

In the init lifecycle stores the function that will be called to update the preferences when a wakeup notification is received from the device.
This will make the function preferences.update_preferences send to the device the values of parameters that have changed their value in preferences with respect to the last value saved in the last preferences_update.

if there has not been a change in preferences you will not see the value being sent to device.

For this automation to work you must restore the original code of the infoCanged function, since it need detect value changes in preferences when the device wakeUp and function is called when wakeUp notification received.

you can put a log.debug or print in preferences.update_preferences to see if it enters the function

1 Like

I remember the code for this being a little hard to find in the Lua library since it’s a default handler for the wakeup command class, not related to a capability like most default handlers. It might be in the init.lua of the default folder. I’m not at a computer to be able to go searching right now.

Yes, it is in the st/zwave/driver.lua

--- Wrap wake up notification handler to call a devices `update_preferences` function if there is one set on the device.
--- This function will be given a table of the previous preference values
---
--- This is used by sleepy device drivers when preferences are updated when the device is
--- sleep.
---
--- @param driver st.zwave.Driver
local function extend_wakeup_handler(driver)
  local existing_wakeup_notification_handler
  if driver.zwave_handlers[cc.WAKE_UP] ~= nil then
    existing_wakeup_notification_handler = driver.zwave_handlers[cc.WAKE_UP][WakeUp.NOTIFICATION]
  end

  if existing_wakeup_notification_handler ~= nil then
    local wakeup_preference_handler = function(inner_driver, device, cmd)
      local update_prefs_fn = device:get_field(constants.UPDATE_PREFERENCES_FUNC)
      if update_prefs_fn ~= nil then
        update_prefs_fn(inner_driver, device)
      end
    end
    if type(existing_wakeup_notification_handler) == "table" then
      table.insert(driver.zwave_handlers[cc.WAKE_UP][WakeUp.NOTIFICATION], 1, wakeup_preference_handler)
    elseif type(existing_wakeup_notification_handler) == "function" then
      driver.zwave_handlers[cc.WAKE_UP][WakeUp.NOTIFICATION] = {wakeup_preference_handler, existing_wakeup_notification_handler}
    end
  end
end

In st/zwave/device.lua is the function called in init lifecycle set_update_preferences_fn(update_pref_fn) to set the function will be called when device wakeUp

---@alias UpdatePreference fun(type: ZwaveDevice, type: table)
--- Set a function to be called with with previous preferences when this device wakes up. Should be used to update preferences on sleepy devices.
---
--- @param update_pref_fn UpdatePreference function to update preferences when a device wakes up.
function ZwaveDevice:set_update_preferences_fn(update_pref_fn)
  self:set_field(CURRENT_PREFERENCES_KEY, utils.deep_copy(self.st_store.preferences), { persist = true })
  if self:get_field(constants.UPDATE_PREFERENCES_FUNC) == nil then
    local wrapped_fn = function(driver, device)
      local args = {
        old_st_store = {
          preferences = device:get_field(CURRENT_PREFERENCES_KEY)
        }
      }
      update_pref_fn(driver, device, args)
      device:set_field(CURRENT_PREFERENCES_KEY, utils.deep_copy(device.st_store.preferences), { persist = true })
    end
    self:set_field(constants.UPDATE_PREFERENCES_FUNC, wrapped_fn)
  else
    log.error_with({ hub_logs = true }, "Attempt to re-set valid update-preferences function.")
  end
end