-
Notifications
You must be signed in to change notification settings - Fork 120
Add support to multiple wifi interfaces #3726
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
base: master
Are you sure you want to change the base?
Conversation
Adds support for multiple WiFi interfaces (wlan0, wlan1, etc.): - New API v2 endpoints: /interfaces/, /wifi/scan, /wifi/status, /wifi/connect - NetworkManager handler discovers all WiFi devices dynamically - Filter uap0 virtual interface from user-selectable interfaces - Hotspot operations remain bound to wlan0 - Use sys.modules pattern in routers to avoid circular imports Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
- WifiInterfaceTrayMenu: Per-interface icon with dropdown - WifiInterfaceManager: Network list and connection UI per interface - InterfaceConnectionDialog: Connection dialog for v2 API - WifiNetworkCard: Add 2.4G/5G/6G frequency band chip - WifiTrayMenu: Show icon for each detected interface - WifiUpdater: Poll v2 API endpoints for interface data Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
…rength NetworkManager returns percentage (0-100), wpa_supplicant returns dBm (negative). Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
Reviewer's GuideImplements multi-interface WiFi support end‑to‑end by extending the NetworkManager backend, introducing a versioned v2 REST API, and updating the frontend to manage and visualize multiple WiFi interfaces while keeping existing v1 behavior intact. Class diagram for multi-interface backend WiFi models and managerclassDiagram
class WifiCredentials {
+str ssid
+str password
}
class ScannedWifiNetwork {
+str ssid
+int signallevel
+int frequency
+str bssid
+str flags
}
class SavedWifiNetwork {
+str ssid
+str nm_uuid
}
class WifiInterface {
+str name
+bool connected
+str ssid
+int signal_strength
+str ip_address
+str mac_address
}
class WifiInterfaceStatus {
+str interface
+str state
+str ssid
+str bssid
+str ip_address
+int signal_strength
+int frequency
+str key_mgmt
}
class WifiInterfaceScanResult {
+str interface
+List~ScannedWifiNetwork~ networks
}
class WifiInterfaceList {
+List~WifiInterface~ interfaces
+str hotspot_interface
}
class ConnectRequest {
+str interface
+WifiCredentials credentials
+bool hidden
}
class DisconnectRequest {
+str interface
}
class AbstractWifiManager {
<<abstract>>
+async status() WifiStatus
+async get_wifi_available() List~ScannedWifiNetwork~
+async try_connect_to_network(credentials, hidden)
+async disconnect()
+async get_saved_wifi_network() List~SavedWifiNetwork~
+async remove_network(ssid)
}
class NetworkManagerWifi {
+Dict~str,str~ _device_paths
+str _device_path
+str _ap_interface
+async start()
+async _get_wifi_devices() Dict~str,str~
+async _create_virtual_interface() bool
+async _autoscan()
+async get_wifi_interfaces() List~WifiInterface~
+async get_interface_status(interface_name) WifiInterface
+async scan_interface(interface_name) List~ScannedWifiNetwork~
+async scan_all_interfaces() List~WifiInterfaceScanResult~
+async _scan_device(device_path) List~ScannedWifiNetwork~
+async get_interface_connection_status(interface_name) WifiInterfaceStatus
+async get_all_interface_status() List~WifiInterfaceStatus~
+async connect_interface(interface_name, credentials, hidden)
+async disconnect_interface(interface_name)
+async get_wifi_available() List~ScannedWifiNetwork~
+async try_connect_to_network(credentials, hidden)
}
AbstractWifiManager <|-- NetworkManagerWifi
WifiInterfaceScanResult "*" --> "*" ScannedWifiNetwork
WifiInterfaceList "*" --> "*" WifiInterface
ConnectRequest --> WifiCredentials
class WifiRouterV2 {
+async scan_interface(interface_name) WifiInterfaceScanResult
+async scan_all_interfaces() List~WifiInterfaceScanResult~
+async get_status(interface_name) WifiInterfaceStatus
+async get_all_status() List~WifiInterfaceStatus~
+async connect(request) dict
+async disconnect(request) dict
+async get_saved_networks() List~SavedWifiNetwork~
+async remove_saved_network(ssid) dict
}
class InterfacesRouterV2 {
+async list_interfaces() WifiInterfaceList
+async get_interface(interface_name) WifiInterface
}
WifiRouterV2 --> AbstractWifiManager
InterfacesRouterV2 --> AbstractWifiManager
WifiRouterV2 --> WifiInterfaceStatus
WifiRouterV2 --> WifiInterfaceScanResult
WifiRouterV2 --> ConnectRequest
WifiRouterV2 --> DisconnectRequest
WifiRouterV2 --> SavedWifiNetwork
InterfacesRouterV2 --> WifiInterface
InterfacesRouterV2 --> WifiInterfaceList
Class diagram for frontend WiFi store and multi-interface typesclassDiagram
class Network {
+str ssid
+int signal
+bool locked
+bool saved
+str bssid
+int frequency
}
class WifiInterface_ts {
+str name
+bool connected
+str ssid
+int signal_strength
+str ip_address
+str mac_address
}
class WifiInterfaceStatus_ts {
+str interface
+str state
+str ssid
+str bssid
+str ip_address
+int signal_strength
+int frequency
+str key_mgmt
}
class WifiInterfaceScanResult_ts {
+str interface
+WPANetwork[] networks
}
class WifiInterfaceList_ts {
+WifiInterface_ts[] interfaces
+str hotspot_interface
}
class WifiStore {
+str API_URL
+str API_URL_V2
+Network current_network
+Network[] available_networks
+Network[] saved_networks
+bool is_loading
+WifiInterface_ts[] wifi_interfaces
+Map~str,Network[]~ interface_scan_results
+Map~str,WifiInterfaceStatus_ts~ interface_status
+setCurrentNetwork(network)
+setAvailableNetworks(networks)
+setSavedNetworks(networks)
+setLoading(loading)
+setWifiInterfaces(interfaces)
+setInterfaceScanResults(payload)
+setInterfaceStatus(payload)
+connectable_networks() Network[]
+getInterface(name) WifiInterface_ts
+getInterfaceNetworks(name) Network[]
+getInterfaceConnectionStatus(name) WifiInterfaceStatus_ts
}
WifiStore --> WifiInterface_ts
WifiStore --> WifiInterfaceStatus_ts
WifiStore --> Network
class WifiUpdater {
+fetch_interfaces_task
+fetch_interface_scans_task
+fetch_interface_status_task
+async fetchInterfaces()
+async fetchInterfaceScans()
+async fetchInterfaceStatus()
}
WifiUpdater --> WifiStore
WifiUpdater --> WifiInterfaceList_ts
WifiUpdater --> WifiInterfaceScanResult_ts
WifiUpdater --> WifiInterfaceStatus_ts
Flow diagram for v2 WiFi interface discovery and scanningflowchart TD
A_start[Start Wifi service] --> B_start_method[NetworkManagerWifi.start]
B_start_method --> C_get_devices[_get_wifi_devices]
C_get_devices --> D_populate_map[Populate _device_paths map keyed by interface name]
D_populate_map --> E_select_primary[Select primary _device_path
prefer wlan0 else first]
E_select_primary --> F_create_virtual[_create_virtual_interface using HOTSPOT_INTERFACE]
F_create_virtual --> G_autoscan_loop[_autoscan over all _device_paths]
subgraph Scan_all_interfaces_v2
G1_call[WifiUpdater.fetchInterfaceScans] --> G2_http[GET /wifi-manager/v2.0/wifi/scan]
G2_http --> G3_router[wifi_router_v2.scan_all_interfaces]
G3_router --> G4_manager[NetworkManagerWifi.scan_all_interfaces]
G4_manager --> G5_scan_device[_scan_device per device_path]
G5_scan_device --> G6_response[List WifiInterfaceScanResult]
G6_response --> G7_store[WifiStore.setInterfaceScanResults per interface]
end
G_autoscan_loop --> G1_call
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey - I've found 2 issues, and left some high level feedback:
- In the NetworkManagerWifi methods where you build
ip_addressfromIPv4Config.address_data, you're indexingaddress_data[0]["address"][1]; this assumes"address"is an indexable sequence – double‑check the actual structure (often it's already a string) and adjust to avoid potentialTypeErroror incorrect IP extraction. - The logic to build
WifiInterface/WifiInterfaceStatusobjects (state, ssid, signal, IP, MAC) is duplicated acrossget_wifi_interfaces,get_interface_status, andget_interface_connection_status; consider refactoring this into a shared helper to keep the behavior consistent and make future changes less error‑prone.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In the NetworkManagerWifi methods where you build `ip_address` from `IPv4Config.address_data`, you're indexing `address_data[0]["address"][1]`; this assumes `"address"` is an indexable sequence – double‑check the actual structure (often it's already a string) and adjust to avoid potential `TypeError` or incorrect IP extraction.
- The logic to build `WifiInterface`/`WifiInterfaceStatus` objects (state, ssid, signal, IP, MAC) is duplicated across `get_wifi_interfaces`, `get_interface_status`, and `get_interface_connection_status`; consider refactoring this into a shared helper to keep the behavior consistent and make future changes less error‑prone.
## Individual Comments
### Comment 1
<location> `core/services/wifi/wifi_handlers/networkmanager/networkmanager.py:394-402` </location>
<code_context>
+ detail=f"Interface '{interface_name}' not found. Multi-interface support requires NetworkManager.",
+ )
+ status_data = await manager.status()
+ return WifiInterfaceStatus(
+ interface="wlan0",
+ state=status_data.state or "unknown",
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Avoid hardcoding key management to WPA-PSK in interface status
`key_mgmt` is always reported as "WPA-PSK", even for open or enterprise networks. This misrepresents the actual security configuration to v2 API consumers. Where possible, derive `key_mgmt` from the active connection/AP; otherwise leave it unset or expose a more accurate representation than a hardcoded string.
</issue_to_address>
### Comment 2
<location> `core/frontend/src/components/wifi/WifiTrayMenu.vue:93-94` </location>
<code_context>
+ wifi_interfaces(): WifiInterface[] {
+ return wifi.wifi_interfaces
+ },
+ has_multiple_interfaces(): boolean {
+ return this.wifi_interfaces.length > 0
+ },
wifi_icon(): string {
</code_context>
<issue_to_address>
**nitpick:** Computed name `has_multiple_interfaces` does not match its current behavior
This returns `true` whenever there is at least one interface, so the multi-interface tray UI is used even for a single interface. Either rename the computed to reflect “v2 UI available” (or similar), or change the condition to `this.wifi_interfaces.length > 1` if it should only be `true` when there are actually multiple interfaces. Aligning the name and condition will make the behavior clearer.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| return WifiInterfaceStatus( | ||
| interface=interface_name, | ||
| state="connected", | ||
| ssid=ssid_bytes.decode("utf-8"), | ||
| bssid=await ap.hw_address, | ||
| ip_address=ip_address, | ||
| signal_strength=await ap.strength, | ||
| frequency=await ap.frequency, | ||
| key_mgmt="WPA-PSK", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (bug_risk): Avoid hardcoding key management to WPA-PSK in interface status
key_mgmt is always reported as "WPA-PSK", even for open or enterprise networks. This misrepresents the actual security configuration to v2 API consumers. Where possible, derive key_mgmt from the active connection/AP; otherwise leave it unset or expose a more accurate representation than a hardcoded string.
| has_multiple_interfaces(): boolean { | ||
| return this.wifi_interfaces.length > 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: Computed name has_multiple_interfaces does not match its current behavior
This returns true whenever there is at least one interface, so the multi-interface tray UI is used even for a single interface. Either rename the computed to reflect “v2 UI available” (or similar), or change the condition to this.wifi_interfaces.length > 1 if it should only be true when there are actually multiple interfaces. Aligning the name and condition will make the behavior clearer.
Summary by Sourcery
Add multi-interface WiFi support across backend and frontend while preserving backward-compatible single-interface behavior.
New Features:
Enhancements: