Files
StarPilot/openpilot/selfdrive/ui/tests/test_display_preferences.py
T
firestar5683 a82935f326 User interfaces and Android Auto
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>
2026-10-14 20:02:39 -05:00

268 lines
13 KiB
Python

"""Native Device policy with saved display choices and no hardware calls."""
from types import SimpleNamespace as NS
import tempfile
import unittest
from unittest.mock import Mock, patch
from openpilot.common.params import Params
from openpilot.selfdrive.ui import ui_state as module
from openpilot.system.ui.lib.application import MouseEvent, MousePos
from openpilot.starpilot.ui.display_preferences import SCREEN_OFF, read_preferences
class DisplayDeviceTests(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory()
self.addCleanup(temporary.cleanup)
self.params = Params(temporary.name)
self.state = NS(params=self.params, started=False, ignition=False, light_sensor=-1,
projection_read_only=False, sm=NS(valid={}, alive={}))
self.patches = [patch.object(module, "ui_state", self.state),
patch.object(module.gui_app, "big_ui", return_value=True),
patch.object(module.HARDWARE, "set_display_power"),
patch.object(module.HARDWARE, "set_screen_brightness")]
for guard in self.patches:
guard.start()
self.addCleanup(guard.stop)
filter_guard = patch.object(module.FirstOrderFilter, "update", autospec=True,
side_effect=lambda _filter, value: value)
self.filter_update = filter_guard.start()
self.addCleanup(filter_guard.stop)
self.device = module.Device()
def test_absent_master_matches_existing_auto_and_timeout_policy(self):
self.device._refresh_display_preferences()
self.assertFalse(self.device._display_preferences.enabled)
self.assertEqual(self.device.interactive_timeout, 30)
self.device._update_brightness()
self.assertEqual(self.device._brightness_target, module.BACKLIGHT_OFFROAD)
self.state.started = self.state.ignition = True
self.state.light_sensor = 100
self.assertEqual(self.device.interactive_timeout, 10)
self.device._update_brightness()
self.assertEqual(self.device._brightness_target, 100)
self.device._awake = False
self.device._update_brightness()
self.assertEqual(self.device._brightness_target, 0)
def test_manual_target_uses_existing_filter_and_wake_zero(self):
self.params.put_bool("StarPilotDisplayPreferencesEnabled", True, block=True)
self.params.put("ScreenBrightness", 40, block=True)
self.params.put("ScreenBrightnessOnroad", 75, block=True)
self.params.put("ScreenTimeout", 45, block=True)
self.params.put("ScreenTimeoutOnroad", 20, block=True)
self.device._refresh_display_preferences()
self.assertEqual(self.device.interactive_timeout, 45)
self.device._update_brightness()
self.filter_update.assert_called_with(self.device._brightness_filter, 40)
self.state.started = self.state.ignition = True
self.assertEqual(self.device.interactive_timeout, 20)
self.device._update_brightness()
self.filter_update.assert_called_with(self.device._brightness_filter, 75)
self.device._awake = False
self.device._update_brightness()
self.assertEqual(self.device._brightness_target, 0)
self.device.set_override_interactive_timeout(7)
self.assertEqual(self.device.interactive_timeout, 7)
def test_bounded_reads_invalidation_and_ignition_transition(self):
with patch.object(module, "read_preferences", wraps=read_preferences) as reads, \
patch.object(module.time, "monotonic", side_effect=[10.0, 10.2, 11.1, 11.2]):
self.device._refresh_display_preferences()
self.device._refresh_display_preferences()
self.assertEqual(reads.call_count, 1)
self.device._refresh_display_preferences()
self.assertEqual(reads.call_count, 2)
self.device.invalidate_display_preferences()
self.device._refresh_display_preferences()
self.assertEqual(reads.call_count, 3)
self.params.put_bool("StarPilotDisplayPreferencesEnabled", True, block=True)
self.device.invalidate_display_preferences()
self.device._refresh_display_preferences()
self.device._interaction_time = 1
with patch.object(self.device, "_reset_interactive_timeout", wraps=self.device._reset_interactive_timeout) as reset, \
patch.object(module.time, "monotonic", return_value=100.):
self.state.ignition = True
self.device._update_wakefulness()
reset.assert_called_once()
self.assertEqual(self.device._interaction_time, 130.)
self.assertTrue(self.device.awake)
def test_forced_offroad_uses_parked_timeout_with_live_ignition(self):
for saved in (False, True):
with self.subTest(saved=saved):
self.params.put_bool("StarPilotDisplayPreferencesEnabled", saved, block=True)
self.params.put("ScreenTimeout", 5, block=True)
self.params.put("ScreenTimeoutOnroad", 20, block=True)
self.state.started = self.state.ignition = True
device = module.Device()
callback = Mock()
device.add_interactive_timeout_callback(callback)
with patch.object(device, "_start_brightness_thread"), \
patch.object(module, "PC", False), \
patch.object(module.gui_app, "_mouse_events", []), \
patch.object(module.gui_app, "set_should_render"), \
patch.object(module.time, "monotonic", return_value=100.) as clock:
device.update()
self.assertTrue(device.awake)
self.state.started = False # hardwared force-offroad, physical ignition remains ON.
clock.return_value = 101.
device.update()
self.assertTrue(self.state.ignition)
self.assertEqual(device.interactive_timeout, 5 if saved else 30)
self.assertEqual(device._interaction_time, 106. if saved else 131.)
self.assertTrue(device.awake)
clock.return_value = 140.
device.update()
self.assertFalse(device.awake)
module.HARDWARE.set_display_power.assert_called_with(False)
callback.assert_called_once()
clock.return_value = 140.1
device.update()
self.assertFalse(device.awake)
self.assertEqual(device._brightness_target, 0)
callback.assert_called_once()
module.gui_app.mouse_events.append(MouseEvent(MousePos(0., 0.), 0, True, False, True, 141.))
clock.return_value = 141.
device.update()
self.assertTrue(device.awake)
module.HARDWARE.set_display_power.assert_called_with(True)
module.gui_app.mouse_events.clear()
self.state.started = True
clock.return_value = 142.
device.update()
self.assertTrue(device.awake)
self.assertEqual(device.interactive_timeout, 20 if saved else 10)
clock.return_value = 200.
device.update()
self.assertTrue(device.awake) # Effective onroad remains awake after its UI timeout.
def test_invalid_saved_values_fail_to_existing_auto_policy(self):
self.params.put_bool("StarPilotDisplayPreferencesEnabled", True, block=True)
self.params.put("ScreenBrightnessOnroad", 0, block=True)
saved = read_preferences(self.params, large=True)
self.assertTrue(saved.enabled)
self.assertEqual(saved.driving_brightness, 101)
self.params.put("ScreenTimeoutOnroad", 999, block=True)
self.assertEqual(read_preferences(self.params, large=True).driving_timeout, 10)
def test_screen_off_timeout_single_tap_wake_and_critical_alerts(self):
for large in (True, False):
with self.subTest(large=large), patch.object(module.gui_app, "big_ui", return_value=large):
self.state.sm = NS(valid={}, alive={})
self.device = module.Device()
self._check_screen_off_timeout_single_tap_wake_and_critical_alerts(large)
def _check_screen_off_timeout_single_tap_wake_and_critical_alerts(self, large):
self.params.put_bool(SCREEN_OFF, True, block=True)
self.state.started = self.state.ignition = True
with patch.object(self.device, "_start_brightness_thread"), \
patch.object(module.gui_app, "_mouse_events", []), \
patch.object(module.gui_app, "set_should_render") as render, \
patch.object(module.time, "monotonic", return_value=100.) as clock:
self.device.update()
self.assertTrue(self.device.awake)
self.assertEqual(self.device.interactive_timeout, 10 if large else 5)
clock.return_value = 106.
self.device.update()
self.assertEqual(self.device.awake, large)
clock.return_value = 111.
self.device.update()
self.assertFalse(self.device.awake)
self.assertEqual(self.device._brightness_target, 0)
render.assert_called_with(False)
module.HARDWARE.set_display_power.assert_called_with(False)
gui_events = module.gui_app.mouse_events
gui_events.append(MouseEvent(MousePos(0., 0.), 0, True, False, True, 112.))
clock.return_value = 112.
self.device.update()
self.assertTrue(self.device.awake)
self.assertGreater(self.device._brightness_target, 0)
self.assertEqual(gui_events, []) # Wake press cannot activate an underlying button.
render.assert_called_with(True)
gui_events.append(MouseEvent(MousePos(0., 0.), 0, False, True, False, 112.1))
clock.return_value = 112.1
self.device.update()
self.assertEqual(gui_events, []) # Wake release is consumed too.
clock.return_value = 123.
self.device.update()
self.assertFalse(self.device.awake)
alert = NS(alertStatus=module.log.SelfdriveState.AlertStatus.critical)
class Messages(dict):
valid = {"selfdriveState": True}
alive = {"selfdriveState": True}
self.state.sm = Messages(selfdriveState=alert)
self.device.update()
self.assertTrue(self.device.awake)
alert.alertStatus = module.log.SelfdriveState.AlertStatus.normal
clock.return_value = 124.
self.device.update()
self.assertTrue(self.device.awake)
clock.return_value = 134.
self.device.update()
self.assertFalse(self.device.awake)
self.assertTrue(self.device.toggle_screen_off()) # Existing controller shortcut also wakes automatic sleep.
self.assertTrue(self.device.awake)
clock.return_value = 155.
self.device.update()
self.assertFalse(self.device.awake)
self.state.started = False
self.device.update()
self.assertTrue(self.device.awake)
def test_screen_off_setting_changes_wake_and_use_saved_driving_timeout(self):
self.params.put_bool("StarPilotDisplayPreferencesEnabled", True, block=True)
self.params.put("ScreenTimeoutOnroad", 20, block=True)
self.params.put_bool(SCREEN_OFF, True, block=True)
self.state.started = self.state.ignition = True
with patch.object(self.device, "_start_brightness_thread"), \
patch.object(module.gui_app, "_mouse_events", []), \
patch.object(module.gui_app, "set_should_render"), \
patch.object(module.time, "monotonic", return_value=100.) as clock:
self.device.update()
clock.return_value = 111.
self.device.update()
self.assertTrue(self.device.awake)
clock.return_value = 121.
self.device.update()
self.assertFalse(self.device.awake)
self.params.put_bool(SCREEN_OFF, False, block=True)
self.device.invalidate_display_preferences()
self.device.update()
self.assertTrue(self.device.awake)
clock.return_value = 200.
self.device.update()
self.assertTrue(self.device.awake)
def test_started_transition_refreshes_saved_profile_without_worker_restart(self):
self.params.put_bool("StarPilotDisplayPreferencesEnabled", True, block=True)
self.params.put("ScreenBrightnessOnroad", 80, block=True)
with patch.object(self.device, "_start_brightness_thread") as start, \
patch.object(module, "read_preferences", wraps=read_preferences) as reads:
self.device.update()
self.device.update()
self.assertEqual(reads.call_count, 1)
self.state.started = self.state.ignition = True
self.device.update()
self.assertEqual(reads.call_count, 2)
self.assertEqual(self.filter_update.call_args.args[-1], 80)
self.assertEqual(start.call_count, 3)
def test_parked_timeout_callback_and_ignition_wake(self):
self.params.put_bool("StarPilotDisplayPreferencesEnabled", True, block=True)
self.params.put("ScreenTimeout", 5, block=True)
self.device._refresh_display_preferences()
callback = Mock()
self.device.add_interactive_timeout_callback(callback)
self.device._interaction_time = 1
with patch.object(module, "PC", False), patch.object(module.gui_app, "set_should_render") as render:
self.device._update_wakefulness()
callback.assert_called_once()
self.assertFalse(self.device.awake)
render.assert_called_with(False)
self.state.ignition = True
self.device._update_wakefulness()
self.assertTrue(self.device.awake)
render.assert_called_with(True)