Skip to content

Commit

Permalink
Support for compound devices and "grandchildren"
Browse files Browse the repository at this point in the history
This refactor furthers the generalizations done previously for devices
that provide multiple services to allow for not only mapping of services
to capabilities or components, but also multiple device records.

This allows for us to support devices such as the Dymera up/down light.

Other changes:

- I found myself frequently introducing bugs related to using the Hue Bridge ID
(MAC Address) as a Device ID, and the Bridge Device ID as a Hue Bridge ID. We renamed
all instances of `bridge_id` referencing the MAC to `bridge_network_id` to try and
alleviate the confusion.

- Button support is further generalized to support two-button models, which covers the
In-Wall Switch Module in the 2-gang configuration.
  • Loading branch information
dljsjr committed May 3, 2024
1 parent 2ace2f2 commit a3be7e3
Show file tree
Hide file tree
Showing 28 changed files with 546 additions and 282 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: HueSmartButton
name: single-button
components:
- id: main
capabilities:
Expand Down
18 changes: 18 additions & 0 deletions drivers/SmartThings/philips-hue/profiles/two-button.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: two-button
components:
- id: main
capabilities:
- id: button
version: 1
- id: battery
version: 1
- id: refresh
version: 1
categories:
- name: RemoteController
- id: button2
capabilities:
- id: button
version: 1
categories:
- name: RemoteController
47 changes: 25 additions & 22 deletions drivers/SmartThings/philips-hue/src/disco/button.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local log = require "logjam"
local log = require "log"
local socket = require "cosock".socket
local st_utils = require "st.utils"

Expand All @@ -7,14 +7,14 @@ local HueDeviceTypes = require "hue_device_types"
---@class DiscoveredButtonHandler: DiscoveredChildDeviceHandler
local M = {}

