diff --git a/.coveragerc b/.coveragerc index 2e5748d2a98..cdc148e5f5c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1427,6 +1427,8 @@ omit = homeassistant/components/ukraine_alarm/__init__.py homeassistant/components/ukraine_alarm/binary_sensor.py homeassistant/components/unifiled/* + homeassistant/components/unifi_direct/__init__.py + homeassistant/components/unifi_direct/device_tracker.py homeassistant/components/upb/__init__.py homeassistant/components/upb/light.py homeassistant/components/upc_connect/* diff --git a/CODEOWNERS b/CODEOWNERS index fda7f27c412..b3d5475379e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1389,6 +1389,7 @@ build.json @home-assistant/supervisor /tests/components/ukraine_alarm/ @PaulAnnekov /homeassistant/components/unifi/ @Kane610 /tests/components/unifi/ @Kane610 +/homeassistant/components/unifi_direct/ @tofuSCHNITZEL /homeassistant/components/unifiled/ @florisvdk /homeassistant/components/unifiprotect/ @AngellusMortis @bdraco /tests/components/unifiprotect/ @AngellusMortis @bdraco diff --git a/homeassistant/components/unifi_direct/device_tracker.py b/homeassistant/components/unifi_direct/device_tracker.py index 13ebd0e33e5..77ce5d80cf9 100644 --- a/homeassistant/components/unifi_direct/device_tracker.py +++ b/homeassistant/components/unifi_direct/device_tracker.py @@ -1,10 +1,10 @@ """Support for Unifi AP direct access.""" from __future__ import annotations -import json import logging +from typing import Any -from pexpect import exceptions, pxssh +from unifi_ap import UniFiAP, UniFiAPConnectionException, UniFiAPDataException import voluptuous as vol from homeassistant.components.device_tracker import ( @@ -20,9 +20,6 @@ from homeassistant.helpers.typing import ConfigType _LOGGER = logging.getLogger(__name__) DEFAULT_SSH_PORT = 22 -UNIFI_COMMAND = 'mca-dump | tr -d "\n"' -UNIFI_SSID_TABLE = "vap_table" -UNIFI_CLIENT_TABLE = "sta_table" PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend( { @@ -37,104 +34,43 @@ PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend( def get_scanner(hass: HomeAssistant, config: ConfigType) -> UnifiDeviceScanner | None: """Validate the configuration and return a Unifi direct scanner.""" scanner = UnifiDeviceScanner(config[DOMAIN]) - if not scanner.connected: - return None - return scanner + return scanner if scanner.update_clients() else None class UnifiDeviceScanner(DeviceScanner): """Class which queries Unifi wireless access point.""" - def __init__(self, config): + def __init__(self, config: ConfigType) -> None: """Initialize the scanner.""" - self.host = config[CONF_HOST] - self.username = config[CONF_USERNAME] - self.password = config[CONF_PASSWORD] - self.port = config[CONF_PORT] - self.ssh = None - self.connected = False - self.last_results = {} - self._connect() - - def scan_devices(self): - """Scan for new devices and return a list with found device IDs.""" - result = _response_to_json(self._get_update()) - if result: - self.last_results = result - return self.last_results.keys() - - def get_device_name(self, device): - """Return the name of the given device or None if we don't know.""" - hostname = next( - ( - value.get("hostname") - for key, value in self.last_results.items() - if key.upper() == device.upper() - ), - None, + self.clients: dict[str, dict[str, Any]] = {} + self.ap = UniFiAP( + target=config[CONF_HOST], + username=config[CONF_USERNAME], + password=config[CONF_PASSWORD], + port=config[CONF_PORT], ) - if hostname is not None: - hostname = str(hostname) - return hostname - def _connect(self): - """Connect to the Unifi AP SSH server.""" + def scan_devices(self) -> list[str]: + """Scan for new devices and return a list with found device IDs.""" + self.update_clients() + return list(self.clients) - self.ssh = pxssh.pxssh(options={"HostKeyAlgorithms": "ssh-rsa"}) + def get_device_name(self, device: str) -> str | None: + """Return the name of the given device or None if we don't know.""" + client_info = self.clients.get(device) + if client_info: + return client_info.get("hostname") + return None + + def update_clients(self) -> bool: + """Update the client info from AP.""" try: - self.ssh.login( - self.host, self.username, password=self.password, port=self.port - ) - self.connected = True - except exceptions.EOF: - _LOGGER.error("Connection refused. SSH enabled?") - self._disconnect() + self.clients = self.ap.get_clients() + except UniFiAPConnectionException: + _LOGGER.error("Failed to connect to accesspoint") + return False + except UniFiAPDataException: + _LOGGER.error("Failed to get proper response from accesspoint") + return False - def _disconnect(self): - """Disconnect the current SSH connection.""" - try: - self.ssh.logout() - except Exception: # pylint: disable=broad-except - pass - finally: - self.ssh = None - - self.connected = False - - def _get_update(self): - try: - if not self.connected: - self._connect() - # If we still aren't connected at this point - # don't try to send anything to the AP. - if not self.connected: - return None - self.ssh.sendline(UNIFI_COMMAND) - self.ssh.prompt() - return self.ssh.before - except pxssh.ExceptionPxssh as err: - _LOGGER.error("Unexpected SSH error: %s", str(err)) - self._disconnect() - return None - except (AssertionError, exceptions.EOF) as err: - _LOGGER.error("Connection to AP unavailable: %s", str(err)) - self._disconnect() - return None - - -def _response_to_json(response): - try: - json_response = json.loads(str(response)[31:-1].replace("\\", "")) - _LOGGER.debug(str(json_response)) - ssid_table = json_response.get(UNIFI_SSID_TABLE) - active_clients = {} - - for ssid in ssid_table: - client_table = ssid.get(UNIFI_CLIENT_TABLE) - for client in client_table: - active_clients[client.get("mac")] = client - - return active_clients - except (ValueError, TypeError): - _LOGGER.error("Failed to decode response from AP") - return {} + return True diff --git a/homeassistant/components/unifi_direct/manifest.json b/homeassistant/components/unifi_direct/manifest.json index 68a1396727f..8ca8ef27bb2 100644 --- a/homeassistant/components/unifi_direct/manifest.json +++ b/homeassistant/components/unifi_direct/manifest.json @@ -1,9 +1,9 @@ { "domain": "unifi_direct", "name": "UniFi AP", - "codeowners": [], + "codeowners": ["@tofuSCHNITZEL"], "documentation": "https://www.home-assistant.io/integrations/unifi_direct", "iot_class": "local_polling", - "loggers": ["pexpect", "ptyprocess"], - "requirements": ["pexpect==4.6.0"] + "loggers": ["unifi_ap"], + "requirements": ["unifi_ap==0.0.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index dce1f3980b4..851e9c2f6e1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1478,7 +1478,6 @@ pescea==1.0.12 # homeassistant.components.aruba # homeassistant.components.cisco_ios # homeassistant.components.pandora -# homeassistant.components.unifi_direct pexpect==4.6.0 # homeassistant.components.modem_callerid @@ -2698,6 +2697,9 @@ ultraheat-api==0.5.7 # homeassistant.components.unifiprotect unifi-discovery==1.1.7 +# homeassistant.components.unifi_direct +unifi_ap==0.0.1 + # homeassistant.components.unifiled unifiled==0.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a474821a2ac..a76da0a37d1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1142,12 +1142,6 @@ peco==0.0.29 # homeassistant.components.escea pescea==1.0.12 -# homeassistant.components.aruba -# homeassistant.components.cisco_ios -# homeassistant.components.pandora -# homeassistant.components.unifi_direct -pexpect==4.6.0 - # homeassistant.components.modem_callerid phone-modem==0.1.1 diff --git a/tests/components/unifi_direct/__init__.py b/tests/components/unifi_direct/__init__.py deleted file mode 100644 index 7f8d0fa29f7..00000000000 --- a/tests/components/unifi_direct/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the unifi_direct component.""" diff --git a/tests/components/unifi_direct/fixtures/data.txt b/tests/components/unifi_direct/fixtures/data.txt deleted file mode 100644 index fcb58070fcc..00000000000 --- a/tests/components/unifi_direct/fixtures/data.txt +++ /dev/null @@ -1 +0,0 @@ -b'mca-dump | tr -d "\r\n> "\r\n{ "board_rev": 16, "bootrom_version": "unifi-v1.6.7.249-gb74e0282", "cfgversion": "63b505a1c328fd9c", "country_code": 840, "default": false, "discovery_response": true, "fw_caps": 855, "guest_token": "E6BAE04FD72C", "has_eth1": false, "has_speaker": false, "hostname": "UBNT", "if_table": [ { "full_duplex": true, "ip": "0.0.0.0", "mac": "80:2a:a8:56:34:12", "name": "eth0", "netmask": "0.0.0.0", "num_port": 1, "rx_bytes": 3879332085, "rx_dropped": 0, "rx_errors": 0, "rx_multicast": 0, "rx_packets": 4093520, "speed": 1000, "tx_bytes": 1745140940, "tx_dropped": 0, "tx_errors": 0, "tx_packets": 3105586, "up": true } ], "inform_url": "?", "ip": "192.168.1.2", "isolated": false, "last_error": "", "locating": false, "mac": "80:2a:a8:56:34:12", "model": "U7LR", "model_display": "UAP-AC-LR", "netmask": "255.255.255.0", "port_table": [ { "media": "GE", "poe_caps": 0, "port_idx": 0, "port_poe": false } ], "radio_table": [ { "athstats": { "ast_ath_reset": 0, "ast_be_xmit": 1098121, "ast_cst": 225, "ast_deadqueue_reset": 0, "ast_fullqueue_stop": 0, "ast_txto": 151, "cu_self_rx": 8, "cu_self_tx": 4, "cu_total": 12, "n_rx_aggr": 3915695, "n_rx_pkts": 6518082, "n_tx_bawadv": 1205430, "n_tx_bawretries": 70257, "n_tx_pkts": 1813368, "n_tx_queue": 1024366, "n_tx_retries": 70273, "n_tx_xretries": 897, "n_txaggr_compgood": 616173, "n_txaggr_compretries": 71170, "n_txaggr_compxretry": 0, "n_txaggr_prepends": 21240, "name": "wifi0" }, "builtin_ant_gain": 0, "builtin_antenna": true, "max_txpower": 24, "min_txpower": 6, "name": "wifi0", "nss": 3, "radio": "ng", "scan_table": [ { "age": 2, "bssid": "28:56:5a:34:23:12", "bw": 20, "center_freq": 2462, "channel": 11, "essid": "someones_wifi", "freq": 2462, "is_adhoc": false, "is_ubnt": false, "rssi": 8, "rssi_age": 2, "security": "secured" }, { "age": 37, "bssid": "00:60:0f:45:34:12", "bw": 20, "center_freq": 2462, "channel": 11, "essid": "", "freq": 2462, "is_adhoc": false, "is_ubnt": false, "rssi": 10, "rssi_age": 37, "security": "secured" }, { "age": 29, "bssid": "b0:93:5b:7a:35:23", "bw": 20, "center_freq": 2462, "channel": 11, "essid": "ARRIS-CB55", "freq": 2462, "is_adhoc": false, "is_ubnt": false, "rssi": 10, "rssi_age": 29, "security": "secured" }, { "age": 0, "bssid": "e0:46:9a:e1:ea:7d", "bw": 20, "center_freq": 2462, "channel": 11, "essid": "Darjeeling", "freq": 2462, "is_adhoc": false, "is_ubnt": false, "rssi": 9, "rssi_age": 0, "security": "secured" }, { "age": 1, "bssid": "00:60:0f:e1:ea:7e", "bw": 20, "center_freq": 2462, "channel": 11, "essid": "", "freq": 2462, "is_adhoc": false, "is_ubnt": false, "rssi": 10, "rssi_age": 1, "security": "secured" }, { "age": 0, "bssid": "7c:d1:c3:cd:e5:f4", "bw": 20, "center_freq": 2462, "channel": 11, "essid": "Chris\'s Wi-Fi Network", "freq": 2462, "is_adhoc": false, "is_ubnt": false, "rssi": 17, "rssi_age": 0, "security": "secured" } ] }, { "athstats": { "ast_ath_reset": 14, "ast_be_xmit": 1097310, "ast_cst": 0, "ast_deadqueue_reset": 41, "ast_fullqueue_stop": 0, "ast_txto": 0, "cu_self_rx": 0, "cu_self_tx": 0, "cu_total": 0, "n_rx_aggr": 106804, "n_rx_pkts": 2453041, "n_tx_bawadv": 557298, "n_tx_bawretries": 0, "n_tx_pkts": 1080, "n_tx_queue": 0, "n_tx_retries": 1, "n_tx_xretries": 44046, "n_txaggr_compgood": 0, "n_txaggr_compretries": 0, "n_txaggr_compxretry": 0, "n_txaggr_prepends": 0, "name": "wifi1" }, "builtin_ant_gain": 0, "builtin_antenna": true, "has_dfs": true, "has_fccdfs": true, "is_11ac": true, "max_txpower": 22, "min_txpower": 4, "name": "wifi1", "nss": 2, "radio": "na", "scan_table": [] } ], "required_version": "3.4.1", "selfrun_beacon": false, "serial": "802AA896363C", "spectrum_scanning": false, "ssh_session_table": [], "state": 0, "stream_token": "", "sys_stats": { "loadavg_1": "0.03", "loadavg_15": "0.06", "loadavg_5": "0.06", "mem_buffer": 0, "mem_total": 129310720, "mem_used": 75800576 }, "system-stats": { "cpu": "8.4", "mem": "58.6", "uptime": "112391" }, "time": 1508795154, "uplink": "eth0", "uptime": 112391, "vap_table": [ { "bssid": "80:2a:a8:97:36:3c", "ccq": 914, "channel": 11, "essid": "220", "id": "55b19c7e50e4e11e798e84c7", "name": "ath0", "num_sta": 20, "radio": "ng", "rx_bytes": 1155345354, "rx_crypts": 5491, "rx_dropped": 5540, "rx_errors": 5540, "rx_frags": 0, "rx_nwids": 647001, "rx_packets": 1840967, "sta_table": [ { "auth_time": 4294967206, "authorized": true, "ccq": 991, "dhcpend_time": 660, "dhcpstart_time": 660, "hostname": "amazon-device", "idletime": 0, "ip": "192.168.1.45", "is_11n": true, "mac": "44:65:0d:12:34:56", "noise": -114, "rssi": 59, "rx_bytes": 1176121, "rx_mcast": 0, "rx_packets": 20927, "rx_rate": 24000, "rx_retries": 0, "signal": -55, "state": 15, "state_ht": true, "state_pwrmgt": false, "tx_bytes": 364495, "tx_packets": 2183, "tx_power": 48, "tx_rate": 72222, "tx_retries": 589, "uptime": 7031, "vlan_id": 0 }, { "auth_time": 4294967266, "authorized": true, "ccq": 991, "dhcpend_time": 290, "dhcpstart_time": 290, "hostname": "iPhone", "idletime": 9, "ip": "192.168.1.209", "is_11n": true, "mac": "98:00:c6:56:34:12", "noise": -114, "rssi": 40, "rx_bytes": 5862172, "rx_mcast": 0, "rx_packets": 30977, "rx_rate": 24000, "rx_retries": 0, "signal": -74, "state": 31, "state_ht": true, "state_pwrmgt": true, "tx_bytes": 31707361, "tx_packets": 27775, "tx_power": 48, "tx_rate": 140637, "tx_retries": 1213, "uptime": 15556, "vlan_id": 0 }, { "auth_time": 4294967276, "authorized": true, "ccq": 991, "dhcpend_time": 630, "dhcpstart_time": 630, "hostname": "android", "idletime": 0, "ip": "192.168.1.10", "is_11n": true, "mac": "b4:79:a7:45:34:12", "noise": -114, "rssi": 60, "rx_bytes": 13694423, "rx_mcast": 0, "rx_packets": 110909, "rx_rate": 1000, "rx_retries": 0, "signal": -54, "state": 15, "state_ht": true, "state_pwrmgt": false, "tx_bytes": 7988429, "tx_packets": 28863, "tx_power": 48, "tx_rate": 72222, "tx_retries": 1254, "uptime": 19052, "vlan_id": 0 }, { "auth_time": 4294967176, "authorized": true, "ccq": 991, "dhcpend_time": 4480, "dhcpstart_time": 4480, "hostname": "wink", "idletime": 0, "ip": "192.168.1.3", "is_11n": true, "mac": "b4:79:a7:56:34:12", "noise": -114, "rssi": 38, "rx_bytes": 18705870, "rx_mcast": 0, "rx_packets": 78794, "rx_rate": 72109, "rx_retries": 0, "signal": -76, "state": 15, "state_ht": true, "state_pwrmgt": false, "tx_bytes": 4416534, "tx_packets": 58304, "tx_power": 48, "tx_rate": 72222, "tx_retries": 1978, "uptime": 51648, "vlan_id": 0 }, { "auth_time": 4294966266, "authorized": true, "ccq": 981, "dhcpend_time": 1530, "dhcpstart_time": 1530, "hostname": "Chromecast", "idletime": 0, "ip": "192.168.1.30", "is_11n": true, "mac": "80:d2:1d:56:34:12", "noise": -114, "rssi": 37, "rx_bytes": 29377621, "rx_mcast": 0, "rx_packets": 105806, "rx_rate": 72109, "rx_retries": 0, "signal": -77, "state": 15, "state_ht": true, "state_pwrmgt": false, "tx_bytes": 122681792, "tx_packets": 145339, "tx_power": 48, "tx_rate": 72222, "tx_retries": 2980, "uptime": 53658, "vlan_id": 0 }, { "auth_time": 4294967176, "authorized": true, "ccq": 991, "dhcpend_time": 370, "dhcpstart_time": 360, "idletime": 2, "ip": "192.168.1.51", "is_11n": false, "mac": "48:02:2d:56:34:12", "noise": -114, "rssi": 56, "rx_bytes": 48148926, "rx_mcast": 0, "rx_packets": 59462, "rx_rate": 1000, "rx_retries": 0, "signal": -58, "state": 16391, "state_ht": false, "state_pwrmgt": false, "tx_bytes": 7075470, "tx_packets": 33047, "tx_power": 48, "tx_rate": 54000, "tx_retries": 2833, "uptime": 63850, "vlan_id": 0 }, { "auth_time": 4294967276, "authorized": true, "ccq": 971, "dhcpend_time": 30, "dhcpstart_time": 30, "hostname": "ESP_1C2F8D", "idletime": 0, "ip": "192.168.1.54", "is_11n": true, "mac": "a0:20:a6:45:35:12", "noise": -114, "rssi": 51, "rx_bytes": 4684699, "rx_mcast": 0, "rx_packets": 137798, "rx_rate": 2000, "rx_retries": 0, "signal": -63, "state": 31, "state_ht": true, "state_pwrmgt": true, "tx_bytes": 355735, "tx_packets": 6977, "tx_power": 48, "tx_rate": 72222, "tx_retries": 590, "uptime": 78427, "vlan_id": 0 }, { "auth_time": 4294967176, "authorized": true, "ccq": 991, "dhcpend_time": 220, "dhcpstart_time": 220, "hostname": "HF-LPB100-ZJ200", "idletime": 2, "ip": "192.168.1.53", "is_11n": true, "mac": "f0:fe:6b:56:34:12", "noise": -114, "rssi": 29, "rx_bytes": 1415840, "rx_mcast": 0, "rx_packets": 22821, "rx_rate": 1000, "rx_retries": 0, "signal": -85, "state": 31, "state_ht": true, "state_pwrmgt": true, "tx_bytes": 402439, "tx_packets": 7779, "tx_power": 48, "tx_rate": 72222, "tx_retries": 891, "uptime": 111944, "vlan_id": 0 }, { "auth_time": 4294967276, "authorized": true, "ccq": 991, "dhcpend_time": 1620, "dhcpstart_time": 1620, "idletime": 0, "ip": "192.168.1.33", "is_11n": false, "mac": "94:10:3e:45:34:12", "noise": -114, "rssi": 48, "rx_bytes": 47843953, "rx_mcast": 0, "rx_packets": 79456, "rx_rate": 54000, "rx_retries": 0, "signal": -66, "state": 16391, "state_ht": false, "state_pwrmgt": false, "tx_bytes": 4357955, "tx_packets": 60958, "tx_power": 48, "tx_rate": 54000, "tx_retries": 4598, "uptime": 112316, "vlan_id": 0 }, { "auth_time": 4294967266, "authorized": true, "ccq": 991, "dhcpend_time": 540, "dhcpstart_time": 540, "hostname": "amazon-device", "idletime": 0, "ip": "192.168.1.46", "is_11n": true, "mac": "ac:63:be:56:34:12", "noise": -114, "rssi": 30, "rx_bytes": 14607810, "rx_mcast": 0, "rx_packets": 326158, "rx_rate": 24000, "rx_retries": 0, "signal": -84, "state": 15, "state_ht": true, "state_pwrmgt": false, "tx_bytes": 3238319, "tx_packets": 25605, "tx_power": 48, "tx_rate": 72222, "tx_retries": 2465, "uptime": 112364, "vlan_id": 0 }, { "auth_time": 4294966266, "authorized": true, "ccq": 941, "dhcpend_time": 1060, "dhcpstart_time": 1060, "hostname": "Broadlink_RMMINI-56-34-12", "idletime": 12, "ip": "192.168.1.52", "is_11n": true, "mac": "34:ea:34:56:34:12", "noise": -114, "rssi": 43, "rx_bytes": 625268, "rx_mcast": 0, "rx_packets": 4711, "rx_rate": 65000, "rx_retries": 0, "signal": -71, "state": 15, "state_ht": true, "state_pwrmgt": false, "tx_bytes": 420763, "tx_packets": 4620, "tx_power": 48, "tx_rate": 65000, "tx_retries": 783, "uptime": 112368, "vlan_id": 0 }, { "auth_time": 4294967256, "authorized": true, "ccq": 930, "dhcpend_time": 3360, "dhcpstart_time": 3360, "hostname": "garage", "idletime": 2, "ip": "192.168.1.28", "is_11n": true, "mac": "00:13:ef:45:34:12", "noise": -114, "rssi": 28, "rx_bytes": 11639474, "rx_mcast": 0, "rx_packets": 102103, "rx_rate": 24000, "rx_retries": 0, "signal": -86, "state": 31, "state_ht": true, "state_pwrmgt": true, "tx_bytes": 6282728, "tx_packets": 85279, "tx_power": 48, "tx_rate": 58500, "tx_retries": 21185, "uptime": 112369, "vlan_id": 0 }, { "auth_time": 4294967286, "authorized": true, "ccq": 991, "dhcpend_time": 30, "dhcpstart_time": 30, "hostname": "keurig", "idletime": 0, "ip": "192.168.1.48", "is_11n": true, "mac": "18:fe:34:56:34:12", "noise": -114, "rssi": 52, "rx_bytes": 17781940, "rx_mcast": 0, "rx_packets": 432172, "rx_rate": 6000, "rx_retries": 0, "signal": -62, "state": 31, "state_ht": true, "state_pwrmgt": true, "tx_bytes": 4143184, "tx_packets": 53751, "tx_power": 48, "tx_rate": 72222, "tx_retries": 3781, "uptime": 112369, "vlan_id": 0 }, { "auth_time": 4294967276, "authorized": true, "ccq": 940, "dhcpend_time": 50, "dhcpstart_time": 50, "hostname": "freezer", "idletime": 0, "ip": "192.168.1.26", "is_11n": true, "mac": "5c:cf:7f:07:5a:a4", "noise": -114, "rssi": 47, "rx_bytes": 13613265, "rx_mcast": 0, "rx_packets": 411785, "rx_rate": 2000, "rx_retries": 0, "signal": -67, "state": 31, "state_ht": true, "state_pwrmgt": true, "tx_bytes": 1411127, "tx_packets": 17492, "tx_power": 48, "tx_rate": 65000, "tx_retries": 5869, "uptime": 112370, "vlan_id": 0 }, { "auth_time": 4294967276, "authorized": true, "ccq": 778, "dhcpend_time": 50, "dhcpstart_time": 50, "hostname": "fan", "idletime": 0, "ip": "192.168.1.34", "is_11n": true, "mac": "5c:cf:7f:02:09:4e", "noise": -114, "rssi": 45, "rx_bytes": 15377230, "rx_mcast": 0, "rx_packets": 417435, "rx_rate": 6000, "rx_retries": 0, "signal": -69, "state": 31, "state_ht": true, "state_pwrmgt": true, "tx_bytes": 2974258, "tx_packets": 36175, "tx_power": 48, "tx_rate": 58500, "tx_retries": 18552, "uptime": 112372, "vlan_id": 0 }, { "auth_time": 4294966266, "authorized": true, "ccq": 991, "dhcpend_time": 1070, "dhcpstart_time": 1070, "hostname": "Broadlink_RMPROPLUS-45-34-12", "idletime": 1, "ip": "192.168.1.9", "is_11n": true, "mac": "b4:43:0d:45:56:56", "noise": -114, "rssi": 57, "rx_bytes": 1792908, "rx_mcast": 0, "rx_packets": 8528, "rx_rate": 72109, "rx_retries": 0, "signal": -57, "state": 15, "state_ht": true, "state_pwrmgt": false, "tx_bytes": 770834, "tx_packets": 8443, "tx_power": 48, "tx_rate": 65000, "tx_retries": 5258, "uptime": 112373, "vlan_id": 0 }, { "auth_time": 4294967276, "authorized": true, "ccq": 991, "dhcpend_time": 210, "dhcpstart_time": 210, "idletime": 49, "ip": "192.168.1.40", "is_11n": true, "mac": "0c:2a:69:02:3e:3b", "noise": -114, "rssi": 36, "rx_bytes": 427418, "rx_mcast": 0, "rx_packets": 2824, "rx_rate": 65000, "rx_retries": 0, "signal": -78, "state": 15, "state_ht": true, "state_pwrmgt": false, "tx_bytes": 176039, "tx_packets": 2872, "tx_power": 48, "tx_rate": 65000, "tx_retries": 87, "uptime": 112373, "vlan_id": 0 }, { "auth_time": 4294966266, "authorized": true, "ccq": 991, "dhcpend_time": 5030, "dhcpstart_time": 5030, "hostname": "HP2C27D78D9F3E", "idletime": 268, "ip": "192.168.1.44", "is_11n": true, "mac": "2c:27:d7:8d:9f:3e", "noise": -114, "rssi": 41, "rx_bytes": 172927, "rx_mcast": 0, "rx_packets": 781, "rx_rate": 72109, "rx_retries": 0, "signal": -73, "state": 15, "state_ht": true, "state_pwrmgt": false, "tx_bytes": 41924, "tx_packets": 453, "tx_power": 48, "tx_rate": 66610, "tx_retries": 66, "uptime": 112373, "vlan_id": 0 }, { "auth_time": 4294967266, "authorized": true, "ccq": 991, "dhcpend_time": 110, "dhcpstart_time": 110, "idletime": 4, "ip": "192.168.1.55", "is_11n": true, "mac": "0c:2a:69:04:e6:ac", "noise": -114, "rssi": 51, "rx_bytes": 300741, "rx_mcast": 0, "rx_packets": 2443, "rx_rate": 65000, "rx_retries": 0, "signal": -63, "state": 15, "state_ht": true, "state_pwrmgt": false, "tx_bytes": 159980, "tx_packets": 2526, "tx_power": 48, "tx_rate": 65000, "tx_retries": 47, "uptime": 112373, "vlan_id": 0 }, { "auth_time": 4294967256, "authorized": true, "ccq": 991, "dhcpend_time": 1570, "dhcpstart_time": 1560, "idletime": 1, "ip": "192.168.1.37", "is_11n": true, "mac": "0c:2a:69:03:df:37", "noise": -114, "rssi": 42, "rx_bytes": 304567, "rx_mcast": 0, "rx_packets": 2468, "rx_rate": 65000, "rx_retries": 0, "signal": -72, "state": 15, "state_ht": true, "state_pwrmgt": false, "tx_bytes": 164382, "tx_packets": 2553, "tx_power": 48, "tx_rate": 65000, "tx_retries": 48, "uptime": 112373, "vlan_id": 0 } ], "state": "RUN", "tx_bytes": 1190129336, "tx_dropped": 7, "tx_errors": 0, "tx_packets": 1907093, "tx_power": 24, "tx_retries": 29927, "up": true, "usage": "user" }, { "bssid": "ff:ff:ff:ff:ff:ff", "ccq": 914, "channel": 157, "essid": "", "extchannel": 1, "id": "user", "name": "ath1", "num_sta": 0, "radio": "na", "rx_bytes": 0, "rx_crypts": 0, "rx_dropped": 0, "rx_errors": 0, "rx_frags": 0, "rx_nwids": 0, "rx_packets": 0, "sta_table": [], "state": "INIT", "tx_bytes": 0, "tx_dropped": 0, "tx_errors": 0, "tx_packets": 0, "tx_power": 22, "tx_retries": 0, "up": false, "usage": "uplink" }, { "bssid": "82:2a:a8:98:36:3c", "ccq": 482, "channel": 157, "essid": "220 5ghz", "extchannel": 1, "id": "55b19c7e50e4e11e798e84c7", "name": "ath2", "num_sta": 3, "radio": "na", "rx_bytes": 250435644, "rx_crypts": 4071, "rx_dropped": 4071, "rx_errors": 4071, "rx_frags": 0, "rx_nwids": 6660, "rx_packets": 1123263, "sta_table": [ { "auth_time": 4294967246, "authorized": true, "ccq": 631, "dhcpend_time": 190, "dhcpstart_time": 190, "hostname": "android-f4aaefc31d5d2f78", "idletime": 26, "ip": "192.168.1.15", "is_11a": true, "is_11ac": true, "is_11n": true, "mac": "c0:ee:fb:24:ef:a0", "noise": -105, "rssi": 16, "rx_bytes": 3188995, "rx_mcast": 0, "rx_packets": 37243, "rx_rate": 81000, "rx_retries": 0, "signal": -89, "state": 11, "state_ht": true, "state_pwrmgt": false, "tx_bytes": 89051905, "tx_packets": 64756, "tx_power": 44, "tx_rate": 108000, "tx_retries": 0, "uptime": 5494, "vlan_id": 0 }, { "auth_time": 4294967286, "authorized": true, "ccq": 333, "dhcpend_time": 10, "dhcpstart_time": 10, "hostname": "mac_book_air", "idletime": 1, "ip": "192.168.1.12", "is_11a": true, "is_11n": true, "mac": "00:88:65:56:34:12", "noise": -105, "rssi": 52, "rx_bytes": 106902966, "rx_mcast": 0, "rx_packets": 270845, "rx_rate": 300000, "rx_retries": 0, "signal": -53, "state": 11, "state_ht": true, "state_pwrmgt": false, "tx_bytes": 289588466, "tx_packets": 339466, "tx_power": 44, "tx_rate": 300000, "tx_retries": 0, "uptime": 15312, "vlan_id": 0 }, { "auth_time": 4294967266, "authorized": true, "ccq": 333, "dhcpend_time": 160, "dhcpstart_time": 160, "hostname": "Chromecast", "idletime": 0, "ip": "192.168.1.29", "is_11a": true, "is_11ac": true, "is_11n": true, "mac": "f4:f5:d8:11:57:6a", "noise": -105, "rssi": 40, "rx_bytes": 50958412, "rx_mcast": 0, "rx_packets": 339563, "rx_rate": 200000, "rx_retries": 0, "signal": -65, "state": 11, "state_ht": true, "state_pwrmgt": false, "tx_bytes": 1186178689, "tx_packets": 890384, "tx_power": 44, "tx_rate": 150000, "tx_retries": 0, "uptime": 56493, "vlan_id": 0 } ], "state": "RUN", "tx_bytes": 2766849222, "tx_dropped": 119, "tx_errors": 23508, "tx_packets": 2247859, "tx_power": 22, "tx_retries": 0, "up": true, "usage": "user" } ], "version": "3.7.58.6385", "wifi_caps": 1909}' \ No newline at end of file diff --git a/tests/components/unifi_direct/test_device_tracker.py b/tests/components/unifi_direct/test_device_tracker.py deleted file mode 100644 index cfb1c7e92bc..00000000000 --- a/tests/components/unifi_direct/test_device_tracker.py +++ /dev/null @@ -1,178 +0,0 @@ -"""The tests for the Unifi direct device tracker platform.""" -from datetime import timedelta -import os -from unittest.mock import MagicMock, call, patch - -import pytest -import voluptuous as vol - -from homeassistant.components.device_tracker import ( - CONF_CONSIDER_HOME, - CONF_NEW_DEVICE_DEFAULTS, - CONF_TRACK_NEW, -) -from homeassistant.components.device_tracker.legacy import YAML_DEVICES -from homeassistant.components.unifi_direct.device_tracker import ( - CONF_PORT, - DOMAIN, - PLATFORM_SCHEMA, - UnifiDeviceScanner, - _response_to_json, - get_scanner, -) -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PLATFORM, CONF_USERNAME -from homeassistant.core import HomeAssistant -from homeassistant.setup import async_setup_component - -from tests.common import assert_setup_component, load_fixture, mock_component - -scanner_path = "homeassistant.components.unifi_direct.device_tracker.UnifiDeviceScanner" - - -@pytest.fixture(autouse=True) -def setup_comp(hass): - """Initialize components.""" - mock_component(hass, "zone") - yaml_devices = hass.config.path(YAML_DEVICES) - yield - if os.path.isfile(yaml_devices): - os.remove(yaml_devices) - - -@patch(scanner_path, return_value=MagicMock(spec=UnifiDeviceScanner)) -async def test_get_scanner(unifi_mock, hass: HomeAssistant) -> None: - """Test creating an Unifi direct scanner with a password.""" - conf_dict = { - DOMAIN: { - CONF_PLATFORM: "unifi_direct", - CONF_HOST: "fake_host", - CONF_USERNAME: "fake_user", - CONF_PASSWORD: "fake_pass", - CONF_TRACK_NEW: True, - CONF_CONSIDER_HOME: timedelta(seconds=180), - CONF_NEW_DEVICE_DEFAULTS: {CONF_TRACK_NEW: True}, - } - } - - with assert_setup_component(1, DOMAIN): - assert await async_setup_component(hass, DOMAIN, conf_dict) - - conf_dict[DOMAIN][CONF_PORT] = 22 - assert unifi_mock.call_args == call(conf_dict[DOMAIN]) - - -@patch("pexpect.pxssh.pxssh") -async def test_get_device_name(mock_ssh, hass: HomeAssistant) -> None: - """Testing MAC matching.""" - conf_dict = { - DOMAIN: { - CONF_PLATFORM: "unifi_direct", - CONF_HOST: "fake_host", - CONF_USERNAME: "fake_user", - CONF_PASSWORD: "fake_pass", - CONF_PORT: 22, - CONF_TRACK_NEW: True, - CONF_CONSIDER_HOME: timedelta(seconds=180), - } - } - mock_ssh.return_value.before = load_fixture("data.txt", "unifi_direct") - scanner = get_scanner(hass, conf_dict) - devices = scanner.scan_devices() - assert len(devices) == 23 - assert scanner.get_device_name("98:00:c6:56:34:12") == "iPhone" - assert scanner.get_device_name("98:00:C6:56:34:12") == "iPhone" - - -@patch("pexpect.pxssh.pxssh.logout") -@patch("pexpect.pxssh.pxssh.login") -async def test_failed_to_log_in(mock_login, mock_logout, hass: HomeAssistant) -> None: - """Testing exception at login results in False.""" - from pexpect import exceptions - - conf_dict = { - DOMAIN: { - CONF_PLATFORM: "unifi_direct", - CONF_HOST: "fake_host", - CONF_USERNAME: "fake_user", - CONF_PASSWORD: "fake_pass", - CONF_PORT: 22, - CONF_TRACK_NEW: True, - CONF_CONSIDER_HOME: timedelta(seconds=180), - } - } - - mock_login.side_effect = exceptions.EOF("Test") - scanner = get_scanner(hass, conf_dict) - assert not scanner - - -@patch("pexpect.pxssh.pxssh.logout") -@patch("pexpect.pxssh.pxssh.login", autospec=True) -@patch("pexpect.pxssh.pxssh.prompt") -@patch("pexpect.pxssh.pxssh.sendline") -async def test_to_get_update( - mock_sendline, mock_prompt, mock_login, mock_logout, hass: HomeAssistant -) -> None: - """Testing exception in get_update matching.""" - conf_dict = { - DOMAIN: { - CONF_PLATFORM: "unifi_direct", - CONF_HOST: "fake_host", - CONF_USERNAME: "fake_user", - CONF_PASSWORD: "fake_pass", - CONF_PORT: 22, - CONF_TRACK_NEW: True, - CONF_CONSIDER_HOME: timedelta(seconds=180), - } - } - - scanner = get_scanner(hass, conf_dict) - # mock_sendline.side_effect = AssertionError("Test") - mock_prompt.side_effect = AssertionError("Test") - devices = scanner._get_update() - assert devices is None - - -def test_good_response_parses(hass: HomeAssistant) -> None: - """Test that the response form the AP parses to JSON correctly.""" - response = _response_to_json(load_fixture("data.txt", "unifi_direct")) - assert response != {} - - -def test_bad_response_returns_none(hass: HomeAssistant) -> None: - """Test that a bad response form the AP parses to JSON correctly.""" - assert _response_to_json("{(}") == {} - - -def test_config_error() -> None: - """Test for configuration errors.""" - with pytest.raises(vol.Invalid): - PLATFORM_SCHEMA( - { - # no username - CONF_PASSWORD: "password", - CONF_PLATFORM: DOMAIN, - CONF_HOST: "myhost", - "port": 123, - } - ) - with pytest.raises(vol.Invalid): - PLATFORM_SCHEMA( - { - # no password - CONF_USERNAME: "foo", - CONF_PLATFORM: DOMAIN, - CONF_HOST: "myhost", - "port": 123, - } - ) - with pytest.raises(vol.Invalid): - PLATFORM_SCHEMA( - { - CONF_PLATFORM: DOMAIN, - CONF_USERNAME: "foo", - CONF_PASSWORD: "password", - CONF_HOST: "myhost", - "port": "foo", # bad port! - } - )