Skip to content

Commit efd7fe1

Browse files
hcarter-775aleclorimer
authored andcommitted
Merge pull request #3215 from JanJakubiszyn/Hager_waasys_rotrary_dim
WWSTCERT-13900 Hager Rotary dimmer, connectable
1 parent 6aa02ae commit efd7fe1

4 files changed

Lines changed: 239 additions & 13 deletions

File tree

‎drivers/SmartThings/matter-switch/fingerprints.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,11 @@ matterManufacturer:
10291029
vendorId: 0x1285
10301030
productId: 0x000A
10311031
deviceProfileName: matter-bridge
1032+
- id: "4741/12"
1033+
deviceLabel: Hager Rotary Dimmer
1034+
vendorId: 0x1285
1035+
productId: 0x000C
1036+
deviceProfileName: light-level
10321037
# HAOJAI
10331038
- id: "5530/4113"
10341039
deviceLabel: HAOJAI Smart Switch 3-key

‎drivers/SmartThings/matter-switch/src/sub_drivers/hager/init.lua‎

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ local PRODUCT_ID = {
2323
SWITCH_2G = 0x0006,
2424
PIR_1_1M = 0x0007,
2525
PIR_2_2M = 0x000A,
26+
ROTARY_DIMMER = 0x000C
2627
}
2728

2829
if version.api < 16 then
@@ -33,6 +34,21 @@ local function subscribe (device, endpoint_id, cluster_id, attr_id, event_id)
3334
device:send(cluster_base.subscribe(device, endpoint_id, cluster_id, attr_id, event_id))
3435
end
3536

37+
-- The Rotary Dimmer (0x000C) is a non-bridge type Hager Matter device, which
38+
-- additionally supports manual mode transitions that enable/disable
39+
-- functionality via a physical knob. Unlike bridge-type Hager devices, it has
40+
-- no Aggregator device type on its root endpoint. This non-bridge device
41+
-- requires explicit handling to: (1) process endpoint EP1 (normally filtered
42+
-- out for bridge devices), (2) manage its own device type changes without a
43+
-- parent-child hierarchy, endpoint mapping and event routing, (3) support
44+
-- dynamic profile switching triggered by user interaction, as with the knob
45+
-- mode switching from Dimmer to only On/Off.
46+
local function is_non_bridge_type(device)
47+
if switch_utils.get_product_override_field(device, "is_non_bridge_type") then
48+
return true
49+
end
50+
end
51+
3652
local function get_parent (driver, device)
3753
return driver:get_device_info(device:get_field(PARENT_ID) or nil)
3854
end
@@ -51,12 +67,14 @@ local function get_matter_device(device)
5167
return nil
5268
end
5369

54-
local function extract_reported_endpoints(ib)
70+
local function extract_reported_endpoints(ib, device)
5571
local eps = {}
5672
if ib.data and ib.data.elements then
5773
for _, el in ipairs(ib.data.elements) do
5874
local ep_id = el.value
59-
if type(ep_id) == "number" and ep_id ~= 1 and ep_id ~= 2 then
75+
-- For bridge architecture HBnet devices: filter out endpoints 1-2 (reserved for hub infrastructure)
76+
-- For simple non-bridge type HBnet devices: accept all endpoints including 1-2 (active application endpoints)
77+
if type(ep_id) == "number" and ep_id ~= 1 and ep_id ~= 2 or is_non_bridge_type(device) then
6078
table.insert(eps, ep_id)
6179
end
6280
end
@@ -188,6 +206,11 @@ local function info_changed(driver, device, event, args)
188206
return
189207
end
190208

209+
if is_non_bridge_type(device) then
210+
device:subscribe()
211+
return
212+
end
213+
191214
if device.profile.id ~= args.old_st_store.profile.id or device.network_type == device_lib.NETWORK_TYPE_CHILD then
192215
device.thread:call_with_delay(3, function()
193216
local parent = device:get_parent_device()
@@ -225,7 +248,7 @@ local function info_changed(driver, device, event, args)
225248
subscribe(device, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.OperationalStatus.ID)
226249
subscribe(device, nil, clusters.WindowCovering.ID, clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths.ID, nil)
227250
end
228-
if device.id == matter_device.id then
251+
if not is_non_bridge_type(device) and device.id == matter_device.id then
229252
parent:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, map, { persist = true })
230253
matter_device:set_field(fields.COMPONENT_TO_ENDPOINT_MAP, map, { persist = true })
231254
end
@@ -234,6 +257,13 @@ local function info_changed(driver, device, event, args)
234257
end
235258

