182 lines
5.9 KiB
Python
182 lines
5.9 KiB
Python
"""Support for Unifi WAP controllers."""
|
|
from datetime import timedelta
|
|
|
|
import logging
|
|
import voluptuous as vol
|
|
|
|
from homeassistant import config_entries
|
|
from homeassistant.components import unifi
|
|
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
|
|
from homeassistant.components.device_tracker.config_entry import ScannerEntity
|
|
from homeassistant.components.device_tracker.const import SOURCE_TYPE_ROUTER
|
|
from homeassistant.core import callback
|
|
from homeassistant.const import (
|
|
CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT, CONF_VERIFY_SSL)
|
|
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|
|
|
import homeassistant.helpers.config_validation as cv
|
|
import homeassistant.util.dt as dt_util
|
|
|
|
from .const import (
|
|
CONF_CONTROLLER, CONF_DETECTION_TIME, CONF_SITE_ID, CONF_SSID_FILTER,
|
|
CONTROLLER_ID, DOMAIN as UNIFI_DOMAIN)
|
|
|
|
LOGGER = logging.getLogger(__name__)
|
|
|
|
DEVICE_ATTRIBUTES = [
|
|
'_is_guest_by_uap', 'ap_mac', 'authorized', 'bssid', 'ccq',
|
|
'channel', 'essid', 'hostname', 'ip', 'is_11r', 'is_guest', 'is_wired',
|
|
'mac', 'name', 'noise', 'noted', 'oui', 'qos_policy_applied', 'radio',
|
|
'radio_proto', 'rssi', 'signal', 'site_id', 'vlan'
|
|
]
|
|
|
|
CONF_DT_SITE_ID = 'site_id'
|
|
|
|
DEFAULT_HOST = 'localhost'
|
|
DEFAULT_PORT = 8443
|
|
DEFAULT_VERIFY_SSL = True
|
|
DEFAULT_DETECTION_TIME = timedelta(seconds=300)
|
|
|
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
|
|
vol.Optional(CONF_DT_SITE_ID, default='default'): cv.string,
|
|
vol.Required(CONF_PASSWORD): cv.string,
|
|
vol.Required(CONF_USERNAME): cv.string,
|
|
vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
|
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): vol.Any(
|
|
cv.boolean, cv.isfile)
|
|
}, extra=vol.ALLOW_EXTRA)
|
|
|
|
|
|
async def async_setup_scanner(hass, config, sync_see, discovery_info):
|
|
"""Set up the Unifi integration."""
|
|
config[CONF_SITE_ID] = config.pop(CONF_DT_SITE_ID) # Current from legacy
|
|
|
|
exist = False
|
|
|
|
for entry in hass.config_entries.async_entries(UNIFI_DOMAIN):
|
|
if config[CONF_HOST] == entry.data[CONF_CONTROLLER][CONF_HOST] and \
|
|
config[CONF_SITE_ID] == \
|
|
entry.data[CONF_CONTROLLER][CONF_SITE_ID]:
|
|
exist = True
|
|
break
|
|
|
|
if not exist:
|
|
hass.async_create_task(hass.config_entries.flow.async_init(
|
|
UNIFI_DOMAIN, context={'source': config_entries.SOURCE_IMPORT},
|
|
data=config
|
|
))
|
|
|
|
return True
|
|
|
|
|
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
|
"""Set up device tracker for UniFi component."""
|
|
controller_id = CONTROLLER_ID.format(
|
|
host=config_entry.data[CONF_CONTROLLER][CONF_HOST],
|
|
site=config_entry.data[CONF_CONTROLLER][CONF_SITE_ID],
|
|
)
|
|
controller = hass.data[unifi.DOMAIN][controller_id]
|
|
tracked = {}
|
|
|
|
@callback
|
|
def update_controller():
|
|
"""Update the values of the controller."""
|
|
update_items(controller, async_add_entities, tracked)
|
|
|
|
async_dispatcher_connect(hass, controller.event_update, update_controller)
|
|
|
|
update_controller()
|
|
|
|
|
|
@callback
|
|
def update_items(controller, async_add_entities, tracked):
|
|
"""Update tracked device state from the controller."""
|
|
new_tracked = []
|
|
|
|
for client_id in controller.api.clients:
|
|
|
|
if client_id in tracked:
|
|
LOGGER.debug("Updating UniFi tracked device %s (%s)",
|
|
tracked[client_id].entity_id,
|
|
tracked[client_id].client.mac)
|
|
tracked[client_id].async_schedule_update_ha_state()
|
|
continue
|
|
|
|
client = controller.api.clients[client_id]
|
|
|
|
if not client.is_wired and \
|
|
CONF_SSID_FILTER in controller.unifi_config and \
|
|
client.essid not in controller.unifi_config[CONF_SSID_FILTER]:
|
|
continue
|
|
|
|
tracked[client_id] = UniFiClientTracker(client, controller)
|
|
new_tracked.append(tracked[client_id])
|
|
LOGGER.debug("New UniFi switch %s (%s)", client.hostname, client.mac)
|
|
|
|
if new_tracked:
|
|
async_add_entities(new_tracked)
|
|
|
|
|
|
class UniFiClientTracker(ScannerEntity):
|
|
"""Representation of a network device."""
|
|
|
|
def __init__(self, client, controller):
|
|
"""Set up tracked device."""
|
|
self.client = client
|
|
self.controller = controller
|
|
|
|
async def async_update(self):
|
|
"""Synchronize state with controller."""
|
|
await self.controller.request_update()
|
|
|
|
@property
|
|
def is_connected(self):
|
|
"""Return true if the device is connected to the network."""
|
|
detection_time = self.controller.unifi_config.get(
|
|
CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME)
|
|
|
|
if (dt_util.utcnow() - dt_util.utc_from_timestamp(float(
|
|
self.client.last_seen))) < detection_time:
|
|
return True
|
|
return False
|
|
|
|
@property
|
|
def source_type(self):
|
|
"""Return the source type of the device."""
|
|
return SOURCE_TYPE_ROUTER
|
|
|
|
@property
|
|
def name(self) -> str:
|
|
"""Return the name of the device."""
|
|
return self.client.name or self.client.hostname
|
|
|
|
@property
|
|
def unique_id(self) -> str:
|
|
"""Return a unique identifier for this client."""
|
|
return '{}-{}'.format(self.client.mac, self.controller.site)
|
|
|
|
@property
|
|
def available(self) -> bool:
|
|
"""Return if controller is available."""
|
|
return self.controller.available
|
|
|
|
@property
|
|
def device_info(self):
|
|
"""Return a device description for device registry."""
|
|
return {
|
|
'connections': {(CONNECTION_NETWORK_MAC, self.client.mac)}
|
|
}
|
|
|
|
@property
|
|
def device_state_attributes(self):
|
|
"""Return the device state attributes."""
|
|
attributes = {}
|
|
|
|
for variable in DEVICE_ATTRIBUTES:
|
|
if variable in self.client.raw:
|
|
attributes[variable] = self.client.raw[variable]
|
|
|
|
return attributes
|