Obtaining Devices Map

I wanted to get a map of all my driver id's/drive name pairs so I could do the following without needing an input statement in the preference section:

def DeviceMap = GetDeviceMap()
app.updateSetting("EM", [value:DeviceMap["EM"], type:"capability.*"])
subscribe(EM, "dryerWatts", dryerHandler)

I could not find anything that would work for me in a custom app or a driver so I coded the following routine:

def GetDeviceMap() {
    def deviceMap = [:]  // create empty device map
    for (int i = 1; i < 500; i++) {
        def deviceId = i.toString()
        app.updateSetting("xyz", [value: deviceId, type: "capability.*"])
        def obj = settings.xyz

        try {
            // only call getDisplayName if object is really a device
            def name = obj?.hasProperty('displayName') ? obj.displayName : obj?.toString()

            // lets make a device map
            deviceMap[name] = deviceId
//            log.debug "${i} xyz = ${name}"
            
        } catch (e) {
//            log.warn "${i} caused error: ${e.message}"
        }
    }
    
    log.debug "Device Map: ${deviceMap}"
    
    return deviceMap
}

It works great but I would like to know if I have a more straight forward way of getting this map. The api's that looked like they might work for me seemed to be restricted in both app's and driver's

Thanks

http://<hub IP>/hub2/devicesList will give you the device name, ID, etc. but to get the device object needed for the subscribe you would need to authorize the device manually.

1 Like

This works great from my windows Chrome browser but I cannot obtain it within Hubitat. It is refused when I try execute that URL in a custom app (with that URL and also 127.0.0.1, and localhost). Can I somehow get the device's map any other way besides what I posted at the beginning of this thread within a custom app?

Thanks

use http://127.0.0.1:8080 when accessing endpoints from within app or driver code.

when I tried that I got the following:

app:15922025-05-19 06:17:30.137 AMerrorFailed to fetch device list: Connect to 127.0.0.1:80 [/127.0.0.1] failed: Connection refused (Connection refused)
app:15922025-05-19 06:17:30.122 AMdebugurl = http://127.0.0.1/hub2/devicesList:8080

Port goes with the IP

http://127.0.0.1:8080/hub2/devicesList

So maybe I have stayed up WAY to late! LOL... thanks for the catch. I wrote the following program to get the map:

import groovy.json.JsonSlurper

definition(
    name: "Device Map Importer",
    namespace: "custom",
    author: "Hubi",
    description: "Fetch and parse device info from external hub",
    category: "Utility",
    iconUrl: "",
    iconX2Url: ""
)

preferences {
    section("Remote Hub URL") {
        input "remoteHubIp", "text", title: "Remote Hub IP", defaultValue: "127.0.0.1:8080", required: true
        input "remotePath", "text", title: "Device List Path", defaultValue: "/hub2/devicesList", required: true
    }
}

def installed() {
    initialize()
}

def updated() {
    unsubscribe()
    initialize()
}

def initialize() {
    runIn(5, fetchDeviceMap)
}

def fetchDeviceMap() {
    def url = "http://${remoteHubIp}${remotePath}"

    log.debug "url = " + url
    
    try {
        httpGet([uri: url, contentType: "application/json"]) { resp ->
            if (resp.status == 200) {
                def result = new JsonSlurper().parseText(resp.data.toString())
                def deviceMap = [:]

                result.each { device ->
                    def id = device?.id
                    def name = device?.name
                    if (id && name) {
                        deviceMap["${id}"] = name
                    }
                }

                log.info "Device Map:\n${deviceMap.inspect()}"
                // You can store it in state if you want to use it later
                state.deviceMap = deviceMap
            } else {
                log.error "HTTP Error: ${resp.status}"
            }
        }
    } catch (Exception e) {
        log.error "Failed to fetch device list: ${e.message}"
    }
}

When I executed it It kinda worked but at the beginning of the logged data I got:

app:15932025-05-19 06:24:40.086 AM error
Failed to fetch device list: expecting '}' or ',' but got current char 'd' with an int value of 100 The current character read is 'd' with an int value of 100 expecting '}' or ',' but got current char 'd' with an int value of 100 line number 1 index number 1 {devices=[{child=false, children=, data={currentStates=[{key=commsError, value=false}], dashboardTypes=[Outlet, Switch, Light], defaultCurrentState=null, defaultIcon=null, deviceTypeId=991, disabled=false, dni=8cb2f298-f035-490f-87a1-3ce31d586952, homekit=false, hubMesh=false, id=305, lastActivity=2024-09-29T00:44:19+0000, level=0, linked=false, name=abc, notes=, remoteDeviceUrl=, roomId=null, roomName=, secondaryName=TpLink Plug EM**...(tons more data)**,

So I got an error at the beginning and then tons of GOOD data? I will look into this and try to figure it out. I am almost there! I really appreciate the help THANK YOU MUCH

I just noticed that my app is formatting for a map and the data is not a map.

Thanks Again

Correct, it is JSON. Couple of ways to parse it into a usable structure - most common is using the jsonSlurper to produce a map.

1 Like

Appreciate that... Thanks

1 Like

Another way to get the device map is as follows:

import groovy.json.JsonSlurper

definition(
    name: "Device Map Importer (Native JSON)",
    namespace: "custom",
    author: "Hubi",
    description: "Imports Hubitat's internal device list using native JSON parsing",
    category: "Utility",
    iconUrl: "",
    iconX2Url: ""
)

preferences {
    section("Internal Endpoint") {
        input "remoteHubIp", "text", title: "Hub IP", defaultValue: "127.0.0.1:8080"
        input "remotePath", "text", title: "Path", defaultValue: "/hub2/devicesList"
    }
}

def installed() { initialize() }
def updated() { unsubscribe(); initialize() }

def initialize() {
    runIn(5, fetchDeviceMap)
}

def fetchDeviceMap() {
    def url = "http://${remoteHubIp}${remotePath}"
    log.debug "🔗 Fetching from: ${url}"

    try {
        httpGet([uri: url, contentType: "application/json"]) { resp ->
            if (resp.status == 200) {
                def result = resp.data  // Use native object
                def deviceMap = [:]

                result?.devices?.each { device ->
                    def id = device?.data?.id
                    def name = device?.data?.name
                    if (id && name) {
                        deviceMap["${id}"] = name
                    }
                }

                state.deviceMap = deviceMap
                log.info "✅ Device map (${deviceMap.size()} devices)"
                deviceMap.each { k, v -> log.debug "🟢 ${k} → ${v}" }
            } else {
                log.error "❌ HTTP Error: ${resp.status}"
            }
        }
    } catch (Exception e) {
        log.error "❌ Exception: ${e.message}"
    }
}

It is much faster at producing the map.

Thanks For All The Help

2 Likes

I'm still confused as to why you need a map of every device on the hub in an app, but glad you got something working that works for you.

Why search for a device in a big list when you can just add the device you need? I'm not saying there is not a reason, I just can't think of one.

I made a custom app in which I had 8 light devices and a few other devices for a total of 11. An especially during development of the app I had to uninstall and install several times and I felt that me taking the time to input the 11 times was a total waste of time. In my case the input would always be the same and I wanted to hard code it. In a situation in which you want to choose then great you have a means to do that but I find that in the apps that I created so far that 100% of the time in makes more sense to hard code and not fool around with inputting. In my 11 input app case if I did not have a map I would have to do a one liner for each of the 11. I also was thinking to go the next step and have an app or driver that produces the map and make it available to all my other apps and those other apps can do a simple lookup based on the device name for each device it needed an NOT need any other coding in which to use that device. And, of course, the other apps would not have the need to create the map again. This next step would probably be better if instead of a map a call would be needed so that the map could be re-created each time so it picks up new devices that might have joined the hubitat environment.

Ok. So why pull it all in? A single line for each device using updateSetting() in the install() method will avoid ever having to choose the devices as user input again.

Do you not have to add code to "look up" a specific device to add from your full list? If you have to add code anyway, why pull in everything and then add code to find the device you want, when one line of code in the install() method will put it there specifically for you with updateSetting()?

I just feel it is clutter and it doesn't actually provide any benefit to pull them all in and add code to find the device instead of just adding the device you want by device ID. You can get the device ID from the URL of the device page, then "hard code" it into your app with updateSetting(). One and done.

It is like trying to find someone in a large group by having them all walk by you until you find the person, instead of just calling out their name and having them come forward.

And just one more item:

Why search for a device in a big list when you can just add the device you need?

After the map is created by just knowing the device name you do a lookup and no additional search is needed:

app.updateSetting("EM", [value:DeviceMap["EM"], type:"capability.*"])

Also you never have to manually search the device list in Hubitat to find the id number, you just need the name which you probably already know. Also if the device number changes at some point you have to change your app instead of just re-installing.

Device IDs do not change though, unless you unpair it and repair it, but the label can be changed at will. It would still seem better from my perspective to use deviceID, since it is easy to get manually from the device page and it generally cannot be changed, only replaced with a new device pairing. If you change the label of a device, you have to update your code.

I do see what you are saying, if you really want to add code to pull in a device using the device label for a device, then you have to already have that association stored somewhere. I just wouldn't bother with all that.

void getDeviceList()
{
    if (debug) log.info "In get Device List"
    
   def params = [ uri: "http://127.0.0.1:8080/device/listJson?capability=capability.*",
                 contentType: "application/json",
                 timeout: 20]
    
    try {
	     httpGet(params) { resp ->   
           if (debug) log.warn "resp = ${resp.data}"
           
           def devices1 = resp.data  as ArrayList
           log.info "Device table items loaded: ${devices1.size()}"
      
           sendEvent(name: "numberOfDevices", value: devices1.size())
           state.hubDevices = devices1
   
         }
    }
      catch (Exception e) {
        log.error "EXCEPTION CAUGHT: ${e.message} ON LINE ${e.stackTrace.find{it.className.contains("user_")}?.lineNumber}"   
      }  
}