Skip to content

Commit b6efe8d

Browse files
Update lifecycle events for Eve and Aqara subdrivers
With the changes from #2041, the matter-switch subdrivers should be updated to maintain consistency. This involves: * Moving the initialization code from device_init into do_configure * Implement the driverSwitched lifecycle event * Additionally, improve the lifecycle event testing for the Aqara subdriver, removing the `TEST_CONFIGURE` field and instead leveraging a technique used in other test files to set `device.profile.id` for a mock device.
1 parent d122771 commit b6efe8d

File tree

3 files changed

+106
-74
lines changed

3 files changed

+106
-74
lines changed

drivers/SmartThings/matter-switch/src/aqara-cube/init.lua

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,13 @@ local log = require "log"
2020
local cubeAction = capabilities["stse.cubeAction"]
2121
local cubeFace = capabilities["stse.cubeFace"]
2222

23-
local COMPONENT_TO_ENDPOINT_MAP_BUTTON = "__component_to_endpoint_map_button"
24-
local DEFERRED_CONFIGURE = "__DEFERRED_CONFIGURE"
25-
26-
-- used in unit testing, since device.profile.id and args.old_st_store.profile.id are always the same
27-
-- and this is to avoid the crash of the test case that occurs when try_update_metadata is performed in the device_init stage.
28-
local TEST_CONFIGURE = "__test_configure"
23+
local COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map"
2924
local INITIAL_PRESS_ONLY = "__initial_press_only" -- for devices that support MS (MomentarySwitch), but not MSR (MomentarySwitchRelease)
3025

26+
local updated_fields = {
27+
{ current_field_name = "__component_to_endpoint_map_button", updated_field_name = COMPONENT_TO_ENDPOINT_MAP }
28+
}
29+
3130
-- after 3 seconds of cubeAction, to automatically change the action status of Plugin UI or Device Card to noAction
3231
local CUBEACTION_TIMER = "__cubeAction_timer"
3332
local CUBEACTION_TIME = 3
@@ -125,6 +124,17 @@ local function endpoint_to_component(device, endpoint)
125124
return "main"
126125
end
127126

127+
local function check_field_name_updates(device)
128+
for _, field in ipairs(updated_fields) do
129+
if device:get_field(field.current_field_name) then
130+
if field.updated_field_name ~= nil then
131+
device:set_field(field.updated_field_name, device:get_field(field.current_field_name), {persist = true})
132+
end
133+
device:set_field(field.current_field_name, nil)
134+
end
135+
end
136+
end
137+
128138
-- This is called either on add for parent/child devices, or after the device profile changes for components
129139
local function configure_buttons(device)
130140
if device.network_type ~= device_lib.NETWORK_TYPE_CHILD then
@@ -160,56 +170,50 @@ local function set_configure(driver, device)
160170
current_component_number = current_component_number + 1
161171
end
162172

163-
device:set_field(COMPONENT_TO_ENDPOINT_MAP_BUTTON, component_map, {persist = true})
173+
device:set_field(COMPONENT_TO_ENDPOINT_MAP, component_map, {persist = true})
164174
device:try_update_metadata({profile = "cube-t1-pro"})
165175
configure_buttons(device)
166176
end
167177

168178
local function device_init(driver, device)
169179
if device.network_type == device_lib.NETWORK_TYPE_MATTER then
180+
check_field_name_updates(device)
170181
device:subscribe()
171182
device:set_endpoint_to_component_fn(endpoint_to_component)
172-
173-
-- when unit testing, call set_configure elsewhere
174-
if not device:get_field(TEST_CONFIGURE) then
175-
set_configure(driver, device)
176-
end
177183
end
178184
end
179185

180186
local function device_added(driver, device)
181-
if device.network_type ~= device_lib.NETWORK_TYPE_CHILD then
182-
device:set_field(DEFERRED_CONFIGURE, true)
187+
if device.network_type == device_lib.NETWORK_TYPE_MATTER then
188+
device_init(driver, device)
183189
end
184190
end
185191

