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
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,6 @@ function CameraDeviceConfiguration.match_profile(device, status_light_enabled_pr
local doorbell_endpoints = switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.DOORBELL)
if #doorbell_endpoints > 0 then
table.insert(doorbell_component_capabilities, capabilities.button.ID)
CameraDeviceConfiguration.update_doorbell_component_map(device, doorbell_endpoints[1])
button_cfg.configure_buttons(device, device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH}))
end
if status_light_enabled_present then
table.insert(status_led_component_capabilities, capabilities.switch.ID)
Expand All @@ -147,6 +145,10 @@ function CameraDeviceConfiguration.match_profile(device, status_light_enabled_pr

if camera_utils.optional_capabilities_list_changed(optional_supported_component_capabilities, device.profile.components) then
device:try_update_metadata({profile = "camera", optional_component_capabilities = optional_supported_component_capabilities})
if #doorbell_endpoints > 0 then
Copy link
Contributor Author

@nickolas-deboom nickolas-deboom Jan 6, 2026

Choose a reason for hiding this comment

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

button config can be put behind the optional_capabilities_list_changed gate so it doesn't run each time CameraAvStreamManagement.AttributeList is received.

CameraDeviceConfiguration.update_doorbell_component_map(device, doorbell_endpoints[1])
button_cfg.configure_buttons(device, device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH}))
end
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,28 +120,49 @@ function CameraUtils.profile_changed(synced_components, prev_components)
return false
end

function CameraUtils.optional_capabilities_list_changed(optional_capabilities, prev_component_list)
local prev_optional_capabilities = {}
for idx, comp in pairs(prev_component_list or {}) do
local cap_list = {}
for _, capability in pairs(comp.capabilities or {}) do
table.insert(cap_list, capability.id)
function CameraUtils.optional_capabilities_list_changed(new_component_capability_list, previous_component_capability_list)
local previous_capability_map = {}
local component_sizes = {}

local previous_component_count = 0
for component_name, component in pairs(previous_component_capability_list or {}) do
previous_capability_map[component_name] = {}
component_sizes[component_name] = 0
for _, capability in pairs(component.capabilities or {}) do
if capability.id ~= "firmwareUpdate" and capability.id ~= "refresh" then
previous_capability_map[component_name][capability.id] = true
component_sizes[component_name] = component_sizes[component_name] + 1
end
end
table.insert(prev_optional_capabilities, {idx, cap_list})
end
if #optional_capabilities ~= #prev_optional_capabilities then
return true
previous_component_count = previous_component_count + 1
end
for _, capability in pairs(optional_capabilities or {}) do
if not switch_utils.tbl_contains(prev_optional_capabilities, capability) then

local number_of_components_counted = 0
for _, new_component_capabilities in pairs(new_component_capability_list or {}) do
local component_name = new_component_capabilities[1]
local capability_list = new_component_capabilities[2]

number_of_components_counted = number_of_components_counted + 1

if previous_capability_map[component_name] == nil then
return true
end
end
for _, capability in pairs(prev_optional_capabilities or {}) do
if not switch_utils.tbl_contains(optional_capabilities, capability) then

for _, capability in ipairs(capability_list) do
if previous_capability_map[component_name][capability] == nil then
return true
end
end

if #capability_list ~= component_sizes[component_name] then
return true
end
end

if number_of_components_counted ~= previous_component_count then
return true
end

return false
end

Expand Down
59 changes: 57 additions & 2 deletions drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ local capabilities = require "st.capabilities"
local clusters = require "st.matter.clusters"
local t_utils = require "integration_test.utils"
local test = require "integration_test"
local uint32 = require "st.matter.data_types.Uint32"

test.disable_startup_messages()

Expand Down Expand Up @@ -284,16 +285,16 @@ local expected_metadata = {
}

local function update_device_profile()
local uint32 = require "st.matter.data_types.Uint32"
test.socket.matter:__queue_receive({
mock_device.id,
clusters.CameraAvStreamManagement.attributes.AttributeList:build_test_report_data(mock_device, CAMERA_EP, {
uint32(clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID),
uint32(clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID)
})
})
test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, DOORBELL_EP)})
mock_device:expect_metadata_update(expected_metadata)
test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, DOORBELL_EP)})
test.wait_for_events()
local updated_device_profile = t_utils.get_profile_definition(
"camera.yml", {enabled_optional_capabilities = expected_metadata.optional_component_capabilities}
)
Expand Down Expand Up @@ -1830,5 +1831,59 @@ test.register_coroutine_test(
end
)

test.register_coroutine_test(
"Camera profile should not update for an unchanged Status Light AttributeList report",
function()
update_device_profile()
test.wait_for_events()
test.socket.matter:__queue_receive({
mock_device.id,
clusters.CameraAvStreamManagement.attributes.AttributeList:build_test_report_data(mock_device, CAMERA_EP, {
uint32(clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID),
uint32(clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID)
})
})
end
)

test.register_coroutine_test(
"Camera profile should update for a changed Status Light AttributeList report",
function()
update_device_profile()
test.wait_for_events()
test.socket.matter:__queue_receive({
mock_device.id,
clusters.CameraAvStreamManagement.attributes.AttributeList:build_test_report_data(mock_device, CAMERA_EP, {
uint32(clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID)
})
})
local expected_metadata = {
optional_component_capabilities = {
{ "main",
{ "videoCapture2", "cameraViewportSettings", "localMediaStorage", "audioRecording", "cameraPrivacyMode",
"imageControl", "hdr", "nightVision", "mechanicalPanTiltZoom", "videoStreamSettings", "zoneManagement",
"webrtc", "motionSensor", "sounds", }
},
{ "statusLed",
{ "switch" } -- only switch capability remains
},
{ "speaker",
{ "audioMute", "audioVolume" }
},
{ "microphone",
{ "audioMute", "audioVolume" }
},
{ "doorbell",
{ "button" }
}
},
profile = "camera"
}
mock_device:expect_metadata_update(expected_metadata)
test.socket.matter:__expect_send({mock_device.id, clusters.Switch.attributes.MultiPressMax:read(mock_device, DOORBELL_EP)})
test.socket.capability:__expect_send(mock_device:generate_test_message("doorbell", capabilities.button.button.pushed({state_change = false})))
end
)

-- run the tests
test.run_registered_tests()
Loading