Improve Wi-Fi recovery and reconnect handling
This commit is contained in:
@@ -13,7 +13,10 @@ from .windows import (
|
||||
ensure_hardware_mac,
|
||||
get_wifi_status,
|
||||
randomize_mac,
|
||||
request_wifi_scan,
|
||||
restart_adapter,
|
||||
set_interface_enabled,
|
||||
set_wlan_autoconfig,
|
||||
test_connectivity,
|
||||
wait_for_ssid,
|
||||
)
|
||||
@@ -149,9 +152,24 @@ class WifiBackgroundService:
|
||||
self.state.last_mac_refresh_monotonic = time.monotonic()
|
||||
self.logger.info("Randomized MAC to %s", mac_address)
|
||||
|
||||
disconnect_wifi(interface_name)
|
||||
restart_adapter(interface_name)
|
||||
self._prepare_interface_for_connect(interface_name)
|
||||
|
||||
try:
|
||||
disconnect_wifi(interface_name)
|
||||
except WifiCommandError as exc:
|
||||
self.logger.warning("Wi-Fi disconnect before recovery failed, continuing: %s", exc)
|
||||
|
||||
try:
|
||||
self.logger.info("Restarting Wi-Fi adapter %s", interface_name)
|
||||
restart_adapter(interface_name)
|
||||
except WifiCommandError as exc:
|
||||
self.logger.warning(
|
||||
"Adapter restart failed, continuing with direct reconnect attempt: %s",
|
||||
exc,
|
||||
)
|
||||
|
||||
time.sleep(self.config.monitor.reconnect_wait_seconds)
|
||||
self.logger.info("Reconnecting to %s after recovery actions", network.ssid)
|
||||
connect_wifi(
|
||||
network.profile_name,
|
||||
interface_name,
|
||||
@@ -166,7 +184,9 @@ class WifiBackgroundService:
|
||||
self.state.last_recovery_monotonic = time.monotonic()
|
||||
|
||||
if not status.is_connected or status.ssid != network.ssid:
|
||||
raise WifiCommandError(f"Failed to reconnect to {network.ssid}")
|
||||
raise WifiCommandError(
|
||||
f"Failed to reconnect to {network.ssid}; final status: {self._format_status(status)}",
|
||||
)
|
||||
|
||||
self.logger.info("Reconnected to %s on interface %s", status.ssid, interface_name)
|
||||
|
||||
@@ -186,13 +206,14 @@ class WifiBackgroundService:
|
||||
network.ssid,
|
||||
interface_name,
|
||||
)
|
||||
self.state.last_connect_attempt_monotonic = time.monotonic()
|
||||
self._prepare_interface_for_connect(interface_name)
|
||||
connect_wifi(
|
||||
network.profile_name,
|
||||
interface_name,
|
||||
ssid=network.ssid,
|
||||
auto_create_open_profile=network.auto_create_open_profile,
|
||||
)
|
||||
self.state.last_connect_attempt_monotonic = time.monotonic()
|
||||
|
||||
new_status = wait_for_ssid(
|
||||
network.ssid,
|
||||
@@ -200,7 +221,11 @@ class WifiBackgroundService:
|
||||
interface_name,
|
||||
)
|
||||
if not new_status.is_connected or new_status.ssid != network.ssid:
|
||||
self.logger.warning("Direct Wi-Fi connection to %s did not succeed", network.ssid)
|
||||
self.logger.warning(
|
||||
"Direct Wi-Fi connection to %s did not succeed; final status: %s",
|
||||
network.ssid,
|
||||
self._format_status(new_status),
|
||||
)
|
||||
return False
|
||||
|
||||
self.logger.info("Connected to %s without adapter reset", network.ssid)
|
||||
@@ -249,3 +274,18 @@ class WifiBackgroundService:
|
||||
return (
|
||||
now - self.state.last_connect_attempt_monotonic
|
||||
) >= self.config.monitor.connect_retry_cooldown_seconds
|
||||
|
||||
def _prepare_interface_for_connect(self, interface_name: str) -> None:
|
||||
for action_name, action in (
|
||||
("enable interface", lambda: set_interface_enabled(interface_name, True)),
|
||||
("enable WLAN autoconfig", lambda: set_wlan_autoconfig(interface_name, True)),
|
||||
("scan Wi-Fi networks", lambda: request_wifi_scan(interface_name)),
|
||||
):
|
||||
try:
|
||||
action()
|
||||
except WifiCommandError as exc:
|
||||
self.logger.warning("Could not %s for %s: %s", action_name, interface_name, exc)
|
||||
|
||||
@staticmethod
|
||||
def _format_status(status) -> str:
|
||||
return f"interface={status.interface_name} state={status.state} ssid={status.ssid}"
|
||||
|
||||
@@ -92,6 +92,23 @@ def restart_adapter(interface_name: str) -> None:
|
||||
run_command(["netsh", "interface", "set", "interface", interface_name, "enable"])
|
||||
|
||||
|
||||
def set_interface_enabled(interface_name: str, enabled: bool = True) -> None:
|
||||
state = "enable" if enabled else "disable"
|
||||
run_command(["netsh", "interface", "set", "interface", interface_name, state])
|
||||
|
||||
|
||||
def set_wlan_autoconfig(interface_name: str, enabled: bool = True) -> None:
|
||||
state = "yes" if enabled else "no"
|
||||
run_command(["netsh", "wlan", "set", "autoconfig", f"enabled={state}", f"interface={interface_name}"])
|
||||
|
||||
|
||||
def request_wifi_scan(interface_name: str | None = None) -> None:
|
||||
command = ["netsh", "wlan", "show", "networks"]
|
||||
if interface_name:
|
||||
command.append(f"interface={interface_name}")
|
||||
run_command(command)
|
||||
|
||||
|
||||
def disconnect_wifi(interface_name: str | None = None) -> None:
|
||||
command = ["netsh", "wlan", "disconnect"]
|
||||
if interface_name:
|
||||
@@ -106,13 +123,9 @@ def connect_wifi(
|
||||
ssid: str | None = None,
|
||||
auto_create_open_profile: bool = False,
|
||||
) -> None:
|
||||
command = ["netsh", "wlan", "connect", f"name={profile_name}"]
|
||||
if ssid:
|
||||
command.append(f"ssid={ssid}")
|
||||
if interface_name:
|
||||
command.append(f"interface={interface_name}")
|
||||
commands = _build_connect_commands(profile_name, interface_name, ssid)
|
||||
try:
|
||||
run_command(command)
|
||||
_run_first_successful_command(commands)
|
||||
except WifiCommandError as exc:
|
||||
if auto_create_open_profile and _is_missing_profile_error(str(exc), profile_name):
|
||||
create_open_wifi_profile(
|
||||
@@ -120,7 +133,7 @@ def connect_wifi(
|
||||
ssid=ssid or profile_name,
|
||||
interface_name=interface_name,
|
||||
)
|
||||
run_command(command)
|
||||
_run_first_successful_command(commands)
|
||||
return
|
||||
if _is_missing_profile_error(str(exc), profile_name):
|
||||
available_profiles = list_wifi_profiles(interface_name)
|
||||
@@ -133,6 +146,40 @@ def connect_wifi(
|
||||
raise
|
||||
|
||||
|
||||
def _build_connect_commands(
|
||||
profile_name: str,
|
||||
interface_name: str | None,
|
||||
ssid: str | None,
|
||||
) -> list[list[str]]:
|
||||
variants: list[list[str]] = []
|
||||
|
||||
def add_variant(include_ssid: bool, include_interface: bool) -> None:
|
||||
command = ["netsh", "wlan", "connect", f"name={profile_name}"]
|
||||
if include_ssid and ssid:
|
||||
command.append(f"ssid={ssid}")
|
||||
if include_interface and interface_name:
|
||||
command.append(f"interface={interface_name}")
|
||||
if command not in variants:
|
||||
variants.append(command)
|
||||
|
||||
add_variant(include_ssid=True, include_interface=True)
|
||||
add_variant(include_ssid=False, include_interface=True)
|
||||
add_variant(include_ssid=True, include_interface=False)
|
||||
add_variant(include_ssid=False, include_interface=False)
|
||||
return variants
|
||||
|
||||
|
||||
def _run_first_successful_command(commands: list[list[str]]) -> None:
|
||||
errors: list[str] = []
|
||||
for command in commands:
|
||||
try:
|
||||
run_command(command)
|
||||
return
|
||||
except WifiCommandError as exc:
|
||||
errors.append(f"{' '.join(command)} -> {exc}")
|
||||
raise WifiCommandError("; ".join(errors))
|
||||
|
||||
|
||||
def list_wifi_profiles(interface_name: str | None = None) -> list[str]:
|
||||
command = ["netsh", "wlan", "show", "profiles"]
|
||||
if interface_name:
|
||||
|
||||
BIN
winsw/WinSW.NET461.exe
Normal file
BIN
winsw/WinSW.NET461.exe
Normal file
Binary file not shown.
Reference in New Issue
Block a user