/* * Hub Info * * 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. * * Change History: * * Date Who What * ---- --- ---- * 2020-12-07 thebearmay Original version 0.1.0 * .......... * 2023-01-10 thebearmay version 3 rewrite ** minFwVersion = "2.2.8.141" ** * 2023-01-11 v3.0.1 - Poll 4 error * 2023-01-12 v3.0.2 - Zigbee status/status2 disagreement handler (happens when radio is shut off without a reboot) * Turn off Debug Logs after 30 minutes * Add removeUnused method, command and preference * v3.0.3 - Add Uptime Descriptor * 2023-01-13 v3.0.4 - Select format for restart formatted attribute * v3.0.5 - Missing zigbeeStatus generating warning message * 2023-01-14 v3.0.6 - Delay baseData() on Initialize to cpature state correctly * v3.0.7 - hubversion to v2Cleanup, * FreeMemoryUnit option * Add Update Check to Initialize if polled * v3.0.8 - Fix 500 Error on device create * 2023-01-16 v3.0.9 - Delay initial freeMemory check for 8 seconds * 2023-01-21 v3.0.10 - lastUpdated conflict, renamed lastPollTime * 2023-01-23 v3.0.11 - Change formatting date formatting description to match lastPollTime * 2023-02-01 v3.0.12 - Add a try around the time zone code * 2023-02-02 v3.0.13 - Add a null character check to time zone formatting * v3.0.14 - US/Arizona timezone fix * 2023-02-13 v3.0.15 - check for null SSID when hasWiFi true * 2023-02-14 v3.0.16 - add connectCapable * v3.0.17 - Check for html conflict at startup * 2023-02-23 v3.0.18 - Add html attribute output file option * 2023-02-27 v3.0.19 - Add 15 minute averages for CPU Load, CPU Percentage, and Free Memory * 2023-03-09 v3.0.20 - Add cloud connection check * v3.0.21 - Modify the cloud check to allow a user specified device * 2023-03-10 v3.0.22 - Add dnsStatus check * 2023-03-14 v3.0.23 - Change Font to red/bold if Cloud URL is blank or does not contain cloud.hubitat * 2023-03-25 v3.0.24 - Add Zigbee Stack check back in * 2023-03-28 v3.0.25 - Check attribute values for startup message * 2023-03-29 v3.0.26 - Remove Zigbee Stack check as the endpoint is no longer available * 2023-10-13 v3.0.27 - add lanSpeed attribute * 2023-10-20 v3.0.28 - add zigbeeInfo endpoint data if HE>= 2.3.6.1 * 2023-10-24 v3.0.29 - HE 2.3.7.x zigbee endpoint change * 2023-10-24 v3.0.30 - Add Matter attributes * 2023-11-14 v3.0.31 - Suppress error on extended Zigbee/Matter reads if hub not ready * 2023-11-27 v3.0.32 - Reboot with Rebuild Option * 2024-01-05 v3.0.33 - Use file methods instead of endpoints if available * v3.0.34 - Reboot with Log Purge, Rebuild changes * 2024-01-09 v3.0.35 - Allow Matter attributes for C-5, C-7, and C-8 * v3.0.36 - Allow C-8 Pro to pass Compatibility checks * 2024-03-07 v3.0.37 - add /hub/advanced/zipgatewayVersion endpoint * 2024-03-19 v3.0.38 - add pCloud (passive cloud check) * 2024-03-28 v3.0.39 - add GB option for memory display * v3.0.40 - Dynamic unit option for memory display * 2024-04-16 v3.0.41 - lanspeed source change *. 2024-05-07 v3.0.42 - fix C8 Pro failing Matter compatibility check * 2024-05-10 v3.0.43 - Add a delayed base data check on initialization * 2024-07-12 v3.1.0/v3.1.1 - 127.0.0.1 replacement *** best using 2.3.9.159+ * 2024-07-22 v3.1.2 - Added accessList attribute * 2024-07-23 v3.1.3 - streamline the firmware version checks * 2024-07-24 v3.1.4 - correct an issue with blank headers and endpoints * v3.1.5 - reboot and shutdown headers issue * 2024-07-30 v3.1.6 - add security information back in for hub2 data * v3.1.7 - alternate method to detect security in use * 2024-07-31 v3.1.8 - split securityInUse check out into its own option, code cleanup * 2024-08-06 v3.1.9 - add a notification URL for hub shutdown * 2024-11-08 v3.1.10 - Add capability URL and attributes to allow display on Easy Dash * v3.1.11 - Fix degree symbol when using File Manager output * 2024-11-16 v3.1.12 - fix min version check * 2025-01-31 v3.1.13 - add zwaveJS(enabled/disabled), zwaveRegion, zwaveUpdateAvail(true/false), zigbeeUpdateAvail(true/false) * 2025-04-06 v3.1.14 - Add jvmSize, jvmFree, zwHealthy, zbHealthy * 2025-05-02 v3.1.15 - Trap file write attempt without data * 2025-06-24 v3.1.16 - Add sunriseTomorrow and sunsetTomorrow * 2025-06-26 v3.1.17 - negative hour fix for sunriseTomorrow * 2025-09-23 v3.1.18 - Add app state compression attribute * 2025-10-02 v3.1.19 - fix typo on zbHealthy * 2025-11-25 v3.1.20 - Extend H2 data timeout to 1500 * 2025-11-27 v3.1.21 - change H2 to httpGet * 2025-12-07 v3.1.22 - add javaDirect * 2025-12-23 v3.1.23 - move driver version update code * 2026-01-13 v3.1.24 - h2Data issue * 2023-01-20 v3.1.25 - make freeMem15 unit agree with freeMemory */ import java.text.SimpleDateFormat import groovy.json.JsonOutput import groovy.json.JsonSlurper import groovy.transform.Field import java.time.LocalDate import java.time.LocalTime import java.time.ZoneId import java.time.ZonedDateTime import java.time.format.DateTimeFormatter import java.util.TimeZone @SuppressWarnings('unused') static String version() {return "3.1.24"} metadata { definition ( name: "Hub Information Driver v3", namespace: "thebearmay", author: "Jean P. May, Jr.", importUrl:"https://raw.githubusercontent.com/thebearmay/hubitat/main/hubInfoV3.groovy" ) { capability "Actuator" capability "Configuration" capability "Initialize" capability "Refresh" capability "Sensor" capability "TemperatureMeasurement" capability "URL" //Virtual URL Device for Easy Dash attribute "latitude", "string" attribute "longitude", "string" attribute "id", "string" attribute "name", "string" attribute "zigbeeId", "string" attribute "zigbeeEui", "string" attribute "hardwareID", "string" attribute "type", "string" attribute "localIP", "string" attribute "localSrvPortTCP", "string" attribute "uptime", "number" attribute "lastPollTime", "string" attribute "lastPoll", "string" attribute "lastHubRestart", "string" attribute "firmwareVersionString", "string" attribute "timeZone", "string" attribute "temperatureScale", "string" attribute "zipCode", "string" attribute "locationName", "string" attribute "locationId", "string" attribute "lastHubRestartFormatted", "string" attribute "freeMemory", "number" attribute "temperatureF", "string" attribute "temperatureC", "string" attribute "formattedUptime", "string" attribute "html", "string" attribute "cpu5Min", "number" attribute "cpuPct", "number" attribute "dbSize", "number" attribute "publicIP", "string" attribute "zigbeeChannel","string" attribute "maxEvtDays", "number" attribute "maxStateDays", "number" attribute "zwaveVersion", "string" attribute "zwaveSDKVersion", "string" //attribute "zwaveData", "string" attribute "hubModel", "string" attribute "hubUpdateStatus", "string" attribute "hubUpdateVersion", "string" attribute "currentMode", "string" attribute "currentHsmMode", "string" attribute "ntpServer", "string" attribute "ipSubnetsAllowed", "string" attribute "zigbeeStatus", "string" attribute "zigbeeStatus2", "string" //attribute "zigbeeStack", "string" attribute "zwaveStatus", "string" attribute "hubAlerts", "string" attribute "hubMeshData", "string" attribute "hubMeshCount", "number" attribute "securityInUse", "string" attribute "sunrise", "string" attribute "sunset", "string" attribute "nextPoll", "string" //HE v2.3.4.126 attribute "connectType", "string" //Ethernet, WiFi, Dual, Not Connected attribute "dnsServers", "string" attribute "staticIPJson", "string" attribute "lanIPAddr", "string" attribute "wirelessIP", "string" attribute "wifiNetwork", "string" attribute "connectCapable", "string" //Ethernet, WiFi, Dual attribute "cpu15Min", "number" attribute "cpu15Pct", "number" attribute "freeMem15", "number" attribute "cloud", "string" attribute "pCloud", "string" attribute "dnsStatus", "string" attribute "lanSpeed", "string" attribute "zigbeePower", "number" attribute "zigbeePan", "string" attribute "zigbeeExtPan", "string" attribute "matterEnabled", "string" attribute "matterStatus", "string" attribute "releaseNotesUrl", "string" attribute "accessList","string" attribute "sunriseTomorrow","string" attribute "sunsetTomorrow","string" //HE v2.7.3.1 attribute "zwaveJS", "string" attribute "zwaveRegion","string" attribute "zwaveUpdateAvail", "string" attribute "zigbeeUpdateAvail", "string" // HE v2.4.1.154 attribute "jvmFree", "number" attribute "jvmSize", "number" attribute "zbHealthy", "string" attribute "zwHealthy", "string" // Virtual URL Device attributes attribute "URL", "string" attribute "type", "string"//iframe, image, link, or video //HE v2.4.3.121 attribute "appStateCompression", "string" attribute "javaDirect","number" command "hiaUpdate", ["string"] command "reboot" command "rebootW_Rebuild" command "rebootPurgeLogs" command "shutdown" command "updateCheck" command "removeUnused" } } preferences { if(state?.errorMinVersion || state?.errorMinVersion == "true") input("errMsg", "hidden", title:"Minimum Version Error",description:"Hub does not meet the minimum of HEv$minFwVersion") input("quickref","hidden", title:"$ttStyleStrQuick Reference v${version()}") input("debugEnable", "bool", title: "Enable debug logging?", width:4) input("warnSuppress", "bool", title: "Suppress Warn Level Logging", width:4) prefList.each { l1 -> l1.each{ pMap = (HashMap) it.value input ("${it.key}", "enum", title: "
${pMap.desc}${pMap.attributeList}
", options:pollList, submitOnChange:true, width:4, defaultValue:"0") } } if(parm16 != null && parm16 != 0 && parm16 != "0") input("makerInfo", "string", title: "MakerApi or Dashboard URL string", submitOnChange: true) input("remUnused", "bool", title: "Remove unused attributes", defaultValue: false, submitOnChange: true, width:4) input("attribEnable", "bool", title: "Enable HTML Attribute Creation?", defaultValue: false, required: false, submitOnChange: true, width:4) input("alternateHtml", "string", title: "Template file for HTML attribute", submitOnChange: true, defaultValue: "hubInfoTemplate.res", width:4) input("htmlOutput", "string", title: "HTML Attribute Output for > 1024 characters", submitOnChange:true, defaultValue:"hubInfoOutput.html", width:4) input("forceFileOutput","bool", title:"Always use Output file for HTML Attribute", submitOnChange:true, width:4) input("attrLogging", "bool", title: "Log all attribute changes", defaultValue: false, submitOnChange: true, width:4) input("allowReboot","bool", title: "Allow Hub to be shutdown or rebooted", defaultValue: false, submitOnChange: true, width:4) input("freeMemUnit", "enum", title: "Free Memory Unit", options:["KB","MB","GB","Dynamic"], defaultValue:"KB", width:4) input("sunSdfPref", "enum", title: "Date/Time Format for Sunrise/Sunset", options:sdfList, defaultValue:"HH:mm:ss", width:4) input("updSdfPref", "enum", title: "Date/Time Format for Last Poll Time", options:sdfList, defaultValue:"Milliseconds", width:4) input("rsrtSdfPref", "enum", title: "Date/Time Format for Hub Restart Formatted", options:sdfList, defaultValue:"yyyy-MM-dd HH:mm:ss", width:4) input("upTimeSep", "string", title: "Separator for Formatted Uptime", defaultValue: ", ", width:4) input("upTimeDesc", "enum", title: "Uptime Descriptors", defaultValue:"d/h/m/s", options:["d/h/m/s"," days/ hrs/ min/ sec"," days/ hours/ minutes/ seconds"],width:4) input("onShutdownUrl","string", title: "URL to notify when hub receives a shutdown request", width:4) input("pollRate1", "number", title: "Poll Rate for Queue 1 in minutes", defaultValue:0, submitOnChange: true, width:4) input("pollRate2", "number", title: "Poll Rate for Queue 2 in minutes", defaultValue:0, submitOnChange: true, width:4) input("pollRate3", "number", title: "Poll Rate for Queue 3 in minutes", defaultValue:0, submitOnChange: true, width:4) input("pollRate4", "number", title: "Poll Rate for Queue 4 in  hours ", defaultValue:0, submitOnChange: true, width:4) } @SuppressWarnings('unused') void installed() { log.trace "installed()" xferFile("https://raw.githubusercontent.com/thebearmay/hubitat/refs/heads/main/hubInfoTemplate.res","hubInfoTemplate.res") initialize() configure() } void initialize() { restartCheck() updated() runIn(30,"initMemory") if (settings["parm12"] != 0) runIn(30,"updateCheck") if(!state?.v2Cleaned) v2Cleanup() if(driverVersionCheck()) runIn(5,"updated") log.info "Hub Information v${version()} initialized" runIn(120,"baseData") } void initMemory(){ freeMemoryReq() } void configure() { updated() baseData() if(!state?.v2Cleaned) v2Cleanup() } void updated(){ baseData() if(debugEnable) log.debug "updated" unschedule() state.poll1 = [] state.poll2 = [] state.poll3 = [] state.poll4 = [] prefList.each{ l1 -> l1.each{ if(settings["${it.key}"] != null && settings["${it.key}"] != "0") { pMap = (HashMap) it.value if(debugEnable) log.debug "poll${settings["${it.key}"]} ${pMap.method}" state["poll${settings["${it.key}"]}"].add("${pMap.method}") } } } //Enforce the integer value try{ if(pollRate1.toString().contains(".")){ pollRate1 = pollRate1.toString().substring(0,pollRate1.toString().indexOf(".")).toInteger() device.updateSetting("pollRate1",[value:pollRate1,type:"number"]) } if(pollRate2.toString().contains(".")){ pollRate2 = pollRate2.toString().substring(0,pollRate2.toString().indexOf(".")).toInteger() device.updateSetting("pollRate2",[value:pollRate2,type:"number"]) } if(pollRate3.toString().contains(".")){ pollRate3 = pollRate3.toString().substring(0,pollRate3.toString().indexOf(".")).toInteger() device.updateSetting("pollRate3",[value:pollRate3,type:"number"]) } if(pollRate4.toString().contains(".")){ pollRate4 = pollRate4.toString().substring(0,pollRate4.toString().indexOf(".")).toInteger() device.updateSetting("pollRate4",[value:pollRate4,type:"number"]) } } catch (ex) { log.error ex } if(pollRate1 > 0) runIn(pollRate1*60, "poll1") if(pollRate2 > 0) runIn(pollRate2*60, "poll2") if(pollRate3 > 0) runIn(pollRate3*60, "poll3") if(pollRate4 > 0) runIn(pollRate4*60*60, "poll4") if(debugEnable) runIn(1800,"logsOff") if(htmlOutput == null) device.updateSetting("htmlOutput",[value:"hubInfoOutput.html",type:"string"]) device.updateSetting("htmlOutput",[value:toCamelCase(htmlOutput),type:"string"]) if(makerInfo == null || !makerInfo.contains("https://cloud.hubitat.com/")) cloudFontStyle = 'font-weight:bold;color:red' elseversion cloudFontStyle = '' if(remUnused) removeUnused() } void refresh(){ baseData() poll1() poll2() poll3() poll4() } void v2Cleanup() { device.deleteCurrentState("data") device.deleteCurrentState("zwaveData") device.deleteCurrentState("nextPoll") device.deleteCurrentState("hubVersion") state.v2Cleaned = true } boolean driverVersionCheck(){ if(version() != getDataValue('driverVersion')){ updateDataValue('driverVersion', "${version()}") return true } else return false } void poll1(){ state.poll1.each{ this."$it"() } if(pollRate1 > 0) runIn(pollRate1*60, "poll1") everyPoll("poll1") } void poll2(){ state.poll2.each{ this."$it"() } if(pollRate2 > 0) runIn(pollRate2*60, "poll2") everyPoll("poll2") } void poll3(){ state.poll3.each{ this."$it"() } if(pollRate3 > 0) runIn(pollRate3*60, "poll3") everyPoll("poll3") } void poll4(){ state.poll4.each{ this."$it"() } if(pollRate4 > 0) runIn(pollRate4*60*60, "poll4") everyPoll("poll4") } void baseData(dummy=null){ if(driverVersionCheck()) runIn(5,"updated") String model = getHubVersion() // requires >=2.2.8.141 updateAttr("hubModel", model) List locProp = ["latitude", "longitude", "timeZone", "zipCode", "temperatureScale"] locProp.each{ if(it != "timeZone") updateAttr(it, location["${it}"]) else { try { tzWork=location["timeZone"].toString() if(tzWork.indexOf("TimeZone") > -1) tzWork=tzWork.substring(tzWork.indexOf("TimeZone")+8) else // US/Arizona uses a shorter format tzWork=tzWork.substring(tzWork.indexOf("ZoneInfo")+8).replace("\"","") if(debugEnable) log.debug "1) $tzWork" tzWork=tzWork.replace("=",":\"") if(debugEnable) log.debug "2) $tzWork" tzWork=tzWork.replace(",","\",") if(debugEnable) log.debug "3) $tzWork" tzWork=tzWork.replace("]]","\"]") if(debugEnable) log.debug "4) $tzWork" tzWork=tzWork.replace("null]","\"]") if(debugEnable) log.debug "5) $tzWork" tzMap= (Map) evaluate(tzWork) updateAttr("timeZone",JsonOutput.toJson(tzMap)) } catch (e) { log.error "Time zone format error: ${location["timeZone"]}
$e" } } } def myHub = location.hub List hubProp = ["id","name","zigbeeId","zigbeeEui","hardwareID","type","localIP","localSrvPortTCP","firmwareVersionString","uptime"] hubProp.each { updateAttr(it, myHub["${it}"]) } if(!minVerCheck(minFwVersion)) { state.errorMinVersion = true } else state.errorMinVersion = false if(location.hub.properties.data.zigbeeChannel != null) updateAttr("zigbeeChannel",location.hub.properties.data.zigbeeChannel) else updateAttr("zigbeeChannel","Not Available") if(location.hub.properties.data.zigbeeChannel != null){ updateAttr("zigbeeStatus", "enabled") }else updateAttr("zigbeeStatus", "disabled") updateAttr("locationName", location.name) updateAttr("locationId", location.id) if(minVerCheck("2.3.6.1")) extendedZigbee() if(minVerCheck("2.4.1.103")) zwaveJsStat() if(minVerCheck("2.4.3.121")) checkAppComp() everyPoll("baseData") } void everyPoll(whichPoll=null){ updateAttr("currentMode", location.properties.currentMode) updateAttr("currentHsmMode", location.hsmStatus) SimpleDateFormat sdfIn = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy") sunrise = sdfIn.parse(location.sunrise.toString()) sunset = sdfIn.parse(location.sunset.toString()) int yearST=new Date().getYear() + 1900 int monthST=new Date().getMonth() + 1 int dayST=new Date().getDate() + 1 try { LocalDate.of(yearST,monthST,dayST) } catch (dCheck){ monthST++ dayST = 1 if(monthST > 12) monthST = 1 } ZonedDateTime ssTom = calculateSunset(yearST, monthST, dayST) ZonedDateTime srTom = calculateSunrise(yearST, monthST, dayST) if(sunSdfPref == null) device.updateSetting("sunSdfPref",[value:"HH:mm:ss",type:"enum"]) if(sunSdfPref != "Milliseconds") { SimpleDateFormat sdf = new SimpleDateFormat(sunSdfPref) DateTimeFormatter dtf = DateTimeFormatter.ofPattern(sunSdfPref) updateAttr("sunrise", sdf.format(sunrise)) updateAttr("sunset", sdf.format(sunset)) updateAttr("sunsetTomorrow",ssTom.format(dtf)) updateAttr("sunriseTomorrow",srTom.format(dtf)) } else { updateAttr("sunrise", sunrise.getTime()) updateAttr("sunset", sunset.getTime()) updateAttr("sunsetTomorrow",ssTom.toInstant().toEpochMilli()) updateAttr("sunriseTomorrow",srTom.toInstant().toEpochMilli()) } updateAttr("localIP",location.hub.localIP) if(updSdfPref == null) device.updateSetting("updSdfPref",[value:"Milliseconds",type:"string"]) if(updSdfPref == "Milliseconds" || updSdfPref == null) updateAttr("lastPollTime", new Date().getTime()) else { SimpleDateFormat sdf = new SimpleDateFormat(updSdfPref) updateAttr("lastPollTime", sdf.format(new Date().getTime())) } if(whichPoll != null) updateAttr("lastPoll", whichPoll) formatUptime() if (attribEnable) createHtml() } void updateAttr(String aKey, aValue, String aUnit = ""){ aValue = aValue.toString() if(aValue.contains("Your hub is starting up")) return sendEvent(name:aKey, value:aValue, unit:aUnit, descriptionText:"$aKey : $aValue$aUnit") if(attrLogging) log.info "$aKey : $aValue$aUnit" } void cpuTemperatureReq(){ params = [ uri : "http://127.0.0.1:8080", path : "/hub/advanced/internalTempCelsius", headers: [ "Connection-Timeout":600 ] ] if (debugEnabled) log.debug params asynchttpGet("getCpuTemperature", params) } void getCpuTemperature(resp, data) { try { if(resp.getStatus() == 200 || resp.getStatus() == 207) { Double tempWork = new Double(resp.data.toString()) if(tempWork > 0) { if(debugEnable) log.debug tempWork if (location.temperatureScale == "F") updateAttr("temperature",String.format("%.1f", celsiusToFahrenheit(tempWork)),"°F") else updateAttr("temperature",String.format("%.1f",tempWork),"°C") updateAttr("temperatureF",String.format("%.1f",celsiusToFahrenheit(tempWork))+ " °F") updateAttr("temperatureC",String.format("%.1f",tempWork)+ " °C") } } } catch(ignored) { def respStatus = resp.getStatus() if (!warnSuppress) log.warn "getTemp httpResp = $respStatus but returned invalid data, will retry next cycle" } } void freeMemoryReq(){ params = [ uri : "http://127.0.0.1:8080", path : "/hub/advanced/freeOSMemory", headers: [ "Connection-Timeout":600 ] ] if (debugEnable) log.debug params asynchttpGet("getFreeMemory", params) jvmReq() } @SuppressWarnings('unused') void getFreeMemory(resp, data) { try { if(resp.getStatus() == 200 || resp.getStatus() == 207) { Integer memWork = new Integer(resp.data.toString()) if(debugEnable) log.debug "Free Memory $memWork" if(freeMemUnit == "Dynamic"){ if(memWork > 1048575){ freeMemUnit = "GB" if(debugEnable) log.debug "unit is $freeMemUnit" }else if(memWork > 150000){ freeMemUnit = "MB" if(debugEnable) log.debug "unit is $freeMemUnit" } else freeMemUnit = "KB" } if(freeMemUnit == "GB") updateAttr("freeMemory",(new Float(memWork/1024/1024).round(2)), "GB") else if(freeMemUnit == "MB") updateAttr("freeMemory",(new Float(memWork/1024).round(2)), "MB") else updateAttr("freeMemory",memWork, "KB") } } catch(ignored) { def respStatus = resp.getStatus() if (!warnSuppress) log.warn "getFreeMem httpResp = $respStatus but returned invalid data, will retry next cycle" } } void jvmReq(){ params = [ uri : "http://127.0.0.1:8080", path : "/hub/advanced/freeOSMemoryHistory", headers: [ "Connection-Timeout":600 ] ] if (debugEnable) log.debug params asynchttpGet("getJvm", params) } @SuppressWarnings('unused') void getJvm(resp, data) { try { if(resp.getStatus() == 200 || resp.getStatus() == 207) { //log.debug "${resp.data}" ArrayList memRecs = resp.data.toString().split('\n') String memRec = memRecs[memRecs.size()-1] ArrayList memWork = memRec.split(',') if(debugEnable) log.debug "JVM record ${memRecs.size()} ${memWork.size()} $memRec
$memWork" if(memWork.size() >= 5){ updateAttr("jvmSize",memWork[3], "KB") updateAttr("jvmFree",memWork[4], "KB") } if(memWork.size >= 6) { updateAttr("javaDirect",memWork[5], "KB") } } } catch(ignored) { def respStatus = resp.getStatus() if (!warnSuppress) log.warn "getJvm httpResp = $respStatus but returned invalid data, will retry next cycle" } } void fifteenMinute(){ if(location.hub.uptime < 900) { //if the hub hasn't been up 15 minutes use current 5 min values updateAttr("cpu15Min",device.currentValue("cpu5Min",true)) updateAttr("cpu15Pct",device.currentValue("cpuPct",true),"%") updateAttr("freeMem15",device.currentValue("freeMemory",true)) return } params = [ uri : "http://127.0.0.1:8080", path : "/hub/advanced/freeOSMemoryHistory", headers: [ "Connection-Timeout":600 ] ] if (debugEnable) log.debug params asynchttpGet("get15Min", params) } void get15Min(resp, data){ String loadWork List loadRec = [] try { if(resp.getStatus() == 200 || resp.getStatus() == 207) { loadWork = resp.data.toString() } } catch(ignored) { def respStatus = resp.getStatus() if (!warnSuppress) log.warn "get15min httpResp = $respStatus but returned invalid data, will retry next cycle" } if (loadWork) { Integer lineCount = 0 loadWork.eachLine{ lineCount++ } Integer lineCount2 = 0 Double cpuWork = 0.0 Long memWork = 0 loadWork.eachLine{ lineCount2++ if(lineCount2 > lineCount-3){ workSplit = it.split(",") cpuWork+=workSplit[2].toDouble() memWork+=workSplit[1].toLong() } } memWork/=3 cpuWork/=3 updateAttr("cpu15Min",cpuWork.round(2)) cpuWork = (cpuWork/4.0D)*100.0D //Load / #Cores - if cores change will need adjusted to reflect updateAttr("cpu15Pct",cpuWork.round(2),"%") if(freeMemUnit == "Dynamic"){ if(memWork > 1048575){ freeMemUnit = "GB" }else if(memWork > 150000){ freeMemUnit = "MB" }else freeMemUnit = "KB" } if(freeMemUnit == "GB") updateAttr("freeMem15",(new Float(memWork/1024/1024).round(2)), "GB") else if(freeMemUnit == "MB") updateAttr("freeMem15",(new Float(memWork/1024).round(2)), "MB") else updateAttr("freeMem15",memWork, "KB") } } void cpuLoadReq(){ params = [ uri : "http://127.0.0.1:8080", path : "/hub/advanced/freeOSMemoryLast", headers: [ "Connection-Timeout":600 ] ] if (debugEnable) log.debug params asynchttpGet("getCpuLoad", params) } void getCpuLoad(resp, data) { String loadWork List loadRec = [] try { if(resp.getStatus() == 200 || resp.getStatus() == 207) { loadWork = resp.data.toString() } } catch(ignored) { def respStatus = resp.getStatus() if (!warnSuppress) log.warn "getCpuLoad httpResp = $respStatus but returned invalid data, will retry next cycle" } if (loadWork) { Integer lineCount = 0 loadWork.eachLine{ lineCount++ } Integer lineCount2 = 0 loadWork.eachLine{ lineCount2++ if(lineCount==lineCount2) workSplit = it.split(",") } if(workSplit.size() > 1){ Double cpuWork=workSplit[2].toDouble() updateAttr("cpu5Min",cpuWork.round(2)) cpuWork = (cpuWork/4.0D)*100.0D //Load / #Cores - if cores change will need adjusted to reflect updateAttr("cpuPct",cpuWork.round(2),"%") } } } void dbSizeReq(){ params = [ uri : "http://127.0.0.1:8080", path : "/hub/advanced/databaseSize", headers: [ "Connection-Timeout":600 ] ] if (debugEnable) log.debug params asynchttpGet("getDbSize", params) } @SuppressWarnings('unused') void getDbSize(resp, data) { try { if(resp.getStatus() == 200 || resp.getStatus() == 207) { Integer dbWork = new Integer(resp.data.toString()) if(debugEnable) log.debug dbWork updateAttr("dbSize",dbWork,"MB") } } catch(ignored) { def respStatus = resp.getStatus() if (!warnSuppress) log.warn "getDb httpResp = $respStatus but returned invalid data, will retry next cycle" } } void publicIpReq(){ params = [ uri: "https://api.ipify.org?format=json", headers: [ Accept: "application/json" ] ] if(debugEnable)log.debug params asynchttpGet("getPublicIp", params) } @SuppressWarnings('unused') void getPublicIp(resp, data){ try{ if (resp.getStatus() == 200){ if (debugEnable) log.debug resp.data def jSlurp = new JsonSlurper() Map ipData = (Map)jSlurp.parseText((String)resp.data) updateAttr("publicIP",ipData.ip) } else { if (!warnSuppress) log.warn "Status ${resp.getStatus()} while fetching Public IP" } } catch (Exception ex){ if (!warnSuppress) log.warn ex } } void evtStateDaysReq(){ //Max State Days params = [ uri : "http://127.0.0.1:8080", path : "/hub/advanced/maxDeviceStateAgeDays", headers: [ "Connection-Timeout":600 ] ] if(debugEnable)log.debug params asynchttpGet("getStateDays", params) //Max Event Days params = [ uri : "http://127.0.0.1:8080", path : "/hub/advanced/maxEventAgeDays", headers: [ "Connection-Timeout":600 ] ] if(debugEnable)log.debug params asynchttpGet("getEvtDays", params) } @SuppressWarnings('unused') void getEvtDays(resp, data) { try { if(resp.getStatus() == 200 || resp.getStatus() == 207) { Integer evtDays = new Integer(resp.data.toString()) if(debugEnable) log.debug "Max Event Days $evtDays" updateAttr("maxEvtDays",evtDays) } } catch(ignored) { def respStatus = resp.getStatus() if (!warnSuppress) log.warn "getEvtDays httpResp = $respStatus but returned invalid data, will retry next cycle" } } @SuppressWarnings('unused') void getStateDays(resp, data) { try { if(resp.getStatus() == 200 || resp.getStatus() == 207) { Integer stateDays = new Integer(resp.data.toString()) if(debugEnable) log.debug "Max State Days $stateDays" updateAttr("maxStateDays",stateDays) } } catch(ignored) { def respStatus = resp.getStatus() if (!warnSuppress) log.warn "getStateDays httpResp = $respStatus but returned invalid data, will retry next cycle" } } void zwaveVersionReq(){ if(!isCompatible(7)) { if(!warnSuppress) log.warn "ZWave Version information not available for this hub" return } param = [ uri : "http://127.0.0.1:8080", path : "/hub/zwaveVersion", headers: [ "Connection-Timeout":600 ] ] if (debugEnable) log.debug param asynchttpGet("getZwaveVersion", param) } @SuppressWarnings('unused') void getZwaveVersion(resp, data) { try { if(resp.getStatus() == 200 || resp.getStatus() == 207) { String zwaveData = resp.data.toString() if(debugEnable) log.debug resp.data.toString() if(zwaveData.length() < 1024){ //updateAttr("zwaveData",zwaveData) parseZwave(zwaveData) } else if (!warnSuppress) log.warn "Invalid data returned for Zwave, length = ${zwaveData.length()} will retry" } } catch(ignored) { if (!warnSuppress) log.warn "getZwave Parsing Error" } } @SuppressWarnings('unused') void parseZwave(String zString){ Integer start = zString.indexOf('(') Integer end = zString.length() String wrkStr if(device.currentValue('zwaveJS',true) != 'true' && zString.length() != 4){ if(start == -1 || end < 1 || zString.indexOf("starting up") > 0 ){ //empty or invalid string - possibly non-C7 //updateAttr("zwaveData",null) if(!warnSuppress) log.warn "Invalid ZWave Data returned ($zString) " }else { wrkStr = zString.substring(start,end) wrkStr = wrkStr.replace("(","[") wrkStr = wrkStr.replace(")","]") HashMap zMap = (HashMap)evaluate(wrkStr) updateAttr("zwaveVersion","${zMap?.firmware0Version}.${zMap?.firmware0SubVersion}.${zMap?.hardwareVersion}") } }else updateAttr("zwaveVersion","$zString") if(!minVerCheck("2.3.8.124")) updateAttr("zwaveSDKVersion","${((List)zMap.targetVersions)[0].version}.${((List)zMap.targetVersions)[0].subVersion}") else { params = [ uri : "http://127.0.0.1:8080", path : "/hub/advanced/zipgatewayVersion", headers: [ "Connection-Timeout":600 ] ] httpGet(params) { resp -> updateAttr("zwaveSDKVersion",resp.data) } } } void ntpServerReq(){ params = [ uri : "http://127.0.0.1:8080", path : "/hub/advanced/ntpServer", headers: [ "Connection-Timeout": 600 ] ] if(debugEnable)log.debug params asynchttpGet("getNtpServer", params) } @SuppressWarnings('unused') void getNtpServer(resp, data) { try { if (resp.status == 200) { ntpServer = resp.data.toString() if(ntpServer == "No value set") ntpServer = "Hub Default(Google)" updateAttr("ntpServer", ntpServer) } else { if(!warnSuppress) log.warn "NTP server check returned status: ${resp.status}" } }catch (ignore) { } } void ipSubnetsReq(){ params = [ uri : "http://127.0.0.1:8080", path : "/hub/allowSubnets", headers: [ "Connection-Timeout": 600 ] ] if(debugEnable)log.debug params asynchttpGet("getSubnets", params) } @SuppressWarnings('unused') void getSubnets(resp, data) { try { if (resp.status == 200) { subNets = resp.data.toString() if(subNets == "Not set") subNets = "Hub Default" updateAttr("ipSubnetsAllowed", subNets) } else { if(!warnSuppress) log.warn "Subnet check returned status: ${resp.status}" } }catch (ignore) { } } void hubMeshReq(){ params = [ uri : "http://127.0.0.1:8080", path : "/hub2/hubMeshJson", headers: [ "Connection-Timeout":600 ] ] if(debugEnable)log.debug params asynchttpGet("getHubMesh", params) } @SuppressWarnings('unused') void getHubMesh(resp, data){ try{ if (resp.getStatus() == 200){ if (debugEnable) log.info resp.data def jSlurp = new JsonSlurper() Map hmData = (Map)jSlurp.parseText((String)resp.data) i=0 subMap2=[:] jStr="[" hmData.hubList.each{ if(i>0) jStr+="," jStr+="{\"hubName\":\"$it.name\"," jStr+="\"active\":\"$it.active\"," jStr+="\"offline\":\"$it.offline\"," jStr+="\"ipAddress\":\"$it.ipAddress\"}" i++ } jStr+="]" updateAttr("hubMeshData", jStr) updateAttr("hubMeshCount",i) } else { if (!warnSuppress) log.warn "Status ${resp.getStatus()} on Hubmesh request" } } catch (Exception ex){ if (!warnSuppress) log.warn ex } } void extNetworkReq(){ if(!minVerCheck("2.3.4.126")){ if(!warnSuppress) log.warn "Extend Network Data not available for HE v${location.hub.firmwareVersionString}" return } params = [ uri : "http://127.0.0.1:8080", path : "/hub2/networkConfiguration", headers: [ "Connection-Timeout":600 ] ] if(debugEnable)log.debug params asynchttpGet("getExtNetwork", params) } void hub2DataReq() { params = [ uri : "http://127.0.0.1:8080", path : "/hub2/hubData", timeout: 300, headers: [ "Connection-Timeout": 1500 ] ] if(debugEnable) log.debug params httpGet(params) {resp -> try{ if (debugEnable) log.debug resp.data try{ // def jSlurp = new JsonSlurper() // h2Data = (Map)jSlurp.parseText((String)resp.data) h2Data = (Map)resp.data } catch (eMsg) { if (debugEnable) log.debug "H2: $eMsg
$h2Data
${resp.data}" return } hubAlerts = [] h2Data.alerts.each{ if(it.value == true){ if("$it.key".indexOf('Database') > -1) hubAlerts.add("hubDatabaseSize") else if("$it.key".indexOf('Load') > -1) hubAlerts.add("hubLoad") else if("$it.key" != "runAlerts") hubAlerts.add(it.key) } } updateAttr("hubAlerts",hubAlerts) if(h2Data?.baseModel == null) { if (debugEnable) log.debug "baseModel is missing from h2Data, ${device.currentValue('hubModel')} ${device.currentValue('firmwareVersionString')}
$h2Data" return } if(h2Data.baseModel.zwaveStatus == "false") updateAttr("zwaveStatus","enabled") else updateAttr("zwaveStatus","disabled") if(h2Data.baseModel.zigbeeStatus == "false"){ updateAttr("zigbeeStatus2", "enabled") } else { updateAttr("zigbeeStatus2", "disabled") } /******************************************************************************************************* * userLoggedIn is ONLY true if security is in use and the user has provided credentials to this driver * * use /logout endpoint to check instead * *******************************************************************************************************/ if(!h2Data.baseModel.cloudDisconnected){ updateAttr("pCloud", "connected") } else { updateAttr("pCloud", "not connected") } } catch (Exception ex){ if (!warnSuppress) log.warn ex } } checkSecurity() zHealthReq() } void checkSecurity(){ params = [ uri : "http://127.0.0.1:8080", path : "/logout", followRedirects: false, headers: [ "Connection-Timeout": 300 ] ] asynchttpGet("getSecurity", params) } @SuppressWarnings('unused') void getSecurity(resp, data){ if(resp.headers.Location == 'http://127.0.0.1:8080/login') updateAttr("securityInUse",'true') else updateAttr("securityInUse",'false') } @SuppressWarnings('unused') void getExtNetwork(resp, data){ try{ if (resp.getStatus() == 200){ if (debugEnable) log.info resp.data def jSlurp = new JsonSlurper() Map h2Data = (Map)jSlurp.parseText((String)resp.data) if(!h2Data.usingStaticIP) updateAttr("staticIPJson", "{}") else { jMap = [staticIP:"${h2Data.staticIP}", staticGateway:"${h2Data.staticGateway}", staticSubnetMask:"${h2Data.staticSubnetMask}",staticNameServers:"${h2Data.staticNameServers}"] updateAttr("staticIPJson",JsonOutput.toJson(jMap)) } if(h2Data.hasEthernet && h2Data.hasWiFi) updateAttr("connectCapable","Dual") else if(h2Data.hasEthernet) updateAttr("connectCapable","Ethernet") else if (h2Data.hasWiFi) updateAttr("connectCapable","WiFi") else updateAttr("connectCapable", "Unknown") if(lanIPAddr != null) updateAttr("lanIPAddr", h2Data.lanAddr) else updateAttr("lanIPAddr", "None") if(h2Data.wlanAddr != null) updateAttr("wirelessIP",h2Data.wlanAddr) else updateAttr("wirelessIP","None") if(h2Data.wifiNetwork != null) updateAttr("wifiNetwork", h2Data.wifiNetwork) else updateAttr("wifiNetwork", "None") if(h2Data.wifiNetwork && h2Data.wlanAddr && h2Data.lanAddr) updateAttr("connectType","Dual") else if(h2Data.wifiNetwork && h2Data.wlanAddr) updateAttr("connectType", "WiFi") else if(h2Data.lanAddr) updateAttr("connectType","Ethernet") else updateAttr("connectType","Not Connected") updateAttr("lanIPAddr", h2Data.lanAddr) dnsList = [] if(h2Data.usingStaticIP){ h2Data.staticNameServers.each{ dnsList.add("$it") } }else { h2Data.dhcpNameServers.each{ dnsList.add("$it") } } h2Data.dnsServers.each{ dnsList.add("$it") } dnsList = dnsList.unique() checkDns(dnsList) updateAttr("dnsServers", dnsList) if(h2Data.lanAutoneg == 'AUTONEG') updateAttr("lanSpeed","Auto","mbps") else updateAttr("lanSpeed", "100","mbps") } }catch (ex) { if (!warnSuppress) log.warn ex } } void checkDns(dnsList) { if(dnsList == null){ updateAttr("dnsStatus","inactive") return } for(i=0;i',',').replace('
',',') updateAttr('accessList',aList.split(',')) } } catch (e) { } } void checkAppComp(){ params = [ uri : "http://127.0.0.1:8080", path : "/hub/advanced/stateCompressionStatus", headers: [ "Connection-Timeout":600 ] ] if (debugEnabled) log.debug params asynchttpGet("getCompressStatus", params) } void getCompressStatus(resp, data) { try{ updateAttr('appStateCompression',"${resp.data.toString()}") } catch (e) { } } @SuppressWarnings('unused') boolean isCompatible(Integer minLevel) { //check to see if the hub version meets the minimum requirement String model = getHubVersion() String[] tokens = model.split('-') String revision = tokens.last() if(revision.contains('Pro')) revision = 9 return (Integer.parseInt(revision) >= minLevel) } @SuppressWarnings('unused') Boolean xferFile(fileIn, fileOut) { fileBuffer = (String) readExtFile(fileIn) retStat = writeFile(fileOut, fileBuffer) if(logResponses) log.info "File xFer Status: $retStat" return retStat } @SuppressWarnings('unused') String readExtFile(fName){ def params = [ uri: fName, contentType: "text/html", textParser: true, ] 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(debugEnable) log.info "Read External File result: delim" return delim } else { log.error "Read External - Null Response" } } } catch (exception) { log.error "Read Ext Error: ${exception.message}" return null; } } @SuppressWarnings('unused') Boolean fileExists(fName){ if(fName == null) return false try{ if(minVerCheck("2.3.4.134")){ byte[] rData = downloadHubFile("$fName") fContent = new String(rData, "UTF-8") if(fContent.size() > 0) { if(debugEnable) log.debug "$fName File Exist: true" return true; } else { if(debugEnable) log.debug "$fName File Exist: false" return false; } } } catch (ex) { log.error "$fName - $ex" return false } uri = "http://127.0.0.1:8080/local/${fName}"; def params = [ uri: uri ] try { httpGet(params) { resp -> if (resp != null){ if(debugEnable) log.debug "$fName File Exist: true" return true; } else { if(debugEnable) log.debug "$fName File Exist: false" return false } } } catch (exception){ if (exception.message == "status code: 404, reason phrase: Not Found"){ if(debugEnable) log.debug "File Exist: false" } else if (resp.getStatus() != 408) { log.error "Find file $fName :: Connection Exception: ${exception.message}" } return false } } @SuppressWarnings('unused') void hiaUpdate(htmlStr) { updateAttr("html",htmlStr) } void createHtml(){ if(alternateHtml == null || fileExists("$alternateHtml") == false){ xferFile("https://raw.githubusercontent.com/thebearmay/hubitat/refs/heads/main/hubInfoTemplate.res","hubInfoTemplate.res") device.updateSetting("alternateHtml",[value:"hubInfoTemplate.res", type:"string"]) } String fContents = readFile("$alternateHtml") if(fContents == 'null' || fContents == null) { xferFile("https://raw.githubusercontent.com/thebearmay/hubitat/refs/heads/main/hubInfoTemplate.res","hubInfoTemplate.res") device.updateSetting("alternateHtml",[value:"hubInfoTemplate.res", type:"string"]) fContents = readFile("$alternateHtml") } List fRecs=fContents.split("\n") String html = "" fRecs.each { int vCount = it.count("<%") if(debugEnable) log.debug "variables found: $vCount" if(vCount > 0){ recSplit = it.split("<%") if(debugEnable) log.debug "$recSplit" recSplit.each { if(it.indexOf("%>") == -1) html+= it else { vName = it.substring(0,it.indexOf('%>')) if(debugEnable) log.debug "${it.indexOf("5>")}
$it
${it.substring(0,it.indexOf("%>"))}" if(vName == "date()" || vName == "@date") aVal = new Date() else if (vName == "@version") aVal = version() else { aVal = device.currentValue("$vName",true) String attrUnit = getUnitFromState("$vName") if (attrUnit != null) aVal+=" $attrUnit" } html+= aVal if(it.indexOf("%>")+2 != it.length()) { if(debugEnable) log.debug "${it.substring(it.indexOf("%>")+2)}" html+=it.substring(it.indexOf("%>")+2) } } } } else html += it } if (debugEnable) log.debug html if(!html.contains("support.hubitat.com")){ updateAttr("html", html) state.htmlError = false if(html.size() > 1024 || forceFileOutput){ if(htmlFileOutput == null) htmlFileOutput = "hubInfoOutput.html" html = html.replace("°","°") writeFile(htmlFileOutput, html) updateAttr("html","Link to attribute data") updateAttr("URL","http://${location.hub.localIP}:8080/local/$htmlFileOutput") updateAttr("type","iframe") } else updateAttr("html", html) }else { updateAttr("html", "

Hub Not Ready

Please hit Initialize, or wait for next poll

${new Date()}

") if("${state.htmlError}" != "true"){ state.htmlError = true runIn(20,"initialize") } } } @SuppressWarnings('unused') String readFile(fName){ try{ if(minVerCheck("2.3.4.134")){ byte[] rData = downloadHubFile("$fName") return new String(rData, "UTF-8") } } catch (ex) { log.error "$fName - $ex" } uri = "http://127.0.0.1:8080/local/${fName}" def params = [ uri: uri, contentType: "text/html", textParser: true, headers: [ "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') Boolean writeFile(String fName, String fData) { if(fData.size() < 1) { log.error "$fName cannot be created with size ${fData.size()}" return false } if(minVerCheck("2.3.4.134")){ wData = fData.getBytes("UTF-8") uploadHubFile("$fName",wData) return true } now = new Date() String encodedString = "thebearmay$now".bytes.encodeBase64().toString(); try { def params = [ uri: 'http://127.0.0.1:8080', path: '/hub/fileManager/upload', query: [ 'folder': '/' ], headers: [ 'Content-Type': "multipart/form-data; boundary=$encodedString;text/html; charset=utf-8" ], body: """--${encodedString} Content-Disposition: form-data; name="uploadFile"; filename="${fName}" Content-Type: text/plain ${fData} --${encodedString} Content-Disposition: form-data; name="folder" --${encodedString}--""", timeout: 300, ignoreSSLIssues: true ] httpPost(params) { resp -> } return true } catch (e) { log.error "Error writing file $fName: ${e}" } return false } @SuppressWarnings('unused') void reboot() { if(!allowReboot){ log.error "Reboot was requested, but allowReboot was set to false" return } log.info "Hub Reboot requested" httpPost( [ uri: "http://127.0.0.1:8080", path: "/hub/reboot" ] ) { resp -> } } @SuppressWarnings('unused') void rebootW_Rebuild() { if(!allowReboot){ log.error "Reboot was requested, but allowReboot was set to false" return } if(!minVerCheck("2.3.7.122")){ log.error "Reboot with rebuild was requested, but failed HE min version." return } log.info "Hub Reboot with Rebuild requested" if(!minVerCheck("2.3.7.14")){ httpPost( [ uri: "http://127.0.0.1:8080", path: "/hub/rebuildDatabaseAndReboot" ] ) { resp -> } } else { httpPost( [ uri: "http://127.0.0.1:8080", path: "/hub/reboot", headers:[ "Content-Type": "application/x-www-form-urlencoded" ], body:[rebuildDatabase:"true"] ] ) { resp -> } } } void rebootPurgeLogs() { if(!allowReboot){ log.error "Reboot was requested, but allowReboot was set to false" return } if(!minVerCheck("2.3.7.140")){ log.error "Reboot with Purge was requested, but failed HE min version." return } log.info "Hub Reboot & Log Purge requested" httpPost( [ uri: "http://127.0.0.1:8080", path: "/hub/reboot", headers:[ "Content-Type": "application/x-www-form-urlencoded" ], body:[purgeLogs:"true"] ] ) { resp -> } } @SuppressWarnings('unused') void shutdown() { if(!allowReboot){ log.error "Shutdown was requested, but allowReboot/Shutdown was set to false" return } log.info "Hub Shutdown requested" if(onShutdownUrl) sendShutdownUrl() httpPost( [ uri: "http://127.0.0.1:8080", path: "/hub/shutdown" ] ) { resp -> } } @SuppressWarnings('unused') void sendShutdownUrl(){ if(debugEnabled) log.debug "Shutdown is sending request to $onShutdownUrl" params = [ uri: onShutdownUrl, headers:[ "Connection-Timeout":600 ] ] asynchttpGet("ssdResp",params) } void ssdResp(resp, data){ if(debugEnabled) log.debug "${resp.properties}" } void formatUptime(){ updateAttr("uptime", location.hub.uptime) try { Long ut = location.hub.uptime.toLong() Integer days = Math.floor(ut/(3600*24)).toInteger() Integer hrs = Math.floor((ut - (days * (3600*24))) /3600).toInteger() Integer min = Math.floor( (ut - ((days * (3600*24)) + (hrs * 3600))) /60).toInteger() Integer sec = Math.floor(ut - ((days * (3600*24)) + (hrs * 3600) + (min * 60))).toInteger() if(upTimeSep == null){ device.updateSetting("upTimeSep",[value:",", type:"string"]) upTimeSep = "," } utD=upTimeDesc.split("/") dayD = (days==1) ? utD[0].replace("s",""):utD[0] hrD = (hrs==1) ? utD[1].replace("s",""):utD[1] minD = (min==1) ? utD[2].replace("s",""):utD[2] if(utD[3] == " seconds") secD = (sec==1) ? " second":utD[3] else secD = utD[3] String attrval = "${days.toString()}${dayD}$upTimeSep${hrs.toString()}${hrD}$upTimeSep${min.toString()}${minD}$upTimeSep${sec.toString()}${secD}" updateAttr("formattedUptime", attrval) } catch(ignore) { updateAttr("formattedUptime", "") } } @SuppressWarnings('unused') void restartCheck() { if(debugEnable) log.debug "$rsDate" Long ut = new Date().getTime().toLong() - (location.hub.uptime.toLong()*1000) Date upDate = new Date(ut) if(debugEnable) log.debug "RS: $rsDate UT:$ut upTime Date: $upDate upTime: ${location.hub.uptime}" updateAttr("lastHubRestart", ut) if(rsrtSdfPref == null){ device.updateSetting("rsrtSdfPref",[value:"yyyy-MM-dd HH:mm:ss",type:"string"]) rsrtSdfPref="yyyy-MM-dd HH:mm:ss" } if(rsrtSdfPref == "Milliseconds") updateAttr("lastHubRestartFormatted", upDate.getTime()) else { SimpleDateFormat sdf = new SimpleDateFormat(rsrtSdfPref) updateAttr("lastHubRestartFormatted", sdf.format(upDate.getTime())) } } void removeUnused() { prefList.each{ l1 -> l1.each{ if(it.key.contains("parm") && (settings["${it.key}"] == null || settings["${it.key}"] == "0")) { pMap = (HashMap) it.value if(debugEnable) log.debug "${it.key} poll${settings["${it.key}"]} ${pMap.attributeList}" aList = pMap.attributeList.split(",") aList.each{ if(debugEnable) log.debug "device.deleteCurrentState(\"${it.trim()}\")" device.deleteCurrentState("${it.trim()}") } } } } v2Cleanup() } @SuppressWarnings('unused') String getUnitFromState(String attrName){ return device.currentState(attrName)?.unit } @SuppressWarnings('unused') String toCamelCase(init) { if (init == null) return null; String ret = "" List word = init.split(" ") if(word.size == 1) return init word.each{ ret+=Character.toUpperCase(it.charAt(0)) ret+=it.substring(1).toLowerCase() } ret="${Character.toLowerCase(ret.charAt(0))}${ret.substring(1)}" if(debugEnabled) log.debug "toCamelCase return $ret" return ret; } @SuppressWarnings('unused') 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 } ZonedDateTime calculateSunrise(int year=new Date().getYear() + 1900, int month=new Date().getMonth() + 1, int day=new Date().getDate()) { final double ZENITH = 90.83333 // Official zenith for sunrise/sunset LocalDate date = LocalDate.of(year, month, day) double latitude = location.latitude double longitude = location.longitude int dayOfYear = date.dayOfYear double lngHour = longitude / 15 double t = dayOfYear + ((6 - lngHour) / 24) // Mean anomaly double M = (0.9856 * t) - 3.289 // Sun's true longitude double L = (M + (1.916 * Math.sin(Math.toRadians(M))) + (0.020 * Math.sin(Math.toRadians(2 * M))) + 282.634) % 360 // Right ascension double RA = Math.toDegrees(Math.atan(0.91764 * Math.tan(Math.toRadians(L)))) RA = RA % 360 // Adjust RA to be in the same quadrant as L double Lquadrant = (Math.floor(L / 90)) * 90 double RAquadrant = (Math.floor(RA / 90)) * 90 RA = RA + (Lquadrant - RAquadrant) // Convert RA into hours RA = RA / 15 // Calculate declination of the sun double sinDec = 0.39782 * Math.sin(Math.toRadians(L)) double cosDec = Math.cos(Math.asin(sinDec)) // Calculate the sun's local hour angle double cosH = (Math.cos(Math.toRadians(ZENITH)) - (sinDec * Math.sin(Math.toRadians(latitude)))) / (cosDec * Math.cos(Math.toRadians(latitude))) if (cosH > 1) { return -1 //no sunrise at this location for this date } // Calculate H and convert into hours double H = 360 - Math.toDegrees(Math.acos(cosH)) H = H / 15 // Calculate local mean time double T = H + RA - (0.06571 * t) - 6.622 // Adjust time back to UTC double UT = (T - lngHour) % 24 if (UT < 0) UT += 24 // Convert UT to Local Time Zone ZoneId zone = ZoneId.systemDefault() ZonedDateTime utcTime = date.atTime((int) UT, (int) ((UT % 1) * 60)).atZone(ZoneId.of("UTC")) ZonedDateTime localTime = utcTime.withZoneSameInstant(zone) // Return the local sunrise time return localTime//.toLocalTime() } ZonedDateTime calculateSunset(int year=new Date().getYear() + 1900, int month=new Date().getMonth() + 1, int day=new Date().getDate()) { final double ZENITH = 90.83333 // Official zenith for sunrise/sunset day++ try { LocalDate.of(year,month,day) } catch (dCheck){ month++ day = 1 if(month > 12) month = 1 } LocalDate date=LocalDate.of(year,month,day) int dayOfYear = date.dayOfYear double latitude = location.latitude double longitude = location.longitude // Approximate time in hours double lngHour = longitude / 15 double t = dayOfYear + ((18 - lngHour) / 24) // Sun's mean anomaly double M = (0.9856 * t) - 3.289 // Sun's true longitude double L = M + (1.916 * Math.sin(Math.toRadians(M))) + (0.020 * Math.sin(Math.toRadians(2 * M))) + 282.634 L = (L + 360) % 360 // Sun's right ascension double RA = Math.toDegrees(Math.atan(0.91764 * Math.tan(Math.toRadians(L)))) RA = (RA + 360) % 360 // Right ascension value needs to be in the same quadrant as L double Lquadrant = (Math.floor(L / 90)) * 90 double RAquadrant = (Math.floor(RA / 90)) * 90 RA = RA + (Lquadrant - RAquadrant) // Convert RA into hours RA = RA / 15 // Sun's declination double sinDec = 0.39782 * Math.sin(Math.toRadians(L)) double cosDec = Math.cos(Math.asin(sinDec)) // Sun's local hour angle double cosH = (Math.cos(Math.toRadians(ZENITH)) - (sinDec * Math.sin(Math.toRadians(latitude)))) / (cosDec * Math.cos(Math.toRadians(latitude))) if (cosH > 1) { return -1 // Sun never sets } else if (cosH < -1) { return -1 // Sun never rises } // H = local hour angle in degrees double H = Math.toDegrees(Math.acos(cosH)) / 15 // Local mean time of sunset double T = H + RA - (0.06571 * t) - 6.622 // Adjust back to UTC double UT = (T - lngHour) % 24 if (UT < 0) UT += 24 // Convert UT to Local Time Zone ZoneId zone = ZoneId.systemDefault() ZonedDateTime utcTime = date.atTime((int) UT, (int) ((UT % 1) * 60)).atZone(ZoneId.of("UTC")) ZonedDateTime localTime = utcTime.withZoneSameInstant(zone) // Return the local sunset time return localTime//.toLocalTime() } @SuppressWarnings('unused') void logsOff(){ device.updateSetting("debugEnable",[value:"false",type:"bool"]) } @Field static String cloudFontStyle = '' @Field static String minFwVersion = "2.2.8.141" @Field static List pollList = ["0", "1", "2", "3", "4"] @Field static prefList = [ [parm01:[desc:"CPU Temperature Polling", attributeList:"temperatureF, temperatureC, temperature", method:"cpuTemperatureReq"]], [parm02:[desc:"Free Memory Polling", attributeList:"freeMemory,jvmSize,jvmFree", method:"freeMemoryReq"]], [parm03:[desc:"CPU Load Polling", attributeList:"cpuLoad, cpuPct", method:"cpuLoadReq"]], [parm04:[desc:"DB Size Polling", attributeList:"dbSize", method:"dbSizeReq"]], [parm05:[desc:"Public IP Address", attributeList:"publicIP", method:"publicIpReq"]], [parm06:[desc:"Max Event/State Days Setting", attributeList:"maxEvtDays,maxStateDays", method:"evtStateDaysReq"]], [parm07:[desc:"ZWave Version", attributeList:"zwaveVersion, zwaveSDKVersion", method:"zwaveVersionReq"]], [parm08:[desc:"Time Sync Server Address", attributeList:"ntpServer", method:"ntpServerReq"]], [parm09:[desc:"Additional Subnets", attributeList:"ipSubnetsAllowed", method:"ipSubnetsReq"]], [parm10:[desc:"Hub Mesh Data", attributeList:"hubMeshData, hubMeshCount", method:"hubMeshReq"]], [parm11:[desc:"Expanded Network Data", attributeList:"connectType (Ethernet, WiFi, Dual, Not Connected), connectCapable (Ethernet, WiFi, Dual), dnsServers, staticIPJson, lanIPAddr, wirelessIP, wifiNetwork, dnsStatus, lanSpeed", method:"extNetworkReq"]], [parm12:[desc:"Check for Firmware Update",attributeList:"hubUpdateStatus, hubUpdateVersion",method:"updateCheckReq"]], [parm13:[desc:"Z Status, Hub Alerts, Passive Cloud Check",attributeList:"hubAlerts,zwaveStatus, zigbeeStatus2, pCloud, zbHealthy, zwHealthy", method:"hub2DataReq"]], [parm14:[desc:"Base Data",attributeList:"appStateCompression, firmwareVersionString, hardwareID, id, latitude, localIP, localSrvPortTCP, locationId, locationName, longitude, name, temperatureScale, timeZone, type, uptime, zigbeeChannel, zigbeeEui, zigbeeId, zigbeeStatus, zigbeePower, zigbeeUpdateAvail, zipCode",method:"baseData"]], [parm15:[desc:"15 Minute Averages",attributeList:"cpu15Min, cpu15Pct, freeMem15", method:"fifteenMinute"]], [parm16:[desc:"Active Cloud Connection Check",attributeList:"cloud", method:"checkCloud"]], [parm17:[desc:"Matter Status (C-5 and > only)",attributeList:"matterEnabled, matterStatus", method:"checkMatter"]], [parm18:[desc:"Restricted Access List",attributeList:"accessList", method:"checkAccess"]], [parm19:[desc:"Hub Security Active",attributeList:"securityInUse", method:"checkSecurity"]], [parm20:[desc:"Extended ZWave",attributeList:"zwaveUpdateAvail, zwaveJS, zwaveRegion", method:"extendedZwave"]] ] @Field static String ttStyleStr = "" @Field sdfList = ["yyyy-MM-dd","yyyy-MM-dd HH:mm","yyyy-MM-dd h:mma","yyyy-MM-dd HH:mm:ss","ddMMMyyyy HH:mm","ddMMMyyyy HH:mm:ss","ddMMMyyyy hh:mma", "dd/MM/yyyy HH:mm:ss", "MM/dd/yyyy HH:mm:ss", "dd/MM/yyyy hh:mma", "MM/dd/yyyy hh:mma", "MM/dd HH:mm", "HH:mm", "H:mm","h:mma", "HH:mm:ss", "Milliseconds"]