core/homeassistant/components/device_tracker/upc_connect.py

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