Skip to content

Commit d6cfef8

Browse files
czeiclaude
andcommitted
feat(network): configurable onboarding AP name — branding belongs to the app
WiFiManager(ap_name=...) sets the setup portal's SSID base; the library keeps only the uniqueness tail (MAC-derived on hardware, -DEV off-device) so two un-onboarded boxes never broadcast identical SSIDs. Default stays the generic "WifiManager-" prefix — deliberately not a product name; no app brand is hardwired in the library. Tests pin both: the generic default and an app brand flowing through. 826 pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVjnTZLoYtSvBRDuHCLhfP
1 parent 523c796 commit d6cfef8

3 files changed

Lines changed: 40 additions & 9 deletions

File tree

‎src/scrollkit/network/wifi_manager.py‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,29 @@ class WiFiManager:
4747
Manages WiFi connections for the application
4848
"""
4949

50-
def __init__(self, settings_manager):
50+
def __init__(self, settings_manager, ap_name=None):
5151
"""
5252
Initialize the WiFi manager
53-
53+
5454
Args:
5555
settings_manager: The settings manager
56+
ap_name: Brand name for the setup-portal access point. The SSID a
57+
customer sees while onboarding is ``"<ap_name>-<tail>"`` where
58+
the tail is a short MAC-derived suffix (kept library-side so two
59+
un-onboarded boxes in one home never broadcast identical SSIDs).
60+
Apps SHOULD pass their product name — the default is the
61+
generic ``WifiManager`` prefix, deliberately not a product name
62+
(branding belongs to the app, never hardwired here).
5663
"""
5764
self.settings_manager = settings_manager
65+
self._ap_base = ap_name or "WifiManager"
5866
self.ssid, self.password = self._resolve_credentials()
5967
self.is_connected = False
6068
self.wifi_client = None
6169
self.ap_enabled = False
6270

6371
# Development mode values
64-
self.AP_SSID = "WifiManager_DEV"
72+
self.AP_SSID = self._ap_base + "-DEV"
6573
self.AP_PASSWORD = "password"
6674

6775
try:
@@ -72,10 +80,10 @@ def __init__(self, settings_manager):
7280
self.wifi = None
7381
self.HAS_WIFI = False
7482
# Set dummy values for development
75-
self.AP_SSID = "WifiManager_DEV"
83+
self.AP_SSID = self._ap_base + "-DEV"
7684
self.AP_PASSWORD = "password"
7785
return
78-
86+
7987
# Try to import CircuitPython specific modules
8088
import wifi
8189
self.wifi = wifi
@@ -84,7 +92,7 @@ def __init__(self, settings_manager):
8492
mac_ap = ' '.join([hex(i) for i in self.wifi.radio.mac_address_ap])
8593
mac_ap = mac_ap.replace('0x', '').replace(' ', '').upper()
8694
# access point settings
87-
self.AP_SSID = "WifiManager_" + mac_ap[5:10] + mac_ap[1:2]
95+
self.AP_SSID = self._ap_base + "-" + mac_ap[5:10] + mac_ap[1:2]
8896
self.AP_PASSWORD = "password"
8997
self.AP_AUTHMODES = [self.wifi.AuthMode.WPA2, self.wifi.AuthMode.PSK]
9098

‎src/scrollkit/web/wifi_setup.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
1. The device can't join Wi-Fi (no/wrong credentials), so the app calls
88
``WiFiManager.run_setup_portal(display=...)`` — usually right after a
99
failed ``connect()``.
10-
2. The device starts its own access point (``WifiManager_XXXX``) and shows
10+
2. The device starts its own access point (named by the app via
11+
``WiFiManager(ap_name=...)``, e.g. ``ThemeParkWaits-XXXX``) and shows
1112
join instructions on the LED panel.
1213
3. The user's phone joins that AP and opens ``http://192.168.4.1``: a page
1314
with the scanned nearby networks (signal bars), a manual-SSID field, and

‎test/unit/network/test_wifi_manager.py‎

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ def test_initialization_with_wifi(self):
3939
assert wifi_manager.ssid == 'TestSSID'
4040
assert wifi_manager.password == 'TestPassword'
4141
assert wifi_manager.is_connected is False
42-
assert "WifiManager_" in wifi_manager.AP_SSID
42+
# Hyphen separator since ap_name became configurable:
43+
# "<base>-<MAC tail>" (base defaults to WifiManager).
44+
assert "WifiManager-" in wifi_manager.AP_SSID
4345

4446
def test_initialization_without_wifi(self):
4547
"""Test initialization without WiFi module available"""
@@ -359,4 +361,24 @@ async def test_returns_false_when_nothing_saved(self):
359361
portal_instance.run = AsyncMock(return_value=False)
360362
with patch("scrollkit.web.wifi_setup.WiFiSetupPortal",
361363
MagicMock(return_value=portal_instance)):
362-
assert await wm.run_setup_portal() is False
364+
assert await wm.run_setup_portal() is False
365+
366+
class TestAccessPointNaming:
367+
"""The onboarding AP's SSID base is per-app configuration (ap_name), never a
368+
product name hardwired in the library. The library owns only the uniqueness
369+
tail (MAC-derived on hardware, -DEV off-device)."""
370+
371+
def _make(self, **kw):
372+
mock_sm = MagicMock()
373+
with patch('scrollkit.network.wifi_manager.load_credentials') as lc:
374+
lc.return_value = ('TestSSID', 'TestPassword')
375+
return WiFiManager(mock_sm, **kw)
376+
377+
def test_default_base_is_generic(self):
378+
wm = self._make()
379+
assert wm.AP_SSID.startswith("WifiManager-")
380+
assert "ThemeParkWaits" not in wm.AP_SSID # no app brand in the library
381+
382+
def test_app_brand_flows_through(self):
383+
wm = self._make(ap_name="ThemeParkWaits")
384+
assert wm.AP_SSID.startswith("ThemeParkWaits-")

0 commit comments

Comments
 (0)