Skip to content

Discover WiFi-interface switches without a reload (#103) - #147

Open
shauneccles wants to merge 6 commits into
HarvsG:masterfrom
shauneccles:pr/103-dynamic-wifi-switches
Open

Discover WiFi-interface switches without a reload (#103)#147
shauneccles wants to merge 6 commits into
HarvsG:masterfrom
shauneccles:pr/103-dynamic-wifi-switches

Conversation

@shauneccles

Copy link
Copy Markdown

Closes #103.

WiFi access-point switches were only created in switch.async_setup_entry, so an interface enabled after setup (e.g. MLO, or a guest/IoT SSID toggled on later) never got a switch until the integration was reloaded.

Change

Adds a signal_iface_new dispatcher that update_wifi_ifaces_state() fires when it first sees a new interface name, and subscribes the switch platform to it (mirroring the device-tracker discovery pattern). New WiFi interfaces now get switches on the regular poll. Also replaces a ternary with or in WifiApSwitch.name to keep the file ruff-clean.

Stacked on #146

This is stacked on #146 (#139 device-tracking). Until #146 merges, this PR's diff also shows #146's commits — please review/merge #146 first, after which only the switch commit remains here.

Testing

Validated live on a GL-iNet MT6000: renaming/enabling a WiFi interface relabels its existing switch (keyed on the stable interface name) without churn. The new-interface dispatcher mirrors the device-tracker discovery path that was confirmed live with a phone join; the MT6000 (Wi-Fi 6) has no spare interface slot to create a brand-new iface name, so the MLO trigger is validated by analogy to that identical pattern.

🤖 Generated with Claude Code

shauneccles and others added 6 commits June 21, 2026 13:08
update_device_trackers() skipped any client that had neither a name nor an
alias. Many IoT devices (bulbs, sensors) and some phones report blank
names, so most of the network was silently untracked. Track every
connected client instead; ClientDevInfo.update() already falls back to a
MAC-derived name for unnamed devices.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sG#139)

Two device-tracker fixes:

1. New devices appeared only after a reload. update_device_trackers()
   fires signal_device_new when it discovers a previously unseen client,
   but async_setup_entry called update_router() once at setup and never
   subscribed to that signal. Connect update_router() to signal_device_new
   (via entry.async_on_unload) so new devices get entities on the 30s poll.

2. hostname/ip_address/mac_address were cached_property values snapshotted
   in __init__, so a device's reported IP stayed frozen at entity-creation
   time. Read them (and the name) live from the underlying ClientDevInfo,
   which async_on_demand_update refreshes each poll. Also set
   should_poll=False, since updates are pushed via the dispatcher (the old
   value of True contradicted its own "No polling needed" docstring).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Log how many clients connected_clients returns (and their MACs), and each
newly discovered tracked device. This makes "missing devices" reports (e.g.
issue HarvsG#139) diagnosable from a debug log without external probing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
router.setup() registered a SCAN_INTERVAL poller via
async_track_time_interval but never stored its unsub, so each config-entry
reload leaked another timer that kept polling the router forever (observed
as multiple concurrent pollers after a few reloads). Register the unsub
with entry.async_on_unload so it is cancelled on unload/reload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removing the name filter means every connected client is tracked, including
phones that use MAC randomization. Those present a fresh address on each
(re)connection, so without handling they pile up as stale trackers.

- Detect locally-administered (randomized) MACs from the address itself
  (is_randomized_mac in utils.py); HA core provides no helper for this.
- Expose a `mac_randomized` attribute on every tracker.
- Add a `track_randomized_mac` option (options flow): "enabled" tracks them
  with the entity enabled by default, "disabled" registers them disabled by
  default (the default), "ignore" skips creating the entity entirely.
- entity_registry_enabled_default composes with HA's ScannerEntity heuristic:
  randomized MACs honour the option; every other client defers to core, which
  enables a tracker only when its MAC maps to a known device.
- Register the previously-unwired options update listener so option changes
  reload the entry and take effect.

Covered by unit tests for is_randomized_mac.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WiFi access-point switches were created only in switch.async_setup_entry,
so an interface enabled after setup (e.g. MLO, or a guest/IoT SSID toggled
on later) never got a switch until the integration was reloaded.

Add a signal_iface_new dispatcher that update_wifi_ifaces_state() fires
when it first sees a new interface name, and have the switch platform
subscribe to it (mirroring the device_tracker pattern) so new WiFi
interfaces get switches on the regular poll.

Also replaces a ternary with `or` in WifiApSwitch.name to keep the file
ruff-clean (pre-existing FURB110).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New (MLO) entities do not appear

1 participant