[Release] Hubitat Ping

Had a few minutes so I wrapped some driver code around the Hubitat Ping endpoint. Driver returns the data packet loss percentage, as well as the avg, max, min, and mdev statistics from the command as attributes.

As it takes a couple of seconds to run, there is also responseReady boolean that can be used as a trigger that the ping has completed.

The Ping Repeat preference allows an automatic periodic ping (defaults to zero which is disabled) - probably shouldn’t use a value lower 15 seconds to make sure the previous ping finishes before the next one starts.

Driver is available through HPM or can be installed manually using the code at:

https://raw.githubusercontent.com/thebearmay/hubitat/main/hubPing.groovy

16 Likes

@Robertgmcneill

Not a RM expert by any stretch, but my thought would be something along the lines of:

Create a Rule that requests a ping using the driver on some routine basis. Then in another rule or after a pause in the first rule to allow the request to complete look at the percentLoss attribute and base your on/off condition off of it, i.e. if 100% loss turn off the switch, if less than 100 turn it on.

I turned off the device to force a failure and received this error
)java.lang.IllegalArgumentException: Command 'size' is not supported by device. on line 1860 (selectConditions)
I think it maybe that you are parsing a failure vice a success

--- Loading Past Logs... ---

Looks like it might be a RM error message (I don’t have a Line 1860).

I’ve also just added a responseReady boolean that you can use as a trigger to check the command return values.

can we convert this to a virtual device (presence sensor like) which would show the availability of the pinged device ?

I can do that , give me about 10 minutes...

...and done (8 minutes):sunglasses:. Now supports capabiliy PresenceSensor.

3 Likes

Outstanding , You know our stuff. It works perfectly

1 Like

I added a Ping Repeat preference, to allow an automatic periodic ping (defaults to zero which is disabled) - probably shouldn’t use a value lower 15 seconds to make sure the previous ping finishes before the next one starts.

actually my intension was to create a device driver which would check availability of the device and show.
I made one copying from your code. (see below)
btw, you have a typo in your code (debubEnable)

metadata {
	definition (name: "Host Ping Device", namespace: "ilkeraktuna", author: "ilkeraktuna") {
		capability "Switch"
    capability "PresenceSensor"
    command "xon"
    command "xoff"
		attribute "state", "string"
    attribute "percentLoss", "number"
	}
preferences {
		section {
			input title: "", description: "Pingable Device", displayDuringSetup: true, type: "paragraph", element: "paragraph"
			input("name", "string", title:"Name", description: "Name", required: true, displayDuringSetup: true)
			input("ip", "string", title:"LAN IP address", description: "LAN IP address", required: true, displayDuringSetup: true)
			input("pingPeriod", "number", title: "Ping Repeat in Seconds\n Zero to disable", defaultValue: 60, required:true, submitOnChange: true)
        input("wol", "bool", title: "Enable Wake On LAN?")
        input("myMac", "string", title: "MAC address", required: false)
			input("debugEnable", "bool", title: "Enable debug logging?")
			if (security) { 
				input("username", "string", title: "Hub Security Username", required: false)
				input("password", "password", title: "Hub Security Password", required: false)
			}
    }
}
	tiles(scale: 2) {
    standardTile("switch", "device.switch", width: 6, height: 6, canChangeIcon: true) {
		state "off", label: 'Offline', icon: "st.Electronics.electronics18", backgroundColor: "#ff0000"
		state "on", label: 'Online', icon: "st.Electronics.electronics18", backgroundColor: "#79b821"
		}
	main("switch")
    details(["switch"])
}
}

def on() {
if(debugEnable)log.debug "turning on"
sendEvent(name: "switch", value: "on");
if(wol) sendHubCommand(createWOL())
}
def off() {
if(debugEnable)log.debug "turning off"
sendEvent(name: "switch", value: "off");
}
def xon() {
if(debugEnable)log.debug "on new"
sendEvent(name: "switch", value: "on");
sendEvent(name: "presence", value: "present");
}

def xoff() {
if(debugEnable)log.debug "off new"
sendEvent(name: "switch", value: "off");
sendEvent(name: "presence", value: "not present");
}

def installed() {
initialize()
}

def updated() {
initialize()
}

def poll(){
refresh()
}

def initialize() {
	//state.statx="off"
unschedule()
	//if(pingPeriod > 0) runIn(pingPeriod, "sendPing", [data:ipAddress])
	if(pingPeriod > 0) runIn(pingPeriod, "refresh")
	//runEvery1Minute(refresh)
}

def refresh() {
def host = ip
	if(pingPeriod > 0) runIn(pingPeriod, "refresh")
sendPing(ip)
}