186-
local function info_changed(driver, device, event, args)
187-
-- for unit testing
188-
if device:get_field(TEST_CONFIGURE) then
192+
local function do_configure(driver, device)
193+
-- when unit testing, call set_configure elsewhere
194+
if device.network_type == device_lib.NETWORK_TYPE_MATTER then
189195
set_configure(driver, device)
190196
end
197+
end
191198

192-
if (device.profile.id ~= args.old_st_store.profile.id or device:get_field(TEST_CONFIGURE))
193-
and device:get_field(DEFERRED_CONFIGURE)
194-
and device.network_type ~= device_lib.NETWORK_TYPE_CHILD then
199+
local function driver_switched(driver, device)
200+
if device.network_type == device_lib.NETWORK_TYPE_MATTER then
201+
set_configure(driver, device)
202+
end
203+
end
195204

205+
local function info_changed(driver, device, event, args)
206+
if device.profile.id ~= args.old_st_store.profile.id and
207+
device.network_type == device_lib.NETWORK_TYPE_MATTER then
196208
reset_thread(device)
197209
device:emit_event(cubeAction.cubeAction("flipToSide1"))
198210
device:emit_event(cubeFace.cubeFace("face1Up"))
199-
200-
device:set_field(DEFERRED_CONFIGURE, nil)
201211
end
202212
end
203213

204-
-- override do_configure to prevent it running in the main driver
205-
local function do_configure(driver, device) end
206-
207-
-- override driver_switched to prevent it running in the main driver
208-
local function driver_switched(driver, device) end
209-
210214
local function initial_press_event_handler(driver, device, ib, response)
211215
if get_field_for_endpoint(device, INITIAL_PRESS_ONLY, ib.endpoint_id) then
212-
local map = device:get_field(COMPONENT_TO_ENDPOINT_MAP_BUTTON) or {}
216+
local map = device:get_field(COMPONENT_TO_ENDPOINT_MAP) or {}
213217
local face = 1
214218
for component, ep in pairs(map) do
215219
if map[component] == ib.endpoint_id then
@@ -255,4 +259,3 @@ local aqara_cube_handler = {
255259
}
256260

257261
return aqara_cube_handler
258-

drivers/SmartThings/matter-switch/src/eve-energy/init.lua

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@ local utils = require "st.utils"
2323
local data_types = require "st.matter.data_types"
2424
local device_lib = require "st.device"
2525

26-
local SWITCH_INITIALIZED = "__switch_intialized"
2726
local COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map"
2827
local ON_OFF_STATES = "ON_OFF_STATES"
2928

29+
local updated_fields = {
30+
{ current_field_name = "__switch_intialized", updated_field_name = nil }
31+
}
32+
3033
local EVE_MANUFACTURER_ID = 0x130A
3134
local PRIVATE_CLUSTER_ID = 0x130AFC01
3235

@@ -203,8 +206,6 @@ local function initialize_switch(driver, device)
203206
end
204207
end
205208
end
206-
207-
device:set_field(SWITCH_INITIALIZED, true)
208209
end
209210

210211
local function component_to_endpoint(device, component)
@@ -245,18 +246,25 @@ local function set_on_off_state(device, endpoint, value)
245246
device:set_field(ON_OFF_STATES, map)
246247
end
247248

249+
local function check_field_name_updates(device)
250+
for _, field in ipairs(updated_fields) do
251+
if device:get_field(field.current_field_name) then
252+
if field.updated_field_name ~= nil then
253+
device:set_field(field.updated_field_name, device:get_field(field.current_field_name), {persist = true})
254+
end
255+
device:set_field(field.current_field_name, nil)
256+
end
257+
end
258+
end
259+
248260

249261
-------------------------------------------------------------------------------------
250262
-- Device Management
251263
-------------------------------------------------------------------------------------
252264

