Cleanup old device_tracker stuff (#8627)

* Cleanup old device_tracker stuff

* Fix lint
This commit is contained in:
Pascal Vizeli 2017-07-24 16:45:02 +02:00 committed by Paulus Schoutsen
parent 654ad41464
commit f86bd15580
22 changed files with 478 additions and 679 deletions

View File

@ -7,9 +7,7 @@ https://home-assistant.io/components/device_tracker.actiontec/
import logging import logging
import re import re
import telnetlib import telnetlib
import threading
from collections import namedtuple from collections import namedtuple
from datetime import timedelta
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -17,9 +15,6 @@ import homeassistant.util.dt as dt_util
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -54,7 +49,6 @@ class ActiontecDeviceScanner(DeviceScanner):
self.host = config[CONF_HOST] self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME] self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD] self.password = config[CONF_PASSWORD]
self.lock = threading.Lock()
self.last_results = [] self.last_results = []
data = self.get_actiontec_data() data = self.get_actiontec_data()
self.success_init = data is not None self.success_init = data is not None
@ -74,7 +68,6 @@ class ActiontecDeviceScanner(DeviceScanner):
return client.ip return client.ip
return None return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Ensure the information from the router is up to date. """Ensure the information from the router is up to date.
@ -84,16 +77,15 @@ class ActiontecDeviceScanner(DeviceScanner):
if not self.success_init: if not self.success_init:
return False return False
with self.lock: now = dt_util.now()
now = dt_util.now() actiontec_data = self.get_actiontec_data()
actiontec_data = self.get_actiontec_data() if not actiontec_data:
if not actiontec_data: return False
return False self.last_results = [Device(data['mac'], name, now)
self.last_results = [Device(data['mac'], name, now) for name, data in actiontec_data.items()
for name, data in actiontec_data.items() if data['timevalid'] > -60]
if data['timevalid'] > -60] _LOGGER.info("Scan successful")
_LOGGER.info("Scan successful") return True
return True
def get_actiontec_data(self): def get_actiontec_data(self):
"""Retrieve data from Actiontec MI424WR and return parsed result.""" """Retrieve data from Actiontec MI424WR and return parsed result."""

View File

@ -6,8 +6,6 @@ https://home-assistant.io/components/device_tracker.aruba/
""" """
import logging import logging
import re import re
import threading
from datetime import timedelta
import voluptuous as vol import voluptuous as vol
@ -15,14 +13,11 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pexpect==4.0.1'] REQUIREMENTS = ['pexpect==4.0.1']
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_DEVICES_REGEX = re.compile( _DEVICES_REGEX = re.compile(
r'(?P<name>([^\s]+))\s+' + r'(?P<name>([^\s]+))\s+' +
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' + r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' +
@ -52,8 +47,6 @@ class ArubaDeviceScanner(DeviceScanner):
self.username = config[CONF_USERNAME] self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD] self.password = config[CONF_PASSWORD]
self.lock = threading.Lock()
self.last_results = {} self.last_results = {}
# Test the router is accessible. # Test the router is accessible.
@ -74,7 +67,6 @@ class ArubaDeviceScanner(DeviceScanner):
return client['name'] return client['name']
return None return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Ensure the information from the Aruba Access Point is up to date. """Ensure the information from the Aruba Access Point is up to date.
@ -83,13 +75,12 @@ class ArubaDeviceScanner(DeviceScanner):
if not self.success_init: if not self.success_init:
return False return False
with self.lock: data = self.get_aruba_data()
data = self.get_aruba_data() if not data:
if not data: return False
return False
self.last_results = data.values() self.last_results = data.values()
return True return True
def get_aruba_data(self): def get_aruba_data(self):
"""Retrieve data from Aruba Access Point and return parsed result.""" """Retrieve data from Aruba Access Point and return parsed result."""

View File

@ -8,9 +8,7 @@ import logging
import re import re
import socket import socket
import telnetlib import telnetlib
import threading
from collections import namedtuple from collections import namedtuple
from datetime import timedelta
import voluptuous as vol import voluptuous as vol
@ -18,7 +16,6 @@ from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT) CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pexpect==4.0.1'] REQUIREMENTS = ['pexpect==4.0.1']
@ -32,8 +29,6 @@ CONF_SSH_KEY = 'ssh_key'
DEFAULT_SSH_PORT = 22 DEFAULT_SSH_PORT = 22
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
SECRET_GROUP = 'Password or SSH Key' SECRET_GROUP = 'Password or SSH Key'
PLATFORM_SCHEMA = vol.All( PLATFORM_SCHEMA = vol.All(
@ -123,8 +118,6 @@ class AsusWrtDeviceScanner(DeviceScanner):
self.password, self.password,
self.mode == "ap") self.mode == "ap")
self.lock = threading.Lock()
self.last_results = {} self.last_results = {}
# Test the router is accessible. # Test the router is accessible.
@ -145,7 +138,6 @@ class AsusWrtDeviceScanner(DeviceScanner):
return client['host'] return client['host']
return None return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Ensure the information from the ASUSWRT router is up to date. """Ensure the information from the ASUSWRT router is up to date.
@ -154,19 +146,18 @@ class AsusWrtDeviceScanner(DeviceScanner):
if not self.success_init: if not self.success_init:
return False return False
with self.lock: _LOGGER.info('Checking Devices')
_LOGGER.info('Checking Devices') data = self.get_asuswrt_data()
data = self.get_asuswrt_data() if not data:
if not data: return False
return False
active_clients = [client for client in data.values() if active_clients = [client for client in data.values() if
client['status'] == 'REACHABLE' or client['status'] == 'REACHABLE' or
client['status'] == 'DELAY' or client['status'] == 'DELAY' or
client['status'] == 'STALE' or client['status'] == 'STALE' or
client['status'] == 'IN_ASSOCLIST'] client['status'] == 'IN_ASSOCLIST']
self.last_results = active_clients self.last_results = active_clients
return True return True
def get_asuswrt_data(self): def get_asuswrt_data(self):
"""Retrieve data from ASUSWRT and return parsed result.""" """Retrieve data from ASUSWRT and return parsed result."""

View File

@ -6,8 +6,6 @@ https://home-assistant.io/components/device_tracker.bt_home_hub_5/
""" """
import logging import logging
import re import re
import threading
from datetime import timedelta
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import json import json
from urllib.parse import unquote from urllib.parse import unquote
@ -19,13 +17,10 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})') _MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})')
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string vol.Required(CONF_HOST): cv.string
}) })
@ -46,11 +41,7 @@ class BTHomeHub5DeviceScanner(DeviceScanner):
"""Initialise the scanner.""" """Initialise the scanner."""
_LOGGER.info("Initialising BT Home Hub 5") _LOGGER.info("Initialising BT Home Hub 5")
self.host = config.get(CONF_HOST, '192.168.1.254') self.host = config.get(CONF_HOST, '192.168.1.254')
self.lock = threading.Lock()
self.last_results = {} self.last_results = {}
self.url = 'http://{}/nonAuth/home_status.xml'.format(self.host) self.url = 'http://{}/nonAuth/home_status.xml'.format(self.host)
# Test the router is accessible # Test the router is accessible
@ -65,17 +56,15 @@ class BTHomeHub5DeviceScanner(DeviceScanner):
def get_device_name(self, device): def get_device_name(self, device):
"""Return the name of the given device or None if we don't know.""" """Return the name of the given device or None if we don't know."""
with self.lock: # If not initialised and not already scanned and not found.
# If not initialised and not already scanned and not found. if device not in self.last_results:
if device not in self.last_results: self._update_info()
self._update_info()
if not self.last_results: if not self.last_results:
return None return None
return self.last_results.get(device) return self.last_results.get(device)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Ensure the information from the BT Home Hub 5 is up to date. """Ensure the information from the BT Home Hub 5 is up to date.
@ -84,18 +73,17 @@ class BTHomeHub5DeviceScanner(DeviceScanner):
if not self.success_init: if not self.success_init:
return False return False
with self.lock: _LOGGER.info("Scanning")
_LOGGER.info("Scanning")
data = _get_homehub_data(self.url) data = _get_homehub_data(self.url)
if not data: if not data:
_LOGGER.warning("Error scanning devices") _LOGGER.warning("Error scanning devices")
return False return False
self.last_results = data self.last_results = data
return True return True
def _get_homehub_data(url): def _get_homehub_data(url):

