mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
UniFi - Track devices (#25570)
This commit is contained in:
parent
71acc6d3f8
commit
35900964cb
@ -7,8 +7,9 @@ from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
|||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_BLOCK_CLIENT, CONF_CONTROLLER, CONF_DETECTION_TIME, CONF_SITE_ID,
|
ATTR_MANUFACTURER, CONF_BLOCK_CLIENT, CONF_CONTROLLER,
|
||||||
CONF_SSID_FILTER, CONTROLLER_ID, DOMAIN, UNIFI_CONFIG)
|
CONF_DETECTION_TIME, CONF_SITE_ID, CONF_SSID_FILTER, CONTROLLER_ID,
|
||||||
|
DOMAIN, UNIFI_CONFIG)
|
||||||
from .controller import UniFiController
|
from .controller import UniFiController
|
||||||
|
|
||||||
CONF_CONTROLLERS = 'controllers'
|
CONF_CONTROLLERS = 'controllers'
|
||||||
@ -66,7 +67,7 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
device_registry.async_get_or_create(
|
device_registry.async_get_or_create(
|
||||||
config_entry_id=config_entry.entry_id,
|
config_entry_id=config_entry.entry_id,
|
||||||
connections={(CONNECTION_NETWORK_MAC, controller.mac)},
|
connections={(CONNECTION_NETWORK_MAC, controller.mac)},
|
||||||
manufacturer='Ubiquiti',
|
manufacturer=ATTR_MANUFACTURER,
|
||||||
model="UniFi Controller",
|
model="UniFi Controller",
|
||||||
name="UniFi Controller",
|
name="UniFi Controller",
|
||||||
# sw_version=config.raw['swversion'],
|
# sw_version=config.raw['swversion'],
|
||||||
|
@ -14,3 +14,5 @@ UNIFI_CONFIG = 'unifi_config'
|
|||||||
CONF_BLOCK_CLIENT = 'block_client'
|
CONF_BLOCK_CLIENT = 'block_client'
|
||||||
CONF_DETECTION_TIME = 'detection_time'
|
CONF_DETECTION_TIME = 'detection_time'
|
||||||
CONF_SSID_FILTER = 'ssid_filter'
|
CONF_SSID_FILTER = 'ssid_filter'
|
||||||
|
|
||||||
|
ATTR_MANUFACTURER = 'Ubiquiti Networks'
|
||||||
|
@ -20,8 +20,8 @@ import homeassistant.helpers.config_validation as cv
|
|||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_CONTROLLER, CONF_DETECTION_TIME, CONF_SITE_ID, CONF_SSID_FILTER,
|
ATTR_MANUFACTURER, CONF_CONTROLLER, CONF_DETECTION_TIME, CONF_SITE_ID,
|
||||||
CONTROLLER_ID, DOMAIN as UNIFI_DOMAIN)
|
CONF_SSID_FILTER, CONTROLLER_ID, DOMAIN as UNIFI_DOMAIN)
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
for entity in registry.entities.values():
|
for entity in registry.entities.values():
|
||||||
|
|
||||||
if entity.config_entry_id == config_entry.entry_id and \
|
if entity.config_entry_id == config_entry.entry_id and \
|
||||||
entity.domain == DOMAIN:
|
entity.domain == DOMAIN and '-' in entity.unique_id:
|
||||||
|
|
||||||
mac, _ = entity.unique_id.split('-', 1)
|
mac, _ = entity.unique_id.split('-', 1)
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ def update_items(controller, async_add_entities, tracked):
|
|||||||
for client_id in controller.api.clients:
|
for client_id in controller.api.clients:
|
||||||
|
|
||||||
if client_id in tracked:
|
if client_id in tracked:
|
||||||
LOGGER.debug("Updating UniFi tracked device %s (%s)",
|
LOGGER.debug("Updating UniFi tracked client %s (%s)",
|
||||||
tracked[client_id].entity_id,
|
tracked[client_id].entity_id,
|
||||||
tracked[client_id].client.mac)
|
tracked[client_id].client.mac)
|
||||||
tracked[client_id].async_schedule_update_ha_state()
|
tracked[client_id].async_schedule_update_ha_state()
|
||||||
@ -131,17 +131,34 @@ def update_items(controller, async_add_entities, tracked):
|
|||||||
|
|
||||||
tracked[client_id] = UniFiClientTracker(client, controller)
|
tracked[client_id] = UniFiClientTracker(client, controller)
|
||||||
new_tracked.append(tracked[client_id])
|
new_tracked.append(tracked[client_id])
|
||||||
LOGGER.debug("New UniFi switch %s (%s)", client.hostname, client.mac)
|
LOGGER.debug("New UniFi client tracker %s (%s)",
|
||||||
|
client.hostname, client.mac)
|
||||||
|
|
||||||
|
for device_id in controller.api.devices:
|
||||||
|
|
||||||
|
if device_id in tracked:
|
||||||
|
LOGGER.debug("Updating UniFi tracked device %s (%s)",
|
||||||
|
tracked[device_id].entity_id,
|
||||||
|
tracked[device_id].device.mac)
|
||||||
|
tracked[device_id].async_schedule_update_ha_state()
|
||||||
|
continue
|
||||||
|
|
||||||
|
device = controller.api.devices[device_id]
|
||||||
|
|
||||||
|
tracked[device_id] = UniFiDeviceTracker(device, controller)
|
||||||
|
new_tracked.append(tracked[device_id])
|
||||||
|
LOGGER.debug("New UniFi device tracker %s (%s)",
|
||||||
|
device.name, device.mac)
|
||||||
|
|
||||||
if new_tracked:
|
if new_tracked:
|
||||||
async_add_entities(new_tracked)
|
async_add_entities(new_tracked)
|
||||||
|
|
||||||
|
|
||||||
class UniFiClientTracker(ScannerEntity):
|
class UniFiClientTracker(ScannerEntity):
|
||||||
"""Representation of a network device."""
|
"""Representation of a network client."""
|
||||||
|
|
||||||
def __init__(self, client, controller):
|
def __init__(self, client, controller):
|
||||||
"""Set up tracked device."""
|
"""Set up tracked client."""
|
||||||
self.client = client
|
self.client = client
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
|
|
||||||
@ -151,7 +168,7 @@ class UniFiClientTracker(ScannerEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_connected(self):
|
def is_connected(self):
|
||||||
"""Return true if the device is connected to the network."""
|
"""Return true if the client is connected to the network."""
|
||||||
detection_time = self.controller.unifi_config.get(
|
detection_time = self.controller.unifi_config.get(
|
||||||
CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME)
|
CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME)
|
||||||
|
|
||||||
@ -162,12 +179,12 @@ class UniFiClientTracker(ScannerEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def source_type(self):
|
def source_type(self):
|
||||||
"""Return the source type of the device."""
|
"""Return the source type of the client."""
|
||||||
return SOURCE_TYPE_ROUTER
|
return SOURCE_TYPE_ROUTER
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return the name of the device."""
|
"""Return the name of the client."""
|
||||||
return self.client.name or self.client.hostname
|
return self.client.name or self.client.hostname
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -182,14 +199,14 @@ class UniFiClientTracker(ScannerEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self):
|
def device_info(self):
|
||||||
"""Return a device description for device registry."""
|
"""Return a client description for device registry."""
|
||||||
return {
|
return {
|
||||||
'connections': {(CONNECTION_NETWORK_MAC, self.client.mac)}
|
'connections': {(CONNECTION_NETWORK_MAC, self.client.mac)}
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return the device state attributes."""
|
"""Return the client state attributes."""
|
||||||
attributes = {}
|
attributes = {}
|
||||||
|
|
||||||
for variable in DEVICE_ATTRIBUTES:
|
for variable in DEVICE_ATTRIBUTES:
|
||||||
@ -197,3 +214,71 @@ class UniFiClientTracker(ScannerEntity):
|
|||||||
attributes[variable] = self.client.raw[variable]
|
attributes[variable] = self.client.raw[variable]
|
||||||
|
|
||||||
return attributes
|
return attributes
|
||||||
|
|
||||||
|
|
||||||
|
class UniFiDeviceTracker(ScannerEntity):
|
||||||
|
"""Representation of a network infrastructure device."""
|
||||||
|
|
||||||
|
def __init__(self, device, controller):
|
||||||
|
"""Set up tracked device."""
|
||||||
|
self.device = device
|
||||||
|
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.device.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.device.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self) -> str:
|
||||||
|
"""Return a unique identifier for this device."""
|
||||||
|
return self.device.mac
|
||||||
|
|
||||||
|
@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.device.mac)},
|
||||||
|
'manufacturer': ATTR_MANUFACTURER,
|
||||||
|
'model': self.device.model,
|
||||||
|
'name': self.device.name,
|
||||||
|
'sw_version': self.device.version
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
"""Return the device state attributes."""
|
||||||
|
attributes = {}
|
||||||
|
|
||||||
|
attributes['upgradable'] = self.device.upgradable
|
||||||
|
attributes['overheating'] = self.device.overheating
|
||||||
|
|
||||||
|
if self.device.has_fan:
|
||||||
|
attributes['fan_level'] = self.device.fan_level
|
||||||
|
|
||||||
|
return attributes
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/components/unifi",
|
"documentation": "https://www.home-assistant.io/components/unifi",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"aiounifi==7"
|
"aiounifi==8"
|
||||||
],
|
],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
|
@ -169,7 +169,7 @@ aiopvapi==1.6.14
|
|||||||
aioswitcher==2019.4.26
|
aioswitcher==2019.4.26
|
||||||
|
|
||||||
# homeassistant.components.unifi
|
# homeassistant.components.unifi
|
||||||
aiounifi==7
|
aiounifi==8
|
||||||
|
|
||||||
# homeassistant.components.wwlln
|
# homeassistant.components.wwlln
|
||||||
aiowwlln==1.0.0
|
aiowwlln==1.0.0
|
||||||
|
@ -64,7 +64,7 @@ aionotion==1.1.0
|
|||||||
aioswitcher==2019.4.26
|
aioswitcher==2019.4.26
|
||||||
|
|
||||||
# homeassistant.components.unifi
|
# homeassistant.components.unifi
|
||||||
aiounifi==7
|
aiounifi==8
|
||||||
|
|
||||||
# homeassistant.components.wwlln
|
# homeassistant.components.wwlln
|
||||||
aiowwlln==1.0.0
|
aiowwlln==1.0.0
|
||||||
|
@ -49,6 +49,22 @@ CLIENT_3 = {
|
|||||||
'mac': '00:00:00:00:00:03',
|
'mac': '00:00:00:00:00:03',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEVICE_1 = {
|
||||||
|
'board_rev': 3,
|
||||||
|
'device_id': 'mock-id',
|
||||||
|
'has_fan': True,
|
||||||
|
'fan_level': 0,
|
||||||
|
'ip': '10.0.1.1',
|
||||||
|
'last_seen': 1562600145,
|
||||||
|
'mac': '00:00:00:00:01:01',
|
||||||
|
'model': 'US16P150',
|
||||||
|
'name': 'device_1',
|
||||||
|
'overheating': False,
|
||||||
|
'type': 'usw',
|
||||||
|
'upgradable': False,
|
||||||
|
'version': '4.0.42.10433',
|
||||||
|
}
|
||||||
|
|
||||||
CONTROLLER_DATA = {
|
CONTROLLER_DATA = {
|
||||||
CONF_HOST: 'mock-host',
|
CONF_HOST: 'mock-host',
|
||||||
CONF_USERNAME: 'mock-user',
|
CONF_USERNAME: 'mock-user',
|
||||||
@ -137,32 +153,41 @@ async def test_tracked_devices(hass, mock_controller):
|
|||||||
"""Test the update_items function with some clients."""
|
"""Test the update_items function with some clients."""
|
||||||
mock_controller.mock_client_responses.append(
|
mock_controller.mock_client_responses.append(
|
||||||
[CLIENT_1, CLIENT_2, CLIENT_3])
|
[CLIENT_1, CLIENT_2, CLIENT_3])
|
||||||
mock_controller.mock_device_responses.append({})
|
mock_controller.mock_device_responses.append([DEVICE_1])
|
||||||
mock_controller.unifi_config = {unifi_dt.CONF_SSID_FILTER: ['ssid']}
|
mock_controller.unifi_config = {unifi_dt.CONF_SSID_FILTER: ['ssid']}
|
||||||
|
|
||||||
await setup_controller(hass, mock_controller)
|
await setup_controller(hass, mock_controller)
|
||||||
assert len(mock_controller.mock_requests) == 2
|
assert len(mock_controller.mock_requests) == 2
|
||||||
assert len(hass.states.async_all()) == 4
|
assert len(hass.states.async_all()) == 5
|
||||||
|
|
||||||
device_1 = hass.states.get('device_tracker.client_1')
|
client_1 = hass.states.get('device_tracker.client_1')
|
||||||
|
assert client_1 is not None
|
||||||
|
assert client_1.state == 'not_home'
|
||||||
|
|
||||||
|
client_2 = hass.states.get('device_tracker.wired_client')
|
||||||
|
assert client_2 is not None
|
||||||
|
assert client_2.state == 'not_home'
|
||||||
|
|
||||||
|
client_3 = hass.states.get('device_tracker.client_3')
|
||||||
|
assert client_3 is None
|
||||||
|
|
||||||
|
device_1 = hass.states.get('device_tracker.device_1')
|
||||||
assert device_1 is not None
|
assert device_1 is not None
|
||||||
assert device_1.state == 'not_home'
|
assert device_1.state == 'not_home'
|
||||||
|
|
||||||
device_2 = hass.states.get('device_tracker.wired_client')
|
client_1_copy = copy(CLIENT_1)
|
||||||
assert device_2 is not None
|
client_1_copy['last_seen'] = dt_util.as_timestamp(dt_util.utcnow())
|
||||||
assert device_2.state == 'not_home'
|
device_1_copy = copy(DEVICE_1)
|
||||||
|
device_1_copy['last_seen'] = dt_util.as_timestamp(dt_util.utcnow())
|
||||||
device_3 = hass.states.get('device_tracker.client_3')
|
mock_controller.mock_client_responses.append([client_1_copy])
|
||||||
assert device_3 is None
|
mock_controller.mock_device_responses.append([device_1_copy])
|
||||||
|
|
||||||
client_1 = copy(CLIENT_1)
|
|
||||||
client_1['last_seen'] = dt_util.as_timestamp(dt_util.utcnow())
|
|
||||||
mock_controller.mock_client_responses.append([client_1])
|
|
||||||
mock_controller.mock_device_responses.append({})
|
|
||||||
await mock_controller.async_update()
|
await mock_controller.async_update()
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
device_1 = hass.states.get('device_tracker.client_1')
|
client_1 = hass.states.get('device_tracker.client_1')
|
||||||
|
assert client_1.state == 'home'
|
||||||
|
|
||||||
|
device_1 = hass.states.get('device_tracker.device_1')
|
||||||
assert device_1.state == 'home'
|
assert device_1.state == 'home'
|
||||||
|
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ async def test_successful_config_entry(hass):
|
|||||||
'connections': {
|
'connections': {
|
||||||
('mac', '00:11:22:33:44:55')
|
('mac', '00:11:22:33:44:55')
|
||||||
},
|
},
|
||||||
'manufacturer': 'Ubiquiti',
|
'manufacturer': unifi.ATTR_MANUFACTURER,
|
||||||
'model': "UniFi Controller",
|
'model': "UniFi Controller",
|
||||||
'name': "UniFi Controller",
|
'name': "UniFi Controller",
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user