Skip to content
Open
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 @@ -300,13 +300,13 @@ function CameraAttributeHandlers.ptz_position_handler(driver, device, ib, respon
))
end
end
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MPAN) then
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PAN) then
emit_event(camera_fields.PAN_IDX, ib.data.elements.pan.value)
end
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MTILT) then
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_TILT) then
emit_event(camera_fields.TILT_IDX, ib.data.elements.tilt.value)
end
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MZOOM) then
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_ZOOM) then
emit_event(camera_fields.ZOOM_IDX, ib.data.elements.zoom.value)
end
end
Expand All @@ -317,13 +317,13 @@ function CameraAttributeHandlers.ptz_presets_handler(driver, device, ib, respons
for _, v in ipairs(ib.data.elements) do
local preset = v.elements
local pan, tilt, zoom = 0, 0, 1
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MPAN) then
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PAN) then
pan = preset.settings.elements.pan.value
end
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MTILT) then
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_TILT) then
tilt = preset.settings.elements.tilt.value
end
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MZOOM) then
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_ZOOM) then
zoom = preset.settings.elements.zoom.value
end
table.insert(presets, { id = preset.preset_id.value, label = preset.name.value, pan = pan, tilt = tilt, zoom = zoom })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,11 @@ end
CameraCapabilityHandlers.ptz_relative_move_factory = function(index)
return function (driver, device, cmd)
local endpoint_id = device:component_to_endpoint(cmd.component)
local pan_delta = index == camera_fields.PAN_IDX and cmd.args.delta or 0
local tilt_delta = index == camera_fields.TILT_IDX and cmd.args.delta or 0
local zoom_delta = index == camera_fields.ZOOM_IDX and cmd.args.delta or 0
-- Only the axis being moved should be included; the other two must be omitted (nil) rather
-- than sent as 0, since each delta field's conformance is tied to its own feature bit.
local pan_delta = index == camera_fields.PAN_IDX and cmd.args.delta or nil
local tilt_delta = index == camera_fields.TILT_IDX and cmd.args.delta or nil
local zoom_delta = index == camera_fields.ZOOM_IDX and cmd.args.delta or nil
device:send(clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRelativeMove(
device, endpoint_id, pan_delta, tilt_delta, zoom_delta
))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,19 @@ end

local function build_ptz_supported_attributes(device)
local supported_attributes = {}
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MPAN) then
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PAN) then
table.insert(supported_attributes, "pan")
table.insert(supported_attributes, "panRange")
end
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MTILT) then
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_TILT) then
table.insert(supported_attributes, "tilt")
table.insert(supported_attributes, "tiltRange")
end
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MZOOM) then
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_ZOOM) then
table.insert(supported_attributes, "zoom")
table.insert(supported_attributes, "zoomRange")
end
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MPRESETS) then
if camera_utils.feature_supported(device, clusters.CameraAvSettingsUserLevelManagement.ID, clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PRESETS) then
table.insert(supported_attributes, "presets")
table.insert(supported_attributes, "maxPresets")
end
Expand Down
123 changes: 120 additions & 3 deletions drivers/SmartThings/matter-switch/src/test/test_matter_camera.lua
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,38 @@ local mock_device_no_per_zone_sensitivity = test.mock_device.build_test_matter_d
}
})

local mock_device_pan_tilt_only = 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.CameraAvSettingsUserLevelManagement.ID,
feature_map = clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PAN |
clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_TILT |
clusters.CameraAvSettingsUserLevelManagement.types.Feature.MECHANICAL_PRESETS,
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},
Expand Down Expand Up @@ -297,6 +329,31 @@ local function test_init_no_user_defined_zone()
mock_device_no_user_defined_zone:expect_metadata_update({ provisioning_state = "PROVISIONED" })
end

-- mock_device_pan_tilt_only has no CameraAvStreamManagement cluster at all, so do_configure's
-- `if #device:get_endpoints(clusters.CameraAvStreamManagement.ID) == 0` branch calls match_profile
-- directly and synchronously during doConfigure, rather than waiting on an AttributeList report
-- like the other reduced fixtures above. That means the profile/capability metadata update and the
-- "PROVISIONED" transition both happen back-to-back off of the same doConfigure lifecycle event.
local subscribe_request_pan_tilt_only

local function test_init_pan_tilt_only()
test.mock_device.add_test_device(mock_device_pan_tilt_only)
test.socket.device_lifecycle:__queue_receive({ mock_device_pan_tilt_only.id, "added" })
test.socket.device_lifecycle:__queue_receive({ mock_device_pan_tilt_only.id, "init" })
subscribe_request_pan_tilt_only = cluster_base.subscribe(
mock_device_pan_tilt_only, nil, camera_fields.CameraAVSULMFeatureMapAttr.cluster, camera_fields.CameraAVSULMFeatureMapAttr.ID
)
test.socket.matter:__expect_send({mock_device_pan_tilt_only.id, subscribe_request_pan_tilt_only})
test.socket.device_lifecycle:__queue_receive({ mock_device_pan_tilt_only.id, "doConfigure" })
mock_device_pan_tilt_only:expect_metadata_update({
optional_component_capabilities = {
{ "main", { "mechanicalPanTiltZoom" } }
},
profile = "camera"
})
mock_device_pan_tilt_only:expect_metadata_update({ provisioning_state = "PROVISIONED" })
end

