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
44 changes: 41 additions & 3 deletions drivers/SmartThings/matter-switch/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ function SwitchLifecycleHandlers.do_configure(driver, device)
if device.network_type == device_lib.NETWORK_TYPE_MATTER and not switch_utils.detect_bridge(device) then
switch_cfg.set_device_control_options(device)
device_cfg.match_profile(driver, device)
elseif device.network_type == device_lib.NETWORK_TYPE_CHILD then
-- because get_parent_device() may cause race conditions if used in init, an initial child subscribe is handled in doConfigure.
-- all future calls to subscribe will be handled by the parent device in init
device:subscribe()
end
end

Expand All @@ -68,7 +72,7 @@ function SwitchLifecycleHandlers.info_changed(driver, device, event, args)
device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH})
)
elseif device.network_type == device_lib.NETWORK_TYPE_CHILD then
switch_utils.update_subscriptions(device:get_parent_device()) -- parent device required to scan through EPs and update subscriptions
device:get_parent_device():subscribe() -- parent device required to send subscription requests
end
end

Expand All @@ -87,7 +91,8 @@ function SwitchLifecycleHandlers.device_init(driver, device)
if device:get_field(fields.IS_PARENT_CHILD_DEVICE) then
device:set_find_child(switch_utils.find_child)
end
switch_utils.update_subscriptions(device)
device:extend_device("subscribe", switch_utils.subscribe)
device:subscribe()

-- device energy reporting must be handled cumulatively, periodically, or by both simulatanously.
-- To ensure a single source of truth, we only handle a device's periodic reporting if cumulative reporting is not supported.
Expand Down Expand Up @@ -290,7 +295,40 @@ local matter_driver_template = {
[capabilities.valve.commands.open.NAME] = capability_handlers.handle_valve_open,
},
},
supported_capabilities = fields.supported_capabilities,
supported_capabilities = {
capabilities.audioMute,
capabilities.audioRecording,
capabilities.audioVolume,
capabilities.battery,
capabilities.batteryLevel,
capabilities.button,
capabilities.cameraPrivacyMode,
capabilities.cameraViewportSettings,
capabilities.colorControl,
capabilities.colorTemperature,
capabilities.energyMeter,
capabilities.fanMode,
capabilities.fanSpeedPercent,
capabilities.hdr,
capabilities.illuminanceMeasurement,
capabilities.imageControl,
capabilities.level,
capabilities.localMediaStorage,
capabilities.mechanicalPanTiltZoom,
capabilities.motionSensor,
capabilities.nightVision,
capabilities.powerMeter,
capabilities.powerConsumptionReport,
capabilities.relativeHumidityMeasurement,
capabilities.sounds,
capabilities.switch,
capabilities.switchLevel,
capabilities.temperatureMeasurement,
capabilities.valve,
capabilities.videoStreamSettings,
capabilities.webrtc,
capabilities.zoneManagement
},
sub_drivers = {
switch_utils.lazy_load_if_possible("sub_drivers.aqara_cube"),
switch_utils.lazy_load("sub_drivers.camera"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,8 @@ function CameraUtils.subscribe(device)
clusters.CameraAvStreamManagement.attributes.StatusLightBrightness
},
[capabilities.switch.ID] = {
clusters.CameraAvStreamManagement.attributes.StatusLightEnabled
clusters.CameraAvStreamManagement.attributes.StatusLightEnabled,
clusters.OnOff.attributes.OnOff
},
[capabilities.videoStreamSettings.ID] = {
clusters.CameraAvStreamManagement.attributes.RateDistortionTradeOffPoints,
Expand Down Expand Up @@ -223,8 +224,26 @@ function CameraUtils.subscribe(device)
},
[capabilities.motionSensor.ID] = {
clusters.OccupancySensing.attributes.Occupancy
}
},
[capabilities.switchLevel.ID] = {
clusters.LevelControl.attributes.CurrentLevel,
clusters.LevelControl.attributes.MaxLevel,
clusters.LevelControl.attributes.MinLevel,
},
[capabilities.colorControl.ID] = {
clusters.ColorControl.attributes.ColorMode,
clusters.ColorControl.attributes.CurrentHue,
clusters.ColorControl.attributes.CurrentSaturation,
clusters.ColorControl.attributes.CurrentX,
clusters.ColorControl.attributes.CurrentY,
},
[capabilities.colorTemperature.ID] = {
clusters.ColorControl.attributes.ColorTemperatureMireds,
clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds,
clusters.ColorControl.attributes.ColorTempPhysicalMinMireds,
},
}