View File

@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.cisco_ios/ https://home-assistant.io/components/device_tracker.cisco_ios/
""" """
import logging import logging
from datetime import timedelta
import voluptuous as vol import voluptuous as vol
@ -14,9 +13,6 @@ from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, \ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, \
CONF_PORT CONF_PORT
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -65,7 +61,6 @@ class CiscoDeviceScanner(DeviceScanner):
return self.last_results return self.last_results
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
""" """
Ensure the information from the Cisco router is up to date. Ensure the information from the Cisco router is up to date.

View File

@ -6,8 +6,6 @@ https://home-assistant.io/components/device_tracker.ddwrt/
""" """
import logging import logging
import re import re
import threading
from datetime import timedelta
import requests import requests
import voluptuous as vol import voluptuous as vol
@ -16,9 +14,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -50,8 +45,6 @@ class DdWrtDeviceScanner(DeviceScanner):
self.username = config[CONF_USERNAME] self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD] self.password = config[CONF_PASSWORD]
self.lock = threading.Lock()
self.last_results = {} self.last_results = {}
self.mac2name = {} self.mac2name = {}
@ -69,68 +62,65 @@ class DdWrtDeviceScanner(DeviceScanner):
def get_device_name(self, device): def get_device_name(self, device):
"""Return the name of the given device or None if we don't know.""" """Return the name of the given device or None if we don't know."""
with self.lock: # If not initialised and not already scanned and not found.
# If not initialised and not already scanned and not found. if device not in self.mac2name:
if device not in self.mac2name: url = 'http://{}/Status_Lan.live.asp'.format(self.host)
url = 'http://{}/Status_Lan.live.asp'.format(self.host) data = self.get_ddwrt_data(url)
data = self.get_ddwrt_data(url)
if not data: if not data:
return None return None
dhcp_leases = data.get('dhcp_leases', None) dhcp_leases = data.get('dhcp_leases', None)
if not dhcp_leases: if not dhcp_leases:
return None return None
# Remove leading and trailing quotes and spaces # Remove leading and trailing quotes and spaces
cleaned_str = dhcp_leases.replace( cleaned_str = dhcp_leases.replace(
"\"", "").replace("\'", "").replace(" ", "") "\"", "").replace("\'", "").replace(" ", "")
elements = cleaned_str.split(',') elements = cleaned_str.split(',')
num_clients = int(len(elements) / 5) num_clients = int(len(elements) / 5)
self.mac2name = {} self.mac2name = {}
for idx in range(0, num_clients): for idx in range(0, num_clients):
# The data is a single array # The data is a single array
# every 5 elements represents one host, the MAC # every 5 elements represents one host, the MAC
# is the third element and the name is the first. # is the third element and the name is the first.
mac_index = (idx * 5) + 2 mac_index = (idx * 5) + 2
if mac_index < len(elements): if mac_index < len(elements):
mac = elements[mac_index] mac = elements[mac_index]
self.mac2name[mac] = elements[idx * 5] self.mac2name[mac] = elements[idx * 5]
return self.mac2name.get(device) return self.mac2name.get(device)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Ensure the information from the DD-WRT router is up to date. """Ensure the information from the DD-WRT router is up to date.
Return boolean if scanning successful. Return boolean if scanning successful.
""" """
with self.lock: _LOGGER.info("Checking ARP")
_LOGGER.info("Checking ARP")
url = 'http://{}/Status_Wireless.live.asp'.format(self.host) url = 'http://{}/Status_Wireless.live.asp'.format(self.host)
data = self.get_ddwrt_data(url) data = self.get_ddwrt_data(url)
if not data: if not data:
return False return False
self.last_results = [] self.last_results = []
active_clients = data.get('active_wireless', None) active_clients = data.get('active_wireless', None)
if not active_clients: if not active_clients:
return False return False
# The DD-WRT UI uses its own data format and then # The DD-WRT UI uses its own data format and then
# regex's out values so this is done here too # regex's out values so this is done here too
# Remove leading and trailing single quotes. # Remove leading and trailing single quotes.
clean_str = active_clients.strip().strip("'") clean_str = active_clients.strip().strip("'")
elements = clean_str.split("','") elements = clean_str.split("','")
self.last_results.extend(item for item in elements self.last_results.extend(item for item in elements
if _MAC_REGEX.match(item)) if _MAC_REGEX.match(item))
return True return True
def get_ddwrt_data(self, url): def get_ddwrt_data(self, url):
"""Retrieve data from DD-WRT and return parsed result.""" """Retrieve data from DD-WRT and return parsed result."""

View File

@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.fritz/ https://home-assistant.io/components/device_tracker.fritz/
""" """
import logging import logging
from datetime import timedelta
import voluptuous as vol import voluptuous as vol
@ -13,12 +12,9 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
REQUIREMENTS = ['fritzconnection==0.6.3'] REQUIREMENTS = ['fritzconnection==0.6.3']
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_DEFAULT_IP = '169.254.1.1' # This IP is valid for all FRITZ!Box routers. CONF_DEFAULT_IP = '169.254.1.1' # This IP is valid for all FRITZ!Box routers.
@ -88,7 +84,6 @@ class FritzBoxScanner(DeviceScanner):
return None return None
return ret return ret
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Retrieve latest information from the FRITZ!Box.""" """Retrieve latest information from the FRITZ!Box."""
if not self.success_init: if not self.success_init:

View File

