[RELEASE] Home Assistant Device Bridge (HADB)

Huh, completely missed that hubitat can do matter directly, thanks for the heads up!

IP and ports are all correct, so I guess I'll try to mess with my router to make sure nothing is is happening that the port is being blocked.

Hubitat can do Matter over WiFi devices directly.

Matter over Thread devices will need a Thread Border Router (TBR) from Apple, Google, Amazon, Home Assistant, etc... to allow the device to communicate with the Hubitat Hub over Ethernet. Current models of Hubitat Hubs do not have a Thread radio, and thus rely on other 3rd party devices to perform the TBR duties.

Often, it is easiest to always pair your Matter devices with your existing Apple, Google, Amazon, ... system first. Then, using that system, share the Matter device with Hubitat using Matter's Multi-Admin functionality. The reason for doin it in this order is that you can then use that original system to share that Matter device with another system. Hubitat does not yet support sharing Matter devices with other systems.

3 Likes

I have been testing the Home Assistant as the thread border router to stay away from Google, Apple, Amazon, etc. I have done open/close sensor that is matter/thread and it took a lot of searching why I couldn't get HA to share the device until there was a mention about the HA app needed to import the keys for the thread network. Once done I was able to get the pairing codes into the Hubitat. Seems to work ok. Haven't seen any latency or lost events since setting up 3 months ago. I did have to flash a Zigbee radio to thread mode for the HA and that was about it.

4 Likes

Having problems pulling any zigbee or zwave devices Home Assistant from Hubitat.
I feel like i missed an importnat step somewhere.
I have the app drivers code all working and set up .

This app (HADB) brings HA stuff into Hubitat, not the other way around.

1 Like

Check out this HE to HA integration.

1 Like

Hey, im in a bit of a pickle here. I have two zigbee lights that ive added to HA, actually moved out of Hubitat into Home Assistant.

These are 'Sengled Element Classic' blubs. The problem is I cannot get them to do level changes when imported from HA using the HADB. Any ideas?

Also, is there any possiblity of importing a ThirdReality single button controller (zigbee). When I try it only shows diagnostic entities.

Thanks.

Does the child device in HE reports the appropriate change when the bulb is activated or dimmed in HA?

Edit: I reread your post. Sorry, HADB does not support level change (no such service for light entities in HA).

1 Like

This morning I backed up my HA server and after reboot none of my HADB devices (Hue Motion) were responsive within Hubitat. One of the log entries showed:
2026-01-21 08:44:51.390 AM debug Failed to connect to /192.168.1.213:8123

Obviously this was my my HA server was down.

I managed to get things working again clicking the Close button on the HADB device page.
My question is, is this the right way to get things working again after an HA server reboot or can this be automated?

I use HADB and it always reconnects automatically. It can take up to ten minutes to reconnect, as it slows down the frequency of attempts the longer HA is down to prevent pounding on the network.

Also, after it does reconnect, the devices may not be in synch again until the device status changes on the HA side.

6 Likes

"Initialize" would be the way to reconnect immediately.

As @ogiewon pointed out, it is allready.

6 Likes

Version 2.19:

  • Modify light entity support to accomodate for upcoming breaking change to CT in HA version 2026.03
8 Likes

Hi -
I think I finally figure out why this was not obvious to me - the device selection is very tedious because of the whole HA entity structure. I have added filtering to the app take a look if this works for you I can either raise a PR or you can incorporate it - thanks

/*
* 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
* 2.5        2024-05-08 Yves Mercier       Add support for valves
* 2.6        2024-05-31 Yves Mercier       Add support for humidifiers
* 2.7        2024-08-13 Yves Mercier       Add support for events, remove HA states response from debug log
* 2.10       2024-11-12 Yves Mercier       Add support for text and vacuums
* 2.11       2024-11-30 Yves Mercier       Add limited support for media_player entity
* 2.12       2024-12-15 Yves Mercier       Add support for select entity
* 2.15       2025-01-17 Yves Mercier       Fix "Toggle all On/Off" included as an entity
* 2.18       2025-01-18 Yves Mercier       Add support for siren entity
* 2.19       2025-02-28 Enis Hoca          Added discovery debug logging; entity type filter and name search in discoveryPage
*/

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("<b>Home Assistant Device Bridge</b>")
        {
            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("<b>Configuration options:</b>")
        {
            href(page: "discoveryPage", title: "<b>Discover and select devices</b>", 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: "<b>Return to previous page</b>", description: "")
    }
}

def getSupportedDomains()
{
    return [
        "binary_sensor", "button", "climate", "cover", "device_tracker", "event",
        "fan", "humidifier", "input_boolean", "input_button", "input_number",
        "input_select", "input_text", "light", "lock", "media_player", "number",
        "select", "sensor", "siren", "switch", "text", "vacuum", "valve"
    ]
}

