diff --git a/drivers/SmartThings/philips-hue/profiles/hue-smart-button.yml b/drivers/SmartThings/philips-hue/profiles/single-button.yml similarity index 90% rename from drivers/SmartThings/philips-hue/profiles/hue-smart-button.yml rename to drivers/SmartThings/philips-hue/profiles/single-button.yml index ff4687187f..ad0c0ddb02 100644 --- a/drivers/SmartThings/philips-hue/profiles/hue-smart-button.yml +++ b/drivers/SmartThings/philips-hue/profiles/single-button.yml @@ -1,4 +1,4 @@ -name: HueSmartButton +name: single-button components: - id: main capabilities: diff --git a/drivers/SmartThings/philips-hue/profiles/two-button.yml b/drivers/SmartThings/philips-hue/profiles/two-button.yml new file mode 100644 index 0000000000..f5fb34ff54 --- /dev/null +++ b/drivers/SmartThings/philips-hue/profiles/two-button.yml @@ -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 diff --git a/drivers/SmartThings/philips-hue/src/disco/button.lua b/drivers/SmartThings/philips-hue/src/disco/button.lua index bd9c67e95f..88af38f89d 100644 --- a/drivers/SmartThings/philips-hue/src/disco/button.lua +++ b/drivers/SmartThings/philips-hue/src/disco/button.lua @@ -1,4 +1,4 @@ -local log = require "logjam" +local log = require "log" local socket = require "cosock".socket local st_utils = require "st.utils" @@ -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? 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 @@ -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 } @@ -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 @@ -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 @@ -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? 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 @@ -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 ---@param device_service_info HueDeviceInfo ---@param device_state_disco_cache 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)) @@ -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, @@ -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)) diff --git a/drivers/SmartThings/philips-hue/src/disco/contact.lua b/drivers/SmartThings/philips-hue/src/disco/contact.lua index b69cd5e2db..b714838bd5 100644 --- a/drivers/SmartThings/philips-hue/src/disco/contact.lua +++ b/drivers/SmartThings/philips-hue/src/disco/contact.lua @@ -1,4 +1,4 @@ -local log = require "logjam" +local log = require "log" local socket = require "cosock".socket local st_utils = require "st.utils" @@ -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? 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 @@ -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, } @@ -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? 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 @@ -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 ---@param device_service_info HueDeviceInfo ---@param device_state_disco_cache 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 @@ -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, diff --git a/drivers/SmartThings/philips-hue/src/disco/init.lua b/drivers/SmartThings/philips-hue/src/disco/init.lua index 89f6c49fb4..b798c04d1d 100644 --- a/drivers/SmartThings/philips-hue/src/disco/init.lua +++ b/drivers/SmartThings/philips-hue/src/disco/init.lua @@ -1,4 +1,4 @@ -local log = require "logjam" +local log = require "log" local socket = require "cosock.socket" local mdns = require "st.mdns" @@ -15,7 +15,7 @@ local SERVICE_TYPE = "_hue._tcp" local DOMAIN = "local" ---@class DiscoveredChildDeviceHandler ----@field public handle_discovered_device fun(driver: HueDriver, bridge_id: string, api_instance: PhilipsHueApi, resource_id: string, device_service_info: table, device_state_disco_cache: table, st_metadata_callback: fun(driver: HueDriver, metadata: table)?) +---@field public handle_discovered_device fun(driver: HueDriver, bridge_network_id: string, api_instance: PhilipsHueApi, primary_services: { [HueDeviceTypes]: HueServiceInfo[] }, device_service_info: table, device_state_disco_cache: table, st_metadata_callback: fun(driver: HueDriver, metadata: table)?) -- This `api_keys` table is an in-memory fall-back table. It gets overwritten -- with a reference to a driver datastore table before the Driver's `run` loop @@ -48,25 +48,39 @@ local function is_device_service_supported(svc_info) end ---@param driver HueDriver ----@param bridge_id string ----@param svc_info HueServiceInfo +---@param bridge_network_id string +---@param primary_services table array of services that *can* map to device records. Multi-buttons wouldn't do so, but compound lights would. ---@param device_info table -local function discovered_device_callback(driver, bridge_id, svc_info, device_info) - local v1_dni = bridge_id .. "/" .. (device_info.id_v1 or "UNKNOWN"):gsub("/lights/", "") - local v2_resource_id = svc_info.rid or "" - if driver:get_device_by_dni(v1_dni) or driver.hue_identifier_to_device_record[v2_resource_id] then return end +local function discovered_device_callback(driver, bridge_network_id, primary_services, device_info) + local v1_dni = bridge_network_id .. "/" .. (device_info.id_v1 or "UNKNOWN"):gsub("/lights/", "") + local primary_service_type = HueDeviceTypes.determine_main_service_rtype(device_info, primary_services) + if not primary_service_type then + log.error( + string.format( + "Couldn't determine primary service type for device %s, unable to join", + (device_info.metadata.name) + ) + ) + return + end + + for _, svc_info in ipairs(primary_services[primary_service_type]) do + local v2_resource_id = svc_info.rid or "" + if driver:get_device_by_dni(v1_dni) or driver.hue_identifier_to_device_record[v2_resource_id] then return end + end - local api_instance = HueDiscovery.disco_api_instances[bridge_id] + local api_instance = HueDiscovery.disco_api_instances[bridge_network_id] if not api_instance then - log.warn("No API instance for bridge_id ", bridge_id) + log.warn("No API instance for bridge_network_id ", bridge_network_id) return end HueDiscovery.handle_discovered_child_device( driver, - bridge_id, + primary_service_type, + bridge_network_id, api_instance, - svc_info, + primary_services, device_info ) end @@ -74,50 +88,50 @@ end -- "forward declarations" ---@param driver HueDriver ---@param bridge_ip string ----@param bridge_id string -local function discovered_bridge_callback(driver, bridge_ip, bridge_id) - if driver.ignored_bridges[bridge_id] then return end +---@param bridge_network_id string +local function discovered_bridge_callback(driver, bridge_ip, bridge_network_id) + if driver.ignored_bridges[bridge_network_id] then return end - local known_bridge_device = driver:get_device_by_dni(bridge_id) + local known_bridge_device = driver:get_device_by_dni(bridge_network_id) if known_bridge_device and known_bridge_device:get_field(HueApi.APPLICATION_KEY_HEADER) then - HueDiscovery.api_keys[bridge_id] = known_bridge_device:get_field(HueApi.APPLICATION_KEY_HEADER) + HueDiscovery.api_keys[bridge_network_id] = known_bridge_device:get_field(HueApi.APPLICATION_KEY_HEADER) end if known_bridge_device ~= nil - and driver.joined_bridges[bridge_id] - and HueDiscovery.api_keys[bridge_id] + and driver.joined_bridges[bridge_network_id] + and HueDiscovery.api_keys[bridge_network_id] then - log.info_with({ hub_logs = true }, string.format("Scanning bridge %s for devices...", bridge_id)) + log.info_with({ hub_logs = true }, string.format("Scanning bridge %s for devices...", bridge_network_id)) - HueDiscovery.disco_api_instances[bridge_id] = HueDiscovery.disco_api_instances[bridge_id] + HueDiscovery.disco_api_instances[bridge_network_id] = HueDiscovery.disco_api_instances[bridge_network_id] or HueApi.new_bridge_manager( "https://" .. bridge_ip, - HueDiscovery.api_keys[bridge_id], - utils.labeled_socket_builder((known_bridge_device.label or bridge_id or known_bridge_device.id or "unknown bridge")) + HueDiscovery.api_keys[bridge_network_id], + utils.labeled_socket_builder((known_bridge_device.label or bridge_network_id or known_bridge_device.id or "unknown bridge")) ) HueDiscovery.search_bridge_for_supported_devices( driver, - bridge_id, - HueDiscovery.disco_api_instances[bridge_id], + bridge_network_id, + HueDiscovery.disco_api_instances[bridge_network_id], function(hue_driver, svc_info, device_info) - discovered_device_callback(hue_driver, bridge_id, svc_info, device_info) + discovered_device_callback(hue_driver, bridge_network_id, svc_info, device_info) end, "[Discovery: " .. - (known_bridge_device.label or bridge_id or known_bridge_device.id or "unknown bridge") .. + (known_bridge_device.label or bridge_network_id or known_bridge_device.id or "unknown bridge") .. " bridge scan]" ) return end - if not HueDiscovery.api_keys[bridge_id] then - local socket_builder = utils.labeled_socket_builder(bridge_id) + if not HueDiscovery.api_keys[bridge_network_id] then + local socket_builder = utils.labeled_socket_builder(bridge_network_id) local api_key_response, err, _ = HueApi.request_api_key(bridge_ip, socket_builder) if err ~= nil or not api_key_response then log.warn(string.format( "Error while trying to request Bridge API Key for %s: %s", - bridge_id, + bridge_network_id, err ) ) @@ -126,40 +140,40 @@ local function discovered_bridge_callback(driver, bridge_ip, bridge_id) for _, item in ipairs(api_key_response) do if item.error ~= nil then - log.warn(string.format("Error payload in bridge %s API key response: %s", bridge_id, item.error.description)) + log.warn(string.format("Error payload in bridge %s API key response: %s", bridge_network_id, item.error.description)) elseif item.success and item.success.username then - log.info(string.format("API key received for Hue Bridge %s", bridge_id)) + log.info(string.format("API key received for Hue Bridge %s", bridge_network_id)) local api_key = item.success.username local bridge_base_url = "https://" .. bridge_ip local api_instance = HueApi.new_bridge_manager(bridge_base_url, api_key, socket_builder) - HueDiscovery.api_keys[bridge_id] = api_key - HueDiscovery.disco_api_instances[bridge_id] = api_instance + HueDiscovery.api_keys[bridge_network_id] = api_key + HueDiscovery.disco_api_instances[bridge_network_id] = api_instance end end end - if HueDiscovery.api_keys[bridge_id] and not driver.joined_bridges[bridge_id] then - local bridge_info = driver.datastore.bridge_netinfo[bridge_id] + if HueDiscovery.api_keys[bridge_network_id] and not driver.joined_bridges[bridge_network_id] then + local bridge_info = driver.datastore.bridge_netinfo[bridge_network_id] if not bridge_info then - log.debug(string.format("Bridge info for %s not yet available", bridge_id)) + log.debug(string.format("Bridge info for %s not yet available", bridge_network_id)) return end if tonumber(bridge_info.swversion or "0", 10) < HueApi.MIN_CLIP_V2_SWVERSION then log.warn(string.format("Found bridge %s that does not support CLIP v2 API, ignoring", bridge_info.name)) - driver.ignored_bridges[bridge_id] = true + driver.ignored_bridges[bridge_network_id] = true return end - driver.joined_bridges[bridge_id] = true + driver.joined_bridges[bridge_network_id] = true if not known_bridge_device then local create_device_msg = { type = "LAN", - device_network_id = bridge_id, + device_network_id = bridge_network_id, label = (bridge_info.name or "Philips Hue Bridge"), profile = "hue-bridge", manufacturer = "Signify Netherlands B.V.", @@ -189,8 +203,8 @@ function HueDiscovery.discover(driver, _, should_continue) HueDiscovery.do_mdns_scan(driver) HueDiscovery.search_for_bridges( driver, - function(hue_driver, bridge_ip, bridge_id) - discovered_bridge_callback(hue_driver, bridge_ip, bridge_id) + function(hue_driver, bridge_ip, bridge_network_id) + discovered_bridge_callback(hue_driver, bridge_ip, bridge_network_id) end ) socket.sleep(1.0) @@ -203,9 +217,9 @@ end ---@param callback fun(driver: HueDriver, ip: string, id: string) function HueDiscovery.search_for_bridges(driver, callback) local scanned_bridges = driver.datastore.bridge_netinfo or {} - for bridge_id, bridge_info in pairs(scanned_bridges) do + for bridge_network_id, bridge_info in pairs(scanned_bridges) do if type(callback) == "function" and bridge_info ~= nil then - callback(driver, bridge_info.ip, bridge_id) + callback(driver, bridge_info.ip, bridge_network_id) else log.warn( "Argument passed in `callback` position for " @@ -216,25 +230,25 @@ function HueDiscovery.search_for_bridges(driver, callback) end ---@param driver HueDriver ----@param bridge_id string -function HueDiscovery.scan_bridge_and_update_devices(driver, bridge_id) - if driver.ignored_bridges[bridge_id] then return end +---@param bridge_network_id string +function HueDiscovery.scan_bridge_and_update_devices(driver, bridge_network_id) + if driver.ignored_bridges[bridge_network_id] then return end - local known_bridge_device = driver:get_device_by_dni(bridge_id) + local known_bridge_device = driver:get_device_by_dni(bridge_network_id) if known_bridge_device then if known_bridge_device:get_field(HueApi.APPLICATION_KEY_HEADER) then - HueDiscovery.api_keys[bridge_id] = known_bridge_device:get_field(HueApi.APPLICATION_KEY_HEADER) + HueDiscovery.api_keys[bridge_network_id] = known_bridge_device:get_field(HueApi.APPLICATION_KEY_HEADER) end HueDiscovery.search_bridge_for_supported_devices( driver, - bridge_id, - HueDiscovery.disco_api_instances[bridge_id], + bridge_network_id, + HueDiscovery.disco_api_instances[bridge_network_id], function(hue_driver, svc_info, device_info) - discovered_device_callback(hue_driver, bridge_id, svc_info, device_info) + discovered_device_callback(hue_driver, bridge_network_id, svc_info, device_info) end, "[Discovery: " .. - (known_bridge_device.label or bridge_id or known_bridge_device.id or "unknown bridge") .. + (known_bridge_device.label or bridge_network_id or known_bridge_device.id or "unknown bridge") .. " bridge re-scan]", true ) @@ -242,12 +256,12 @@ function HueDiscovery.scan_bridge_and_update_devices(driver, bridge_id) end ---@param driver HueDriver ----@param bridge_id string +---@param bridge_network_id string ---@param api_instance PhilipsHueApi ---@param callback fun(driver: HueDriver, svc_info: HueServiceInfo, device_data: table) ---@param log_prefix string? ---@param do_delete boolean? -function HueDiscovery.search_bridge_for_supported_devices(driver, bridge_id, api_instance, callback, log_prefix, do_delete) +function HueDiscovery.search_bridge_for_supported_devices(driver, bridge_network_id, api_instance, callback, log_prefix, do_delete) local prefix = "" if type(log_prefix) == "string" and #log_prefix > 0 then prefix = log_prefix .. " " end @@ -268,32 +282,40 @@ function HueDiscovery.search_bridge_for_supported_devices(driver, bridge_id, api local device_is_joined_to_bridge = {} for _, device_data in ipairs(devices.data or {}) do - local primary_device_service + local primary_device_services = {} for _, svc_info in ipairs(device_data.services or {}) do if is_device_service_supported(svc_info) then driver.services_for_device_rid[device_data.id] = driver.services_for_device_rid[device_data.id] or {} driver.services_for_device_rid[device_data.id][svc_info.rid] = svc_info.rtype - if HueDeviceTypes.can_join_device_for_service(svc_info.rtype) and primary_device_service == nil then - primary_device_service = svc_info + if HueDeviceTypes.can_join_device_for_service(svc_info.rtype) then + local services_for_type = primary_device_services[svc_info.rtype] or {} + table.insert(services_for_type, svc_info) + primary_device_services[svc_info.rtype] = services_for_type device_is_joined_to_bridge[device_data.id] = true end end end - if primary_device_service and type(callback) == "function" then - log.info_with( - { hub_logs = true }, string.format( - prefix .. - "Processing supported svc [rid: %s | type: %s] for Hue device [v2_id: %s | v1_id: %s], with Hue provided name: %s", - primary_device_service.rid, primary_device_service.rtype, device_data.id, device_data.id_v1, device_data.metadata.name + if next(primary_device_services) then + if type(callback) == "function" then + log.info_with( + { hub_logs = true }, + string.format( + prefix .. + "Processing supported services [%s] for Hue device [v2_id: %s | v1_id: %s], with Hue provided name: %s", + st_utils.stringify_table(primary_device_services), device_data.id, device_data.id_v1, device_data.metadata.name + ) ) - ) - callback(driver, primary_device_service, device_data) + callback(driver, primary_device_services, device_data) + else + log.warn( + prefix .. "Argument passed in `callback` position for " + .. "`HueDiscovery.search_bridge_for_supported_devices` is not a function" + ) + end else - log.warn( - prefix .. "Argument passed in `callback` position for " - .. "`HueDiscovery.search_bridge_for_supported_devices` is not a function" - ) + log.warn(string.format("No primary services for %s", device_data.metadata.name)) + log.warn(st_utils.stringify_table(device_data.services, "services", true)) end end @@ -303,8 +325,8 @@ function HueDiscovery.search_bridge_for_supported_devices(driver, bridge_id, api if utils.is_bridge(driver, device) then goto continue end local not_known_to_bridge = device_is_joined_to_bridge[device:get_field(Fields.HUE_DEVICE_ID) or ""] local parent_device_id = device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID) or "" - local parent_bridge_device = driver:get_device_info(parent_device_id) - local is_child_of_bridge = parent_bridge_device and (parent_bridge_device:get_field(Fields.BRIDGE_ID) == bridge_id) + local parent_bridge_device = utils.get_hue_bridge_for_device(driver, device, parent_device_id) + local is_child_of_bridge = parent_bridge_device and (parent_bridge_device:get_field(Fields.BRIDGE_ID) == bridge_network_id) if parent_bridge_device and is_child_of_bridge and not not_known_to_bridge and device.id then device.log.info(string.format("Device is no longer joined to Hue Bridge %q, deleting", parent_bridge_device.label)) driver:do_hue_child_delete(device) @@ -315,16 +337,17 @@ function HueDiscovery.search_bridge_for_supported_devices(driver, bridge_id, api end ---@param driver HueDriver ----@param bridge_id string +---@param primary_service_type HueDeviceTypes +---@param bridge_network_id string ---@param api_instance PhilipsHueApi ----@param svc_info table +---@param primary_services table array of services that *can* map to device records. Multi-buttons wouldn't do so, but compound lights would. ---@param device_info table -function HueDiscovery.handle_discovered_child_device(driver, bridge_id, api_instance, svc_info, device_info) - discovered_device_handlers[svc_info.rtype].handle_discovered_device( +function HueDiscovery.handle_discovered_child_device(driver, primary_service_type, bridge_network_id, api_instance, primary_services, device_info) + discovered_device_handlers[primary_service_type].handle_discovered_device( driver, - bridge_id, + bridge_network_id, api_instance, - svc_info.rid, + primary_services, device_info, HueDiscovery.device_state_disco_cache, driver.try_create_device @@ -394,7 +417,7 @@ function HueDiscovery.do_mdns_scan(driver) end bridge_info.ip = ip_addr - local bridge_id = bridge_info.mac:gsub("-", ""):gsub(":", ""):upper() + local bridge_network_id = bridge_info.mac:gsub("-", ""):gsub(":", ""):upper() -- sanitize userdata nulls from JSON decode for k, v in pairs(bridge_info) do @@ -405,16 +428,16 @@ function HueDiscovery.do_mdns_scan(driver) ---@type boolean? local update_needed = false - if not utils.deep_table_eq((bridge_netinfo[bridge_id] or {}), bridge_info) then - bridge_netinfo[bridge_id] = bridge_info + if not utils.deep_table_eq((bridge_netinfo[bridge_network_id] or {}), bridge_info) then + bridge_netinfo[bridge_network_id] = bridge_info update_needed = true end - if driver.joined_bridges[bridge_id] and not driver.ignored_bridges[bridge_id] then - local bridge_device = driver:get_device_by_dni(bridge_id, true) + if driver.joined_bridges[bridge_network_id] and not driver.ignored_bridges[bridge_network_id] then + local bridge_device = driver:get_device_by_dni(bridge_network_id, true) update_needed = update_needed or (bridge_device and (bridge_device:get_field(Fields.IPV4) ~= bridge_info.ip)) if update_needed then - driver:update_bridge_netinfo(bridge_id, bridge_info) + driver:update_bridge_netinfo(bridge_network_id, bridge_info) end end ::continue:: diff --git a/drivers/SmartThings/philips-hue/src/disco/light.lua b/drivers/SmartThings/philips-hue/src/disco/light.lua index 243a2417d9..07dcf9a6d8 100644 --- a/drivers/SmartThings/philips-hue/src/disco/light.lua +++ b/drivers/SmartThings/philips-hue/src/disco/light.lua @@ -1,21 +1,152 @@ -local log = require "logjam" +local log = require "log" local socket = require "cosock".socket local st_utils = require "st.utils" ----@class DiscoveredLightHandler: DiscoveredChildDeviceHandler -local M = {} +local HueDeviceTypes = require "hue_device_types" + +local function join_light(driver, light, device_service_info, parent_device_id, st_metadata_callback) + local profile_ref + if light.color then + if light.color_temperature then + profile_ref = "white-and-color-ambiance" + else + profile_ref = "legacy-color" + end + elseif light.color_temperature then + profile_ref = "white-ambiance" -- all color temp products support `white` (dimming) + elseif light.dimming then + profile_ref = "white" -- `white` refers to dimmable and includes filament bulbs + elseif light.on then -- Case for plug which uses same category as 'light' + profile_ref = "plug" + else + log.warn( + string.format( + "Light resource [%s] does not seem to be A White/White-Ambiance/White-Color-Ambiance/Plug device, currently unsupported" + , + light.id + ) + ) + return + end + + local device_name = light.metadata.name + local parent_assigned_child_key = string.format("%s:%s", light.type, light.id) + + local st_metadata = { + type = "EDGE_CHILD", + label = device_name, + vendor_provided_label = device_service_info.product_data.product_name, + profile = profile_ref, + manufacturer = device_service_info.product_data.manufacturer_name, + model = device_service_info.product_data.model_id, + parent_device_id = parent_device_id, + parent_assigned_child_key = parent_assigned_child_key + } + + log.debug(true, st_utils.stringify_table(st_metadata, "light create", true)) + st_metadata_callback(driver, st_metadata) + -- rate limit ourself. + socket.sleep(0.1) +end + +local function get_light_state_table_and_update_cache(light, parent_device_id, device_service_info, cache) + local light_resource_description = { + hue_provided_name = light.metadata.name, + id = light.id, + on = light.on, + color = light.color, + dimming = light.dimming, + color_temperature = light.color_temperature, + mode = light.mode, + parent_device_id = parent_device_id, + hue_device_id = light.owner.rid, + hue_device_data = device_service_info + } + + if type(cache) == "table" then + cache[light.id] = light_resource_description + if device_service_info.id_v1 then + cache[device_service_info.id_v1] = light_resource_description + end + end + return light_resource_description +end + +---@param driver HueDriver +---@param api_instance PhilipsHueApi +---@param services HueServiceInfo[] +---@param device_service_info HueDeviceInfo +---@param bridge_network_id string +---@param cache table +---@param st_metadata_callback fun(driver: HueDriver, metadata: table)? +local function handle_compound_light( + driver, api_instance, services, + device_service_info, bridge_network_id, cache, st_metadata_callback +) + ---@type HueLightInfo[] + local all_lights = {} + local main_light_resource_id + for idx, svc in ipairs(services) do + local light_resource, err, _ = api_instance:get_light_by_id(svc.rid) + if not light_resource or (light_resource and #light_resource.errors > 0) or err then + log.error(string.format("Couldn't get light resource for rid %s, skipping", svc.rid)) + goto continue + end + table.insert(all_lights, light_resource.data[1]) + if light_resource.data[1].id_v1 and light_resource.data[1].id_v1 == device_service_info.id_v1 then + main_light_resource_id = light_resource.data[1].id_v1 + break + end + ::continue:: + end + + if type(main_light_resource_id) ~= "string" then + log.warn( + string.format( + "Couldn't determine the primary light for compound light [%s] from V1 ID, picking the first light service", + device_service_info.metadata.name + ) + ) + main_light_resource_id = services[1].rid + end + + ---@type HueLightInfo[] + local grandchild_lights = {} + for _, light in pairs(all_lights) do + if light.id == main_light_resource_id then + local bridge_device = driver:get_device_by_dni(bridge_network_id) --[[@as HueBridgeDevice]] + get_light_state_table_and_update_cache(light, bridge_device.id, device_service_info, cache) + if type(st_metadata_callback) == "function" then + join_light( + driver, light, device_service_info, bridge_device.id, st_metadata_callback + ) + end + else + table.insert(grandchild_lights, { + device = light, + join_callback = function(driver, waiting_info, parent_device) + get_light_state_table_and_update_cache(waiting_info, parent_device.id, device_service_info, cache) + join_light( + driver, waiting_info, device_service_info, parent_device.id, st_metadata_callback + ) + end + }) + end + end + + driver:queue_grandchild_device_for_join(grandchild_lights, main_light_resource_id) +end ---@param driver HueDriver ----@param bridge_id string ---@param api_instance PhilipsHueApi ---@param resource_id string ---@param device_service_info HueDeviceInfo ----@param device_state_disco_cache table +---@param bridge_network_id string +---@param cache 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, - device_state_disco_cache, st_metadata_callback +local function handle_simple_light( + driver, api_instance, resource_id, + device_service_info, bridge_network_id, cache, st_metadata_callback ) local light_resource, err, _ = api_instance:get_light_by_id(resource_id) if err ~= nil or not light_resource then @@ -27,76 +158,50 @@ function M.handle_discovered_device( if light_resource.errors and #light_resource.errors > 0 then log.error_with({ hub_logs = true }, "Errors found in API response:") - for idx, err in ipairs(light_resource.errors) do - log.error_with({ hub_logs = true }, st_utils.stringify_table(err, "Error " .. idx, true)) + for idx, rest_err in ipairs(light_resource.errors) do + log.error_with({ hub_logs = true }, st_utils.stringify_table(rest_err, "Error " .. idx, true)) end return end - for _, light in ipairs(light_resource.data or {}) do - local bridge_device = driver:get_device_by_dni(bridge_id) --[[@as HueBridgeDevice]] - local light_resource_description = { - hue_provided_name = light.metadata.name, - id = light.id, - on = light.on, - color = light.color, - dimming = light.dimming, - color_temperature = light.color_temperature, - mode = light.mode, - parent_device_id = bridge_device.id, - hue_device_id = light.owner.rid, - hue_device_data = device_service_info - } - device_state_disco_cache[light.id] = light_resource_description - if device_service_info.id_v1 then - device_state_disco_cache[device_service_info.id_v1] = light_resource_description - end + local light = light_resource.data[1] + local bridge_device = driver:get_device_by_dni(bridge_network_id) --[[@as HueBridgeDevice]] + get_light_state_table_and_update_cache(light, bridge_device.id, device_service_info, cache) - if type(st_metadata_callback) == "function" then - local profile_ref - if light.color then - if light.color_temperature then - profile_ref = "white-and-color-ambiance" - else - profile_ref = "legacy-color" - end - elseif light.color_temperature then - profile_ref = "white-ambiance" -- all color temp products support `white` (dimming) - elseif light.dimming then - profile_ref = "white" -- `white` refers to dimmable and includes filament bulbs - elseif light.on then -- Case for plug which uses same category as 'light' - profile_ref = "plug" - else - log.warn( - string.format( - "Light resource [%s] does not seem to be A White/White-Ambiance/White-Color-Ambiance/Plug device, currently unsupported" - , - resource_id - ) - ) - goto continue - end + if type(st_metadata_callback) == "function" then + join_light( + driver, light, device_service_info, bridge_device.id, st_metadata_callback + ) + end +end - local device_name = light.metadata.name - local parent_assigned_child_key = string.format("%s:%s", light.type, light.id) - - local st_metadata = { - type = "EDGE_CHILD", - label = device_name, - vendor_provided_label = device_service_info.product_data.product_name, - profile = profile_ref, - 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 = parent_assigned_child_key - } - - log.debug(true, st_utils.stringify_table(st_metadata, "light create", true)) - st_metadata_callback(driver, st_metadata) - -- rate limit ourself. - socket.sleep(0.1) - end - ::continue:: +---@class DiscoveredLightHandler: DiscoveredChildDeviceHandler +local M = {} + +---@param driver HueDriver +---@param bridge_network_id string +---@param api_instance PhilipsHueApi +---@param primary_services table +---@param device_service_info HueDeviceInfo +---@param device_state_disco_cache table +---@param st_metadata_callback fun(driver: HueDriver, metadata: table)? +function M.handle_discovered_device( + driver, bridge_network_id, api_instance, + primary_services, device_service_info, + device_state_disco_cache, st_metadata_callback +) + local light_services = primary_services[HueDeviceTypes.LIGHT] + local is_compound_light = #light_services > 1 + if is_compound_light then + handle_compound_light( + driver, api_instance, light_services, device_service_info, + bridge_network_id, device_state_disco_cache, st_metadata_callback + ) + else + handle_simple_light( + driver, api_instance, light_services[1].rid, device_service_info, + bridge_network_id, device_state_disco_cache, st_metadata_callback + ) end end diff --git a/drivers/SmartThings/philips-hue/src/disco/motion.lua b/drivers/SmartThings/philips-hue/src/disco/motion.lua index f325e91240..4ca14ccda1 100644 --- a/drivers/SmartThings/philips-hue/src/disco/motion.lua +++ b/drivers/SmartThings/philips-hue/src/disco/motion.lua @@ -1,4 +1,4 @@ -local log = require "logjam" +local log = require "log" local socket = require "cosock".socket local st_utils = require "st.utils" @@ -7,13 +7,14 @@ local HueDeviceTypes = require "hue_device_types" ---@class DiscoveredMotionSensorHandler: 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? 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 @@ -32,10 +33,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.MOTION] + local bridge_device = driver:get_device_by_dni(bridge_network_id) --[[@as HueBridgeDevice]] local motion_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, } @@ -73,13 +75,14 @@ local function _do_update(api_instance, device_service_info, bridge_id, cache) return motion_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? 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 @@ -88,24 +91,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 ---@param device_service_info HueDeviceInfo ---@param device_state_disco_cache 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 @@ -114,7 +117,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.MOTION][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, diff --git a/drivers/SmartThings/philips-hue/src/handlers/attribute_emitters.lua b/drivers/SmartThings/philips-hue/src/handlers/attribute_emitters.lua index a4bd3d99e2..5e5736a191 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/attribute_emitters.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/attribute_emitters.lua @@ -1,5 +1,5 @@ local capabilities = require "st.capabilities" -local log = require "logjam" +local log = require "log" local st_utils = require "st.utils" -- trick to fix the VS Code Lua Language Server typechecking ---@type fun(val: table, name: string?, multi_line: boolean?): string @@ -130,7 +130,7 @@ function AttributeEmitters.emit_button_attribute_events(button_device, button_in return end - if button_info.power_state then + if button_info.power_state and type(button_info.power_state.battery_level) == "number" then log.debug(true, "emit power") button_device:emit_event( capabilities.battery.battery( @@ -158,7 +158,7 @@ function AttributeEmitters.emit_button_attribute_events(button_device, button_in component_idx = string.format("button%s", idx) end - local button_report = button_info.button.button_report or { event = "" } + local button_report = (button_info.button and button_info.button.button_report) or { event = "" } if button_report.event == "long_press" and not button_device:get_field("button_held") then button_device:set_field("button_held", true) @@ -184,7 +184,7 @@ function AttributeEmitters.emit_contact_sensor_attribute_events(sensor_device, s return end - if sensor_info.power_state then + if sensor_info.power_state and type(sensor_info.power_state.battery_level) == "number" then log.debug(true, "emit power") sensor_device:emit_event(capabilities.battery.battery(st_utils.clamp_value(sensor_info.power_state.battery_level, 0, 100))) end @@ -222,7 +222,7 @@ function AttributeEmitters.emit_motion_sensor_attribute_events(sensor_device, se return end - if sensor_info.power_state then + if sensor_info.power_state and type(sensor_info.power_state.battery_level) == "number" then log.debug(true, "emit power") sensor_device:emit_event(capabilities.battery.battery(st_utils.clamp_value(sensor_info.power_state.battery_level, 0, 100))) end diff --git a/drivers/SmartThings/philips-hue/src/handlers/commands.lua b/drivers/SmartThings/philips-hue/src/handlers/commands.lua index b118659382..608817c5f2 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/commands.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/commands.lua @@ -1,5 +1,5 @@ local capabilities = require "st.capabilities" -local log = require "logjam" +local log = require "log" local st_utils = require "st.utils" local Consts = require "consts" @@ -21,7 +21,7 @@ local CommandHandlers = {} local function do_switch_action(driver, device, args) local on = args.command == "on" local id = device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID) - local bridge_device = driver:get_device_info(id) + local bridge_device = utils.get_hue_bridge_for_device(driver, device, id) if not bridge_device then log.warn( @@ -66,7 +66,7 @@ end local function do_switch_level_action(driver, device, args) local level = st_utils.clamp_value(args.args.level, 1, 100) local id = device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID) - local bridge_device = driver:get_device_info(id) + local bridge_device = utils.get_hue_bridge_for_device(driver, device, id) if not bridge_device then log.warn( @@ -130,7 +130,7 @@ local function do_color_action(driver, device, args) device:set_field(Fields.WRAPPED_HUE, true) end local id = device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID) - local bridge_device = driver:get_device_info(id) + local bridge_device = utils.get_hue_bridge_for_device(driver, device, id) if not bridge_device then log.warn( @@ -207,7 +207,7 @@ end local function do_color_temp_action(driver, device, args) local kelvin = args.args.temperature local id = device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID) - local bridge_device = driver:get_device_info(id) + local bridge_device = utils.get_hue_bridge_for_device(driver, device, id) if not bridge_device then log.warn( diff --git a/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers.lua b/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers.lua index 1110c6469f..e558389a4c 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers.lua @@ -1,4 +1,4 @@ -local log = require "logjam" +local log = require "log" local st_utils = require "st.utils" local Discovery = require "disco" @@ -53,11 +53,21 @@ function LifecycleHandlers.device_added(driver, device, ...) local resource_state_known = (Discovery.device_state_disco_cache[resource_id] ~= nil) log.info( string.format("Querying device info for parent of %s", (device.label or device.id or "unknown device"))) - local parent_bridge = driver:get_device_info(device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID)) + + local parent_bridge = utils.get_hue_bridge_for_device( + driver, device, device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID) + ) local key = parent_bridge and parent_bridge:get_field(HueApi.APPLICATION_KEY_HEADER) local bridge_ip = parent_bridge and parent_bridge:get_field(Fields.IPV4) local bridge_id = parent_bridge and parent_bridge:get_field(Fields.BRIDGE_ID) + log.trace(true, + st_utils.stringify_table( + {parent_bridge and parent_bridge.label, key, bridge_ip, bridge_id}, + "device added bridge deets", + true + ) + ) if not (bridge_ip and bridge_id and resource_state_known and (Discovery.api_keys[bridge_id or {}] or key)) then log.warn(true, "Found \"stray\" bulb without associated Hue Bridge. Waiting to see if a bridge becomes available.") diff --git a/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/bridge.lua b/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/bridge.lua index 3ed6069a5c..68973a4cb2 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/bridge.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/bridge.lua @@ -1,5 +1,5 @@ local cosock = require "cosock" -local log = require "logjam" +local log = require "log" local Discovery = require "disco" local Fields = require "fields" 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 94e5369ce5..f28e07395b 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/button.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/button.lua @@ -1,5 +1,5 @@ local capabilities = require "st.capabilities" -local log = require "logjam" +local log = require "log" local st_utils = require "st.utils" local refresh_handler = require("handlers.commands").refresh_handler @@ -119,7 +119,9 @@ function ButtonLifecycleHandlers.init(driver, device) button_info = Discovery.device_state_disco_cache[device_button_resource_id] if not button_info then log.debug("no button info") - local parent_bridge = driver:get_device_info(device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID)) + local parent_bridge = utils.get_hue_bridge_for_device( + driver, device, device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID) + ) local api_instance = (parent_bridge and parent_bridge:get_field(Fields.BRIDGE_API)) or Discovery.api_keys[(parent_bridge and parent_bridge.device_network_id) or ""] @@ -128,10 +130,10 @@ function ButtonLifecycleHandlers.init(driver, device) driver._devices_pending_refresh[device.id] = device else button_info, err = button_disco.update_state_for_all_device_services( + driver, api_instance, hue_device_id, parent_bridge.device_network_id, - device_button_resource_id, Discovery.device_state_disco_cache ) if err then diff --git a/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/contact.lua b/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/contact.lua index 2ef9f73f64..0b03f845ed 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/contact.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/contact.lua @@ -1,4 +1,4 @@ -local log = require "logjam" +local log = require "log" local st_utils = require "st.utils" local refresh_handler = require("handlers.commands").refresh_handler @@ -81,7 +81,9 @@ function ContactLifecycleHandlers.init(driver, device) sensor_info = Discovery.device_state_disco_cache[device_sensor_resource_id] if not sensor_info then log.debug("no sensor info") - local parent_bridge = driver:get_device_info(device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID)) + local parent_bridge = utils.get_hue_bridge_for_device( + driver, device, device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID) + ) local api_instance = (parent_bridge and parent_bridge:get_field(Fields.BRIDGE_API)) or Discovery.api_keys[(parent_bridge and parent_bridge.device_network_id) or ""] if not (parent_bridge and api_instance) then @@ -89,6 +91,7 @@ function ContactLifecycleHandlers.init(driver, device) driver._devices_pending_refresh[device.id] = device else sensor_info, err = contact_sensor_disco.update_state_for_all_device_services( + driver, api_instance, hue_device_id, parent_bridge.device_network_id, diff --git a/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/light.lua b/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/light.lua index 7c660dda29..42402bf106 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/light.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/light.lua @@ -1,4 +1,4 @@ -local log = require "logjam" +local log = require "log" local refresh_handler = require("handlers.commands").refresh_handler local st_utils = require "st.utils" @@ -36,8 +36,7 @@ function LightLifecycleHandlers.added(driver, device, parent_device_id, resource if not light_info_known then log.info( string.format("Querying device info for parent of %s", (device.label or device.id or "unknown device"))) - local parent_bridge = driver:get_device_info(parent_device_id or device.parent_device_id or - device:get_field(Fields.PARENT_DEVICE_ID)) + local parent_bridge = utils.get_hue_bridge_for_device(driver, device, parent_device_id) if not parent_bridge then log.error_with({ hub_logs = true }, string.format( "Device %s added with parent UUID of %s but could not find a device with that UUID in the driver", @@ -107,7 +106,7 @@ function LightLifecycleHandlers.added(driver, device, parent_device_id, resource dimming = light.dimming, color_temperature = light.color_temperature, mode = light.mode, - parent_device_id = parent_bridge.id, + parent_device_id = parent_device_id or device.parent_device_id, hue_device_id = light.owner.rid, hue_device_data = { product_data = { @@ -190,6 +189,7 @@ function LightLifecycleHandlers.init(driver, device) refresh_handler(driver, device) device:set_field(Fields._REFRESH_AFTER_INIT, false, { persist = true }) end + driver:check_waiting_grandchildren_for_device(device) end return LightLifecycleHandlers diff --git a/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/motion.lua b/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/motion.lua index 5a87f866bc..9f03a3ff30 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/motion.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/lifecycle_handlers/motion.lua @@ -1,4 +1,4 @@ -local log = require "logjam" +local log = require "log" local st_utils = require "st.utils" local refresh_handler = require("handlers.commands").refresh_handler @@ -82,7 +82,9 @@ function MotionLifecycleHandlers.init(driver, device) sensor_info = Discovery.device_state_disco_cache[device_sensor_resource_id] if not sensor_info then log.debug("no sensor info") - local parent_bridge = driver:get_device_info(device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID)) + local parent_bridge = utils.get_hue_bridge_for_device( + driver, device, device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID) + ) local api_instance = (parent_bridge and parent_bridge:get_field(Fields.BRIDGE_API)) or Discovery.api_keys[(parent_bridge and parent_bridge.device_network_id) or ""] if not (parent_bridge and api_instance) then @@ -91,6 +93,7 @@ function MotionLifecycleHandlers.init(driver, device) else log.debug("--------------------- update all start") sensor_info, err = motion_sensor_disco.update_state_for_all_device_services( + driver, api_instance, hue_device_id, parent_bridge.device_network_id, diff --git a/drivers/SmartThings/philips-hue/src/handlers/migration_handlers/bridge.lua b/drivers/SmartThings/philips-hue/src/handlers/migration_handlers/bridge.lua index 7b74668bbf..d1633ec6f1 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/migration_handlers/bridge.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/migration_handlers/bridge.lua @@ -1,5 +1,5 @@ local cosock = require "cosock" -local log = require "logjam" +local log = require "log" local Discovery = require "disco" local HueApi = require "hue.api" diff --git a/drivers/SmartThings/philips-hue/src/handlers/migration_handlers/light.lua b/drivers/SmartThings/philips-hue/src/handlers/migration_handlers/light.lua index 4581ff4203..819085ca8c 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/migration_handlers/light.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/migration_handlers/light.lua @@ -1,5 +1,5 @@ local capabilities = require "st.capabilities" -local log = require "logjam" +local log = require "log" local st_utils = require "st.utils" local Discovery = require "disco" @@ -29,7 +29,7 @@ function LightMigrationHandler.migrate(driver, device, lifecycle_handlers, paren local bridge_device = nil if parent_device_id ~= nil then - bridge_device = driver:get_device_info(parent_device_id, false) + bridge_device = utils.get_hue_bridge_for_device(driver, device, parent_device_id) end if not bridge_device then diff --git a/drivers/SmartThings/philips-hue/src/handlers/refresh_handlers.lua b/drivers/SmartThings/philips-hue/src/handlers/refresh_handlers.lua index 60c946f8c6..f733a77469 100644 --- a/drivers/SmartThings/philips-hue/src/handlers/refresh_handlers.lua +++ b/drivers/SmartThings/philips-hue/src/handlers/refresh_handlers.lua @@ -1,5 +1,5 @@ local cosock = require "cosock" -local log = require "logjam" +local log = require "log" local st_utils = require "st.utils" local Fields = require "fields" @@ -156,7 +156,7 @@ end function RefreshHandlers.do_refresh_button(driver, button_device, _, skip_zigbee) local hue_device_id = button_device:get_field(Fields.HUE_DEVICE_ID) local bridge_id = button_device.parent_device_id or button_device:get_field(Fields.PARENT_DEVICE_ID) - local bridge_device = driver:get_device_info(bridge_id) + local bridge_device = utils.get_hue_bridge_for_device(driver, button_device, bridge_id) if not bridge_device then log.warn("Couldn't get Hue bridge for light " .. (button_device.label or button_device.id or "unknown device")) @@ -174,7 +174,7 @@ function RefreshHandlers.do_refresh_button(driver, button_device, _, skip_zigbee _refresh_zigbee(button_device, hue_api) end - local sensor_info, err = MultiServiceDeviceUtils.get_all_service_states(HueDeviceTypes.BUTTON, hue_api, hue_device_id, bridge_id) + local sensor_info, err = MultiServiceDeviceUtils.get_all_service_states(driver, HueDeviceTypes.BUTTON, hue_api, hue_device_id, bridge_device.device_network_id) if err then log.error(string.format("Error refreshing motion sensor %s: %s", (button_device and button_device.label), err)) end @@ -186,7 +186,7 @@ end function RefreshHandlers.do_refresh_motion_sensor(driver, sensor_device, _, skip_zigbee) local hue_device_id = sensor_device:get_field(Fields.HUE_DEVICE_ID) local bridge_id = sensor_device.parent_device_id or sensor_device:get_field(Fields.PARENT_DEVICE_ID) - local bridge_device = driver:get_device_info(bridge_id) + local bridge_device = utils.get_hue_bridge_for_device(driver, sensor_device, bridge_id) if not bridge_device then log.warn("Couldn't get Hue bridge for light " .. (sensor_device.label or sensor_device.id or "unknown device")) @@ -204,7 +204,7 @@ function RefreshHandlers.do_refresh_motion_sensor(driver, sensor_device, _, skip _refresh_zigbee(sensor_device, hue_api) end - local sensor_info, err = MultiServiceDeviceUtils.get_all_service_states(HueDeviceTypes.MOTION, hue_api, hue_device_id, bridge_id) + local sensor_info, err = MultiServiceDeviceUtils.get_all_service_states(driver, HueDeviceTypes.MOTION, hue_api, hue_device_id, bridge_device.device_network_id) if err then log.error(string.format("Error refreshing motion sensor %s: %s", (sensor_device and sensor_device.label), err)) end @@ -215,7 +215,7 @@ end function RefreshHandlers.do_refresh_contact_sensor(driver, sensor_device, _, skip_zigbee) local hue_device_id = sensor_device:get_field(Fields.HUE_DEVICE_ID) local bridge_id = sensor_device.parent_device_id or sensor_device:get_field(Fields.PARENT_DEVICE_ID) - local bridge_device = driver:get_device_info(bridge_id) + local bridge_device = utils.get_hue_bridge_for_device(driver, sensor_device, bridge_id) if not bridge_device then log.warn("Couldn't get Hue bridge for light " .. (sensor_device.label or sensor_device.id or "unknown device")) @@ -233,7 +233,7 @@ function RefreshHandlers.do_refresh_contact_sensor(driver, sensor_device, _, ski _refresh_zigbee(sensor_device, hue_api) end - local sensor_info, err = MultiServiceDeviceUtils.get_all_service_states(HueDeviceTypes.CONTACT, hue_api, hue_device_id, bridge_id) + local sensor_info, err = MultiServiceDeviceUtils.get_all_service_states(driver, HueDeviceTypes.CONTACT, hue_api, hue_device_id, bridge_device.device_network_id) if err then log.error(string.format("Error refreshing contact sensor %s: %s", (sensor_device and sensor_device.label), err)) end @@ -274,7 +274,7 @@ function RefreshHandlers.do_refresh_light(driver, light_device, light_status_cac end local bridge_id = light_device.parent_device_id or light_device:get_field(Fields.PARENT_DEVICE_ID) - local bridge_device = driver:get_device_info(bridge_id) + local bridge_device = utils.get_hue_bridge_for_device(driver, light_device, bridge_id) if not bridge_device then log.warn("Couldn't get Hue bridge for light " .. (light_device.label or light_device.id or "unknown device")) diff --git a/drivers/SmartThings/philips-hue/src/hue/api.lua b/drivers/SmartThings/philips-hue/src/hue/api.lua index 033f3aa57c..6e80b652da 100644 --- a/drivers/SmartThings/philips-hue/src/hue/api.lua +++ b/drivers/SmartThings/philips-hue/src/hue/api.lua @@ -2,7 +2,7 @@ local cosock = require "cosock" local channel = require "cosock.channel" local json = require "st.json" -local log = require "logjam" +local log = require "log" local RestClient = require "lunchbox.rest" local st_utils = require "st.utils" diff --git a/drivers/SmartThings/philips-hue/src/hue_debug/init.lua b/drivers/SmartThings/philips-hue/src/hue_debug/init.lua index 3d4075213e..875c94076c 100644 --- a/drivers/SmartThings/philips-hue/src/hue_debug/init.lua +++ b/drivers/SmartThings/philips-hue/src/hue_debug/init.lua @@ -1,6 +1,6 @@ local st_utils = require "st.utils" -local log = require "logjam" +local log = require "log" local utils = require "utils" local Discovery = require "disco" diff --git a/drivers/SmartThings/philips-hue/src/hue_device_types.lua b/drivers/SmartThings/philips-hue/src/hue_device_types.lua index d267fbe3b5..f0c4dec0c7 100644 --- a/drivers/SmartThings/philips-hue/src/hue_device_types.lua +++ b/drivers/SmartThings/philips-hue/src/hue_device_types.lua @@ -13,8 +13,9 @@ local HueDeviceTypes = { } local SupportedNumberOfButtons = { - [1] = true, -- For Philips Hue Smart Button device which contains only 1 button - [4] = true, -- For Philips Hue Dimmer Remote which contains 4 buttons + [1] = true, -- For Philips Hue Smart Button or single switch In-Wall Switch module which contains only 1 button + [2] = true, -- For double switch In-Wall Switch module + [4] = true, -- For Philips Hue Dimmer Remote and Tap Dial, which contains 4 buttons } local PrimaryDeviceTypes = { @@ -43,4 +44,30 @@ function HueDeviceTypes.supports_button_configuration(button_description) return SupportedNumberOfButtons[button_description.num_buttons] end +---@param device_info HueDeviceInfo +---@param primary_services table +---@return HueDeviceTypes? svc_rtype the main service rtype for the device +function HueDeviceTypes.determine_main_service_rtype(device_info, primary_services) + -- If the id_v1 is present, it'll be of the form '//' + local service_from_v1_id = string.match((device_info.id_v1 or ""), "/([%a]+)/[%d]+") or "" + -- Lights show up as `light` here, but buttons and sensors both show up as `sensors` + if PrimaryDeviceTypes[service_from_v1_id] then + return service_from_v1_id + end + + local has_service = {} + for rtype, _ in pairs(primary_services) do + has_service[rtype] = PrimaryDeviceTypes[rtype] + end + + -- At this point we'll make our best guess by establishing an order of precedence as a heuristic; + -- lights first, then the actual sensors, then buttons. + if has_service[HueDeviceTypes.LIGHT] then return HueDeviceTypes.LIGHT end + if has_service[HueDeviceTypes.CONTACT] then return HueDeviceTypes.CONTACT end + if has_service[HueDeviceTypes.MOTION] then return HueDeviceTypes.MOTION end + if has_service[HueDeviceTypes.BUTTON] then return HueDeviceTypes.BUTTON end + + return nil +end + return HueDeviceTypes diff --git a/drivers/SmartThings/philips-hue/src/hue_driver_template.lua b/drivers/SmartThings/philips-hue/src/hue_driver_template.lua index a93f300fd5..3f7f7d03c9 100644 --- a/drivers/SmartThings/philips-hue/src/hue_driver_template.lua +++ b/drivers/SmartThings/philips-hue/src/hue_driver_template.lua @@ -1,7 +1,8 @@ local Driver = require "st.driver" local capabilities = require "st.capabilities" -local log = require "logjam" +local cosock = require "cosock" +local log = require "log" local Discovery = require "disco" local Fields = require "fields" @@ -65,6 +66,7 @@ local set_color_temp_handler = utils.safe_wrap_handler(command_handlers.set_colo --- @field public joined_bridges table --- @field public hue_identifier_to_device_record table --- @field public services_for_device_rid table> Map the device resource ID to another map that goes from service rid to service rtype +--- @field public waiting_grandchildren table? --- @field public stray_device_tx table cosock channel --- @field public datastore HueDriverDatastore persistent store --- @field public api_key_to_bridge_id table @@ -133,16 +135,44 @@ function HueDriver:run() Driver.run(self) end ----@param bridge_id string +---@param grandchild_devices { waiting_resource_info: HueResourceInfo, join_callback: fun(driver: HueDriver, waiting_resource_info: HueResourceInfo, parent_device: HueChildDevice)}[] +---@param waiting_for string +function HueDriver:queue_grandchild_device_for_join(grandchild_devices, waiting_for) + self.waiting_grandchildren = self.waiting_grandchildren or {} + + for _, waiting_info in ipairs(grandchild_devices) do + self.waiting_grandchildren[waiting_for] = self.waiting_grandchildren[waiting_for] or {} + table.insert(self.waiting_grandchildren[waiting_for], waiting_info) + end +end + +---@param new_device HueChildDevice +function HueDriver:check_waiting_grandchildren_for_device(new_device) + if not self.waiting_grandchildren then + return + end + local rid = utils.get_hue_rid(new_device) + for _, waiting in pairs(self.waiting_grandchildren[rid or ""] or {}) do + local waiting_info = waiting.waiting_resource_info + local join_callback = waiting.join_callback + if type(join_callback) == "function" then + cosock.spawn(function() + join_callback(self, waiting_info, new_device) + end) + end + end +end + +---@param bridge_network_id string ---@param bridge_info HueBridgeInfo -function HueDriver:update_bridge_netinfo(bridge_id, bridge_info) - if self.joined_bridges[bridge_id] then - local bridge_device = self:get_device_by_dni(bridge_id) --[[@as HueBridgeDevice]] +function HueDriver:update_bridge_netinfo(bridge_network_id, bridge_info) + if self.joined_bridges[bridge_network_id] then + local bridge_device = self:get_device_by_dni(bridge_network_id) --[[@as HueBridgeDevice]] if not bridge_device then log.warn_with({ hub_logs = true }, string.format( "Couldn't locate bridge device for joined bridge with DNI %s", - bridge_id + bridge_network_id ) ) return @@ -151,7 +181,7 @@ function HueDriver:update_bridge_netinfo(bridge_id, bridge_info) if bridge_info.ip ~= bridge_device:get_field(Fields.IPV4) then bridge_utils.update_bridge_fields_from_info(self, bridge_info, bridge_device) local maybe_api_client = bridge_device:get_field(Fields.BRIDGE_API) --[[@as PhilipsHueApi]] - local maybe_api_key = bridge_device:get_field(HueApi.APPLICATION_KEY_HEADER) or Discovery.api_keys[bridge_id] + local maybe_api_key = bridge_device:get_field(HueApi.APPLICATION_KEY_HEADER) or Discovery.api_keys[bridge_network_id] local maybe_event_source = bridge_device:get_field(Fields.EVENT_SOURCE) local bridge_url = "https://" .. bridge_info.ip diff --git a/drivers/SmartThings/philips-hue/src/init.lua b/drivers/SmartThings/philips-hue/src/init.lua index 8f20a40706..7372c96d2f 100644 --- a/drivers/SmartThings/philips-hue/src/init.lua +++ b/drivers/SmartThings/philips-hue/src/init.lua @@ -19,7 +19,7 @@ -- =============================================================================================== local Driver = require "st.driver" -local log = require "logjam" +local log = require "log" local st_utils = require "st.utils" -- trick to fix the VS Code Lua Language Server typechecking ---@type fun(val: table, name: string?, multi_line: boolean?): string diff --git a/drivers/SmartThings/philips-hue/src/lunchbox/sse/eventsource.lua b/drivers/SmartThings/philips-hue/src/lunchbox/sse/eventsource.lua index 44684d6650..d6d51018e1 100644 --- a/drivers/SmartThings/philips-hue/src/lunchbox/sse/eventsource.lua +++ b/drivers/SmartThings/philips-hue/src/lunchbox/sse/eventsource.lua @@ -5,7 +5,7 @@ local ssl = require "cosock.ssl" ---@type fun(sock: table, config: table?): table?, string? ssl.wrap = ssl.wrap -local log = require "logjam" +local log = require "log" local util = require "lunchbox.util" local Request = require "luncheon.request" local Response = require "luncheon.response" diff --git a/drivers/SmartThings/philips-hue/src/stray_device_helper.lua b/drivers/SmartThings/philips-hue/src/stray_device_helper.lua index c5a614f627..23cceb8c36 100644 --- a/drivers/SmartThings/philips-hue/src/stray_device_helper.lua +++ b/drivers/SmartThings/philips-hue/src/stray_device_helper.lua @@ -1,5 +1,5 @@ local cosock = require "cosock" -local log = require "logjam" +local log = require "log" local st_utils = require "st.utils" local Discovery = require "disco" diff --git a/drivers/SmartThings/philips-hue/src/utils.lua b/drivers/SmartThings/philips-hue/src/utils.lua index bfc98e3630..34d9a03420 100644 --- a/drivers/SmartThings/philips-hue/src/utils.lua +++ b/drivers/SmartThings/philips-hue/src/utils.lua @@ -1,4 +1,4 @@ -local log = require "logjam" +local log = require "log" local Fields = require "fields" local HueDeviceTypes = require "hue_device_types" @@ -44,7 +44,8 @@ function utils.safe_wrap_handler(handler) end local success, result = pcall(handler, driver, device, ...) if not success then - log.error_with({ hub_logs = true }, string.format("Failed to invoke capability command handler. Reason: %s", result)) + log.error_with({ hub_logs = true }, + string.format("Failed to invoke capability command handler. Reason: %s", result)) end return result end @@ -60,7 +61,7 @@ function utils.mirek_to_kelvin(mirek) return 1000000 / mirek end -- the data as it unpacks it; it just dispatches each event in the batch as it encounters it. -- We could implement 2x-6x press if we add some smarts to the SSE stream handling. function utils.get_supported_button_values(event_values) - local values = {"pushed"} + local values = { "pushed" } for _, event_value in ipairs(event_values) do if event_value == "long_press" then table.insert(values, "held") @@ -212,7 +213,8 @@ function utils.get_hue_rid(device) end if utils.is_dth_light(device) then - return nil, string.format("Can't get the Hue RID of migrated DTH light %s that hasn't completed migration", device.label) + return nil, + string.format("Can't get the Hue RID of migrated DTH light %s that hasn't completed migration", device.label) end local success, rid, _ = utils.parse_parent_assigned_key(device) @@ -221,11 +223,11 @@ function utils.get_hue_rid(device) end return - nil, - string.format( - "error establishing Hue RID from parent assigned key [%s]", - (device and device.parent_assigned_child_key) or "Parent Assigned Key Not Available" - ) + nil, + string.format( + "error establishing Hue RID from parent assigned key [%s]", + (device and device.parent_assigned_child_key) or "Parent Assigned Key Not Available" + ) end --- Get the HueDeviceType value for a device. If available on the device field, then it @@ -266,11 +268,11 @@ function utils.determine_device_type(device) end return - nil, - string.format( - "Couldn't determine device type for device %s", - (device and device.label) or "Unknown Device" - ) + nil, + string.format( + "Couldn't determine device type for device %s", + (device and device.label) or "Unknown Device" + ) end --- Attempts an exhaustive check of all the ways a device @@ -280,9 +282,9 @@ end ---@return boolean is_bridge true if the device record represents a Hue Bridge function utils.is_bridge(driver, device) return (device:get_field(Fields.DEVICE_TYPE) == "bridge") - or (driver.datastore.bridge_netinfo[device.device_network_id] ~= nil) - or utils.is_edge_bridge(device) or utils.is_dth_light(device) - or (device.parent_assigned_child_key == nil) + or (driver.datastore.bridge_netinfo[device.device_network_id] ~= nil) + or utils.is_edge_bridge(device) or utils.is_dth_light(device) + or (device.parent_assigned_child_key == nil) end --- Only checked during `added` callback, or as a later @@ -293,9 +295,9 @@ end ---@return boolean function utils.is_edge_bridge(device) return - device.device_network_id and - utils.is_valid_mac_addr_string(device.device_network_id) and - not (device.data and device.data.username) + device.device_network_id and + utils.is_valid_mac_addr_string(device.device_network_id) and + not (device.data and device.data.username) end --- Only checked during `added` callback, or as a later @@ -306,9 +308,9 @@ end ---@return boolean function utils.is_edge_light(device) return - device.parent_assigned_child_key ~= nil and - not utils.is_valid_mac_addr_string(device.device_network_id) and - not (device.data and device.data.username and device.data.bulbId) + device.parent_assigned_child_key ~= nil and + not utils.is_valid_mac_addr_string(device.device_network_id) and + not (device.data and device.data.username and device.data.bulbId) end --- Only checked during `added` callback @@ -333,6 +335,35 @@ function utils.is_dth_light(device) and device.data.username ~= nil end +--- Get the Hue Bridge for a Device; useful when you don't know if you have a child +--- or a grandchild device and you need to walk up the family tree. Will return the +--- passed argument if the argument is a bridge. +---@param driver HueDriver +---@param device HueDevice +---@param parent_device_id string? +---@return HueBridgeDevice? bridge_device +function utils.get_hue_bridge_for_device(driver, device, parent_device_id) + log.trace(string.format("------------------------ Looking for bridge for %s with parent_device_id %s", device.label, device.parent_device_id)) + if utils.is_bridge(driver, device) then + log.trace(string.format("------------------------- %s is a bridge", device.label)) + return device --[[ @as HueBridgeDevice ]] + end + + local parent_device_id = parent_device_id or device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID) + local parent_device = driver:get_device_info(parent_device_id) + if not parent_device then + log.trace(string.format("------------------------- get_device_info for %s was nil", parent_device_id)) + return nil + end + + log.trace(string.format("------------------------- parent_device label is %s, checking if bridge", parent_device.label)) + if parent_device and utils.is_bridge(driver, parent_device) then + return parent_device --[[ @as HueBridgeDevice ]] + end + + return utils.get_hue_bridge_for_device(driver, parent_device) +end + --- build a exponential backoff time value generator --- ---@param max number the maximum wait interval (not including `rand factor`) diff --git a/drivers/SmartThings/philips-hue/src/utils/hue_bridge_utils.lua b/drivers/SmartThings/philips-hue/src/utils/hue_bridge_utils.lua index 302de61662..0c37d15250 100644 --- a/drivers/SmartThings/philips-hue/src/utils/hue_bridge_utils.lua +++ b/drivers/SmartThings/philips-hue/src/utils/hue_bridge_utils.lua @@ -1,5 +1,5 @@ local cosock = require "cosock" -local log = require "logjam" +local log = require "log" local json = require "st.json" local st_utils = require "st.utils" diff --git a/drivers/SmartThings/philips-hue/src/utils/hue_multi_service_device_utils.lua b/drivers/SmartThings/philips-hue/src/utils/hue_multi_service_device_utils.lua index af3a75384f..ee959a1c93 100644 --- a/drivers/SmartThings/philips-hue/src/utils/hue_multi_service_device_utils.lua +++ b/drivers/SmartThings/philips-hue/src/utils/hue_multi_service_device_utils.lua @@ -4,14 +4,15 @@ local lazy_disco_handlers = utils.lazy_handler_loader("disco") ---@class MultiServiceDeviceUtils local multi_service_device_utils = {} +---@param driver HueDriver ---@param sensor_device_type HueDeviceTypes ---@param api_instance PhilipsHueApi ---@param device_service_id string ----@param bridge_id string +---@param bridge_network_id string ---@return table? nil on error ---@return string? err nil on success -function multi_service_device_utils.get_all_service_states(sensor_device_type, api_instance, device_service_id, bridge_id) - return lazy_disco_handlers[sensor_device_type].update_state_for_all_device_services(api_instance, device_service_id, bridge_id) +function multi_service_device_utils.get_all_service_states(driver, sensor_device_type, api_instance, device_service_id, bridge_network_id) + return lazy_disco_handlers[sensor_device_type].update_state_for_all_device_services(driver, api_instance, device_service_id, bridge_network_id) end return multi_service_device_utils