@ -6,8 +6,6 @@ https://home-assistant.io/components/device_tracker.linksys_ap/
""" """
import base64 import base64
import logging import logging
import threading
from datetime import timedelta
import requests import requests
import voluptuous as vol import voluptuous as vol
@ -16,9 +14,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_VERIFY_SSL) CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_VERIFY_SSL)
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
INTERFACES = 2 INTERFACES = 2
DEFAULT_TIMEOUT = 10 DEFAULT_TIMEOUT = 10
@ -51,8 +47,6 @@ class LinksysAPDeviceScanner(object):
self.username = config[CONF_USERNAME] self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD] self.password = config[CONF_PASSWORD]
self.verify_ssl = config[CONF_VERIFY_SSL] self.verify_ssl = config[CONF_VERIFY_SSL]
self.lock = threading.Lock()
self.last_results = [] self.last_results = []
# Check if the access point is accessible # Check if the access point is accessible
@ -76,24 +70,22 @@ class LinksysAPDeviceScanner(object):
""" """
return None return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Check for connected devices.""" """Check for connected devices."""
from bs4 import BeautifulSoup as BS from bs4 import BeautifulSoup as BS
with self.lock: _LOGGER.info("Checking Linksys AP")
_LOGGER.info("Checking Linksys AP")
self.last_results = [] self.last_results = []
for interface in range(INTERFACES): for interface in range(INTERFACES):
request = self._make_request(interface) request = self._make_request(interface)
self.last_results.extend( self.last_results.extend(
[x.find_all('td')[1].text [x.find_all('td')[1].text
for x in BS(request.content, "html.parser") for x in BS(request.content, "html.parser")
.find_all(class_='section-row')] .find_all(class_='section-row')]
) )
return True return True
def _make_request(self, unit=0): def _make_request(self, unit=0):
# No, the '&&' is not a typo - this is expected by the web interface. # No, the '&&' is not a typo - this is expected by the web interface.

View File

@ -1,7 +1,5 @@
"""Support for Linksys Smart Wifi routers.""" """Support for Linksys Smart Wifi routers."""
import logging import logging
import threading
from datetime import timedelta
import requests import requests
import voluptuous as vol import voluptuous as vol
@ -10,9 +8,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
DEFAULT_TIMEOUT = 10 DEFAULT_TIMEOUT = 10
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -36,8 +32,6 @@ class LinksysSmartWifiDeviceScanner(DeviceScanner):
def __init__(self, config): def __init__(self, config):
"""Initialize the scanner.""" """Initialize the scanner."""
self.host = config[CONF_HOST] self.host = config[CONF_HOST]
self.lock = threading.Lock()
self.last_results = {} self.last_results = {}
# Check if the access point is accessible # Check if the access point is accessible
@ -55,48 +49,46 @@ class LinksysSmartWifiDeviceScanner(DeviceScanner):
"""Return the name (if known) of the device.""" """Return the name (if known) of the device."""
return self.last_results.get(mac) return self.last_results.get(mac)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Check for connected devices.""" """Check for connected devices."""
with self.lock: _LOGGER.info("Checking Linksys Smart Wifi")
_LOGGER.info("Checking Linksys Smart Wifi")
self.last_results = {} self.last_results = {}
response = self._make_request() response = self._make_request()
if response.status_code != 200: if response.status_code != 200:
_LOGGER.error( _LOGGER.error(
"Got HTTP status code %d when getting device list", "Got HTTP status code %d when getting device list",
response.status_code) response.status_code)
return False return False
try: try:
data = response.json() data = response.json()
result = data["responses"][0] result = data["responses"][0]
devices = result["output"]["devices"] devices = result["output"]["devices"]
for device in devices: for device in devices:
macs = device["knownMACAddresses"] macs = device["knownMACAddresses"]
if not macs: if not macs:
_LOGGER.warning( _LOGGER.warning(
"Skipping device without known MAC address") "Skipping device without known MAC address")
continue continue
mac = macs[-1] mac = macs[-1]
connections = device["connections"] connections = device["connections"]
if not connections: if not connections:
_LOGGER.debug("Device %s is not connected", mac) _LOGGER.debug("Device %s is not connected", mac)
continue continue
name = None name = None
for prop in device["properties"]: for prop in device["properties"]:
if prop["name"] == "userDeviceName": if prop["name"] == "userDeviceName":
name = prop["value"] name = prop["value"]
if not name: if not name:
name = device.get("friendlyName", device["deviceID"]) name = device.get("friendlyName", device["deviceID"])
_LOGGER.debug("Device %s is connected", mac) _LOGGER.debug("Device %s is connected", mac)
self.last_results[mac] = name self.last_results[mac] = name
except (KeyError, IndexError): except (KeyError, IndexError):
_LOGGER.exception("Router returned unexpected response") _LOGGER.exception("Router returned unexpected response")
return False return False
return True return True
def _make_request(self): def _make_request(self):
# Weirdly enough, this doesn't seem to require authentication # Weirdly enough, this doesn't seem to require authentication

View File

