diff --git a/lib/web_interface.py b/lib/web_interface.py index 9302ed1..447a3eb 100644 --- a/lib/web_interface.py +++ b/lib/web_interface.py @@ -97,9 +97,9 @@ def textbox(settings): } hidden_keys = {"ai_provider", "ai_key", "ai_model", "repository_file"} # ssid/password/wifi_power/channel live in their own WiFi card - # (wifi_setup.wifi_card()) and rotation in the Quick Actions card + # (wlan.config_card()) and rotation in the Quick Actions card # (_quick_actions_card()) -- none of them belong in this generic form. - wifi_card_keys = {"ssid", "password", "wifi_power", "channel"} + config_card_keys = {"ssid", "password", "wifi_power", "channel"} # Panel hardware -- normally auto-detected, grouped in Advanced with a warning. hardware_order = ["width", "height", "tiles", "color_correct"] other_advanced_order = ["repository_url", "enable_button"] @@ -109,7 +109,7 @@ def textbox(settings): adv_items = {} for setting in settings: if setting in hidden_keys: continue - if setting in wifi_card_keys: continue + if setting in config_card_keys: continue if setting == "rotation": continue print("Setting: ", setting) val = settings[setting] @@ -720,18 +720,18 @@ def _quick_actions_card(): def _apps_content(): # Same Quick Actions + WiFi cards as /system/settings (not the separate - # "WiFi Setup" wizard render_wifi_setup() used to be) -- one experience + # "WiFi Setup" wizard setup_content() used to be) -- one experience # for configuring the device regardless of which page you land on. if not wifi.radio.connected: - top_html = _quick_actions_card() + wifi_setup.wifi_card() + top_html = _quick_actions_card() + wlan.config_card() else: # The full card only makes sense while disconnected, but a - # lingering wifi_status (e.g. a failed post-connect settings + # lingering wlan.STATUS (e.g. a failed post-connect settings # save) still needs to surface somewhere -- this is the page the # AP setup flow lands back on after connecting, so it's the one # place that's guaranteed to be seen even if the user never # visits /system/settings afterward. - top_html = wifi_setup.status_banner() + top_html = wlan.status_banner() installed_apps = "" for app in os.listdir("/"): if app == "LICENSE": continue @@ -787,7 +787,7 @@ def webinterface_post(request): except: pass clearscreen(True) if not savesettings(settings): - __main__.wifi_status = "Couldn't save settings (read-only filesystem)" + wlan.STATUS = "Couldn't save settings (read-only filesystem)" clearscreen(False) __main__.autostart = settings.get("autostart", False) __main__.screensaver_app = settings.get("screensaver", "") @@ -908,7 +908,7 @@ def _settings_content(): app_card = f'
App Behavior
{app_html}
' if app_html else "" adv_card = f'
⚙ Advanced{adv_html}
' if adv_html else "" return """ -""" + _quick_actions_card() + wifi_setup.wifi_card() + f""" +""" + _quick_actions_card() + wlan.config_card() + f"""
{main_card} {app_card} @@ -985,7 +985,7 @@ def _system_perf(request): import cmd import filemanager -import wifi_setup +import wlan @ampule.route('/system/favicon.svg') diff --git a/lib/wifi_setup.py b/lib/wlan.py similarity index 71% rename from lib/wifi_setup.py rename to lib/wlan.py index 691981b..2a2092c 100644 --- a/lib/wifi_setup.py +++ b/lib/wlan.py @@ -1,54 +1,114 @@ +import time + import ampule -import web_interface import web_components +import web_interface -import __main__ from __main__ import ( - connect_to_network, macid, pprint, render_home_screen, + savesettings, settings, wifi, ) - -# Shared "this device isn't on WiFi yet" UI + connect flow, usable from any -# app or route via wifi_setup.needs_setup() / render_wifi_setup() / page(). -# Routes live under /system/wifi/... so they're auto-promoted (see -# ampule.py) and reachable from inside any app. +# All WiFi radio management: connecting, AP/hotspot fallback, connection +# status, and the shared "this device isn't on WiFi yet" UI + connect flow, +# usable from any app or route via wlan.is_connected() / wlan.setup_content() +# / wlan.setup_page(). Routes live under /system/wifi/... so they're +# auto-promoted (see ampule.py) and reachable from inside any app. # # Expected usage from an app: # -# import wifi_setup +# import wlan # # def main_loop(): # while True: -# if wifi_setup.needs_setup(): -# wifi_setup.show_setup_on_led() +# if not wlan.is_connected(): +# wlan.show_setup_on_led() # continue # # ... # normal app behavior # # @ampule.route('/', method='GET') # def index(request): -# if wifi_setup.needs_setup(): -# return (200, {}, header("WiFi Setup", app=True) + wifi_setup.render_wifi_setup() + footer()) +# if not wlan.is_connected(): +# return wlan.setup_page() # # return (200, {}, normal_app_page()) # -# wifi_card() is the full picker+power+channel card shared by the home -# page and /system/settings; wifi_fields() is just the network/password -# part, reused by render_wifi_setup()'s standalone page. +# wlan.config_card() is the full picker+power+channel card shared by the +# home page and /system/settings; wlan.credentials_fields() is just the +# network/password part, reused by wlan.setup_content()'s standalone page. # # apps/departures has its own separate copy, untouched for now. # # web_interface is imported eagerly since this module is only ever # imported from inside web_interface.py's own execution. +# +# macid, the boot-time socket/tx_power bring-up, and the shared HTTP +# session (pool/socket/requests) stay in main.py -- fetch_data.py reads +# macid via a `from __main__ import *` that runs before web_interface.py +# (and this module) are ever loaded, and pool/socket/requests are plain +# HTTP/TCP plumbing used directly by nearly every app, not WiFi-radio setup. -def needs_setup(): - return not wifi.radio.connected +STATUS = "" + + +def connect_to_network(timeout=False, silent=False, save=False): + # Never draws. save=True only for an explicit user-initiated connect -- + # boot/retry reuse stored settings and have nothing new to persist. + global STATUS + + if silent and wifi.radio.connected: + return time.monotonic() + + STATUS = "" + print("Connecting...") + + try: + channel = settings.get("channel", 0) + if channel: + wifi.radio.connect( + str(settings["ssid"]), + str(settings["password"]), + channel=int(channel), + timeout=timeout, + ) + else: + wifi.radio.connect( + str(settings["ssid"]), str(settings["password"]), timeout=timeout + ) + if save and wifi.radio.connected: + if not savesettings(settings): + STATUS = "Connected, but couldn't save settings (read-only filesystem)" + + except Exception as e: + if "unknown failure" in str(e).lower(): + e = "Router distance!" + if "no network with" in str(e).lower(): + e = "Wrong WiFi name" + if "authentication failure" in str(e).lower(): + e = "Wrong password" + + print(e) + STATUS = str(e) + + return time.monotonic() + + +def start_hotspot(): + try: + wifi.radio.start_ap(ssid=macid) + render_home_screen() + except Exception as e: + pprint(str(e)) + + +def is_connected(): + return wifi.radio.connected def show_setup_on_led(): @@ -84,19 +144,25 @@ def _scan_options(current_ssid=""): wifi.radio.stop_scanning_networks() if current_ssid and not matched_current: - networks = f"" + networks + networks = ( + f"" + + networks + ) networks += "" return networks -def wifi_fields(current_ssid=""): +def credentials_fields(current_ssid=""): """Network picker + password field, shared by the AP-setup page and /system/settings. Only auto-scans while disconnected -- scanning while connected can drop the radio off its own AP, so once connected only the rescan button (⟳) scans.""" - options = _scan_options(current_ssid) if not wifi.radio.connected else _current_options(current_ssid) - + options = ( + _scan_options(current_ssid) + if not wifi.radio.connected + else _current_options(current_ssid) + ) return f"""
@@ -158,12 +224,12 @@ def wifi_fields(current_ssid=""): def status_banner(): - """Last wifi_status error on its own, for pages that don't show the full wifi_card().""" - wifi_error = str(__main__.wifi_status) + """Last STATUS error on its own, for pages that don't show the full config_card().""" + wifi_error = str(STATUS) return f'

