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
67 changes: 53 additions & 14 deletions drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ local PRODUCT_ID = {
SWITCH_2G = 0x0006,
PIR_1_1M = 0x0007,
PIR_2_2M = 0x000A,
ROTARY_DIMMER = 0x000C
}

if version.api < 16 then
Expand All @@ -33,6 +34,12 @@ local function subscribe (device, endpoint_id, cluster_id, attr_id, event_id)
device:send(cluster_base.subscribe(device, endpoint_id, cluster_id, attr_id, event_id))
end

local function is_standalone_device (device)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what does this function imply?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This function is created to separate standalone devices which are from WAASYS series but do not utilize the matter bridge device type. They differ in the endpoint architecture and have to be handled in a different way but they also support manually set functions like adding a child device responsible for remote light control.

This function allows to determine this type of device and eliminate/include them the driver behavior.

for example, standalone devices do not require mapping done in the info_changed function.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So you are saying that these are just devices from Hager, rather than bridged devices from the Hager Bridge? I am curious, but I also think this would be a worthwhile thing to explain in a comment, since is_standalone does not express a lot (in my opinion) about what is being checked, especially when the internals are extremely device specific.

I also suggest that you add some commentary in the function itself to explain what is being checked, since the endpoint id checks are not understandable without Hager-specific context.

@JanJakubiszyn JanJakubiszyn Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, they are just simple Hager devices without the bridge functionality.

I will change the function name to "is_non_bridge_type" and add comments for better clarity
and understanding of the architectural differences between them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah, I'd maybe just check then if the device is 1. an Aggregator device type and 2. is a Matter (not EDGE child) device? Or something like that. If it isn't, then we'd know it is a non-bridge Matter device. I am not sure we need this extremely specific endpoint checking to know whether it is a regular device or not

if switch_utils.get_product_override_field(device, "is_standalone") then
return true
end
end

local function get_parent (driver, device)
return driver:get_device_info(device:get_field(PARENT_ID) or nil)
end
Expand All @@ -51,12 +58,12 @@ local function get_matter_device(device)
return nil
end

local function extract_reported_endpoints(ib)
local function extract_reported_endpoints(ib, device)
local eps = {}
if ib.data and ib.data.elements then
for _, el in ipairs(ib.data.elements) do
local ep_id = el.value
if type(ep_id) == "number" and ep_id ~= 1 and ep_id ~= 2 then
if type(ep_id) == "number" and ep_id ~= 1 and ep_id ~= 2 or is_standalone_device(device) then
table.insert(eps, ep_id)
end
end
Expand Down Expand Up @@ -188,11 +195,16 @@ local function info_changed(driver, device, event, args)
return
end

if is_standalone_device(device) then
device:subscribe()
return
end

if device.profile.id ~= args.old_st_store.profile.id or device.network_type == device_lib.NETWORK_TYPE_CHILD then
local parent = device:get_parent_device()
local matter_device = get_matter_device(parent)
local map = {}
device.thread:call_with_delay(2, function()
local parent = device:get_parent_device()
local matter_device = get_matter_device(parent)
local map = {}
if device:supports_capability(capabilities.button) then
local button_eps = parent:get_field(BUTTON_EPS)
local clean_eps = {}
Expand Down Expand Up @@ -225,7 +237,7 @@ local function info_changed(driver, device, event, args)
subscribe(device, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.OperationalStatus.ID)
subscribe(device, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID, nil)
end
if device.id == matter_device.id then
if not is_standalone_device(device) and device.id == matter_device.id then
parent:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, map, { persist = true })
matter_device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, map, { persist = true })
end
Expand All @@ -234,6 +246,13 @@ local function info_changed(driver, device, event, args)
end

