from dataclasses import dataclass from typing import Literal import pyray as rl from openpilot.system.ui.lib.wifi_manager import NetworkInfo, WifiManagerCallbacks, WifiManagerWrapper from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.button import gui_button from openpilot.system.ui.lib.label import gui_label from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.widgets.keyboard import Keyboard from openpilot.system.ui.widgets.confirm_dialog import confirm_dialog NM_DEVICE_STATE_NEED_AUTH = 60 ITEM_HEIGHT = 160 @dataclass class StateIdle: action: Literal["idle"] = "idle" @dataclass class StateConnecting: network: NetworkInfo action: Literal["connecting"] = "connecting" @dataclass class StateNeedsAuth: network: NetworkInfo action: Literal["needs_auth"] = "needs_auth" @dataclass class StateShowForgetConfirm: network: NetworkInfo action: Literal["show_forget_confirm"] = "show_forget_confirm" @dataclass class StateForgetting: network: NetworkInfo action: Literal["forgetting"] = "forgetting" UIState = StateIdle | StateConnecting | StateNeedsAuth | StateShowForgetConfirm | StateForgetting class WifiManagerUI: def __init__(self, wifi_manager: WifiManagerWrapper): self.state: UIState = StateIdle() self.btn_width = 200 self.scroll_panel = GuiScrollPanel() self.keyboard = Keyboard() self.wifi_manager = wifi_manager self.wifi_manager.set_callbacks(WifiManagerCallbacks(self._on_need_auth, self._on_activated, self._on_forgotten)) self.wifi_manager.start() self.wifi_manager.connect() def render(self, rect: rl.Rectangle): if not self.wifi_manager.networks: gui_label(rect, "Scanning Wi-Fi networks...", 72, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) return match self.state: case StateNeedsAuth(network): result = self.keyboard.render(rect, "Enter password", f"for {network.ssid}") if result == 1: self.connect_to_network(network, self.keyboard.text) elif result == 0: self.state = StateIdle() case StateShowForgetConfirm(network): result = confirm_dialog(rect, f'Forget Wi-Fi Network "{network.ssid}"?', "Forget") if result == 1: self.forget_network(network) elif result == 0: self.state = StateIdle() case _: self._draw_network_list(rect) def _draw_network_list(self, rect: rl.Rectangle): content_rect = rl.Rectangle(rect.x, rect.y, rect.width, len(self.wifi_manager.networks) * ITEM_HEIGHT) offset = self.scroll_panel.handle_scroll(rect, content_rect) clicked = self.scroll_panel.is_click_valid() rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) for i, network in enumerate(self.wifi_manager.networks): y_offset = rect.y + i * ITEM_HEIGHT + offset.y item_rect = rl.Rectangle(rect.x, y_offset, rect.width, ITEM_HEIGHT) if not rl.check_collision_recs(item_rect, rect): continue self._draw_network_item(item_rect, network, clicked) if i < len(self.wifi_manager.networks) - 1: line_y = int(item_rect.y + item_rect.height - 1) rl.draw_line(int(item_rect.x), int(line_y), int(item_rect.x + item_rect.width), line_y, rl.LIGHTGRAY) rl.end_scissor_mode() def _draw_network_item(self, rect, network: NetworkInfo, clicked: bool): label_rect = rl.Rectangle(rect.x, rect.y, rect.width - self.btn_width * 2, ITEM_HEIGHT) state_rect = rl.Rectangle(rect.x + rect.width - self.btn_width * 2 - 150, rect.y, 300, ITEM_HEIGHT) gui_label(label_rect, network.ssid, 55) status_text = "" if network.is_connected: status_text = "Connected" match self.state: case StateConnecting(network=connecting): if connecting.ssid == network.ssid: status_text = "CONNECTING..." case StateForgetting(network=forgetting): if forgetting.ssid == network.ssid: status_text = "FORGETTING..." if status_text: rl.gui_label(state_rect, status_text) # If the network is saved, show the "Forget" button if self.wifi_manager.is_saved(network.ssid): forget_btn_rect = rl.Rectangle( rect.x + rect.width - self.btn_width, rect.y + (ITEM_HEIGHT - 80) / 2, self.btn_width, 80, ) if isinstance(self.state, StateIdle) and gui_button(forget_btn_rect, "Forget") and clicked: self.state = StateShowForgetConfirm(network) if isinstance(self.state, StateIdle) and rl.check_collision_point_rec(rl.get_mouse_position(), label_rect) and clicked: if not self.wifi_manager.is_saved(network.ssid): self.state = StateNeedsAuth(network) else: self.connect_to_network(network) def connect_to_network(self, network: NetworkInfo, password=''): if self.wifi_manager.is_saved(network.ssid) and not password: self.wifi_manager.activate_connection(network.ssid) else: self.wifi_manager.connect_to_network(network.ssid, password) def forget_network(self, network: NetworkInfo): self.state = StateForgetting(network) self.wifi_manager.forget_connection(network.ssid) def _on_need_auth(self): match self.state: case StateConnecting(network): self.state = StateNeedsAuth(network) def _on_activated(self): if isinstance(self.state, StateConnecting): self.state = StateIdle() def _on_forgotten(self): if isinstance(self.state, StateForgetting): self.state = StateIdle() def main(): gui_app.init_window("Wi-Fi Manager") wifi_manager = WifiManagerWrapper() wifi_ui = WifiManagerUI(wifi_manager) for _ in gui_app.render(): wifi_ui.render(rl.Rectangle(50, 50, gui_app.width - 100, gui_app.height - 100)) wifi_manager.shutdown() gui_app.close() if __name__ == "__main__": main()