Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ local MIN_EPOCH_S = 0
local MAX_EPOCH_S = 0xffffffff
local THIRTY_YEARS_S = 946684800 -- 1970-01-01T00:00:00 ~ 2000-01-01T00:00:00

local MODULAR_PROFILE_UPDATED = "__MODULAR_PROFILE_UPDATED"

local RESPONSE_STATUS_MAP = {
[DoorLock.types.DlStatus.SUCCESS] = "success",
[DoorLock.types.DlStatus.FAILURE] = "failure",
Expand Down Expand Up @@ -203,7 +201,6 @@ local function match_profile_modular(driver, device)

table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities})
device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs})
device:set_field(MODULAR_PROFILE_UPDATED, true)
end

local function match_profile_switch(driver, device)
Expand Down Expand Up @@ -241,11 +238,37 @@ local function match_profile_switch(driver, device)
device:try_update_metadata({profile = profile_name})
end

local function profile_changed(latest_profile, previous_profile)
if latest_profile.id ~= previous_profile.id then
return true
end
for component_id, synced_component in pairs(latest_profile.components or {}) do
local prev_component = previous_profile.components[component_id]
if prev_component == nil then
return true
end
if #synced_component.capabilities ~= #prev_component.capabilities then
return true
end
-- Build a table of capability IDs from the previous component. Then, use this map to check
-- that all capabilities in the synced component existed in the previous component.
local prev_cap_ids = {}
for _, capability in ipairs(prev_component.capabilities or {}) do
prev_cap_ids[capability.id] = true
end
for _, capability in ipairs(synced_component.capabilities or {}) do
if not prev_cap_ids[capability.id] then
return true
end
end
end
return false
end

local function info_changed(driver, device, event, args)
if device.profile.id == args.old_st_store.profile.id and not device:get_field(MODULAR_PROFILE_UPDATED) then
if not profile_changed(device.profile, args.old_st_store.profile) then
return
end
device:set_field(MODULAR_PROFILE_UPDATED, nil)
for cap_id, attributes in pairs(subscribed_attributes) do
if device:supports_capability_by_id(cap_id) then
for _, attr in ipairs(attributes) do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,22 +77,32 @@ function AirQualitySensorUtils.set_supported_health_concern_values(device)
end
end