@ -7,8 +7,6 @@ https://home-assistant.io/components/device_tracker.luci/
import json import json
import logging import logging
import re import re
import threading
from datetime import timedelta
import requests import requests
import voluptuous as vol import voluptuous as vol
@ -18,9 +16,6 @@ from homeassistant.exceptions import HomeAssistantError
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -55,12 +50,8 @@ class LuciDeviceScanner(DeviceScanner):
self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);") self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);")
self.lock = threading.Lock()
self.last_results = {} self.last_results = {}
self.refresh_token() self.refresh_token()
self.mac2name = None self.mac2name = None
self.success_init = self.token is not None self.success_init = self.token is not None
@ -75,24 +66,22 @@ class LuciDeviceScanner(DeviceScanner):
def get_device_name(self, device): def get_device_name(self, device):
"""Return the name of the given device or None if we don't know.""" """Return the name of the given device or None if we don't know."""
with self.lock: if self.mac2name is None:
if self.mac2name is None: url = 'http://{}/cgi-bin/luci/rpc/uci'.format(self.host)
url = 'http://{}/cgi-bin/luci/rpc/uci'.format(self.host) result = _req_json_rpc(url, 'get_all', 'dhcp',
result = _req_json_rpc(url, 'get_all', 'dhcp', params={'auth': self.token})
params={'auth': self.token}) if result:
if result: hosts = [x for x in result.values()
hosts = [x for x in result.values() if x['.type'] == 'host' and
if x['.type'] == 'host' and 'mac' in x and 'name' in x]
'mac' in x and 'name' in x] mac2name_list = [
mac2name_list = [ (x['mac'].upper(), x['name']) for x in hosts]
(x['mac'].upper(), x['name']) for x in hosts] self.mac2name = dict(mac2name_list)
self.mac2name = dict(mac2name_list) else:
else: # Error, handled in the _req_json_rpc
# Error, handled in the _req_json_rpc return
return return self.mac2name.get(device.upper(), None)
return self.mac2name.get(device.upper(), None)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Ensure the information from the Luci router is up to date. """Ensure the information from the Luci router is up to date.
@ -101,31 +90,30 @@ class LuciDeviceScanner(DeviceScanner):
if not self.success_init: if not self.success_init:
return False return False
with self.lock: _LOGGER.info("Checking ARP")
_LOGGER.info("Checking ARP")
url = 'http://{}/cgi-bin/luci/rpc/sys'.format(self.host) url = 'http://{}/cgi-bin/luci/rpc/sys'.format(self.host)
try:
result = _req_json_rpc(url, 'net.arptable',
params={'auth': self.token})
except InvalidLuciTokenError:
_LOGGER.info("Refreshing token")
self.refresh_token()
return False
if result:
self.last_results = []
for device_entry in result:
# Check if the Flags for each device contain
# NUD_REACHABLE and if so, add it to last_results
if int(device_entry['Flags'], 16) & 0x2:
self.last_results.append(device_entry['HW address'])
return True
try:
result = _req_json_rpc(url, 'net.arptable',
params={'auth': self.token})
except InvalidLuciTokenError:
_LOGGER.info("Refreshing token")
self.refresh_token()
return False return False
if result:
self.last_results = []
for device_entry in result:
# Check if the Flags for each device contain
# NUD_REACHABLE and if so, add it to last_results
if int(device_entry['Flags'], 16) & 0x2:
self.last_results.append(device_entry['HW address'])
return True
return False
def _req_json_rpc(url, method, *args, **kwargs): def _req_json_rpc(url, method, *args, **kwargs):
"""Perform one JSON RPC operation.""" """Perform one JSON RPC operation."""

View File

@ -5,25 +5,17 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.mikrotik/ https://home-assistant.io/components/device_tracker.mikrotik/
""" """
import logging import logging
import threading
from datetime import timedelta
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import (CONF_HOST, from homeassistant.const import (
CONF_PASSWORD, CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
CONF_USERNAME,
CONF_PORT)
from homeassistant.util import Throttle
REQUIREMENTS = ['librouteros==1.0.2'] REQUIREMENTS = ['librouteros==1.0.2']
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MTK_DEFAULT_API_PORT = '8728' MTK_DEFAULT_API_PORT = '8728'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -54,12 +46,9 @@ class MikrotikScanner(DeviceScanner):
self.username = config[CONF_USERNAME] self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD] self.password = config[CONF_PASSWORD]
self.lock = threading.Lock()
self.connected = False self.connected = False
self.success_init = False self.success_init = False
self.client = None self.client = None
self.wireless_exist = None self.wireless_exist = None
self.success_init = self.connect_to_device() self.success_init = self.connect_to_device()
@ -118,51 +107,48 @@ class MikrotikScanner(DeviceScanner):
def get_device_name(self, mac): def get_device_name(self, mac):
"""Return the name of the given device or None if we don't know.""" """Return the name of the given device or None if we don't know."""
with self.lock: return self.last_results.get(mac)
return self.last_results.get(mac)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Retrieve latest information from the Mikrotik box.""" """Retrieve latest information from the Mikrotik box."""
with self.lock: if self.wireless_exist:
if self.wireless_exist: devices_tracker = 'wireless'
devices_tracker = 'wireless' else:
else: devices_tracker = 'ip'
devices_tracker = 'ip'
_LOGGER.info( _LOGGER.info(
"Loading %s devices from Mikrotik (%s) ...", "Loading %s devices from Mikrotik (%s) ...",
devices_tracker, devices_tracker,
self.host self.host
)
device_names = self.client(cmd='/ip/dhcp-server/lease/getall')
if self.wireless_exist:
devices = self.client(
cmd='/interface/wireless/registration-table/getall'
) )
else:
devices = device_names
device_names = self.client(cmd='/ip/dhcp-server/lease/getall') if device_names is None and devices is None:
if self.wireless_exist: return False
devices = self.client(
cmd='/interface/wireless/registration-table/getall'
)
else:
devices = device_names
if device_names is None and devices is None: mac_names = {device.get('mac-address'): device.get('host-name')
return False for device in device_names
if device.get('mac-address')}
mac_names = {device.get('mac-address'): device.get('host-name') if self.wireless_exist:
for device in device_names self.last_results = {
if device.get('mac-address')} device.get('mac-address'):
mac_names.get(device.get('mac-address'))
for device in devices
}
else:
self.last_results = {
device.get('mac-address'):
mac_names.get(device.get('mac-address'))
for device in device_names
if device.get('active-address')
}
if self.wireless_exist: return True
self.last_results = {
device.get('mac-address'):
mac_names.get(device.get('mac-address'))
for device in devices
}
else:
self.last_results = {
device.get('mac-address'):
mac_names.get(device.get('mac-address'))
for device in device_names
if device.get('active-address')
}
return True

View File

@ -5,8 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.netgear/ https://home-assistant.io/components/device_tracker.netgear/
""" """
import logging import logging
import threading
from datetime import timedelta
import voluptuous as vol import voluptuous as vol
@ -15,14 +13,11 @@ from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT) CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
from homeassistant.util import Throttle
REQUIREMENTS = ['pynetgear==0.3.3'] REQUIREMENTS = ['pynetgear==0.3.3']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
DEFAULT_HOST = 'routerlogin.net' DEFAULT_HOST = 'routerlogin.net'
DEFAULT_USER = 'admin' DEFAULT_USER = 'admin'
DEFAULT_PORT = 5000 DEFAULT_PORT = 5000
@ -56,8 +51,6 @@ class NetgearDeviceScanner(DeviceScanner):
import pynetgear import pynetgear
self.last_results = [] self.last_results = []
self.lock = threading.Lock()
self._api = pynetgear.Netgear(password, host, username, port) self._api = pynetgear.Netgear(password, host, username, port)
_LOGGER.info("Logging in") _LOGGER.info("Logging in")
@ -85,7 +78,6 @@ class NetgearDeviceScanner(DeviceScanner):
except StopIteration: except StopIteration:
return None return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Retrieve latest information from the Netgear router. """Retrieve latest information from the Netgear router.
@ -94,12 +86,11 @@ class NetgearDeviceScanner(DeviceScanner):
if not self.success_init: if not self.success_init:
return return
with self.lock: _LOGGER.info("Scanning")
_LOGGER.info("Scanning")
results = self._api.get_attached_devices() results = self._api.get_attached_devices()
if results is None: if results is None:
_LOGGER.warning("Error scanning devices") _LOGGER.warning("Error scanning devices")
self.last_results = results or [] self.last_results = results or []

View File

@ -4,11 +4,11 @@ Support for scanning a network with nmap.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.nmap_tracker/ https://home-assistant.io/components/device_tracker.nmap_tracker/
""" """
from datetime import timedelta
import logging import logging
import re import re
import subprocess import subprocess
from collections import namedtuple from collections import namedtuple
from datetime import timedelta
import voluptuous as vol import voluptuous as vol
@ -17,7 +17,6 @@ import homeassistant.util.dt as dt_util
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOSTS from homeassistant.const import CONF_HOSTS
from homeassistant.util import Throttle
REQUIREMENTS = ['python-nmap==0.6.1'] REQUIREMENTS = ['python-nmap==0.6.1']
@ -29,8 +28,6 @@ CONF_HOME_INTERVAL = 'home_interval'
CONF_OPTIONS = 'scan_options' CONF_OPTIONS = 'scan_options'
DEFAULT_OPTIONS = '-F --host-timeout 5s' DEFAULT_OPTIONS = '-F --host-timeout 5s'
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOSTS): cv.ensure_list, vol.Required(CONF_HOSTS): cv.ensure_list,
@ -97,7 +94,6 @@ class NmapDeviceScanner(DeviceScanner):
return filter_named[0] return filter_named[0]
return None return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Scan the network for devices. """Scan the network for devices.

View File

