Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

UniFi - Allow tracking of clients connected to third party APs #34067

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions homeassistant/components/unifi/.translations/en.json
Expand Up @@ -41,6 +41,7 @@
"device_tracker": {
"data": {
"detection_time": "Time in seconds from last seen until considered away",
"ignore_wired_bug": "Disable UniFi wired bug logic",
"ssid_filter": "Select SSIDs to track wireless clients on",
"track_clients": "Track network clients",
"track_devices": "Track network devices (Ubiquiti devices)",
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/unifi/__init__.py
Expand Up @@ -7,7 +7,7 @@
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC

from .config_flow import get_controller_id_from_config_entry
from .const import ATTR_MANUFACTURER, DOMAIN, UNIFI_WIRELESS_CLIENTS
from .const import ATTR_MANUFACTURER, DOMAIN, LOGGER, UNIFI_WIRELESS_CLIENTS
from .controller import UniFiController

SAVE_DELAY = 10
Expand Down Expand Up @@ -42,6 +42,8 @@ async def async_setup_entry(hass, config_entry):

hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, controller.shutdown)

LOGGER.debug("UniFi config options %s", config_entry.options)

if controller.mac is None:
return True

Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/unifi/config_flow.py
Expand Up @@ -19,6 +19,7 @@
CONF_BLOCK_CLIENT,
CONF_CONTROLLER,
CONF_DETECTION_TIME,
CONF_IGNORE_WIRED_BUG,
CONF_POE_CLIENTS,
CONF_SITE_ID,
CONF_SSID_FILTER,
Expand Down Expand Up @@ -216,6 +217,10 @@ async def async_step_device_tracker(self, user_input=None):
self.controller.option_detection_time.total_seconds()
),
): int,
vol.Optional(
CONF_IGNORE_WIRED_BUG,
default=self.controller.option_ignore_wired_bug,
): bool,
}
),
)
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/unifi/const.py
Expand Up @@ -14,13 +14,15 @@
CONF_ALLOW_BANDWIDTH_SENSORS = "allow_bandwidth_sensors"
CONF_BLOCK_CLIENT = "block_client"
CONF_DETECTION_TIME = "detection_time"
CONF_IGNORE_WIRED_BUG = "ignore_wired_bug"
CONF_POE_CLIENTS = "poe_clients"
CONF_TRACK_CLIENTS = "track_clients"
CONF_TRACK_DEVICES = "track_devices"
CONF_TRACK_WIRED_CLIENTS = "track_wired_clients"
CONF_SSID_FILTER = "ssid_filter"

DEFAULT_ALLOW_BANDWIDTH_SENSORS = False
DEFAULT_IGNORE_WIRED_BUG = False
DEFAULT_POE_CLIENTS = True
DEFAULT_TRACK_CLIENTS = True
DEFAULT_TRACK_DEVICES = True
Expand Down
71 changes: 43 additions & 28 deletions homeassistant/components/unifi/controller.py
Expand Up @@ -24,6 +24,7 @@
CONF_BLOCK_CLIENT,
CONF_CONTROLLER,
CONF_DETECTION_TIME,
CONF_IGNORE_WIRED_BUG,
CONF_POE_CLIENTS,
CONF_SITE_ID,
CONF_SSID_FILTER,
Expand All @@ -33,6 +34,7 @@
CONTROLLER_ID,
DEFAULT_ALLOW_BANDWIDTH_SENSORS,
DEFAULT_DETECTION_TIME,
DEFAULT_IGNORE_WIRED_BUG,
DEFAULT_POE_CLIENTS,
DEFAULT_TRACK_CLIENTS,
DEFAULT_TRACK_DEVICES,
Expand Down Expand Up @@ -89,39 +91,37 @@ def site_role(self):
return self._site_role

@property
def option_allow_bandwidth_sensors(self):
"""Config entry option to allow bandwidth sensors."""
return self.config_entry.options.get(
CONF_ALLOW_BANDWIDTH_SENSORS, DEFAULT_ALLOW_BANDWIDTH_SENSORS
)

@property
def option_block_clients(self):
"""Config entry option with list of clients to control network access."""
return self.config_entry.options.get(CONF_BLOCK_CLIENT, [])
def mac(self):
"""Return the mac address of this controller."""
for client in self.api.clients.values():
if self.host == client.ip:
return client.mac
return None

@property
def option_poe_clients(self):
"""Config entry option to control poe clients."""
return self.config_entry.options.get(CONF_POE_CLIENTS, DEFAULT_POE_CLIENTS)
# Device tracker options

@property
def option_track_clients(self):
"""Config entry option to not track clients."""
return self.config_entry.options.get(CONF_TRACK_CLIENTS, DEFAULT_TRACK_CLIENTS)

@property
def option_track_devices(self):
"""Config entry option to not track devices."""
return self.config_entry.options.get(CONF_TRACK_DEVICES, DEFAULT_TRACK_DEVICES)

