-
-
Notifications
You must be signed in to change notification settings - Fork 38.2k
UniFi - Try to handle when UniFi erroneously marks offline client as wired #26960
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’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
97a593f
cba069a
506722a
ea94428
14fdcc3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,13 @@ | ||
| """Support for devices connected to UniFi POE.""" | ||
| import voluptuous as vol | ||
|
|
||
| from homeassistant.components.unifi.config_flow import ( | ||
| get_controller_id_from_config_entry, | ||
| ) | ||
| from homeassistant.const import CONF_HOST | ||
| from homeassistant.core import callback | ||
| from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC | ||
|
|
||
| import homeassistant.helpers.config_validation as cv | ||
|
|
||
| from .config_flow import get_controller_id_from_config_entry | ||
| from .const import ( | ||
| ATTR_MANUFACTURER, | ||
| CONF_BLOCK_CLIENT, | ||
|
|
@@ -20,9 +19,14 @@ | |
| CONF_SSID_FILTER, | ||
| DOMAIN, | ||
| UNIFI_CONFIG, | ||
| UNIFI_WIRELESS_CLIENTS, | ||
| ) | ||
| from .controller import UniFiController | ||
|
|
||
| SAVE_DELAY = 10 | ||
| STORAGE_KEY = "unifi_data" | ||
| STORAGE_VERSION = 1 | ||
|
|
||
| CONF_CONTROLLERS = "controllers" | ||
|
|
||
| CONTROLLER_SCHEMA = vol.Schema( | ||
|
|
@@ -61,6 +65,9 @@ async def async_setup(hass, config): | |
| if DOMAIN in config: | ||
| hass.data[UNIFI_CONFIG] = config[DOMAIN][CONF_CONTROLLERS] | ||
|
|
||
| hass.data[UNIFI_WIRELESS_CLIENTS] = wireless_clients = unifi_wireless_clients(hass) | ||
| await wireless_clients.async_load() | ||
|
|
||
| return True | ||
|
|
||
|
|
||
|
|
@@ -70,9 +77,7 @@ async def async_setup_entry(hass, config_entry): | |
| hass.data[DOMAIN] = {} | ||
|
|
||
| controller = UniFiController(hass, config_entry) | ||
|
|
||
| controller_id = get_controller_id_from_config_entry(config_entry) | ||
|
|
||
| hass.data[DOMAIN][controller_id] = controller | ||
|
|
||
| if not await controller.async_setup(): | ||
|
|
@@ -99,3 +104,42 @@ async def async_unload_entry(hass, config_entry): | |
| controller_id = get_controller_id_from_config_entry(config_entry) | ||
| controller = hass.data[DOMAIN].pop(controller_id) | ||
| return await controller.async_reset() | ||
|
|
||
|
|
||
| class unifi_wireless_clients: | ||
| """Class to store clients known to be wireless. | ||
|
|
||
| This is needed since wireless devices going offline might get marked as wired by UniFi. | ||
| """ | ||
|
|
||
| def __init__(self, hass): | ||
| """Set up client storage.""" | ||
| self.hass = hass | ||
| self.data = {} | ||
| self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) | ||
|
|
||
| async def async_load(self): | ||
| """Load data from file.""" | ||
| data = await self._store.async_load() | ||
|
|
||
| if data is not None: | ||
| self.data = data | ||
|
|
||
| @callback | ||
| def get_data(self, config_entry): | ||
| """Get data related to a specific controller.""" | ||
| controller_id = get_controller_id_from_config_entry(config_entry) | ||
| return set(self.data.get(controller_id, list())) | ||
|
|
||
| @callback | ||
| def update_data(self, data, config_entry): | ||
| """Update data and schedule to save to file.""" | ||
| controller_id = get_controller_id_from_config_entry(config_entry) | ||
| self.data[controller_id] = list(data) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would we not pass in a list already?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. data is a set from one specific controller containing all known wireless devices |
||
|
|
||
| self._store.async_delay_save(self._data_to_save, SAVE_DELAY) | ||
|
|
||
| @callback | ||
| def _data_to_save(self): | ||
| """Return data of UniFi wireless clients to store in a file.""" | ||
| return self.data | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,6 +36,7 @@ | |
| DOMAIN, | ||
| LOGGER, | ||
| UNIFI_CONFIG, | ||
| UNIFI_WIRELESS_CLIENTS, | ||
| ) | ||
| from .errors import AuthenticationRequired, CannotConnect | ||
|
|
||
|
|
@@ -50,6 +51,7 @@ def __init__(self, hass, config_entry): | |
| self.available = True | ||
| self.api = None | ||
| self.progress = None | ||
| self.wireless_clients = None | ||
|
|
||
| self._site_name = None | ||
| self._site_role = None | ||
|
|
@@ -128,6 +130,22 @@ def signal_options_update(self): | |
| """Event specific per UniFi entry to signal new options.""" | ||
| return f"unifi-options-{CONTROLLER_ID.format(host=self.host, site=self.site)}" | ||
|
|
||
| def update_wireless_clients(self): | ||
| """Update set of known to be wireless clients.""" | ||
| wireless_clients = set() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you prefer sets, just convert it to sets when you load the data and convert it to lists when you save it.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just a local list to know which new wireless clients we see from last update |
||
|
|
||
| for client_id in self.api.clients: | ||
| if ( | ||
| client_id not in self.wireless_clients | ||
| and not self.api.clients[client_id].is_wired | ||
| ): | ||
| wireless_clients.add(client_id) | ||
|
|
||
| if wireless_clients: | ||
| self.wireless_clients |= wireless_clients | ||
| unifi_wireless_clients = self.hass.data[UNIFI_WIRELESS_CLIENTS] | ||
| unifi_wireless_clients.update_data(self.wireless_clients, self.config_entry) | ||
|
|
||
| async def request_update(self): | ||
| """Request an update.""" | ||
| if self.progress is not None: | ||
|
|
@@ -170,6 +188,8 @@ async def async_update(self): | |
| LOGGER.info("Reconnected to controller %s", self.host) | ||
| self.available = True | ||
|
|
||
| self.update_wireless_clients() | ||
|
|
||
| async_dispatcher_send(self.hass, self.signal_update) | ||
|
|
||
| async def async_setup(self): | ||
|
|
@@ -197,6 +217,10 @@ async def async_setup(self): | |
| LOGGER.error("Unknown error connecting with UniFi controller: %s", err) | ||
| return False | ||
|
|
||
| wireless_clients = hass.data[UNIFI_WIRELESS_CLIENTS] | ||
| self.wireless_clients = wireless_clients.get_data(self.config_entry) | ||
| self.update_wireless_clients() | ||
|
|
||
| self.import_configuration() | ||
|
|
||
| self.config_entry.add_update_listener(self.async_options_updated) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.