@ -6,8 +6,6 @@ https://home-assistant.io/components/device_tracker.sky_hub/
""" """
import logging import logging
import re import re
import threading
from datetime import timedelta
import requests import requests
import voluptuous as vol import voluptuous as vol
@ -16,13 +14,10 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})') _MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})')
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string vol.Required(CONF_HOST): cv.string
}) })
@ -43,11 +38,7 @@ class SkyHubDeviceScanner(DeviceScanner):
"""Initialise the scanner.""" """Initialise the scanner."""
_LOGGER.info("Initialising Sky Hub") _LOGGER.info("Initialising Sky Hub")
self.host = config.get(CONF_HOST, '192.168.1.254') self.host = config.get(CONF_HOST, '192.168.1.254')
self.lock = threading.Lock()
self.last_results = {} self.last_results = {}
self.url = 'http://{}/'.format(self.host) self.url = 'http://{}/'.format(self.host)
# Test the router is accessible # Test the router is accessible
@ -62,17 +53,15 @@ class SkyHubDeviceScanner(DeviceScanner):
def get_device_name(self, device): def get_device_name(self, device):
"""Return the name of the given device or None if we don't know.""" """Return the name of the given device or None if we don't know."""
with self.lock: # If not initialised and not already scanned and not found.
# If not initialised and not already scanned and not found. if device not in self.last_results:
if device not in self.last_results: self._update_info()
self._update_info()
if not self.last_results: if not self.last_results:
return None return None
return self.last_results.get(device) return self.last_results.get(device)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Ensure the information from the Sky Hub is up to date. """Ensure the information from the Sky Hub is up to date.
@ -81,18 +70,17 @@ class SkyHubDeviceScanner(DeviceScanner):
if not self.success_init: if not self.success_init:
return False return False
with self.lock: _LOGGER.info("Scanning")
_LOGGER.info("Scanning")
data = _get_skyhub_data(self.url) data = _get_skyhub_data(self.url)
if not data: if not data:
_LOGGER.warning('Error scanning devices') _LOGGER.warning('Error scanning devices')
return False return False
self.last_results = data self.last_results = data
return True return True
def _get_skyhub_data(url): def _get_skyhub_data(url):

View File

@ -6,8 +6,6 @@ https://home-assistant.io/components/device_tracker.snmp/
""" """
import binascii import binascii
import logging import logging
import threading
from datetime import timedelta
import voluptuous as vol import voluptuous as vol
@ -15,7 +13,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -28,8 +25,6 @@ CONF_BASEOID = 'baseoid'
DEFAULT_COMMUNITY = 'public' DEFAULT_COMMUNITY = 'public'
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string, vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_COMMUNITY, default=DEFAULT_COMMUNITY): cv.string, vol.Optional(CONF_COMMUNITY, default=DEFAULT_COMMUNITY): cv.string,
@ -68,9 +63,6 @@ class SnmpScanner(DeviceScanner):
privProtocol=cfg.usmAesCfb128Protocol privProtocol=cfg.usmAesCfb128Protocol
) )
self.baseoid = cmdgen.MibVariable(config[CONF_BASEOID]) self.baseoid = cmdgen.MibVariable(config[CONF_BASEOID])
self.lock = threading.Lock()
self.last_results = [] self.last_results = []
# Test the router is accessible # Test the router is accessible
@ -90,7 +82,6 @@ class SnmpScanner(DeviceScanner):
# We have no names # We have no names
return None return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Ensure the information from the device is up to date. """Ensure the information from the device is up to date.
@ -99,13 +90,12 @@ class SnmpScanner(DeviceScanner):
if not self.success_init: if not self.success_init:
return False return False
with self.lock: data = self.get_snmp_data()
data = self.get_snmp_data() if not data:
if not data: return False
return False
self.last_results = data self.last_results = data
return True return True
def get_snmp_data(self): def get_snmp_data(self):
"""Fetch MAC addresses from access point via SNMP.""" """Fetch MAC addresses from access point via SNMP."""

View File

@ -5,8 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.swisscom/ https://home-assistant.io/components/device_tracker.swisscom/
""" """
import logging import logging
import threading
from datetime import timedelta
import requests import requests
import voluptuous as vol import voluptuous as vol
@ -15,9 +13,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -41,9 +36,6 @@ class SwisscomDeviceScanner(DeviceScanner):
def __init__(self, config): def __init__(self, config):
"""Initialize the scanner.""" """Initialize the scanner."""
self.host = config[CONF_HOST] self.host = config[CONF_HOST]
self.lock = threading.Lock()
self.last_results = {} self.last_results = {}
# Test the router is accessible. # Test the router is accessible.
@ -64,7 +56,6 @@ class SwisscomDeviceScanner(DeviceScanner):
return client['host'] return client['host']
return None return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Ensure the information from the Swisscom router is up to date. """Ensure the information from the Swisscom router is up to date.
@ -73,16 +64,15 @@ class SwisscomDeviceScanner(DeviceScanner):
if not self.success_init: if not self.success_init:
return False return False
with self.lock: _LOGGER.info("Loading data from Swisscom Internet Box")
_LOGGER.info("Loading data from Swisscom Internet Box") data = self.get_swisscom_data()
data = self.get_swisscom_data() if not data:
if not data: return False
return False
active_clients = [client for client in data.values() if active_clients = [client for client in data.values() if
client['status']] client['status']]
self.last_results = active_clients self.last_results = active_clients
return True return True
def get_swisscom_data(self): def get_swisscom_data(self):
"""Retrieve data from Swisscom and return parsed result.""" """Retrieve data from Swisscom and return parsed result."""

View File

@ -7,8 +7,6 @@ https://home-assistant.io/components/device_tracker.thomson/
import logging import logging
import re import re
import telnetlib import telnetlib
import threading
from datetime import timedelta
import voluptuous as vol import voluptuous as vol
@ -16,9 +14,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -54,9 +49,6 @@ class ThomsonDeviceScanner(DeviceScanner):
self.host = config[CONF_HOST] self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME] self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD] self.password = config[CONF_PASSWORD]
self.lock = threading.Lock()
self.last_results = {} self.last_results = {}
# Test the router is accessible. # Test the router is accessible.
@ -77,7 +69,6 @@ class ThomsonDeviceScanner(DeviceScanner):
return client['host'] return client['host']
return None return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Ensure the information from the THOMSON router is up to date. """Ensure the information from the THOMSON router is up to date.
@ -86,17 +77,16 @@ class ThomsonDeviceScanner(DeviceScanner):
if not self.success_init: if not self.success_init:
return False return False
with self.lock: _LOGGER.info("Checking ARP")
_LOGGER.info("Checking ARP") data = self.get_thomson_data()
data = self.get_thomson_data() if not data:
if not data: return False
return False
# Flag C stands for CONNECTED # Flag C stands for CONNECTED
active_clients = [client for client in data.values() if active_clients = [client for client in data.values() if
client['status'].find('C') != -1] client['status'].find('C') != -1]
self.last_results = active_clients self.last_results = active_clients
return True return True
def get_thomson_data(self): def get_thomson_data(self):
"""Retrieve data from THOMSON and return parsed result.""" """Retrieve data from THOMSON and return parsed result."""

View File