function AirQualitySensorUtils.profile_changed(synced_components, prev_components)
if #synced_components ~= #prev_components then
function AirQualitySensorUtils.profile_changed(latest_profile, previous_profile)
if latest_profile.id ~= previous_profile.id then
return true
end
for _, component in pairs(synced_components or {}) do
if (prev_components[component.id] == nil) or
(#component.capabilities ~= #prev_components[component.id].capabilities) then
for component_id, synced_component in pairs(latest_profile.components or {}) do
local prev_component = previous_profile.components[component_id]
if prev_component == nil then
return true
end
for _, capability in pairs(component.capabilities or {}) do
if prev_components[component.id][capability.id] == nil then
if #synced_component.capabilities ~= #prev_component.capabilities then
return true
end
-- Build a table of capability IDs from the previous component. Then, use this map to check
-- that all capabilities in the synced component existed in the previous component.
local prev_cap_ids = {}
for _, capability in ipairs(prev_component.capabilities or {}) do
prev_cap_ids[capability.id] = true
end
for _, capability in ipairs(synced_component.capabilities or {}) do
if not prev_cap_ids[capability.id] then
return true
end
end
end
return false
end


return AirQualitySensorUtils
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ function AirQualitySensorLifecycleHandlers.device_init(driver, device)
end

function AirQualitySensorLifecycleHandlers.info_changed(driver, device, event, args)
if device.profile.id ~= args.old_st_store.profile.id or
aqs_utils.profile_changed(device.profile.components, args.old_st_store.profile.components) then
if aqs_utils.profile_changed(device.profile, args.old_st_store.profile) then
if device:get_field(fields.SUPPORTED_COMPONENT_CAPABILITIES) then
--re-up subscription with new capabilities using the modular supports_capability override
device:extend_device("supports_capability_by_id", aqs_utils.supports_capability_by_id_modular)
Expand Down
3 changes: 1 addition & 2 deletions drivers/SmartThings/matter-switch/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ function SwitchLifecycleHandlers.driver_switched(driver, device)
end

function SwitchLifecycleHandlers.info_changed(driver, device, event, args)
if device.profile.id ~= args.old_st_store.profile.id or device:get_field(fields.MODULAR_PROFILE_UPDATED) then
device:set_field(fields.MODULAR_PROFILE_UPDATED, nil)
if switch_utils.profile_changed(device.profile, args.old_st_store.profile) then
if device.network_type == device_lib.NETWORK_TYPE_MATTER then
device:subscribe()
button_cfg.configure_buttons(device,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,24 +134,6 @@ function CameraUtils.build_supported_resolutions(device, max_encoded_pixel_rate,
return resolutions
end

function CameraUtils.profile_changed(synced_components, prev_components)
if #synced_components ~= #prev_components then
return true
end
for _, component in pairs(synced_components or {}) do
if (prev_components[component.id] == nil) or
(#component.capabilities ~= #prev_components[component.id].capabilities) then
return true
end
for _, capability in pairs(component.capabilities or {}) do
if prev_components[component.id][capability.id] == nil then
return true
end
end
end
return false
end

function CameraUtils.optional_capabilities_list_changed(new_component_capability_list, previous_component_capability_list)
local previous_capability_map = {}
local component_sizes = {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function CameraLifecycleHandlers.driver_switched(driver, device)
end

function CameraLifecycleHandlers.info_changed(driver, device, event, args)
if camera_utils.profile_changed(device.profile.components, args.old_st_store.profile.components) then
if switch_utils.profile_changed(device.profile, args.old_st_store.profile) then
camera_cfg.initialize_camera_capabilities(device)
device:subscribe()
if #switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.DOORBELL) > 0 then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,6 @@ function DeviceConfiguration.match_profile(driver, device)
local fan_device_type_ep_ids = switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.FAN)
if #fan_device_type_ep_ids > 0 then
updated_profile, optional_component_capabilities = FanDeviceConfiguration.assign_profile_for_fan_ep(device, default_endpoint_id)
device:set_field(fields.MODULAR_PROFILE_UPDATED, true)
end

-- initialize the main device card with buttons if applicable
Expand Down
2 changes: 0 additions & 2 deletions drivers/SmartThings/matter-switch/src/switch_utils/fields.lua
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,6 @@ SwitchFields.ELECTRICAL_SENSOR_EPS = "__electrical_sensor_eps"
--- for an Electrical Sensor EP with a "primary" endpoint, used during device profiling.
SwitchFields.ELECTRICAL_TAGS = "__electrical_tags"

SwitchFields.MODULAR_PROFILE_UPDATED = "__modular_profile_updated"

SwitchFields.profiling_data = {
POWER_TOPOLOGY = "__power_topology",
BATTERY_SUPPORT = "__battery_support",
Expand Down
27 changes: 27 additions & 0 deletions drivers/SmartThings/matter-switch/src/switch_utils/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,33 @@ function utils.create_multi_press_values_list(size, supportsHeld)
return list
end

function utils.profile_changed(latest_profile, previous_profile)
if latest_profile.id ~= previous_profile.id then
return true
end
for component_id, synced_component in pairs(latest_profile.components or {}) do
local prev_component = previous_profile.components[component_id]
if prev_component == nil then
return true
end
if #synced_component.capabilities ~= #prev_component.capabilities then
return true
end
-- Build a table of capability IDs from the previous component. Then, use this map to check
-- that all capabilities in the synced component existed in the previous component.
local prev_cap_ids = {}
for _, capability in ipairs(prev_component.capabilities or {}) do
prev_cap_ids[capability.id] = true
end
for _, capability in ipairs(synced_component.capabilities or {}) do
if not prev_cap_ids[capability.id] then
return true
end
end
end
return false
end

function utils.detect_bridge(device)
return #utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.AGGREGATOR) > 0
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ local mock_device_ep2 = 2

local mock_device = test.mock_device.build_test_matter_device({
label = "Matter Fan Light",
profile = t_utils.get_profile_definition("fan-modular.yml", {}),
profile = t_utils.get_profile_definition("fan-modular.yml",
{enabled_optional_capabilities = {{"main", {"fanSpeedPercent", "fanMode"}}}}),
manufacturer_info = {
vendor_id = 0x0000,
product_id = 0x0000,
Expand Down Expand Up @@ -58,6 +59,40 @@ local mock_device = test.mock_device.build_test_matter_device({
}
})

local mock_device_capabilities_disabled = test.mock_device.build_test_matter_device({
label = "Matter Fan Light",
profile = t_utils.get_profile_definition("fan-modular.yml",
{enabled_optional_capabilities = {{"main", {}}}}),
manufacturer_info = {
vendor_id = 0x0000,
product_id = 0x0000,
},
matter_version = {
software = 1,
hardware = 1,
},
endpoints = {
{
endpoint_id = 0,
clusters = {
{cluster_id = clusters.Basic.ID, cluster_type = "SERVER"},
},
device_types = {
{device_type_id = 0x0016, device_type_revision = 1} -- RootNode
}
},
{
endpoint_id = mock_device_ep2,
clusters = {
{cluster_id = clusters.FanControl.ID, cluster_type = "SERVER", feature_map = 15},
},
device_types = {
{device_type_id = 0x002B, device_type_revision = 1,} -- Fan
}
}
}
})

local CLUSTER_SUBSCRIBE_LIST ={
clusters.OnOff.attributes.OnOff,
clusters.LevelControl.attributes.CurrentLevel,
Expand Down Expand Up @@ -110,16 +145,48 @@ local function test_init()
})
mock_device:expect_metadata_update({ profile = "fan-modular", optional_component_capabilities = {{"main", {"fanSpeedPercent", "fanMode"}}} })
mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })

local updated_device_profile = t_utils.get_profile_definition("fan-modular.yml",
{enabled_optional_capabilities = {{"main", {"fanSpeedPercent", "fanMode"}}}}
)
test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ profile = updated_device_profile }))
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
end

test.set_test_init_function(test_init)

test.register_coroutine_test(
"Component-capability update without profile ID update should cause re-subscribe in infoChanged handler", function()
local cluster_subscribe_list ={
clusters.FanControl.attributes.FanModeSequence,
clusters.FanControl.attributes.FanMode,
clusters.FanControl.attributes.PercentCurrent,
}
local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_capabilities_disabled)
for i, clus in ipairs(cluster_subscribe_list) do
if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_capabilities_disabled)) end
end
test.socket.device_lifecycle:__queue_receive(mock_device_capabilities_disabled:generate_info_changed(
{profile = {id = "00000000-1111-2222-3333-000000000004", components = { main = {capabilities={{id="fanSpeedPercent", version=1}, {id="fanMode", version=1}, {id="firmwareUpdate", version=1}, {id="refresh", version=1}}}}}})
)
test.socket.matter:__expect_send({mock_device_capabilities_disabled.id, subscribe_request})
end,
{ test_init = function() test.mock_device.add_test_device(mock_device_capabilities_disabled) end }
)

test.register_coroutine_test(
"No component-capability update an no profile ID update should not cause a re-subscribe in infoChanged handler", function()
local cluster_subscribe_list ={
clusters.FanControl.attributes.FanModeSequence,
clusters.FanControl.attributes.FanMode,
clusters.FanControl.attributes.PercentCurrent,
}
local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_capabilities_disabled)
for i, clus in ipairs(cluster_subscribe_list) do
if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_capabilities_disabled)) end
end
test.socket.device_lifecycle:__queue_receive(mock_device_capabilities_disabled:generate_info_changed(
{profile = {id = "00000000-1111-2222-3333-000000000004", components = { main = {capabilities={{id="firmwareUpdate", version=1}, {id="refresh", version=1}}}}}})
)
end,
{ test_init = function() test.mock_device.add_test_device(mock_device_capabilities_disabled) end }
)


test.register_coroutine_test(
"Switch capability should send the appropriate commands", function()
test.socket.capability:__queue_receive(
Expand Down
Loading