[ST Edge] How to create child devices with edge?

Hi,
I wonder how it is possible to create child devices with Edge, similar to what we have in Groovy.

I found some info regarding multi component devices
https://developer-preview.smartthings.com/edge-device-drivers/zigbee/device.html

However, as far as I understand it only allows to provide a different handling for each end point.

I basically have 2 use cases:

  1. 2 gang switch where each gang is used in different automation. While it is possible to control each gang with this approach, I’m not sure how specific end point can be controlled by Aleksa for example.
  2. Non standard devices implementing proprietary Tuya cluster. For those devices the communication is always for standard zigbee ep=1 and the actual end point is hidden inside the proprietary message

What would be the best approach for those 2 use cases? Is there any examples for that?

Thanks

1 Like

There has been a bit of discussion on it, in the edge threads.

@blkwll Thank you for your replay.

Could you please clarify how supporting Tuya cluster can be possibly addressed by endpoint/component association? Tuya device will always report the same endpoint, the dispatch should be performed based on the ZCL Data, not ZCL header. I don’t think the current endpoint/component association API supports it.

Also, how integration with Aleksa will be performed? Aleksa is unaware of multi component devices. Does it mean that for each gang on multi gang switch there will be a need to create a virtual switch?

Thanks

That’s above my level of understanding, but I found the discussion of the subject, in general.

I hope you might find someone with the answer:

1 Like

Hi @nayelyz,
I have created a multi-component edge driver for a 3-socket Lidl zigbee strip, following the example in the documentation and have some errors.
https://developer-preview.smartthings.com/edge-device-drivers/zigbee/device.html#multi-component-devices
After many attempts and changes to the ini.lua file, I fixed that the profile file example is wrong:

name: three-outlet
components:

  • id: main
    capabilities:
    • id: switch
      Version 1
      categories:
    • name: Switch
  • id: switch1
    capabilities:
    • id: switch
      Version 1
      categories:
    • name: Switch
  • id: switch2
    capabilities:
    • id: switch
      Version 1
      categories:
    • name: Switch

The first component must be always the “main”, which after seeing a thousand logcat, corresponds to the endpoint 1.
And endpoints 2 and 3 have to be switch1 and switch2 by default.

In the ini.lua code, it assigns the endpoints by the component_id of the profile.yml file, so it is assigning the endpoint 1 two times and the third switch remains unassigned.

Solution the file of profile.yml it is necessary to change “switch1 by switch2” and “switch2 by switch3”.

On the other hand, it needs the capability refresh, also added to the file profile.yml and init.lua in the definition of capabilities of the driver_template.

profile.yml file:
name: three-outlet
components:

  • id: main
    capabilities:
    • id: switch
      version: 1
      - id: refresh
      version: 1
      categories:
    • name: Switch
  • id: switch2
    capabilities:
    • id: switch
      version: 1
      - id: refresh
      version: 1
      categories:
    • name: Switch
  • id: switch3
    capabilities:
    • id: switch
      version: 1
      - id: refresh
      version: 1
      categories:
    • name: Switch

init.lua:
local zigbee_outlet_driver_template = {
supported_capabilities = {
capabilities.switch,
capabilities.refresh
},
lifecycle_handlers = {
init = device_init
},
}

Everything works fine except:

  • Switches 2 and 3 after perform action, need to slide your finger on the device details for the status to be refreshed.
  • Quick controls are not individual. It is programmed and executed in all switches at the same time.
  • In the Automations it has the same problem of the multiple buttons, that I already put a ticket a long time ago, the actions to program appear without separation by switch.

Do you have any solutions or recommendations?
Thanks

Hey, @Mariano_Colmenarejo

It seems that your driver is communicating properly with your device, but is missing to push the capability event back to the platform once you send a switch command.

You can use the device:emit_event_for_endpoint to push the capability event according to the component that was updated (see reference).

I am not able to get switch 2 and 3 to update state.
If update the status, they do not execute the action.

I’m quite clumsy, hahaha :upside_down_face:
Sorry

@Mariano_Colmenarejo

Have you tried the following?