@ -7,8 +7,6 @@ https://home-assistant.io/components/device_tracker.tomato/
import json import json
import logging import logging
import re import re
import threading
from datetime import timedelta
import requests import requests
import voluptuous as vol import voluptuous as vol
@ -17,9 +15,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
CONF_HTTP_ID = 'http_id' CONF_HTTP_ID = 'http_id'
@ -54,8 +49,6 @@ class TomatoDeviceScanner(DeviceScanner):
self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);") self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);")
self.logger = logging.getLogger("{}.{}".format(__name__, "Tomato")) self.logger = logging.getLogger("{}.{}".format(__name__, "Tomato"))
self.lock = threading.Lock()
self.last_results = {"wldev": [], "dhcpd_lease": []} self.last_results = {"wldev": [], "dhcpd_lease": []}
self.success_init = self._update_tomato_info() self.success_init = self._update_tomato_info()
@ -76,50 +69,48 @@ class TomatoDeviceScanner(DeviceScanner):
return filter_named[0] return filter_named[0]
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_tomato_info(self): def _update_tomato_info(self):
"""Ensure the information from the Tomato router is up to date. """Ensure the information from the Tomato router is up to date.
Return boolean if scanning successful. Return boolean if scanning successful.
""" """
with self.lock: self.logger.info("Scanning")
self.logger.info("Scanning")
try: try:
response = requests.Session().send(self.req, timeout=3) response = requests.Session().send(self.req, timeout=3)
# Calling and parsing the Tomato api here. We only need the # Calling and parsing the Tomato api here. We only need the
# wldev and dhcpd_lease values. # wldev and dhcpd_lease values.
if response.status_code == 200: if response.status_code == 200:
for param, value in \ for param, value in \
self.parse_api_pattern.findall(response.text): self.parse_api_pattern.findall(response.text):
if param == 'wldev' or param == 'dhcpd_lease': if param == 'wldev' or param == 'dhcpd_lease':
self.last_results[param] = \ self.last_results[param] = \
json.loads(value.replace("'", '"')) json.loads(value.replace("'", '"'))
return True return True
elif response.status_code == 401: elif response.status_code == 401:
# Authentication error # Authentication error
self.logger.exception(( self.logger.exception((
"Failed to authenticate, " "Failed to authenticate, "
"please check your username and password")) "please check your username and password"))
return False
except requests.exceptions.ConnectionError:
# We get this if we could not connect to the router or
# an invalid http_id was supplied.
self.logger.exception("Failed to connect to the router or "
"invalid http_id supplied")
return False return False
except requests.exceptions.Timeout: except requests.exceptions.ConnectionError:
# We get this if we could not connect to the router or # We get this if we could not connect to the router or
# an invalid http_id was supplied. # an invalid http_id was supplied.
self.logger.exception("Connection to the router timed out") self.logger.exception("Failed to connect to the router or "
return False "invalid http_id supplied")
return False
except ValueError: except requests.exceptions.Timeout:
# If JSON decoder could not parse the response. # We get this if we could not connect to the router or
self.logger.exception("Failed to parse response from router") # an invalid http_id was supplied.
return False self.logger.exception("Connection to the router timed out")
return False
except ValueError:
# If JSON decoder could not parse the response.
self.logger.exception("Failed to parse response from router")
return False

View File

