From 97076f1ff882475a2a2d056c06ea6ba2f7247b95 Mon Sep 17 00:00:00 2001 From: Nolan Gilley Date: Tue, 1 Sep 2015 14:43:14 -0400 Subject: [PATCH 1/5] add support for home_interval variable --- .../components/device_tracker/actiontec.py | 56 +++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/device_tracker/actiontec.py b/homeassistant/components/device_tracker/actiontec.py index 06956475ba0..634deb35eff 100644 --- a/homeassistant/components/device_tracker/actiontec.py +++ b/homeassistant/components/device_tracker/actiontec.py @@ -9,13 +9,17 @@ This device tracker needs telnet to be enabled on the router. Configuration: To use the Actiontec tracker you will need to add something like the -following to your config/configuration.yaml +following to your config/configuration.yaml. If you experience disconnects +you can modify the home_interval variable. + device_tracker: platform: actiontec host: YOUR_ROUTER_IP username: YOUR_ADMIN_USERNAME password: YOUR_ADMIN_PASSWORD + # optional: + home_interval: 10 Variables: @@ -30,21 +34,30 @@ The username of an user with administrative privileges, usually 'admin'. password *Required The password for your given admin account. + +home_interval +*Optional +Number of minutes it will not scan devices that it found in previous results. """ import logging from datetime import timedelta +from collections import namedtuple import re import threading import telnetlib +import homeassistant.util.dt as dt_util from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD from homeassistant.helpers import validate_config -from homeassistant.util import Throttle +from homeassistant.util import Throttle, convert from homeassistant.components.device_tracker import DOMAIN # Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) +# interval in minutes to exclude devices from a scan while they are home +CONF_HOME_INTERVAL = "home_interval" + _LOGGER = logging.getLogger(__name__) _LEASES_REGEX = re.compile( @@ -64,6 +77,8 @@ def get_scanner(hass, config): return scanner if scanner.success_init else None +Device = namedtuple("Device", ["mac", "ip", "last_update"]) + class ActiontecDeviceScanner(object): """ This class queries a an actiontec router @@ -74,6 +89,8 @@ class ActiontecDeviceScanner(object): self.host = config[CONF_HOST] self.username = config[CONF_USERNAME] self.password = config[CONF_PASSWORD] + minutes = convert(config.get(CONF_HOME_INTERVAL), int, 0) + self.home_interval = timedelta(minutes=minutes) self.lock = threading.Lock() @@ -82,37 +99,56 @@ class ActiontecDeviceScanner(object): # Test the router is accessible data = self.get_actiontec_data() self.success_init = data is not None + _LOGGER.info("actiontec scanner initialized") + if self.home_interval: + _LOGGER.info("home_interval set to: %s" % self.home_interval) def scan_devices(self): """ Scans for new devices and return a list containing found device ids. """ self._update_info() - return [client['mac'] for client in self.last_results] + return [client.mac for client in self.last_results] def get_device_name(self, device): """ Returns 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: - return client['ip'] + if client.mac == device: + return client.ip return None @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): """ Ensures the information from the Actiontec MI424WR router is up to date. Returns boolean if scanning successful. """ + _LOGGER.info("Scanning") if not self.success_init: return False with self.lock: - # _LOGGER.info("Checking ARP") - data = self.get_actiontec_data() - if not data: + exclude_targets = set() + target_list = [] + self.last_results = [] + now = dt_util.now() + if self.home_interval: + for host in self.last_results: + if host.last_update + self.home_interval > now: + exclude_targets.add(host) + if len(exclude_targets) > 0: + target_list = [t.ip for t in exclude_targets] + + devices = self.get_actiontec_data() + if not devices: return False - active_clients = [client for client in data.values()] - self.last_results = active_clients + for ip in target_list: + if ip in devices: + devices.pop(ip) + for ip, data in devices.items(): + device = Device(data['mac'], ip, now) + self.last_results.append(device) + return True def get_actiontec_data(self): From d2a13da930521e36a5e3309c2e776b65a00aa86e Mon Sep 17 00:00:00 2001 From: Nolan Gilley Date: Tue, 1 Sep 2015 15:09:41 -0400 Subject: [PATCH 2/5] pylint fix --- homeassistant/components/device_tracker/actiontec.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/device_tracker/actiontec.py b/homeassistant/components/device_tracker/actiontec.py index 634deb35eff..33f1b8d6461 100644 --- a/homeassistant/components/device_tracker/actiontec.py +++ b/homeassistant/components/device_tracker/actiontec.py @@ -101,7 +101,7 @@ class ActiontecDeviceScanner(object): self.success_init = data is not None _LOGGER.info("actiontec scanner initialized") if self.home_interval: - _LOGGER.info("home_interval set to: %s" % self.home_interval) + _LOGGER.info("home_interval set to: %s", self.home_interval) def scan_devices(self): """ Scans for new devices and return a @@ -142,11 +142,11 @@ class ActiontecDeviceScanner(object): devices = self.get_actiontec_data() if not devices: return False - for ip in target_list: - if ip in devices: - devices.pop(ip) - for ip, data in devices.items(): - device = Device(data['mac'], ip, now) + for client in target_list: + if client in devices: + devices.pop(client) + for name, data in devices.items(): + device = Device(data['mac'], name, now) self.last_results.append(device) return True From 5b643a81068c4faf1f0066e89be28db67435851f Mon Sep 17 00:00:00 2001 From: Nolan Gilley Date: Wed, 2 Sep 2015 11:46:09 -0400 Subject: [PATCH 3/5] fixes for Paulus' comments. --- .../components/device_tracker/actiontec.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/device_tracker/actiontec.py b/homeassistant/components/device_tracker/actiontec.py index 33f1b8d6461..c54436edd84 100644 --- a/homeassistant/components/device_tracker/actiontec.py +++ b/homeassistant/components/device_tracker/actiontec.py @@ -37,7 +37,8 @@ The password for your given admin account. home_interval *Optional -Number of minutes it will not scan devices that it found in previous results. +If the home_interval is set then the component will not let a device +be AWAY if it has been HOME in the last home_interval seconds. """ import logging from datetime import timedelta @@ -94,7 +95,7 @@ class ActiontecDeviceScanner(object): self.lock = threading.Lock() - self.last_results = {} + self.last_results = [] # Test the router is accessible data = self.get_actiontec_data() @@ -129,26 +130,27 @@ class ActiontecDeviceScanner(object): with self.lock: exclude_targets = set() - target_list = [] - self.last_results = [] + exclude_target_list = [] now = dt_util.now() if self.home_interval: for host in self.last_results: if host.last_update + self.home_interval > now: exclude_targets.add(host) if len(exclude_targets) > 0: - target_list = [t.ip for t in exclude_targets] + exclude_target_list = [t.ip for t in exclude_targets] - devices = self.get_actiontec_data() - if not devices: + self.last_results = [] + actiontec_data = self.get_actiontec_data() + if not actiontec_data: return False - for client in target_list: - if client in devices: - devices.pop(client) - for name, data in devices.items(): + for client in exclude_target_list: + if client in actiontec_data: + actiontec_data.pop(client) + for name, data in actiontec_data.items(): device = Device(data['mac'], name, now) self.last_results.append(device) - + self.last_results.extend(exclude_targets) + _LOGGER.info("actiontec scan successful") return True def get_actiontec_data(self): From 5533618bd2a8bd02af382d2bc429fe889d267ec7 Mon Sep 17 00:00:00 2001 From: Nolan Gilley Date: Wed, 2 Sep 2015 11:48:36 -0400 Subject: [PATCH 4/5] fix comments for home_interval --- homeassistant/components/device_tracker/actiontec.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/actiontec.py b/homeassistant/components/device_tracker/actiontec.py index c54436edd84..7c6e9cce6be 100644 --- a/homeassistant/components/device_tracker/actiontec.py +++ b/homeassistant/components/device_tracker/actiontec.py @@ -38,7 +38,8 @@ The password for your given admin account. home_interval *Optional If the home_interval is set then the component will not let a device -be AWAY if it has been HOME in the last home_interval seconds. +be AWAY if it has been HOME in the last home_interval minutes. This is +in addition to the 3 minute wait built into the device_tracker component. """ import logging from datetime import timedelta From b9b751d234d6feb9fcba8cc24c3f3a681f8de6ee Mon Sep 17 00:00:00 2001 From: Nolan Gilley Date: Wed, 2 Sep 2015 12:00:20 -0400 Subject: [PATCH 5/5] fix for last_results --- homeassistant/components/device_tracker/actiontec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/actiontec.py b/homeassistant/components/device_tracker/actiontec.py index 7c6e9cce6be..a922da9fe88 100644 --- a/homeassistant/components/device_tracker/actiontec.py +++ b/homeassistant/components/device_tracker/actiontec.py @@ -140,10 +140,10 @@ class ActiontecDeviceScanner(object): if len(exclude_targets) > 0: exclude_target_list = [t.ip for t in exclude_targets] - self.last_results = [] actiontec_data = self.get_actiontec_data() if not actiontec_data: return False + self.last_results = [] for client in exclude_target_list: if client in actiontec_data: actiontec_data.pop(client)