local camera_subscribed_events = {
[capabilities.zoneManagement.ID] = {
clusters.ZoneManagement.events.ZoneTriggered,
Expand All @@ -238,56 +257,26 @@ function CameraUtils.subscribe(device)
}
}

for capability, attr_list in pairs(camera_subscribed_attributes) do
if device:supports_capability_by_id(capability) then
for _, attr in pairs(attr_list) do
device:add_subscribed_attribute(attr)
end
end
end
for capability, event_list in pairs(camera_subscribed_events) do
if device:supports_capability_by_id(capability) then
for _, event in pairs(event_list) do
device:add_subscribed_event(event)
end
end
end
local im = require "st.matter.interaction_model"
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we replace the block below with a call to the subscribe function implemented in switch_utils?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the issue there is how do we add the subscription to clusters.CameraAvStreamManagement.attributes.AttributeList.ID if we do it like that? I couldn't really find a way around that at this time, so I left it like this


local subscribe_request = im.InteractionRequest(im.InteractionRequest.RequestType.SUBSCRIBE, {})
local devices_seen, capabilities_seen, attributes_seen, events_seen = {}, {}, {}, {}

-- match_profile is called from the CameraAvStreamManagement AttributeList handler,
-- so the subscription needs to be added here first
if #device:get_endpoints(clusters.CameraAvStreamManagement.ID) > 0 then
device:add_subscribed_attribute(clusters.CameraAvStreamManagement.attributes.AttributeList)
local ib = im.InteractionInfoBlock(nil, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.attributes.AttributeList.ID)
subscribe_request:with_info_block(ib)
end

-- Add subscription for attributes specific to child devices
if device:get_field(fields.IS_PARENT_CHILD_DEVICE) then
for _, ep in ipairs(device.endpoints or {}) do
local id = 0
for _, dt in ipairs(ep.device_types or {}) do
if dt.device_type_id ~= fields.DEVICE_TYPE_ID.GENERIC_SWITCH then
id = math.max(id, dt.device_type_id)
end
end
for _, attr in pairs(fields.device_type_attribute_map[id] or {}) do
device:add_subscribed_attribute(attr)
end
for _, endpoint_info in ipairs(device.endpoints) do
local checked_device = switch_utils.find_child(device, endpoint_info.endpoint_id) or device
if not devices_seen[checked_device.id] then
switch_utils.populate_subscribe_request_for_device(checked_device, subscribe_request, capabilities_seen, attributes_seen, events_seen,
camera_subscribed_attributes, camera_subscribed_events
)
devices_seen[checked_device.id] = true -- only loop through any device once
end
end

