/* Tile Multi-Device Template Manager * * 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, 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. * * Date Who Description * =========== =========== ===================================================== * 2022-08-30 thebearmay add file list and template checking * 2022-09-05 thebearmay add @room * 2022-09-18 thebearmay handle template read error * 2023-01-05 thebearmay add a filter for template selection * 2023-02-07 thebearmay Allow the use of Hub Variables * 2023-09-20 thebearmay Security screen lock issue */ static String version() { return '0.1.1' } definition ( name: "Tile Multi-Device Template Manager", namespace: "thebearmay", author: "Jean P. May, Jr.", description: "Use a template file to generate an HTML element for multiple named devices.", category: "Utility", importUrl:"https://raw.githubusercontent.com/thebearmay/hubitat/main/tileTemplate/ttMultiDevMgr.groovy", installOnOpen: true, oauth: false, singleThreaded: true, iconUrl: "", iconX2Url: "" ) preferences { page name: "mainPage" page name: "previewTemplate" } void installed() { // log.trace "installed()" state?.isInstalled = true initialize() } void updated(){ // log.trace "updated()" if(!state?.isInstalled) { state?.isInstalled = true } if(debugEnable) runIn(1800,logsOff) } void initialize(){ } void logsOff(){ app.updateSetting("debugEnable",[value:"false",type:"bool"]) } def mainPage(){ dynamicPage (name: "mainPage", title: "

${app.getLabel()}
v${version()}

", install: true, uninstall: true) { if (app.getInstallationState() == 'COMPLETE') { section("Main") { state.validTemplate = false List fList input "mustContain", "string", title:"Filter to Templates that contain", required:false, submitOnUpdate: true, width:4 input "applyFilter", "button", title: "Apply Filter" if(state.afPushed) { state.afPushed = false } if(mustContain != null) fList = listFiles("$mustContain") else fList = listFiles() input "templateName", "enum", title: "Template to Process", required: false, width:5, submitOnUpdate:true, options:fList input "templateCheck", "button", title:"Check Template" if(templateName != null && state?.tCheck == true) { List devList = templateScan() if (devList.size > 0){ devListE = [] devList.each { if("$it".isNumber()) devListE.add("$it") else devListE.add("variable:$it") } state.validTemplate = true } else devListE = 'No devices found **Invalid Template**' paragraph "The following devices are required for this template: $devListE" state.tCheck = false } if (state.validTemplate) input "qryDevice", "capability.*", title: "Devices of Interest:", multiple: true, required: false, submitOnChange: true if(qryDevice) { unsubscribe() qryDevice.each{ subscribe(it, "altHtml", [filterEvents:true]) } href "previewTemplate", title: "Template Preview", required: false } HashMap varMap = getAllGlobalVars() List varListIn = [] varMap.each { varListIn.add("$it.key") } input "varList", "enum", title: "Select variables to monitor:", options: varListIn.sort(), multiple: true, required: false, submitOnChange: true if(varList != null) { removeAllInUseGlobalVar() varlist.each { var="variable:$it" subscribe(location,"$var", "altHtml") success = addInUseGlobalVar(it.toString()) } } input "clearSettings", "button", title: "Clear previous settings" if(state?.clearAll == true) { unsubscribe() settings.each { if(it.key != 'isInstalled') { app.removeSetting("${it.key}") } } state.clearAll = false } if(!this.getChildDevice("ttdm${app.id}")) addChildDevice("thebearmay","Generic HTML Device","ttdm${app.id}", [name: "HTML Tile Device${app.id}", isComponent: true, label:"HTML Tile Device${app.id}"]) input "security", "bool", title: "Hub Security Enabled", defaultValue: false, submitOnChange: true, width:4 if (security) { input("username", "string", title: "Hub Security Username", required: false) input("password", "password", title: "Hub Security Password", required: false) } } section("Change Application Name", hideable: true, hidden: 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) } } else { section("") { paragraph title: "Click Done", "Please click Done to install app before continuing" } } } } def previewTemplate(){ dynamicPage (name: "previewTemplate", title: "Template Preview", install: false, uninstall: false) { section(""){ html = altHtml() paragraph "${html}" } } } List templateScan() { if(templateName == null) return [] String fContents = readFile("$templateName") List fRecs=fContents.split("\n") List devList =[] fRecs.each { int vCount = it.count("<%") if(vCount > 0){ recSplit = it.split("<%") recSplit.each { if(it.indexOf("%>") > -1){ vEnd = it.indexOf("%>") // log.debug "${it.indexOf(":")}" if(it.indexOf(":") > -1 && it.indexOf(":") < vEnd){ //format of <%devId:attribute%> devId = it.substring(0,it.indexOf(":")) if(devId != "var") devList.add(devId.toLong()) else devList.add(it.substring(it.indexOf(":"),vEnd)) } } } } } return devList.unique() } String altHtml(evt = "") { //log.debug "altHtml $evt.properties" String fContents = readFile("$templateName") if (fContents == null) return List fRecs=fContents.split("\n") String html = "" fRecs.each { int vCount = it.count("<%") if(vCount > 0){ recSplit = it.split("<%") recSplit.each { if(it.indexOf("%>") == -1) html+= it else { vEnd = it.indexOf("%>") if(it.indexOf(":") > -1 && it.indexOf(":") < vEnd){ // if(it.indexOf(":") > -1){ //format of <%devId:attribute%> devId = it.substring(0,it.indexOf(":")) if(devId != "var") { devId = devId.toLong() qryDevice.each{ if(it.deviceId == devId) dev=it } } vName = it.substring(it.indexOf(":")+1,it.indexOf('%>')) //log.debug "$devId $vName" } else vName = it.substring(0,it.indexOf('%>')) if(vName == "@date") aVal = new Date() else if (vName == "@version") aVal = version() else if (vName == "@name" && dev != null)// requires a format of <%devId:attribute%> aVal = dev.properties.displayName else if (vName == "@room" && dev != null) aVal = dev.properties.roomName else if(devId=="var") { aVal = getGlobalVar("$vName").value } else if(dev != null) { aVal = dev.currentValue("$vName",true) String attrUnit = dev.currentState("vName")?.unit if (attrUnit != null) aVal+=" $attrUnit" } html+= aVal if(it.indexOf("%>")+2 != it.length()) { html+=it.substring(it.indexOf("%>")+2) } } } } else html += it } if (!evt) return html chd = getChildDevice("ttdm${app.id}") chd.sendEvent(name:"html1", value:html) return null } void refreshSlot(sNum) { altHtml([refresh:true]) } @SuppressWarnings('unused') String readFile(fName){ if(security) cookie = getCookie() uri = "http://${location.hub.localIP}:8080/local/${fName}" def params = [ uri: uri, contentType: "text/html", textParser: true, headers: [ "Cookie": cookie, "Accept": "application/octet-stream" ] ] try { httpGet(params) { resp -> if(resp!= null) { int i = 0 String delim = "" i = resp.data.read() while (i != -1){ char c = (char) i delim+=c i = resp.data.read() } if(debugEnabled) log.info "File Read Data: $delim" return delim } else { log.error "Null Response" } } } catch (exception) { log.error "Read Error: ${exception.message}" return null; } } @SuppressWarnings('unused') List listFiles(filt = null){ if(security) cookie = getCookie() if(debugEnabled) log.debug "Getting list of files" uri = "http://${location.hub.localIP}:8080/hub/fileManager/json"; def params = [ uri: uri, headers: [ "Cookie": cookie ] ] try { fileList = [] httpGet(params) { resp -> if (resp != null){ if(logEnable) log.debug "Found the files" def json = resp.data for (rec in json.files) { if(filt != null){ if(rec.name.contains("$filt")){ fileList << rec.name } } else fileList << rec.name } } else { // } } if(debugEnabled) log.debug fileList.sort() return fileList.sort() } catch (e) { log.error e } } @SuppressWarnings('unused') String getCookie(){ try{ httpPost( [ uri: "http://127.0.0.1:8080", path: "/login", query: [ loginRedirect: "/" ], body: [ username: username, password: password, submit: "Login" ] ] ) { resp -> cookie = ((List)((String)resp?.headers?.'Set-Cookie')?.split(';'))?.getAt(0) if(debugEnable) log.debug "$cookie" } } catch (e){ cookie = "" } return "$cookie" } def appButtonHandler(btn) { switch(btn) { case "clearSettings": state.clearAll = true break case "templateCheck": state.tCheck = true break case "applyFilter": state.afPushed = true mainPage() break default: log.error "Undefined button $btn pushed" break } } void intialize() { }