Hello,
Does anyone have any simple easy to understand examples of basic telnet usage in Hubitat? There doesn't seem to be any sort of official docs on how it works so I'm trying to glean from the various examples I can find in the forums.
Since Hubitat doesn't natively support Wifi based presence detection based on MAC address. (IE: the sort that OpenHAB uses) I'm trying to build a device driver that mimics the OpenHAB capability by telneting into my router, running some ARP and PING commands and then parsing the response to determine if a given device (by MAC address) is currently on my Wifi network.
I've done a considerable amount of testing the router commands via telnet and so far it seems to do a very good job of detecting the presence of our Android phones. The only hitch is getting Hubitat to send the commands to the router. Here is my device driver code:
metadata
{
definition (name: "Telnet Sensor", namespace: "jeremy.akers", author: "Jeremy Akers")
{
capability "Presence Sensor"
capability "Sensor"
capability "Polling"
capability "Telnet"
capability "Initialize"
command "poll"
command "forcePoll"
}
preferences
{
section("Device Settings:")
{
input "username", "string", title:"Router Username", description: "Username", required: true, displayDuringSetup: true
input "password", "password", title:"Router Password", description: "Password", required: true, displayDuringSetup: true
input "MAC", "string", title:"MAC of Device", description: "MAC Address", required: true, displayDuringSetup: true
}
}
}
def installed()
{
log.info('Telnet Presence: installed()')
initialize()
state.presence = false
state.ip = null
state.waiting = false
}
def updated()
{
log.info('Telnet Presence: updated()')
initialize()
state.presence = false
state.ip = null
state.waiting = false
unschedule()
runEvery1Minute(pollSchedule)
}
def pollSchedule()
{
forcePoll()
}
def initialize()
{
log.info('Telnet Presence: initialize()')
telnetClose()
telnetConnect([terminalType: 'VT100'], '192.168.0.1', 23, settings.username, settings.password)
}
def forcePoll()
{
log.debug "Polling for host ${settings.MAC}..."
//sendMsg("arp -an | grep ${settings.MAC} || echo No Match")
sendMsg("sleep 10000")
log.debug("Message sent. Awaiting response.")
state.waiting = true
state.lastPoll = now()
}
def poll()
{
if(now() - state.lastPoll > (60000))
forcePoll()
else
log.debug "poll called before interval threshold was reached"
}
def sendMsg(String msg)
{
log.info("Sending telnet msg: " + msg)
return new hubitat.device.HubAction(msg, hubitat.device.Protocol.TELNET)
}
private parse(String msg)
{
log.debug("Parse: " + msg)
if(msg.startsWith("? "))
{
Scanner scanner = new Scanner(msg);
scanner.next()
iptext = scanner.next()
ip = iptext.substring(1, iptext.length()-1)
scanner.next()
mac = scanner.next()
log.debug("IP: " + ip + ", MAC: " + mac)
state.ip = ip
state.presence = true
state.waiting = false
}
else if(msg.equals("No Match"))
{
state.ip = null
state.presence = false
state.waiting = false
}
}
def telnetStatus(String status){
log.warn "telnetStatus: error: " + status
if (status != "receive error: Stream is closed")
{
log.error "Connection was dropped."
initialize()
}
}
Now here's the really confusing part: This code was working for a few minutes today, and then stopped again. I was making changes and fine tuning the parsing of the ARP command output when it just stopped sending the telnet commands to the router. I hadn't touched any of the actual telnet handling code, only the part that parses the response. As you can see from the code I log the entire response before parsing it so if it were failing somewhere in the parse block I should be able to see that in the logs.
However what I am seeing is this behavior:
[dev:577](http://192.168.0.55/logs#dev577)2018-12-04 07:59:39.163 pm [debug](http://192.168.0.55/device/edit/577)Message sent. Awaiting response.
[dev:577](http://192.168.0.55/logs#dev577)2018-12-04 07:59:39.161 pm [info](http://192.168.0.55/device/edit/577)Sending telnet msg: sleep 10000
[dev:577](http://192.168.0.55/logs#dev577)2018-12-04 07:59:39.160 pm [debug](http://192.168.0.55/device/edit/577)Polling for host <mac address>...
[dev:577](http://192.168.0.55/logs#dev577)2018-12-04 07:59:32.258 pm [debug](http://192.168.0.55/device/edit/577)Parse:
[dev:577](http://192.168.0.55/logs#dev577)2018-12-04 07:59:32.093 pm [info](http://192.168.0.55/device/edit/577)Telnet Presence: initialize()
The telnet connection is made within "initialize". This step works because I can see the telnet connection when I run a netstat command on the router itself:
admin@RT-AC3200:/tmp/home/root# netstat -en | grep :23
tcp 0 0 192.168.0.1:23 192.168.0.233:41642 ESTABLISHED
tcp 0 0 192.168.0.1:23 192.168.0.55:48424 ESTABLISHED
192.168.0.1 is the ASUS router, 192.168.0.55 is my Hubitat device and 192.168.0.233 is my PC. You can see the Hubitat shows an established connection via telnet.
Further evidence that the telnetConnect succeeds: if I enter the wrong password I get a message back (via my parse method) saying "Login Incorrect"
However what isn't showing up is any evidence of successfully running any commands, so I suspect something is failing with the HubAction. It's not throwing any exceptions or putting any errors in the logs so it's making it very difficult to understand what the problem is.
If I let this sit idle long enough, I do finally get a message back via parse:
[dev:577](http://192.168.0.55/logs#dev577)2018-12-04 08:19:32.497 pm [warn](http://192.168.0.55/device/edit/577)telnetStatus: error: receive error: Stream is closed
[dev:577](http://192.168.0.55/logs#dev577)2018-12-04 08:19:32.483 pm [debug](http://192.168.0.55/device/edit/577)Parse: admin@RT-AC3200:/tmp/home/root# atimed out waiting for input: auto-logout
Any tips on how to investigate this further? What's really puzzling me is that this was working, and then just mysteriously stopped as I was working on the "parse" method code.
-Jeremy