Skip to content

Commit 47ea359

Browse files
committed
zigbee-switch: Filter incorrect network type
A small number of zwave devices failed to migrate correctly and ended up as zwave devices attached to the zigbee switch driver. This change adds a patched driver.lua which includes a mechanism to filter devices when building device info which don't match device type filters added to the driver. https://smartthings.atlassian.net/browse/CHAD-16558
1 parent 32685fb commit 47ea359

File tree

3 files changed

+191
-1
lines changed

3 files changed

+191
-1
lines changed

drivers/SmartThings/zigbee-switch/src/init.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
-- limitations under the License.
1414

1515
local capabilities = require "st.capabilities"
16-
local ZigbeeDriver = require "st.zigbee"
16+
local ZigbeeDriver = require "zigbee_driver_patch"
1717
local defaults = require "st.zigbee.defaults"
1818
local clusters = require "st.zigbee.zcl.clusters"
1919
local configurationMap = require "configurations"
@@ -186,6 +186,7 @@ local zigbee_switch_driver_template = {
186186
doConfigure = do_configure
187187
},
188188
health_check = false,
189+
device_network_type_filter = {[device_lib.NETWORK_TYPE_ZIGBEE] = true}
189190
}
190191
defaults.register_for_default_handlers(zigbee_switch_driver_template,
191192
zigbee_switch_driver_template.supported_capabilities,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
-- Copyright 2025 SmartThings
2+
--
3+
-- Licensed under the Apache License, Version 2.0 (the "License");
4+
-- you may not use this file except in compliance with the License.
5+
-- You may obtain a copy of the License at
6+
--
7+
-- http://www.apache.org/licenses/LICENSE-2.0
8+
--
9+
-- Unless required by applicable law or agreed to in writing, software
10+
-- distributed under the License is distributed on an "AS IS" BASIS,
11+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
-- See the License for the specific language governing permissions and
13+
-- limitations under the License.
14+
15+
local test = require "integration_test"
16+
local t_utils = require "integration_test.utils"
17+
18+
local mock_device = test.mock_device.build_test_zwave_device({
19+
profile = t_utils.get_profile_definition("on-off-level.yml"),
20+
})
21+
22+
local function test_init()
23+
test.mock_device.add_test_device(mock_device)
24+
end
25+
26+
test.set_test_init_function(test_init)
27+
test.register_test("mismatched_prot_ignored", function() end, nil)
28+
29+
test.run_registered_tests()
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
-- Copyright 2025 SmartThings
2+
--
3+
-- Licensed under the Apache License, Version 2.0 (the "License");
4+
-- you may not use this file except in compliance with the License.
5+
-- You may obtain a copy of the License at
6+
--
7+
-- http://www.apache.org/licenses/LICENSE-2.0
8+
--
9+
-- Unless required by applicable law or agreed to in writing, software
10+
-- distributed under the License is distributed on an "AS IS" BASIS,
11+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
-- See the License for the specific language governing permissions and
13+
-- limitations under the License.
14+
15+
-- This is a patch for the zigbee driver to fix https://smartthings.atlassian.net/browse/CHAD-16558
16+
-- Several hubs were found that had zigbee switch drivers hosting zwave devices.
17+
-- This patch works around it until hubcore 0.59 is released with
18+
-- https://smartthings.atlassian.net/browse/CHAD-16552
19+
20+
local json = require "st.json"
21+
local device_lib = require "st.device"
22+
local log = require "log"
23+
local version = require "version"
24+
25+
local Driver = require "st.zigbee"
26+
27+
--- Default handler that can be registered for the device lifecycle events
28+
---
29+
--- @param self Driver the driver to handle the device lifecycle events
30+
--- @param lifecycle_channel message_channel the lifecycle message channel with data to be read
31+
function Driver:lifecycle_message_handler(lifecycle_channel)
32+
local device_uuid, event, data = lifecycle_channel:receive()
33+
local device_already_existed = self.device_cache and self.device_cache[device_uuid] ~= nil
34+
local device = self:get_device_info(device_uuid)
35+
if not device then
36+
return
37+
end
38+
device.log.info_with({ hub_logs = true }, string.format("received lifecycle event: %s", event))
39+
if version.rpc >= 9 then
40+
-- handle the init event
41+
if event == "init" then
42+
if not device then
43+
log.warn_with({ hub_logs = true }, string.format("device (%s) not found for init event", device_uuid))
44+
else
45+
device.thread:queue_event(self.lifecycle_dispatcher.dispatch, self.lifecycle_dispatcher, self, device, "init")
46+
if self.environment_info.startup_devices then
47+
self.environment_info.startup_devices[device_uuid] = nil
48+
end
49+
end
50+
return
51+
end
52+
end
53+
54+
-- handle the update event, not something that can be overridden by a template callback
55+
if event == "update" then
56+
local status, tbl = pcall(json.decode, data)
57+
if status then
58+
device:_updated(tbl)
59+
else
60+
log.warn_with({hub_logs=true}, string.format("Failed to decode device update payload: %s", data))
61+
end
62+
return
63+
end
64+
65+
local args = {}
66+
args["device_already_existed"] = device_already_existed
67+
if event == "infoChanged" then
68+
local old_device_st_store = self:get_device_info(device_uuid).st_store
69+
args["old_st_store"] = old_device_st_store
70+
local raw_device = json.decode(data)
71+
self.device_cache[device_uuid]:load_updated_data(raw_device)
72+
device = self.device_cache[device_uuid]
73+
end
74+
75+
local event_result_handler = lifecycle_result_handler
76+
device.thread:queue_event_with_handler(
77+
self.lifecycle_dispatcher.dispatch,
78+
event_result_handler,
79+
self.lifecycle_dispatcher, self, device, event, args
80+
)
81+
82+
-- Do event cleanup that needs to happen regardless
83+
if event == "removed" then
84+
if self.device_cache ~= nil then
85+
self.device_cache[device_uuid] = nil
86+
end
87+
device.thread:queue_event(device.deleted, device)
88+
end
89+
end
90+
91+
function Driver:_filter_network_type(raw_device_table)
92+
if self.device_network_type_filter then
93+
return self.device_network_type_filter[raw_device_table.network_type] ~= nil
94+
else
95+
return true
96+
end
97+
end
98+
99+
--------------------------------------------------------------------------------------------
100+
-- Default get device info handling
101+
--------------------------------------------------------------------------------------------
102+
103+
--- Default function for getting and caching device info on a driver
104+
---
105+
--- By default this will use the devices api to request information about the device id provided
106+
--- it will then cache that information on the driver. The information will be stored as a table
107+
--- after being decoded from the JSON sent across.
108+
---
109+
--- @param self Driver the driver running
110+
--- @param device_uuid string the uuid of the device to get info for
111+
--- @param force_refresh boolean if true, re-request from the driver api instead of returning cached value
112+
function Driver:get_device_info(device_uuid, force_refresh)
113+
-- check if device__uuid is a string
114+
if type(device_uuid) ~= "string" then
115+
return nil, "device_uuid is required to be a string"
116+
end
117+
118+
if self.device_cache == nil then
119+
self.device_cache = {}
120+
end
121+
-- We don't have any information for this device
122+
if self.device_cache[device_uuid] == nil then
123+
-- During driver startup, we use a lot of memory initializing devices.
124+
-- Doing a gc before building up the device object avoids adding to heap.
125+
collectgarbage()
126+
local unknown_device_info = self.device_api.get_device_info(device_uuid)
127+
if unknown_device_info == nil then
128+
return nil, "device_uuid is invalid string or non-corresponding uuid"
129+
end
130+
131+
local raw_device = json.decode(unknown_device_info)
132+
local new_device
133+
if not self:_filter_network_type(raw_device.network_type) then
134+
return nil, "device network type doesn't pass filter"
135+
end
136+
if raw_device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then
137+
local zigbee_device = require "st.zigbee.device"
138+
new_device = zigbee_device.ZigbeeDevice(self, raw_device)
139+
elseif raw_device.network_type == device_lib.NETWORK_TYPE_ZWAVE then
140+
local zwave_device = require "st.zwave.device"
141+
new_device = zwave_device.ZwaveDevice(self, raw_device)
142+
elseif raw_device.network_type == device_lib.NETWORK_TYPE_MATTER then
143+
local matter_device = require "st.matter.device"
144+
new_device = matter_device.MatterDevice(self, raw_device)
145+
elseif raw_device.network_type == device_lib.NETWORK_TYPE_CHILD then
146+
new_device = self:build_child_device(raw_device)
147+
else
148+
new_device = device_lib.Device(self, raw_device)
149+
end
150+
151+
self.device_cache[new_device.id] = new_device
152+
elseif force_refresh == true then
153+
-- We have a device record, but we want to force refresh the data
154+
local raw_device = json.decode(self.device_api.get_device_info(device_uuid))
155+
self.device_cache[device_uuid]:load_updated_data(raw_device)
156+
end
157+
return self.device_cache[device_uuid]
158+
end
159+
160+
return Driver

0 commit comments

Comments
 (0)