local additional_subscribed_attributes = {
clusters.CameraAvStreamManagement.attributes.HDRModeEnabled,
clusters.CameraAvStreamManagement.attributes.ImageRotation,
Expand Down Expand Up @@ -616,6 +673,40 @@ local function update_device_profile_no_user_defined_zone()
test.socket.matter:__expect_send({mock_device_no_user_defined_zone.id, subscribe_request_no_user_defined_zone})
end

local additional_subscribed_attributes_pan_tilt_only = {
clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPosition,
clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPresets,
clusters.CameraAvSettingsUserLevelManagement.attributes.MaxPresets,
clusters.CameraAvSettingsUserLevelManagement.attributes.ZoomMax,
clusters.CameraAvSettingsUserLevelManagement.attributes.PanMax,
clusters.CameraAvSettingsUserLevelManagement.attributes.PanMin,
clusters.CameraAvSettingsUserLevelManagement.attributes.TiltMax,
clusters.CameraAvSettingsUserLevelManagement.attributes.TiltMin,
}

-- Unlike the other reduced fixtures, mock_device_pan_tilt_only's profile/capability update already
-- happened synchronously during doConfigure (see test_init_pan_tilt_only), so this only needs to
-- simulate the platform pushing the updated profile back down via infoChanged, then confirm the PTZ
-- capability is initialized with pan/tilt/presets only -- no zoom or zoomRange, since this device's
-- CameraAvSettingsUserLevelManagement feature_map never claimed MECHANICAL_ZOOM support.
local function update_device_profile_pan_tilt_only()
local updated_device_profile = t_utils.get_profile_definition(
"camera.yml", {enabled_optional_capabilities = {
{ "main", { "mechanicalPanTiltZoom" } }
}}
)
test.socket.device_lifecycle:__queue_receive(mock_device_pan_tilt_only:generate_info_changed({ profile = updated_device_profile }))
test.socket.capability:__expect_send(
mock_device_pan_tilt_only:generate_test_message("main", capabilities.mechanicalPanTiltZoom.supportedAttributes(
{"pan", "panRange", "tilt", "tiltRange", "presets", "maxPresets"}
))
)
for _, attr in ipairs(additional_subscribed_attributes_pan_tilt_only) do
subscribe_request_pan_tilt_only:merge(attr:subscribe(mock_device_pan_tilt_only))
end
test.socket.matter:__expect_send({mock_device_pan_tilt_only.id, subscribe_request_pan_tilt_only})
end

-- Matter Handler UTs

test.register_coroutine_test(
Expand Down Expand Up @@ -1358,6 +1449,32 @@ test.register_coroutine_test(
}
)

test.register_coroutine_test(
"PTZ Position report with zoom omitted should be handled normally on a device with no zoom feature",
function()
update_device_profile_pan_tilt_only()
test.wait_for_events()
-- This device has no zoom feature, so its MPTZPosition report never has a zoom field;
-- pan/tilt should still be handled normally.
test.socket.matter:__queue_receive({
mock_device_pan_tilt_only.id,
clusters.CameraAvSettingsUserLevelManagement.attributes.MPTZPosition:build_test_report_data(
mock_device_pan_tilt_only, CAMERA_EP, {pan = 10, tilt = 20})
})
test.socket.capability:__set_channel_ordering("relaxed")
test.socket.capability:__expect_send(
mock_device_pan_tilt_only:generate_test_message("main", capabilities.mechanicalPanTiltZoom.pan(10))
)
test.socket.capability:__expect_send(
mock_device_pan_tilt_only:generate_test_message("main", capabilities.mechanicalPanTiltZoom.tilt(20))
)
end,
{
min_api_version = 14,
test_init = test_init_pan_tilt_only
}
)

test.register_coroutine_test(
"PTZ Presets reports should generate appropriate events",
function()
Expand Down Expand Up @@ -2006,21 +2123,21 @@ test.register_coroutine_test(
{ capability = "mechanicalPanTiltZoom", component = "main", command = "panRelative", args = { 10 } },
})
test.socket.matter:__expect_send({
mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRelativeMove(mock_device, CAMERA_EP, 10, 0, 0)
mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRelativeMove(mock_device, CAMERA_EP, 10, nil, nil)
})
test.socket.capability:__queue_receive({
mock_device.id,
{ capability = "mechanicalPanTiltZoom", component = "main", command = "tiltRelative", args = { -35 } },
})
test.socket.matter:__expect_send({
mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRelativeMove(mock_device, CAMERA_EP, 0, -35, 0)
mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRelativeMove(mock_device, CAMERA_EP, nil, -35, nil)
})
test.socket.capability:__queue_receive({
mock_device.id,
{ capability = "mechanicalPanTiltZoom", component = "main", command = "zoomRelative", args = { 80 } },
})
test.socket.matter:__expect_send({
mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRelativeMove(mock_device, CAMERA_EP, 0, 0, 80)
mock_device.id, clusters.CameraAvSettingsUserLevelManagement.server.commands.MPTZRelativeMove(mock_device, CAMERA_EP, nil, nil, 80)
})
end,
{
Expand Down
Loading