253265
local function device_init(driver, device)
254266
if device.network_type == device_lib.NETWORK_TYPE_MATTER then
255-
if not device:get_field(COMPONENT_TO_ENDPOINT_MAP) and
256-
not device:get_field(SWITCH_INITIALIZED) then
257-
-- create child devices as needed for multi-switch devices
258-
initialize_switch(driver, device)
259-
end
267+
check_field_name_updates(device)
260268
device:set_component_to_endpoint_fn(component_to_endpoint)
261269
device:set_endpoint_to_component_fn(endpoint_to_component)
262270
device:set_find_child(find_child)
@@ -277,11 +285,18 @@ local function device_removed(driver, device)
277285
delete_poll_schedule(device)
278286
end
279287

280-
-- override do_configure to prevent it running in the main driver
281-
local function do_configure(driver, device) end
288+
local function do_configure(driver, device)
289+
if device.network_type == device_lib.NETWORK_TYPE_MATTER then
290+
-- create child devices as needed for multi-switch devices
291+
initialize_switch(driver, device)
292+
end
293+
end
282294

283-
-- override driver_switched to prevent it running in the main driver
284-
local function driver_switched(driver, device) end
295+
local function driver_switched(driver, device)
296+
if device.network_type == device_lib.NETWORK_TYPE_MATTER then
297+
initialize_switch(driver, device)
298+
end
299+
end
285300

286301
local function handle_refresh(self, device)
287302
requestData(device)

drivers/SmartThings/matter-switch/src/test/test_aqara_cube.lua

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,28 @@
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+
115
local test = require "integration_test"
216
test.add_package_capability("cubeAction.yml")
317
test.add_package_capability("cubeFace.yml")
418
local capabilities = require "st.capabilities"
519
local cubeAction = capabilities["stse.cubeAction"]
620
local cubeFace = capabilities["stse.cubeFace"]
721

8-
local t_utils = require "integration_test.utils"
922
local clusters = require "st.matter.clusters"
10-
11-
-- used in unit testing, since device.profile.id and args.old_st_store.profile.id are always the same
12-
-- and this is to avoid the crash of the test case that occurs when try_update_metadata is performed in the device_init stage.
13-
local TEST_CONFIGURE = "__test_configure"
23+
local dkjson = require "dkjson"
24+
local t_utils = require "integration_test.utils"
25+
local utils = require "st.utils"
1426