---@param driver HueDriver
---@param api_instance PhilipsHueApi
---@param device_service_info HueDeviceInfo
---@param bridge_id string
---@param resource_id string
---@param bridge_network_id string
---@param cache table?
---@return table<string,any>? description nil on error
---@return string? err nil on success
local function _do_update(api_instance, device_service_info, bridge_id, resource_id, cache)
local function _do_update(driver, api_instance, device_service_info, bridge_network_id, cache)
local rid_by_rtype = {}
local button_services = {}
local num_buttons = 0
Expand All @@ -28,12 +28,12 @@ local function _do_update(api_instance, device_service_info, bridge_id, resource
end
end

local bridge_device = driver:get_device_by_dni(bridge_network_id) --[[@as HueBridgeDevice]]
local button_remote_description = {
hue_provided_name = device_service_info.metadata.name,
parent_device_id = bridge_id,
parent_device_id = bridge_device.id,
hue_device_id = device_service_info.id,
hue_device_data = device_service_info,
id = resource_id,
num_buttons = num_buttons
}

Expand All @@ -48,7 +48,7 @@ local function _do_update(api_instance, device_service_info, bridge_id, resource
button_remote_description[button_key] = button_repr.data[1].button
button_remote_description[button_id_key] = button_repr.data[1].id

if control_id == 1 and button_remote_description.id == nil then
if control_id == 1 then
button_remote_description.id = button_repr.data[1].id
end
end
Expand All @@ -63,7 +63,7 @@ local function _do_update(api_instance, device_service_info, bridge_id, resource
end

if type(cache) == "table" then
cache[resource_id] = button_remote_description
cache[button_remote_description.id] = button_remote_description
if device_service_info.id_v1 then
cache[device_service_info.id_v1] = button_remote_description
end
Expand All @@ -72,14 +72,14 @@ local function _do_update(api_instance, device_service_info, bridge_id, resource
return button_remote_description
end

---@param driver HueDriver
---@param api_instance PhilipsHueApi
---@param device_service_id string
---@param bridge_id string
---@param primary_button_resource_id string
---@param bridge_network_id string
---@param cache table?
---@return table<string,any>? description nil on error
---@return string? err nil on success
function M.update_state_for_all_device_services(api_instance, device_service_id, bridge_id, primary_button_resource_id, cache)
function M.update_state_for_all_device_services(driver, api_instance, device_service_id, bridge_network_id, cache)
log.debug("----------- Calling REST API")
local device_service_info, err = api_instance:get_device_by_id(device_service_id)
if err or not (device_service_info and device_service_info.data) then
Expand All @@ -88,23 +88,23 @@ function M.update_state_for_all_device_services(api_instance, device_service_id,
end

log.debug("------------ _do_update")
return _do_update(api_instance, device_service_info.data[1], bridge_id, primary_button_resource_id, cache)
return _do_update(driver, api_instance, device_service_info.data[1], bridge_network_id, cache)
end

---@param driver HueDriver
---@param bridge_id string
---@param bridge_network_id string
---@param api_instance PhilipsHueApi
---@param resource_id string
---@param primary_services table<HueDeviceTypes,HueServiceInfo[]>
---@param device_service_info HueDeviceInfo
---@param device_state_disco_cache table<string, table>
---@param st_metadata_callback fun(driver: HueDriver, metadata: table)?
function M.handle_discovered_device(
driver, bridge_id, api_instance,
resource_id, device_service_info,
driver, bridge_network_id, api_instance,
primary_services, device_service_info,
device_state_disco_cache, st_metadata_callback
)
local button_description, err = _do_update(
api_instance, device_service_info, bridge_id, resource_id, device_state_disco_cache
driver, api_instance, device_service_info, bridge_network_id, device_state_disco_cache
)
if err then
log.error("Error updating contact button initial state: " .. st_utils.stringify_table(err))
Expand All @@ -123,15 +123,18 @@ function M.handle_discovered_device(
end

local button_profile_ref = ""
-- For Philips Hue Smart Button device which contains only 1 button
-- For Philips Hue Smart Button or single switch In-Wall Switch module which contains only 1 button
if button_description.num_buttons == 1 then
button_profile_ref = "HueSmartButton"
-- For Philips Hue Dimmer Remote which contains 4 buttons
button_profile_ref = "single-button"
-- 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
elseif button_description.num_buttons == 4 then
button_profile_ref = "4-button-remote"
end

local bridge_device = driver:get_device_by_dni(bridge_id) or {}
local bridge_device = driver:get_device_by_dni(bridge_network_id) or {}
local st_metadata = {
type = "EDGE_CHILD",
label = device_service_info.metadata.name,
Expand All @@ -140,7 +143,7 @@ function M.handle_discovered_device(
manufacturer = device_service_info.product_data.manufacturer_name,
model = device_service_info.product_data.model_id,
parent_device_id = bridge_device.id,
parent_assigned_child_key = string.format("%s:%s", HueDeviceTypes.BUTTON, resource_id)
parent_assigned_child_key = string.format("%s:%s", HueDeviceTypes.BUTTON, button_description.id)
}

log.debug(st_utils.stringify_table(st_metadata, "button create", true))
Expand Down
30 changes: 17 additions & 13 deletions drivers/SmartThings/philips-hue/src/disco/contact.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local log = require "logjam"
local log = require "log"
local socket = require "cosock".socket
local st_utils = require "st.utils"

Expand All @@ -7,13 +7,14 @@ local HueDeviceTypes = require "hue_device_types"
---@class DiscoveredContactSensorHandler: DiscoveredChildDeviceHandler
local M = {}

---@param driver HueDriver
---@param api_instance PhilipsHueApi
---@param device_service_info HueDeviceInfo
---@param bridge_id string
---@param bridge_network_id string
---@param cache table?
---@return table<string,any>? description nil on error
---@return string? err nil on success
local function _do_update(api_instance, device_service_info, bridge_id, cache)
local function _do_update(driver, api_instance, device_service_info, bridge_network_id, cache)
local rid_by_rtype = {}
for _, svc in ipairs(device_service_info.services) do
rid_by_rtype[svc.rtype] = svc.rid
Expand All @@ -29,10 +30,11 @@ local function _do_update(api_instance, device_service_info, bridge_id, cache)
if battery_err then return nil, battery_err end

local resource_id = rid_by_rtype[HueDeviceTypes.CONTACT]
local bridge_device = driver:get_device_by_dni(bridge_network_id) --[[@as HueBridgeDevice]]
local contact_sensor_description = {
hue_provided_name = device_service_info.metadata.name,
id = resource_id,
parent_device_id = bridge_id,
parent_device_id = bridge_device.id,
hue_device_id = device_service_info.id,
hue_device_data = device_service_info,
}
Expand Down Expand Up @@ -63,13 +65,14 @@ local function _do_update(api_instance, device_service_info, bridge_id, cache)
return contact_sensor_description
end

---@param driver HueDriver
---@param api_instance PhilipsHueApi
---@param device_service_id string
---@param bridge_id string
---@param bridge_network_id string
---@param cache table?
---@return table<string,any>? description nil on error
---@return string? err nil on success
function M.update_state_for_all_device_services(api_instance, device_service_id, bridge_id, cache)
function M.update_state_for_all_device_services(driver, api_instance, device_service_id, bridge_network_id, cache)
log.debug("----------- Calling REST API")
local device_service_info, err = api_instance:get_device_by_id(device_service_id)
if err or not (device_service_info and device_service_info.data) then
Expand All @@ -78,24 +81,24 @@ function M.update_state_for_all_device_services(api_instance, device_service_id,
end

log.debug("------------ _do_update")
return _do_update(api_instance, device_service_info.data[1], bridge_id, cache)
return _do_update(driver, api_instance, device_service_info.data[1], bridge_network_id, cache)
end

---@param driver HueDriver
---@param bridge_id string
---@param bridge_network_id string
---@param api_instance PhilipsHueApi
---@param resource_id string
---@param primary_services table<HueDeviceTypes,HueServiceInfo[]>
---@param device_service_info HueDeviceInfo
---@param device_state_disco_cache table<string, table>
---@param st_metadata_callback fun(driver: HueDriver, metadata: table)?
function M.handle_discovered_device(
driver, bridge_id, api_instance,
resource_id, device_service_info,
driver, bridge_network_id, api_instance,
primary_services, device_service_info,
device_state_disco_cache, st_metadata_callback
)
local err = select(2,
_do_update(
api_instance, device_service_info, bridge_id, device_state_disco_cache
driver, api_instance, device_service_info, bridge_network_id, device_state_disco_cache
)
)
if err then
Expand All @@ -104,7 +107,8 @@ function M.handle_discovered_device(
end

if type(st_metadata_callback) == "function" then
local bridge_device = driver:get_device_by_dni(bridge_id) or {}
local resource_id = primary_services[HueDeviceTypes.CONTACT][1].rid
local bridge_device = driver:get_device_by_dni(bridge_network_id) or {}
local st_metadata = {
type = "EDGE_CHILD",
label = device_service_info.metadata.name,
Expand Down
Loading

0 comments on commit a3be7e3

Please sign in to comment.