local function on_handler(_, device, command)
  -- capability reference
  local attr = capabilities.switch.switch

  -- parse component to endpoint
  local endpoint = device:get_endpoint_for_component_id(command.component)

  -- send zigbee event
  device:send(OnOff.server.commands.On(device):to_endpoint(endpoint))

  -- send platform event
  device:emit_event_for_endpoint(
    endpoint,
    attr.on())
end

Notice how the switch event is divided into two additional tasks:

  1. Send the Zigbee message to the device.
  2. Push platform event to confirm the device command.
1 Like

@erickv,
The device installed fine, but when you turn on or off any of the 3 plugs it gives this error:

2021-09-09T22:38:59.350998848+00:00 DEBUG Zigbee Multi Switch-v1  Lidl MultiPlug device thread event handled
2021-09-09T22:39:21.021951525+00:00 TRACE Zigbee Multi Switch-v1  Received event with handler capability
2021-09-09T22:39:21.031821525+00:00 INFO Zigbee Multi Switch-v1  <ZigbeeDevice: e689144b-94c6-4871-9da5-fd98fe2e8452 [0x6A13] (Lidl MultiPlug)> received command: {"component":"main","args":[],"command":"on","capability":"switch","positional_args":[]}
2021-09-09T22:39:21.038372191+00:00 TRACE Zigbee Multi Switch-v1  Found CapabilityCommandDispatcher handler in Zigbee_Multi_Switch
2021-09-09T22:39:21.044232858+00:00 PRINT Zigbee Multi Switch-v1  component_id: main
2021-09-09T22:39:21.050194525+00:00 ERROR Zigbee Multi Switch-v1  Lidl MultiPlug thread encountered error: [string "init.lua"]:70: attempt to index 
a nil value (global 'OnOff')

Driver does not send the command On or Off:
The variable OnOff is undefined.

  -- send zigbee event
  device:send(OnOff.server.commands.On(device):to_endpoint(endpoint))
-- send zigbee event
  device:send(OnOff.server.commands.Off(device):to_endpoint(endpoint))

If cancel these two instructions, the device status is updated correctly but the command is not sent to the device and not errors reported in log.

I think it is necessary to load some library.
In the documentation I cannot find references to these procedures.

I have only found those referring to the ZCL cluster command.
https://developer-preview.smartthings.com/edge-device-drivers/zigbee/zcl/zcl_clusters.html#cluster-command
I have tried to load that library:

local OnOffCluster = require "st.zigbee.generated.zcl_clusters.OnOffCluster"

But it gives a library not found error when installing the device.

Thanks

@Mariano_Colmenarejo

I apologize for the missing reference :face_with_hand_over_mouth:. Add the following lines above your handlers to make it work:

local zcl_clusters = require "st.zigbee.zcl.clusters"
local OnOff = zcl_clusters.OnOff
1 Like

I’ll try it as soon as I can, a couple of days.

I did the same, but with the wrong library, which I found in the documentation and which does not exist in reality.

by the way when you use the search in the documentation, all the links give error “page not found”. I don’t know if it happens to more people.

Thanks, I’ll tell you how it goes

Indeed :pensive:, but don’t worry, this is something that our documentation team keeps reviewing and will fix as soon as possible.

2 Likes

With this modification it already updates the app status of the 3 plugs.

After several tests, this is what I see that it does not work well or as expected:

  • This strip has a button that activates or deactivates all 3 plugs at the same time. When you press it, app only updates the state of the main socket (1).
    I am not able to capture these events from switch 2 and 3, only the event received from main is seen in the log.

  • The same happens with the configuration reports, they should send status every 300 sec and only the main component sends it.

  • If you send a “refresh” command, update the 3 plugs.

  • A solution that works is to program a timer every 300 sec that a refrech executes.

  • Automations are local if you only execute actions on the “main” socket. If you perform actions with plug 2 or 3, they are not local.

  • What I already commented in another post, in automation it does not show the names of the plugs. Shows all actions followed. This also happens now with multi button DTHs.

Thanks

Great! thanks for the heads up.


Moving forward:

If your device is not automatically sending the respective Zigbee messages, then you can perform this state-binding directly at the driver. This way the app will have the current state of your device, e.g.:

local ENDPOINTS = {1, 2, 3}

for _, ep in pairs(ENDPOINTS) do
  device:emit_event_for_endpoint(ep, attr.on())
end

And regarding the reports

