more ty, part 2 (#38379)

This commit is contained in:
Adeeb Shihadeh
2026-07-19 11:02:00 -07:00
committed by GitHub
parent 19ecc37de8
commit f0d93eb32d
57 changed files with 194 additions and 165 deletions
+4 -1
View File
@@ -669,7 +669,10 @@ def log_handler(end_event: threading.Event) -> None:
def ws_proxy_recv(ws: WebSocket, local_sock: socket.socket, ssock: socket.socket, end_event: threading.Event, global_end_event: threading.Event) -> None:
while not (end_event.is_set() or global_end_event.is_set()):
try:
r = select.select((ws.sock,), (), (), 30)
sock = ws.sock
if sock is None:
return
r = select.select((sock,), (), (), 30)
if r[0]:
data = ws.recv()
if isinstance(data, str):
@@ -60,7 +60,7 @@ class TestAthenadMethods:
@classmethod
def setup_class(cls):
cls.SOCKET_PORT = 45454
athenad.Api = MockApi
athenad.Api = MockApi # ty: ignore[invalid-assignment] # test double
athenad.LOCAL_PORT_WHITELIST = {cls.SOCKET_PORT}
def setup_method(self):
@@ -351,6 +351,7 @@ class TestAthenadMethods:
assert items[0] == asdict(item)
assert not items[0]['current']
assert item.id is not None
athenad.cancelled_uploads.add(item.id)
items = dispatcher["listUploadQueue"]()
assert len(items) == 0
@@ -363,6 +364,7 @@ class TestAthenadMethods:
athenad.upload_queue.put_nowait(item2)
# Ensure canceled items are not persisted
assert item2.id is not None
athenad.cancelled_uploads.add(item2.id)
# serialize item
@@ -34,7 +34,7 @@ class PowerMonitoring:
self.car_battery_capacity_uWh = max((CAR_BATTERY_CAPACITY_uWh / 10), car_battery_capacity_uWh)
# Calculation tick
def calculate(self, voltage: int | None, ignition: bool):
def calculate(self, voltage: float | None, ignition: bool):
try:
now = time.monotonic()
@@ -35,7 +35,7 @@ class TestPowerMonitoring:
def test_panda_state_present(self):
pm = PowerMonitoring()
for _ in range(10):
pm.calculate(None, None)
pm.calculate(None, False)
assert pm.get_power_used() == 0
assert pm.get_car_battery_capacity() == (CAR_BATTERY_CAPACITY_uWh / 10)
@@ -63,10 +63,10 @@ class UploaderTestCase:
seg_dir: str
def set_ignore(self):
uploader.Api = MockApiIgnore
uploader.Api = MockApiIgnore # ty: ignore[invalid-assignment] # test double
def setup_method(self):
uploader.Api = MockApi
uploader.Api = MockApi # ty: ignore[invalid-assignment] # test double
uploader.fake_upload = True
uploader.force_wifi = True
uploader.allow_sleep = False
@@ -19,7 +19,7 @@ class TestDeleter(UploaderTestCase):
self.f_type = "fcamera.hevc"
super().setup_method()
self.fake_stats = Stats(f_bavail=0, f_blocks=10, f_frsize=4096)
deleter.os.statvfs = self.fake_statvfs
deleter.os.statvfs = self.fake_statvfs # ty: ignore[invalid-assignment] # test double
def start_thread(self):
self.end_event = threading.Event()
@@ -5,6 +5,7 @@ import random
import string
import subprocess
import time
from collections.abc import Collection
from collections import defaultdict
from pathlib import Path
import pytest
@@ -74,8 +75,8 @@ class TestLoggerd:
end_type = SentinelType.endOfRoute if route else SentinelType.endOfSegment
assert msgs[-1].sentinel.type == end_type
def _publish_random_messages(self, services: list[str]) -> dict[str, list]:
pm = messaging.PubMaster(services)
def _publish_random_messages(self, services: Collection[str]) -> dict[str, list]:
pm = messaging.PubMaster(list(services))
managed_processes["loggerd"].start()
for s in services:
+36 -36
View File
@@ -1,9 +1,9 @@
import os
import sys
from dataclasses import dataclass, fields
from dataclasses import dataclass
from subprocess import check_output, CalledProcessError
from time import sleep
from typing import NoReturn, cast
from typing import NoReturn
DEBUG = int(os.environ.get("DEBUG", "0"))
@@ -17,27 +17,27 @@ class GnssClockNmeaPort:
# 0x10 = bias_uncertainty_ns valid
# 0x20 = drift_nsps valid
# 0x40 = drift_uncertainty_nsps valid
flags: int
leap_seconds: int
time_ns: int
time_uncertainty_ns: int # 1-sigma
full_bias_ns: int
bias_ns: float
bias_uncertainty_ns: float # 1-sigma
drift_nsps: float
drift_uncertainty_nsps: float # 1-sigma
flags: int | None
leap_seconds: int | None
time_ns: int | None
time_uncertainty_ns: int | None # 1-sigma
full_bias_ns: int | None
bias_ns: float | None
bias_uncertainty_ns: float | None # 1-sigma
drift_nsps: float | None
drift_uncertainty_nsps: float | None # 1-sigma
def __post_init__(self):
for field in fields(self):
val = getattr(self, field.name)
field_type = cast(type, field.type)
setattr(self, field.name, field_type(val) if val else None)
@classmethod
def from_fields(cls, values: list[str]) -> 'GnssClockNmeaPort':
ints = [int(value) if value else None for value in values[:5]]
floats = [float(value) if value else None for value in values[5:9]]
return cls(*ints, *floats)
@dataclass
class GnssMeasNmeaPort:
messageCount: int
messageNum: int
svCount: int
messageCount: int | None
messageNum: int | None
svCount: int | None
# constellation enum:
# 1 = GPS
# 2 = SBAS
@@ -45,10 +45,10 @@ class GnssMeasNmeaPort:
# 4 = QZSS
# 5 = BEIDOU
# 6 = GALILEO
constellation: int
svId: int
flags: int # always zero
time_offset_ns: int
constellation: int | None
svId: int | None
flags: int | None # always zero
time_offset_ns: int | None
# state bit mask:
# 0x0001 = CODE LOCK
# 0x0002 = BIT SYNC
@@ -64,18 +64,18 @@ class GnssMeasNmeaPort:
# 0x0800 = GALILEO E1C 2ND CODE LOCK
# 0x1000 = GALILEO E1B PAGE SYNC
# 0x2000 = GALILEO E1B PAGE SYNC
state: int
time_of_week_ns: int
time_of_week_uncertainty_ns: int # 1-sigma
carrier_to_noise_ratio: float
pseudorange_rate: float
pseudorange_rate_uncertainty: float # 1-sigma
state: int | None
time_of_week_ns: int | None
time_of_week_uncertainty_ns: int | None # 1-sigma
carrier_to_noise_ratio: float | None
pseudorange_rate: float | None
pseudorange_rate_uncertainty: float | None # 1-sigma
def __post_init__(self):
for field in fields(self):
val = getattr(self, field.name)
field_type = cast(type, field.type)
setattr(self, field.name, field_type(val) if val else None)
@classmethod
def from_fields(cls, values: list[str]) -> 'GnssMeasNmeaPort':
ints = [int(value) if value else None for value in values[:10]]
floats = [float(value) if value else None for value in values[10:13]]
return cls(*ints, *floats)
def nmea_checksum_ok(s):
checksum = 0
@@ -109,11 +109,11 @@ def process_nmea_port_messages(device:str="/dev/ttyUSB1") -> NoReturn:
match fields[0]:
case "$GNCLK":
# fields at end are reserved (not used)
gnss_clock = GnssClockNmeaPort(*fields[1:10])
gnss_clock = GnssClockNmeaPort.from_fields(fields[1:10])
print(gnss_clock)
case "$GNMEAS":
# fields at end are reserved (not used)
gnss_meas = GnssMeasNmeaPort(*fields[1:14])
gnss_meas = GnssMeasNmeaPort.from_fields(fields[1:14])
print(gnss_meas)
except Exception as e:
print(e)
@@ -20,7 +20,7 @@ SENSOR_CONFIGS = (
)
SENSOR_CONFIGS_BY_MEASUREMENT = {config.measurement: config for config in SENSOR_CONFIGS}
def get_irq_count(irq: int):
def get_irq_count(irq: str):
with open(f"/sys/kernel/irq/{irq}/per_cpu_count") as f:
per_cpu = map(int, f.read().split(","))
return sum(per_cpu)
+1 -1
View File
@@ -184,7 +184,7 @@ class BinaryStruct:
setattr(obj, name, value)
return obj
cls._read = _read
cls._read = _read # ty: ignore[invalid-assignment] # installed dynamically for each subclass
@classmethod
def from_bytes(cls: type[T], data: bytes) -> T:
+2 -1
View File
@@ -1,8 +1,9 @@
import pyray as rl
from collections.abc import Sequence
class GuiStyleContext:
def __init__(self, styles: list[tuple[int, int, int]]):
def __init__(self, styles: Sequence[tuple[int, int, int]]):
"""styles is a list of tuples (control, prop, new_value)"""
self.styles = styles
self.prev_styles: list[tuple[int, int, int]] = []
+1 -1
View File
@@ -257,7 +257,7 @@ class WifiManager:
def add_callbacks(self, need_auth: Callable[[str], None] | None = None,
activated: Callable[[], None] | None = None,
forgotten: Callable[[str], None] | None = None,
forgotten: Callable[[str | None], None] | None = None,
networks_updated: Callable[[list[Network]], None] | None = None,
disconnected: Callable[[], None] | None = None):
if need_auth is not None:
+5 -1
View File
@@ -37,7 +37,11 @@ class Reset(Widget):
self._reset_state = ResetState.NONE
self._cancel_button = Button("Cancel", gui_app.request_close)
self._confirm_button = Button("Confirm", self._confirm, button_style=ButtonStyle.PRIMARY)
self._reboot_button = Button("Reboot", lambda: subprocess.run("sudo reboot", shell=True))
self._reboot_button = Button("Reboot", self._reboot)
@staticmethod
def _reboot() -> None:
subprocess.run("sudo reboot", shell=True)
def _do_erase(self):
if PC:
+19 -10
View File
@@ -3,16 +3,25 @@ from __future__ import annotations
import abc
import pyray as rl
from enum import IntEnum
from typing import TypeVar
from typing import Protocol, TypeVar
from collections.abc import Callable
from openpilot.system.ui.lib.application import gui_app, MousePos, MAX_TOUCH_SLOTS, MouseEvent
try:
from openpilot.selfdrive.ui.ui_state import device
except ImportError:
class Device:
awake = True
device = Device()
class DeviceLike(Protocol):
awake: bool
def _get_device() -> DeviceLike:
try:
from openpilot.selfdrive.ui.ui_state import device
return device
except ImportError:
class Device:
awake = True
return Device()
device = _get_device()
W = TypeVar('W', bound='Widget')
@@ -185,16 +194,16 @@ class Widget(abc.ABC):
"""Optionally update the widget's non-layout state. This is called before rendering."""
@abc.abstractmethod
def _render(self, rect: rl.Rectangle) -> bool | int | None:
def _render(self, rect: rl.Rectangle, /) -> bool | int | None:
"""Render the widget within the given rectangle."""
def _update_layout_rects(self) -> None:
"""Optionally update any layout rects on Widget rect change."""
def _handle_mouse_press(self, mouse_pos: MousePos) -> None:
def _handle_mouse_press(self, mouse_pos: MousePos, /) -> None:
"""Optionally handle mouse press events."""
def _handle_mouse_release(self, mouse_pos: MousePos) -> None:
def _handle_mouse_release(self, mouse_pos: MousePos, /) -> None:
"""Optionally handle mouse release events."""
if self._click_delay is not None:
self._click_release_time = rl.get_time() + self._click_delay
+1 -2
View File
@@ -1,7 +1,6 @@
import math
from enum import IntEnum
from collections.abc import Callable
from itertools import zip_longest
from typing import Union
import pyray as rl
@@ -210,7 +209,7 @@ class Label(Widget):
icon_x = self._rect.x + (self._rect.width - self._icon.width) / 2
rl.draw_texture_v(self._icon, rl.Vector2(icon_x, icon_y), rl.WHITE)
for text, text_size, emojis in zip_longest(self._text_wrapped, self._text_size, self._emojis, fillvalue=[]):
for text, text_size, emojis in zip(self._text_wrapped, self._text_size, self._emojis, strict=True):
line_pos = rl.Vector2(text_pos.x, text_pos.y)
if self._text_alignment == rl.GuiTextAlignment.TEXT_ALIGN_LEFT:
line_pos.x += self._text_padding
+1 -1
View File
@@ -78,7 +78,7 @@ class NavWidget(Widget, abc.ABC):
# the top of a vertical scroll panel to prevent erroneous swipes
return True
def set_back_callback(self, callback: Callable[[], None]) -> None:
def set_back_callback(self, callback: Callable[[], None] | None) -> None:
self._back_callback = callback
def set_shown_callback(self, callback: Callable[[], None] | None) -> None:
+7 -12
View File
@@ -15,16 +15,6 @@ from openpilot.system.ui.widgets.label import gui_label
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets.list_view import ButtonAction, ListItem, MultipleButtonAction, ToggleAction, button_item, text_item
# These are only used for AdvancedNetworkSettings, standalone apps just need WifiManagerUI
try:
from openpilot.common.params import Params
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.selfdrive.ui.lib.prime_state import PrimeType
except Exception:
Params = None
ui_state = None
PrimeType = None
NM_DEVICE_STATE_NEED_AUTH = 60
MIN_PASSWORD_LENGTH = 8
MAX_PASSWORD_LENGTH = 64
@@ -105,11 +95,16 @@ class NetworkUI(Widget):
class AdvancedNetworkSettings(Widget):
def __init__(self, wifi_manager: WifiManager):
assert Params is not None
# AdvancedNetworkSettings needs the full openpilot environment, standalone apps just use WifiManagerUI
from openpilot.common.params import Params
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.selfdrive.ui.lib.prime_state import PrimeType
super().__init__()
self._wifi_manager = wifi_manager
self._wifi_manager.add_callbacks(networks_updated=self._on_network_updated)
self._params = Params()
self._prime_state = ui_state.prime_state
self._cell_prime_types = (PrimeType.NONE, PrimeType.LITE)
self._keyboard = Keyboard(max_text_size=MAX_PASSWORD_LENGTH, min_text_size=MIN_PASSWORD_LENGTH, show_password_toggle=True)
@@ -254,7 +249,7 @@ class AdvancedNetworkSettings(Widget):
self._wifi_manager.process_callbacks()
# If not using prime SIM, show GSM settings and enable IPv4 forwarding
show_cell_settings = ui_state.prime_state.get_type() in (PrimeType.NONE, PrimeType.LITE)
show_cell_settings = self._prime_state.get_type() in self._cell_prime_types
self._wifi_manager.set_ipv4_forward(show_cell_settings)
self._roaming_btn.set_visible(show_cell_settings)
self._apn_btn.set_visible(show_cell_settings)
+6 -6
View File
@@ -1,6 +1,6 @@
import pyray as rl
import numpy as np
from collections.abc import Callable
from collections.abc import Callable, Sequence
from openpilot.common.filter_simple import FirstOrderFilter, BounceFilter
from openpilot.common.swaglog import cloudlog
@@ -40,7 +40,7 @@ class ScrollIndicator(Widget):
self._content_size = content_size
self._viewport = viewport
def _render(self, _):
def _render(self, _, /):
# scale indicator width based on content size
indicator_w = float(np.interp(self._content_size, [1000, 3000], [300, 100]))
@@ -69,7 +69,7 @@ class ScrollIndicator(Widget):
class _Scroller(Widget):
"""Should use wrapper below to reduce boilerplate"""
def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: bool = False, spacing: int = ITEM_SPACING,
def __init__(self, items: Sequence[Widget], horizontal: bool = True, snap_items: bool = False, spacing: int = ITEM_SPACING,
pad: int = ITEM_SPACING, scroll_indicator: bool = True, edge_shadows: bool = True):
super().__init__()
self._items: list[Widget] = []
@@ -150,7 +150,7 @@ class _Scroller(Widget):
and not self.moving_items and (original_touch_valid_callback() if
original_touch_valid_callback else True))
def add_widgets(self, items: list[Widget]) -> None:
def add_widgets(self, items: Sequence[Widget]) -> None:
for item in items:
self.add_widget(item)
@@ -332,7 +332,7 @@ class _Scroller(Widget):
else:
item.render()
def _render(self, _):
def _render(self, _, /):
rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y),
int(self._rect.width), int(self._rect.height))
@@ -397,7 +397,7 @@ class Scroller(Widget):
# pass down enabled to child widget for nav stack
self._scroller.set_enabled(lambda: self.enabled)
def _render(self, _):
def _render(self, _, /):
self._scroller.render(self._rect)
+2 -1
View File
@@ -1,4 +1,5 @@
import pyray as rl
from collections.abc import Sequence
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
from openpilot.system.ui.widgets import Widget
@@ -23,7 +24,7 @@ class LineSeparator(Widget):
class Scroller(Widget):
def __init__(self, items: list[Widget], spacing: int = ITEM_SPACING, line_separator: bool = False, pad_end: bool = True):
def __init__(self, items: Sequence[Widget], spacing: int = ITEM_SPACING, line_separator: bool = False, pad_end: bool = True):
super().__init__()
self._items: list[Widget] = []
self._spacing = spacing
+1 -1
View File
@@ -357,7 +357,7 @@ class Updater:
setup_git_options(OVERLAY_MERGED)
output = run(["git", "ls-remote", "--heads"], OVERLAY_MERGED)
self.branches = defaultdict(lambda: None)
self.branches.clear()
for line in output.split('\n'):
ls_remotes_re = r'(?P<commit_sha>\b[0-9a-f]{5,40}\b)(\s+)(refs\/heads\/)(?P<branch_name>.*$)'
x = re.fullmatch(ls_remotes_re, line.strip())
@@ -55,9 +55,11 @@ class TestStreamSession:
mocked_pubmaster.send.assert_called_once()
mt, md = mocked_pubmaster.send.call_args.args
assert mt == msg["type"]
msg_type = msg["type"]
assert isinstance(msg_type, str)
assert mt == msg_type
assert isinstance(md, capnp._DynamicStructBuilder)
assert hasattr(md, msg["type"])
assert hasattr(md, msg_type)
mocked_pubmaster.reset_mock()
+1 -1
View File
@@ -519,7 +519,7 @@ class WebrtcdHandler(BaseHTTPRequestHandler):
def do_OPTIONS(self) -> None:
self._dispatch_request()
def log_message(self, fmt, *args) -> None:
def log_message(self, format: str, *args: object) -> None: # noqa: A002 # stdlib override
# silence default access logging; errors are logged explicitly in _dispatch_request
pass