diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/device_configuration.lua index 398150d063..0619194926 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/device_configuration.lua @@ -259,8 +259,9 @@ function CameraDeviceConfiguration.match_profile(device) clus_has_feature(clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_ZOOM) then table.insert(main_component_capabilities, capabilities.mechanicalPanTiltZoom.ID) end - elseif ep_cluster.cluster_id == clusters.ZoneManagement.ID and has_server_cluster_type(ep_cluster) then - table.insert(main_component_capabilities, capabilities.zoneManagement.ID) + elseif ep_cluster.cluster_id == clusters.ZoneManagement.ID and has_server_cluster_type(ep_cluster) and + clusters.ZoneManagement.are_features_supported(clusters.ZoneManagement.types.Feature.USER_DEFINED, ep_cluster.feature_map) then + table.insert(main_component_capabilities, capabilities.zoneManagement.ID) elseif ep_cluster.cluster_id == clusters.OccupancySensing.ID and has_server_cluster_type(ep_cluster) then table.insert(main_component_capabilities, capabilities.motionSensor.ID) elseif ep_cluster.cluster_id == clusters.WebRTCTransportProvider.ID and has_server_cluster_type(ep_cluster) then diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua index d443bdc573..c238bfd2ca 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua @@ -62,7 +62,8 @@ local mock_device = test.mock_device.build_test_matter_device({ { cluster_id = clusters.ZoneManagement.ID, feature_map = clusters.ZoneManagement.types.Feature.TWO_DIMENSIONAL_CARTESIAN_ZONE | - clusters.ZoneManagement.types.Feature.PER_ZONE_SENSITIVITY, + clusters.ZoneManagement.types.Feature.PER_ZONE_SENSITIVITY | + clusters.ZoneManagement.types.Feature.USER_DEFINED, cluster_type = "SERVER" }, { @@ -120,6 +121,46 @@ local mock_device = test.mock_device.build_test_matter_device({ }) local mock_device_no_per_zone_sensitivity = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("camera.yml"), + manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000}, + matter_version = {hardware = 1, software = 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 = CAMERA_EP, + clusters = { + { + cluster_id = clusters.CameraAvStreamManagement.ID, + feature_map = clusters.CameraAvStreamManagement.types.Feature.VIDEO, + cluster_type = "SERVER" + }, + { + cluster_id = clusters.ZoneManagement.ID, + feature_map = clusters.ZoneManagement.types.Feature.TWO_DIMENSIONAL_CARTESIAN_ZONE | + clusters.ZoneManagement.types.Feature.USER_DEFINED, + cluster_type = "SERVER" + }, + { + cluster_id = clusters.PushAvStreamTransport.ID, + cluster_type = "SERVER" + } + }, + device_types = { + {device_type_id = 0x0142, device_type_revision = 1} -- Camera + } + } + } +}) + +local mock_device_no_user_defined_zone = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("camera.yml"), manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000}, matter_version = {hardware = 1, software = 1}, @@ -229,6 +270,26 @@ local function test_init_no_per_zone_sensitivity() mock_device_no_per_zone_sensitivity:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end +local subscribe_request_no_user_defined_zone +local subscribed_attributes_no_user_defined_zone = { + clusters.CameraAvStreamManagement.attributes.AttributeList, +} + +local function test_init_no_user_defined_zone() + test.mock_device.add_test_device(mock_device_no_user_defined_zone) + test.socket.device_lifecycle:__queue_receive({ mock_device_no_user_defined_zone.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_no_user_defined_zone.id, "init" }) + subscribe_request_no_user_defined_zone = subscribed_attributes_no_user_defined_zone[1]:subscribe(mock_device_no_user_defined_zone) + subscribe_request_no_user_defined_zone:merge(cluster_base.subscribe(mock_device_no_user_defined_zone, nil, camera_fields.CameraAVSMFeatureMapAttr.cluster, camera_fields.CameraAVSMFeatureMapAttr.ID)) + subscribe_request_no_user_defined_zone:merge(cluster_base.subscribe(mock_device_no_user_defined_zone, nil, camera_fields.ZoneManagementFeatureMapAttr.cluster, camera_fields.ZoneManagementFeatureMapAttr.ID)) + for i, attr in ipairs(subscribed_attributes_no_user_defined_zone) do + if i > 1 then subscribe_request_no_user_defined_zone:merge(attr:subscribe(mock_device_no_user_defined_zone)) end + end + test.socket.matter:__expect_send({mock_device_no_user_defined_zone.id, subscribe_request_no_user_defined_zone}) + test.socket.device_lifecycle:__queue_receive({ mock_device_no_user_defined_zone.id, "doConfigure" }) + mock_device_no_user_defined_zone:expect_metadata_update({ provisioning_state = "PROVISIONED" }) +end + local additional_subscribed_attributes = { clusters.CameraAvStreamManagement.attributes.HDRModeEnabled, clusters.CameraAvStreamManagement.attributes.ImageRotation, @@ -485,6 +546,69 @@ local function update_device_profile_no_per_zone_sensitivity() test.socket.matter:__expect_send({mock_device_no_per_zone_sensitivity.id, subscribe_request_no_per_zone_sensitivity}) end +local additional_subscribed_attributes_no_user_defined_zone = { + clusters.CameraAvStreamManagement.attributes.StatusLightBrightness, + clusters.CameraAvStreamManagement.attributes.StatusLightEnabled, + clusters.CameraAvStreamManagement.attributes.RateDistortionTradeOffPoints, + clusters.CameraAvStreamManagement.attributes.MaxEncodedPixelRate, + clusters.CameraAvStreamManagement.attributes.VideoSensorParams, + clusters.CameraAvStreamManagement.attributes.AllocatedVideoStreams, + clusters.CameraAvSettingsUserLevelManagement.attributes.DPTZStreams, + clusters.CameraAvStreamManagement.attributes.MinViewportResolution, + clusters.CameraAvStreamManagement.attributes.Viewport, + clusters.CameraAvStreamManagement.attributes.AttributeList, + clusters.OnOff.attributes.OnOff, +} + +local function update_device_profile_no_user_defined_zone() + local expected_metadata = { + optional_component_capabilities = { + { + "main", + { + "videoCapture2", + "cameraViewportSettings", + "videoStreamSettings", + } + }, + { + "statusLed", + { + "switch", + "mode" + } + } + }, + profile = "camera" + } + + test.socket.matter:__queue_receive({ + mock_device_no_user_defined_zone.id, + clusters.CameraAvStreamManagement.attributes.AttributeList:build_test_report_data(mock_device_no_user_defined_zone, CAMERA_EP, { + uint32(clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID), + uint32(clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID) + }) + }) + mock_device_no_user_defined_zone:expect_metadata_update(expected_metadata) + test.wait_for_events() + local updated_device_profile = t_utils.get_profile_definition( + "camera.yml", {enabled_optional_capabilities = expected_metadata.optional_component_capabilities} + ) + test.wait_for_events() + test.socket.device_lifecycle:__queue_receive(mock_device_no_user_defined_zone:generate_info_changed({ profile = updated_device_profile })) + + test.socket.capability:__expect_send( + mock_device_no_user_defined_zone:generate_test_message("main", capabilities.videoStreamSettings.supportedFeatures( + {"liveStreaming", "clipRecording", "perStreamViewports"} + )) + ) + + for _, attr in ipairs(additional_subscribed_attributes_no_user_defined_zone) do + subscribe_request_no_user_defined_zone:merge(attr:subscribe(mock_device_no_user_defined_zone)) + end + test.socket.matter:__expect_send({mock_device_no_user_defined_zone.id, subscribe_request_no_user_defined_zone}) +end + -- Matter Handler UTs test.register_coroutine_test( @@ -3185,49 +3309,61 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "Zone Management trigger reports should omit sensitivity when per-zone sensitivity is unsupported, even if provided by client", + "Camera profile should include zoneManagement when USER_DEFINED feature is present", function() update_device_profile_no_per_zone_sensitivity() test.wait_for_events() - -- Create a trigger with + end, + { + test_init = test_init_no_per_zone_sensitivity, + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Camera profile should not include zoneManagement when USER_DEFINED feature is missing", + function() + update_device_profile_no_user_defined_zone() + test.wait_for_events() + end, + { + test_init = test_init_no_user_defined_zone, + min_api_version = 17 + } +) + +test.register_coroutine_test( + "Zone Management trigger command should be ignored when zoneManagement capability is not exposed", + function() + update_device_profile_no_user_defined_zone() + test.wait_for_events() test.socket.capability:__queue_receive({ - mock_device_no_per_zone_sensitivity.id, + mock_device_no_user_defined_zone.id, { capability = "zoneManagement", component = "main", command = "createOrUpdateTrigger", args = { 1, 10, 3, 15, 3, 5 }} }) - test.socket.matter:__expect_send({ - mock_device_no_per_zone_sensitivity.id, clusters.ZoneManagement.server.commands.CreateOrUpdateTrigger(mock_device_no_per_zone_sensitivity, CAMERA_EP, { - zone_id = 1, - initial_duration = 10, - augmentation_duration = 3, - max_duration = 15, - blind_duration = 3 - }) - }) + test.wait_for_events() end, { - test_init = test_init_no_per_zone_sensitivity, + test_init = test_init_no_user_defined_zone, min_api_version = 17 } ) test.register_coroutine_test( - "Zone Management setSensitivity command should handle global sensitivity when per-zone sensitivity is unsupported", + "Zone Management setSensitivity command should be ignored when zoneManagement capability is not exposed", function() - update_device_profile_no_per_zone_sensitivity() + update_device_profile_no_user_defined_zone() test.wait_for_events() test.socket.capability:__queue_receive({ - mock_device_no_per_zone_sensitivity.id, + mock_device_no_user_defined_zone.id, { capability = "zoneManagement", component = "main", command = "setSensitivity", args = { 5 } } }) - test.socket.matter:__expect_send({ - mock_device_no_per_zone_sensitivity.id, - clusters.ZoneManagement.attributes.Sensitivity:write(mock_device_no_per_zone_sensitivity, CAMERA_EP, 5) - }) + test.wait_for_events() end, { - test_init = test_init_no_per_zone_sensitivity, + test_init = test_init_no_user_defined_zone, min_api_version = 17 } )