local im = require "st.matter.interaction_model"
local subscribed_attributes = device:get_field("__subscribed_attributes") or {}
local subscribed_events = device:get_field("__subscribed_events") or {}
local subscribe_request = im.InteractionRequest(im.InteractionRequest.RequestType.SUBSCRIBE, {})
for _, attributes in pairs(subscribed_attributes) do
for _, ib in pairs(attributes) do
subscribe_request:with_info_block(ib)
end
end
for _, events in pairs(subscribed_events) do
for _, ib in pairs(events) do
subscribe_request:with_info_block(ib)
end
end
if #subscribe_request.info_blocks > 0 then
device:send(subscribe_request)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ end
function CameraLifecycleHandlers.info_changed(driver, device, event, args)
if camera_utils.profile_changed(device.profile.components, args.old_st_store.profile.components) 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
button_cfg.configure_buttons(device, device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH}))
end
device:subscribe()
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ function AttributeHandlers.color_temperature_mireds_handler(driver, device, ib,
end

function AttributeHandlers.current_x_handler(driver, device, ib, response)
if device:get_field(fields.COLOR_MODE) == fields.HUE_SAT_COLOR_MODE then
if device:get_field(fields.COLOR_MODE) == clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then
return
end
local y = device:get_field(fields.RECEIVED_Y)
Expand All @@ -157,7 +157,7 @@ function AttributeHandlers.current_x_handler(driver, device, ib, response)
end

function AttributeHandlers.current_y_handler(driver, device, ib, response)
if device:get_field(fields.COLOR_MODE) == fields.HUE_SAT_COLOR_MODE then
if device:get_field(fields.COLOR_MODE) == clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then
return
end
local x = device:get_field(fields.RECEIVED_X)
Expand All @@ -173,15 +173,17 @@ function AttributeHandlers.current_y_handler(driver, device, ib, response)
end

function AttributeHandlers.color_mode_handler(driver, device, ib, response)
if ib.data.value == device:get_field(fields.COLOR_MODE) or (ib.data.value ~= fields.HUE_SAT_COLOR_MODE and ib.data.value ~= fields.X_Y_COLOR_MODE) then
return
if ib.data.value == device:get_field(fields.COLOR_MODE)
or (ib.data.value ~= clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION
and ib.data.value ~= clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY) then
return
end
device:set_field(fields.COLOR_MODE, ib.data.value)
local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {})
if ib.data.value == fields.HUE_SAT_COLOR_MODE then
if ib.data.value == clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION then
req:merge(clusters.ColorControl.attributes.CurrentHue:read())
req:merge(clusters.ColorControl.attributes.CurrentSaturation:read())
elseif ib.data.value == fields.X_Y_COLOR_MODE then
elseif ib.data.value == clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY then
req:merge(clusters.ColorControl.attributes.CurrentX:read())
req:merge(clusters.ColorControl.attributes.CurrentY:read())
end
Expand Down
131 changes: 1 addition & 130 deletions drivers/SmartThings/matter-switch/src/switch_utils/fields.lua
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
-- Copyright © 2025 SmartThings, Inc.
-- Licensed under the Apache License, Version 2.0

local clusters = require "st.matter.clusters"
local capabilities = require "st.capabilities"
local version = require "version"

-- Include driver-side definitions when lua libs api version is < 11
if version.api < 11 then
clusters.ElectricalEnergyMeasurement = require "embedded_clusters.ElectricalEnergyMeasurement"
clusters.ElectricalPowerMeasurement = require "embedded_clusters.ElectricalPowerMeasurement"
end

local SwitchFields = {}

SwitchFields.HUE_SAT_COLOR_MODE = clusters.ColorControl.types.ColorMode.CURRENT_HUE_AND_CURRENT_SATURATION
SwitchFields.X_Y_COLOR_MODE = clusters.ColorControl.types.ColorMode.CURRENTX_AND_CURRENTY

SwitchFields.MOST_RECENT_TEMP = "mostRecentTemp"
SwitchFields.RECEIVED_X = "receivedX"
SwitchFields.RECEIVED_Y = "receivedY"
SwitchFields.HUESAT_SUPPORT = "huesatSupport"


SwitchFields.MIRED_KELVIN_CONVERSION_CONSTANT = 1000000

-- These values are a "sanity check" to check that values we are getting are reasonable
Expand Down Expand Up @@ -192,119 +178,4 @@ SwitchFields.TRANSITION_TIME = 0 --1/10ths of a second
SwitchFields.OPTIONS_MASK = 0x01
SwitchFields.OPTIONS_OVERRIDE = 0x01


SwitchFields.supported_capabilities = {
capabilities.audioMute,
capabilities.audioRecording,
capabilities.audioVolume,
capabilities.battery,
capabilities.batteryLevel,
capabilities.button,
capabilities.cameraPrivacyMode,
capabilities.cameraViewportSettings,
capabilities.colorControl,
capabilities.colorTemperature,
capabilities.energyMeter,
capabilities.fanMode,
capabilities.fanSpeedPercent,
capabilities.hdr,
capabilities.illuminanceMeasurement,
capabilities.imageControl,
capabilities.level,
capabilities.localMediaStorage,
capabilities.mechanicalPanTiltZoom,
capabilities.motionSensor,
capabilities.nightVision,
capabilities.powerMeter,
capabilities.powerConsumptionReport,
capabilities.relativeHumidityMeasurement,
capabilities.sounds,
capabilities.switch,
capabilities.switchLevel,
capabilities.temperatureMeasurement,
capabilities.valve,
capabilities.videoStreamSettings,
capabilities.webrtc,
capabilities.zoneManagement
}