{wifi_error}

' if wifi_error else "" -def wifi_card(): +def config_card(): """Full WiFi card (picker, power, channel, connect, error) shared by home and /system/settings.""" try: power = int(float(settings.get("wifi_power", 9))) @@ -176,7 +242,7 @@ def wifi_card(): channel_label = "Auto" if channel == 0 else str(channel) error_html = status_banner() return f"""
WiFi
-{wifi_fields(settings.get("ssid", ""))} +{credentials_fields(settings.get("ssid", ""))}
@@ -193,28 +259,28 @@ def wifi_card():
""" -def render_wifi_setup(): - wifi_error = str(__main__.wifi_status) +def setup_content(): + wifi_error = str(STATUS) error_html = f'

{wifi_error}

' if wifi_error else "" return f"""
- {wifi_fields(settings.get("ssid", ""))} + {credentials_fields(settings.get("ssid", ""))} {error_html}
""" -def page(title="WiFi Setup"): +def setup_page(title="WiFi Setup"): # Convenience for the common case: an app that already uses - # web_interface's header()/footer() can just return wifi_setup.page(). + # web_interface's header()/footer() can just return wlan.setup_page(). return ( 200, {}, web_interface.header(title, app=True) - + render_wifi_setup() + + setup_content() + web_interface.footer(), ) diff --git a/main.py b/main.py index 928fc7b..81b454a 100644 --- a/main.py +++ b/main.py @@ -58,42 +58,10 @@ def logo_anim_step(): socket.listen(5) socket_timeout = 5 macid = "matrixbox-" + "".join([hex(i) for i in wifi.radio.mac_address]).replace("0x","")[:3] # mac-id för hotspot -wifi_status = "" ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) requests = adafruit_requests.Session(pool, ssl_context) screensaver = time.monotonic() -def start_hotspot(): - try: - wifi.radio.start_ap(ssid=macid) - render_home_screen() - except Exception as e: pprint(str(e)) - -def connect_to_network(timeout=False, silent=False, save=False): - # Never draws. save=True only for an explicit user-initiated connect -- - # boot/retry reuse stored settings and have nothing new to persist. - global wifi_status - if silent and wifi.radio.connected: - return time.monotonic() - wifi_status = "" - print("Connecting...") - try: - channel = settings.get("channel", 0) - if channel: - wifi.radio.connect(str(settings["ssid"]), str(settings["password"]), channel=int(channel), timeout=timeout) - else: - wifi.radio.connect(str(settings["ssid"]), str(settings["password"]), timeout=timeout) - if save and wifi.radio.connected: - if not savesettings(settings): - wifi_status = "Connected, but couldn't save settings (read-only filesystem)" - except Exception as e: - if "unknown failure" in str(e).lower(): e = "Router distance!" - if "no network with" in str(e).lower(): e = "Wrong WIFI name" - if "authentication failure" in str(e).lower(): e = "Wrong password" - print(e) - wifi_status = str(e) - return time.monotonic() - @ampule.route("/exit", method="GET") def webinterface(request): load_settings.app_running = False @@ -184,7 +152,7 @@ def render_home_screen(): if wifi.radio.connected: _wifi_address = f"IP: {wifi.radio.ipv4_address}" if wifi.radio.ipv4_address else "OFFLINE" pprint(_wifi_address, line=1) - if wifi_status: + if wlan.STATUS: # Full message is in the Settings error box; screen is too narrow for it. pprint("Read-only filesystem", line=2, color="red") pprint("Restart to fix", line=3, color="red") @@ -192,7 +160,7 @@ def render_home_screen(): pprint("Select app:", line=2) show_first_app() elif wifi.radio.ap_active: - wifi_setup.show_setup_on_led() + wlan.show_setup_on_led() def next_program_in_list(run=False): try: load_settings.installed_apps_list[1] @@ -220,14 +188,14 @@ def check_for_button_next_program(): wifi.radio.tx_power = float(settings["wifi_power"]) from web_interface import * -import wifi_setup -connect_to_network() +import wlan +wlan.connect_to_network() while 1: print("Entered main loop") while not wifi.radio.connected and not wifi.radio.ap_active: check_network_again_timer = time.monotonic() - start_hotspot() + wlan.start_hotspot() while wifi.radio.ap_active and not wifi.radio.connected: ampule.listen(socket) @@ -239,7 +207,7 @@ def check_for_button_next_program(): print("Attempting... " + str(wifi.radio.tx_power)) wifi.radio.tx_power += 1 if wifi.radio.tx_power == 21: wifi.radio.tx_power = 18 - check_network_again_timer = connect_to_network(timeout=3, silent=True) + check_network_again_timer = wlan.connect_to_network(timeout=3, silent=True) if wifi.radio.connected: wifi.radio.stop_ap() while wifi.radio.connected or wifi.radio.ap_active: diff --git a/repository.txt b/repository.txt index c991d9e..d32d692 100644 --- a/repository.txt +++ b/repository.txt @@ -5,7 +5,7 @@ "main.py", "lib/web_interface.py", "lib/web_components.py", - "lib/wifi_setup.py", + "lib/wlan.py", "lib/ampule.py", "lib/load_screen.py", "lib/downloader.py",