You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
Add Hotspot configuration
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "Hotspot",
|
||||
"publisher": "MicroPythonOS",
|
||||
"short_description": "Configure Wi-Fi hotspot settings.",
|
||||
"long_description": "Configure and toggle the device Wi-Fi hotspot, including SSID, security, and network options.",
|
||||
"icon_url": "https://apps.micropythonos.com/apps/com.micropythonos.hotspot/icons/com.micropythonos.hotspot_0.1.0_64x64.png",
|
||||
"download_url": "https://apps.micropythonos.com/apps/com.micropythonos.hotspot/mpks/com.micropythonos.hotspot_0.1.0.mpk",
|
||||
"fullname": "com.micropythonos.hotspot",
|
||||
"version": "0.1.0",
|
||||
"category": "networking",
|
||||
"activities": [
|
||||
{
|
||||
"entrypoint": "assets/hotspot.py",
|
||||
"classname": "Hotspot",
|
||||
"intent_filters": [
|
||||
{
|
||||
"action": "main",
|
||||
"category": "launcher"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import lvgl as lv
|
||||
|
||||
from mpos import Activity, Intent, SettingsActivity, SharedPreferences, WifiService
|
||||
|
||||
|
||||
class Hotspot(SettingsActivity):
|
||||
"""
|
||||
Hotspot configuration app.
|
||||
|
||||
Uses SettingsActivity to render and edit hotspot preferences stored under
|
||||
com.micropythonos.system.hotspot.
|
||||
"""
|
||||
|
||||
DEFAULTS = {
|
||||
"enabled": False,
|
||||
"ssid": "MicroPythonOS",
|
||||
"password": "",
|
||||
"channel": 1,
|
||||
"hidden": False,
|
||||
"max_clients": 4,
|
||||
"authmode": None,
|
||||
"ip": "192.168.4.1",
|
||||
"netmask": "255.255.255.0",
|
||||
"gateway": "192.168.4.1",
|
||||
"dns": "8.8.8.8",
|
||||
}
|
||||
|
||||
def getIntent(self):
|
||||
prefs = SharedPreferences("com.micropythonos.system.hotspot", defaults=self.DEFAULTS)
|
||||
intent = Intent()
|
||||
intent.putExtra("prefs", prefs)
|
||||
intent.putExtra(
|
||||
"settings",
|
||||
[
|
||||
{
|
||||
"title": "Hotspot Enabled",
|
||||
"key": "enabled",
|
||||
"ui": "radiobuttons",
|
||||
"ui_options": [("On", "True"), ("Off", "False")],
|
||||
"changed_callback": self.toggle_hotspot,
|
||||
},
|
||||
{
|
||||
"title": "Network Name (SSID)",
|
||||
"key": "ssid",
|
||||
"placeholder": "Hotspot SSID",
|
||||
},
|
||||
{
|
||||
"title": "Password",
|
||||
"key": "password",
|
||||
"placeholder": "Leave empty for open network",
|
||||
},
|
||||
{
|
||||
"title": "Channel",
|
||||
"key": "channel",
|
||||
"placeholder": "Wi-Fi channel, e.g. 1",
|
||||
},
|
||||
{
|
||||
"title": "Hidden Network",
|
||||
"key": "hidden",
|
||||
"ui": "radiobuttons",
|
||||
"ui_options": [("Visible", "False"), ("Hidden", "True")],
|
||||
"changed_callback": self.toggle_hotspot,
|
||||
},
|
||||
{
|
||||
"title": "Max Clients",
|
||||
"key": "max_clients",
|
||||
"placeholder": "Max connections, e.g. 4",
|
||||
},
|
||||
{
|
||||
"title": "Auth Mode",
|
||||
"key": "authmode",
|
||||
"ui": "dropdown",
|
||||
"ui_options": [
|
||||
("Auto", None),
|
||||
("Open", "open"),
|
||||
("WPA", "wpa"),
|
||||
("WPA2", "wpa2"),
|
||||
("WPA/WPA2", "wpa_wpa2"),
|
||||
],
|
||||
"changed_callback": self.toggle_hotspot,
|
||||
},
|
||||
{
|
||||
"title": "IP Address",
|
||||
"key": "ip",
|
||||
"placeholder": "Hotspot IP, e.g. 192.168.4.1",
|
||||
},
|
||||
{
|
||||
"title": "Netmask",
|
||||
"key": "netmask",
|
||||
"placeholder": "Netmask, e.g. 255.255.255.0",
|
||||
},
|
||||
{
|
||||
"title": "Gateway",
|
||||
"key": "gateway",
|
||||
"placeholder": "Gateway, e.g. 192.168.4.1",
|
||||
},
|
||||
{
|
||||
"title": "DNS",
|
||||
"key": "dns",
|
||||
"placeholder": "DNS, e.g. 8.8.8.8",
|
||||
},
|
||||
],
|
||||
)
|
||||
return intent
|
||||
|
||||
def toggle_hotspot(self, new_value):
|
||||
enabled_value = self.prefs.get_string("enabled", "False")
|
||||
should_enable = str(enabled_value).lower() in ("true", "1", "yes", "on")
|
||||
if should_enable:
|
||||
WifiService.enable_hotspot()
|
||||
else:
|
||||
WifiService.disable_hotspot()
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
@@ -12,6 +12,12 @@ class LaunchWiFi(Activity):
|
||||
AppManager.start_app("com.micropythonos.wifi")
|
||||
|
||||
|
||||
class LaunchHotspot(Activity):
|
||||
|
||||
def onCreate(self):
|
||||
AppManager.start_app("com.micropythonos.hotspot")
|
||||
|
||||
|
||||
class Settings(SettingsActivity):
|
||||
|
||||
"""Override getIntent to provide prefs and settings via Intent extras"""
|
||||
@@ -46,6 +52,7 @@ class Settings(SettingsActivity):
|
||||
intent.putExtra("prefs", SharedPreferences("com.micropythonos.settings"))
|
||||
intent.putExtra("settings", [
|
||||
{"title": "Wi-Fi", "key": "wifi_settings", "ui": "activity", "activity_class": LaunchWiFi},
|
||||
{"title": "Hotspot", "key": "hotspot_settings", "ui": "activity", "activity_class": LaunchHotspot},
|
||||
# Basic settings, alphabetically:
|
||||
{"title": "Light/Dark Theme", "key": "theme_light_dark", "ui": "radiobuttons", "ui_options": [("Light", "light"), ("Dark", "dark")], "changed_callback": self.theme_changed},
|
||||
{"title": "Theme Color", "key": "theme_primary_color", "placeholder": "HTML hex color, like: EC048C", "ui": "dropdown", "ui_options": theme_colors, "changed_callback": self.theme_changed, "default_value": AppearanceManager.DEFAULT_PRIMARY_COLOR},
|
||||
|
||||
@@ -46,6 +46,129 @@ class WifiService:
|
||||
# Desktop mode: simulated connected SSID (None = not connected)
|
||||
_desktop_connected_ssid = None
|
||||
|
||||
# Hotspot state tracking
|
||||
hotspot_enabled = False
|
||||
_temp_disable_state = None
|
||||
_needs_hotspot_restore = False
|
||||
|
||||
@staticmethod
|
||||
def _get_hotspot_config():
|
||||
prefs = mpos.config.SharedPreferences("com.micropythonos.system.hotspot")
|
||||
return {
|
||||
"enabled": prefs.get_bool("enabled", False),
|
||||
"ssid": prefs.get_string("ssid", "MicroPythonOS"),
|
||||
"password": prefs.get_string("password", ""),
|
||||
"channel": prefs.get_int("channel", 1),
|
||||
"hidden": prefs.get_bool("hidden", False),
|
||||
"max_clients": prefs.get_int("max_clients", 4),
|
||||
"authmode": prefs.get_string("authmode", None),
|
||||
"ip": prefs.get_string("ip", "192.168.4.1"),
|
||||
"netmask": prefs.get_string("netmask", "255.255.255.0"),
|
||||
"gateway": prefs.get_string("gateway", "192.168.4.1"),
|
||||
"dns": prefs.get_string("dns", "8.8.8.8"),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _resolve_hotspot_authmode(net, password, authmode_value):
|
||||
if authmode_value is None:
|
||||
if password:
|
||||
return net.AUTH_WPA_WPA2_PSK
|
||||
return net.AUTH_OPEN
|
||||
if isinstance(authmode_value, int):
|
||||
return authmode_value
|
||||
if isinstance(authmode_value, str):
|
||||
authmode_key = authmode_value.lower().strip()
|
||||
mapping = {
|
||||
"open": net.AUTH_OPEN,
|
||||
"wpa": net.AUTH_WPA_PSK,
|
||||
"wpa2": net.AUTH_WPA2_PSK,
|
||||
"wpa_wpa2": net.AUTH_WPA_WPA2_PSK,
|
||||
"wpa-wpa2": net.AUTH_WPA_WPA2_PSK,
|
||||
}
|
||||
return mapping.get(authmode_key, net.AUTH_WPA_WPA2_PSK)
|
||||
return net.AUTH_WPA_WPA2_PSK
|
||||
|
||||
@staticmethod
|
||||
def enable_hotspot(network_module=None):
|
||||
if WifiService.wifi_busy:
|
||||
print("WifiService: Cannot enable hotspot, WiFi is busy")
|
||||
return False
|
||||
|
||||
if not HAS_NETWORK_MODULE and network_module is None:
|
||||
WifiService.hotspot_enabled = True
|
||||
print("WifiService: Desktop mode, hotspot enabled (simulated)")
|
||||
return True
|
||||
|
||||
net = network_module if network_module else network
|
||||
config = WifiService._get_hotspot_config()
|
||||
|
||||
try:
|
||||
sta = net.WLAN(net.STA_IF)
|
||||
if sta.active() or sta.isconnected():
|
||||
sta.disconnect()
|
||||
sta.active(False)
|
||||
|
||||
ap = net.WLAN(net.AP_IF)
|
||||
ap.active(True)
|
||||
|
||||
authmode = WifiService._resolve_hotspot_authmode(
|
||||
net, config.get("password"), config.get("authmode")
|
||||
)
|
||||
|
||||
ap_config = {
|
||||
"essid": config.get("ssid"),
|
||||
"channel": config.get("channel"),
|
||||
"hidden": config.get("hidden"),
|
||||
"max_clients": config.get("max_clients"),
|
||||
"authmode": authmode,
|
||||
}
|
||||
if config.get("password"):
|
||||
ap_config["password"] = config.get("password")
|
||||
|
||||
ap.config(**ap_config)
|
||||
ap.ifconfig(
|
||||
(
|
||||
config.get("ip"),
|
||||
config.get("netmask"),
|
||||
config.get("gateway"),
|
||||
config.get("dns"),
|
||||
)
|
||||
)
|
||||
|
||||
WifiService.hotspot_enabled = True
|
||||
print("WifiService: Hotspot enabled")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"WifiService: Failed to enable hotspot: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def disable_hotspot(network_module=None):
|
||||
if not HAS_NETWORK_MODULE and network_module is None:
|
||||
WifiService.hotspot_enabled = False
|
||||
print("WifiService: Desktop mode, hotspot disabled (simulated)")
|
||||
return
|
||||
|
||||
try:
|
||||
net = network_module if network_module else network
|
||||
ap = net.WLAN(net.AP_IF)
|
||||
ap.active(False)
|
||||
WifiService.hotspot_enabled = False
|
||||
print("WifiService: Hotspot disabled")
|
||||
except Exception:
|
||||
WifiService.hotspot_enabled = False
|
||||
|
||||
@staticmethod
|
||||
def is_hotspot_enabled(network_module=None):
|
||||
if not HAS_NETWORK_MODULE and network_module is None:
|
||||
return WifiService.hotspot_enabled
|
||||
try:
|
||||
net = network_module if network_module else network
|
||||
ap = net.WLAN(net.AP_IF)
|
||||
return ap.active()
|
||||
except Exception:
|
||||
return WifiService.hotspot_enabled
|
||||
|
||||
@staticmethod
|
||||
def connect(network_module=None, time_module=None):
|
||||
"""
|
||||
@@ -141,7 +264,16 @@ class WifiService:
|
||||
|
||||
net = network_module if network_module else network
|
||||
|
||||
def _restore_hotspot_if_needed():
|
||||
if WifiService._needs_hotspot_restore:
|
||||
WifiService._needs_hotspot_restore = False
|
||||
WifiService.enable_hotspot(network_module=network_module)
|
||||
|
||||
try:
|
||||
if WifiService.is_hotspot_enabled(network_module=network_module):
|
||||
WifiService._needs_hotspot_restore = True
|
||||
WifiService.disable_hotspot(network_module=network_module)
|
||||
|
||||
wlan = net.WLAN(net.STA_IF)
|
||||
wlan.connect(ssid, password)
|
||||
|
||||
@@ -156,21 +288,25 @@ class WifiService:
|
||||
except Exception as e:
|
||||
print(f"WifiService: Could not sync time: {e}")
|
||||
|
||||
WifiService._needs_hotspot_restore = False
|
||||
return True
|
||||
|
||||
elif not wlan.active():
|
||||
# WiFi was disabled during connection attempt
|
||||
print("WifiService: WiFi disabled during connection, aborting")
|
||||
_restore_hotspot_if_needed()
|
||||
return False
|
||||
|
||||
print(f"WifiService: Waiting for connection, attempt {i+1}/10")
|
||||
time_mod.sleep(1)
|
||||
|
||||
print(f"WifiService: Connection timeout for '{ssid}'")
|
||||
_restore_hotspot_if_needed()
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"WifiService: Connection error: {e}")
|
||||
_restore_hotspot_if_needed()
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@@ -187,21 +323,37 @@ class WifiService:
|
||||
"""
|
||||
print("WifiService: Auto-connect thread starting")
|
||||
|
||||
hotspot_config = WifiService._get_hotspot_config()
|
||||
if hotspot_config.get("enabled"):
|
||||
print("WifiService: Hotspot enabled, skipping STA auto-connect")
|
||||
WifiService.enable_hotspot(network_module=network_module)
|
||||
return
|
||||
if WifiService.is_hotspot_enabled(network_module=network_module):
|
||||
WifiService._needs_hotspot_restore = True
|
||||
WifiService.disable_hotspot(network_module=network_module)
|
||||
|
||||
# Load saved access points from config
|
||||
WifiService.access_points = mpos.config.SharedPreferences(
|
||||
"com.micropythonos.system.wifiservice"
|
||||
).get_dict("access_points")
|
||||
|
||||
if not len(WifiService.access_points):
|
||||
if WifiService._needs_hotspot_restore:
|
||||
WifiService._needs_hotspot_restore = False
|
||||
WifiService.enable_hotspot(network_module=network_module)
|
||||
print("WifiService: No access points configured, exiting")
|
||||
return
|
||||
|
||||
# Check if WiFi is busy (e.g., WiFi app is scanning)
|
||||
if WifiService.wifi_busy:
|
||||
if WifiService._needs_hotspot_restore:
|
||||
WifiService._needs_hotspot_restore = False
|
||||
WifiService.enable_hotspot(network_module=network_module)
|
||||
print("WifiService: WiFi busy, auto-connect aborted")
|
||||
return
|
||||
|
||||
WifiService.wifi_busy = True
|
||||
connected = False
|
||||
|
||||
try:
|
||||
if not HAS_NETWORK_MODULE and network_module is None:
|
||||
@@ -209,6 +361,7 @@ class WifiService:
|
||||
print("WifiService: Desktop mode, simulating connection...")
|
||||
time_mod = time_module if time_module else time
|
||||
time_mod.sleep(2)
|
||||
connected = True
|
||||
print("WifiService: Simulated connection complete")
|
||||
else:
|
||||
# Attempt to connect to saved networks
|
||||
@@ -216,6 +369,7 @@ class WifiService:
|
||||
network_module=network_module,
|
||||
time_module=time_module,
|
||||
):
|
||||
connected = True
|
||||
print("WifiService: Auto-connect successful")
|
||||
else:
|
||||
print("WifiService: Auto-connect failed")
|
||||
@@ -231,6 +385,9 @@ class WifiService:
|
||||
print("WifiService: WiFi disabled to conserve power")
|
||||
|
||||
finally:
|
||||
if not connected and WifiService._needs_hotspot_restore:
|
||||
WifiService._needs_hotspot_restore = False
|
||||
WifiService.enable_hotspot(network_module=network_module)
|
||||
WifiService.wifi_busy = False
|
||||
print("WifiService: Auto-connect thread finished")
|
||||
|
||||
@@ -254,16 +411,23 @@ class WifiService:
|
||||
if WifiService.wifi_busy:
|
||||
raise RuntimeError("Cannot disable WiFi: WifiService is already busy")
|
||||
|
||||
# Check actual connection status BEFORE setting wifi_busy
|
||||
was_connected = False
|
||||
hotspot_was_enabled = False
|
||||
if HAS_NETWORK_MODULE or network_module:
|
||||
try:
|
||||
net = network_module if network_module else network
|
||||
wlan = net.WLAN(net.STA_IF)
|
||||
was_connected = wlan.isconnected()
|
||||
ap = net.WLAN(net.AP_IF)
|
||||
hotspot_was_enabled = ap.active()
|
||||
except Exception as e:
|
||||
print(f"WifiService: Error checking connection: {e}")
|
||||
|
||||
WifiService._temp_disable_state = {
|
||||
"was_connected": was_connected,
|
||||
"hotspot_was_enabled": hotspot_was_enabled,
|
||||
}
|
||||
|
||||
# Now set busy flag and disconnect
|
||||
WifiService.wifi_busy = True
|
||||
WifiService.disconnect(network_module=network_module)
|
||||
@@ -283,6 +447,12 @@ class WifiService:
|
||||
"""
|
||||
WifiService.wifi_busy = False
|
||||
|
||||
state = WifiService._temp_disable_state or {}
|
||||
WifiService._temp_disable_state = None
|
||||
|
||||
if state.get("hotspot_was_enabled"):
|
||||
WifiService.enable_hotspot(network_module=network_module)
|
||||
|
||||
# Only reconnect if WiFi was connected before we disabled it
|
||||
if was_connected:
|
||||
try:
|
||||
@@ -313,9 +483,11 @@ class WifiService:
|
||||
if not HAS_NETWORK_MODULE and network_module is None:
|
||||
return True
|
||||
|
||||
# Check actual connection status
|
||||
try:
|
||||
net = network_module if network_module else network
|
||||
if WifiService.is_hotspot_enabled(network_module=network_module):
|
||||
ap = net.WLAN(net.AP_IF)
|
||||
return ap.active()
|
||||
wlan = net.WLAN(net.STA_IF)
|
||||
return wlan.isconnected()
|
||||
except Exception as e:
|
||||
@@ -333,9 +505,11 @@ class WifiService:
|
||||
if not HAS_NETWORK_MODULE and network_module is None:
|
||||
return "123.456.789.000"
|
||||
|
||||
# Check actual connection status
|
||||
try:
|
||||
net = network_module if network_module else network
|
||||
if WifiService.is_hotspot_enabled(network_module=network_module):
|
||||
ap = net.WLAN(net.AP_IF)
|
||||
return ap.ifconfig()[0]
|
||||
wlan = net.WLAN(net.STA_IF)
|
||||
return wlan.ipconfig("addr4")
|
||||
except Exception as e:
|
||||
@@ -352,9 +526,11 @@ class WifiService:
|
||||
if not HAS_NETWORK_MODULE and network_module is None:
|
||||
return "000.123.456.789"
|
||||
|
||||
# Check actual connection status
|
||||
try:
|
||||
net = network_module if network_module else network
|
||||
if WifiService.is_hotspot_enabled(network_module=network_module):
|
||||
ap = net.WLAN(net.AP_IF)
|
||||
return ap.ifconfig()[2]
|
||||
wlan = net.WLAN(net.STA_IF)
|
||||
return wlan.ipconfig("gw4")
|
||||
except Exception as e:
|
||||
@@ -378,6 +554,9 @@ class WifiService:
|
||||
wlan = net.WLAN(net.STA_IF)
|
||||
wlan.disconnect()
|
||||
wlan.active(False)
|
||||
ap = net.WLAN(net.AP_IF)
|
||||
ap.active(False)
|
||||
WifiService.hotspot_enabled = False
|
||||
print("WifiService: Disconnected and WiFi disabled")
|
||||
except Exception as e:
|
||||
#print(f"WifiService: Error disconnecting: {e}") # probably "Wifi Not Started" so harmless
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# Partition table for Fri3D Camp 2024 Badge with ESP-IDF OTA support using 16MB flash
|
||||
#
|
||||
# Also present in flash:
|
||||
# 0x0 images/bootloader.bin
|
||||
# 0x8000 images/partition-table.bin
|
||||
#
|
||||
# Notes:
|
||||
# - app partitions should be aligned at 0x10000 (64k block)
|
||||
# - otadata size should be 0x2000
|
||||
#
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
otadata, data, ota, 0x9000, 0x2000,
|
||||
nvs, data, nvs, 0xb000, 0x5000,
|
||||
ota_0,app,ota_0,0x20000,0x400000
|
||||
ota_1,app,ota_1,0x420000,0x400000
|
||||
launcher, app, ota_2, 0x820000, 0x100000,
|
||||
retro-core, app, ota_3, 0x930000, 0xd0000
|
||||
prboom-go, app, ota_4, 0xa00000, 0xe0000,
|
||||
vfs, data, fat, 0xae0000, 0x520000
|
||||
|
@@ -7,86 +7,17 @@ Tests ADC1/ADC2 detection, caching, WiFi coordination, and voltage calculations.
|
||||
import unittest
|
||||
import sys
|
||||
|
||||
# Allow importing shared test mocks
|
||||
sys.path.insert(0, "../tests")
|
||||
|
||||
from mocks import MockADC, MockMachineADC, MockWifiService
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, '../internal_filesystem')
|
||||
|
||||
# Mock modules before importing BatteryManager
|
||||
class MockADC:
|
||||
"""Mock ADC for testing."""
|
||||
ATTN_11DB = 3
|
||||
|
||||
def __init__(self, pin):
|
||||
self.pin = pin
|
||||
self._atten = None
|
||||
self._read_value = 2048 # Default mid-range value
|
||||
|
||||
def atten(self, value):
|
||||
self._atten = value
|
||||
|
||||
def read(self):
|
||||
return self._read_value
|
||||
|
||||
def set_read_value(self, value):
|
||||
"""Test helper to set ADC reading."""
|
||||
self._read_value = value
|
||||
|
||||
|
||||
class MockPin:
|
||||
"""Mock Pin for testing."""
|
||||
def __init__(self, pin_num):
|
||||
self.pin_num = pin_num
|
||||
|
||||
|
||||
class MockMachine:
|
||||
"""Mock machine module."""
|
||||
ADC = MockADC
|
||||
Pin = MockPin
|
||||
|
||||
|
||||
class MockWifiService:
|
||||
"""Mock WifiService for testing."""
|
||||
wifi_busy = False
|
||||
_connected = False
|
||||
_temporarily_disabled = False
|
||||
|
||||
@classmethod
|
||||
def is_connected(cls):
|
||||
return cls._connected
|
||||
|
||||
@classmethod
|
||||
def disconnect(cls):
|
||||
cls._connected = False
|
||||
|
||||
@classmethod
|
||||
def temporarily_disable(cls):
|
||||
"""Temporarily disable WiFi and return whether it was connected."""
|
||||
if cls.wifi_busy:
|
||||
raise RuntimeError("Cannot disable WiFi: WifiService is already busy")
|
||||
was_connected = cls._connected
|
||||
cls.wifi_busy = True
|
||||
cls._connected = False
|
||||
cls._temporarily_disabled = True
|
||||
return was_connected
|
||||
|
||||
@classmethod
|
||||
def temporarily_enable(cls, was_connected):
|
||||
"""Re-enable WiFi and reconnect if it was connected before."""
|
||||
cls.wifi_busy = False
|
||||
cls._temporarily_disabled = False
|
||||
if was_connected:
|
||||
cls._connected = True # Simulate reconnection
|
||||
|
||||
@classmethod
|
||||
def reset(cls):
|
||||
"""Test helper to reset state."""
|
||||
cls.wifi_busy = False
|
||||
cls._connected = False
|
||||
cls._temporarily_disabled = False
|
||||
|
||||
sys.path.insert(0, "../internal_filesystem")
|
||||
|
||||
# Inject mocks
|
||||
sys.modules['machine'] = MockMachine
|
||||
sys.modules['mpos.net.wifi_service'] = type('module', (), {'WifiService': MockWifiService})()
|
||||
sys.modules["machine"] = MockMachineADC
|
||||
sys.modules["mpos.net.wifi_service"] = type("module", (), {"WifiService": MockWifiService})()
|
||||
|
||||
# Now import BatteryManager
|
||||
from mpos.battery_manager import BatteryManager
|
||||
|
||||
@@ -1,31 +1,18 @@
|
||||
import unittest
|
||||
import sys
|
||||
|
||||
# Add parent directory to path so we can import network_test_helper
|
||||
# Add parent directory to path so we can import shared mocks/network_test_helper
|
||||
# When running from unittest.sh, we're in internal_filesystem/, so tests/ is ../tests/
|
||||
sys.path.insert(0, '../tests')
|
||||
sys.path.insert(0, "../tests")
|
||||
|
||||
from mocks import make_machine_timer_module, make_usocket_module
|
||||
|
||||
# Import our network test helpers
|
||||
from network_test_helper import MockNetwork, MockTimer, MockTime, MockRequests, MockSocket
|
||||
|
||||
# Mock machine module with Timer
|
||||
class MockMachine:
|
||||
"""Mock machine module."""
|
||||
Timer = MockTimer
|
||||
|
||||
# Mock usocket module
|
||||
class MockUsocket:
|
||||
"""Mock usocket module."""
|
||||
AF_INET = MockSocket.AF_INET
|
||||
SOCK_STREAM = MockSocket.SOCK_STREAM
|
||||
|
||||
@staticmethod
|
||||
def socket(af, sock_type):
|
||||
return MockSocket(af, sock_type)
|
||||
|
||||
# Inject mocks into sys.modules BEFORE importing connectivity_manager
|
||||
sys.modules['machine'] = MockMachine
|
||||
sys.modules['usocket'] = MockUsocket
|
||||
sys.modules["machine"] = make_machine_timer_module(MockTimer)
|
||||
sys.modules["usocket"] = make_usocket_module(MockSocket)
|
||||
|
||||
# Mock requests module
|
||||
mock_requests = MockRequests()
|
||||
|
||||
+20
-131
@@ -2,91 +2,17 @@
|
||||
import unittest
|
||||
import sys
|
||||
|
||||
# Allow importing shared test mocks
|
||||
sys.path.insert(0, "../tests")
|
||||
|
||||
# Mock hardware before importing SensorManager
|
||||
class MockI2C:
|
||||
"""Mock I2C bus for testing."""
|
||||
def __init__(self, bus_id, sda=None, scl=None):
|
||||
self.bus_id = bus_id
|
||||
self.sda = sda
|
||||
self.scl = scl
|
||||
self.memory = {} # addr -> {reg -> value}
|
||||
|
||||
def readfrom_mem(self, addr, reg, nbytes):
|
||||
"""Read from memory (simulates I2C read)."""
|
||||
if addr not in self.memory:
|
||||
raise OSError("I2C device not found")
|
||||
if reg not in self.memory[addr]:
|
||||
return bytes([0] * nbytes)
|
||||
return bytes(self.memory[addr][reg])
|
||||
|
||||
def writeto_mem(self, addr, reg, data):
|
||||
"""Write to memory (simulates I2C write)."""
|
||||
if addr not in self.memory:
|
||||
self.memory[addr] = {}
|
||||
self.memory[addr][reg] = list(data)
|
||||
|
||||
|
||||
class MockQMI8658:
|
||||
"""Mock QMI8658 IMU sensor."""
|
||||
def __init__(self, i2c_bus, address=0x6B, accel_scale=0b10, gyro_scale=0b100):
|
||||
self.i2c = i2c_bus
|
||||
self.address = address
|
||||
self.accel_scale = accel_scale
|
||||
self.gyro_scale = gyro_scale
|
||||
|
||||
@property
|
||||
def temperature(self):
|
||||
"""Return mock temperature."""
|
||||
return 25.5 # Mock temperature in °C
|
||||
|
||||
@property
|
||||
def acceleration(self):
|
||||
"""Return mock acceleration (in G)."""
|
||||
return (0.0, 0.0, 1.0) # At rest, Z-axis = 1G
|
||||
|
||||
@property
|
||||
def gyro(self):
|
||||
"""Return mock gyroscope (in deg/s)."""
|
||||
return (0.0, 0.0, 0.0) # Stationary
|
||||
|
||||
|
||||
class MockWsenIsds:
|
||||
"""Mock WSEN_ISDS IMU sensor."""
|
||||
def __init__(self, i2c, address=0x6B, acc_range="8g", acc_data_rate="104Hz",
|
||||
gyro_range="500dps", gyro_data_rate="104Hz"):
|
||||
self.i2c = i2c
|
||||
self.address = address
|
||||
self.acc_range = acc_range
|
||||
self.gyro_range = gyro_range
|
||||
self.acc_sensitivity = 0.244 # mg/digit for 8g
|
||||
self.gyro_sensitivity = 17.5 # mdps/digit for 500dps
|
||||
self.acc_offset_x = 0
|
||||
self.acc_offset_y = 0
|
||||
self.acc_offset_z = 0
|
||||
self.gyro_offset_x = 0
|
||||
self.gyro_offset_y = 0
|
||||
self.gyro_offset_z = 0
|
||||
|
||||
def get_chip_id(self):
|
||||
"""Return WHO_AM_I value."""
|
||||
return 0x6A
|
||||
|
||||
def _read_raw_accelerations(self):
|
||||
"""Return mock acceleration (in mg)."""
|
||||
return (0.0, 0.0, 1000.0) # At rest, Z-axis = 1000 mg
|
||||
|
||||
def read_angular_velocities(self):
|
||||
"""Return mock gyroscope (in mdps)."""
|
||||
return (0.0, 0.0, 0.0)
|
||||
|
||||
def acc_calibrate(self, samples=None):
|
||||
"""Mock calibration."""
|
||||
pass
|
||||
|
||||
def gyro_calibrate(self, samples=None):
|
||||
"""Mock calibration."""
|
||||
pass
|
||||
from mocks import (
|
||||
MockI2C,
|
||||
MockQMI8658,
|
||||
MockSharedPreferences,
|
||||
MockWsenIsds,
|
||||
make_config_module,
|
||||
make_machine_i2c_module,
|
||||
)
|
||||
|
||||
|
||||
# Mock constants from drivers
|
||||
@@ -96,57 +22,20 @@ _ACCELSCALE_RANGE_8G = 0b10
|
||||
_GYROSCALE_RANGE_256DPS = 0b100
|
||||
|
||||
|
||||
# Mock SharedPreferences to prevent loading real calibration
|
||||
class MockSharedPreferences:
|
||||
"""Mock SharedPreferences for testing."""
|
||||
def __init__(self, package, filename=None):
|
||||
self.package = package
|
||||
self.filename = filename
|
||||
self.data = {}
|
||||
|
||||
def get_list(self, key):
|
||||
"""Get list value."""
|
||||
return self.data.get(key)
|
||||
|
||||
def edit(self):
|
||||
"""Return editor."""
|
||||
return MockEditor(self.data)
|
||||
|
||||
class MockEditor:
|
||||
"""Mock SharedPreferences editor."""
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
|
||||
def put_list(self, key, value):
|
||||
"""Put list value."""
|
||||
self.data[key] = value
|
||||
return self
|
||||
|
||||
def commit(self):
|
||||
"""Commit changes."""
|
||||
pass
|
||||
|
||||
mock_config = type('module', (), {
|
||||
'SharedPreferences': MockSharedPreferences
|
||||
})()
|
||||
mock_config = make_config_module(MockSharedPreferences)
|
||||
|
||||
# Create mock modules
|
||||
mock_machine = type('module', (), {
|
||||
'I2C': MockI2C,
|
||||
'Pin': type('Pin', (), {})
|
||||
mock_machine = make_machine_i2c_module(MockI2C)
|
||||
|
||||
mock_qmi8658 = type("module", (), {
|
||||
"QMI8658": MockQMI8658,
|
||||
"_QMI8685_PARTID": _QMI8685_PARTID,
|
||||
"_REG_PARTID": _REG_PARTID,
|
||||
"_ACCELSCALE_RANGE_8G": _ACCELSCALE_RANGE_8G,
|
||||
"_GYROSCALE_RANGE_256DPS": _GYROSCALE_RANGE_256DPS,
|
||||
})()
|
||||
|
||||
mock_qmi8658 = type('module', (), {
|
||||
'QMI8658': MockQMI8658,
|
||||
'_QMI8685_PARTID': _QMI8685_PARTID,
|
||||
'_REG_PARTID': _REG_PARTID,
|
||||
'_ACCELSCALE_RANGE_8G': _ACCELSCALE_RANGE_8G,
|
||||
'_GYROSCALE_RANGE_256DPS': _GYROSCALE_RANGE_256DPS
|
||||
})()
|
||||
|
||||
mock_wsen_isds = type('module', (), {
|
||||
'Wsen_Isds': MockWsenIsds
|
||||
})()
|
||||
mock_wsen_isds = type("module", (), {"Wsen_Isds": MockWsenIsds})()
|
||||
|
||||
# Mock esp32 module
|
||||
def _mock_mcu_temperature(*args, **kwargs):
|
||||
|
||||
+358
-57
@@ -2,70 +2,20 @@ import unittest
|
||||
import sys
|
||||
|
||||
# Add tests directory to path for network_test_helper
|
||||
sys.path.insert(0, '../tests')
|
||||
sys.path.insert(0, "../tests")
|
||||
|
||||
# Import network test helpers
|
||||
from network_test_helper import MockNetwork, MockTime
|
||||
|
||||
# Mock config classes
|
||||
class MockSharedPreferences:
|
||||
"""Mock SharedPreferences for testing."""
|
||||
_all_data = {} # Class-level storage
|
||||
|
||||
def __init__(self, app_id):
|
||||
self.app_id = app_id
|
||||
if app_id not in MockSharedPreferences._all_data:
|
||||
MockSharedPreferences._all_data[app_id] = {}
|
||||
|
||||
def get_dict(self, key):
|
||||
return MockSharedPreferences._all_data.get(self.app_id, {}).get(key, {})
|
||||
|
||||
def edit(self):
|
||||
return MockEditor(self)
|
||||
|
||||
@classmethod
|
||||
def reset_all(cls):
|
||||
cls._all_data = {}
|
||||
|
||||
|
||||
class MockEditor:
|
||||
"""Mock editor for SharedPreferences."""
|
||||
|
||||
def __init__(self, prefs):
|
||||
self.prefs = prefs
|
||||
self.pending = {}
|
||||
|
||||
def put_dict(self, key, value):
|
||||
self.pending[key] = value
|
||||
|
||||
def commit(self):
|
||||
if self.prefs.app_id not in MockSharedPreferences._all_data:
|
||||
MockSharedPreferences._all_data[self.prefs.app_id] = {}
|
||||
MockSharedPreferences._all_data[self.prefs.app_id].update(self.pending)
|
||||
|
||||
|
||||
# Create mock mpos module
|
||||
class MockMpos:
|
||||
"""Mock mpos module with config and time."""
|
||||
|
||||
class config:
|
||||
@staticmethod
|
||||
def SharedPreferences(app_id):
|
||||
return MockSharedPreferences(app_id)
|
||||
|
||||
class time:
|
||||
@staticmethod
|
||||
def sync_time():
|
||||
pass # No-op for testing
|
||||
|
||||
from mocks import HotspotMockNetwork, MockMpos, MockSharedPreferences
|
||||
|
||||
# Inject mocks before importing WifiService
|
||||
sys.modules['mpos'] = MockMpos
|
||||
sys.modules['mpos.config'] = MockMpos.config
|
||||
sys.modules['mpos.time'] = MockMpos.time
|
||||
sys.modules["mpos"] = MockMpos
|
||||
sys.modules["mpos.config"] = MockMpos.config
|
||||
sys.modules["mpos.time"] = MockMpos.time
|
||||
|
||||
# Add path to wifi_service.py
|
||||
sys.path.append('lib/mpos/net')
|
||||
sys.path.append("lib/mpos/net")
|
||||
|
||||
# Import WifiService
|
||||
from wifi_service import WifiService
|
||||
@@ -331,7 +281,9 @@ class TestWifiServiceIsConnected(unittest.TestCase):
|
||||
|
||||
def test_is_connected_when_disconnected(self):
|
||||
"""Test is_connected returns False when WiFi is disconnected."""
|
||||
mock_network = MockNetwork(connected=False)
|
||||
mock_network = HotspotMockNetwork()
|
||||
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
||||
ap_wlan.active(False)
|
||||
|
||||
result = WifiService.is_connected(network_module=mock_network)
|
||||
|
||||
@@ -347,6 +299,17 @@ class TestWifiServiceIsConnected(unittest.TestCase):
|
||||
# Should return False even though connected
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_is_connected_when_hotspot_enabled(self):
|
||||
"""Test is_connected checks AP state when hotspot is enabled."""
|
||||
mock_network = HotspotMockNetwork()
|
||||
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
||||
ap_wlan.active(True)
|
||||
WifiService.hotspot_enabled = True
|
||||
|
||||
result = WifiService.is_connected(network_module=mock_network)
|
||||
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_is_connected_desktop_mode(self):
|
||||
"""Test is_connected in desktop mode."""
|
||||
result = WifiService.is_connected(network_module=None)
|
||||
@@ -424,6 +387,329 @@ class TestWifiServiceNetworkManagement(unittest.TestCase):
|
||||
self.assertEqual(len(saved), 0)
|
||||
|
||||
|
||||
class TestWifiServiceHotspot(unittest.TestCase):
|
||||
"""Test hotspot configuration and mode switching."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
MockSharedPreferences.reset_all()
|
||||
WifiService.hotspot_enabled = False
|
||||
WifiService.wifi_busy = False
|
||||
WifiService._needs_hotspot_restore = False
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after test."""
|
||||
WifiService.hotspot_enabled = False
|
||||
WifiService.wifi_busy = False
|
||||
WifiService._needs_hotspot_restore = False
|
||||
MockSharedPreferences.reset_all()
|
||||
|
||||
def test_enable_hotspot_applies_config(self):
|
||||
"""Test enable_hotspot reads config and configures AP."""
|
||||
prefs = MockSharedPreferences("com.micropythonos.system.hotspot")
|
||||
editor = prefs.edit()
|
||||
editor.put_bool("enabled", True)
|
||||
editor.put_string("ssid", "MyAP")
|
||||
editor.put_string("password", "ap-pass")
|
||||
editor.put_int("channel", 6)
|
||||
editor.put_bool("hidden", True)
|
||||
editor.put_int("max_clients", 3)
|
||||
editor.put_string("authmode", "wpa2")
|
||||
editor.put_string("ip", "192.168.4.2")
|
||||
editor.put_string("netmask", "255.255.255.0")
|
||||
editor.put_string("gateway", "192.168.4.1")
|
||||
editor.put_string("dns", "1.1.1.1")
|
||||
editor.commit()
|
||||
|
||||
mock_network = HotspotMockNetwork()
|
||||
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
||||
sta_wlan = mock_network.WLAN(mock_network.STA_IF)
|
||||
sta_wlan.active(True)
|
||||
sta_wlan._connected = True
|
||||
|
||||
result = WifiService.enable_hotspot(network_module=mock_network)
|
||||
|
||||
self.assertTrue(result)
|
||||
self.assertTrue(WifiService.hotspot_enabled)
|
||||
self.assertTrue(ap_wlan.active())
|
||||
self.assertFalse(sta_wlan.active())
|
||||
self.assertEqual(ap_wlan._config.get("essid"), "MyAP")
|
||||
self.assertEqual(ap_wlan._config.get("channel"), 6)
|
||||
self.assertTrue(ap_wlan._config.get("hidden"))
|
||||
self.assertEqual(ap_wlan._config.get("max_clients"), 3)
|
||||
self.assertEqual(ap_wlan._config.get("authmode"), mock_network.AUTH_WPA2_PSK)
|
||||
self.assertEqual(ap_wlan._config.get("password"), "ap-pass")
|
||||
self.assertEqual(
|
||||
ap_wlan.ifconfig(),
|
||||
("192.168.4.2", "255.255.255.0", "192.168.4.1", "1.1.1.1"),
|
||||
)
|
||||
|
||||
def test_enable_hotspot_respects_busy_flag(self):
|
||||
"""Test enable_hotspot returns False when WiFi is busy."""
|
||||
WifiService.wifi_busy = True
|
||||
mock_network = HotspotMockNetwork()
|
||||
|
||||
result = WifiService.enable_hotspot(network_module=mock_network)
|
||||
|
||||
self.assertFalse(result)
|
||||
self.assertFalse(WifiService.hotspot_enabled)
|
||||
|
||||
def test_disable_hotspot_deactivates_ap(self):
|
||||
"""Test disable_hotspot turns off AP and updates flag."""
|
||||
mock_network = HotspotMockNetwork()
|
||||
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
||||
ap_wlan.active(True)
|
||||
WifiService.hotspot_enabled = True
|
||||
|
||||
WifiService.disable_hotspot(network_module=mock_network)
|
||||
|
||||
self.assertFalse(ap_wlan.active())
|
||||
self.assertFalse(WifiService.hotspot_enabled)
|
||||
|
||||
def test_enable_hotspot_desktop_mode(self):
|
||||
"""Test enable_hotspot in desktop mode uses simulated flag."""
|
||||
result = WifiService.enable_hotspot(network_module=None)
|
||||
|
||||
self.assertTrue(result)
|
||||
self.assertTrue(WifiService.hotspot_enabled)
|
||||
|
||||
def test_disable_hotspot_desktop_mode(self):
|
||||
"""Test disable_hotspot in desktop mode uses simulated flag."""
|
||||
WifiService.hotspot_enabled = True
|
||||
|
||||
WifiService.disable_hotspot(network_module=None)
|
||||
|
||||
self.assertFalse(WifiService.hotspot_enabled)
|
||||
|
||||
def test_auto_connect_with_hotspot_enabled_prefers_ap_mode(self):
|
||||
"""Test auto_connect uses hotspot mode when enabled in config."""
|
||||
prefs = MockSharedPreferences("com.micropythonos.system.hotspot")
|
||||
editor = prefs.edit()
|
||||
editor.put_bool("enabled", True)
|
||||
editor.commit()
|
||||
|
||||
mock_network = HotspotMockNetwork()
|
||||
|
||||
WifiService.auto_connect(network_module=mock_network, time_module=MockTime())
|
||||
|
||||
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
||||
self.assertTrue(ap_wlan.active())
|
||||
self.assertTrue(WifiService.hotspot_enabled)
|
||||
|
||||
def test_attempt_connecting_temporarily_disables_hotspot(self):
|
||||
"""Test STA connect disables hotspot and leaves it off on success."""
|
||||
mock_network = HotspotMockNetwork()
|
||||
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
||||
ap_wlan.active(True)
|
||||
WifiService.hotspot_enabled = True
|
||||
|
||||
sta_wlan = mock_network.WLAN(mock_network.STA_IF)
|
||||
call_count = [0]
|
||||
|
||||
def mock_isconnected():
|
||||
call_count[0] += 1
|
||||
return call_count[0] >= 1
|
||||
|
||||
sta_wlan.isconnected = mock_isconnected
|
||||
|
||||
result = WifiService.attempt_connecting(
|
||||
"TestSSID",
|
||||
"pass",
|
||||
network_module=mock_network,
|
||||
time_module=MockTime(),
|
||||
)
|
||||
|
||||
self.assertTrue(result)
|
||||
self.assertFalse(WifiService.hotspot_enabled)
|
||||
self.assertFalse(ap_wlan.active())
|
||||
|
||||
def test_attempt_connecting_restores_hotspot_on_timeout(self):
|
||||
"""Test STA connect restores hotspot when connection times out."""
|
||||
mock_network = HotspotMockNetwork()
|
||||
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
||||
ap_wlan.active(True)
|
||||
WifiService.hotspot_enabled = True
|
||||
|
||||
sta_wlan = mock_network.WLAN(mock_network.STA_IF)
|
||||
|
||||
def mock_isconnected():
|
||||
return False
|
||||
|
||||
sta_wlan.isconnected = mock_isconnected
|
||||
|
||||
result = WifiService.attempt_connecting(
|
||||
"TestSSID",
|
||||
"pass",
|
||||
network_module=mock_network,
|
||||
time_module=MockTime(),
|
||||
)
|
||||
|
||||
self.assertFalse(result)
|
||||
self.assertTrue(WifiService.hotspot_enabled)
|
||||
self.assertTrue(ap_wlan.active())
|
||||
|
||||
def test_attempt_connecting_restores_hotspot_on_abort(self):
|
||||
"""Test STA connect restores hotspot if WiFi is disabled mid-try."""
|
||||
mock_network = HotspotMockNetwork()
|
||||
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
||||
ap_wlan.active(True)
|
||||
WifiService.hotspot_enabled = True
|
||||
|
||||
sta_wlan = mock_network.WLAN(mock_network.STA_IF)
|
||||
|
||||
def mock_isconnected():
|
||||
return False
|
||||
|
||||
def mock_active(state=None):
|
||||
if state is not None:
|
||||
sta_wlan._active = state
|
||||
return None
|
||||
return False
|
||||
|
||||
sta_wlan.isconnected = mock_isconnected
|
||||
sta_wlan.active = mock_active
|
||||
|
||||
result = WifiService.attempt_connecting(
|
||||
"TestSSID",
|
||||
"pass",
|
||||
network_module=mock_network,
|
||||
time_module=MockTime(),
|
||||
)
|
||||
|
||||
self.assertFalse(result)
|
||||
self.assertTrue(WifiService.hotspot_enabled)
|
||||
self.assertTrue(ap_wlan.active())
|
||||
|
||||
|
||||
class TestWifiServiceTemporaryDisable(unittest.TestCase):
|
||||
"""Test temporarily_disable/temporarily_enable behavior."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
WifiService.wifi_busy = False
|
||||
WifiService._temp_disable_state = None
|
||||
WifiService.hotspot_enabled = False
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after test."""
|
||||
WifiService.wifi_busy = False
|
||||
WifiService._temp_disable_state = None
|
||||
WifiService.hotspot_enabled = False
|
||||
|
||||
def test_temporarily_disable_raises_when_busy(self):
|
||||
"""Test temporarily_disable raises if wifi_busy is set."""
|
||||
WifiService.wifi_busy = True
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
WifiService.temporarily_disable(network_module=HotspotMockNetwork())
|
||||
|
||||
def test_temporarily_disable_disconnects_and_tracks_state(self):
|
||||
"""Test temporarily_disable stores state and disconnects."""
|
||||
mock_network = HotspotMockNetwork()
|
||||
sta_wlan = mock_network.WLAN(mock_network.STA_IF)
|
||||
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
||||
sta_wlan._connected = True
|
||||
ap_wlan.active(True)
|
||||
WifiService.hotspot_enabled = True
|
||||
|
||||
disconnect_called = [False]
|
||||
|
||||
def mock_disconnect(network_module=None):
|
||||
disconnect_called[0] = True
|
||||
|
||||
original_disconnect = WifiService.disconnect
|
||||
WifiService.disconnect = mock_disconnect
|
||||
try:
|
||||
was_connected = WifiService.temporarily_disable(network_module=mock_network)
|
||||
finally:
|
||||
WifiService.disconnect = original_disconnect
|
||||
|
||||
self.assertTrue(was_connected)
|
||||
self.assertTrue(WifiService.wifi_busy)
|
||||
self.assertEqual(
|
||||
WifiService._temp_disable_state,
|
||||
{"was_connected": True, "hotspot_was_enabled": True},
|
||||
)
|
||||
self.assertTrue(disconnect_called[0])
|
||||
|
||||
def test_temporarily_enable_restores_hotspot_and_reconnects(self):
|
||||
"""Test temporarily_enable restores hotspot and triggers reconnect."""
|
||||
mock_network = HotspotMockNetwork()
|
||||
WifiService._temp_disable_state = {"was_connected": True, "hotspot_was_enabled": True}
|
||||
WifiService.wifi_busy = True
|
||||
|
||||
thread_calls = []
|
||||
|
||||
class MockThreadModule:
|
||||
@staticmethod
|
||||
def start_new_thread(func, args):
|
||||
thread_calls.append((func, args))
|
||||
|
||||
original_thread = sys.modules.get("_thread")
|
||||
sys.modules["_thread"] = MockThreadModule
|
||||
|
||||
try:
|
||||
WifiService.temporarily_enable(True, network_module=mock_network)
|
||||
finally:
|
||||
if original_thread is not None:
|
||||
sys.modules["_thread"] = original_thread
|
||||
else:
|
||||
sys.modules.pop("_thread", None)
|
||||
|
||||
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
||||
self.assertFalse(WifiService.wifi_busy)
|
||||
self.assertIsNone(WifiService._temp_disable_state)
|
||||
self.assertTrue(ap_wlan.active())
|
||||
self.assertTrue(WifiService.hotspot_enabled)
|
||||
self.assertEqual(thread_calls[0][0], WifiService.auto_connect)
|
||||
|
||||
|
||||
class TestWifiServiceIPv4Info(unittest.TestCase):
|
||||
"""Test IPv4 info accessors for AP/STA modes."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
WifiService.wifi_busy = False
|
||||
WifiService.hotspot_enabled = False
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after test."""
|
||||
WifiService.wifi_busy = False
|
||||
WifiService.hotspot_enabled = False
|
||||
|
||||
def test_get_ipv4_info_from_ap_when_hotspot_enabled(self):
|
||||
"""Test IPv4 getters use AP info when hotspot is enabled."""
|
||||
mock_network = HotspotMockNetwork()
|
||||
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
||||
ap_wlan.active(True)
|
||||
ap_wlan.ifconfig(("192.168.4.1", "255.255.255.0", "192.168.4.1", "8.8.4.4"))
|
||||
WifiService.hotspot_enabled = True
|
||||
|
||||
address = WifiService.get_ipv4_address(network_module=mock_network)
|
||||
gateway = WifiService.get_ipv4_gateway(network_module=mock_network)
|
||||
|
||||
self.assertEqual(address, "192.168.4.1")
|
||||
self.assertEqual(gateway, "192.168.4.1")
|
||||
|
||||
def test_get_ipv4_info_returns_none_when_busy(self):
|
||||
"""Test IPv4 getters return None when wifi_busy is set."""
|
||||
WifiService.wifi_busy = True
|
||||
|
||||
address = WifiService.get_ipv4_address(network_module=HotspotMockNetwork())
|
||||
gateway = WifiService.get_ipv4_gateway(network_module=HotspotMockNetwork())
|
||||
|
||||
self.assertIsNone(address)
|
||||
self.assertIsNone(gateway)
|
||||
|
||||
def test_get_ipv4_info_desktop_mode(self):
|
||||
"""Test IPv4 getters return simulated values in desktop mode."""
|
||||
address = WifiService.get_ipv4_address(network_module=None)
|
||||
gateway = WifiService.get_ipv4_gateway(network_module=None)
|
||||
|
||||
self.assertEqual(address, "123.456.789.000")
|
||||
self.assertEqual(gateway, "000.123.456.789")
|
||||
|
||||
|
||||
class TestWifiServiceDisconnect(unittest.TestCase):
|
||||
"""Test WifiService.disconnect() method."""
|
||||
|
||||
@@ -453,6 +739,21 @@ class TestWifiServiceDisconnect(unittest.TestCase):
|
||||
self.assertTrue(disconnect_called[0])
|
||||
self.assertTrue(active_false_called[0])
|
||||
|
||||
def test_disconnect_disables_ap(self):
|
||||
"""Test disconnect also disables AP and clears hotspot flag."""
|
||||
mock_network = HotspotMockNetwork()
|
||||
ap_wlan = mock_network.WLAN(mock_network.AP_IF)
|
||||
sta_wlan = mock_network.WLAN(mock_network.STA_IF)
|
||||
ap_wlan.active(True)
|
||||
sta_wlan._connected = True
|
||||
|
||||
WifiService.hotspot_enabled = True
|
||||
|
||||
WifiService.disconnect(network_module=mock_network)
|
||||
|
||||
self.assertFalse(ap_wlan.active())
|
||||
self.assertFalse(WifiService.hotspot_enabled)
|
||||
|
||||
def test_disconnect_desktop_mode(self):
|
||||
"""Test disconnect in desktop mode."""
|
||||
# Should not raise an error
|
||||
|
||||
Reference in New Issue
Block a user