Skip to content

add support for generic zigbee multi-switch#1922

Merged
cbaumler merged 1 commit intoSmartThingsCommunity:mainfrom
pInksenberg:add_support_for_zigbee_mutli-switch
Mar 11, 2025
Merged

add support for generic zigbee multi-switch#1922
cbaumler merged 1 commit intoSmartThingsCommunity:mainfrom
pInksenberg:add_support_for_zigbee_mutli-switch

Conversation

@pInksenberg
Copy link
Contributor

@pInksenberg pInksenberg commented Feb 11, 2025

Check all that apply

Type of Change

  • WWST Certification Request
    • If this is your first time contributing code:
      • I have reviewed the README.md file
      • I have reviewed the CODE_OF_CONDUCT.md file
      • I have signed the CLA
    • I plan on entering a WWST Certification Request or have entered a request through the WWST Certification console at developer.smartthings.com
  • Bug fix
  • New feature
  • Refactor

Checklist

  • I have performed a self-review of my code
  • I have commented my code in hard-to-understand areas
  • I have verified my changes by testing with a device or have communicated a plan for testing
  • I am adding new behavior, such as adding a sub-driver, and have added and run new unit tests to cover the new behavior

Description of Change

add support for generic zigbee multi-switch

Summary of Completed Tests

@github-actions
Copy link

github-actions bot commented Feb 11, 2025

Channel deleted.

@github-actions
Copy link

github-actions bot commented Feb 11, 2025

Test Results

   64 files  ±0    416 suites  +1   0s ⏱️ ±0s
2 100 tests ±0  2 100 ✅ ±0  0 💤 ±0  0 ❌ ±0 
3 597 runs  +5  3 597 ✅ +5  0 💤 ±0  0 ❌ ±0 

Results for commit 83678e9. ± Comparison against base commit 3dd1b23.

♻️ This comment has been updated with latest results.

@github-actions
Copy link

github-actions bot commented Feb 11, 2025

File Coverage
All files 90%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua 90%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/inovelli-vzm31-sn/init.lua 37%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/zigbee-dimmer-power-energy/enbrighten-metering-dimmer/init.lua 91%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/init.lua 91%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/preferences.lua 97%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/wallhero/init.lua 97%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/zigbee-dimmer-power-energy/init.lua 73%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/jasco/init.lua 94%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/ge-link-bulb/init.lua 95%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua 93%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/aqara/version/init.lua 94%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/aqara/init.lua 95%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/vimar/init.lua 70%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/aurora-relay/init.lua 80%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/tuya-multi/init.lua 43%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/zigbee-switch/src/aqara/multi-switch/init.lua 87%

Minimum allowed coverage is 90%

Generated by 🐒 cobertura-action against 83678e9

})
end

local function device_added(driver, device, event)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added function is now going to be run for all devices joined to the zigbee-switch driver, I don't think we should be making a change that affects all those other devices.

Copy link
Contributor

@varzac varzac Feb 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved to the subdriver as this should only be run for the tuya devices.

EDIT: Oh, I see it was changed to not include the custom stuff so this may be fine to leave in the base driver since it is pretty generic.

Copy link
Collaborator

@kdbang kdbang Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured out which devices are affected by this code in wwst certified devices. Most of devices that have multi endpoint has their own added_handler. but there is one device that doesn't have sub-driver.

- id: "ubisys/S2 (5502)"
    deviceLabel: ubisys S2
    manufacturer: ubisys
    model: "S2 (5502)"
    deviceProfileName: switch-power-firmware-2

ubisys S2 is multi component device.

name: switch-power-firmware-2
components:
  - id: main
    capabilities:
      - id: powerMeter
        version: 1
      - id: firmwareUpdate
        version: 1
      - id: refresh
        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 device will be affected by this code. we need to avoid these code for this device.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we will add S2 device(MCD) fingerprint in added handler to avoid running our logic.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should test that the device is a Zigbee device type as well, as EDGE_CHILD devices are added to the driver and will try to run added code as well.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have changed it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be ideal if we could test that this correctly generates the create_device calls, and won't trigger for MCD devices.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we do not have WWST MCD device in office, we mocked a device which fingerprint and clusters are totally same with a real one. This create_device call will not be triggered.



