The existing ASUSWRT component was not working for me, probably because I’m only using my ASUS router as an access point so DHCP server is not enabled on it and therefore /var/lib/misc/dnsmasq.leases does not exist on the router.
So, I wrote up an alternate component, that goes through the web interface instead. Here it is if anyone is interested in using it:
import logging
import re
import threading
try:
from urllib2 import urlopen
PYTHON = 2
except ImportError:
from urllib.request import urlopen
PYTHON = 3
import json
from collections import defaultdict
from datetime import timedelta
from homeassistant.components.device_tracker import DOMAIN
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
REQUIREMENTS = ['urllib3']
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
_FTR = [3600,60,1]
# pylint: disable=unused-argument
def get_scanner(hass, config):
"""Validate the configuration and return an ASUS-WRT scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return None
scanner = AsusWrtDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
class AsusWrtDeviceScanner(object):
"""This class queries a router running ASUSWRT firmware."""
def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST]
self.username = str(config[CONF_USERNAME])
self.password = str(config[CONF_PASSWORD])
self.lock = threading.Lock()
self.last_results = {}
# Test the router is accessible.
data = self.get_asuswrt_data()
self.success_init = data is not None
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return [client['mac'] for client in self.last_results]
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
if not self.last_results:
return None
for client in self.last_results:
if client['mac'] == device and 'name' in client:
return client['name']
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Ensure the information from the ASUSWRT router is up to date.
Return boolean if scanning successful.
"""
if not self.success_init:
return False
with self.lock:
_LOGGER.info("Checking ASUSWRT clients")
data = self.get_asuswrt_data()
if not data:
return False
active_clients = [client for client in data.values() if
'wifi' in client and
client['wifi'] and
'connected' in client and
client['connected']]
#_LOGGER.info(active_clients)
self.last_results = active_clients
return True
def get_asuswrt_data(self):
"""Retrieve data from ASUSWRT and return parsed result."""
data = urlopen('http://%s/update_clients.asp' % self.host).read()
if PYTHON == 3:
data = data.decode('utf-8')
devices = defaultdict(dict)
device_info = re.findall(r"fromNetworkmapd: '(.*?)'", data)[0].split('<')
for d in device_info:
info = d.split('>')
if len(info) < 2:
continue
devices[info[3]] = {
'name': info[1],
'ip': info[2],
'mac': info[3]
}
val = re.findall(r'([A-Za-z_0-9]*): (\[.*\])', data)
json_lists = {x[0]: x[1] for x in val}
for d in json.loads(json_lists['wlList_2g']):
mac = d[0]
devices[mac]['mac'] = mac
devices[mac]['wifi'] = True
devices[mac]['2g'] = True
devices[mac]['signal'] = d[3]
devices[mac]['connected'] = d[1]=='Yes'
for d in json.loads(json_lists['wlList_5g']):
mac = d[0]
devices[mac]['mac'] = mac
devices[mac]['wifi'] = True
devices[mac]['5g'] = True
devices[mac]['signal'] = d[3]
devices[mac]['connected'] = d[1]=='Yes'
for d in json.loads(json_lists['wlListInfo_2g']):
mac = d[0]
devices[mac]['mac'] = mac
devices[mac]['tx'] = d[1]
devices[mac]['rx'] = d[2]
devices[mac]['connection_time'] = sum([a*b for a,b in zip(_FTR, map(int,d[3].split(':')))]) #convert from 12:34:12 to seconds
for d in json.loads(json_lists['wlListInfo_5g']):
mac = d[0]
devices[mac]['mac'] = mac
devices[mac]['tx'] = d[1]
devices[mac]['rx'] = d[2]
devices[mac]['connection_time'] = sum([a*b for a,b in zip(_FTR, map(int,d[3].split(':')))])
return devices
I just modified the existing asuswrt.py component, so this is probably not very clean and has some extra stuff that’s not needed, such as the username and password config fields (surprisingly you don’t need to be authenticated to fetch that URL). Also not sure if it is robust to different versions of the ASUSWRT firmware.
I’m not sure how it compares to the existing component, but since this is checking on the wifi signal info it gets to know almost instantly when devices have disconnected from the network.
Maybe it could be possible to combine the two methods into one component and define in the configuration which one to use. Anyway, just wanted to put this code out there.