@ -8,8 +8,7 @@ import base64
import hashlib import hashlib
import logging import logging
import re import re
import threading from datetime import datetime
from datetime import timedelta, datetime
import requests import requests
import voluptuous as vol import voluptuous as vol
@ -18,9 +17,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -59,7 +55,6 @@ class TplinkDeviceScanner(DeviceScanner):
self.password = password self.password = password
self.last_results = {} self.last_results = {}
self.lock = threading.Lock()
self.success_init = self._update_info() self.success_init = self._update_info()
def scan_devices(self): def scan_devices(self):
@ -72,28 +67,26 @@ class TplinkDeviceScanner(DeviceScanner):
"""Get firmware doesn't save the name of the wireless device.""" """Get firmware doesn't save the name of the wireless device."""
return None return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Ensure the information from the TP-Link router is up to date. """Ensure the information from the TP-Link router is up to date.
Return boolean if scanning successful. Return boolean if scanning successful.
""" """
with self.lock: _LOGGER.info("Loading wireless clients...")
_LOGGER.info("Loading wireless clients...")
url = 'http://{}/userRpm/WlanStationRpm.htm'.format(self.host) url = 'http://{}/userRpm/WlanStationRpm.htm'.format(self.host)
referer = 'http://{}'.format(self.host) referer = 'http://{}'.format(self.host)
page = requests.get( page = requests.get(
url, auth=(self.username, self.password), url, auth=(self.username, self.password),
headers={'referer': referer}, timeout=4) headers={'referer': referer}, timeout=4)
result = self.parse_macs.findall(page.text) result = self.parse_macs.findall(page.text)
if result: if result:
self.last_results = [mac.replace("-", ":") for mac in result] self.last_results = [mac.replace("-", ":") for mac in result]
return True return True
return False return False
class Tplink2DeviceScanner(TplinkDeviceScanner): class Tplink2DeviceScanner(TplinkDeviceScanner):
@ -109,48 +102,46 @@ class Tplink2DeviceScanner(TplinkDeviceScanner):
"""Get firmware doesn't save the name of the wireless device.""" """Get firmware doesn't save the name of the wireless device."""
return self.last_results.get(device) return self.last_results.get(device)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Ensure the information from the TP-Link router is up to date. """Ensure the information from the TP-Link router is up to date.
Return boolean if scanning successful. Return boolean if scanning successful.
""" """
with self.lock: _LOGGER.info("Loading wireless clients...")
_LOGGER.info("Loading wireless clients...")
url = 'http://{}/data/map_access_wireless_client_grid.json' \ url = 'http://{}/data/map_access_wireless_client_grid.json' \
.format(self.host) .format(self.host)
referer = 'http://{}'.format(self.host) referer = 'http://{}'.format(self.host)
# Router uses Authorization cookie instead of header # Router uses Authorization cookie instead of header
# Let's create the cookie # Let's create the cookie
username_password = '{}:{}'.format(self.username, self.password) username_password = '{}:{}'.format(self.username, self.password)
b64_encoded_username_password = base64.b64encode( b64_encoded_username_password = base64.b64encode(
username_password.encode('ascii') username_password.encode('ascii')
).decode('ascii') ).decode('ascii')
cookie = 'Authorization=Basic {}' \ cookie = 'Authorization=Basic {}' \
.format(b64_encoded_username_password) .format(b64_encoded_username_password)
response = requests.post( response = requests.post(
url, headers={'referer': referer, 'cookie': cookie}, url, headers={'referer': referer, 'cookie': cookie},
timeout=4) timeout=4)
try:
result = response.json().get('data')
except ValueError:
_LOGGER.error("Router didn't respond with JSON. "
"Check if credentials are correct.")
return False
if result:
self.last_results = {
device['mac_addr'].replace('-', ':'): device['name']
for device in result
}
return True
try:
result = response.json().get('data')
except ValueError:
_LOGGER.error("Router didn't respond with JSON. "
"Check if credentials are correct.")
return False return False
if result:
self.last_results = {
device['mac_addr'].replace('-', ':'): device['name']
for device in result
}
return True
return False
class Tplink3DeviceScanner(TplinkDeviceScanner): class Tplink3DeviceScanner(TplinkDeviceScanner):
"""This class queries the Archer C9 router with version 150811 or high.""" """This class queries the Archer C9 router with version 150811 or high."""
@ -202,70 +193,67 @@ class Tplink3DeviceScanner(TplinkDeviceScanner):
response.text) response.text)
return False return False
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Ensure the information from the TP-Link router is up to date. """Ensure the information from the TP-Link router is up to date.
Return boolean if scanning successful. Return boolean if scanning successful.
""" """
with self.lock: if (self.stok == '') or (self.sysauth == ''):
if (self.stok == '') or (self.sysauth == ''): self._get_auth_tokens()
self._get_auth_tokens()
_LOGGER.info("Loading wireless clients...") _LOGGER.info("Loading wireless clients...")
url = ('http://{}/cgi-bin/luci/;stok={}/admin/wireless?' url = ('http://{}/cgi-bin/luci/;stok={}/admin/wireless?'
'form=statistics').format(self.host, self.stok) 'form=statistics').format(self.host, self.stok)
referer = 'http://{}/webpages/index.html'.format(self.host) referer = 'http://{}/webpages/index.html'.format(self.host)
response = requests.post(url, response = requests.post(url,
params={'operation': 'load'}, params={'operation': 'load'},
headers={'referer': referer}, headers={'referer': referer},
cookies={'sysauth': self.sysauth}, cookies={'sysauth': self.sysauth},
timeout=5) timeout=5)
try: try:
json_response = response.json() json_response = response.json()
if json_response.get('success'): if json_response.get('success'):
result = response.json().get('data') result = response.json().get('data')
else: else:
if json_response.get('errorcode') == 'timeout': if json_response.get('errorcode') == 'timeout':
_LOGGER.info("Token timed out. Relogging on next scan") _LOGGER.info("Token timed out. Relogging on next scan")
self.stok = '' self.stok = ''
self.sysauth = '' self.sysauth = ''
return False
_LOGGER.error(
"An unknown error happened while fetching data")
return False return False
except ValueError: _LOGGER.error(
_LOGGER.error("Router didn't respond with JSON. " "An unknown error happened while fetching data")
"Check if credentials are correct")
return False return False
except ValueError:
if result: _LOGGER.error("Router didn't respond with JSON. "
self.last_results = { "Check if credentials are correct")
device['mac'].replace('-', ':'): device['mac']
for device in result
}
return True
return False return False
if result:
self.last_results = {
device['mac'].replace('-', ':'): device['mac']
for device in result
}
return True
return False
def _log_out(self): def _log_out(self):
with self.lock: _LOGGER.info("Logging out of router admin interface...")
_LOGGER.info("Logging out of router admin interface...")
url = ('http://{}/cgi-bin/luci/;stok={}/admin/system?' url = ('http://{}/cgi-bin/luci/;stok={}/admin/system?'
'form=logout').format(self.host, self.stok) 'form=logout').format(self.host, self.stok)
referer = 'http://{}/webpages/index.html'.format(self.host) referer = 'http://{}/webpages/index.html'.format(self.host)
requests.post(url, requests.post(url,
params={'operation': 'write'}, params={'operation': 'write'},
headers={'referer': referer}, headers={'referer': referer},
cookies={'sysauth': self.sysauth}) cookies={'sysauth': self.sysauth})
self.stok = '' self.stok = ''
self.sysauth = '' self.sysauth = ''
class Tplink4DeviceScanner(TplinkDeviceScanner): class Tplink4DeviceScanner(TplinkDeviceScanner):
@ -318,38 +306,36 @@ class Tplink4DeviceScanner(TplinkDeviceScanner):
_LOGGER.error("Couldn't fetch auth tokens") _LOGGER.error("Couldn't fetch auth tokens")
return False return False
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Ensure the information from the TP-Link router is up to date. """Ensure the information from the TP-Link router is up to date.
Return boolean if scanning successful. Return boolean if scanning successful.
""" """
with self.lock: if (self.credentials == '') or (self.token == ''):
if (self.credentials == '') or (self.token == ''): self._get_auth_tokens()
self._get_auth_tokens()
_LOGGER.info("Loading wireless clients...") _LOGGER.info("Loading wireless clients...")
mac_results = [] mac_results = []
# Check both the 2.4GHz and 5GHz client list URLs # Check both the 2.4GHz and 5GHz client list URLs
for clients_url in ('WlanStationRpm.htm', 'WlanStationRpm_5g.htm'): for clients_url in ('WlanStationRpm.htm', 'WlanStationRpm_5g.htm'):
url = 'http://{}/{}/userRpm/{}' \ url = 'http://{}/{}/userRpm/{}' \
.format(self.host, self.token, clients_url) .format(self.host, self.token, clients_url)
referer = 'http://{}'.format(self.host) referer = 'http://{}'.format(self.host)
cookie = 'Authorization=Basic {}'.format(self.credentials) cookie = 'Authorization=Basic {}'.format(self.credentials)
page = requests.get(url, headers={ page = requests.get(url, headers={
'cookie': cookie, 'cookie': cookie,
'referer': referer 'referer': referer
}) })
mac_results.extend(self.parse_macs.findall(page.text)) mac_results.extend(self.parse_macs.findall(page.text))
if not mac_results: if not mac_results:
return False return False
self.last_results = [mac.replace("-", ":") for mac in mac_results] self.last_results = [mac.replace("-", ":") for mac in mac_results]
return True return True
class Tplink5DeviceScanner(TplinkDeviceScanner): class Tplink5DeviceScanner(TplinkDeviceScanner):
@ -365,69 +351,67 @@ class Tplink5DeviceScanner(TplinkDeviceScanner):
"""Get firmware doesn't save the name of the wireless device.""" """Get firmware doesn't save the name of the wireless device."""
return None return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Ensure the information from the TP-Link AP is up to date. """Ensure the information from the TP-Link AP is up to date.
Return boolean if scanning successful. Return boolean if scanning successful.
""" """
with self.lock: _LOGGER.info("Loading wireless clients...")
_LOGGER.info("Loading wireless clients...")
base_url = 'http://{}'.format(self.host) base_url = 'http://{}'.format(self.host)
header = { header = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12;" "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12;"
" rv:53.0) Gecko/20100101 Firefox/53.0", " rv:53.0) Gecko/20100101 Firefox/53.0",
"Accept": "application/json, text/javascript, */*; q=0.01", "Accept": "application/json, text/javascript, */*; q=0.01",
"Accept-Language": "Accept-Language: en-US,en;q=0.5", "Accept-Language": "Accept-Language: en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate", "Accept-Encoding": "gzip, deflate",
"Content-Type": "application/x-www-form-urlencoded; " "Content-Type": "application/x-www-form-urlencoded; "
"charset=UTF-8", "charset=UTF-8",
"X-Requested-With": "XMLHttpRequest", "X-Requested-With": "XMLHttpRequest",
"Referer": "http://" + self.host + "/", "Referer": "http://" + self.host + "/",
"Connection": "keep-alive", "Connection": "keep-alive",
"Pragma": "no-cache", "Pragma": "no-cache",
"Cache-Control": "no-cache" "Cache-Control": "no-cache"
} }
password_md5 = hashlib.md5( password_md5 = hashlib.md5(
self.password.encode('utf')).hexdigest().upper() self.password.encode('utf')).hexdigest().upper()
# create a session to handle cookie easier # create a session to handle cookie easier
session = requests.session() session = requests.session()
session.get(base_url, headers=header) session.get(base_url, headers=header)
login_data = {"username": self.username, "password": password_md5} login_data = {"username": self.username, "password": password_md5}
session.post(base_url, login_data, headers=header) session.post(base_url, login_data, headers=header)
# a timestamp is required to be sent as get parameter # a timestamp is required to be sent as get parameter
timestamp = int(datetime.now().timestamp() * 1e3) timestamp = int(datetime.now().timestamp() * 1e3)
client_list_url = '{}/data/monitor.client.client.json'.format( client_list_url = '{}/data/monitor.client.client.json'.format(
base_url) base_url)
get_params = { get_params = {
'operation': 'load', 'operation': 'load',
'_': timestamp '_': timestamp
} }
response = session.get(client_list_url,
headers=header,
params=get_params)
session.close()
try:
list_of_devices = response.json()
except ValueError:
_LOGGER.error("AP didn't respond with JSON. "
"Check if credentials are correct.")
return False
if list_of_devices:
self.last_results = {
device['MAC'].replace('-', ':'): device['DeviceName']
for device in list_of_devices['data']
}
return True
response = session.get(client_list_url,
headers=header,
params=get_params)
session.close()
try:
list_of_devices = response.json()
except ValueError:
_LOGGER.error("AP didn't respond with JSON. "
"Check if credentials are correct.")
return False return False
if list_of_devices:
self.last_results = {
device['MAC'].replace('-', ':'): device['DeviceName']
for device in list_of_devices['data']
}
return True
return False

View File

@ -7,8 +7,6 @@ https://home-assistant.io/components/device_tracker.ubus/
import json import json
import logging import logging
import re import re
import threading
from datetime import timedelta
import requests import requests
import voluptuous as vol import voluptuous as vol
@ -17,12 +15,8 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -70,7 +64,6 @@ class UbusDeviceScanner(DeviceScanner):
self.password = config[CONF_PASSWORD] self.password = config[CONF_PASSWORD]
self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);") self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);")
self.lock = threading.Lock()
self.last_results = {} self.last_results = {}
self.url = 'http://{}/ubus'.format(host) self.url = 'http://{}/ubus'.format(host)
@ -89,34 +82,32 @@ class UbusDeviceScanner(DeviceScanner):
@_refresh_on_acccess_denied @_refresh_on_acccess_denied
def get_device_name(self, device): def get_device_name(self, device):
"""Return the name of the given device or None if we don't know.""" """Return the name of the given device or None if we don't know."""
with self.lock: if self.leasefile is None:
if self.leasefile is None: result = _req_json_rpc(
result = _req_json_rpc( self.url, self.session_id, 'call', 'uci', 'get',
self.url, self.session_id, 'call', 'uci', 'get', config="dhcp", type="dnsmasq")
config="dhcp", type="dnsmasq") if result:
if result: values = result["values"].values()
values = result["values"].values() self.leasefile = next(iter(values))["leasefile"]
self.leasefile = next(iter(values))["leasefile"] else:
else: return
return
if self.mac2name is None: if self.mac2name is None:
result = _req_json_rpc( result = _req_json_rpc(
self.url, self.session_id, 'call', 'file', 'read', self.url, self.session_id, 'call', 'file', 'read',
path=self.leasefile) path=self.leasefile)
if result: if result:
self.mac2name = dict() self.mac2name = dict()
for line in result["data"].splitlines(): for line in result["data"].splitlines():
hosts = line.split(" ") hosts = line.split(" ")
self.mac2name[hosts[1].upper()] = hosts[3] self.mac2name[hosts[1].upper()] = hosts[3]
else: else:
# Error, handled in the _req_json_rpc # Error, handled in the _req_json_rpc
return return
return self.mac2name.get(device.upper(), None) return self.mac2name.get(device.upper(), None)
@_refresh_on_acccess_denied @_refresh_on_acccess_denied
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Ensure the information from the Luci router is up to date. """Ensure the information from the Luci router is up to date.
@ -125,25 +116,24 @@ class UbusDeviceScanner(DeviceScanner):
if not self.success_init: if not self.success_init:
return False return False
with self.lock: _LOGGER.info("Checking ARP")
_LOGGER.info("Checking ARP")
if not self.hostapd: if not self.hostapd:
hostapd = _req_json_rpc( hostapd = _req_json_rpc(
self.url, self.session_id, 'list', 'hostapd.*', '') self.url, self.session_id, 'list', 'hostapd.*', '')
self.hostapd.extend(hostapd.keys()) self.hostapd.extend(hostapd.keys())
self.last_results = [] self.last_results = []
results = 0 results = 0
for hostapd in self.hostapd: for hostapd in self.hostapd:
result = _req_json_rpc( result = _req_json_rpc(
self.url, self.session_id, 'call', hostapd, 'get_clients') self.url, self.session_id, 'call', hostapd, 'get_clients')
if result: if result:
results = results + 1 results = results + 1
self.last_results.extend(result['clients'].keys()) self.last_results.extend(result['clients'].keys())
return bool(results) return bool(results)
def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params): def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params):

