From 9324fcd48c22234ebbf976fb8a391f97849fa620 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Thu, 16 Apr 2026 12:14:25 -0500 Subject: [PATCH 1/3] simplify camera subdriver reinit checks --- .../camera_utils/device_configuration.lua | 242 ++++++------------ .../src/sub_drivers/camera/init.lua | 4 +- 2 files changed, 83 insertions(+), 163 deletions(-) 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..87ed4dddda 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 @@ -13,16 +13,6 @@ local switch_utils = require "switch_utils.utils" local CameraDeviceConfiguration = {} -local managed_capability_map = { - { key = "webrtc", capability = capabilities.webrtc }, - { key = "ptz", capability = capabilities.mechanicalPanTiltZoom }, - { key = "zone_management", capability = capabilities.zoneManagement }, - { key = "local_media_storage", capability = capabilities.localMediaStorage }, - { key = "audio_recording", capability = capabilities.audioRecording }, - { key = "video_stream_settings", capability = capabilities.videoStreamSettings }, - { key = "camera_privacy_mode", capability = capabilities.cameraPrivacyMode }, -} - local function get_status_light_presence(device) return device:get_field(camera_fields.STATUS_LIGHT_ENABLED_PRESENT), device:get_field(camera_fields.STATUS_LIGHT_BRIGHTNESS_PRESENT) @@ -108,59 +98,8 @@ local function build_camera_privacy_supported_commands() return { "setSoftRecordingPrivacyMode", "setSoftLivestreamPrivacyMode" } end -local function capabilities_needing_reinit(device) - local capabilities_to_reinit = {} - - local function should_init(capability, attribute, expected) - if device:supports_capability(capability) then - local current = st_utils.deep_copy(device:get_latest_state( - camera_fields.profile_components.main, - capability.ID, - attribute.NAME, - {} - )) - return not switch_utils.deep_equals(current, expected) - end - return false - end - - if should_init(capabilities.webrtc, capabilities.webrtc.supportedFeatures, build_webrtc_supported_features()) then - capabilities_to_reinit.webrtc = true - end - - if should_init(capabilities.mechanicalPanTiltZoom, capabilities.mechanicalPanTiltZoom.supportedAttributes, build_ptz_supported_attributes(device)) then - capabilities_to_reinit.ptz = true - end - - if should_init(capabilities.zoneManagement, capabilities.zoneManagement.supportedFeatures, build_zone_management_supported_features(device)) then - capabilities_to_reinit.zone_management = true - end - - if should_init(capabilities.localMediaStorage, capabilities.localMediaStorage.supportedAttributes, build_local_media_storage_supported_attributes(device)) then - capabilities_to_reinit.local_media_storage = true - end - - if device:supports_capability(capabilities.audioRecording) then - local audio_enabled_state = device:get_latest_state( - camera_fields.profile_components.main, - capabilities.audioRecording.ID, - capabilities.audioRecording.audioRecording.NAME - ) - if audio_enabled_state == nil then - capabilities_to_reinit.audio_recording = true - end - end - - if should_init(capabilities.videoStreamSettings, capabilities.videoStreamSettings.supportedFeatures, build_video_stream_settings_supported_features(device)) then - capabilities_to_reinit.video_stream_settings = true - end - - if should_init(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedAttributes, build_camera_privacy_supported_attributes()) or - should_init(capabilities.cameraPrivacyMode, capabilities.cameraPrivacyMode.supportedCommands, build_camera_privacy_supported_commands()) then - capabilities_to_reinit.camera_privacy_mode = true - end - - return capabilities_to_reinit +local function build_audio_recording() + return "enabled" end function CameraDeviceConfiguration.create_child_devices(driver, device) @@ -309,130 +248,112 @@ function CameraDeviceConfiguration.match_profile(device) return profile_update_requested end -local function init_webrtc(device) - if device:supports_capability(capabilities.webrtc) then - local transport_provider_ep_ids = device:get_endpoints(clusters.WebRTCTransportProvider.ID) - device:emit_event_for_endpoint(transport_provider_ep_ids[1], capabilities.webrtc.supportedFeatures(build_webrtc_supported_features())) - end +local function init_webrtc(device, supported_features) + local transport_provider_ep_ids = device:get_endpoints(clusters.WebRTCTransportProvider.ID) + device:emit_event_for_endpoint(transport_provider_ep_ids[1], capabilities.webrtc.supportedFeatures(supported_features)) end -local function init_ptz(device) - if device:supports_capability(capabilities.mechanicalPanTiltZoom) then - local av_settings_ep_ids = device:get_endpoints(clusters.CameraAvSettingsUserLevelManagement.ID) - device:emit_event_for_endpoint(av_settings_ep_ids[1], capabilities.mechanicalPanTiltZoom.supportedAttributes(build_ptz_supported_attributes(device))) - end +local function init_ptz(device, supported_attributes) + local av_settings_ep_ids = device:get_endpoints(clusters.CameraAvSettingsUserLevelManagement.ID) + device:emit_event_for_endpoint(av_settings_ep_ids[1], capabilities.mechanicalPanTiltZoom.supportedAttributes(supported_attributes)) end -local function init_zone_management(device) - if device:supports_capability(capabilities.zoneManagement) then - local zone_management_ep_ids = device:get_endpoints(clusters.ZoneManagement.ID) - device:emit_event_for_endpoint(zone_management_ep_ids[1], capabilities.zoneManagement.supportedFeatures(build_zone_management_supported_features(device))) - end +local function init_zone_management(device, supported_features) + local zone_management_ep_ids = device:get_endpoints(clusters.ZoneManagement.ID) + device:emit_event_for_endpoint(zone_management_ep_ids[1], capabilities.zoneManagement.supportedFeatures(supported_features)) end -local function init_local_media_storage(device) - if device:supports_capability(capabilities.localMediaStorage) then - local av_stream_management_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID) - device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.localMediaStorage.supportedAttributes(build_local_media_storage_supported_attributes(device))) - end +local function init_local_media_storage(device, supported_attributes) + local av_stream_management_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.localMediaStorage.supportedAttributes(supported_attributes)) end -local function init_audio_recording(device) - if device:supports_capability(capabilities.audioRecording) then - local audio_enabled_state = device:get_latest_state( - camera_fields.profile_components.main, capabilities.audioRecording.ID, capabilities.audioRecording.audioRecording.NAME - ) - if audio_enabled_state == nil then - -- Initialize with enabled default if state is unset - local av_stream_management_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID) - device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.audioRecording.audioRecording("enabled")) - end +local function init_audio_recording(device, initial_audio_recording_state) + local audio_enabled_state = device:get_latest_state( + camera_fields.profile_components.main, capabilities.audioRecording.ID, capabilities.audioRecording.audioRecording.NAME + ) + if audio_enabled_state == nil then + -- Initialize with enabled default if state is unset + local av_stream_management_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.audioRecording.audioRecording(initial_audio_recording_state)) end end -local function init_video_stream_settings(device) - if device:supports_capability(capabilities.videoStreamSettings) then - local av_stream_management_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID) - device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.videoStreamSettings.supportedFeatures(build_video_stream_settings_supported_features(device))) - end +local function init_video_stream_settings(device, supported_features) + local av_stream_management_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.videoStreamSettings.supportedFeatures(supported_features)) end -local function init_camera_privacy_mode(device) - if device:supports_capability(capabilities.cameraPrivacyMode) then - local av_stream_management_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID) - device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.cameraPrivacyMode.supportedAttributes(build_camera_privacy_supported_attributes())) - device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.cameraPrivacyMode.supportedCommands(build_camera_privacy_supported_commands())) - end +local function init_camera_privacy_mode_attributes(device, supported_attributes) + local av_stream_management_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.cameraPrivacyMode.supportedAttributes(supported_attributes)) end -function CameraDeviceConfiguration.initialize_camera_capabilities(device) - init_webrtc(device) - init_ptz(device) - init_zone_management(device) - init_local_media_storage(device) - init_audio_recording(device) - init_video_stream_settings(device) - init_camera_privacy_mode(device) +local function init_camera_privacy_mode_commands(device, supported_commands) + local av_stream_management_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID) + device:emit_event_for_endpoint(av_stream_management_ep_ids[1], capabilities.cameraPrivacyMode.supportedCommands(supported_commands)) end -local function initialize_selected_camera_capabilities(device, capabilities_to_reinit) - local reinit_targets = capabilities_to_reinit or {} +function CameraDeviceConfiguration.initialize_select_camera_capabilities(device) + local ordered_managed_capabilities = { + capabilities.webrtc, + capabilities.mechanicalPanTiltZoom, + capabilities.zoneManagement, + capabilities.localMediaStorage, + capabilities.audioRecording, + capabilities.videoStreamSettings, + capabilities.cameraPrivacyMode + } - if reinit_targets.webrtc then - init_webrtc(device) - end - if reinit_targets.ptz then - init_ptz(device) - end - if reinit_targets.zone_management then - init_zone_management(device) - end - if reinit_targets.local_media_storage then - init_local_media_storage(device) - end - if reinit_targets.audio_recording then - init_audio_recording(device) - end - if reinit_targets.video_stream_settings then - init_video_stream_settings(device) - end - if reinit_targets.camera_privacy_mode then - init_camera_privacy_mode(device) - end -end + local managed_capabilities = { + [capabilities.webrtc] = { + [capabilities.webrtc.supportedFeatures] = { init_webrtc, build_webrtc_supported_features} + }, + [capabilities.mechanicalPanTiltZoom] = { + [capabilities.mechanicalPanTiltZoom.supportedAttributes] = { init_ptz, build_ptz_supported_attributes} + }, + [capabilities.zoneManagement] = { + [capabilities.zoneManagement.supportedFeatures] = { init_zone_management, build_zone_management_supported_features} + }, + [capabilities.localMediaStorage] = { + [capabilities.localMediaStorage.supportedAttributes] = { init_local_media_storage, build_local_media_storage_supported_attributes} + }, + [capabilities.audioRecording] = { + [capabilities.audioRecording.audioRecording] = { init_audio_recording, build_audio_recording} + }, + [capabilities.videoStreamSettings] = { + [capabilities.videoStreamSettings.supportedFeatures] = { init_video_stream_settings, build_video_stream_settings_supported_features} + }, + [capabilities.cameraPrivacyMode] = { + [capabilities.cameraPrivacyMode.supportedAttributes] = { init_camera_privacy_mode_attributes, build_camera_privacy_supported_attributes}, + [capabilities.cameraPrivacyMode.supportedCommands] = { init_camera_privacy_mode_commands, build_camera_privacy_supported_commands} + } + } -local function profile_capability_set(profile) - local capability_set = {} - for _, component in pairs((profile or {}).components or {}) do - for _, capability in pairs(component.capabilities or {}) do - if capability.id ~= nil then - capability_set[capability.id] = true - end + local function should_init(capability, attribute, expected) + if device:supports_capability(capability) then + local current = st_utils.deep_copy(device:get_latest_state(camera_fields.profile_components.main, capability.ID, attribute.NAME, {})) + return not switch_utils.deep_equals(current, expected) end + return false end - return capability_set -end -local function changed_capabilities_from_profiles(old_profile, new_profile) - local flags = {} - local old_set = profile_capability_set(old_profile) - local new_set = profile_capability_set(new_profile) - - for _, managed in ipairs(managed_capability_map) do - local id = managed.capability.ID - if old_set[id] ~= new_set[id] and new_set[id] == true then - flags[managed.key] = true + for _, capability in ipairs(ordered_managed_capabilities) do + for attribute, functions in pairs(managed_capabilities[capability]) do + local init_function = functions[1] + local build_function = functions[2] + local expected = build_function(device) + if should_init(capability, attribute, expected) then + init_function(device, expected) + end end end - - return flags end function CameraDeviceConfiguration.reconcile_profile_and_capabilities(device) local profile_update_requested = CameraDeviceConfiguration.match_profile(device) if not profile_update_requested then - local capabilities_to_reinit = capabilities_needing_reinit(device) - initialize_selected_camera_capabilities(device, capabilities_to_reinit) + CameraDeviceConfiguration.initialize_select_camera_capabilities(device) end return profile_update_requested end @@ -441,9 +362,8 @@ function CameraDeviceConfiguration.update_status_light_attribute_presence(device set_status_light_presence(device, status_light_enabled_present, status_light_brightness_present) end -function CameraDeviceConfiguration.reinitialize_changed_camera_capabilities_and_subscriptions(device, old_profile, new_profile) - local changed_capabilities = changed_capabilities_from_profiles(old_profile, new_profile) - initialize_selected_camera_capabilities(device, changed_capabilities) +function CameraDeviceConfiguration.reinitialize_changed_camera_capabilities_and_subscriptions(device) + CameraDeviceConfiguration.initialize_select_camera_capabilities(device) device:subscribe() if #switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.DOORBELL) > 0 then button_cfg.configure_buttons(device, device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH})) diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua index a72aa0b234..842db8f275 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua @@ -35,7 +35,7 @@ function CameraLifecycleHandlers.do_configure(driver, device) camera_cfg.match_profile(device) end camera_cfg.create_child_devices(driver, device) - camera_cfg.initialize_camera_capabilities(device) + camera_cfg.initialize_select_camera_capabilities(device) end function CameraLifecycleHandlers.driver_switched(driver, device) @@ -53,7 +53,7 @@ function CameraLifecycleHandlers.info_changed(driver, device, event, args) if software_version_changed then camera_cfg.reconcile_profile_and_capabilities(device) elseif profile_changed then - camera_cfg.reinitialize_changed_camera_capabilities_and_subscriptions(device, args.old_st_store.profile, device.profile) + camera_cfg.reinitialize_changed_camera_capabilities_and_subscriptions(device) end end From 5a5f1ce3b236b6670fa9914898b228118771ba2a Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Thu, 16 Apr 2026 13:28:01 -0500 Subject: [PATCH 2/3] simplify the camera component update logic --- .../camera_handlers/attribute_handlers.lua | 24 ++++--- .../camera_utils/device_configuration.lua | 18 ++---- .../sub_drivers/camera/camera_utils/utils.lua | 64 ++++++++++--------- 3 files changed, 51 insertions(+), 55 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/attribute_handlers.lua index b32b1dd55c..d933b03b62 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/attribute_handlers.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_handlers/attribute_handlers.lua @@ -6,7 +6,6 @@ local camera_utils = require "sub_drivers.camera.camera_utils.utils" local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local camera_cfg = require "sub_drivers.camera.camera_utils.device_configuration" -local fields = require "switch_utils.fields" local utils = require "st.utils" local CameraAttributeHandlers = {} @@ -447,25 +446,24 @@ function CameraAttributeHandlers.selected_chime_handler(driver, device, ib, resp end function CameraAttributeHandlers.camera_av_stream_management_attribute_list_handler(driver, device, ib, response) - if not ib.data.elements then return end local status_light_enabled_present, status_light_brightness_present = false, false - local attribute_ids = {} - for _, attr in ipairs(ib.data.elements) do + local status_light_attribute_ids = {} + for _, attr in ipairs(ib.data.elements or {}) do if attr.value == clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID then status_light_enabled_present = true - table.insert(attribute_ids, clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID) + table.insert(status_light_attribute_ids, clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID) elseif attr.value == clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID then status_light_brightness_present = true - table.insert(attribute_ids, clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID) + table.insert(status_light_attribute_ids, clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID) end end - local component_map = device:get_field(fields.COMPONENT_TO_ENDPOINT_MAP) or {} - component_map.statusLed = { - endpoint_id = ib.endpoint_id, - cluster_id = ib.cluster_id, - attribute_ids = attribute_ids, - } - device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, component_map, {persist=true}) + if #status_light_attribute_ids > 0 then + camera_utils.update_component_to_endpoint_map(device, camera_fields.profile_components.statusLed, { + endpoint_id = ib.endpoint_id, + cluster_id = ib.cluster_id, + attribute_ids = status_light_attribute_ids, + }) + end camera_cfg.update_status_light_attribute_presence(device, status_light_enabled_present, status_light_brightness_present) camera_cfg.reconcile_profile_and_capabilities(device) end 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 87ed4dddda..72380b684f 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 @@ -240,7 +240,7 @@ function CameraDeviceConfiguration.match_profile(device) profile_update_requested = true device:try_update_metadata({profile = "camera", optional_component_capabilities = optional_supported_component_capabilities}) if #doorbell_endpoints > 0 then - CameraDeviceConfiguration.update_doorbell_component_map(device, doorbell_endpoints[1]) + camera_utils.update_component_to_endpoint_map(device, camera_fields.profile_components.doorbell, doorbell_endpoints[1]) button_cfg.configure_buttons(device, device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH})) end end @@ -340,11 +340,11 @@ function CameraDeviceConfiguration.initialize_select_camera_capabilities(device) for _, capability in ipairs(ordered_managed_capabilities) do for attribute, functions in pairs(managed_capabilities[capability]) do - local init_function = functions[1] - local build_function = functions[2] - local expected = build_function(device) - if should_init(capability, attribute, expected) then - init_function(device, expected) + local cap_init_function = functions[1] + local cap_build_function = functions[2] + local expected_state = cap_build_function(device) + if should_init(capability, attribute, expected_state) then + cap_init_function(device, expected_state) end end end @@ -370,10 +370,4 @@ function CameraDeviceConfiguration.reinitialize_changed_camera_capabilities_and_ end end -function CameraDeviceConfiguration.update_doorbell_component_map(device, ep) - local component_map = device:get_field(fields.COMPONENT_TO_ENDPOINT_MAP) or {} - component_map.doorbell = ep - device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, component_map, {persist = true}) -end - return CameraDeviceConfiguration diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/utils.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/utils.lua index 78792b94b3..2996cbf292 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/utils.lua @@ -4,7 +4,6 @@ local camera_fields = require "sub_drivers.camera.camera_utils.fields" local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" -local fields = require "switch_utils.fields" local switch_utils = require "switch_utils.utils" local CameraUtils = {} @@ -21,35 +20,33 @@ function CameraUtils.component_to_endpoint(device, component) end function CameraUtils.update_camera_component_map(device) - local camera_av_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID) - if #camera_av_ep_ids > 0 then - -- An assumption here: there is only 1 CameraAvStreamManagement cluster on the device (which is all our profile supports) - local component_map = {} - if CameraUtils.feature_supported(device, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.types.Feature.AUDIO) then - component_map.microphone = { - endpoint_id = camera_av_ep_ids[1], - cluster_id = clusters.CameraAvStreamManagement.ID, - attribute_ids = { - clusters.CameraAvStreamManagement.attributes.MicrophoneMuted.ID, - clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel.ID, - clusters.CameraAvStreamManagement.attributes.MicrophoneMaxLevel.ID, - clusters.CameraAvStreamManagement.attributes.MicrophoneMinLevel.ID, - }, - } - end - if CameraUtils.feature_supported(device, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.types.Feature.VIDEO) then - component_map.speaker = { - endpoint_id = camera_av_ep_ids[1], - cluster_id = clusters.CameraAvStreamManagement.ID, - attribute_ids = { - clusters.CameraAvStreamManagement.attributes.SpeakerMuted.ID, - clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel.ID, - clusters.CameraAvStreamManagement.attributes.SpeakerMaxLevel.ID, - clusters.CameraAvStreamManagement.attributes.SpeakerMinLevel.ID, - }, - } - end - device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, component_map, {persist = true}) + -- An assumption here: there is only 1 CameraAvStreamManagement cluster on the device (which is all our profile supports) + local audio_camera_av_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID, {feature_bitmap=clusters.CameraAvStreamManagement.types.Feature.AUDIO}) + if #audio_camera_av_ep_ids > 0 then + CameraUtils.update_component_to_endpoint_map(device, camera_fields.profile_components.microphone, { + endpoint_id = audio_camera_av_ep_ids[1], + cluster_id = clusters.CameraAvStreamManagement.ID, + attribute_ids = { + clusters.CameraAvStreamManagement.attributes.MicrophoneMuted.ID, + clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel.ID, + clusters.CameraAvStreamManagement.attributes.MicrophoneMaxLevel.ID, + clusters.CameraAvStreamManagement.attributes.MicrophoneMinLevel.ID, + }, + }) + end + + local speaker_camera_av_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID, {feature_bitmap=clusters.CameraAvStreamManagement.types.Feature.SPEAKER}) + if #speaker_camera_av_ep_ids > 0 then + CameraUtils.update_component_to_endpoint_map(device, camera_fields.profile_components.speaker, { + endpoint_id = speaker_camera_av_ep_ids[1], + cluster_id = clusters.CameraAvStreamManagement.ID, + attribute_ids = { + clusters.CameraAvStreamManagement.attributes.SpeakerMuted.ID, + clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel.ID, + clusters.CameraAvStreamManagement.attributes.SpeakerMaxLevel.ID, + clusters.CameraAvStreamManagement.attributes.SpeakerMinLevel.ID, + }, + }) end end @@ -78,6 +75,13 @@ function CameraUtils.get_ptz_map(device) return ptz_map end +function CameraUtils.update_component_to_endpoint_map(device, component, endpoint_mapping) + local fields = require "switch_utils.fields" + local component_endpoint_map = device:get_field(fields.COMPONENT_TO_ENDPOINT_MAP) or {} + component_endpoint_map[component] = endpoint_mapping + device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, component_endpoint_map, { persist = true }) +end + function CameraUtils.feature_supported(device, cluster_id, feature_flag) return #device:get_endpoints(cluster_id, { feature_bitmap = feature_flag }) > 0 end From be05336e3e0c8c5614bface2492579f26dd76262 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Thu, 16 Apr 2026 13:34:04 -0500 Subject: [PATCH 3/3] move update_camera_component_map to cfg file --- .../camera_utils/device_configuration.lua | 31 +++++++++++++++++++ .../sub_drivers/camera/camera_utils/utils.lua | 31 ------------------- .../src/sub_drivers/camera/init.lua | 4 +-- 3 files changed, 33 insertions(+), 33 deletions(-) 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 72380b684f..ebee1d6702 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 @@ -370,4 +370,35 @@ function CameraDeviceConfiguration.reinitialize_changed_camera_capabilities_and_ end end +function CameraDeviceConfiguration.update_camera_component_map(device) + -- An assumption here: there is only 1 CameraAvStreamManagement cluster on the device (which is all our profile supports) + local audio_camera_av_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID, {feature_bitmap=clusters.CameraAvStreamManagement.types.Feature.AUDIO}) + if #audio_camera_av_ep_ids > 0 then + camera_utils.update_component_to_endpoint_map(device, camera_fields.profile_components.microphone, { + endpoint_id = audio_camera_av_ep_ids[1], + cluster_id = clusters.CameraAvStreamManagement.ID, + attribute_ids = { + clusters.CameraAvStreamManagement.attributes.MicrophoneMuted.ID, + clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel.ID, + clusters.CameraAvStreamManagement.attributes.MicrophoneMaxLevel.ID, + clusters.CameraAvStreamManagement.attributes.MicrophoneMinLevel.ID, + }, + }) + end + + local speaker_camera_av_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID, {feature_bitmap=clusters.CameraAvStreamManagement.types.Feature.SPEAKER}) + if #speaker_camera_av_ep_ids > 0 then + camera_utils.update_component_to_endpoint_map(device, camera_fields.profile_components.speaker, { + endpoint_id = speaker_camera_av_ep_ids[1], + cluster_id = clusters.CameraAvStreamManagement.ID, + attribute_ids = { + clusters.CameraAvStreamManagement.attributes.SpeakerMuted.ID, + clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel.ID, + clusters.CameraAvStreamManagement.attributes.SpeakerMaxLevel.ID, + clusters.CameraAvStreamManagement.attributes.SpeakerMinLevel.ID, + }, + }) + end +end + return CameraDeviceConfiguration diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/utils.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/utils.lua index 2996cbf292..b3420d206e 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/camera_utils/utils.lua @@ -19,37 +19,6 @@ function CameraUtils.component_to_endpoint(device, component) return nil end -function CameraUtils.update_camera_component_map(device) - -- An assumption here: there is only 1 CameraAvStreamManagement cluster on the device (which is all our profile supports) - local audio_camera_av_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID, {feature_bitmap=clusters.CameraAvStreamManagement.types.Feature.AUDIO}) - if #audio_camera_av_ep_ids > 0 then - CameraUtils.update_component_to_endpoint_map(device, camera_fields.profile_components.microphone, { - endpoint_id = audio_camera_av_ep_ids[1], - cluster_id = clusters.CameraAvStreamManagement.ID, - attribute_ids = { - clusters.CameraAvStreamManagement.attributes.MicrophoneMuted.ID, - clusters.CameraAvStreamManagement.attributes.MicrophoneVolumeLevel.ID, - clusters.CameraAvStreamManagement.attributes.MicrophoneMaxLevel.ID, - clusters.CameraAvStreamManagement.attributes.MicrophoneMinLevel.ID, - }, - }) - end - - local speaker_camera_av_ep_ids = device:get_endpoints(clusters.CameraAvStreamManagement.ID, {feature_bitmap=clusters.CameraAvStreamManagement.types.Feature.SPEAKER}) - if #speaker_camera_av_ep_ids > 0 then - CameraUtils.update_component_to_endpoint_map(device, camera_fields.profile_components.speaker, { - endpoint_id = speaker_camera_av_ep_ids[1], - cluster_id = clusters.CameraAvStreamManagement.ID, - attribute_ids = { - clusters.CameraAvStreamManagement.attributes.SpeakerMuted.ID, - clusters.CameraAvStreamManagement.attributes.SpeakerVolumeLevel.ID, - clusters.CameraAvStreamManagement.attributes.SpeakerMaxLevel.ID, - clusters.CameraAvStreamManagement.attributes.SpeakerMinLevel.ID, - }, - }) - end -end - function CameraUtils.get_ptz_map(device) local mechanicalPanTiltZoom = capabilities.mechanicalPanTiltZoom local ptz_map = { diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua index 842db8f275..0683cef8bd 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/camera/init.lua @@ -30,7 +30,7 @@ function CameraLifecycleHandlers.device_init(driver, device) end function CameraLifecycleHandlers.do_configure(driver, device) - camera_utils.update_camera_component_map(device) + camera_cfg.update_camera_component_map(device) if #device:get_endpoints(clusters.CameraAvStreamManagement.ID) == 0 then camera_cfg.match_profile(device) end @@ -39,7 +39,7 @@ function CameraLifecycleHandlers.do_configure(driver, device) end function CameraLifecycleHandlers.driver_switched(driver, device) - camera_utils.update_camera_component_map(device) + camera_cfg.update_camera_component_map(device) if #device:get_endpoints(clusters.CameraAvStreamManagement.ID) == 0 then camera_cfg.match_profile(device) end