/* * Home Assistant to Hubitat Integration * * Description: * Allow control of HA devices. * * Required Information: * Home Asisstant IP and Port number * Home Assistant long term Access Token * * Features List: * * Licensing: * Copyright 2021 tomw * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License * for the specific language governing permissions and limitations under the License. * * Version Control: * 0.1.22 2021-02-24 tomw Optional configuration app to selectively filter out Home Assistant devices * 0.1.23 2021-02-25 Dan Ogorchock Switched logic from Exclude to Include to make more intuitive. Sorted Device List. * 0.1.32 2021-09-27 kaimyn Add option to use HTTPS support in configuration app * 0.1.45 2022-06-06 tomw Added confirmation step before completing select/de-select all * 0.1.46 2022-07-04 tomw Advanced configuration - manual add/remove of devices; option to disable filtering; unused child cleanup * 0.1.52 2023-02-02 tomw UI improvements for app usability * 0.1.53 2023-02-19 tomw Allow multiple instances of HADB app to be installed * 0.1.58 2023-08-02 Yves Mercier Add support for number domain * 0.1.62 2023-08-02 Yves Mercier Add support for input_number domain * 0.1.63 2024-01-11 tomw Remove entityList state * 2.0 2024-01-20 Yves Mercier Introduce entity subscription model * 2.3 2024-03-26 Yves Mercier Add support for buttons */ definition( name: "Home Assistant Device Bridge", namespace: "tomw", author: "tomw", description: "", category: "Convenience", importUrl: "https://raw.githubusercontent.com/ymerj/HE-HA-control/main/haDeviceBridgeConfiguration.groovy", iconUrl: "", iconX2Url: "", iconX3Url: "") preferences { page(name: "mainPage") page(name: "discoveryPage") page(name: "advOptionsPage") } def mainPage() { dynamicPage(name: "mainPage", title: "", install: true, uninstall: true) { section("Home Assistant Device Bridge") { input ("ip", "text", title: "Home Assistant IP Address", description: "HomeAssistant IP Address", required: true) input ("port", "text", title: "Home Assistant Port", description: "HomeAssistant Port Number", required: true, defaultValue: "8123") input ("token", "text", title: "Home Assistant Long-Lived Access Token", description: "HomeAssistant Access Token", required: true) input name: "secure", type: "bool", title: "Require secure connection", defaultValue: false, required: true input name: "ignoreSSLIssues", type: "bool", title: "Ignore SSL Issues", defaultValue: false, required: true input name: "enableLogging", type: "bool", title: "Enable debug logging?", defaultValue: false, required: true } section("Configuration options:") { href(page: "discoveryPage", title: "Discover and select devices", description: "Query Home Assistant for all currently configured devices. Then select which entities to Import to Hubitat.", params: [runDiscovery : true]) } section("App Name") { label title: "Optionally assign a custom name for this app", required: false } } } def linkToMain() { section { href(page: "mainPage", title: "Return to previous page", description: "") } } def discoveryPage(params) { dynamicPage(name: "discoveryPage", title: "", install: true, uninstall: true) { if(wasButtonPushed("cleanupUnused")) { cullGrandchildren() clearButtonPushed() } if(params?.runDiscovery) { state.entityList = [:] def domain // query HA to get entity_id list def resp = httpGetExec(genParamsMain("states")) logDebug("states response = ${resp?.data}") if(resp?.data) { resp.data.each { domain = it.entity_id?.tokenize(".")?.getAt(0) if(["fan", "switch", "light", "binary_sensor", "sensor", "device_tracker", "cover", "lock", "climate", "input_boolean", "number", "input_number", "button", "input_button"].contains(domain)) { state.entityList.put(it.entity_id, "${it.attributes?.friendly_name} (${it.entity_id})") } } state.entityList = state.entityList.sort { it.value } } } section { input name: "includeList", type: "enum", title: "Select any devices to include from Home Assistant Device Bridge", options: state.entityList, required: false, multiple: true, offerAll: true } section("Administration option") { input(name: "cleanupUnused", type: "button", title: "Remove all child devices that are not currently selected (use carefully!)") } linkToMain() } } def cullGrandchildren() { // remove all child devices that aren't currently on either filtering list def ch = getChild() ch?.getChildDevices()?.each() { def entity = it.getDeviceNetworkId()?.tokenize("-")?.getAt(1) if(!includeList?.contains(entity)) { ch.removeChild(entity) } } } def logDebug(msg) { if(enableLogging) { log.debug "${msg}" } } def installed() { def ch = getChild() if(!ch) { ch = addChildDevice("ymerj", "HomeAssistant Hub Parent", now().toString(), [name: "Home Assistant Device Bridge", label: "Home Assistant Device Bridge (${ip})", isComponent: false]) } if(ch) { // propoagate our settings to the child ch.updateSetting("ip", ip) ch.updateSetting("port", port) ch.updateSetting("token", token) ch.updateSetting("secure", secure) def filterListForChild = includeList?.join(",") ch.updateDataValue("filterList", filterListForChild) ch.updated() } state.remove("entityList") } def getChild() { return getChildDevices()?.getAt(0) } def uninstalled() { deleteChildren() } def deleteChildren() { getChildDevices()?.each { deleteChildDevice(it.getDeviceNetworkId()) } } def updated() { installed() } void appButtonHandler(btn) { // flag button pushed and let pages sort it out setButtonPushed(btn) } def setButtonPushed(btn) { state.button = [btn: btn] } def wasButtonPushed(btn) { return state.button?.btn == btn } def clearButtonPushed() { state.remove("button") } def genParamsMain(suffix, body = null) { def params = [ uri: getBaseURI() + suffix, headers: [ 'Authorization': "Bearer ${token}", 'Content-Type': "application/json" ], ignoreSSLIssues: ignoreSSLIssues ] if(body) { params['body'] = body } return params } def getBaseURI() { if(secure) return "https://${ip}:${port}/api/" return "http://${ip}:${port}/api/" } def httpGetExec(params, throwToCaller = false) { logDebug("httpGetExec(${params})") try { def result httpGet(params) { resp -> if (resp) { //logDebug("resp.data = ${resp.data}") result = resp } } return result } catch (Exception e) { logDebug("httpGetExec() failed: ${e.message}") //logDebug("status = ${e.getResponse().getStatus().toInteger()}") if(throwToCaller) { throw(e) } } }