Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d019056
Added three WISTAR Smart Vertical Blind Motors
zhouchengyu-cell Oct 23, 2025
165feec
sonos: Retry ssdp task creation to fix log spam and spinning
NoahCornell Nov 14, 2025
5d997ba
Merge pull request #2490 from zhouchengyu-cell/WISTAR-Smart-Vertical-…
greens Nov 17, 2025
c178696
WWSTCERT-8722/8725 [Aqara] add Aqara Wireless Remote Switch H1(Single…
seojune79 Nov 18, 2025
2994e5e
WWSTCERT-8756 Add support to frient air quality sensor (#2439)
marcintyminski Nov 18, 2025
713c658
WWSTCERT-9021 Aqara Presence Multi-Sensor FP300
greens Nov 19, 2025
ff41419
Merge pull request #2556 from SmartThingsCommunity/sonos_ssdp_spam
NoahCornell Nov 19, 2025
e628b47
Matter Window Covering: Remove default tilt preset (#2565)
nickolas-deboom Nov 19, 2025
b828a5a
Matter Switch: Add more reliability to device type checking (#2564)
hcarter-775 Nov 19, 2025
1395f67
Fix webrtc capability inclusion requirements (#2568)
nickolas-deboom Nov 19, 2025
2389712
Matter Camera: Check for server cluster type (#2570)
nickolas-deboom Nov 24, 2025
d957e69
Matter Switch: Simplify component to endpoint mapping logic (#2560)
hcarter-775 Nov 24, 2025
a4e1904
Merge pull request #2567 from SmartThingsCommunity/new_device/WWSTCER…
greens Nov 24, 2025
5ca5f9c
WWSTCERT-8445 Kwikset Halo Select Plus (#2498)
greens Nov 24, 2025
d7460ea
WWSTCERT-9074 Cync Fan Switch
greens Nov 24, 2025
2e7ffb9
reorder the matter thermostat directory structure (#2548)
hcarter-775 Nov 24, 2025
7355666
WWSTCERT-9021 Aqara Presence Multi-Sensor FP300 (update profile)
tpmanley Nov 25, 2025
5b9ff3a
deprecate matter- prefixed profiles (#2583)
hcarter-775 Nov 25, 2025
dc132c9
Merge pull request #2580 from SmartThingsCommunity/feature/fp300
tpmanley Nov 25, 2025
fb88d26
WWSTCERT-9117 OSRAM MATTER PLUG UK
greens Nov 25, 2025
5aeb76d
WWSTCERT-9113 LUX TQX Smart Thermostat
greens Nov 25, 2025
32fe776
Merge pull request #2576 from SmartThingsCommunity/new_device/WWSTCER…
greens Nov 26, 2025
b69e1d5
WWSTCERT-8802 Inovelli: adding support for vzm30 (zigbee on/off) and …
InovelliUSA Nov 26, 2025
c0f12f8
WWSTCERT-9105 Meross Smart Presence Sensor (Thread) (#2578)
greens Nov 26, 2025
37691d5
WWSTCERT-8360 Inovelli VZW32-SN: Adding support for this device for t…
InovelliUSA Nov 26, 2025
641a5a7
[PLM251120-09263][PLM251120-09698] Hide some specific events in history
FrankSpringfield Nov 28, 2025
974ea61
Merge pull request #2589 from FrankSpringfield/main
greens Dec 1, 2025
ef928eb
Merge pull request #2585 from SmartThingsCommunity/new_device/WWSTCER…
greens Dec 1, 2025
961bc1b
Merge pull request #2586 from SmartThingsCommunity/new_device/WWSTCER…
greens Dec 1, 2025
ca52745
WWSTCERT-9060 Resideo Valve Controller
greens Dec 1, 2025
d978bac
Merge pull request #2595 from SmartThingsCommunity/new_device/WWSTCER…
greens Dec 1, 2025
48c52ab
Merge branch 'beta' into main
greens Dec 1, 2025
9380f9c
Revert "Merge branch 'beta' into main"
greens Dec 1, 2025
afbb8b3
fix timing issue with aqara test
greens Dec 1, 2025
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
6 changes: 6 additions & 0 deletions drivers/SmartThings/matter-lock/fingerprints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ matterManufacturer:
vendorId: 0x1533
productId: 0x0012
deviceProfileName: lock-user-pin-battery
#Kwikset
- id: "5153/66"
deviceLabel: Kwikset Halo Select Plus
vendorId: 0x1421
productId: 0x0042
deviceProfileName: lock-user-pin-battery
#Level
- id: "4767/1"
deviceLabel: Level Lock Plus (Matter)
Expand Down
3 changes: 2 additions & 1 deletion drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ local NEW_MATTER_LOCK_PRODUCTS = {
{0x135D, 0x00B0}, -- Nuki, Smart Lock
{0x15F2, 0x0001}, -- Viomi, AiSafety Smart Lock E100
{0x158B, 0x0001}, -- Deasino, DS-MT01
{0x10E1, 0x2002} -- VDA
{0x10E1, 0x2002}, -- VDA
{0x1421, 0x0042}, -- Kwikset Halo Select Plus
}

local battery_support = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Deprecated: do not use this profile for device fingerprinting.
name: matter-motion-battery-illuminance
components:
- id: main
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Deprecated: do not use this profile for device fingerprinting.
name: matter-motion-battery
components:
- id: main
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Deprecated: do not use this profile for device fingerprinting.
name: matter-motion-batteryLevel-illuminance
components:
- id: main
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Deprecated: do not use this profile for device fingerprinting.
name: matter-motion-batteryLevel
components:
- id: main
Expand Down
35 changes: 35 additions & 0 deletions drivers/SmartThings/matter-switch/fingerprints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,41 @@ matterManufacturer:
vendorId: 0x1189
productId: 0x0AC3
deviceProfileName: light-color-level
- id: "4489/61444"
deviceLabel: OSRAM MATTER PLUG UK
vendorId: 0x1189
productId: 0xF004
deviceProfileName: plug-binary
- id: "4489/61443"
deviceLabel: OSRAM MATTER PLUG EU WH
vendorId: 0x1189
productId: 0xF003
deviceProfileName: plug-binary
- id: "4489/61441"
deviceLabel: OSRAM MATTER CLASSIC A 60W
vendorId: 0x1189
productId: 0xF001
deviceProfileName: light-color-level
- id: "4489/2353"
deviceLabel: SMART MATTER FLOORCORN200 MGC WT
vendorId: 0x1189
productId: 0x0931
deviceProfileName: light-color-level
- id: "4489/2350"
deviceLabel: SMART MATTER FLOORCORN140 MGC BK
vendorId: 0x1189
productId: 0x092E
deviceProfileName: light-color-level
- id: "4489/2352"
deviceLabel: SMART MATTER FLOORCORN140 MGC WT
vendorId: 0x1189
productId: 0x0930
deviceProfileName: light-color-level
- id: "4489/2351"
deviceLabel: SMART MATTER FLOORCORN200 MGC BK
vendorId: 0x1189
productId: 0x092F
deviceProfileName: light-color-level
#Shelly
- id: "5264/1"
deviceLabel: Shelly Plug S MTR Gen3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,15 +396,13 @@ 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, capability_ids = {}, {}
local attribute_ids = {}
for _, attr in ipairs(ib.data.elements) do
if attr.value == clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID then
status_light_enabled_present = true
table.insert(capability_ids, capabilities.switch.ID)
table.insert(attribute_ids, clusters.CameraAvStreamManagement.attributes.StatusLightEnabled.ID)
elseif attr.value == clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID then
status_light_brightness_present = true
table.insert(capability_ids, capabilities.mode.ID)
table.insert(attribute_ids, clusters.CameraAvStreamManagement.attributes.StatusLightBrightness.ID)
end
end
Expand All @@ -413,7 +411,6 @@ function CameraAttributeHandlers.camera_av_stream_management_attribute_list_hand
endpoint_id = ib.endpoint_id,
cluster_id = ib.cluster_id,
attribute_ids = attribute_ids,
capability_ids = capability_ids
}
device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, component_map, {persist=true})
camera_cfg.match_profile(device, status_light_enabled_present, status_light_brightness_present)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ function CameraDeviceConfiguration.match_profile(device, status_light_enabled_pr
local microphone_component_capabilities = {}
local doorbell_component_capabilities = {}

local function has_server_cluster_type(cluster)
return cluster.cluster_type == "SERVER" or cluster.cluster_type == "BOTH"
end

local camera_endpoints = switch_utils.get_endpoints_by_device_type(device, fields.DEVICE_TYPE_ID.CAMERA)
if #camera_endpoints > 0 then
if #device:get_endpoints(clusters.WebRTCTransportProvider.ID, {cluster_type = "SERVER"}) > 0 and
#device:get_endpoints(clusters.WebRTCTransportRequestor.ID, {cluster_type = "CLIENT"}) > 0 then
table.insert(main_component_capabilities, capabilities.webrtc.ID)
end
local camera_ep = switch_utils.get_endpoint_info(device, camera_endpoints[1])
for _, ep_cluster in pairs(camera_ep.clusters or {}) do
if ep_cluster.cluster_id == clusters.CameraAvStreamManagement.ID then
if ep_cluster.cluster_id == clusters.CameraAvStreamManagement.ID and has_server_cluster_type(ep_cluster) then
local clus_has_feature = function(feature_bitmap)
return clusters.CameraAvStreamManagement.are_features_supported(feature_bitmap, ep_cluster.feature_map)
end
Expand Down Expand Up @@ -92,7 +92,7 @@ function CameraDeviceConfiguration.match_profile(device, status_light_enabled_pr
if clus_has_feature(clusters.CameraAvStreamManagement.types.Feature.NIGHT_VISION) then
table.insert(main_component_capabilities, capabilities.nightVision.ID)
end
elseif ep_cluster.cluster_id == clusters.CameraAvSettingsUserLevelManagement.ID then
elseif ep_cluster.cluster_id == clusters.CameraAvSettingsUserLevelManagement.ID and has_server_cluster_type(ep_cluster) then
local clus_has_feature = function(feature_bitmap)
return clusters.CameraAvSettingsUserLevelManagement.are_features_supported(feature_bitmap, ep_cluster.feature_map)
end
Expand All @@ -102,10 +102,13 @@ function CameraDeviceConfiguration.match_profile(device, status_light_enabled_pr
table.insert(main_component_capabilities, capabilities.mechanicalPanTiltZoom.ID)
end
table.insert(main_component_capabilities, capabilities.videoStreamSettings.ID)
elseif ep_cluster.cluster_id == clusters.ZoneManagement.ID then
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.OccupancySensing.ID then
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) and
#device:get_endpoints(clusters.WebRTCTransportRequestor.ID, {cluster_type = "CLIENT"}) > 0 then
table.insert(main_component_capabilities, capabilities.webrtc.ID)
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ function CameraUtils.update_camera_component_map(device)
clusters.CameraAvStreamManagement.attributes.MicrophoneMaxLevel.ID,
clusters.CameraAvStreamManagement.attributes.MicrophoneMinLevel.ID,
},
capability_ids = {
capabilities.audioMute.ID,
capabilities.audioVolume.ID,
}
}
end
if CameraUtils.feature_supported(device, clusters.CameraAvStreamManagement.ID, clusters.CameraAvStreamManagement.types.Feature.VIDEO) then
Expand All @@ -51,10 +47,6 @@ function CameraUtils.update_camera_component_map(device)
clusters.CameraAvStreamManagement.attributes.SpeakerMaxLevel.ID,
clusters.CameraAvStreamManagement.attributes.SpeakerMinLevel.ID,
},
capability_ids = {
capabilities.audioMute.ID,
capabilities.audioVolume.ID,
}
}
end
device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, component_map, {persist = true})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function SwitchDeviceConfiguration.assign_profile_for_onoff_ep(device, server_on
-- per spec, the Switch device types support OnOff as CLIENT, though some vendors break spec and support it as SERVER.
local primary_dt_id = switch_utils.find_max_subset_device_type(ep_info, fields.DEVICE_TYPE_ID.LIGHT)
or switch_utils.find_max_subset_device_type(ep_info, fields.DEVICE_TYPE_ID.SWITCH)
or ep_info.device_types[1] and ep_info.device_types[1].device_type_id
or switch_utils.find_primary_device_type(ep_info)

local generic_profile = fields.device_type_profile_map[primary_dt_id]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ SwitchFields.CURRENT_HUESAT_ATTR_MAX = 254

SwitchFields.DEVICE_TYPE_ID = {
AGGREGATOR = 0x000E,
BRIDGED_NODE = 0x0013,
CAMERA = 0x0142,
CHIME = 0x0146,
DIMMABLE_PLUG_IN_UNIT = 0x010B,
Expand Down
89 changes: 39 additions & 50 deletions drivers/SmartThings/matter-switch/src/switch_utils/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,33 @@ function utils.device_type_supports_button_switch_combination(device, endpoint_i
return utils.tbl_contains(dimmable_eps, endpoint_id)
end

-- Some devices report multiple device types which are a subset of
-- a superset device type (Ex. Dimmable Light is a superset of On/Off Light).
-- We should map to the largest superset device type supported.
-- This can be done by matching to the device type with the highest ID
--- Some devices report multiple device types which are a subset of a superset
--- device type (Ex. Dimmable Light is a superset of On/Off Light). We should map
--- to the largest superset device type supported.
--- This can be done by matching to the device type with the highest ID
--- note: that superset device types have a higher ID than those of their subset
--- is heuristic and could therefore break in the future, were the spec expanded
function utils.find_max_subset_device_type(ep, device_type_set)
if ep.endpoint_id == 0 then return end -- EP-scoped device types not permitted on Root Node
local primary_dt_id = ep.device_types[1] and ep.device_types[1].device_type_id
if utils.tbl_contains(device_type_set, primary_dt_id) then
for _, dt in ipairs(ep.device_types) do
-- only device types in the subset should be considered.
if utils.tbl_contains(device_type_set, dt.device_type_id) then
primary_dt_id = math.max(primary_dt_id, dt.device_type_id)
end
local primary_dt_id = -1
for _, dt in ipairs(ep.device_types) do
-- only device types in the subset should be considered.
if utils.tbl_contains(device_type_set, dt.device_type_id) then
primary_dt_id = math.max(primary_dt_id, dt.device_type_id)
end
end
return (primary_dt_id > 0) and primary_dt_id or nil
end

--- Lights and Switches are Device Types that have Superset-style functionality
--- For all other device types, this function should be used to identify the primary device type
function utils.find_primary_device_type(ep_info)
for _, dt in ipairs(ep_info.device_types) do
if dt.device_type_id ~= fields.DEVICE_TYPE_ID.BRIDGED_NODE then
-- if this is not a bridged node, return the first device type seen
return dt.device_type_id
end
return primary_dt_id
end
return nil
end

--- find_default_endpoint is a helper function to handle situations where
Expand Down Expand Up @@ -168,60 +178,40 @@ function utils.component_to_endpoint(device, component)
return utils.find_default_endpoint(device)
end

--- An extension of the library function endpoint_to_component, to support a mapping scheme
--- that includes cluster and attribute id's so that we can use multiple components for a
--- single endpoint.
--- An extension of the library function endpoint_to_component, used to support a mapping scheme
--- that optionally includes cluster and attribute ids so that multiple components can be mapped
--- to a single endpoint.
---
--- @param device any a Matter device object
--- @param opts number|table either is an ep_id or a table { endpoint_id, capability_id }
--- @param ep_info number|table either an ep_id or a table { endpoint_id, optional(cluster_id), optional(attribute_id) }
--- where cluster_id is required for an attribute_id to be handled.
--- @return string component
function utils.endpoint_to_component(device, opts)
local ep_info = {}
if type(opts) == "number" then
ep_info.endpoint_id = opts
elseif type(opts) == "table" then
if opts.endpoint_info then
ep_info = opts.endpoint_info
else
ep_info = {
endpoint_id = opts.endpoint_id,
cluster_id = opts.cluster_id,
attribute_id = opts.attribute_id
}
end
function utils.endpoint_to_component(device, ep_info)
if type(ep_info) == "number" then
ep_info = { endpoint_id = ep_info }
end
for component, map_info in pairs(device:get_field(fields.COMPONENT_TO_ENDPOINT_MAP) or {}) do
if type(map_info) == "number" and map_info == ep_info.endpoint_id then
return component
elseif type(map_info) == "table" and map_info.endpoint_id == ep_info.endpoint_id then
if (not map_info.cluster_id or (map_info.cluster_id == ep_info.cluster_id
and utils.tbl_contains(map_info.attribute_ids, ep_info.attribute_id)))
and (not opts.capability_id or utils.tbl_contains(map_info.capability_ids, opts.capability_id)) then
elseif type(map_info) == "table" and map_info.endpoint_id == ep_info.endpoint_id
and (not map_info.cluster_id or (map_info.cluster_id == ep_info.cluster_id
and (not map_info.attribute_ids or utils.tbl_contains(map_info.attribute_ids, ep_info.attribute_id)))) then
return component
end
end
end
return "main"
end

--- An extension of the library function emit_event_for_endpoint, to support devices with
--- multiple components defined for the same endpoint, since they can't be easily
--- differentiated based on a simple endpoint id to component mapping, but we can extend
--- this mapping to include the cluster and attribute id's so that we know which component
--- to route events to.
--- An extension of the library function emit_event_for_endpoint, used to support devices with
--- multiple components mapped to the same endpoint. This is handled by extending the parameters to optionally
--- include a cluster id and attribute id for more specific routing
---
--- @param device any a Matter device object
--- @param ep_info number|table endpoint_id or ib (includes endpoint_id, cluster_id, attribute_id)
--- @param ep_info number|table endpoint_id or an ib (the ib data includes endpoint_id, cluster_id, and attribute_id fields)
--- @param event any a capability event object
function utils.emit_event_for_endpoint(device, ep_info, event)
if type(ep_info) == "number" then
ep_info = { endpoint_id = ep_info }
elseif type(ep_info) == "table" then
ep_info = {
endpoint_id = ep_info.endpoint_id,
cluster_id = ep_info.cluster_id,
attribute_id = ep_info.attribute_id
}
end
if device:get_field(fields.IS_PARENT_CHILD_DEVICE) then
local child = utils.find_child(device, ep_info.endpoint_id)
Expand All @@ -230,8 +220,7 @@ function utils.emit_event_for_endpoint(device, ep_info, event)
return
end
end
local opts = { endpoint_info = ep_info, capability_id = event.capability.ID }
local comp_id = utils.endpoint_to_component(device, opts)
local comp_id = utils.endpoint_to_component(device, ep_info)
local comp = device.profile.components[comp_id]
device:emit_component_event(comp, event)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ local function update_device_profile()
{
"main",
{
"webrtc",
"videoCapture2",
"cameraViewportSettings",
"localMediaStorage",
Expand All @@ -168,6 +167,7 @@ local function update_device_profile()
"mechanicalPanTiltZoom",
"videoStreamSettings",
"zoneManagement",
"webrtc",
"motionSensor",
"sounds",
}
Expand Down
5 changes: 5 additions & 0 deletions drivers/SmartThings/matter-thermostat/fingerprints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ matterManufacturer:
vendorId: 0x1206
productId: 0x0001
deviceProfileName: thermostat-nostate-nobattery
- id: "4614/17"
deviceLabel: LUX TQX Smart Thermostat
vendorId: 0x1206
productId: 0x0011
deviceProfileName: thermostat-humidity-nostate-nobattery
#Meross
- id: "4933/57345"
deviceLabel: Smart Wi-Fi Thermostat
Expand Down
Loading
Loading