/*
* Hub Updater
*
* 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
* ---- --- --------------------------------------
* 06Aug2024 thebearmay v2.0.5 code reversion issue
* v2.0.6 send request even if publisher is current, fix notes link
* 09Aug2024 v2.0.7 update msg to show current HE version at initialize (5min delay)
* 06Nov2024 v2.0.8 add "Switch" capability to enable HomeKit usage
* 21Nov2024 v2.0.9 add capability to use a list of hubs for updates
*
*/
import groovy.transform.Field
import groovy.json.JsonSlurper
import groovy.json.JsonOutput
static String version() { return '2.0.8' }
metadata {
definition (
name: "Hub Updater v2",
namespace: "thebearmay",
author: "Jean P. May, Jr.",
importUrl: "https://raw.githubusercontent.com/thebearmay/hubitat/main/hubUpdaterV2.groovy"
) {
capability "Actuator"
capability "Configuration"
capability "Initialize"
capability "Momentary"
capability "Switch"
attribute "msg", "string"
attribute "notesUrl", "string"
attribute "hubList", "string"
command "sendTestMsg"
command "subscribe",[[type:"string",description:"IP of Publisher Hub"]]
command "unsubscribe"
command "updateHubList",[[type:"string",description:"Comma separated list of hubs to update"]]
}
}
preferences {
input("termsAccepted","bool",title: "By using this driver you are agreeing to the Hubitat Terms of Service",required:true)
input("updMesh","bool", title: "Push Update Request to All HubMeshed Hubs")
input("useHubList","bool",title: "Push Update Request using Hub List Attribute")
input("debugEnabled", "bool", title: "Enable debug logging?", width:4)
}
void installed() {
log.trace "Hub Updater v${version()} installed()"
configure()
}
void configure() {
if(debugEnabled) log.debug "configure()"
device.updateDataValue('publishingDNI', getHostAddress())
off()
}
void initialize() {
runIn(300,"verCheck")
}
def updateAttr(aKey, aValue){
sendEvent(name:aKey, value:aValue)
}
def updateAttr(aKey, aValue, aUnit){
sendEvent(name:aKey, value:aValue, unit:aUnit)
}
void verCheck() {
updateAttr('msg',"Hub is on v${location.hub.firmwareVersionString}")
}
void test(){
log.debug "Test $debugEnabled"
}
void updateHubList(hList){
if(hList == null) {
device.deleteCurrentState('hubList')
return
}
updateAttr("hubList",hList.replace(" ",""))
}
def on(){
push()
updateAttr("switch","on")
off()
}
def off(){
updateAttr("switch","off")
}
def push(){
log.info "Firmware Update Requested"
if(!termsAccepted) {
updateAttr("msg", "Please accept terms and conditions first")
return
}
updateAttr ("msg", "Update Requested at ${new Date()}")
params = [
uri: "http://127.0.0.1:8080",
path:"/hub/cloud/checkForUpdate",
timeout: 10
]
asynchttpGet("getUpdateCheck", params)
}
void getUpdateCheck(resp, data) {
if(updMesh) {
updateMesh()
} else if(useHubList) {
updateFromList()
}
if(debugEnable) log.debug "update check: ${resp.status}"
try {
if (resp.status == 200) {
def jSlurp = new JsonSlurper()
if (debugEnabled) log.debug "${resp.data}"
Map resMap = (Map)jSlurp.parseText((String)resp.data)
if(resMap.status == "NO_UPDATE_AVAILABLE")
updateAttr("msg","Hub is Current")
else {
updateAttr("msg","${resMap.version} requested")
updateAttr("notesUrl","Release Notes")
httpGet("http://127.0.0.1:8080/hub/cloud/updatePlatform"){ response ->
updateAttr("msg", "${response.data}")
}
}
}
}catch(ignore) {
updateAttr("msg", "Hub is Current")
}
}
void updateMesh(){
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.debug resp.data
def jSlurp = new JsonSlurper()
Map h2Data = (Map)jSlurp.parseText((String)resp.data)
if (debugEnable)
log.debug "${h2Data.hubList}"
h2Data.hubList.each{
log.info "Requesting update of ${it.ipAddress}"
sendMsg(it.ipAddress,"Update Requested")
}
} else {
if (!warnSuppress) log.warn "Status ${resp.getStatus()} on Hubmesh request"
}
} catch (Exception ex){
if (!warnSuppress) log.warn ex
}
}
void updateFromList(){
hubList = device.currentValue("hubList")
hList = hubList.split(',')
hList.each{
log.info "Requesting update of ${it}"
sendMsg(it,"Update Requested")
}
}
def updated(){
log.trace "updated()"
if(debugEnabled) runIn(1800,logsOff)
}
void logsOff(){
device.updateSetting("debugEnabled",[value:"false",type:"bool"])
}
def subscribe(ip){
hostHex = getHostAddress(ip)
device.setDeviceNetworkId(hostHex)
device.updateDataValue('subscriptionDNI',hostHex)
}
def unsubscribe(){
newDNI=UUID.randomUUID().toString()
device.removeDataValue('subscriptionDNI')
device.setDeviceNetworkId(newDNI)
}
def getHostAddress(ip=''){
if(!ip)
ipTokens = location.hub.localIP.split('\\.')
else
ipTokens = ip.split('\\.')
hexStr=''
ipTokens.each{
wStr=Integer.toString(it.toInteger(),16).toUpperCase()
if(wStr.size() == 1)
hexStr+="0$wStr"
else
hexStr+="$wStr"
}
return hexStr
}
def sendTestMsg(){
if(useHubList){
hubList = device.currentValue("hubList")
hList = hubList.split(',')
hList.each{
log.info "Testing Message to ${it}"
sendMsg(it,"Test Message")
}
return
}
params = [
uri : "http://127.0.0.1:8080",
path : "/hub2/hubMeshJson",
headers: [
"Connection-Timeout":600
]
]
if(debugEnable)log.debug params
asynchttpGet("getHubMeshData", params)
}
@SuppressWarnings('unused')
void getHubMeshData(resp, data){
try{
if (resp.getStatus() == 200){
if (debugEnable)
log.debug resp.data
def jSlurp = new JsonSlurper()
Map h2Data = (Map)jSlurp.parseText((String)resp.data)
if (debugEnable)
log.debug "${h2Data.hubList}"
h2Data.hubList.each{
log.info "Testing Message to ${it.ipAddress}"
sendMsg(it.ipAddress,"Test Message")
}
} else {
if (!warnSuppress) log.warn "Status ${resp.getStatus()} on Hubmesh request"
}
} catch (Exception ex){
if (!warnSuppress) log.warn ex
}
}
def sendMsg(ip, msg){
def bodyText = JsonOutput.toJson(['msg':"$msg"])
Map requestParams =
[
uri: "http://$ip:39501",
contentType: 'application/json',
body: "$bodyText"
]
if(debugEnabled)
log.debug "$requestParams"
asynchttpPost("getResp", requestParams)
}
def getResp(resp, data){
if(debugEnabled) log.debug "Send response: ${resp.properties}"
}
void parse(String msgIn) {
// Called everytime a POST message is received from Port 39501 if DNI matches Hex of sender
try {
Map data = parseLanMessage(msgIn);
if(data.json.msg =="Update Requested" )
push()
else
updateAttr("msg", data.json.msg)
} catch (e) {
log.error "$e"
}
}