def discoveryPage(params)
{
    dynamicPage(name: "discoveryPage", title: "", install: true, uninstall: true)
    {
        if(wasButtonPushed("cleanupUnused"))
        {
            cullGrandchildren()
            clearButtonPushed()
        }

        // Only re-query HA when navigating here fresh
        if(params?.runDiscovery)
        {
            state.entityList = [:]

            logDebug("discoveryPage: Starting HA entity discovery from ${getBaseURI()}states")

            def resp = httpGetExec(genParamsMain("states"))

            if(resp?.data)
            {
                logDebug("discoveryPage: HA returned ${resp.data.size()} total states")

                resp.data.each
                {
                    def domain = it.entity_id?.tokenize(".")?.getAt(0)
                    if(domain && getSupportedDomains().contains(domain))
                    {
                        state.entityList.put(it.entity_id, "${it.attributes?.friendly_name} (${it.entity_id})")
                    }
                }

                state.entityList = state.entityList.sort { it.value }

                logDebug("discoveryPage: ${state.entityList.size()} entities matched supported domains and are available for selection")
            }
            else
            {
                logDebug("discoveryPage: No data received from HA β€” check IP (${ip}), port (${port}), token, and SSL settings")
            }
        }

        // ── Build filtered view ───────────────────────────────────────────────
        def fullList     = state.entityList ?: [:]
        def filteredList = fullList

        if(typeFilter && typeFilter != "__ALL__")
        {
            filteredList = filteredList.findAll { k, v -> k.startsWith("${typeFilter}.") }
        }

        if(nameFilter && nameFilter.trim())
        {
            def term = nameFilter.trim().toLowerCase()
            filteredList = filteredList.findAll { k, v ->
                (v?.toLowerCase()?.contains(term)) || (k?.toLowerCase()?.contains(term))
            }
        }

        // ── KEY FIX: always re-inject already-selected items into options ─────
        // This prevents Hubitat from dropping selections that are filtered out of view.
        def selectedOptions = [:]
        if(includeList)
        {
            includeList.each { entityId ->
                if(fullList.containsKey(entityId))
                {
                    selectedOptions.put(entityId, fullList[entityId])
                }
            }
        }
        // Merge: selected items always present; filtered items show for new picks
        def displayOptions = (filteredList + selectedOptions).sort { it.value }

        // ── Filter controls ───────────────────────────────────────────────────
        section("<b>Filter Device List</b>")
        {
            input name: "typeFilter",
                  type: "enum",
                  title: "Filter by Entity Type",
                  options: ["__ALL__": "β€” All Types β€”"] + getSupportedDomains().collectEntries { [(it): it] },
                  defaultValue: "__ALL__",
                  required: false,
                  submitOnChange: true

            input name: "nameFilter",
                  type: "text",
                  title: "Search by Name or Entity ID (partial match)",
                  required: false,
                  submitOnChange: true

            def selectedCount = includeList?.size() ?: 0
            paragraph "<i>Showing <b>${filteredList.size()}</b> of <b>${fullList.size()}</b> entities &nbsp;|&nbsp; <b>${selectedCount}</b> selected total</i>"
        }

        // ── Selection input β€” filtered view + selected items always present ───
        section
        {
            input name: "includeList",
                  type: "enum",
                  title: "Select devices to <b>include</b> from Home Assistant",
                  options: displayOptions,
                  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()
{
    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)
    {
        ch.updateSetting("ip", ip)
        ch.updateSetting("port", port)
        ch.updateSetting("token", token)
        ch.updateSetting("secure", secure)
        def filterListForChild = includeList?.join(",")
        filterListForChild -= "Toggle All On/Off"
        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)
{
    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?.uri}")

    try
    {
        def result
        httpGet(params)
        { resp ->
            if(resp)
            {
                result = resp
            }
        }
        return result
    }
    catch (Exception e)
    {
        logDebug("httpGetExec() failed: ${e.message}")
        if(throwToCaller) { throw(e) }
    }
}
4 Likes

When the dog sings opera, don't complain that his pitch isn't perfect.

4 Likes

This is quite interesting and would be very helpfull. I surely would like to add this enhancement to the HADB app.

I ran into some issues when I tried it though. I'll message you with my findings.

6 Likes

Version 2.20

  • Add discovery debug logging; entity type filter and name search in discoveryPage of the app, thanks to @enishoca
  • Add an option to translate HE "auto" mode to HA "heat_cool" in thermostat child driver
12 Likes

Thank you @ymerj and @enishoca for adding these new filter and search features. They've been on my wish list for a very long time. I've updated the original post (sorry, it was long overdue) and added a brief explanation.

If I messed up anything or missed some important information that's been needed, please let me know.

7 Likes