I’ll keep an I on this, and as soon as I have an update, I’ll share it with you.


Also, can you please give us more details about this specific issue?

Well that has been fixed. Now automations work as local with all the combinations I have tried.
I do not know if will have related with update of the beta firmware to 39.05 that was made today.

Yesterday only the local icon appeared in the automations that only the main plug (1) was in the then part.
If you added one of the other two plugs or only 2 and / or 3, the local execution icon would disappear.

Well, Can be delete.

Where would you place this so that it only updates the status of the three plugs when the main switch is pressed?
I have a 240 sec timer that runs the refresh command and updates the status if it has changed

We can include that state-binding action at the example from above, so, when you get an on/off command from the endpoint #1 of device (i.e. “main” component), you update all the components at platform-level. For example:

local zcl_clusters = require "st.zigbee.zcl.clusters"
local OnOff = zcl_clusters.OnOff

local function on_handler(_, device, command)
  -- capability reference
  local attr = capabilities.switch.switch

  -- parse component to endpoint
  local endpoint = device:get_endpoint_for_component_id(command.component)

  -- handle global on from
  -- "main" main component
  if endpoint == 1 then
    for _, ep in pairs({1,2,3}) do
      -- send Zigbee message
      --
      -- this will be necessary in
      -- case your device doesn't
      -- apply the command by itself.
      device:send(OnOff.server.commands.On(device:to_endpoint(ep)))

      -- send platform event
      device:emit_event_for_endpoint(ep, attr.on())
    end
    return
  end
  -- send single events
  device:send(OnOff.server.commands.On(device):to_endpoint(endpoint))
  device:emit_event_for_endpoint(endpoint, attr.on())
end

A little offtopic, but I think

  1. it is better to have a single handler for both On and Off commands
  2. to create a command only once and reuse it for each send

Therefore, I propose something like that

-- create a command to be sent, either On or Off
local cmd = (command.command == "off") and OnOff.server.commands.Off(device) or OnOff.server.commands.On(device)

for _, ep in pairs({1,2,3}) do
    -- modify dest end point
	cmd.address_header.dest_endpoint.value = ep
	-- send command
	device:send(cmd)
end

Another note, if you notify the platform right after sending the command, the platform will show the new state, even if the command was lost in the network or the device was not able to process it.
This approach is prone to get the device and platform out of sync.

I would suggest to consider notifying the platform about the new change in zigbee handler not in platform/capability handlers

1 Like

Can you actually identify when the main switch has been used? If you can then I can see that @erickv is offering a workable solution. If you only know that switch 1 has turned on or off but not why then I don’t see how it helps.

However I really don’t like the idea of setting the status of a switch except in response to a report from the device itself. I think it would be better if you could do an immediate refresh to get switches 2 and 3 to report.

1 Like

I’m going to test it and tell you what I think happens.

For Aqara switch I have the following solution.
I have a switch under “main” component and in addition “switch” component for each gang.
Something like that:

components:
- id: main
  capabilities:
  - id: switch
    version: 1
...
- id: switch1
  capabilities:
  - id: switch
    version: 1
- id: switch2
  capabilities:
  - id: switch
    version: 1

The platform handler checks whether the event is received for “main” or for the child.
If it is received from “main” component it issues send command for all children.
If it is received from specific gang, then the component is parsed to retrieve the gang number. Then the command is sent to this specific gang.
As a side note, I personally prefer to call whatever comes from platform as ‘event’, not command.
I use ‘command’ only for communication with the device.

This is basically my code, but please use it as a reference, as I have a lot of utiity functions

local function event_handler_switch_on_off(driver, device, event)
    utils.log_debug(device, "event_handler_switch_on_off  DNI="  .. tostring(device.device_network_id) .. " event=" .. st_utils.stringify_table(event) )

    local command = (event.command == "off") and zcl_clusters.OnOff.server.commands.Off(device) or zcl_clusters.OnOff.server.commands.On(device)

    if event.component == "main" then
        -- Send the same command (either On or Off) to all gangs
        for i = 1, utils.get_switch_count(device) do
            command.address_header.dest_endpoint.value = i
            device:send(command)
        end
    else
        command.address_header.dest_endpoint.value = utils.component_name_to_ep(event.component)
        device:send(command)
    end
end