diff --git a/homeassistant/components/device_tracker/ee_brightbox.py b/homeassistant/components/device_tracker/ee_brightbox.py new file mode 100644 index 00000000000..fc23abda1db --- /dev/null +++ b/homeassistant/components/device_tracker/ee_brightbox.py @@ -0,0 +1,107 @@ +""" +Support for EE Brightbox router. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/device_tracker.ee_brightbox/ +""" +import logging + +import voluptuous as vol + +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['eebrightbox==0.0.4'] + +_LOGGER = logging.getLogger(__name__) + +CONF_VERSION = 'version' + +CONF_DEFAULT_IP = '192.168.1.1' +CONF_DEFAULT_USERNAME = 'admin' +CONF_DEFAULT_VERSION = 2 + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_VERSION, default=CONF_DEFAULT_VERSION): cv.positive_int, + vol.Required(CONF_HOST, default=CONF_DEFAULT_IP): cv.string, + vol.Required(CONF_USERNAME, default=CONF_DEFAULT_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, +}) + + +def get_scanner(hass, config): + """Return a router scanner instance.""" + scanner = EEBrightBoxScanner(config[DOMAIN]) + + return scanner if scanner.check_config() else None + + +class EEBrightBoxScanner(DeviceScanner): + """Scan EE Brightbox router.""" + + def __init__(self, config): + """Initialise the scanner.""" + self.config = config + self.devices = {} + + def check_config(self): + """Check if provided configuration and credentials are correct.""" + from eebrightbox import EEBrightBox, EEBrightBoxException + + try: + with EEBrightBox(self.config) as ee_brightbox: + return bool(ee_brightbox.get_devices()) + except EEBrightBoxException: + _LOGGER.exception("Failed to connect to the router") + return False + + def scan_devices(self): + """Scan for devices.""" + from eebrightbox import EEBrightBox + + with EEBrightBox(self.config) as ee_brightbox: + self.devices = {d['mac']: d for d in ee_brightbox.get_devices()} + + macs = [d['mac'] for d in self.devices.values() if d['activity_ip']] + + _LOGGER.debug('Scan devices %s', macs) + + return macs + + def get_device_name(self, device): + """Get the name of a device from hostname.""" + if device in self.devices: + return self.devices[device]['hostname'] or None + + return None + + def get_extra_attributes(self, device): + """ + Get the extra attributes of a device. + + Extra attributes include: + - ip + - mac + - port - ethX or wifiX + - last_active + """ + port_map = { + 'wl1': 'wifi5Ghz', + 'wl0': 'wifi2.4Ghz', + 'eth0': 'eth0', + 'eth1': 'eth1', + 'eth2': 'eth2', + 'eth3': 'eth3', + } + + if device in self.devices: + return { + 'ip': self.devices[device]['ip'], + 'mac': self.devices[device]['mac'], + 'port': port_map[self.devices[device]['port']], + 'last_active': self.devices[device]['time_last_active'], + } + + return {} diff --git a/requirements_all.txt b/requirements_all.txt index e2a0a70a88c..a693defc6ee 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -342,6 +342,9 @@ dweepy==0.3.0 # homeassistant.components.edp_redy edp_redy==0.0.3 +# homeassistant.components.device_tracker.ee_brightbox +eebrightbox==0.0.4 + # homeassistant.components.media_player.horizon einder==0.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8b24a7b2466..082cd3a195f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -61,6 +61,9 @@ defusedxml==0.5.0 # homeassistant.components.sensor.dsmr dsmr_parser==0.12 +# homeassistant.components.device_tracker.ee_brightbox +eebrightbox==0.0.4 + # homeassistant.components.emulated_roku emulated_roku==0.1.7 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index e351c7b022b..67702635d47 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -46,6 +46,7 @@ TEST_REQUIREMENTS = ( 'coinmarketcap', 'defusedxml', 'dsmr_parser', + 'eebrightbox', 'emulated_roku', 'enturclient', 'ephem', diff --git a/tests/components/device_tracker/test_ee_brightbox.py b/tests/components/device_tracker/test_ee_brightbox.py new file mode 100644 index 00000000000..75609571e6c --- /dev/null +++ b/tests/components/device_tracker/test_ee_brightbox.py @@ -0,0 +1,122 @@ +"""Tests for the EE BrightBox device scanner.""" +from datetime import datetime + +from asynctest import patch +import pytest + +from homeassistant.components.device_tracker import DOMAIN +from homeassistant.const import ( + CONF_PASSWORD, CONF_PLATFORM) +from homeassistant.setup import async_setup_component + + +def _configure_mock_get_devices(eebrightbox_mock): + eebrightbox_instance = eebrightbox_mock.return_value + eebrightbox_instance.__enter__.return_value = eebrightbox_instance + eebrightbox_instance.get_devices.return_value = [ + { + 'mac': 'AA:BB:CC:DD:EE:FF', + 'ip': '192.168.1.10', + 'hostname': 'hostnameAA', + 'activity_ip': True, + 'port': 'eth0', + 'time_last_active': datetime(2019, 1, 20, 16, 4, 0), + }, + { + 'mac': '11:22:33:44:55:66', + 'hostname': 'hostname11', + 'ip': '192.168.1.11', + 'activity_ip': True, + 'port': 'wl0', + 'time_last_active': datetime(2019, 1, 20, 11, 9, 0), + }, + { + 'mac': 'FF:FF:FF:FF:FF:FF', + 'hostname': 'hostnameFF', + 'ip': '192.168.1.12', + 'activity_ip': False, + 'port': 'wl1', + 'time_last_active': datetime(2019, 1, 15, 16, 9, 0), + } + ] + + +def _configure_mock_failed_config_check(eebrightbox_mock): + from eebrightbox import EEBrightBoxException + eebrightbox_instance = eebrightbox_mock.return_value + eebrightbox_instance.__enter__.side_effect = EEBrightBoxException( + "Failed to connect to the router") + + +@pytest.fixture(autouse=True) +def mock_dev_track(mock_device_tracker_conf): + """Mock device tracker config loading.""" + pass + + +@patch('eebrightbox.EEBrightBox') +async def test_missing_credentials(eebrightbox_mock, hass): + """Test missing credentials.""" + _configure_mock_get_devices(eebrightbox_mock) + + result = await async_setup_component(hass, DOMAIN, { + DOMAIN: { + CONF_PLATFORM: 'ee_brightbox', + } + }) + + assert result + + await hass.async_block_till_done() + + assert hass.states.get('device_tracker.hostnameaa') is None + assert hass.states.get('device_tracker.hostname11') is None + assert hass.states.get('device_tracker.hostnameff') is None + + +@patch('eebrightbox.EEBrightBox') +async def test_invalid_credentials(eebrightbox_mock, hass): + """Test invalid credentials.""" + _configure_mock_failed_config_check(eebrightbox_mock) + + result = await async_setup_component(hass, DOMAIN, { + DOMAIN: { + CONF_PLATFORM: 'ee_brightbox', + CONF_PASSWORD: 'test_password', + } + }) + + assert result + + await hass.async_block_till_done() + + assert hass.states.get('device_tracker.hostnameaa') is None + assert hass.states.get('device_tracker.hostname11') is None + assert hass.states.get('device_tracker.hostnameff') is None + + +@patch('eebrightbox.EEBrightBox') +async def test_get_devices(eebrightbox_mock, hass): + """Test valid configuration.""" + _configure_mock_get_devices(eebrightbox_mock) + + result = await async_setup_component(hass, DOMAIN, { + DOMAIN: { + CONF_PLATFORM: 'ee_brightbox', + CONF_PASSWORD: 'test_password', + } + }) + + assert result + + await hass.async_block_till_done() + + assert hass.states.get('device_tracker.hostnameaa') is not None + assert hass.states.get('device_tracker.hostname11') is not None + assert hass.states.get('device_tracker.hostnameff') is None + + state = hass.states.get('device_tracker.hostnameaa') + assert state.attributes['mac'] == 'AA:BB:CC:DD:EE:FF' + assert state.attributes['ip'] == '192.168.1.10' + assert state.attributes['port'] == 'eth0' + assert state.attributes['last_active'] == datetime(2019, 1, 20, 16, 4, 0)