mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
a82935f326
Integrate onroad views, configurable widgets, local settings, steering-profile contracts, Bluetooth and Android Auto with their device and runtime services. Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com> Co-authored-by: Daniel Koepping <elkoled@gmail.com> Co-authored-by: Danny <dannyirwin31@yahoo.com> Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com> Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com> Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com> Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu> Co-authored-by: Shane Smiskol <shane@smiskol.com> Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com> Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com> Co-authored-by: stef <19478336+stefpi@users.noreply.github.com> Co-authored-by: whoisdomi <dcantu83@gmail.com> Co-authored-by: Intelli <contact@intelli.software> Co-authored-by: pharmacomaniac <blittle65@gmail.com> Co-authored-by: prabhaavp <prabhaav@msu.edu>
378 lines
19 KiB
Python
378 lines
19 KiB
Python
"""Native NetworkUI ownership in the large custom settings rail."""
|
|
|
|
from types import SimpleNamespace as NS
|
|
from contextlib import nullcontext
|
|
from dataclasses import replace
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import Mock, call, patch
|
|
|
|
import pyray as rl
|
|
|
|
from openpilot.common.params import Params
|
|
from openpilot.starpilot.ui.network_panel import NetworkPanelBridge
|
|
from openpilot.starpilot.ui import runtime_app
|
|
from openpilot.starpilot.ui.runtime_snapshot import RuntimeSnapshotAdapter
|
|
from openpilot.starpilot.ui.settings_state import Destination, DestinationAvailability, SettingsAction, SettingsActionKind
|
|
from openpilot.starpilot.ui.shell import ShellMode, ShellRequest
|
|
from openpilot.starpilot.ui.tests.test_runtime_snapshot import NOW, ui_fake
|
|
from openpilot.system.ui.widgets import DialogResult, Widget
|
|
from openpilot.system.ui.widgets import network as native_network
|
|
from openpilot.system.ui.lib.wifi_manager import Network, SecurityType
|
|
|
|
|
|
NETWORK = Network("fixture", 75, SecurityType.WPA2, False)
|
|
|
|
|
|
def native_panel(manager):
|
|
"""Use real native lifecycle methods without constructing fonts or NetworkManager."""
|
|
panel = native_network.NetworkUI.__new__(native_network.NetworkUI)
|
|
Widget.__init__(panel)
|
|
wifi = native_network.WifiManagerUI.__new__(native_network.WifiManagerUI)
|
|
Widget.__init__(wifi)
|
|
wifi._wifi_manager = manager
|
|
wifi._action_guard = None
|
|
wifi._networks = [NETWORK]
|
|
advanced = native_network.AdvancedNetworkSettings.__new__(native_network.AdvancedNetworkSettings)
|
|
Widget.__init__(advanced)
|
|
advanced._wifi_manager = manager
|
|
advanced._action_guard = None
|
|
panel._wifi_panel = panel._child(wifi)
|
|
panel._advanced_panel = panel._child(advanced)
|
|
panel._action_guard = None
|
|
return panel, wifi, advanced
|
|
|
|
|
|
class NetworkPanelTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.manager = Mock()
|
|
self.panel, self.wifi, self.advanced = native_panel(self.manager)
|
|
self.parked = True
|
|
self.selected = True
|
|
self.bridge = NetworkPanelBridge(self.panel, lambda: self.parked and self.selected)
|
|
|
|
def test_large_network_stays_open_onroad_and_revokes_on_navigation(self):
|
|
from openpilot.selfdrive.ui.layouts.main import MainState
|
|
from openpilot.starpilot.ui.tests.test_runtime_snapshot import TestParkedClockDomains
|
|
fixture = TestParkedClockDomains()
|
|
fixture.setUp()
|
|
fixture.ui.sm['pandaStates'][0].ignitionLine = True
|
|
layout = runtime_app.StarMainLayout.__new__(runtime_app.StarMainLayout)
|
|
object.__setattr__(layout, '_current_mode', MainState.SETTINGS)
|
|
object.__setattr__(layout, 'star', NS(selected=Destination.NETWORK,
|
|
connectivity_allowed=fixture.adapter.connectivity_allowed))
|
|
bridge = NetworkPanelBridge(self.panel, layout._network_authority)
|
|
self.assertTrue(fixture.adapter.confirmed_offroad())
|
|
self.assertTrue(bridge.enter())
|
|
self.wifi.forget_network(NETWORK)
|
|
self.manager.forget_connection.assert_called_once_with('fixture')
|
|
fixture.ui.sm['deviceState'].started = True
|
|
fixture.ui.started = True
|
|
self.wifi.forget_network(NETWORK)
|
|
self.assertEqual(self.manager.forget_connection.call_count, 2)
|
|
with patch.object(self.panel, 'render'):
|
|
self.assertTrue(bridge.render(rl.Rectangle(550, 25, 1560, 1030)))
|
|
layout.star.selected = Destination.STAR
|
|
self.wifi.forget_network(NETWORK)
|
|
self.assertEqual(self.manager.forget_connection.call_count, 2)
|
|
self.assertFalse(bridge.render(rl.Rectangle(550, 25, 1560, 1030)))
|
|
self.manager.set_active.assert_called_with(False)
|
|
|
|
def test_native_child_scanning_lifecycle_is_paired_without_frame_restart(self):
|
|
self.assertTrue(self.bridge.enter())
|
|
self.assertTrue(self.bridge.enter())
|
|
self.manager.set_active.assert_called_once_with(True)
|
|
self.bridge.leave()
|
|
self.bridge.leave()
|
|
self.assertEqual(self.manager.set_active.call_args_list, [call(True), call(False)])
|
|
self.assertTrue(self.bridge.enter())
|
|
self.bridge.leave()
|
|
self.assertEqual(self.manager.set_active.call_count, 4)
|
|
|
|
def test_real_settings_layout_lifecycle_does_not_duplicate_network_scan(self):
|
|
from openpilot.selfdrive.ui.layouts.settings.settings import PanelInfo, PanelType, SettingsLayout
|
|
settings = SettingsLayout.__new__(SettingsLayout)
|
|
Widget.__init__(settings)
|
|
native_device = Mock()
|
|
settings._current_panel = PanelType.DEVICE
|
|
settings._panels = {PanelType.DEVICE: PanelInfo("Device", native_device),
|
|
PanelType.NETWORK: PanelInfo("Network", self.panel)}
|
|
settings.show_event()
|
|
self.assertTrue(self.bridge.enter())
|
|
self.bridge.leave()
|
|
settings.hide_event()
|
|
self.manager.set_active.assert_has_calls([call(True), call(False)])
|
|
self.assertEqual(self.manager.set_active.call_count, 2)
|
|
native_device.show_event.assert_called_once()
|
|
native_device.hide_event.assert_called_once()
|
|
|
|
def test_offroad_loss_revokes_effect_and_stops_scan(self):
|
|
self.assertTrue(self.bridge.enter())
|
|
self.parked = False
|
|
network = NETWORK
|
|
self.wifi.forget_network(network)
|
|
self.manager.forget_connection.assert_not_called()
|
|
self.assertFalse(self.bridge.render(rl.Rectangle(550, 25, 1560, 1030)))
|
|
self.manager.set_active.assert_called_with(False)
|
|
self.assertFalse(self.bridge.enter())
|
|
|
|
def test_native_password_and_forget_callbacks_expire_after_navigation_and_reentry(self):
|
|
self.assertTrue(self.bridge.enter())
|
|
network = NETWORK
|
|
self.wifi.state = native_network.UIState.NEEDS_AUTH
|
|
self.wifi._state_network = network
|
|
self.wifi._password_retry = False
|
|
self.wifi.keyboard = Mock(text="secret123")
|
|
with patch.object(native_network.gui_app, "push_widget"):
|
|
self.wifi._render(rl.Rectangle(0, 0, 100, 100))
|
|
password_callback = self.wifi.keyboard.set_callback.call_args.args[0]
|
|
self.bridge.leave()
|
|
self.assertTrue(self.bridge.enter())
|
|
password_callback(DialogResult.CONFIRM)
|
|
self.manager.connect_to_network.assert_not_called()
|
|
self.wifi.state = native_network.UIState.SHOW_FORGET_CONFIRM
|
|
self.wifi._state_network = network
|
|
with patch.object(native_network, "ConfirmDialog", side_effect=lambda *args, **kwargs: NS(callback=kwargs["callback"], set_text=Mock())), \
|
|
patch.object(native_network.gui_app, "push_widget") as pushed:
|
|
self.wifi._render(rl.Rectangle(0, 0, 100, 100))
|
|
forget_callback = pushed.call_args.args[0].callback
|
|
self.bridge.leave()
|
|
forget_callback(DialogResult.CONFIRM)
|
|
self.manager.forget_connection.assert_not_called()
|
|
|
|
def test_native_apn_and_hidden_network_confirmations_recheck_fresh_authority(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
params = Params(directory)
|
|
self.advanced._params = params
|
|
self.advanced._keyboard = Mock(text="fixture.apn")
|
|
self.assertTrue(self.bridge.enter())
|
|
with patch.object(native_network.gui_app, "push_widget"):
|
|
self.advanced._edit_apn()
|
|
apn_callback = self.advanced._keyboard.set_callback.call_args.args[0]
|
|
self.parked = False
|
|
apn_callback(DialogResult.CONFIRM)
|
|
self.assertIsNone(params.get("GsmApn"))
|
|
self.parked = True
|
|
self.bridge.leave()
|
|
self.assertTrue(self.bridge.enter())
|
|
with patch.object(native_network.gui_app, "push_widget"):
|
|
self.advanced._connect_to_hidden_network()
|
|
hidden_callback = self.advanced._keyboard.set_callback.call_args.args[0]
|
|
self.bridge.leave()
|
|
hidden_callback(DialogResult.CONFIRM)
|
|
self.manager.connect_to_network.assert_not_called()
|
|
|
|
def test_nested_hidden_password_from_prior_page_cannot_connect_after_reentry(self):
|
|
self.advanced._keyboard = Mock(text="hidden-name")
|
|
self.assertTrue(self.bridge.enter())
|
|
with patch.object(native_network.gui_app, "push_widget"):
|
|
self.advanced._connect_to_hidden_network()
|
|
name_callback = self.advanced._keyboard.set_callback.call_args.args[0]
|
|
name_callback(DialogResult.CONFIRM)
|
|
password_callback = self.advanced._keyboard.set_callback.call_args.args[0]
|
|
self.bridge.leave()
|
|
self.assertTrue(self.bridge.enter())
|
|
self.advanced._keyboard.text = "secret123"
|
|
password_callback(DialogResult.CONFIRM)
|
|
self.manager.connect_to_network.assert_not_called()
|
|
|
|
def test_advanced_side_effects_are_silent_after_authority_loss(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
params = Params(directory)
|
|
self.advanced._params = params
|
|
self.advanced._prime_state = NS(get_type=lambda: 0)
|
|
self.advanced._cell_prime_types = (0,)
|
|
self.advanced._roaming_btn = Mock()
|
|
self.advanced._apn_btn = Mock()
|
|
self.advanced._cellular_metered_btn = Mock()
|
|
self.advanced._roaming_action = Mock()
|
|
self.advanced._tethering_action = Mock()
|
|
self.assertTrue(self.bridge.enter())
|
|
self.parked = False
|
|
self.advanced._update_state()
|
|
self.advanced._toggle_tethering()
|
|
self.advanced._toggle_roaming()
|
|
self.advanced._toggle_cellular_metered()
|
|
self.advanced._toggle_wifi_metered(1)
|
|
self.manager.set_ipv4_forward.assert_not_called()
|
|
self.manager.set_tethering_active.assert_not_called()
|
|
self.manager.set_current_network_metered.assert_not_called()
|
|
self.assertIsNone(params.get("GsmRoaming"))
|
|
self.assertIsNone(params.get("GsmMetered"))
|
|
|
|
def test_render_failure_cleans_up_scan_and_revokes_callbacks(self):
|
|
self.assertTrue(self.bridge.enter())
|
|
with patch.object(self.panel, "render", side_effect=RuntimeError("render failed")):
|
|
self.assertFalse(self.bridge.render(rl.Rectangle(550, 25, 1560, 1030)))
|
|
self.assertFalse(self.bridge.active)
|
|
self.manager.set_active.assert_called_with(False)
|
|
self.wifi.forget_network(NETWORK)
|
|
self.manager.forget_connection.assert_not_called()
|
|
|
|
def test_default_native_caller_has_no_new_guard(self):
|
|
manager = Mock()
|
|
panel, wifi, _advanced = native_panel(manager)
|
|
panel.show_event()
|
|
wifi.forget_network(NETWORK)
|
|
panel.hide_event()
|
|
manager.forget_connection.assert_called_once_with("fixture")
|
|
|
|
def test_connectivity_panels_are_available_offroad_onroad_and_without_fresh_telemetry(self):
|
|
ui = ui_fake()
|
|
ui.started = False
|
|
ui.sm.messages["deviceState"].started = False
|
|
ui.sm.messages["pandaStates"][0].ignitionLine = False
|
|
state = RuntimeSnapshotAdapter(ui).build(ShellMode.SETTINGS, Destination.NETWORK, now_ns=NOW)
|
|
self.assertEqual(state.selected, Destination.NETWORK)
|
|
self.assertTrue(state.settings.destination(Destination.NETWORK).available)
|
|
ui.started = True
|
|
ui.sm.messages["deviceState"].started = True
|
|
state = RuntimeSnapshotAdapter(ui).build(ShellMode.SETTINGS, Destination.NETWORK, now_ns=NOW)
|
|
self.assertTrue(state.settings.destination(Destination.NETWORK).available)
|
|
for service in ('deviceState', 'pandaStates'):
|
|
ui.sm.valid[service] = False
|
|
for destination in (Destination.NETWORK, Destination.BLUETOOTH):
|
|
with self.subTest(destination=destination):
|
|
state = RuntimeSnapshotAdapter(ui).build(ShellMode.SETTINGS, destination, now_ns=NOW)
|
|
self.assertEqual(state.selected, destination)
|
|
self.assertTrue(state.settings.destination(destination).available)
|
|
|
|
def test_large_bluetooth_opens_and_stays_open_without_connectivity_authority(self):
|
|
layout = runtime_app.StarMainLayout.__new__(runtime_app.StarMainLayout)
|
|
Widget.__init__(layout)
|
|
layout._current_mode = runtime_app.MainState.SETTINGS
|
|
layout._network_bridge = Mock()
|
|
layout._large_panels = {}
|
|
layout._large_destination = None
|
|
object.__setattr__(layout, 'star', NS(selected=Destination.BLUETOOTH, connectivity_allowed=Mock(return_value=False),
|
|
fonts=NS(profile=runtime_app.Profile.LARGE)))
|
|
object.__setattr__(layout, 'page', NS(render=Mock()))
|
|
with patch('openpilot.starpilot.ui.bluetooth_large.BluetoothLarge') as factory:
|
|
layout._network_destination(Destination.BLUETOOTH)
|
|
factory.assert_called_once_with(layout.star.connectivity_allowed)
|
|
panel = factory.return_value
|
|
panel.show_event.assert_called_once()
|
|
layout._render_main_content()
|
|
self.assertEqual(layout.star.selected, Destination.BLUETOOTH)
|
|
self.assertEqual(layout._large_destination, Destination.BLUETOOTH)
|
|
panel.hide_event.assert_not_called()
|
|
layout._network_destination(Destination.STAR)
|
|
panel.hide_event.assert_called_once()
|
|
|
|
def test_shell_selection_cancels_press_and_renders_native_pane_in_screen_coordinates(self):
|
|
session = runtime_app.StarShellSession.__new__(runtime_app.StarShellSession)
|
|
session.slc_actions = None
|
|
session.pip_warning = Mock()
|
|
session.favorites = Mock()
|
|
session.selected = Destination.STAR
|
|
session._mode = ShellMode.SETTINGS
|
|
session.profile = runtime_app.Profile.LARGE
|
|
object.__setattr__(session, "input", NS(cancel=Mock()))
|
|
session._on_destination_change = Mock()
|
|
session._snapshot_cache = None
|
|
session.sidebar_expanded = True
|
|
object.__setattr__(session, "view", NS(render=Mock(), settings=NS(render_rail=Mock())))
|
|
session.notice = ""
|
|
session.network_layer = Mock(return_value=True)
|
|
session._emit(ShellRequest("settings", SettingsAction(SettingsActionKind.REQUEST_DESTINATION,
|
|
DestinationAvailability(Destination.NETWORK, True))))
|
|
self.assertEqual(session.selected, Destination.NETWORK)
|
|
session.input.cancel.assert_called_once()
|
|
session._on_destination_change.assert_called_once_with(Destination.NETWORK)
|
|
state = RuntimeSnapshotAdapter(self._parked_ui()).build(ShellMode.SETTINGS, Destination.NETWORK, now_ns=NOW)
|
|
session.snapshot = Mock(return_value=state)
|
|
with patch.object(runtime_app, "placed_at", return_value=nullcontext()):
|
|
session.render(ShellMode.SETTINGS, rl.Rectangle(10, 20, 2160, 1080))
|
|
session.view.settings.render_rail.assert_called_once_with(state.settings, selected=Destination.NETWORK)
|
|
rect = session.network_layer.call_args.args[0]
|
|
self.assertEqual((rect.x, rect.y, rect.width, rect.height), (560, 45, 1560, 1030))
|
|
def back_from_network(_rect):
|
|
session._emit(ShellRequest("settings", SettingsAction(SettingsActionKind.CLOSE)))
|
|
return False # the bridge revokes authority when Back changes the destination
|
|
session.network_layer.side_effect = back_from_network
|
|
with patch.object(runtime_app, "placed_at", return_value=nullcontext()):
|
|
session.render(ShellMode.SETTINGS, rl.Rectangle(10, 20, 2160, 1080))
|
|
self.assertEqual(session.notice, "")
|
|
self.assertEqual(session.selected, Destination.STAR)
|
|
self.assertEqual(session._on_destination_change.call_args.args[0], Destination.STAR)
|
|
self.assertEqual(session.view.settings.render_rail.call_args_list, [
|
|
call(state.settings, selected=Destination.NETWORK), call(state.settings, selected=Destination.NETWORK),
|
|
])
|
|
|
|
def test_shell_render_failure_returns_to_star_without_retrying_deactivated_bridge(self):
|
|
session = runtime_app.StarShellSession.__new__(runtime_app.StarShellSession)
|
|
session.slc_actions = None
|
|
session.pip_warning = Mock()
|
|
session.favorites = Mock()
|
|
session.selected = Destination.NETWORK
|
|
session._mode = ShellMode.SETTINGS
|
|
session.profile = runtime_app.Profile.LARGE
|
|
object.__setattr__(session, "input", NS(cancel=Mock()))
|
|
object.__setattr__(session, "view", NS(render=Mock(), settings=NS(render_rail=Mock())))
|
|
object.__setattr__(session, "fonts", NS(draw=Mock()))
|
|
session._on_destination_change = Mock()
|
|
session.notice = ""
|
|
session.notice_until = 0.0
|
|
session.network_layer = Mock(return_value=False) # bridge already deactivated
|
|
network_state = RuntimeSnapshotAdapter(self._parked_ui()).build(ShellMode.SETTINGS, Destination.NETWORK, now_ns=NOW)
|
|
session._snapshot_cache = ((), network_state)
|
|
session.snapshot = Mock(side_effect=lambda _mode: replace(network_state, selected=session.selected))
|
|
with (patch.object(runtime_app, "placed_at", return_value=nullcontext()),
|
|
patch.object(runtime_app.time, "monotonic", return_value=100.0),
|
|
patch.object(runtime_app.rl, "draw_rectangle")):
|
|
session.render(ShellMode.SETTINGS, rl.Rectangle(0, 0, 2160, 1080))
|
|
self.assertEqual(session.selected, Destination.STAR)
|
|
self.assertIsNone(session._snapshot_cache)
|
|
self.assertIn("network panel is unavailable", session.notice)
|
|
session.view.settings.render_rail.assert_called_once_with(network_state.settings, selected=Destination.NETWORK)
|
|
session.input.cancel.assert_called_once()
|
|
session._on_destination_change.assert_called_once_with(Destination.STAR)
|
|
session.render(ShellMode.SETTINGS, rl.Rectangle(0, 0, 2160, 1080))
|
|
session.network_layer.assert_called_once()
|
|
session.view.settings.render_rail.assert_called_once_with(network_state.settings, selected=Destination.NETWORK)
|
|
|
|
@staticmethod
|
|
def _parked_ui():
|
|
ui = ui_fake()
|
|
ui.started = False
|
|
ui.sm.messages["deviceState"].started = False
|
|
ui.sm.messages["pandaStates"][0].ignitionLine = False
|
|
return ui
|
|
|
|
def test_native_entrypoint_and_exit_clear_retained_network_selection(self):
|
|
from openpilot.selfdrive.ui.layouts.settings.settings import PanelType
|
|
layout = runtime_app.StarMainLayout.__new__(runtime_app.StarMainLayout)
|
|
layout._network_bridge = Mock()
|
|
layout._large_destination = None
|
|
layout._large_panels = {}
|
|
layout._current_mode = runtime_app.MainState.HOME
|
|
object.__setattr__(layout, "star", NS(selected=Destination.STAR, cancel=Mock(), _snapshot_cache="old"))
|
|
object.__setattr__(layout, "page", NS(hide_event=Mock(), show_event=Mock()))
|
|
def opened_native(_panel):
|
|
layout._current_mode = runtime_app.MainState.SETTINGS
|
|
with patch.object(runtime_app.MainLayout, "open_settings", side_effect=opened_native) as opened:
|
|
layout.open_settings(PanelType.NETWORK)
|
|
opened.assert_called_once_with(PanelType.DEVICE)
|
|
self.assertEqual(layout.star.selected, Destination.NETWORK)
|
|
layout._network_bridge.enter.assert_called_once()
|
|
with patch.object(runtime_app.MainLayout, "_set_current_layout", side_effect=lambda mode: setattr(layout, "_current_mode", mode)):
|
|
layout._set_current_layout(runtime_app.MainState.ONROAD)
|
|
layout._network_bridge.leave.assert_called()
|
|
self.assertEqual(layout.star.selected, Destination.STAR)
|
|
layout.star.cancel.assert_called_once()
|
|
|
|
def test_body_transition_releases_network_before_native_layout_change(self):
|
|
layout = runtime_app.StarMainLayout.__new__(runtime_app.StarMainLayout)
|
|
layout._network_bridge = Mock()
|
|
layout._large_destination = None
|
|
layout._current_mode = runtime_app.MainState.SETTINGS
|
|
object.__setattr__(layout, "star", NS(selected=Destination.NETWORK, cancel=Mock(), _snapshot_cache="old"))
|
|
object.__setattr__(layout, "page", NS(hide_event=Mock(), show_event=Mock()))
|
|
with patch.object(runtime_app.MainLayout, "_on_body_changed",
|
|
side_effect=lambda: layout._set_current_layout(runtime_app.MainState.HOME)), \
|
|
patch.object(runtime_app.MainLayout, "_set_current_layout",
|
|
side_effect=lambda mode: setattr(layout, "_current_mode", mode)):
|
|
layout._on_body_changed()
|
|
layout._network_bridge.leave.assert_called_once()
|
|
self.assertEqual(layout.star.selected, Destination.STAR)
|