SwitchFields.device_type_attribute_map = {
[SwitchFields.DEVICE_TYPE_ID.LIGHT.ON_OFF] = {
clusters.OnOff.attributes.OnOff
},
[SwitchFields.DEVICE_TYPE_ID.LIGHT.DIMMABLE] = {
clusters.OnOff.attributes.OnOff,
clusters.LevelControl.attributes.CurrentLevel,
clusters.LevelControl.attributes.MaxLevel,
clusters.LevelControl.attributes.MinLevel
},
[SwitchFields.DEVICE_TYPE_ID.LIGHT.COLOR_TEMPERATURE] = {
clusters.OnOff.attributes.OnOff,
clusters.LevelControl.attributes.CurrentLevel,
clusters.LevelControl.attributes.MaxLevel,
clusters.LevelControl.attributes.MinLevel,
clusters.ColorControl.attributes.ColorTemperatureMireds,
clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds,
clusters.ColorControl.attributes.ColorTempPhysicalMinMireds
},
[SwitchFields.DEVICE_TYPE_ID.LIGHT.EXTENDED_COLOR] = {
clusters.OnOff.attributes.OnOff,
clusters.LevelControl.attributes.CurrentLevel,
clusters.LevelControl.attributes.MaxLevel,
clusters.LevelControl.attributes.MinLevel,
clusters.ColorControl.attributes.ColorTemperatureMireds,
clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds,
clusters.ColorControl.attributes.ColorTempPhysicalMinMireds,
clusters.ColorControl.attributes.CurrentHue,
clusters.ColorControl.attributes.CurrentSaturation,
clusters.ColorControl.attributes.CurrentX,
clusters.ColorControl.attributes.CurrentY,
clusters.ColorControl.attributes.ColorMode
},
[SwitchFields.DEVICE_TYPE_ID.ON_OFF_PLUG_IN_UNIT] = {
clusters.OnOff.attributes.OnOff
},
[SwitchFields.DEVICE_TYPE_ID.DIMMABLE_PLUG_IN_UNIT] = {
clusters.OnOff.attributes.OnOff,
clusters.LevelControl.attributes.CurrentLevel,
clusters.LevelControl.attributes.MaxLevel,
clusters.LevelControl.attributes.MinLevel
},
[SwitchFields.DEVICE_TYPE_ID.SWITCH.ON_OFF_LIGHT] = {
clusters.OnOff.attributes.OnOff
},
[SwitchFields.DEVICE_TYPE_ID.SWITCH.DIMMER] = {
clusters.OnOff.attributes.OnOff,
clusters.LevelControl.attributes.CurrentLevel,
clusters.LevelControl.attributes.MaxLevel,
clusters.LevelControl.attributes.MinLevel
},
[SwitchFields.DEVICE_TYPE_ID.SWITCH.COLOR_DIMMER] = {
clusters.OnOff.attributes.OnOff,
clusters.LevelControl.attributes.CurrentLevel,
clusters.LevelControl.attributes.MaxLevel,
clusters.LevelControl.attributes.MinLevel,
clusters.ColorControl.attributes.ColorTemperatureMireds,
clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds,
clusters.ColorControl.attributes.ColorTempPhysicalMinMireds,
clusters.ColorControl.attributes.CurrentHue,
clusters.ColorControl.attributes.CurrentSaturation,
clusters.ColorControl.attributes.CurrentX,
clusters.ColorControl.attributes.CurrentY,
clusters.ColorControl.attributes.ColorMode
},
[SwitchFields.DEVICE_TYPE_ID.GENERIC_SWITCH] = {
clusters.PowerSource.attributes.BatPercentRemaining,
clusters.Switch.events.InitialPress,
clusters.Switch.events.LongPress,
clusters.Switch.events.ShortRelease,
clusters.Switch.events.MultiPressComplete
},
[SwitchFields.DEVICE_TYPE_ID.ELECTRICAL_SENSOR] = {
clusters.ElectricalPowerMeasurement.attributes.ActivePower,
clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported,
clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported
}
}

return SwitchFields
return SwitchFields
Loading
Loading