1527
--mock the actual device1
1628
local mock_device = test.mock_device.build_test_matter_device(
@@ -146,16 +158,22 @@ local CLUSTER_SUBSCRIBE_LIST ={
146158
}
147159

148160
local function test_init()
149-
local opts = { persist = true }
150-
mock_device:set_field(TEST_CONFIGURE, true, opts)
151-
152161
local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_device)
153162
for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do
154163
if i > 1 then
155164
subscribe_request:merge(clus:subscribe(mock_device))
156165
end
157166
end
158167
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
168+
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" })
169+
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
170+
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" })
171+
mock_device:expect_metadata_update({ profile = "cube-t1-pro" })
172+
mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
173+
local device_info_copy = utils.deep_copy(mock_device.raw_st_data)
174+
device_info_copy.profile.id = "matter-thing"
175+
local device_info_json = dkjson.encode(device_info_copy)
176+
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "infoChanged", device_info_json })
159177
test.mock_device.add_test_device(mock_device)
160178
test.socket.capability:__expect_send(
161179
mock_device:generate_test_message("main", cubeAction.cubeAction({value = "flipToSide1"}))
@@ -168,16 +186,22 @@ end
168186
test.set_test_init_function(test_init)
169187

170188
local function test_init_exhausted()
171-
local opts = { persist = true }
172-
mock_device_exhausted:set_field(TEST_CONFIGURE, true, opts)
173-
174189
local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_device_exhausted)
175190
for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do
176191
if i > 1 then
177192
subscribe_request:merge(clus:subscribe(mock_device_exhausted))
178193
end
179194
end
180195
test.socket.matter:__expect_send({mock_device_exhausted.id, subscribe_request})
196+
test.socket.device_lifecycle:__queue_receive({ mock_device_exhausted.id, "added" })
197+
test.socket.matter:__expect_send({mock_device_exhausted.id, subscribe_request})
198+
test.socket.device_lifecycle:__queue_receive({ mock_device_exhausted.id, "doConfigure" })
199+
mock_device_exhausted:expect_metadata_update({ profile = "cube-t1-pro" })
200+
mock_device_exhausted:expect_metadata_update({ provisioning_state = "PROVISIONED" })
201+
local device_info_copy = utils.deep_copy(mock_device_exhausted.raw_st_data)
202+
device_info_copy.profile.id = "matter-thing"
203+
local device_info_json = dkjson.encode(device_info_copy)
204+
test.socket.device_lifecycle:__queue_receive({ mock_device_exhausted.id, "infoChanged", device_info_json })
181205
test.mock_device.add_test_device(mock_device_exhausted)
182206
test.socket.capability:__expect_send(
183207
mock_device_exhausted:generate_test_message("main", cubeAction.cubeAction({value = "flipToSide1"}))
@@ -188,22 +212,13 @@ local function test_init_exhausted()
188212
end
189213

190214
test.register_coroutine_test(
191-
"Handle single press sequence when changing the device_lifecycle",
215+
"Handle single press sequence",
192216
function()
193-
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" })
194-
test.mock_devices_api._expected_device_updates[mock_device.device_id] = "00000000-1111-2222-3333-000000000001"
195-
test.mock_devices_api._expected_device_updates[1] = {device_id = "00000000-1111-2222-3333-000000000001"}
196-
test.mock_devices_api._expected_device_updates[1].metadata = {deviceId="00000000-1111-2222-3333-000000000001", profileReference="cube-t1-pro"}
197-
198-
test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({value = "face1Up"}))
199-
-- let the driver run
200-
test.wait_for_events()
201-
202217
test.socket.matter:__queue_receive(
203218
{
204219
mock_device.id,
205220
clusters.Switch.events.InitialPress:build_test_event_report(
206-
mock_device, 2, {new_position = 1} --move to position 1?
221+
mock_device, 2, {new_position = 1}
207222
)
208223
}
209224
)
@@ -236,20 +251,11 @@ test.register_coroutine_test(
236251
test.register_coroutine_test(
237252
"Handle single press sequence in case of exhausted endpoint",
238253
function()
239-
test.socket.device_lifecycle:__queue_receive({ mock_device_exhausted.id, "added" })
240-
test.mock_devices_api._expected_device_updates[mock_device_exhausted.device_id] = "00000000-1111-2222-3333-000000000003"
241-
test.mock_devices_api._expected_device_updates[1] = {device_id = "00000000-1111-2222-3333-000000000003"}
242-
test.mock_devices_api._expected_device_updates[1].metadata = {deviceId="00000000-1111-2222-3333-000000000003", profileReference="cube-t1-pro"}
243-
244-
test.socket.device_lifecycle:__queue_receive(mock_device_exhausted:generate_info_changed({value = "face1Up"}))
245-
-- let the driver run
246-
test.wait_for_events()
247-
248254
test.socket.matter:__queue_receive(
249255
{
250256
mock_device_exhausted.id,
251257
clusters.Switch.events.InitialPress:build_test_event_report(
252-
mock_device_exhausted, 250, {new_position = 1} --move to position 1?
258+
mock_device_exhausted, 250, {new_position = 1}
253259
)
254260
}
255261
)
@@ -265,6 +271,14 @@ test.register_coroutine_test(
265271
{ test_init = test_init_exhausted }
266272
)
267273

274+
test.register_coroutine_test(
275+
"Test driver switched event",
276+
function()
277+
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "driverSwitched" })
278+
mock_device:expect_metadata_update({ profile = "cube-t1-pro" })
279+
end
280+
)
281+
268282
-- run the tests
269283
test.run_registered_tests()
270284

0 commit comments

Comments
 (0)