@property
def option_track_wired_clients(self):
"""Config entry option to not track wired clients."""
return self.config_entry.options.get(
CONF_TRACK_WIRED_CLIENTS, DEFAULT_TRACK_WIRED_CLIENTS
)

@property
def option_track_devices(self):
"""Config entry option to not track devices."""
return self.config_entry.options.get(CONF_TRACK_DEVICES, DEFAULT_TRACK_DEVICES)

@property
def option_ssid_filter(self):
"""Config entry option listing what SSIDs are being used to track clients."""
return self.config_entry.options.get(CONF_SSID_FILTER, [])

@property
def option_detection_time(self):
"""Config entry option defining number of seconds from last seen to away."""
Expand All @@ -132,17 +132,32 @@ def option_detection_time(self):
)

@property
def option_ssid_filter(self):
"""Config entry option listing what SSIDs are being used to track clients."""
return self.config_entry.options.get(CONF_SSID_FILTER, [])
def option_ignore_wired_bug(self):
"""Config entry option to ignore wired bug."""
return self.config_entry.options.get(
CONF_IGNORE_WIRED_BUG, DEFAULT_IGNORE_WIRED_BUG
)

# Client control options

@property
def mac(self):
"""Return the mac address of this controller."""
for client in self.api.clients.values():
if self.host == client.ip:
return client.mac
return None
def option_poe_clients(self):
"""Config entry option to control poe clients."""
return self.config_entry.options.get(CONF_POE_CLIENTS, DEFAULT_POE_CLIENTS)

@property
def option_block_clients(self):
"""Config entry option with list of clients to control network access."""
return self.config_entry.options.get(CONF_BLOCK_CLIENT, [])

# Statistics sensor options

@property
def option_allow_bandwidth_sensors(self):
"""Config entry option to allow bandwidth sensors."""
return self.config_entry.options.get(
CONF_ALLOW_BANDWIDTH_SENSORS, DEFAULT_ALLOW_BANDWIDTH_SENSORS
)

@callback
def async_unifi_signalling_callback(self, signal, data):
Expand Down
29 changes: 15 additions & 14 deletions homeassistant/components/unifi/device_tracker.py
Expand Up @@ -67,6 +67,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities):

client = controller.api.clients_all[mac]
controller.api.clients.process_raw([client.raw])
LOGGER.debug(
"Restore disconnected client %s (%s)", entity.entity_id, client.mac,
)

@callback
def update_controller():
Expand Down Expand Up @@ -121,20 +124,21 @@ def options_updated():
remove.add(mac)

if option_ssid_filter != controller.option_ssid_filter:
option_ssid_filter = controller.option_ssid_filter
update = True

for mac, entity in tracked.items():
if (
isinstance(entity, UniFiClientTracker)
and not entity.is_wired
and entity.client.essid not in option_ssid_filter
):
remove.add(mac)
if controller.option_ssid_filter:
for mac, entity in tracked.items():
if (
isinstance(entity, UniFiClientTracker)
and not entity.is_wired
and entity.client.essid not in controller.option_ssid_filter
):
remove.add(mac)

option_track_clients = controller.option_track_clients
option_track_devices = controller.option_track_devices
option_track_wired_clients = controller.option_track_wired_clients
option_ssid_filter = controller.option_ssid_filter

for mac in remove:
entity = tracked.pop(mac)
Expand Down Expand Up @@ -304,13 +308,12 @@ def __init__(self, device, controller):
"""Set up tracked device."""
self.device = device
self.controller = controller
self.listeners = []