236259
local function device_init (driver, device)
260+
if is_non_bridge_type(device) then
261+
device:set_find_child(switch_utils.find_child)
262+
device:set_field(PARENT_ID, device.id)
263+
device:set_field(MATTER_DEVICE_ID, device.id)
264+
subscribe(device, nil, clusters.Descriptor.ID, clusters.Descriptor.attributes.PartsList.ID, nil)
265+
return
266+
end
237267
if device.network_type ~= device_lib.NETWORK_TYPE_MATTER then
238268
device.thread:call_with_delay(4, function()
239269
info_changed(driver, device, nil, {
@@ -265,7 +295,6 @@ local function device_init (driver, device)
265295
end)
266296
end)
267297
end
268-
269298
device:set_component_to_endpoint_fn(switch_utils.component_to_endpoint)
270299
device:set_endpoint_to_component_fn(switch_utils.endpoint_to_component)
271300
end
@@ -274,21 +303,26 @@ local function handle_descriptor_report(driver, device, ib, response)
274303
local product_id = device.manufacturer_info and device.manufacturer_info.product_id
275304
local parent = get_parent(driver, device)
276305

277-
if not parent or ib.endpoint_id ~= 2 then
278-
return
306+
if is_non_bridge_type(device) then
307+
if ib.endpoint_id ~= 0 then
308+
return
309+
end
310+
else
311+
if ib.endpoint_id ~= 2 or not parent then
312+
return
313+
end
279314
end
280315

281-
local matter_device = get_matter_device(device)
316+
local matter_device = get_matter_device(device) or device
317+
local new_eps = extract_reported_endpoints(ib, device) or {}
282318

283-
local new_eps = extract_reported_endpoints(ib) or {}
284319
table.sort(new_eps)
285-
local removed_eps, added_eps = detect_endpoint_changes(device, new_eps)
286320

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

289324
for _, ep_id in ipairs(added_eps or {}) do
290325
subscribe(parent, ep_id, clusters.Descriptor.ID, clusters.Descriptor.attributes.DeviceTypeList.ID, nil)
291-
292326
if product_id == PRODUCT_ID.SWITCH_1G and ep_id == 3 then
293327
matter_device:try_update_metadata({ profile = "light-binary" })
294328
elseif product_id == PRODUCT_ID.SWITCH_2G then
@@ -347,6 +381,7 @@ local function device_type_handler (driver, device, ib)
347381
end
348382

349383
local parent = get_parent(driver, device)
384+
350385
if not parent then
351386
return
352387
end
@@ -356,10 +391,19 @@ local function device_type_handler (driver, device, ib)
356391
local new_btn_eps = {}
357392
local ep_id = ib.endpoint_id
358393
local value = ib.data.elements
394+
local reports_dimmable = false
395+
396+
for _, element in ipairs(value or {}) do
397+
local device_type_field = element.elements.device_type
398+
if device_type_field and device_type_field.value == fields.DEVICE_TYPE_ID.LIGHT.DIMMABLE then
399+
reports_dimmable = true
400+
end
401+
end
359402

360403
for _, v in ipairs(stored_btn_eps) do
361404
table.insert(new_btn_eps, v)
362405
end
406+
363407
for _, element in ipairs(value) do
364408
local device_type_field = element.elements.device_type
365409
local device_type_id = device_type_field and device_type_field.value
@@ -396,11 +440,17 @@ local function device_type_handler (driver, device, ib)
396440
driver:try_delete_device(ep_id)
397441
end
398442
return
443+
elseif ep_id == 1 and device.manufacturer_info.product_id == PRODUCT_ID.ROTARY_DIMMER then
444+
if not reports_dimmable then
445+
device:try_update_metadata({ profile = "light-binary" })
446+
end
399447
end
400448
create_child(driver, parent, { ep_id }, 1, assign_profile_for_endpoint(device_type_id))
401449
elseif device_type_id == fields.DEVICE_TYPE_ID.LIGHT.DIMMABLE then
402450
if ep_id == 4 and device.manufacturer_info.product_id == PRODUCT_ID.SWITCH_1G then
403451
return
452+
elseif ep_id == 1 and device.manufacturer_info.product_id == PRODUCT_ID.ROTARY_DIMMER then
453+
device:try_update_metadata({ profile = "light-level" })
404454
end
405455
create_child(driver, parent, { ep_id }, 1, assign_profile_for_endpoint(device_type_id))
406456
elseif device_type_id == fields.DEVICE_TYPE_ID.WINDOW_COVERING then
@@ -433,7 +483,7 @@ local function do_configure (driver, device)
433483
device:try_update_metadata({ profile = "4-button" })
434484
elseif #bt_eps == 2 then
435485
device:try_update_metadata({ profile = "2-button" })
436-
elseif #lvl_eps > 0 and product_id == PRODUCT_ID.SWITCH_1G then
486+
elseif #lvl_eps > 0 and product_id == PRODUCT_ID.SWITCH_1G or is_non_bridge_type(device) then
437487
device:try_update_metadata({ profile = "light-level" })
438488
end
439489
device:set_field(BUTTON_EPS, bt_eps, { persist = true })

‎drivers/SmartThings/matter-switch/src/switch_utils/fields.lua‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ SwitchFields.vendor_overrides = {
125125
[0x0006] = { needs_hager_subdriver = true }, -- Hager HBnet 2g switch
126126
[0x0007] = { needs_hager_subdriver = true }, -- Hager HBnet PIR 1.1M
127127
[0x000A] = { needs_hager_subdriver = true }, -- Hager HBnet PIR 2.2M
128+
[0x000C] = { needs_hager_subdriver = true, is_non_bridge_type = true }, -- Hager Rotary Dimmer
128129
},
129130
[0x130A] = { -- EVE_MANUFACTURER_ID
130131
[0x0050] = { needs_eve_energy_subdriver = true }, -- Eve Energy EU

0 commit comments

Comments
 (0)