Skip to content

Commit 5a7a84f

Browse files
lukas-hetzeneckerballoob
authored andcommitted
Device Tracker for Linksys Access Points (home-assistant#4933)
* Implementation for Linksys Access Points * update .coveragerc * update requirements * add default timeout of 10sec * address lint issues
1 parent 86dfa7a commit 5a7a84f

File tree

3 files changed

+110
-0
lines changed

3 files changed

+110
-0
lines changed

.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ omit =
167167
homeassistant/components/device_tracker/fritz.py
168168
homeassistant/components/device_tracker/gpslogger.py
169169
homeassistant/components/device_tracker/icloud.py
170+
homeassistant/components/device_tracker/linksys_ap.py
170171
homeassistant/components/device_tracker/luci.py
171172
homeassistant/components/device_tracker/netgear.py
172173
homeassistant/components/device_tracker/nmap_tracker.py
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
"""
2+
Support for Linksys Access Points.
3+
4+
For more details about this platform, please refer to the documentation at
5+
https://home-assistant.io/components/device_tracker.linksys_ap/
6+
"""
7+
import base64
8+
import logging
9+
import threading
10+
from datetime import timedelta
11+
12+
import requests
13+
import voluptuous as vol
14+
15+
import homeassistant.helpers.config_validation as cv
16+
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
17+
from homeassistant.const import (CONF_HOST, CONF_PASSWORD, CONF_USERNAME,
18+
CONF_VERIFY_SSL)
19+
from homeassistant.util import Throttle
20+
21+
# Return cached results if last scan was less then this time ago.
22+
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
23+
INTERFACES = 2
24+
DEFAULT_TIMEOUT = 10
25+
26+
REQUIREMENTS = ['beautifulsoup4==4.5.3']
27+
28+
_LOGGER = logging.getLogger(__name__)
29+
30+
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
31+
vol.Required(CONF_HOST): cv.string,
32+
vol.Required(CONF_PASSWORD): cv.string,
33+
vol.Required(CONF_USERNAME): cv.string,
34+
vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
35+
})
36+
37+
38+
def get_scanner(hass, config):
39+
"""Validate the configuration and return a Linksys AP scanner."""
40+
try:
41+
return LinksysAPDeviceScanner(config[DOMAIN])
42+
except ConnectionError:
43+
return None
44+
45+
46+
class LinksysAPDeviceScanner(object):
47+
"""This class queries a Linksys Access Point."""
48+
49+
def __init__(self, config):
50+
"""Initialize the scanner."""
51+
self.host = config[CONF_HOST]
52+
self.username = config[CONF_USERNAME]
53+
self.password = config[CONF_PASSWORD]
54+
self.verify_ssl = config[CONF_VERIFY_SSL]
55+
56+
self.lock = threading.Lock()
57+
self.last_results = []
58+
59+
# Check if the access point is accessible
60+
response = self._make_request()
61+
if not response.status_code == 200:
62+
raise ConnectionError('Cannot connect to Linksys Access Point')
63+
64+
def scan_devices(self):
65+
"""Scan for new devices and return a list with found device IDs."""
66+
self._update_info()
67+
68+
return self.last_results
69+
70+
# pylint: disable=no-self-use
71+
def get_device_name(self, mac):
72+
"""
73+
Return the name (if known) of the device.
74+
75+
Linksys does not provide an API to get a name for a device,
76+
so we just return None
77+
"""
78+
return None
79+
80+
@Throttle(MIN_TIME_BETWEEN_SCANS)
81+
def _update_info(self):
82+
"""Check for connected devices."""
83+
from bs4 import BeautifulSoup as BS
84+
85+
with self.lock:
86+
_LOGGER.info('Checking Linksys AP')
87+
88+
self.last_results = []
89+
for interface in range(INTERFACES):
90+
request = self._make_request(interface)
91+
self.last_results.extend(
92+
[x.find_all('td')[1].text
93+
for x in BS(request.content, "html.parser")
94+
.find_all(class_='section-row')]
95+
)
96+
97+
return True
98+
99+
def _make_request(self, unit=0):
100+
# No, the '&&' is not a typo - this is expected by the web interface.
101+
login = base64.b64encode(bytes(self.username, 'utf8')).decode('ascii')
102+
pwd = base64.b64encode(bytes(self.password, 'utf8')).decode('ascii')
103+
return requests.get(
104+
'https://%s/StatusClients.htm&&unit=%s&vap=0' % (self.host, unit),
105+
timeout=DEFAULT_TIMEOUT,
106+
verify=self.verify_ssl,
107+
cookies={'LoginName': login,
108+
'LoginPWD': pwd})

requirements_all.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ astral==1.3.3
5959
# homeassistant.components.sensor.linux_battery
6060
batinfo==0.4.2
6161

62+
# homeassistant.components.device_tracker.linksys_ap
6263
# homeassistant.components.sensor.hydroquebec
6364
# homeassistant.components.sensor.scrape
6465
beautifulsoup4==4.5.3

0 commit comments

Comments
 (0)