View File

@ -9,8 +9,7 @@ import logging
from homeassistant.util import slugify from homeassistant.util import slugify
from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.dispatcher import (
dispatcher_connect, dispatcher_send) dispatcher_connect, dispatcher_send)
from homeassistant.components.volvooncall import ( from homeassistant.components.volvooncall import DATA_KEY, SIGNAL_VEHICLE_SEEN
DATA_KEY, SIGNAL_VEHICLE_SEEN)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -5,8 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.xiaomi/ https://home-assistant.io/components/device_tracker.xiaomi/
""" """
import logging import logging
import threading
from datetime import timedelta
import requests import requests
import voluptuous as vol import voluptuous as vol
@ -15,12 +13,9 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import ( from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner) DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string, vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME, default='admin'): cv.string, vol.Required(CONF_USERNAME, default='admin'): cv.string,
@ -47,8 +42,6 @@ class XiaomiDeviceScanner(DeviceScanner):
self.username = config[CONF_USERNAME] self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD] self.password = config[CONF_PASSWORD]
self.lock = threading.Lock()
self.last_results = {} self.last_results = {}
self.token = _get_token(self.host, self.username, self.password) self.token = _get_token(self.host, self.username, self.password)
@ -62,21 +55,19 @@ class XiaomiDeviceScanner(DeviceScanner):
def get_device_name(self, device): def get_device_name(self, device):
"""Return the name of the given device or None if we don't know.""" """Return the name of the given device or None if we don't know."""
with self.lock: if self.mac2name is None:
if self.mac2name is None: result = self._retrieve_list_with_retry()
result = self._retrieve_list_with_retry() if result:
if result: hosts = [x for x in result
hosts = [x for x in result if 'mac' in x and 'name' in x]
if 'mac' in x and 'name' in x] mac2name_list = [
mac2name_list = [ (x['mac'].upper(), x['name']) for x in hosts]
(x['mac'].upper(), x['name']) for x in hosts] self.mac2name = dict(mac2name_list)
self.mac2name = dict(mac2name_list) else:
else: # Error, handled in the _retrieve_list_with_retry
# Error, handled in the _retrieve_list_with_retry return
return return self.mac2name.get(device.upper(), None)
return self.mac2name.get(device.upper(), None)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
"""Ensure the informations from the router are up to date. """Ensure the informations from the router are up to date.
@ -85,12 +76,11 @@ class XiaomiDeviceScanner(DeviceScanner):
if not self.success_init: if not self.success_init:
return False return False
with self.lock: result = self._retrieve_list_with_retry()
result = self._retrieve_list_with_retry() if result:
if result: self._store_result(result)
self._store_result(result) return True
return True return False
return False
def _retrieve_list_with_retry(self): def _retrieve_list_with_retry(self):
"""Retrieve the device list with a retry if token is invalid. """Retrieve the device list with a retry if token is invalid.