async def async_added_to_hass(self):
"""Subscribe to device events."""
LOGGER.debug("New UniFi device tracker %s (%s)", self.name, self.device.mac)
LOGGER.debug("New device %s (%s)", self.entity_id, self.device.mac)
self.device.register_callback(self.async_update_callback)
self.listeners.append(
self.async_on_remove(
async_dispatcher_connect(
self.hass, self.controller.signal_reachable, self.async_update_callback
)
Expand All @@ -319,13 +322,11 @@ async def async_added_to_hass(self):
async def async_will_remove_from_hass(self) -> None:
"""Disconnect device object when removed."""
self.device.remove_callback(self.async_update_callback)
for unsub_dispatcher in self.listeners:
Kane610 marked this conversation as resolved.
Show resolved Hide resolved
unsub_dispatcher()

@callback
def async_update_callback(self):
"""Update the sensor's state."""
LOGGER.debug("Updating UniFi tracked device %s", self.entity_id)
LOGGER.debug("Updating device %s (%s)", self.entity_id, self.device.mac)

self.async_write_ha_state()

Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/unifi/sensor.py
Expand Up @@ -98,7 +98,7 @@ class UniFiRxBandwidthSensor(UniFiClient):
@property
def state(self):
"""Return the state of the sensor."""
if self.is_wired:
if self._is_wired:
return self.client.wired_rx_bytes / 1000000
return self.client.raw.get("rx_bytes", 0) / 1000000

Expand All @@ -125,7 +125,7 @@ class UniFiTxBandwidthSensor(UniFiRxBandwidthSensor):
@property
def state(self):
"""Return the state of the sensor."""
if self.is_wired:
if self._is_wired:
return self.client.wired_tx_bytes / 1000000
return self.client.raw.get("tx_bytes", 0) / 1000000

Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/unifi/strings.json
Expand Up @@ -32,6 +32,7 @@
"device_tracker": {
"data": {
"detection_time": "Time in seconds from last seen until considered away",
"ignore_wired_bug": "Disable UniFi wired bug logic",
"ssid_filter": "Select SSIDs to track wireless clients on",
"track_clients": "Track network clients",
"track_devices": "Track network devices (Ubiquiti devices)",
Expand Down Expand Up @@ -61,4 +62,4 @@
"error": {
"unknown_client_mac": "No client available in UniFi on that MAC address"
}
}
}
26 changes: 16 additions & 10 deletions homeassistant/components/unifi/unifi_client.py
Expand Up @@ -39,18 +39,17 @@ def __init__(self, client, controller) -> None:
"""Set up client."""
self.client = client
self.controller = controller
self.listeners = []

self.is_wired = self.client.mac not in controller.wireless_clients
self._is_wired = self.client.mac not in controller.wireless_clients
self.is_blocked = self.client.blocked
self.wired_connection = None
self.wireless_connection = None

async def async_added_to_hass(self) -> None:
"""Client entity created."""
LOGGER.debug("New UniFi client %s (%s)", self.name, self.client.mac)
LOGGER.debug("New client %s (%s)", self.entity_id, self.client.mac)
self.client.register_callback(self.async_update_callback)
self.listeners.append(
Kane610 marked this conversation as resolved.
Show resolved Hide resolved
self.async_on_remove(
async_dispatcher_connect(
self.hass, self.controller.signal_reachable, self.async_update_callback
)
Expand All @@ -59,17 +58,14 @@ async def async_added_to_hass(self) -> None:
async def async_will_remove_from_hass(self) -> None:
"""Disconnect client object when removed."""
self.client.remove_callback(self.async_update_callback)
for unsub_dispatcher in self.listeners:
unsub_dispatcher()

@callback
def async_update_callback(self) -> None:
"""Update the clients state."""
if self.is_wired and self.client.mac in self.controller.wireless_clients:
self.is_wired = False
if self._is_wired and self.client.mac in self.controller.wireless_clients:
self._is_wired = False

if self.client.last_updated == SOURCE_EVENT:

if self.client.event.event in WIRELESS_CLIENT:
self.wireless_connection = self.client.event.event in (
WIRELESS_CLIENT_CONNECTED,
Expand All @@ -84,9 +80,19 @@ def async_update_callback(self) -> None:
elif self.client.event.event in CLIENT_BLOCKED + CLIENT_UNBLOCKED:
self.is_blocked = self.client.event.event in CLIENT_BLOCKED

LOGGER.debug("Updating client %s %s", self.entity_id, self.client.mac)
LOGGER.debug("Updating client %s (%s)", self.entity_id, self.client.mac)
self.async_write_ha_state()

@property
def is_wired(self):
"""Return if the client is wired.

Allows disabling logic to keep track of clients affected by UniFi wired bug marking wireless devices as wired. This is useful when running a network not only containing UniFi APs.
"""
if self.controller.option_ignore_wired_bug:
return self.client.is_wired
return self._is_wired

@property
def name(self) -> str:
"""Return the name of the client."""
Expand Down
6 changes: 4 additions & 2 deletions tests/components/unifi/test_config_flow.py
Expand Up @@ -11,6 +11,7 @@
CONF_BLOCK_CLIENT,
CONF_CONTROLLER,
CONF_DETECTION_TIME,
CONF_IGNORE_WIRED_BUG,
CONF_POE_CLIENTS,
CONF_SITE_ID,
CONF_SSID_FILTER,
Expand Down Expand Up @@ -338,9 +339,10 @@ async def test_option_flow(hass):
CONF_TRACK_CLIENTS: False,
CONF_TRACK_WIRED_CLIENTS: False,
CONF_TRACK_DEVICES: False,
CONF_DETECTION_TIME: 100,
CONF_SSID_FILTER: ["SSID 1"],
CONF_BLOCK_CLIENT: ["00:00:00:00:00:01"],
CONF_DETECTION_TIME: 100,
CONF_IGNORE_WIRED_BUG: False,
CONF_POE_CLIENTS: False,
CONF_BLOCK_CLIENT: ["00:00:00:00:00:01"],
CONF_ALLOW_BANDWIDTH_SENSORS: True,
}