From 9f4ca54f8885e7061d2a3b8ed2f93a955e187dce Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Sat, 21 Feb 2026 14:37:12 -0600 Subject: [PATCH] map the knob capability onto Hue Tap Dial --- .../profiles/4-button-remote-with-knob.yml | 32 +++++++++++++++++++ .../philips-hue/src/disco/button.lua | 15 ++++++++- .../src/handlers/attribute_emitters.lua | 15 +++++++++ .../handlers/lifecycle_handlers/button.lua | 9 ++++++ .../SmartThings/philips-hue/src/hue/api.lua | 7 ++++ .../philips-hue/src/hue_device_types.lua | 1 + 6 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/philips-hue/profiles/4-button-remote-with-knob.yml diff --git a/drivers/SmartThings/philips-hue/profiles/4-button-remote-with-knob.yml b/drivers/SmartThings/philips-hue/profiles/4-button-remote-with-knob.yml new file mode 100644 index 0000000000..7f66516913 --- /dev/null +++ b/drivers/SmartThings/philips-hue/profiles/4-button-remote-with-knob.yml @@ -0,0 +1,32 @@ +name: 4-button-remote-with-knob +components: + - id: main + capabilities: + - id: button + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + - id: knob + version: 1 + categories: + - name: Button + - id: button2 + capabilities: + - id: button + version: 1 + categories: + - name: Button + - id: button3 + capabilities: + - id: button + version: 1 + categories: + - name: Button + - id: button4 + capabilities: + - id: button + version: 1 + categories: + - name: Button diff --git a/drivers/SmartThings/philips-hue/src/disco/button.lua b/drivers/SmartThings/philips-hue/src/disco/button.lua index 4fcb40ed84..de23692d75 100644 --- a/drivers/SmartThings/philips-hue/src/disco/button.lua +++ b/drivers/SmartThings/philips-hue/src/disco/button.lua @@ -23,11 +23,14 @@ local function _do_update(driver, api_instance, device_service_info, bridge_netw local rid_by_rtype = {} local button_services = {} local num_buttons = 0 + local relative_rotary_services = {} for _, svc in ipairs(device_service_info.services) do if svc.rtype == HueDeviceTypes.BUTTON then num_buttons = num_buttons + 1 table.insert(button_services, svc.rid) + elseif svc.rtype == HueDeviceTypes.RELATIVE_ROTARY then + table.insert(relative_rotary_services, svc.rid) else rid_by_rtype[svc.rtype] = svc.rid end @@ -40,9 +43,16 @@ local function _do_update(driver, api_instance, device_service_info, bridge_netw hue_device_id = device_service_info.id, hue_device_data = device_service_info, num_buttons = num_buttons, + has_relative_rotary = #relative_rotary_services > 0, sensor_list = { power_id = HueDeviceTypes.DEVICE_POWER } } + for idx, rotary_rid in ipairs(relative_rotary_services) do + local rotary_id_key = string.format("relative_rotary%s_id", idx) + button_remote_description[rotary_id_key] = rotary_rid + button_remote_description.sensor_list[rotary_id_key] = HueDeviceTypes.RELATIVE_ROTARY + end + for _, button_rid in ipairs(button_services) do local button_repr, err = api_instance:get_button_by_id(button_rid) if err or not button_repr then @@ -136,7 +146,10 @@ function M.handle_discovered_device( -- For double switch In-Wall Switch module elseif button_description.num_buttons == 2 then button_profile_ref = "two-button" - -- For Philips Hue Dimmer Remote and Tap Dial, which contains 4 buttons + -- For Philips Hue Tap Dial, which contains 4 buttons and a relative rotary + elseif button_description.num_buttons == 4 and button_description.has_relative_rotary then + button_profile_ref = "4-button-remote-with-knob" + -- For Philips Hue Dimmer Remote, which contains 4 buttons elseif button_description.num_buttons == 4 then button_profile_ref = "4-button-remote" else diff --git a/drivers/SmartThings/philips-hue/src/handlers/attribute_emitters.lua b/drivers/SmartThings/philips-hue/src/handlers/attribute_emitters.lua index f274ef37f0..f696ce6001 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/attribute_emitters.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/attribute_emitters.lua @@ -209,6 +209,21 @@ function AttributeEmitters.emit_button_attribute_events(button_device, button_in ) end + -- Handle relative_rotary events (e.g. from the Hue Tap Dial) + local rotary_report = button_info.relative_rotary and button_info.relative_rotary.rotary_report + if rotary_report and rotary_report.rotation then + local rotation = rotary_report.rotation + local direction_sign = (rotation.direction == "clock_wise") and 1 or -1 + -- Scale: 1000 steps = one full rotation = 100 units. Including direction_sign, translate to an integer, scaled between [-100, 100] + local rotate_amount = st_utils.round(st_utils.clamp_value(direction_sign * ((rotation.steps / 1000) * 100), -100, 100)) + log.debug(string.format("emit knob rotateAmount: %s", rotate_amount)) + if rotate_amount ~= 0 then + button_device:emit_event( + capabilities.knob.rotateAmount(rotate_amount, { state_change = true }) + ) + end + end + local button_idx_map = button_device:get_field(Fields.BUTTON_INDEX_MAP) if not button_idx_map then log.error( diff --git a/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/button.lua b/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/button.lua index d10ad5ee65..725c587763 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/button.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/button.lua @@ -90,6 +90,15 @@ function ButtonLifecycleHandlers.added(driver, device, parent_device_id, resourc hue_id_to_device[button_info.power_id] = device end + if button_info.has_relative_rotary then + device.profile.components["main"]:emit_event( + capabilities.knob.supportedAttributes( + { "rotateAmount" }, + { visibility = { displayed = false } } + ) + ) + end + log.debug(st_utils.stringify_table(button_rid_to_index_map, "button index map", true)) device:set_field(Fields.BUTTON_INDEX_MAP, button_rid_to_index_map, { persist = true }) device:set_field(Fields.DEVICE_TYPE, HueDeviceTypes.BUTTON, { persist = true }) diff --git a/drivers/SmartThings/philips-hue/src/hue/api.lua b/drivers/SmartThings/philips-hue/src/hue/api.lua index e05fa3d0a0..da9934fe86 100644 --- a/drivers/SmartThings/philips-hue/src/hue/api.lua +++ b/drivers/SmartThings/philips-hue/src/hue/api.lua @@ -405,6 +405,13 @@ function PhilipsHueApi:get_light_level_by_id(light_level_resource_id) return self:get_rtype_by_rid(HueDeviceTypes.LIGHT_LEVEL, light_level_resource_id) end +---@param relative_rotary_resource_id string +---@return HueResourceResponse? +---@return string? err nil on success +function PhilipsHueApi:get_relative_rotary_by_id(relative_rotary_resource_id) + return self:get_rtype_by_rid(HueDeviceTypes.RELATIVE_ROTARY, relative_rotary_resource_id) +end + ---@param id string ---@param on boolean ---@return { errors: table[], [string]: any }? response json payload in response to the request, nil on error diff --git a/drivers/SmartThings/philips-hue/src/hue_device_types.lua b/drivers/SmartThings/philips-hue/src/hue_device_types.lua index f0c4dec0c7..ed212b7ef6 100644 --- a/drivers/SmartThings/philips-hue/src/hue_device_types.lua +++ b/drivers/SmartThings/philips-hue/src/hue_device_types.lua @@ -7,6 +7,7 @@ local HueDeviceTypes = { LIGHT = "light", LIGHT_LEVEL = "light_level", MOTION = "motion", + RELATIVE_ROTARY = "relative_rotary", TAMPER = "tamper", TEMPERATURE = "temperature", ZIGBEE_CONNECTIVITY = "zigbee_connectivity"