@@ -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
2829if 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 ))
3435end
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+
3652local function get_parent (driver , device )
3753 return driver :get_device_info (device :get_field (PARENT_ID ) or nil )
3854end
@@ -51,12 +67,14 @@ local function get_matter_device(device)
5167 return nil
5268end
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)
234257end
235258
236259local 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 )
271300end
@@ -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 })
0 commit comments