local function device_init (driver, device)
if is_standalone_device(device) then
device:set_find_child(switch_utils.find_child)
device:set_field(PARENT_ID, device.id)
device:set_field(MATTER_DEVICE_ID, device.id)
subscribe(device, nil, clusters.Descriptor.ID, clusters.Descriptor.attributes.PartsList.ID, nil)
return
end
if device.network_type ~= device_lib.NETWORK_TYPE_MATTER then
device.thread:call_with_delay(4, function()
info_changed(driver, device, nil, {
Expand Down Expand Up @@ -265,7 +284,6 @@ local function device_init (driver, device)
end)
end)
end

device:set_component_to_endpoint_fn(switch_utils.component_to_endpoint)
device:set_endpoint_to_component_fn(switch_utils.endpoint_to_component)
end
Expand All @@ -274,21 +292,26 @@ local function handle_descriptor_report(driver, device, ib, response)
local product_id = device.manufacturer_info and device.manufacturer_info.product_id
local parent = get_parent(driver, device)

if not parent or ib.endpoint_id ~= 2 then
return
if is_standalone_device(device) then
if ib.endpoint_id ~= 0 then
return
end
else
if ib.endpoint_id ~= 2 or not parent then
return
end
end

local matter_device = get_matter_device(device)
local matter_device = get_matter_device(device) or device
local new_eps = extract_reported_endpoints(ib, device) or {}

local new_eps = extract_reported_endpoints(ib) or {}
table.sort(new_eps)
local removed_eps, added_eps = detect_endpoint_changes(device, new_eps)

local removed_eps, added_eps = detect_endpoint_changes(device, new_eps)
device:set_field(ACTIVE_EPS, new_eps, { persist = true })

for _, ep_id in ipairs(added_eps or {}) do
subscribe(parent, ep_id, clusters.Descriptor.ID, clusters.Descriptor.attributes.DeviceTypeList.ID, nil)

if product_id == PRODUCT_ID.SWITCH_1G and ep_id == 3 then
matter_device:try_update_metadata({ profile = "light-binary" })
elseif product_id == PRODUCT_ID.SWITCH_2G then
Expand Down Expand Up @@ -347,6 +370,7 @@ local function device_type_handler (driver, device, ib)
end

local parent = get_parent(driver, device)

if not parent then
return
end
Expand All @@ -356,10 +380,19 @@ local function device_type_handler (driver, device, ib)
local new_btn_eps = {}
local ep_id = ib.endpoint_id
local value = ib.data.elements
local reports_dimmable = false

for _, element in ipairs(value) do

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
for _, element in ipairs(value) do
for _, element in ipairs(value or {}) do

local device_type_field = element.elements.device_type
if device_type_field and device_type_field.value == fields.DEVICE_TYPE_ID.LIGHT.DIMMABLE then
reports_dimmable = true
end
end

for _, v in ipairs(stored_btn_eps) do
table.insert(new_btn_eps, v)
end

for _, element in ipairs(value) do
local device_type_field = element.elements.device_type
local device_type_id = device_type_field and device_type_field.value
Expand Down Expand Up @@ -396,11 +429,17 @@ local function device_type_handler (driver, device, ib)
driver:try_delete_device(ep_id)
end
return
elseif ep_id == 1 and device.manufacturer_info.product_id == PRODUCT_ID.ROTARY_DIMMER then
if not reports_dimmable then
device:try_update_metadata({ profile = "light-binary" })
end
end
create_child(driver, parent, { ep_id }, 1, assign_profile_for_endpoint(device_type_id))
elseif device_type_id == fields.DEVICE_TYPE_ID.LIGHT.DIMMABLE then
if ep_id == 4 and device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_1G then
return
elseif ep_id == 1 and device.manufacturer_info.product_id == PRODUCT_ID.ROTARY_DIMMER then
device:try_update_metadata({ profile = "light-level" })
end
create_child(driver, parent, { ep_id }, 1, assign_profile_for_endpoint(device_type_id))
elseif device_type_id == fields.DEVICE_TYPE_ID.WINDOW_COVERING then
Expand Down Expand Up @@ -433,7 +472,7 @@ local function do_configure (driver, device)
device:try_update_metadata({ profile = "4-button" })
elseif #bt_eps == 2 then
device:try_update_metadata({ profile = "2-button" })
elseif #lvl_eps > 0 and product_id == PRODUCT_ID.SWITCH_1G then
elseif #lvl_eps > 0 and product_id == PRODUCT_ID.SWITCH_1G or is_standalone_device(device) then
device:try_update_metadata({ profile = "light-level" })
end
device:set_field(BUTTON_EPS, bt_eps, { persist = true })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ SwitchFields.vendor_overrides = {
[0x0006] = { needs_hager_subdriver = true }, -- Hager HBnet 2g switch
[0x0007] = { needs_hager_subdriver = true }, -- Hager HBnet PIR 1.1M
[0x000A] = { needs_hager_subdriver = true }, -- Hager HBnet PIR 2.2M
[0x000C] = { needs_hager_subdriver = true, is_standalone = true }, -- Hager Rotary
},
[0x130A] = { -- EVE_MANUFACTURER_ID
[0x0050] = { needs_eve_energy_subdriver = true }, -- Eve Energy EU
Expand Down
Loading
Loading