local function send_discover_command(device)
local discover_attribute_id = 0x0c
Copy link
Contributor

@varzac varzac Feb 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks to be a command ID and not an attribute ID, and this command is not defined in the ZCL spec, which makes it seem like a manufacturer specific function, and we should not be sending a manufacturer specific command to every device that is onboarded.

Copy link
Contributor

@tpmanley tpmanley Feb 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the intention is to send the global command Discovery Attributes which does have a command ID of 0x0C

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I had forgotten the default for the frame control was a global command. So that explains the command. I'm still not sure what the reason for sending it is.

device:send(send_message)
end

local function read_attribute_function(device, cluster_id, attr_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A function like this is already defined in BasicCluster.read_attribute so you don't need to redifine it here.

return result
end

local function all_zigbee_message_handler(self, message_channel)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to change how EVERY message processed by ANY Zigbee Switch on the platform is handled. I do not think we should make this change.

@pInksenberg pInksenberg force-pushed the add_support_for_zigbee_mutli-switch branch from bc2da4c to fc3b686 Compare February 12, 2025 10:56
end

local function discover_response_message_handler(driver, device, zb_rx)
if zb_rx.address_header.cluster.value == clusters.Basic.ID and zb_rx.body.zcl_header.cmd.value == DISCOVER_ATTR_RSP_ID then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this function is called, both of these have already been validated, so you don't need to check again here.

device:send(send_message)
end

local function read_attribute_function(device, cluster_id, attr_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned, this code is already defined in the zigbee cluster_base, so you can just require the BasicCluster and then use BasicCluster.read_attribute(device, cluster_id, attr_id) instead of duplicating the code here.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We try read_attribute(device, cluster_id, attr_id) in cluster_base, it seems that this API can only send single AttributeId,
but Tuya needs a list of attribute to send, we wonder your advice

end
table.remove(bytes, 1) -- remove first type which means discover attribute command is completed
local result = {}
for i = 1, #bytes, 3 do -- remove data type information
Copy link
Contributor

@varzac varzac Feb 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this loop can be simplified by using the buf lib local buf = buf_lib.Reader(data) and then you can use

https://github.ecodesamsung.com/iot-hub/scripting-engine/blob/main/lua_libs/st/zigbee/data_types/AttributeId.lua
and
https://github.ecodesamsung.com/iot-hub/scripting-engine/blob/main/lua_libs/st/zigbee/data_types/ZigbeeDataType.lua

using their deserialize functions.

So something like

local attr_list = {}
while buf:remain() > 0 do
     attr_list[#attr_list+1] = AttributeId.deserialize(buf)
     local type_info = ZigbeeDataType.deserialize(buf)
end

you could also create a definition for the discover attribute response global command similar to https://developer.smartthings.com/docs/edge-device-drivers/zigbee/zcl/zcl_commands.html#read-attribute-response so that we can support this from any driver instead of custom in this driver.

local TUYA_MFR_HEADER = "_TZ"

local function is_tuya_products(opts, driver, device)
if string.sub(device:get_manufacturer(),1,3) == TUYA_MFR_HEADER then -- if it is a tuya device, then send the magic packet
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also check that there is more than 1 zigbee endpoint since this handling is only for multi switch devices?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we will check more for 1 zigbee endpoint device

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pInksenberg can you address @varzac 's comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, we will add this logic in next commit tonight.

end

local function device_added(driver, device, event)
device:set_find_child(find_child)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move setting this function to only be set if at least 1 child device is created.

@pInksenberg pInksenberg force-pushed the add_support_for_zigbee_mutli-switch branch 3 times, most recently from c7c1799 to bc4219c Compare February 17, 2025 10:10
@CLAassistant
Copy link

CLAassistant commented Feb 18, 2025

CLA assistant check
All committers have signed the CLA.


local function is_mcd_device(device)
for _, fingerprint in ipairs(MCD_fingerprint) do
if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't check that the device is specific model, we should just check if the profile on the device object has multiple components

@pInksenberg pInksenberg force-pushed the add_support_for_zigbee_mutli-switch branch from f839401 to 9220461 Compare February 20, 2025 09:03
- 0x0B04 #Electrical Measurment
deviceProfileName: switch-power
- 0x0702 #Simple Metering
deviceProfileName: switch-power-energy
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like all the changes in the fingerprints file is just moving things around, which is making the PR more confusing. I think you should revert all these changes as they don't seem relevant.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me explain that, after we moving fingerprints around, we find that a switch which has 0x0b04 & 0x0702 in server cluster will match with switch-power profile. We want these plug to show as a "switch" not a "plug" in the beginning, then we update its profile to "basic-switch", so from user sides, its icon will not change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the order here probably won't affect anything. It might make a difference in your testing, because you are removing and republishing the driver for test, this might do something, but for the already published driver, I don't think there is any guarantee that the order here makes any difference. I think a better solution would be to add a generic fingerprint that includes both clusters, and have it set the profile/icon you want there.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will add a new generic fingerprint.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,
Most devices using clusters 0x0702 and 0x0B04 should have the switch-power-energy profile.

They use cluster 0x0B04 to send Power with the Active Power attribute (0x050B) and cluster 0x0702 to send Total Energy with the CurrentSummationDelivered attribute (0x0000).

@hongye-samsung hongye-samsung force-pushed the add_support_for_zigbee_mutli-switch branch from 9220461 to 1559923 Compare February 21, 2025 12:50
Comment on lines +55 to +56
local magic_spell = {0x0004, 0x0000, 0x0001, 0x0005, 0x0007, 0xfffe}
device:send(read_attribute_function(device, clusters.Basic.ID, magic_spell))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be ideal to add a test for this behavior.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem

@qy-fang qy-fang force-pushed the add_support_for_zigbee_mutli-switch branch 2 times, most recently from 98d9782 to 9bcf1df Compare February 25, 2025 10:19
@tpmanley tpmanley requested a review from greens February 25, 2025 15:45
Comment on lines +100 to +104
if component_count >= 2 then
return true
else
return false
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if component_count >= 2 then
return true
else
return false
end
return component_count >= 2

label = name,
profile = child_profile,
parent_device_id = device.id,
parent_assigned_child_key = string.format("%02X", num_switch_server_eps),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I think you used this number num_switch_server_eps in the case that the device has non-contiguous indices for its switch endpoints (i.e. endpoints 1, 3, and 5 are switches, but you want them to appear as 1, 2, and 3), but I think this would break your find_child function if it ever happened. I think it's probably safer to just use ep.id here. The label can still be whatever you like, though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, actually it doesn't matter because you increment num_switch_server_eps regardless of whether or not the endpoint is a switch endpoint. But if you wanted the numbering to be contiguous you could change it.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, we will change it

local main_endpoint = device:get_endpoint(clusters.OnOff.ID)
local updated_flag = false
if is_mcd_device(device) == false and device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then
for _, ep in ipairs(device.zigbee_endpoints) do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could probably use this index in the place of num_switch_server_eps rather than discard it

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,

In my experience with various multi-component device manufacturers, the best way to identify the main endpoint component is to use the value returned by device.fingerprinted_endpoint_id.

Regards

@qy-fang qy-fang force-pushed the add_support_for_zigbee_mutli-switch branch from 9bcf1df to 6a47f9b Compare February 26, 2025 03:45
Comment on lines +112 to +114
device:try_update_metadata({profile="basic-switch"})
updated_flag = true
device:set_find_child(find_child)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason you're switching the main device to a basic switch? I figure it could still use whatever functionality we've fingerprinted it with.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some switches will match with "switch-power" or "switch-power-energy" profile, but we cannot make power & energy meter value right for every generic switch(some switch use private cluster to meter), so we update their profile to "basic-switch".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they use a private cluster for metering how would they fingerprint to the power-energy profile?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should explain more accurately. Some switch use private cluster to calibrate power meter value. For example, tuya use 0xe0001's attribute value as a power & electric coefficient, without that coefficient, we may get wrong power & energy meter value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's my point. The code you're adding here should be generic and for devices that use standard clusters and standard attributes. Those devices should have no trouble keeping their power and energy capabilities if they've fingerprinted to them using an appropriate generic fingerprint. Treating them as basic switches because it won't work otherwise for Tuya devices is not generic behavior.

Tuya-specific behavior belongs in a Tuya-specific sub-driver.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get your point, but the code we adding here is not just for Tuya. We cannot get every manufacturer's power & electric coefficient so we want these multi-switch work as basic-switch in generic driver first.
Besides, only "no fingerprint to match" & "multi on/off cluster" & "none MCD device" will be effected with that code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand, but the effort here was to increase functionality for devices we don't have fingerprints for.

Current functionality for, say, a power strip that supports metering each outlet would be to have one metered switch controllable.

With this change, you gain the ability to control on/off of each endpoint, but you lose the metering.

It seems possible to make these changes such that we could keep metering on the main endpoint while adding on/off control to the remaining endpoints.

I understand that this generic solution would not work for the Tuya devices because they use a custom attribute, but it should work for any device using standard zigbee attributes. We poll the multiplier and divisors as part of the base driver's configure method.

If the Tuya devices do not work with the base generic driver, then the non-standard behavior should be accommodated in the sub-driver.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate your comments, it will be grate to support more function.
We try your solutions and find that Aqara Z1 series' switch cannot meter power(power meter value is 0 in plugin), so if we just move this to tuya sub-driver, we will encounter this issue. We wonder your opinions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Device-specific departures from the zigbee standard are intended to be handled by sub-drivers when the device manufacturer submits the device through the WWST process.

If our base driver uses Zigbee standard behavior, and a device does not function fully with it, that's an issue with the device, (unless you've investigated and found that our driver is acting in a way not up to the standards).

But if a device fingerprints to our generic fingerprint for a metered switch and uses the simple metering cluster, we would expect to be able to report its power/energy consumption.

A device failing to operate using our base drivers is an argument for adding a sub-driver, not for removing functionality from the base driver.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand, we decide to delete "update_profile", thank you for your advices!

@pInksenberg pInksenberg force-pushed the add_support_for_zigbee_mutli-switch branch 2 times, most recently from 8447586 to 18f796b Compare March 5, 2025 11:05
lazy_load_if_possible("wallhero"),
lazy_load_if_possible("inovelli-vzm31-sn"),
lazy_load_if_possible("laisiao")
lazy_load_if_possible("laisiao"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: whitespace

@pInksenberg pInksenberg force-pushed the add_support_for_zigbee_mutli-switch branch from 1990b29 to 15faf3e Compare March 11, 2025 02:05
@pInksenberg pInksenberg force-pushed the add_support_for_zigbee_mutli-switch branch from 15faf3e to e76cba8 Compare March 11, 2025 07:08
@pInksenberg pInksenberg force-pushed the add_support_for_zigbee_mutli-switch branch from e76cba8 to 83678e9 Compare March 11, 2025 07:14
@cbaumler cbaumler merged commit d09ed9b into SmartThingsCommunity:main Mar 11, 2025
11 checks passed
iquix added a commit to iquix/ST-Edge-Driver that referenced this pull request Mar 20, 2025
Now stock zigbee swtich edge driver supports generic zigbee multi-endpoint switches.
SmartThingsCommunity/SmartThingsEdgeDrivers#1922

This driver is no longer needed for multi- endpoint switches.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants