/* * * * Licensed Virtual 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, WIyTHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License * for the specific language governing permissions and limitations under the License. * * Date Who Description * ------------- ------------------- --------------------------------------------------------- * 10Apr2026 thebearmay Add a display list of all IDs */ import groovy.transform.Field import groovy.json.JsonSlurper import groovy.json.JsonOutput static String version() { return '0.0.3' } definition ( name: "App and Device ID Logger", namespace: "thebearmay", author: "Jean P. May, Jr.", description: "Keeps a running list of every App and Dev ID used, even after the device/app has been deleted", category: "Utility", importUrl: "https://raw.githubusercontent.com/thebearmay/hubitat/main/apps/appDevIdLogger.groovy", installOnOpen: true, oauth: false, iconUrl: "", iconX2Url: "" ) preferences { page name: "mainPage" } def installed() { // log.trace "installed()" state?.isInstalled = true initialize() } def updated(){ // log.trace "updated()" if(!state?.isInstalled) { state?.isInstalled = true } if(debugEnable) runIn(1800,logsOff) } def initialize(){ } void logsOff(){ app.updateSetting("debugEnabled",[value:"false",type:"bool"]) } def mainPage(){ dynamicPage (name: "mainPage", title: "", install: true, uninstall: true) { section("

Settings

", hideable:true, hidden: true){ input "debugEnabled", "bool", title: "Enable Debug Logging", defaultValue: false, submitOnChange:true input "nameOverride", "text", title: "New Name for Application", multiple: false, required: false, submitOnChange: true, defaultValue: app.getLabel() if(nameOverride != app.getLabel()) app.updateLabel(nameOverride) input "snapFreq", "number", title:"Minutes between snapshots", defaultValue:60, submitOnChange:true input "displayList", "bool", title:"Automatically display list of IDs when App opens", defaultValue: false, submitOnChange:true } section("") { if(minVerCheck("2.4.0.0")) { input "startSnaps", "button", title:"Start/Restart" if(state.startPressed) { state.startPressed = false paragraph "Start requested" getSnapshot() } } else paragraph "Must be on HE v2.4.0.0 or Higher" } section(title: "Find App/Dev"){ input "aOrD", "enum", title:"App or Device", options: ["APP","DEV"], submitOnChange:true, width:4 input "iKey", "number", title:"ID number", submitOnChange:true, width:4 input "srch", "button", title:"Search" if(state.srch) { state.srch = false try { buData = downloadHubFile('appDevID.json') } catch (ignore) { buData = '' paragraph "App/Device Data File not Found" } if(buData.size() > 0){ jSlurp = new JsonSlurper() foundIt = false oldData = jSlurp.parseText(new String(buData, "UTF-8")) oldData.each { if(debugEnabled) log.debug "$it" if("${it.key}" == "$aOrD-$iKey"){ paragraph "Found: ${it.value} Last Seen: ${it.lastSeen}" foundIt = true } } if(!foundIt) paragraph "$aOrD$iKey - not found" } } if(displayList){ paragraph buildDisp() } } } } String buildDisp(){ String retVal = '' try { buData = downloadHubFile('appDevID.json') if(buData.size() > 0){ jSlurp = new JsonSlurper() oldData = jSlurp.parseText(new String(buData, "UTF-8")) retVal = '' oldData.sort{it.key}.each { if(debugEnabled) log.debug "$it" retVal += "" } retVal+="
IDNameLast Seen
${it.key}${it.value}${it.lastSeen}
" } } catch (ignore) { buData = '' return "App/Device Data File not Found" } return retVal } String getName(keyVal, appList){ rVal = '' appList.each{ if(debugEnabled) log.debug "$keyVal ${it.key}" if(it.key == keyVal) rVal = it.value } return rVal } void getSnapshot() { if(!snapFreq) snapFreq = 60 idList = getAppsList() dList = getDevList() idList+=dList try { buData = downloadHubFile('appDevID.json') } catch (ignore) { buData = '' } if(buData.size() < 1){ buData = JsonOutput.toJson(idList) uploadHubFile('appDevID.json',buData.getBytes()) runIn(snapFreq*60, 'getSnapshot') return } def jSlurp = new JsonSlurper() oldData = jSlurp.parseText(new String(buData, "UTF-8")) //idL = JsonOutput.toJson(idList) //jSlurp.parseText(idList) idList.each{ idl -> foundIt = false oldData.each{ od -> if(od.key == idl.key){ //log.debug "${od.key}
O:${od.lastSeen}
I:${idl.lastSeen}" od.lastSeen = idl.lastSeen od.value = idl.value foundIt = true } } if(!foundIt) oldData.add(idl) } mergedData = JsonOutput.toJson(oldData) uploadHubFile('appDevID.json',mergedData.toString().getBytes()) runIn(snapFreq*60, 'getSnapshot') } ArrayList getAppsList() { Map requestParams = [ uri: "http://127.0.0.1:8080", path:"/hub2/appsList" ] httpGet(requestParams) { resp -> wrkList = [] resp.data.apps.each{ wrkMap =[key:"${it.key}",id:"${it.data.id}",value:"${it.data.name}",lastSeen:"${new Date()}"] wrkList.add(wrkMap) it.children.each{ wrkMap =[key:"${it.key}",id:"${it.data.id}",value:"${it.data.name}",lastSeen:"${new Date()}"] wrkList.add(wrkMap) } } return wrkList.sort{it.key} } } ArrayList getDevList() { Map requestParams = [ uri: "http://127.0.0.1:8080", path:"/hub2/devicesList" ] httpGet(requestParams) { resp -> wrkList = [] resp.data.devices.each{ wrkMap =[key:"${it.key}",id:"${it.data.id}",value:"${it.data.name}",lastSeen:"${new Date()}"] wrkList.add(wrkMap) it.children.each{ wrkMap =[key:"${it.key}",id:"${it.data.id}",value:"${it.data.name}",lastSeen:"${new Date()}"] wrkList.add(wrkMap) } } return wrkList.sort{it.key} } } def readJsonPage(fName){ def params = [ uri: fName, contentType: "application/json", //textParser: false, headers: [ "Connection-Timeout":600 ] ] try { httpGet(params) { resp -> if(resp!= null) { return resp.data } else { log.error "Read External - Null Response" return null } } } catch (exception) { log.error "Read JFile Error: ${exception.message}" return null } } Boolean minVerCheck(vStr){ //check if HE is >= to the requirement fwTokens = location.hub.firmwareVersionString.split("\\.") vTokens = vStr.split("\\.") if(fwTokens.size() != vTokens.size()) return false rValue = true for(i=0;i fwTokens[i].toInteger()) rValue=false } return rValue } def appButtonHandler(btn) { switch(btn) { case "startSnaps": state.startPressed = true break case "srch": state.srch = true break default: log.error "Undefined button $btn pushed" break } }