def sendPing(ipAddress){
if(ipAddress == null) ipAddress = data.ip
// start - Modified from dman2306 Rebooter app
if(security) {
    httpPost(
        [
            uri: "http://127.0.0.1:8080",
            path: "/login",
            query: [ loginRedirect: "/" ],
            body: [
                username: username,
                password: password,
                submit: "Login"
            ]
        ]
    ) { resp -> cookie = resp?.headers?.'Set-Cookie'?.split(';')?.getAt(0) }
}
// End - Modified from dman2306 Rebooter app

params = [
    uri: "http://${location.hub.localIP}:8080",
    path:"/hub/networkTest/ping/"+ipAddress,
    headers: [ "Cookie": cookie ]
]
if(debugEnable)log.debug params
asynchttpGet("sendPingHandler", params)
//updateAttr("responseReady",false)
//updateAttr("pingReturn","Pinging $ipAddress")  
if(pingPeriod > 0) runIn(pingPeriod, "sendPing", [data:ipAddress])

}

def sendPingHandler(resp, data) {
def errFlag = 0
try {
	    if(resp.getStatus() == 200 || resp.getStatus() == 207) {
		    strWork = resp.data.toString()
		if(debugEnable) log.debug strWork
			//xon()
  	    }
} catch(Exception ex) { 
    errFlag = 1
    respStatus = resp.getStatus()
    sendEvent(name:"pingReturn", value: "httpResp = $respStatus but returned invalid data")
    xoff()
		log.warn "sendPing httpResp = $respStatus but returned invalid data"
} 
if (errFlag==0) extractValues(strWork)
}

def extractValues(strWork) {
startInx = strWork.indexOf("%")
if(debugEnable)log.debug startInx
percentLossX=0
if (startInx == -1){
    percentLossX=100
    updateAttr("percentLoss",100)      
} else {
    startInx -=3
    strWork=strWork.substring(startInx)
    if(strWork.substring(0,1)==","){
        percentLossX = strWork.substring(1,3).toInteger()
    } else
        percentLossX = strWork.substring(0,3).toInteger()
    updateAttr("percentLoss",percentLossX)        
    startInx = strWork.indexOf("=")
}
if(debugEnable)log.debug percentLossX
if (percentLossX < 100 ) xon()
else xoff()
}

def updateAttr(aKey, aValue){
if(debugEnable)log.debug "update percentLoss"
sendEvent(name:aKey, value:aValue)
}

def updateAttr(aKey, aValue, aUnit){
sendEvent(name:aKey, value:aValue, unit:aUnit)
}

def createWOL() {
	def newMac = myMac.replaceAll(":","").replaceAll("-","")
if(debugEnable) log.debug "Sending Magic Packet to: $newMac"
def result = new hubitat.device.HubAction (
   	"wake on lan $newMac",
   	hubitat.device.Protocol.LAN,
   	null
)    
return result
}
2 Likes

is it possible to increase/decrease the ping packets per test ?
is this an option for the "/hub/networkTest/ping/" command ?
because with the current 5 packets , I sometimes get a false negative.

Doesn’t look like the endpoint accepts any parameters outside of the IP Address at this time.

I am trying to use your driver to continuously check if my pi-hole is present, put I need it to save the ip so that I can have a swich to reboot it if it crashes, any input?

Are you wanting to save the IP address in the driver, or on the router? If the Ping Repeat interval is greater than zero, the driver should be handling retention of the IP address, and restart the ping repeat after hub restart.

I want to be able to see the ip adress that is being used
I am also noticing some false negatives upon pinging, to then change to a positif, is there a way to wait for the ping before sending a response?

I can add a lastIpAddress attribute. The responseReady attribute is designed to be the indicator that the attributes are set appropriately; while the ping command is processing presence is set to "not present" as part of the initialization (the capability requires either "present" or "not present") but if it is causing an issue I can just as easily leave it at the last value.

Edit: Changes above are in v1.1.0, should be available from HPM or the URL in the first post.

ok, it worked! but it does not change state when ping is unresponsive, also, should there be a timeout if it does not respond in a certain amount of time?

Ah... I see the issue, I was assuming that I’d already set it to “not present” and didn’t need to handle that condition any more. Give me a few minutes...

Edit:. Changes are out there. Didn’t increment the version, so do a repair if using HPM.

Hub always responds eventually, unless it’s locked up or rebooting at which point the device driver can’t update anyway.

image
this is what I am trying to say, the ping is sent every 20 seconds, but the presence is still at present. It eventually sends another ping without having the result of the previous ping, hence not changing the presence state

That was the reason I suggested a longer interval above, but the way it was coded could result in a potential runaway condition, so I’ve moved the repeat submission to occur after receiving the response instead of after sending the request to the hub. Go ahead and pull again.