130 lines
4.3 KiB
Python
130 lines
4.3 KiB
Python
"""
|
|
Support for UPC ConnectBox router.
|
|
|
|
For more details about this platform, please refer to the documentation at
|
|
https://home-assistant.io/components/device_tracker.upc_connect/
|
|
"""
|
|
import asyncio
|
|
import logging
|
|
|
|
import aiohttp
|
|
from aiohttp.hdrs import REFERER, USER_AGENT
|
|
import async_timeout
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components.device_tracker import (
|
|
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
|
|
from homeassistant.const import CONF_HOST, HTTP_HEADER_X_REQUESTED_WITH
|
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
|
import homeassistant.helpers.config_validation as cv
|
|
|
|
REQUIREMENTS = ['defusedxml==0.5.0']
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
CMD_DEVICES = 123
|
|
|
|
DEFAULT_IP = '192.168.0.1'
|
|
|
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|
vol.Optional(CONF_HOST, default=DEFAULT_IP): cv.string,
|
|
})
|
|
|
|
|
|
async def async_get_scanner(hass, config):
|
|
"""Return the UPC device scanner."""
|
|
scanner = UPCDeviceScanner(hass, config[DOMAIN])
|
|
success_init = await scanner.async_initialize_token()
|
|
|
|
return scanner if success_init else None
|
|
|
|
|
|
class UPCDeviceScanner(DeviceScanner):
|
|
"""This class queries a router running UPC ConnectBox firmware."""
|
|
|
|
def __init__(self, hass, config):
|
|
"""Initialize the scanner."""
|
|
self.hass = hass
|
|
self.host = config[CONF_HOST]
|
|
|
|
self.data = {}
|
|
self.token = None
|
|
|
|
self.headers = {
|
|
HTTP_HEADER_X_REQUESTED_WITH: 'XMLHttpRequest',
|
|
REFERER: "http://{}/index.html".format(self.host),
|
|
USER_AGENT: ("Mozilla/5.0 (Windows NT 10.0; WOW64) "
|
|
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
|
"Chrome/47.0.2526.106 Safari/537.36")
|
|
}
|
|
|
|
self.websession = async_get_clientsession(hass)
|
|
|
|
async def async_scan_devices(self):
|
|
"""Scan for new devices and return a list with found device IDs."""
|
|
import defusedxml.ElementTree as ET
|
|
|
|
if self.token is None:
|
|
token_initialized = await self.async_initialize_token()
|
|
if not token_initialized:
|
|
_LOGGER.error("Not connected to %s", self.host)
|
|
return []
|
|
|
|
raw = await self._async_ws_function(CMD_DEVICES)
|
|
|
|
try:
|
|
xml_root = ET.fromstring(raw)
|
|
return [mac.text for mac in xml_root.iter('MACAddr')]
|
|
except (ET.ParseError, TypeError):
|
|
_LOGGER.warning("Can't read device from %s", self.host)
|
|
self.token = None
|
|
return []
|
|
|
|
async def async_get_device_name(self, device):
|
|
"""Get the device name (the name of the wireless device not used)."""
|
|
return None
|
|
|
|
async def async_initialize_token(self):
|
|
"""Get first token."""
|
|
try:
|
|
# get first token
|
|
with async_timeout.timeout(10, loop=self.hass.loop):
|
|
response = await self.websession.get(
|
|
"http://{}/common_page/login.html".format(self.host),
|
|
headers=self.headers)
|
|
|
|
await response.text()
|
|
|
|
self.token = response.cookies['sessionToken'].value
|
|
|
|
return True
|
|
|
|
except (asyncio.TimeoutError, aiohttp.ClientError):
|
|
_LOGGER.error("Can not load login page from %s", self.host)
|
|
return False
|
|
|
|
async def _async_ws_function(self, function):
|
|
"""Execute a command on UPC firmware webservice."""
|
|
try:
|
|
with async_timeout.timeout(10, loop=self.hass.loop):
|
|
# The 'token' parameter has to be first, and 'fun' second
|
|
# or the UPC firmware will return an error
|
|
response = await self.websession.post(
|
|
"http://{}/xml/getter.xml".format(self.host),
|
|
data="token={}&fun={}".format(self.token, function),
|
|
headers=self.headers, allow_redirects=False)
|
|
|
|
# Error?
|
|
if response.status != 200:
|
|
_LOGGER.warning("Receive http code %d", response.status)
|
|
self.token = None
|
|
return
|
|
|
|
# Load data, store token for next request
|
|
self.token = response.cookies['sessionToken'].value
|
|
return await response.text()
|
|
|
|
except (asyncio.TimeoutError, aiohttp.ClientError):
|
|
_LOGGER.error("Error on %s", function)
|
|
self.token = None
|