Compare commits

..

1 Commits

Author SHA1 Message Date
royjr d5b5652165 ui: external storage 2026-07-22 10:00:19 -04:00
247 changed files with 3324 additions and 4618 deletions
-8
View File
@@ -93,14 +93,6 @@ jobs:
const { owner, repo } = context.repo; const { owner, repo } = context.repo;
const upstream = `${owner}/${repo}`; const upstream = `${owner}/${repo}`;
const closed = context.payload.pull_request;
if (closed) {
if (closed.head.repo?.full_name === upstream) {
await github.rest.git.deleteRef({ owner, repo, ref: `heads/${closed.head.ref}` }).catch(console.log);
}
return;
}
for await (const response of github.paginate.iterator(github.rest.pulls.list, { for await (const response of github.paginate.iterator(github.rest.pulls.list, {
owner, owner,
repo, repo,
-5
View File
@@ -166,11 +166,6 @@ env = Environment(
tools=["default", "cython", "compilation_db", "rednose_filter"], tools=["default", "cython", "compilation_db", "rednose_filter"],
toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"], toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
) )
# SCons' Darwin linker tool doesn't define the variables used to expand RPATH.
if arch == "Darwin":
env["RPATHPREFIX"] = "-Wl,-rpath,"
env["RPATHSUFFIX"] = ""
env["_RPATH"] = "${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}"
if arch != "larch64": if arch != "larch64":
env['_LIBFLAGS'] = _libflags env['_LIBFLAGS'] = _libflags
+1 -1
View File
@@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1
export QCOM_PRIORITY=12 export QCOM_PRIORITY=12
if [ -z "$AGNOS_VERSION" ]; then if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="18.5" export AGNOS_VERSION="18.4"
fi fi
export STAGING_ROOT="/data/safe_staging" export STAGING_ROOT="/data/safe_staging"
+1 -3
View File
@@ -812,7 +812,6 @@ struct SelfdriveState {
promptDistracted @8; promptDistracted @8;
preAlert @9; preAlert @9;
complete @10;
} }
enum OpenpilotState @0xdbe58b96d2d1ac61 { enum OpenpilotState @0xdbe58b96d2d1ac61 {
@@ -2161,8 +2160,7 @@ struct DriverMonitoringStateDEPRECATED @0xb83cda094a1da284 {
struct DriverMonitoringState { struct DriverMonitoringState {
lockout @0 :Bool; lockout @0 :Bool;
lockoutCount @15 :Int8; lockoutRecoveryPercent @11 :Int8;
lockoutMinutesRemaining @11 :Int8;
alert3Count @12 :Int8; alert3Count @12 :Int8;
noResponseCount @13 :Int8; noResponseCount @13 :Int8;
noResponseForceDecel @14 :Bool; noResponseForceDecel @14 :Bool;
+24 -55
View File
@@ -7,43 +7,12 @@ import os
import capnp import capnp
import time import time
from typing import Union from typing import Optional, List, Union, Dict
from openpilot.cereal import log from openpilot.cereal import log
from openpilot.cereal.services import SERVICE_LIST from openpilot.cereal.services import SERVICE_LIST
from openpilot.common.utils import MovingAverage from openpilot.common.utils import MovingAverage
__all__ = (
"NO_TRAVERSAL_LIMIT",
"Context",
"FrequencyTracker",
"IpcError",
"MultiplePublishersError",
"Poller",
"PubMaster",
"PubSocket",
"SocketEventHandle",
"SubMaster",
"SubSocket",
"delete_fake_prefix",
"drain_sock",
"drain_sock_raw",
"fake_event_handle",
"get_fake_prefix",
"log_from_bytes",
"new_message",
"pub_sock",
"recv_one",
"recv_one_or_none",
"recv_one_retry",
"recv_sock",
"reset_context",
"set_fake_prefix",
"sub_sock",
"toggle_fake_events",
"wait_for_one_event",
)
NO_TRAVERSAL_LIMIT = 2**64-1 NO_TRAVERSAL_LIMIT = 2**64-1
@@ -53,8 +22,8 @@ def pub_sock(endpoint: str) -> PubSocket:
return msgq.pub_sock(endpoint, segment_size) return msgq.pub_sock(endpoint, segment_size)
def sub_sock(endpoint: str, poller: Poller | None = None, addr: str = "127.0.0.1", def sub_sock(endpoint: str, poller: Optional[Poller] = None, addr: str = "127.0.0.1",
conflate: bool = False, timeout: int | None = None) -> SubSocket: conflate: bool = False, timeout: Optional[int] = None) -> SubSocket:
service = SERVICE_LIST.get(endpoint) service = SERVICE_LIST.get(endpoint)
segment_size = service.queue_size if service else 0 segment_size = service.queue_size if service else 0
return msgq.sub_sock(endpoint, poller=poller, addr=addr, conflate=conflate, return msgq.sub_sock(endpoint, poller=poller, addr=addr, conflate=conflate,
@@ -70,7 +39,7 @@ def log_from_bytes(dat: bytes, struct: capnp.lib.capnp._StructModule = log.Event
return msg return msg
def new_message(service: str | None, size: int | None = None, **kwargs) -> capnp.lib.capnp._DynamicStructBuilder: def new_message(service: Optional[str], size: Optional[int] = None, **kwargs) -> capnp.lib.capnp._DynamicStructBuilder:
args = { args = {
'valid': False, 'valid': False,
'logMonoTime': int(time.monotonic() * 1e9), 'logMonoTime': int(time.monotonic() * 1e9),
@@ -85,14 +54,14 @@ def new_message(service: str | None, size: int | None = None, **kwargs) -> capnp
return dat return dat
def drain_sock(sock: SubSocket, wait_for_one: bool = False) -> list[capnp.lib.capnp._DynamicStructReader]: def drain_sock(sock: SubSocket, wait_for_one: bool = False) -> List[capnp.lib.capnp._DynamicStructReader]:
"""Receive all message currently available on the queue""" """Receive all message currently available on the queue"""
msgs = drain_sock_raw(sock, wait_for_one=wait_for_one) msgs = drain_sock_raw(sock, wait_for_one=wait_for_one)
return [log_from_bytes(m) for m in msgs] return [log_from_bytes(m) for m in msgs]
# TODO: print when we drop packets? # TODO: print when we drop packets?
def recv_sock(sock: SubSocket, wait: bool = False) -> capnp.lib.capnp._DynamicStructReader | None: def recv_sock(sock: SubSocket, wait: bool = False) -> Optional[capnp.lib.capnp._DynamicStructReader]:
"""Same as drain sock, but only returns latest message. Consider using conflate instead.""" """Same as drain sock, but only returns latest message. Consider using conflate instead."""
dat = None dat = None
@@ -113,14 +82,14 @@ def recv_sock(sock: SubSocket, wait: bool = False) -> capnp.lib.capnp._DynamicSt
return dat return dat
def recv_one(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader | None: def recv_one(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
dat = sock.receive() dat = sock.receive()
if dat is not None: if dat is not None:
dat = log_from_bytes(dat) dat = log_from_bytes(dat)
return dat return dat
def recv_one_or_none(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader | None: def recv_one_or_none(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
dat = sock.receive(non_blocking=True) dat = sock.receive(non_blocking=True)
if dat is not None: if dat is not None:
dat = log_from_bytes(dat) dat = log_from_bytes(dat)
@@ -179,27 +148,27 @@ class FrequencyTracker:
class SubMaster: class SubMaster:
def __init__(self, services: list[str], poll: str | None = None, def __init__(self, services: List[str], poll: Optional[str] = None,
ignore_alive: list[str] | None = None, ignore_avg_freq: list[str] | None = None, ignore_alive: Optional[List[str]] = None, ignore_avg_freq: Optional[List[str]] = None,
ignore_valid: list[str] | None = None, addr: str = "127.0.0.1", frequency: float | None = None): ignore_valid: Optional[List[str]] = None, addr: str = "127.0.0.1", frequency: Optional[float] = None):
self.frame = -1 self.frame = -1
self.services = services self.services = services
self.seen = dict.fromkeys(services, False) self.seen = {s: False for s in services}
self.updated = dict.fromkeys(services, False) self.updated = {s: False for s in services}
self.recv_time = dict.fromkeys(services, 0.0) self.recv_time = {s: 0. for s in services}
self.recv_frame = dict.fromkeys(services, 0) self.recv_frame = {s: 0 for s in services}
self.sock = {} self.sock = {}
self.data = {} self.data = {}
self.logMonoTime = dict.fromkeys(services, 0) self.logMonoTime = {s: 0 for s in services}
# zero-frequency / on-demand services are always alive and presumed valid; all others must pass checks # zero-frequency / on-demand services are always alive and presumed valid; all others must pass checks
on_demand = {s: SERVICE_LIST[s].frequency <= 1e-5 for s in services} on_demand = {s: SERVICE_LIST[s].frequency <= 1e-5 for s in services}
self.static_freq_services = {s for s in services if not on_demand[s]} self.static_freq_services = set(s for s in services if not on_demand[s])
self.alive = {s: on_demand[s] for s in services} self.alive = {s: on_demand[s] for s in services}
self.freq_ok = {s: on_demand[s] for s in services} self.freq_ok = {s: on_demand[s] for s in services}
self.valid = {s: on_demand[s] for s in services} self.valid = {s: on_demand[s] for s in services}
self.freq_tracker: dict[str, FrequencyTracker] = {} self.freq_tracker: Dict[str, FrequencyTracker] = {}
self.poller = Poller() self.poller = Poller()
polled_services = set([poll, ] if poll is not None else services) polled_services = set([poll, ] if poll is not None else services)
self.non_polled_services = set(services) - polled_services self.non_polled_services = set(services) - polled_services
@@ -242,7 +211,7 @@ class SubMaster:
msgs.append(recv_one_or_none(self.sock[s])) msgs.append(recv_one_or_none(self.sock[s]))
self.update_msgs(time.monotonic(), msgs) self.update_msgs(time.monotonic(), msgs)
def update_msgs(self, cur_time: float, msgs: list[capnp.lib.capnp._DynamicStructReader]) -> None: def update_msgs(self, cur_time: float, msgs: List[capnp.lib.capnp._DynamicStructReader]) -> None:
self.frame += 1 self.frame += 1
self.updated = dict.fromkeys(self.services, False) self.updated = dict.fromkeys(self.services, False)
for msg in msgs: for msg in msgs:
@@ -265,21 +234,21 @@ class SubMaster:
self.alive[s] = (cur_time - self.recv_time[s]) < (10. / SERVICE_LIST[s].frequency) or (self.seen[s] and self.simulation) self.alive[s] = (cur_time - self.recv_time[s]) < (10. / SERVICE_LIST[s].frequency) or (self.seen[s] and self.simulation)
self.freq_ok[s] = self.freq_tracker[s].valid or self.simulation self.freq_ok[s] = self.freq_tracker[s].valid or self.simulation
def all_alive(self, service_list: list[str] | None = None) -> bool: def all_alive(self, service_list: Optional[List[str]] = None) -> bool:
return all(self.alive[s] for s in (service_list or self.services) if s not in self.ignore_alive) return all(self.alive[s] for s in (service_list or self.services) if s not in self.ignore_alive)
def all_freq_ok(self, service_list: list[str] | None = None) -> bool: def all_freq_ok(self, service_list: Optional[List[str]] = None) -> bool:
return all(self.freq_ok[s] for s in (service_list or self.services) if self._check_avg_freq(s)) return all(self.freq_ok[s] for s in (service_list or self.services) if self._check_avg_freq(s))
def all_valid(self, service_list: list[str] | None = None) -> bool: def all_valid(self, service_list: Optional[List[str]] = None) -> bool:
return all(self.valid[s] for s in (service_list or self.services) if s not in self.ignore_valid) return all(self.valid[s] for s in (service_list or self.services) if s not in self.ignore_valid)
def all_checks(self, service_list: list[str] | None = None) -> bool: def all_checks(self, service_list: Optional[List[str]] = None) -> bool:
return self.all_alive(service_list) and self.all_freq_ok(service_list) and self.all_valid(service_list) return self.all_alive(service_list) and self.all_freq_ok(service_list) and self.all_valid(service_list)
class PubMaster: class PubMaster:
def __init__(self, services: list[str]): def __init__(self, services: List[str]):
self.sock = {} self.sock = {}
for s in services: for s in services:
self.sock[s] = pub_sock(s) self.sock[s] = pub_sock(s)
@@ -1,3 +1,4 @@
import os
import capnp import capnp
import multiprocessing import multiprocessing
import numbers import numbers
@@ -5,6 +6,7 @@ import random
import threading import threading
import time import time
from openpilot.common.parameterized import parameterized from openpilot.common.parameterized import parameterized
import pytest
from openpilot.cereal import log from openpilot.cereal import log
from opendbc.car.structs import car from opendbc.car.structs import car
@@ -22,6 +24,10 @@ def random_socks(num_socks=10):
def random_bytes(length=1000): def random_bytes(length=1000):
return bytes([random.randrange(0xFF) for _ in range(length)]) return bytes([random.randrange(0xFF) for _ in range(length)])
def zmq_sleep(t=1):
if "ZMQ" in os.environ:
time.sleep(t)
# TODO: this should take any capnp struct and returrn a msg with random populated data # TODO: this should take any capnp struct and returrn a msg with random populated data
def random_carstate(): def random_carstate():
@@ -47,6 +53,16 @@ def delayed_send(delay, sock, dat):
class TestMessaging: class TestMessaging:
def setUp(self):
# TODO: ZMQ tests are too slow; all sleeps will need to be
# replaced with logic to block on the necessary condition
if "ZMQ" in os.environ:
pytest.skip()
# ZMQ pub socket takes too long to die
# sleep to prevent multiple publishers error between tests
zmq_sleep()
@parameterized.expand(events) @parameterized.expand(events)
def test_new_message(self, evt): def test_new_message(self, evt):
try: try:
@@ -73,6 +89,7 @@ class TestMessaging:
sock = "carState" sock = "carState"
pub_sock = messaging.pub_sock(sock) pub_sock = messaging.pub_sock(sock)
sub_sock = messaging.sub_sock(sock, timeout=1000) sub_sock = messaging.sub_sock(sock, timeout=1000)
zmq_sleep()
# no wait and no msgs in queue # no wait and no msgs in queue
msgs = func(sub_sock) msgs = func(sub_sock)
@@ -93,6 +110,7 @@ class TestMessaging:
sock = "carState" sock = "carState"
pub_sock = messaging.pub_sock(sock) pub_sock = messaging.pub_sock(sock)
sub_sock = messaging.sub_sock(sock, timeout=100) sub_sock = messaging.sub_sock(sock, timeout=100)
zmq_sleep()
# no wait and no msg in queue, socket should timeout # no wait and no msg in queue, socket should timeout
recvd = messaging.recv_sock(sub_sock) recvd = messaging.recv_sock(sub_sock)
@@ -111,6 +129,7 @@ class TestMessaging:
sock = "carState" sock = "carState"
pub_sock = messaging.pub_sock(sock) pub_sock = messaging.pub_sock(sock)
sub_sock = messaging.sub_sock(sock, timeout=1000) sub_sock = messaging.sub_sock(sock, timeout=1000)
zmq_sleep()
# no msg in queue, socket should timeout # no msg in queue, socket should timeout
recvd = messaging.recv_one(sub_sock) recvd = messaging.recv_one(sub_sock)
@@ -123,10 +142,12 @@ class TestMessaging:
assert isinstance(recvd, capnp._DynamicStructReader) assert isinstance(recvd, capnp._DynamicStructReader)
assert_carstate(msg.carState, recvd.carState) assert_carstate(msg.carState, recvd.carState)
@pytest.mark.xfail(condition="ZMQ" in os.environ, reason='ZMQ detected')
def test_recv_one_or_none(self): def test_recv_one_or_none(self):
sock = "carState" sock = "carState"
pub_sock = messaging.pub_sock(sock) pub_sock = messaging.pub_sock(sock)
sub_sock = messaging.sub_sock(sock) sub_sock = messaging.sub_sock(sock)
zmq_sleep()
# no msg in queue, socket shouldn't block # no msg in queue, socket shouldn't block
recvd = messaging.recv_one_or_none(sub_sock) recvd = messaging.recv_one_or_none(sub_sock)
@@ -144,13 +165,16 @@ class TestMessaging:
sock_timeout = 0.1 sock_timeout = 0.1
pub_sock = messaging.pub_sock(sock) pub_sock = messaging.pub_sock(sock)
sub_sock = messaging.sub_sock(sock, timeout=round(sock_timeout*1000)) sub_sock = messaging.sub_sock(sock, timeout=round(sock_timeout*1000))
zmq_sleep()
# wait 5 socket timeouts and make sure it's still retrying # this test doesn't work with ZMQ since multiprocessing interrupts it
p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,)) if "ZMQ" not in os.environ:
p.start() # wait 5 socket timeouts and make sure it's still retrying
time.sleep(sock_timeout*5) p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,))
assert p.is_alive() p.start()
p.terminate() time.sleep(sock_timeout*5)
assert p.is_alive()
p.terminate()
# wait 5 socket timeouts before sending # wait 5 socket timeouts before sending
msg = random_carstate() msg = random_carstate()
@@ -1,16 +1,21 @@
import random import random
import time import time
from typing import cast from typing import Sized, cast
from collections.abc import Sized
import openpilot.cereal.messaging as messaging import openpilot.cereal.messaging as messaging
from openpilot.cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \ from openpilot.cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \
random_bytes, random_carstate, assert_carstate random_bytes, random_carstate, assert_carstate, \
zmq_sleep
from openpilot.cereal.services import SERVICE_LIST from openpilot.cereal.services import SERVICE_LIST
class TestSubMaster: class TestSubMaster:
def setup_method(self):
# ZMQ pub socket takes too long to die
# sleep to prevent multiple publishers error between tests
zmq_sleep(3)
def test_init(self): def test_init(self):
sm = messaging.SubMaster(events) sm = messaging.SubMaster(events)
for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive, for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive,
@@ -37,6 +42,7 @@ class TestSubMaster:
sock = "carState" sock = "carState"
pub_sock = messaging.pub_sock(sock) pub_sock = messaging.pub_sock(sock)
sm = messaging.SubMaster([sock,]) sm = messaging.SubMaster([sock,])
zmq_sleep()
msg = random_carstate() msg = random_carstate()
pub_sock.send(msg.to_bytes()) pub_sock.send(msg.to_bytes())
@@ -48,6 +54,7 @@ class TestSubMaster:
sock = "carState" sock = "carState"
pub_sock = messaging.pub_sock(sock) pub_sock = messaging.pub_sock(sock)
sm = messaging.SubMaster([sock,]) sm = messaging.SubMaster([sock,])
zmq_sleep()
for i in range(10): for i in range(10):
msg = messaging.new_message(sock) msg = messaging.new_message(sock)
@@ -84,13 +91,21 @@ class TestSubMaster:
for service, (max_freq, min_freq) in checks.items(): for service, (max_freq, min_freq) in checks.items():
if max_freq is not None: if max_freq is not None:
assert min_freq is not None
assert sm._check_avg_freq(service) assert sm._check_avg_freq(service)
assert sm.freq_tracker[service].max_freq == max_freq*1.2 assert sm.freq_tracker[service].max_freq == max_freq*1.2
assert sm.freq_tracker[service].min_freq == min_freq*0.8 assert sm.freq_tracker[service].min_freq == min_freq*0.8
else: else:
assert not sm._check_avg_freq(service) assert not sm._check_avg_freq(service)
def test_alive(self):
pass
def test_ignore_alive(self):
pass
def test_valid(self):
pass
# SubMaster should always conflate # SubMaster should always conflate
def test_conflate(self): def test_conflate(self):
sock = "carState" sock = "carState"
@@ -109,6 +124,11 @@ class TestSubMaster:
class TestPubMaster: class TestPubMaster:
def setup_method(self):
# ZMQ pub socket takes too long to die
# sleep to prevent multiple publishers error between tests
zmq_sleep(3)
def test_init(self): def test_init(self):
messaging.PubMaster(events) messaging.PubMaster(events)
@@ -116,6 +136,7 @@ class TestPubMaster:
socks = random_socks() socks = random_socks()
pm = messaging.PubMaster(socks) pm = messaging.PubMaster(socks)
sub_socks = {s: messaging.sub_sock(s, conflate=True, timeout=1000) for s in socks} sub_socks = {s: messaging.sub_sock(s, conflate=True, timeout=1000) for s in socks}
zmq_sleep()
# PubMaster accepts either a capnp msg builder or bytes # PubMaster accepts either a capnp msg builder or bytes
for capnp in [True, False]: for capnp in [True, False]:
+4 -2
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from enum import IntEnum from enum import IntEnum
from typing import Optional
# TODO: this should be automatically determined using the capnp schema # TODO: this should be automatically determined using the capnp schema
@@ -10,7 +11,7 @@ class QueueSize(IntEnum):
class Service: class Service:
def __init__(self, should_log: bool, frequency: float, decimation: int | None = None, def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] = None,
queue_size: QueueSize = QueueSize.SMALL): queue_size: QueueSize = QueueSize.SMALL):
self.should_log = should_log self.should_log = should_log
self.frequency = frequency self.frequency = frequency
@@ -124,7 +125,8 @@ def build_header():
for k, v in SERVICE_LIST.items(): for k, v in SERVICE_LIST.items():
should_log = "true" if v.should_log else "false" should_log = "true" if v.should_log else "false"
decimation = -1 if v.decimation is None else v.decimation decimation = -1 if v.decimation is None else v.decimation
h += f' {{ "{k}", {{"{k}", {should_log}, {v.frequency:f}, {decimation:d}, {v.queue_size:d}}}}},\n' h += ' { "%s", {"%s", %s, %f, %d, %d}},\n' % \
(k, k, should_log, v.frequency, decimation, v.queue_size)
h += "};\n" h += "};\n"
h += "#endif\n" h += "#endif\n"
-1
View File
@@ -38,7 +38,6 @@ class BaseApi:
} }
if payload_extra is not None: if payload_extra is not None:
payload.update(payload_extra) payload.update(payload_extra)
assert self.private_key is not None
token = jwt.encode(payload, self.private_key, algorithm=self.jwt_algorithm) token = jwt.encode(payload, self.private_key, algorithm=self.jwt_algorithm)
if isinstance(token, bytes): if isinstance(token, bytes):
token = token.decode('utf8') token = token.decode('utf8')
+8 -9
View File
@@ -42,26 +42,25 @@ def get_existing_chunks(path):
class ChunkStream(io.RawIOBase): class ChunkStream(io.RawIOBase):
def __init__(self, paths): def __init__(self, paths):
self._paths = iter(paths) self._paths = iter(paths)
self._f = None self._buf = memoryview(b'')
def readable(self): def readable(self):
return True return True
def readinto(self, b): def readinto(self, b):
n = 0 n = 0
view = memoryview(b)
while n < len(b): while n < len(b):
if self._f is None: if not self._buf:
p = next(self._paths, None) p = next(self._paths, None)
if p is None: if p is None:
break break
self._f = open(p, 'rb') with open(p, 'rb') as f:
count = self._f.readinto(view[n:]) self._buf = memoryview(f.read())
if not count:
self._f.close()
self._f = None
continue continue
n += count take = min(len(b) - n, len(self._buf))
b[n:n + take] = self._buf[:take]
self._buf = self._buf[take:]
n += take
return n return n
def open_file_chunked(path): def open_file_chunked(path):
+10 -10
View File
@@ -56,28 +56,28 @@
}, },
{ {
"name": "boot", "name": "boot",
"url": "https://commadist.azureedge.net/agnosupdate/boot-19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39.img.xz", "url": "https://commadist.azureedge.net/agnosupdate/boot-8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8.img.xz",
"hash": "19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39", "hash": "8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8",
"hash_raw": "19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39", "hash_raw": "8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8",
"size": 17487872, "size": 17487872,
"sparse": false, "sparse": false,
"full_check": true, "full_check": true,
"has_ab": true, "has_ab": true,
"ondevice_hash": "ddfe93cc6a8531af92ee331d9bbaeae2f1d933bdb38e579769dc9fe7998eb626" "ondevice_hash": "edca8bee1531e66953d107eeceeed2dc7b3ca46417e49d55508f94e58bf95db8"
}, },
{ {
"name": "system", "name": "system",
"url": "https://commadist.azureedge.net/agnosupdate/system-a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724.img.xz", "url": "https://commadist.azureedge.net/agnosupdate/system-ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f.img.xz",
"hash": "4dc41c2c072f5f5d5cd484cd6173049cd96acfb9a67bc20049775585fe881539", "hash": "78acfe16a7b62a3a91fc7a81f40a693e4468cec1c69df7d0b1e550aacc646113",
"hash_raw": "a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724", "hash_raw": "ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f",
"size": 4718592000, "size": 4718592000,
"sparse": true, "sparse": true,
"full_check": false, "full_check": false,
"has_ab": true, "has_ab": true,
"ondevice_hash": "cf1229630b7a2b8497705bca4ba947dbf0c217418ff4febff571aa4f4a878134", "ondevice_hash": "743142c5a898f27b2a1029cca42c8a5d5d1fc0096414422b850fe84c8d0b8342",
"alt": { "alt": {
"hash": "a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724", "hash": "ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f",
"url": "https://commadist.azureedge.net/agnosupdate/system-a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724.img", "url": "https://commadist.azureedge.net/agnosupdate/system-ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f.img",
"size": 4718592000 "size": 4718592000
} }
} }
+1 -1
View File
@@ -19,7 +19,7 @@ class StreamingDecompressor:
def __init__(self, url: str) -> None: def __init__(self, url: str) -> None:
self.buf = b"" self.buf = b""
self.req = requests.get(url, stream=True, headers={'Accept-Encoding': 'identity'}, timeout=60) self.req = requests.get(url, stream=True, headers={'Accept-Encoding': None}, timeout=60)
self.it = self.req.iter_content(chunk_size=1024 * 1024) self.it = self.req.iter_content(chunk_size=1024 * 1024)
self.decompressor = lzma.LZMADecompressor(format=lzma.FORMAT_AUTO) self.decompressor = lzma.LZMADecompressor(format=lzma.FORMAT_AUTO)
self.eof = False self.eof = False
+8 -8
View File
@@ -339,7 +339,7 @@ class Tici(HardwareBase):
# Ensure fan gpio is enabled so fan runs until shutdown, also turned on at boot by the ABL # Ensure fan gpio is enabled so fan runs until shutdown, also turned on at boot by the ABL
gpio_init(GPIO.SOM_ST_IO, True) gpio_init(GPIO.SOM_ST_IO, True)
gpio_set(GPIO.SOM_ST_IO, True) gpio_set(GPIO.SOM_ST_IO, 1)
# *** IRQ config *** # *** IRQ config ***
@@ -389,21 +389,21 @@ class Tici(HardwareBase):
gpio_init(GPIO.STM_RST_N, True) gpio_init(GPIO.STM_RST_N, True)
gpio_init(GPIO.STM_BOOT0, True) gpio_init(GPIO.STM_BOOT0, True)
gpio_set(GPIO.STM_RST_N, True) gpio_set(GPIO.STM_RST_N, 1)
gpio_set(GPIO.STM_BOOT0, False) gpio_set(GPIO.STM_BOOT0, 0)
time.sleep(0.01) time.sleep(0.01)
gpio_set(GPIO.STM_RST_N, False) gpio_set(GPIO.STM_RST_N, 0)
def recover_internal_panda(self): def recover_internal_panda(self):
gpio_init(GPIO.STM_RST_N, True) gpio_init(GPIO.STM_RST_N, True)
gpio_init(GPIO.STM_BOOT0, True) gpio_init(GPIO.STM_BOOT0, True)
gpio_set(GPIO.STM_RST_N, True) gpio_set(GPIO.STM_RST_N, 1)
gpio_set(GPIO.STM_BOOT0, True) gpio_set(GPIO.STM_BOOT0, 1)
time.sleep(0.01) time.sleep(0.01)
gpio_set(GPIO.STM_RST_N, False) gpio_set(GPIO.STM_RST_N, 0)
time.sleep(0.01) time.sleep(0.01)
gpio_set(GPIO.STM_BOOT0, False) gpio_set(GPIO.STM_BOOT0, 0)
def booted(self): def booted(self):
# this normally boots within 8s, but on rare occasions takes 30+s # this normally boots within 8s, but on rare occasions takes 30+s
+1 -1
View File
@@ -52,7 +52,7 @@ PPPD_CMD = [
"novj", "novjccomp", "ipcp-accept-local", "ipcp-accept-remote", "nomagic", "novj", "novjccomp", "ipcp-accept-local", "ipcp-accept-remote", "nomagic",
"user", '""', "password", '""', "user", '""', "password", '""',
] ]
INITIAL_STATE: dict[str, object] = { INITIAL_STATE = {
"seconds_since_boot": 0, "seconds_since_boot": 0,
"state": "INITIALIZING", "state": "INITIALIZING",
"connected": False, "ip_address": "", "connected": False, "ip_address": "",
-1
View File
@@ -37,7 +37,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"DoShutdown", {CLEAR_ON_MANAGER_START, BOOL}}, {"DoShutdown", {CLEAR_ON_MANAGER_START, BOOL}},
{"DoUninstall", {CLEAR_ON_MANAGER_START, BOOL}}, {"DoUninstall", {CLEAR_ON_MANAGER_START, BOOL}},
{"DriverTooDistracted", {CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON, BOOL}}, {"DriverTooDistracted", {CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON, BOOL}},
{"DriverLockoutCount", {CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON, INT, "0"}},
{"AlphaLongitudinalEnabled", {PERSISTENT | DEVELOPMENT_ONLY | BACKUP, BOOL}}, {"AlphaLongitudinalEnabled", {PERSISTENT | DEVELOPMENT_ONLY | BACKUP, BOOL}},
{"ExperimentalMode", {PERSISTENT | BACKUP, BOOL}}, {"ExperimentalMode", {PERSISTENT | BACKUP, BOOL}},
{"ExperimentalModeConfirmed", {PERSISTENT | BACKUP, BOOL}}, {"ExperimentalModeConfirmed", {PERSISTENT | BACKUP, BOOL}},
+5 -7
View File
@@ -1,13 +1,11 @@
import numpy as np import numpy as np
from collections.abc import Sequence from numbers import Number
Gain = int | float | tuple[Sequence[float], Sequence[float]] | list[list[float]]
class PIDController: class PIDController:
def __init__(self, k_p: Gain, k_i: Gain, k_d: Gain = 0., pos_limit=1e308, neg_limit=-1e308, rate=100): def __init__(self, k_p, k_i, k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100):
self._k_p = ([0], [k_p]) if isinstance(k_p, (int, float)) else k_p self._k_p: list[list[float]] = [[0], [k_p]] if isinstance(k_p, Number) else k_p
self._k_i = ([0], [k_i]) if isinstance(k_i, (int, float)) else k_i self._k_i: list[list[float]] = [[0], [k_i]] if isinstance(k_i, Number) else k_i
self._k_d = ([0], [k_d]) if isinstance(k_d, (int, float)) else k_d self._k_d: list[list[float]] = [[0], [k_d]] if isinstance(k_d, Number) else k_d
self.set_limits(pos_limit, neg_limit) self.set_limits(pos_limit, neg_limit)
+2 -2
View File
@@ -27,7 +27,7 @@ class SwaglogRotatingFileHandler(BaseRotatingHandler):
self.log_files = self.get_existing_logfiles() self.log_files = self.get_existing_logfiles()
log_indexes = [f.split(".")[-1] for f in self.log_files] log_indexes = [f.split(".")[-1] for f in self.log_files]
self.last_file_idx = max([int(i) for i in log_indexes if i.isdigit()] or [-1]) self.last_file_idx = max([int(i) for i in log_indexes if i.isdigit()] or [-1])
self.last_rollover = 0.0 self.last_rollover = None
self.doRollover() self.doRollover()
def _open(self): def _open(self):
@@ -39,7 +39,7 @@ class SwaglogRotatingFileHandler(BaseRotatingHandler):
return stream return stream
def get_existing_logfiles(self): def get_existing_logfiles(self):
log_files = [] log_files = list()
base_dir = os.path.dirname(self.base_filename) base_dir = os.path.dirname(self.base_filename)
for fn in os.listdir(base_dir): for fn in os.listdir(base_dir):
fp = os.path.join(base_dir, fn) fp = os.path.join(base_dir, fn)
+3 -3
View File
@@ -112,14 +112,14 @@ class TestParams:
def test_params_default_value(self): def test_params_default_value(self):
self.params.remove("LanguageSetting") self.params.remove("LanguageSetting")
self.params.remove("LongitudinalPersonality") self.params.remove("LongitudinalPersonality")
self.params.remove("LiveParametersV2") self.params.remove("LiveParameters")
assert self.params.get("LanguageSetting") is None assert self.params.get("LanguageSetting") is None
assert self.params.get("LanguageSetting", return_default=False) is None assert self.params.get("LanguageSetting", return_default=False) is None
assert isinstance(self.params.get("LanguageSetting", return_default=True), str) assert isinstance(self.params.get("LanguageSetting", return_default=True), str)
assert isinstance(self.params.get("LongitudinalPersonality", return_default=True), int) assert isinstance(self.params.get("LongitudinalPersonality", return_default=True), int)
assert self.params.get("LiveParametersV2") is None assert self.params.get("LiveParameters") is None
assert self.params.get("LiveParametersV2", return_default=True) is None assert self.params.get("LiveParameters", return_default=True) is None
def test_params_get_type(self): def test_params_get_type(self):
# json # json
+2 -1
View File
@@ -52,7 +52,7 @@ _ar_ox_config = DeviceCameraConfig(CameraConfig(1928, 1208, 2648.0), _ar_ox_fish
_os_config = DeviceCameraConfig(CameraConfig(2688 // 2, 1520 // 2, 1522.0 * 3 / 4), _os_fisheye, _os_fisheye) _os_config = DeviceCameraConfig(CameraConfig(2688 // 2, 1520 // 2, 1522.0 * 3 / 4), _os_fisheye, _os_fisheye)
_neo_config = DeviceCameraConfig(CameraConfig(1164, 874, 910.0), CameraConfig(816, 612, 650.0), _NoneCameraConfig()) _neo_config = DeviceCameraConfig(CameraConfig(1164, 874, 910.0), CameraConfig(816, 612, 650.0), _NoneCameraConfig())
DEVICE_CAMERAS: dict[tuple[str, str], DeviceCameraConfig] = { DEVICE_CAMERAS = {
# A "device camera" is defined by a device type and sensor # A "device camera" is defined by a device type and sensor
# sensor type was never set on eon/neo/two # sensor type was never set on eon/neo/two
@@ -176,3 +176,4 @@ def img_from_device(pt_device):
pt_img = pt_view/pt_view[:, 2:3] pt_img = pt_view/pt_view[:, 2:3]
return pt_img.reshape(input_shape)[:, :2] return pt_img.reshape(input_shape)[:, :2]
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3027da9834adf9c71177bc3086f8de3018ce22e4725b694d635723c5b3c860cd
size 97120
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ca29ff46fbd6d00cc02596de8c9abfbf1cacdc6a7b2f98b27a74d24c790da004
size 52374
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:6f47633b5082b911e79dd13fc2d1f4c2a9d44d2fe0860f3d390ffc4e3d772f7d oid sha256:42bd04a57b527c787a0555503e02a203f7d672c12d448769a3f41f17befbf013
size 97120 size 48044
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:538271820d618046009bb456e930598f2736dd1a6174c11925ec3912581b05c1
size 52374
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:634fad6590295ef2e73d5f7e5aecbb5dd245a0f7692f1e300c2ebac95aabb8e3
size 73026
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:a380fe0a022856b302841a0dd19b71eab318c6b08ec851f128942758a7f1631a oid sha256:b1e177499d9439367179cc57a6301b6162393972e3a136cc35c5fdac026bf10a
size 97120 size 48044
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:cc9e67cfaba77e8e4f4049f0add9238660e4cf8f886c6915600d8f47a30c98db oid sha256:2c0af7f5fe57bb36ab96fae868e20feca763541c97a61b3a3a84a0e7fcb81163
size 97120 size 83350
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ad19268e4aaaeac8dd21f6b26c16a121e7b3f50bba867748e7226727643ae682
size 144642
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:412ef25d2fb103c1ebd55c667313a5921493305fb4e1f4e1dafc08d3b95d86ab
size 73026
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:081d64ca28a84a59ff5e6aea4732865b8687585938b8a287bc7e6a49d0646508 oid sha256:4af81cbf1d96a42cc351878b015298aee82874b46baaf1a615ca91ec36c0ced6
size 97120 size 83228
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1bb440f424c989c06e203bfba0b32d39aede8f6c78a9b335e5e778460202b601
size 73026
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5a390831afca3bfc6ea3c2739b872ebf866e70df8ae30653f8587e5cd3993959
size 68306
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:67e636d072703e6b1233a12344c0a6304fd43d64dbb31c66b71c2c8870a339c1
size 153764
+4 -5
View File
@@ -3,7 +3,7 @@ import copy
import os import os
import pytest import pytest
import random import random
import unittest import unittest # noqa: TID251
from collections import defaultdict, Counter from collections import defaultdict, Counter
import hypothesis.strategies as st import hypothesis.strategies as st
from hypothesis import Phase, given, settings from hypothesis import Phase, given, settings
@@ -22,7 +22,6 @@ from openpilot.selfdrive.pandad import can_capnp_to_list
from openpilot.selfdrive.test.helpers import read_segment_list from openpilot.selfdrive.test.helpers import read_segment_list
from openpilot.common.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT from openpilot.common.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
from openpilot.tools.lib.logreader import LogReader, LogsUnavailable, openpilotci_source, internal_source, comma_api_source from openpilot.tools.lib.logreader import LogReader, LogsUnavailable, openpilotci_source, internal_source, comma_api_source
from openpilot.tools.lib.file_sources import Source
from openpilot.tools.lib.route import SegmentName from openpilot.tools.lib.route import SegmentName
SafetyModel = car.CarParams.SafetyModel SafetyModel = car.CarParams.SafetyModel
@@ -132,7 +131,7 @@ class TestCarModelBase(unittest.TestCase):
segment_range = f"{cls.test_route.route}/{seg}" segment_range = f"{cls.test_route.route}/{seg}"
try: try:
sources: list[Source] = [internal_source] if len(INTERNAL_SEG_LIST) else [openpilotci_source, comma_api_source] sources = [internal_source] if len(INTERNAL_SEG_LIST) else [openpilotci_source, comma_api_source]
lr = LogReader(segment_range, sources=sources, sort_by_time=True) lr = LogReader(segment_range, sources=sources, sort_by_time=True)
return cls.get_testing_data_from_logreader(lr) return cls.get_testing_data_from_logreader(lr)
except (LogsUnavailable, AssertionError): except (LogsUnavailable, AssertionError):
@@ -258,7 +257,7 @@ class TestCarModelBase(unittest.TestCase):
# Don't check relay malfunction on disabled routes (relay closed), # Don't check relay malfunction on disabled routes (relay closed),
# or before fingerprinting is done (elm327 and noOutput) # or before fingerprinting is done (elm327 and noOutput)
if self.car_safety_mode_frame is not None and t / 1e4 > self.car_safety_mode_frame: if self.openpilot_enabled and t / 1e4 > self.car_safety_mode_frame:
self.assertFalse(self.safety.get_relay_malfunction()) self.assertFalse(self.safety.get_relay_malfunction())
else: else:
self.safety.set_relay_malfunction(False) self.safety.set_relay_malfunction(False)
@@ -452,7 +451,7 @@ class TestCarModelBase(unittest.TestCase):
# TODO: remove this exception once this mismatch is resolved # TODO: remove this exception once this mismatch is resolved
brake_pressed = CS.brakePressed brake_pressed = CS.brakePressed
if CS.brakePressed and not self.safety.get_brake_pressed_prev(): if CS.brakePressed and not self.safety.get_brake_pressed_prev():
if self.CP.carFingerprint in (HONDA.HONDA_PILOT, HONDA.HONDA_RIDGELINE) and CS.deprecated.brake > 0.05: if self.CP.carFingerprint in (HONDA.HONDA_PILOT, HONDA.HONDA_RIDGELINE) and CS.brakeDEPRECATED > 0.05:
brake_pressed = False brake_pressed = False
checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev() checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev()
checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev() checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev()
@@ -147,7 +147,6 @@ class Controls(ControlsExt):
lat_delay = self.sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS lat_delay = self.sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS
actuators.curvature = self.desired_curvature actuators.curvature = self.desired_curvature
assert self.calibrated_pose is not None
steer, lateral_output, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp, steer, lateral_output, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp,
self.steer_limited_by_safety, self.desired_curvature, self.steer_limited_by_safety, self.desired_curvature,
self.calibrated_pose, curvature_limited, lat_delay) self.calibrated_pose, curvature_limited, lat_delay)
@@ -15,9 +15,6 @@ MAX_LATERAL_JERK = 5.0 # m/s^3
MAX_LATERAL_ACCEL_NO_ROLL = 3.0 # m/s^2 MAX_LATERAL_ACCEL_NO_ROLL = 3.0 # m/s^2
def should_stop(v_ego: float, a_target: float) -> bool:
return bool(v_ego < 0.25 and a_target < 0.1)
def clamp(val, min_val, max_val): def clamp(val, min_val, max_val):
clamped_val = float(np.clip(val, min_val, max_val)) clamped_val = float(np.clip(val, min_val, max_val))
return clamped_val, clamped_val != val return clamped_val, clamped_val != val
@@ -43,7 +40,7 @@ def clip_curvature(v_ego, prev_curvature, new_curvature, roll) -> tuple[float, b
return float(new_curvature), limited_accel or limited_max_curv return float(new_curvature), limited_accel or limited_max_curv
def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL): def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL, vEgoStopping=0.3):
if len(speeds) == len(t_idxs): if len(speeds) == len(t_idxs):
v_now = speeds[0] v_now = speeds[0]
a_now = accels[0] a_now = accels[0]
@@ -56,7 +53,8 @@ def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL):
v_now = 0.0 v_now = 0.0
v_target = 0.0 v_target = 0.0
a_target = 0.0 a_target = 0.0
return a_target, should_stop(v_now, a_target) should_stop = (v_now < vEgoStopping and a_target < 0.1)
return a_target, should_stop
def curv_from_psis(psi_target, psi_rate, vego, action_t): def curv_from_psis(psi_target, psi_rate, vego, action_t):
vego = np.clip(vego, MIN_SPEED, np.inf) vego = np.clip(vego, MIN_SPEED, np.inf)
@@ -10,7 +10,7 @@ CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
LongCtrlState = car.CarControl.Actuators.LongControlState LongCtrlState = car.CarControl.Actuators.LongControlState
def long_control_state_trans(CP_SP, active, long_control_state, def long_control_state_trans(CP, CP_SP, active, long_control_state, v_ego,
should_stop, brake_pressed, cruise_standstill): should_stop, brake_pressed, cruise_standstill):
# Gas Interceptor # Gas Interceptor
cruise_standstill = cruise_standstill and not CP_SP.enableGasInterceptor cruise_standstill = cruise_standstill and not CP_SP.enableGasInterceptor
@@ -26,17 +26,22 @@ def long_control_state_trans(CP_SP, active, long_control_state,
if long_control_state == LongCtrlState.off: if long_control_state == LongCtrlState.off:
if not starting_condition: if not starting_condition:
long_control_state = LongCtrlState.stopping long_control_state = LongCtrlState.stopping
elif CP.startingState:
long_control_state = LongCtrlState.starting
else: else:
long_control_state = LongCtrlState.pid long_control_state = LongCtrlState.pid
elif long_control_state == LongCtrlState.stopping: elif long_control_state == LongCtrlState.stopping:
if starting_condition: if starting_condition and CP.startingState:
long_control_state = LongCtrlState.starting
elif starting_condition:
long_control_state = LongCtrlState.pid long_control_state = LongCtrlState.pid
elif long_control_state == LongCtrlState.pid: elif long_control_state in [LongCtrlState.starting, LongCtrlState.pid]:
if should_stop: if should_stop:
long_control_state = LongCtrlState.stopping long_control_state = LongCtrlState.stopping
elif v_ego > CP.vEgoStarting:
long_control_state = LongCtrlState.pid
return long_control_state return long_control_state
class LongControl: class LongControl:
@@ -57,7 +62,7 @@ class LongControl:
self.pid.neg_limit = accel_limits[0] self.pid.neg_limit = accel_limits[0]
self.pid.pos_limit = accel_limits[1] self.pid.pos_limit = accel_limits[1]
self.long_control_state = long_control_state_trans(self.CP_SP, active, self.long_control_state, self.long_control_state = long_control_state_trans(self.CP, self.CP_SP, active, self.long_control_state, CS.vEgo,
should_stop, CS.brakePressed, should_stop, CS.brakePressed,
CS.cruiseState.standstill) CS.cruiseState.standstill)
if self.long_control_state == LongCtrlState.off: if self.long_control_state == LongCtrlState.off:
@@ -68,8 +73,11 @@ class LongControl:
output_accel = self.last_output_accel output_accel = self.last_output_accel
if output_accel > self.CP.stopAccel: if output_accel > self.CP.stopAccel:
output_accel = min(output_accel, 0.0) output_accel = min(output_accel, 0.0)
# TODO: can we just go straight to stopAccel? output_accel -= self.CP.stoppingDecelRate * DT_CTRL
output_accel -= 1.0 * DT_CTRL # m/s^2/s while trying to stop self.reset()
elif self.long_control_state == LongCtrlState.starting:
output_accel = self.CP.startAccel
self.reset() self.reset()
else: # LongCtrlState.pid else: # LongCtrlState.pid
@@ -137,7 +137,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
action_t = self.CP.longitudinalActuatorDelay + DT_MDL action_t = self.CP.longitudinalActuatorDelay + DT_MDL
output_a_target_mpc, output_should_stop_mpc = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX, output_a_target_mpc, output_should_stop_mpc = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX,
action_t=action_t) action_t=action_t, vEgoStopping=self.CP.vEgoStopping)
output_a_target_e2e = sm['modelV2'].action.desiredAcceleration output_a_target_e2e = sm['modelV2'].action.desiredAcceleration
output_should_stop_e2e = sm['modelV2'].action.shouldStop output_should_stop_e2e = sm['modelV2'].action.shouldStop
@@ -1,43 +1,60 @@
from openpilot.cereal import custom from openpilot.cereal import custom
from opendbc.car.structs import car
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState, long_control_state_trans from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState, long_control_state_trans
class TestLongControlStateTransition: class TestLongControlStateTransition:
def test_stay_stopped(self): def test_stay_stopped(self):
CP = car.CarParams.new_message()
CP_SP = custom.CarParamsSP.new_message() CP_SP = custom.CarParamsSP.new_message()
active = True active = True
current_state = LongCtrlState.stopping current_state = LongCtrlState.stopping
next_state = long_control_state_trans(CP_SP, active, current_state, next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
should_stop=True, brake_pressed=False, cruise_standstill=False) should_stop=True, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.stopping assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP_SP, active, current_state, next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=True, cruise_standstill=False) should_stop=False, brake_pressed=True, cruise_standstill=False)
assert next_state == LongCtrlState.stopping assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP_SP, active, current_state, next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=False, cruise_standstill=True) should_stop=False, brake_pressed=False, cruise_standstill=True)
assert next_state == LongCtrlState.stopping assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP_SP, active, current_state, next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0,
should_stop=False, brake_pressed=False, cruise_standstill=False) should_stop=False, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.pid assert next_state == LongCtrlState.pid
active = False active = False
next_state = long_control_state_trans(CP_SP, active, current_state, next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0,
should_stop=False, brake_pressed=False, cruise_standstill=False) should_stop=False, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.off assert next_state == LongCtrlState.off
def test_engage(): def test_engage():
CP = car.CarParams.new_message()
CP_SP = custom.CarParamsSP.new_message() CP_SP = custom.CarParamsSP.new_message()
active = True active = True
current_state = LongCtrlState.off current_state = LongCtrlState.off
next_state = long_control_state_trans(CP_SP, active, current_state, next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
should_stop=True, brake_pressed=False, cruise_standstill=False) should_stop=True, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.stopping assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP_SP, active, current_state, next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=True, cruise_standstill=False) should_stop=False, brake_pressed=True, cruise_standstill=False)
assert next_state == LongCtrlState.stopping assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP_SP, active, current_state, next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=False, cruise_standstill=True) should_stop=False, brake_pressed=False, cruise_standstill=True)
assert next_state == LongCtrlState.stopping assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP_SP, active, current_state, next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.pid
def test_starting():
CP = car.CarParams.new_message(startingState=True, vEgoStarting=0.5)
CP_SP = custom.CarParamsSP.new_message()
active = True
current_state = LongCtrlState.starting
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.starting
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0,
should_stop=False, brake_pressed=False, cruise_standstill=False) should_stop=False, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.pid assert next_state == LongCtrlState.pid
@@ -7,6 +7,8 @@ from opendbc.car.lateral import get_friction, FRICTION_THRESHOLD
from openpilot.common.realtime import DT_MDL from openpilot.common.realtime import DT_MDL
from openpilot.selfdrive.locationd.torqued import TorqueEstimator, MIN_BUCKET_POINTS, POINTS_PER_BUCKET, STEER_BUCKET_BOUNDS from openpilot.selfdrive.locationd.torqued import TorqueEstimator, MIN_BUCKET_POINTS, POINTS_PER_BUCKET, STEER_BUCKET_BOUNDS
np.random.seed(0)
LA_ERR_STD = 1.0 LA_ERR_STD = 1.0
INPUT_NOISE_STD = 0.08 INPUT_NOISE_STD = 0.08
V_EGO = 30.0 V_EGO = 30.0
@@ -56,17 +58,16 @@ def simulate_straight_road_msgs(est):
for which, msg in (('carControl', carControl), ('carOutput', carOutput), ('carState', carState), ('livePose', livePose)): for which, msg in (('carControl', carControl), ('carOutput', carOutput), ('carState', carState), ('livePose', livePose)):
est.handle_log(t, which, msg) est.handle_log(t, which, msg)
class TestTorquedLatAccelOffset: def test_estimated_offset():
def test_estimated_offset(self): steer_torques, lat_accels = generate_inputs(TORQUE_TUNE_BIASED, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE_BIASED, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD) est = get_warmed_up_estimator(steer_torques, lat_accels)
est = get_warmed_up_estimator(steer_torques, lat_accels) msg = est.get_msg()
msg = est.get_msg() # TODO add lataccelfactor and friction check when we have more accurate estimates
# TODO add lataccelfactor and friction check when we have more accurate estimates assert abs(msg.liveTorqueParameters.latAccelOffsetRaw - TORQUE_TUNE_BIASED.latAccelOffset) < 0.1
assert abs(msg.liveTorqueParameters.latAccelOffsetRaw - TORQUE_TUNE_BIASED.latAccelOffset) < 0.1
def test_straight_road_roll_bias(self): def test_straight_road_roll_bias():
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD) steer_torques, lat_accels = generate_inputs(TORQUE_TUNE, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
est = get_warmed_up_estimator(steer_torques, lat_accels) est = get_warmed_up_estimator(steer_torques, lat_accels)
simulate_straight_road_msgs(est) simulate_straight_road_msgs(est)
msg = est.get_msg() msg = est.get_msg()
assert (msg.liveTorqueParameters.latAccelOffsetRaw < -0.05) and np.isfinite(msg.liveTorqueParameters.latAccelOffsetRaw) assert (msg.liveTorqueParameters.latAccelOffsetRaw < -0.05) and np.isfinite(msg.liveTorqueParameters.latAccelOffsetRaw)
+3 -5
View File
@@ -1,5 +1,4 @@
import numpy as np import numpy as np
from collections.abc import Sequence
from typing import Any from typing import Any
from functools import cache from functools import cache
@@ -69,8 +68,7 @@ class NPQueue:
class PointBuckets: class PointBuckets:
def __init__(self, x_bounds: list[tuple[float, float]], min_points: Sequence[float], min_points_total: int, points_per_bucket: int, rowsize: int) -> None: def __init__(self, x_bounds: list[tuple[float, float]], min_points: list[float], min_points_total: int, points_per_bucket: int, rowsize: int) -> None:
self._rng = np.random.default_rng()
self.x_bounds = x_bounds self.x_bounds = x_bounds
self.buckets = {bounds: NPQueue(maxlen=points_per_bucket, rowsize=rowsize) for bounds in x_bounds} self.buckets = {bounds: NPQueue(maxlen=points_per_bucket, rowsize=rowsize) for bounds in x_bounds}
self.buckets_min_points = dict(zip(x_bounds, min_points, strict=True)) self.buckets_min_points = dict(zip(x_bounds, min_points, strict=True))
@@ -100,9 +98,9 @@ class PointBuckets:
points = np.vstack([x.arr for x in self.buckets.values()]) points = np.vstack([x.arr for x in self.buckets.values()])
if num_points is None: if num_points is None:
return points return points
return points[self._rng.choice(np.arange(len(points)), min(len(points), num_points), replace=False)] return points[np.random.choice(np.arange(len(points)), min(len(points), num_points), replace=False)]
def load_points(self, points: Sequence[Sequence[float]]) -> None: def load_points(self, points: list[list[float]]) -> None:
for point in points: for point in points:
self.add_point(*point) self.add_point(*point)
+1 -1
View File
@@ -66,7 +66,7 @@ class LocationEstimator:
self.observations = {kind: np.zeros(3, dtype=np.float32) for kind in obs_kinds} self.observations = {kind: np.zeros(3, dtype=np.float32) for kind in obs_kinds}
self.observation_errors = {kind: np.zeros(3, dtype=np.float32) for kind in obs_kinds} self.observation_errors = {kind: np.zeros(3, dtype=np.float32) for kind in obs_kinds}
def reset(self, t: float | None, x_initial: np.ndarray = PoseKalman.initial_x, P_initial: np.ndarray = PoseKalman.initial_P): def reset(self, t: float, x_initial: np.ndarray = PoseKalman.initial_x, P_initial: np.ndarray = PoseKalman.initial_P):
self.kf.init_state(x_initial, covs=P_initial, filter_time=t) self.kf.init_state(x_initial, covs=P_initial, filter_time=t)
def _validate_sensor_source(self, source: log.SensorEventData.SensorSource): def _validate_sensor_source(self, source: log.SensorEventData.SensorSource):
+21
View File
@@ -200,6 +200,25 @@ def check_valid_with_hysteresis(current_valid: bool, val: float, threshold: floa
return current_valid return current_valid
# TODO: Remove this function after few releases (added in 0.9.9)
def migrate_cached_vehicle_params_if_needed(params: Params):
last_parameters_data_old = params.get("LiveParameters")
last_parameters_data = params.get("LiveParametersV2")
if last_parameters_data_old is None or last_parameters_data is not None:
return
try:
last_parameters_msg = messaging.new_message('liveParameters')
last_parameters_msg.liveParameters.valid = True
last_parameters_msg.liveParameters.steerRatio = last_parameters_data_old['steerRatio']
last_parameters_msg.liveParameters.stiffnessFactor = last_parameters_data_old['stiffnessFactor']
last_parameters_msg.liveParameters.angleOffsetAverageDeg = last_parameters_data_old['angleOffsetAverageDeg']
params.put("LiveParametersV2", last_parameters_msg.to_bytes(), block=True)
except Exception as e:
cloudlog.error(f"Failed to perform parameter migration: {e}")
params.remove("LiveParameters")
def retrieve_initial_vehicle_params(params: Params, CP: car.CarParams, replay: bool, debug: bool): def retrieve_initial_vehicle_params(params: Params, CP: car.CarParams, replay: bool, debug: bool):
last_parameters_data = params.get("LiveParametersV2") last_parameters_data = params.get("LiveParametersV2")
last_carparams_data = params.get("CarParamsPrevRoute") last_carparams_data = params.get("CarParamsPrevRoute")
@@ -254,6 +273,8 @@ def main():
params = Params() params = Params()
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
migrate_cached_vehicle_params_if_needed(params)
steer_ratio, stiffness_factor, angle_offset_deg, pInitial = retrieve_initial_vehicle_params(params, CP, REPLAY, DEBUG) steer_ratio, stiffness_factor, angle_offset_deg, pInitial = retrieve_initial_vehicle_params(params, CP, REPLAY, DEBUG)
learner = VehicleParamsLearner(CP, steer_ratio, stiffness_factor, np.radians(angle_offset_deg), pInitial) learner = VehicleParamsLearner(CP, steer_ratio, stiffness_factor, np.radians(angle_offset_deg), pInitial)
@@ -81,7 +81,6 @@ class TestLagd:
assert retrieve_initial_lag(params, CP) is None assert retrieve_initial_lag(params, CP) is None
def test_ncc(self): def test_ncc(self):
rng = np.random.default_rng()
lag_frames = random.randint(1, 19) lag_frames = random.randint(1, 19)
desired_sig = np.sin(np.arange(0.0, 10.0, 0.1)) desired_sig = np.sin(np.arange(0.0, 10.0, 0.1))
@@ -92,15 +91,15 @@ class TestLagd:
assert np.argmax(corr) == lag_frames assert np.argmax(corr) == lag_frames
# add some noise # add some noise
desired_sig += rng.normal(0, 0.05, len(desired_sig)) desired_sig += np.random.normal(0, 0.05, len(desired_sig))
actual_sig += rng.normal(0, 0.05, len(actual_sig)) actual_sig += np.random.normal(0, 0.05, len(actual_sig))
corr = masked_normalized_cross_correlation(desired_sig, actual_sig, mask, 200)[len(desired_sig) - 1:len(desired_sig) + 20] corr = masked_normalized_cross_correlation(desired_sig, actual_sig, mask, 200)[len(desired_sig) - 1:len(desired_sig) + 20]
assert np.argmax(corr) in range(lag_frames - MAX_ERR_FRAMES, lag_frames + MAX_ERR_FRAMES + 1) assert np.argmax(corr) in range(lag_frames - MAX_ERR_FRAMES, lag_frames + MAX_ERR_FRAMES + 1)
# mask out 40% of the values, and make them noise # mask out 40% of the values, and make them noise
mask = rng.choice([True, False], size=len(desired_sig), p=[0.6, 0.4]) mask = np.random.choice([True, False], size=len(desired_sig), p=[0.6, 0.4])
desired_sig[~mask] = rng.normal(0, 1, size=np.sum(~mask)) desired_sig[~mask] = np.random.normal(0, 1, size=np.sum(~mask))
actual_sig[~mask] = rng.normal(0, 1, size=np.sum(~mask)) actual_sig[~mask] = np.random.normal(0, 1, size=np.sum(~mask))
corr = masked_normalized_cross_correlation(desired_sig, actual_sig, mask, 200)[len(desired_sig) - 1:len(desired_sig) + 20] corr = masked_normalized_cross_correlation(desired_sig, actual_sig, mask, 200)[len(desired_sig) - 1:len(desired_sig) + 20]
assert np.argmax(corr) in range(lag_frames - MAX_ERR_FRAMES, lag_frames + MAX_ERR_FRAMES + 1) assert np.argmax(corr) in range(lag_frames - MAX_ERR_FRAMES, lag_frames + MAX_ERR_FRAMES + 1)
@@ -37,9 +37,9 @@ def get_select_fields_data(logs):
def sig_smooth(signal): def sig_smooth(signal):
return masked_symmetric_moving_average(signal, np.ones_like(signal), 5, 1.0) return masked_symmetric_moving_average(signal, np.ones_like(signal), 5, 1.0)
def get_nested_keys(msg, keys): def get_nested_keys(msg, keys):
val = msg val = None
for key in keys: for key in keys:
val = getattr(val, key) if isinstance(key, str) else val[key] val = getattr(msg if val is None else val, key) if isinstance(key, str) else val[key]
return val return val
lp = [x.livePose for x in logs if x.which() == 'livePose'] lp = [x.livePose for x in logs if x.which() == 'livePose']
data = defaultdict(list) data = defaultdict(list)
@@ -2,7 +2,7 @@ import random
import numpy as np import numpy as np
from openpilot.cereal import messaging from openpilot.cereal import messaging
from openpilot.selfdrive.locationd.paramsd import retrieve_initial_vehicle_params from openpilot.selfdrive.locationd.paramsd import retrieve_initial_vehicle_params, migrate_cached_vehicle_params_if_needed
from openpilot.selfdrive.locationd.models.car_kf import CarKalman from openpilot.selfdrive.locationd.models.car_kf import CarKalman
from openpilot.selfdrive.locationd.test.test_locationd_scenarios import TEST_ROUTE from openpilot.selfdrive.locationd.test.test_locationd_scenarios import TEST_ROUTE
from openpilot.selfdrive.test.process_replay.migration import migrate, migrate_carParams from openpilot.selfdrive.test.process_replay.migration import migrate, migrate_carParams
@@ -30,9 +30,38 @@ class TestParamsd:
params.put("LiveParametersV2", msg.to_bytes(), block=True) params.put("LiveParametersV2", msg.to_bytes(), block=True)
params.put("CarParamsPrevRoute", CP.as_builder().to_bytes(), block=True) params.put("CarParamsPrevRoute", CP.as_builder().to_bytes(), block=True)
migrate_cached_vehicle_params_if_needed(params) # this is not tested here but should not mess anything up or throw an error
sr, sf, offset, p_init = retrieve_initial_vehicle_params(params, CP, replay=True, debug=True) sr, sf, offset, p_init = retrieve_initial_vehicle_params(params, CP, replay=True, debug=True)
np.testing.assert_allclose(sr, msg.liveParameters.steerRatio) np.testing.assert_allclose(sr, msg.liveParameters.steerRatio)
np.testing.assert_allclose(sf, msg.liveParameters.stiffnessFactor) np.testing.assert_allclose(sf, msg.liveParameters.stiffnessFactor)
np.testing.assert_allclose(offset, msg.liveParameters.angleOffsetAverageDeg) np.testing.assert_allclose(offset, msg.liveParameters.angleOffsetAverageDeg)
np.testing.assert_equal(p_init.shape, CarKalman.P_initial.shape) np.testing.assert_equal(p_init.shape, CarKalman.P_initial.shape)
np.testing.assert_allclose(np.diagonal(p_init), msg.liveParameters.debugFilterState.std) np.testing.assert_allclose(np.diagonal(p_init), msg.liveParameters.debugFilterState.std)
# TODO Remove this test after the support for old format is removed
def test_read_saved_params_old_format(self):
params = Params()
lr = migrate(LogReader(TEST_ROUTE), [migrate_carParams])
CP = next(m for m in lr if m.which() == "carParams").carParams
msg = get_random_live_parameters(CP)
params.put("LiveParameters", msg.liveParameters.to_dict(), block=True)
params.put("CarParamsPrevRoute", CP.as_builder().to_bytes(), block=True)
params.remove("LiveParametersV2")
migrate_cached_vehicle_params_if_needed(params)
sr, sf, offset, _ = retrieve_initial_vehicle_params(params, CP, replay=True, debug=True)
np.testing.assert_allclose(sr, msg.liveParameters.steerRatio)
np.testing.assert_allclose(sf, msg.liveParameters.stiffnessFactor)
np.testing.assert_allclose(offset, msg.liveParameters.angleOffsetAverageDeg)
assert params.get("LiveParametersV2") is not None
def test_read_saved_params_corrupted_old_format(self):
params = Params()
params.put("LiveParameters", {}, block=True)
params.remove("LiveParametersV2")
migrate_cached_vehicle_params_if_needed(params)
assert params.get("LiveParameters") is None
assert params.get("LiveParametersV2") is None
@@ -2,25 +2,24 @@ from opendbc.car.structs import car
from openpilot.selfdrive.locationd.torqued import TorqueEstimator from openpilot.selfdrive.locationd.torqued import TorqueEstimator
class TestTorqued: def test_cal_percent():
def test_cal_percent(self): est = TorqueEstimator(car.CarParams())
est = TorqueEstimator(car.CarParams()) msg = est.get_msg()
msg = est.get_msg() assert msg.liveTorqueParameters.calPerc == 0
assert msg.liveTorqueParameters.calPerc == 0
for (low, high), min_pts in zip(est.filtered_points.buckets.keys(), for (low, high), min_pts in zip(est.filtered_points.buckets.keys(),
est.filtered_points.buckets_min_points.values(), strict=True): est.filtered_points.buckets_min_points.values(), strict=True):
for _ in range(int(min_pts)): for _ in range(int(min_pts)):
est.filtered_points.add_point((low + high) / 2.0, 0.0) est.filtered_points.add_point((low + high) / 2.0, 0.0)
# enough bucket points, but not enough total points # enough bucket points, but not enough total points
msg = est.get_msg() msg = est.get_msg()
assert msg.liveTorqueParameters.calPerc == (len(est.filtered_points) / est.min_points_total * 100 + 100) / 2 assert msg.liveTorqueParameters.calPerc == (len(est.filtered_points) / est.min_points_total * 100 + 100) / 2
# add enough points to bucket with most capacity # add enough points to bucket with most capacity
key = list(est.filtered_points.buckets)[0] key = list(est.filtered_points.buckets)[0]
for _ in range(est.min_points_total - len(est.filtered_points)): for _ in range(est.min_points_total - len(est.filtered_points)):
est.filtered_points.add_point((key[0] + key[1]) / 2.0, 0.0) est.filtered_points.add_point((key[0] + key[1]) / 2.0, 0.0)
msg = est.get_msg() msg = est.get_msg()
assert msg.liveTorqueParameters.calPerc == 100 assert msg.liveTorqueParameters.calPerc == 100
+4 -5
View File
@@ -62,14 +62,14 @@ class TorqueEstimator(ParameterEstimator, TorqueEstimatorExt):
self.lag = 0.0 self.lag = 0.0
self.track_all_points = track_all_points # for offline analysis, without max lateral accel or max steer torque filters self.track_all_points = track_all_points # for offline analysis, without max lateral accel or max steer torque filters
if decimated: if decimated:
self.min_bucket_points: list[float] = (MIN_BUCKET_POINTS / 10).tolist() self.min_bucket_points = MIN_BUCKET_POINTS / 10
self.min_points_total = MIN_POINTS_TOTAL_QLOG self.min_points_total = MIN_POINTS_TOTAL_QLOG
self.fit_points = FIT_POINTS_TOTAL_QLOG self.fit_points = FIT_POINTS_TOTAL_QLOG
self.factor_sanity = FACTOR_SANITY_QLOG self.factor_sanity = FACTOR_SANITY_QLOG
self.friction_sanity = FRICTION_SANITY_QLOG self.friction_sanity = FRICTION_SANITY_QLOG
else: else:
self.min_bucket_points = MIN_BUCKET_POINTS.tolist() self.min_bucket_points = MIN_BUCKET_POINTS
self.min_points_total = MIN_POINTS_TOTAL self.min_points_total = MIN_POINTS_TOTAL
self.fit_points = FIT_POINTS_TOTAL self.fit_points = FIT_POINTS_TOTAL
self.factor_sanity = FACTOR_SANITY self.factor_sanity = FACTOR_SANITY
@@ -120,10 +120,9 @@ class TorqueEstimator(ParameterEstimator, TorqueEstimatorExt):
'latAccelOffset': cache_ltp.latAccelOffsetFiltered, 'latAccelOffset': cache_ltp.latAccelOffsetFiltered,
'frictionCoefficient': cache_ltp.frictionCoefficientFiltered 'frictionCoefficient': cache_ltp.frictionCoefficientFiltered
} }
cached_points: list[list[float]] = [list(point) for point in cache_ltp.points] initial_params['points'] = cache_ltp.points
initial_params['points'] = cached_points
self.decay = cache_ltp.decay self.decay = cache_ltp.decay
self.filtered_points.load_points(cached_points) self.filtered_points.load_points(initial_params['points'])
cloudlog.info("restored torque params from cache") cloudlog.info("restored torque params from cache")
except Exception: except Exception:
cloudlog.exception("failed to restore cached torque params") cloudlog.exception("failed to restore cached torque params")
+2 -6
View File
@@ -13,7 +13,6 @@ from collections import namedtuple
import numpy as np import numpy as np
from openpilot.selfdrive.modeld.helpers import dump_oob, load_oob from openpilot.selfdrive.modeld.helpers import dump_oob, load_oob
from openpilot.selfdrive.modeld.usbgpu_link import wait_usbgpu_link
def _patch_tinygrad_fetch_fw(): def _patch_tinygrad_fetch_fw():
import hashlib import hashlib
@@ -243,7 +242,7 @@ def compile_jit(jit, make_random_inputs, input_keys, make_queues):
SEED = 42 SEED = 42
def random_inputs_run(fn, seed, test_val=None, test_buffers=None, expect_match=True): def random_inputs_run(fn, seed, test_val=None, test_buffers=None, expect_match=True):
input_queues, npy = make_queues(Device.DEFAULT) input_queues, npy = make_queues(Device.DEFAULT)
rng = np.random.default_rng(seed) np.random.seed(seed)
Tensor.manual_seed(seed) Tensor.manual_seed(seed)
testing = test_val is not None or test_buffers is not None testing = test_val is not None or test_buffers is not None
@@ -251,7 +250,7 @@ def compile_jit(jit, make_random_inputs, input_keys, make_queues):
for i in range(n_runs): for i in range(n_runs):
for v in npy.values(): for v in npy.values():
v[:] = rng.standard_normal(v.shape).astype(v.dtype) v[:] = np.random.randn(*v.shape).astype(v.dtype)
Device.default.synchronize() Device.default.synchronize()
random_inputs = make_random_inputs() random_inputs = make_random_inputs()
st = time.perf_counter() st = time.perf_counter()
@@ -312,9 +311,6 @@ if __name__ == "__main__":
p.add_argument('--frame-skip', type=int, required=True) p.add_argument('--frame-skip', type=int, required=True)
args = p.parse_args() args = p.parse_args()
if 'USB+AMD' in os.environ.get('DEV', ''):
wait_usbgpu_link()
model_path = read_file_chunked_to_disk(args.onnx) model_path = read_file_chunked_to_disk(args.onnx)
model_w, model_h = args.model_size model_w, model_h = args.model_size
+1 -4
View File
@@ -25,7 +25,6 @@ from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_drivi
from openpilot.common.file_chunker import open_file_chunked, get_manifest_path from openpilot.common.file_chunker import open_file_chunked, get_manifest_path
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
from openpilot.selfdrive.modeld.helpers import usbgpu_present, modeld_pkl_path, get_tg_input_devices, load_oob from openpilot.selfdrive.modeld.helpers import usbgpu_present, modeld_pkl_path, get_tg_input_devices, load_oob
from openpilot.selfdrive.modeld.usbgpu_link import wait_usbgpu_link
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
@@ -95,7 +94,7 @@ class ModelState(ModelStateBase):
self.frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ self.frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ
self.input_queues, self.npy = make_input_queues(self.input_shapes, self.frame_skip, device=self.QUEUE_DEV) self.input_queues, self.npy = make_input_queues(self.input_shapes, self.frame_skip, device=self.QUEUE_DEV)
self.full_frames: dict[str, Tensor] = {} self.full_frames: dict[str, Tensor] = {}
self._blob_cache: dict[tuple[str, int], Tensor] = {} self._blob_cache: dict[int, Tensor] = {}
self.parser = Parser() self.parser = Parser()
self.frame_buf_params = {k: get_nv12_info(cam_w, cam_h) for k in ('img', 'big_img')} self.frame_buf_params = {k: get_nv12_info(cam_w, cam_h) for k in ('img', 'big_img')}
self.run_policy = jits['run_policy'] self.run_policy = jits['run_policy']
@@ -174,8 +173,6 @@ def main(demo=False):
if use_extra_client: if use_extra_client:
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})") cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
if USBGPU:
wait_usbgpu_link()
st = time.monotonic() st = time.monotonic()
cloudlog.warning("loading model") cloudlog.warning("loading model")
model = ModelState(vipc_client_main.width, vipc_client_main.height, USBGPU) model = ModelState(vipc_client_main.width, vipc_client_main.height, USBGPU)
@@ -41,7 +41,7 @@ class Parser:
raw = outs[name] raw = outs[name]
outs[name] = sigmoid(raw) outs[name] = sigmoid(raw)
def parse_mdn(self, name, outs, in_N=0, out_N=1, out_shape=()): def parse_mdn(self, name, outs, in_N=0, out_N=1, out_shape=None):
if self.check_missing(outs, name): if self.check_missing(outs, name):
return return
raw = outs[name] raw = outs[name]
-34
View File
@@ -1,34 +0,0 @@
import time
from pathlib import Path
from openpilot.common.swaglog import cloudlog
from openpilot.common.hardware.usb import CHESTNUT_VENDOR_ID, CHESTNUT_PRODUCT_ID, usb_devices, controller, read_int
STABLE_SECONDS = 2.0
STABLE_THRESHOLD = 5.0 # link errors per second
def _chestnut_portli() -> Path | None:
for device in usb_devices():
if read_int(device / "idVendor", 16) == CHESTNUT_VENDOR_ID and \
read_int(device / "idProduct", 16) == CHESTNUT_PRODUCT_ID:
ctrl = controller(device)
if ctrl is not None and (ctrl / "portli").exists():
return ctrl / "portli"
return None
def wait_usbgpu_link(timeout: float = 30.0) -> None:
portli = _chestnut_portli()
if portli is None:
return
t0 = time.monotonic()
while time.monotonic() - t0 < timeout:
start = read_int(portli, 0)
time.sleep(STABLE_SECONDS)
rate = (read_int(portli, 0) - start) / STABLE_SECONDS
if rate <= STABLE_THRESHOLD:
return
cloudlog.warning(f"usbgpu link not stable: {rate:.0f} errors/s")
cloudlog.error("usbgpu link never stabilized")
+11 -19
View File
@@ -41,7 +41,7 @@ class DRIVER_MONITOR_SETTINGS:
# lockout specs # lockout specs
self._MAX_ALERT_3 = 2 self._MAX_ALERT_3 = 2
self._MAX_NO_RESPONSE = 1 self._MAX_NO_RESPONSE = 1
self._LOCKOUT_TIMES = [int(60 * n_min / DT_DMON) for n_min in [1, 5, 15, 30]] self._LOCKOUT_TIME = int(1800 / DT_DMON)
self._TIMEOUT_RECOVERY_FACTOR_MAX = 5. self._TIMEOUT_RECOVERY_FACTOR_MAX = 5.
self._TIMEOUT_RECOVERY_FACTOR_MIN = 1.25 self._TIMEOUT_RECOVERY_FACTOR_MIN = 1.25
@@ -152,10 +152,7 @@ class DriverMonitoring:
self.cnt_since_alert_3 = 0 self.cnt_since_alert_3 = 0
self.no_response_timeout = int(self.settings._NO_RESPONSE_TIMEOUT / DT_DMON) self.no_response_timeout = int(self.settings._NO_RESPONSE_TIMEOUT / DT_DMON)
self.no_response_cnt = 0 self.no_response_cnt = 0
self.lockout_active = Params().get_bool("DriverTooDistracted") self.lockout_time = 0
self.lockout_count = Params().get("DriverLockoutCount") or 0
self.lockout_duration = self.settings._LOCKOUT_TIMES[min(max(self.lockout_count - 1, 0), len(self.settings._LOCKOUT_TIMES) - 1)]
self.lockout_time_elapsed = 0
self.step_change = 0. self.step_change = 0.
self.active_policy = MonitoringPolicy.vision self.active_policy = MonitoringPolicy.vision
self.driver_interacting = False self.driver_interacting = False
@@ -166,6 +163,7 @@ class DriverMonitoring:
self.threshold_alert_2 = 0. self.threshold_alert_2 = 0.
self.dcam_uncertain_cnt = 0 self.dcam_uncertain_cnt = 0
self.dcam_reset_cnt = 0 self.dcam_reset_cnt = 0
self.too_distracted = Params().get_bool("DriverTooDistracted")
self._reset_awareness() self._reset_awareness()
self._set_policy(MonitoringPolicy.vision) self._set_policy(MonitoringPolicy.vision)
@@ -312,20 +310,16 @@ class DriverMonitoring:
self.driver_interacting = driver_engaged self.driver_interacting = driver_engaged
if self.alert_3_cnt >= self.settings._MAX_ALERT_3 or self.no_response_cnt >= self.settings._MAX_NO_RESPONSE: if self.alert_3_cnt >= self.settings._MAX_ALERT_3 or self.no_response_cnt >= self.settings._MAX_NO_RESPONSE:
if not self.lockout_active: self.too_distracted = True
self.lockout_count += 1
self.lockout_duration = self.settings._LOCKOUT_TIMES[min(self.lockout_count - 1, len(self.settings._LOCKOUT_TIMES) - 1)]
Params().put("DriverLockoutCount", self.lockout_count)
self.lockout_active = True
if self.lockout_active: if self.too_distracted:
self.lockout_time_elapsed += 1 self.lockout_time += 1
if self.lockout_time_elapsed > self.lockout_duration: if self.lockout_time > self.settings._LOCKOUT_TIME:
self.lockout_active = False self.too_distracted = False
self.alert_3_cnt = 0 self.alert_3_cnt = 0
self.cnt_since_alert_3 = 0 self.cnt_since_alert_3 = 0
self.no_response_cnt = 0 self.no_response_cnt = 0
self.lockout_time_elapsed = 0 self.lockout_time = 0
always_on_valid = self.always_on and not wrong_gear always_on_valid = self.always_on and not wrong_gear
if (self.driver_interacting and self.awareness > 0 and self.active_policy == MonitoringPolicy.wheeltouch) or \ if (self.driver_interacting and self.awareness > 0 and self.active_policy == MonitoringPolicy.wheeltouch) or \
@@ -385,10 +379,8 @@ class DriverMonitoring:
dat = messaging.new_message('driverMonitoringState', valid=valid) dat = messaging.new_message('driverMonitoringState', valid=valid)
dm = dat.driverMonitoringState dm = dat.driverMonitoringState
dm.lockout = self.lockout_active dm.lockout = self.too_distracted
dm.lockoutCount = self.lockout_count dm.lockoutRecoveryPercent = to_percent(self.lockout_time / self.settings._LOCKOUT_TIME)
if self.lockout_active:
dm.lockoutMinutesRemaining = max(1, round((self.lockout_duration - self.lockout_time_elapsed) * DT_DMON / 60.))
dm.alert3Count = self.alert_3_cnt dm.alert3Count = self.alert_3_cnt
dm.noResponseCount = self.no_response_cnt dm.noResponseCount = self.no_response_cnt
dm.noResponseForceDecel = self.alert_level == AlertLevel.three and self.cnt_since_alert_3 >= self.no_response_timeout dm.noResponseForceDecel = self.alert_level == AlertLevel.three and self.cnt_since_alert_3 >= self.no_response_timeout
@@ -86,17 +86,21 @@ class TestMonitoring:
# engaged, distracted past red and beyond the no-response window -> unavailability response + lockout # engaged, distracted past red and beyond the no-response window -> unavailability response + lockout
def test_distracted_lockout(self): def test_distracted_lockout(self):
alert_lvls, d_status = self._run_seq(always_distracted, always_false, always_true, always_false) alert_lvls, d_status = self._run_seq(always_distracted, always_false, always_true, always_false)
s = d_status.settings
assert alert_lvls[int(DISTRACTED_SECONDS_TO_RED / DT_DMON)] == 3 assert alert_lvls[int(DISTRACTED_SECONDS_TO_RED / DT_DMON)] == 3
assert d_status.lockout_active assert d_status.alert_3_cnt == 1
assert d_status.lockout_time_elapsed > 0 assert d_status.no_response_cnt == s._MAX_NO_RESPONSE
assert d_status.lockout_count >= 1 assert d_status.too_distracted
assert d_status.lockout_time > 0
# no face -> wheeltouch red, sustained past the no-response timeout -> unavailability response + lockout # no face -> wheeltouch red, sustained past the no-response timeout -> unavailability response + lockout
def test_invisible_lockout(self): def test_invisible_lockout(self):
_, d_status = self._run_seq(always_no_face, always_false, always_true, always_false) _, d_status = self._run_seq(always_no_face, always_false, always_true, always_false)
s = d_status.settings
assert d_status.active_policy == log.DriverMonitoringState.MonitoringPolicy.wheeltouch assert d_status.active_policy == log.DriverMonitoringState.MonitoringPolicy.wheeltouch
assert d_status.lockout_active assert d_status.alert_3_cnt == 1
assert d_status.lockout_count >= 1 assert d_status.no_response_cnt == s._MAX_NO_RESPONSE
assert d_status.too_distracted
# engaged, no face detected the whole time, no action # engaged, no face detected the whole time, no action
def test_fully_invisible_driver(self): def test_fully_invisible_driver(self):
@@ -271,7 +275,7 @@ def test_run_step_engagement(selfdrive_enabled, lat_active, steering, gas,
captured['op_engaged'] = op_engaged captured['op_engaged'] = op_engaged
return orig(driver_engaged, op_engaged, lowspeed, wrong_gear) return orig(driver_engaged, op_engaged, lowspeed, wrong_gear)
object.__setattr__(dm, '_update_events', spy) dm._update_events = spy
dm.run_step(sm, demo=False) dm.run_step(sm, demo=False)
assert captured['op_engaged'] == expected_op_engaged assert captured['op_engaged'] == expected_op_engaged
assert captured['driver_engaged'] == expected_driver_engaged assert captured['driver_engaged'] == expected_driver_engaged
@@ -60,7 +60,7 @@ class TestPandad:
def test_in_reset(self): def test_in_reset(self):
gpio_init(GPIO.STM_RST_N, True) gpio_init(GPIO.STM_RST_N, True)
gpio_set(GPIO.STM_RST_N, True) gpio_set(GPIO.STM_RST_N, 1)
assert not Panda.list() assert not Panda.list()
self._run_test() self._run_test()
@@ -38,10 +38,10 @@ class TestBoarddSpi:
total_recv_count = 0 total_recv_count = 0
total_sent_count = 0 total_sent_count = 0
sent_msgs = {bus: [] for bus in range(3)} sent_msgs = {bus: list() for bus in range(3)}
st = time.monotonic() st = time.monotonic()
ts = {s: [] for s in socks.keys()} ts = {s: list() for s in socks.keys()}
for _ in range(int(os.getenv("TEST_TIME", "20"))): for _ in range(int(os.getenv("TEST_TIME", "20"))):
# send some CAN messages # send some CAN messages
if not JUNGLE_SPAM: if not JUNGLE_SPAM:
+4 -2
View File
@@ -7,8 +7,9 @@ from opendbc.car.structs import car
import openpilot.cereal.messaging as messaging import openpilot.cereal.messaging as messaging
from openpilot.common.constants import CV from openpilot.common.constants import CV
from openpilot.common.git import get_short_branch from openpilot.common.git import get_short_branch
from openpilot.common.realtime import DT_CTRL from openpilot.common.realtime import DT_CTRL, DT_DMON
from openpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER from openpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER
from openpilot.selfdrive.monitoring.policy import DRIVER_MONITOR_SETTINGS
from openpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER from openpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER
from openpilot.selfdrive.ui.feedback.feedbackd import FEEDBACK_MAX_DURATION from openpilot.selfdrive.ui.feedback.feedbackd import FEEDBACK_MAX_DURATION
from openpilot.common.hardware import HARDWARE from openpilot.common.hardware import HARDWARE
@@ -24,6 +25,7 @@ VisualAlert = car.CarControl.HUDControl.VisualAlert
AudibleAlert = log.SelfdriveState.AudibleAlert AudibleAlert = log.SelfdriveState.AudibleAlert
EventName = log.OnroadEvent.EventName EventName = log.OnroadEvent.EventName
DMON_LOCKOUT_TIME = DRIVER_MONITOR_SETTINGS()._LOCKOUT_TIME
# get event name from enum # get event name from enum
EVENT_NAME = {v: k for k, v in EventName.schema.enumerants.items()} EVENT_NAME = {v: k for k, v in EventName.schema.enumerants.items()}
@@ -99,7 +101,7 @@ def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messag
def too_distracted_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: def too_distracted_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
if sm['driverMonitoringState'].lockout: if sm['driverMonitoringState'].lockout:
mins_left = sm['driverMonitoringState'].lockoutMinutesRemaining mins_left = max(1, round((100 - sm['driverMonitoringState'].lockoutRecoveryPercent) / 100 * DMON_LOCKOUT_TIME * DT_DMON / 60.))
return NoEntryAlert("Too Distracted", f"{mins_left} minute{'s' if mins_left != 1 else ''} Left", priority=Priority.HIGH) return NoEntryAlert("Too Distracted", f"{mins_left} minute{'s' if mins_left != 1 else ''} Left", priority=Priority.HIGH)
return NoEntryAlert("Pay Attention to Engage", priority=Priority.HIGH) return NoEntryAlert("Pay Attention to Engage", priority=Priority.HIGH)
@@ -13,11 +13,11 @@ ALL_STATES = tuple(State.schema.enumerants.values())
ENABLE_EVENT_TYPES = (ET.ENABLE, ET.PRE_ENABLE, ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL) ENABLE_EVENT_TYPES = (ET.ENABLE, ET.PRE_ENABLE, ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL)
def make_event(event_types: list[str | None]): def make_event(event_types):
EVENTS[0] = {} event = {}
for ev in event_types: for ev in event_types:
if ev is not None: event[ev] = NormalPermanentAlert("alert")
EVENTS[0][ev] = NormalPermanentAlert("alert") EVENTS[0] = event
return 0 return 0
+1 -1
View File
@@ -7,7 +7,7 @@ from openpilot.common.utils import tabulate
DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496" DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496"
MB = 1024 * 1024 MB = 1024 * 1024
TABULATE_OPTS = {"tablefmt": "simple_grid", "stralign": "center", "numalign": "center"} TABULATE_OPTS = dict(tablefmt="simple_grid", stralign="center", numalign="center")
def _get_procs(): def _get_procs():
@@ -297,7 +297,7 @@ def migrate_carOutput(msgs):
co = messaging.new_message('carOutput') co = messaging.new_message('carOutput')
co.valid = msg.valid co.valid = msg.valid
co.logMonoTime = msg.logMonoTime co.logMonoTime = msg.logMonoTime
co.carOutput.actuatorsOutput = msg.carControl.deprecated.actuatorsOutput co.carOutput.actuatorsOutput = msg.carControl.actuatorsOutputDEPRECATED
add_ops.append(as_reader(co)) add_ops.append(as_reader(co))
return [], add_ops, [] return [], add_ops, []
@@ -323,10 +323,10 @@ def migrate_pandaStates(msgs):
safety_param = safety_param_migration[fingerprint].value safety_param = safety_param_migration[fingerprint].value
elif len(CP.safetyConfigs): elif len(CP.safetyConfigs):
safety_param = CP.safetyConfigs[0].safetyParam safety_param = CP.safetyConfigs[0].safetyParam
if CP.safetyConfigs[0].deprecated.safetyParam != 0: if CP.safetyConfigs[0].safetyParamDEPRECATED != 0:
safety_param = CP.safetyConfigs[0].deprecated.safetyParam safety_param = CP.safetyConfigs[0].safetyParamDEPRECATED
else: else:
safety_param = CP.deprecated.safetyParam safety_param = CP.safetyParamDEPRECATED
ops = [] ops = []
for index, msg in msgs: for index, msg in msgs:
@@ -216,7 +216,8 @@ class ProcessContainer:
def _start_process(self): def _start_process(self):
if self.capture is not None: if self.capture is not None:
self.process.launcher = LauncherWithCapture(self.capture, self.process.launcher) # ty: ignore[invalid-assignment] # intentional wrapper self.process.launcher = LauncherWithCapture(self.capture, self.process.launcher)
self.process.prepare()
self.process.start() self.process.start()
def start( def start(
@@ -634,10 +635,10 @@ def replay_process(
fingerprint: str | None = None, return_all_logs: bool = False, custom_params: dict[str, Any] | None = None, fingerprint: str | None = None, return_all_logs: bool = False, custom_params: dict[str, Any] | None = None,
captured_output_store: dict[str, dict[str, str]] | None = None, disable_progress: bool = False captured_output_store: dict[str, dict[str, str]] | None = None, disable_progress: bool = False
) -> list[capnp._DynamicStructReader]: ) -> list[capnp._DynamicStructReader]:
if isinstance(cfg, ProcessConfig): if isinstance(cfg, Iterable):
cfgs = [cfg]
else:
cfgs = list(cfg) cfgs = list(cfg)
else:
cfgs = [cfg]
all_msgs = migrate_all(lr, all_msgs = migrate_all(lr,
manager_states=True, manager_states=True,
+4 -7
View File
@@ -204,10 +204,7 @@ class FaceAnimator:
frames_back = round(rewind_elapsed / self._animation.frame_duration) frames_back = round(rewind_elapsed / self._animation.frame_duration)
frame_index = self._rewind_from - frames_back frame_index = self._rewind_from - frames_back
if frame_index <= 0: if frame_index <= 0:
if self._next is None: return self._switch_to_next(now)
self._rewinding = False
return self._animation.frames[0]
return self._switch_to_next(now, self._next)
return self._animation.frames[frame_index] return self._animation.frames[frame_index]
# Play starting frames first (once) # Play starting frames first (once)
@@ -226,7 +223,7 @@ class FaceAnimator:
if self._next is not None: if self._next is not None:
if frame_index == 0 and (len(self._animation.frames) == 1 or self._seen_nonzero): if frame_index == 0 and (len(self._animation.frames) == 1 or self._seen_nonzero):
return self._switch_to_next(now, self._next) return self._switch_to_next(now)
# No natural return to frame 0 — start rewinding # No natural return to frame 0 — start rewinding
if self._animation.mode in (AnimationMode.ONCE_FORWARD, AnimationMode.REPEAT_FORWARD): if self._animation.mode in (AnimationMode.ONCE_FORWARD, AnimationMode.REPEAT_FORWARD):
self._rewinding = True self._rewinding = True
@@ -235,8 +232,8 @@ class FaceAnimator:
return self._animation.frames[frame_index] return self._animation.frames[frame_index]
def _switch_to_next(self, now: float, animation: Animation) -> list[tuple[int, int]]: def _switch_to_next(self, now: float) -> list[tuple[int, int]]:
self._animation = animation self._animation = self._next
self._next = None self._next = None
self._rewinding = False self._rewinding = False
self._seen_nonzero = False self._seen_nonzero = False
+1 -5
View File
@@ -34,11 +34,7 @@ class MainLayout(Widget):
# Initialize layouts # Initialize layouts
self._home_layout = HomeLayout() self._home_layout = HomeLayout()
self._home_body_layout = BodyLayout() self._home_body_layout = BodyLayout()
self._layouts: dict[MainState, Widget] = { self._layouts = {MainState.HOME: self._home_layout, MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView()}
MainState.HOME: self._home_layout,
MainState.SETTINGS: SettingsLayout(),
MainState.ONROAD: AugmentedRoadView(),
}
self._sidebar_rect = rl.Rectangle(0, 0, 0, 0) self._sidebar_rect = rl.Rectangle(0, 0, 0, 0)
self._content_rect = rl.Rectangle(0, 0, 0, 0) self._content_rect = rl.Rectangle(0, 0, 0, 0)
@@ -105,6 +105,7 @@ class DeviceLayout(Widget):
self._params.remove("CalibrationParams") self._params.remove("CalibrationParams")
self._params.remove("LiveTorqueParameters") self._params.remove("LiveTorqueParameters")
self._params.remove("LiveParameters")
self._params.remove("LiveParametersV2") self._params.remove("LiveParametersV2")
self._params.remove("LiveDelay") self._params.remove("LiveDelay")
self._params.put_bool("OnroadCycleRequested", True, block=True) self._params.put_bool("OnroadCycleRequested", True, block=True)
@@ -1,5 +1,5 @@
import pyray as rl import pyray as rl
from dataclasses import dataclass, field from dataclasses import dataclass
from enum import IntEnum from enum import IntEnum
from collections.abc import Callable from collections.abc import Callable
from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout
@@ -43,7 +43,7 @@ class PanelType(IntEnum):
class PanelInfo: class PanelInfo:
name: str name: str
instance: Widget instance: Widget
button_rect: rl.Rectangle = field(default_factory=lambda: rl.Rectangle(0, 0, 0, 0)) button_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
class SettingsLayout(Widget): class SettingsLayout(Widget):
+1 -1
View File
@@ -68,7 +68,7 @@ class Sidebar(Widget, SidebarSP):
def __init__(self): def __init__(self):
Widget.__init__(self) Widget.__init__(self)
SidebarSP.__init__(self) SidebarSP.__init__(self)
self._net_type = NETWORK_TYPES[NetworkType.none] self._net_type = NETWORK_TYPES.get(NetworkType.none)
self._net_strength = 0 self._net_strength = 0
self._temp_status = MetricData(tr_noop("TEMP"), tr_noop("GOOD"), Colors.GOOD) self._temp_status = MetricData(tr_noop("TEMP"), tr_noop("GOOD"), Colors.GOOD)
+1 -1
View File
@@ -251,7 +251,7 @@ class MiciHomeLayout(Widget):
self._egpu_icon.set_visible(ui_state.usbgpu and ui_state.usbgpu_compiled) self._egpu_icon.set_visible(ui_state.usbgpu and ui_state.usbgpu_compiled)
self._egpu_icon_gray.set_visible(ui_state.usbgpu and not ui_state.usbgpu_compiled) self._egpu_icon_gray.set_visible(ui_state.usbgpu and not ui_state.usbgpu_compiled)
self._mic_icon.set_visible(ui_state.recording_audio) self._mic_icon.set_visible(ui_state.recording_audio)
self._body_icon.set_visible(bool(ui_state.is_body)) self._body_icon.set_visible(ui_state.is_body)
footer_rect = rl.Rectangle(self.rect.x + HOME_PADDING, self.rect.y + self.rect.height - 48, self.rect.width - HOME_PADDING, 48) footer_rect = rl.Rectangle(self.rect.x + HOME_PADDING, self.rect.y + self.rect.height - 48, self.rect.width - HOME_PADDING, 48)
self._status_bar_layout.render(footer_rect) self._status_bar_layout.render(footer_rect)
+1 -1
View File
@@ -148,4 +148,4 @@ class MiciMainLayout(Scroller):
def _on_body_changed(self): def _on_body_changed(self):
self._car_onroad_layout.set_visible(not ui_state.is_body) self._car_onroad_layout.set_visible(not ui_state.is_body)
self._body_onroad_layout.set_visible(bool(ui_state.is_body)) self._body_onroad_layout.set_visible(ui_state.is_body)
@@ -250,12 +250,12 @@ class MiciOffroadAlerts(Scroller):
{alert_data.key: self.params.get(alert_data.key) for alert_data in self.sorted_alerts}) {alert_data.key: self.params.get(alert_data.key) for alert_data in self.sorted_alerts})
time.sleep(REFRESH_INTERVAL) time.sleep(REFRESH_INTERVAL)
def _refresh(self, pending_params: dict) -> int: def _refresh(self) -> int:
"""Refresh alerts from params and return active count.""" """Refresh alerts from params and return active count."""
active_count = 0 active_count = 0
# Handle UpdateAvailable alert specially # Handle UpdateAvailable alert specially
update_available = pending_params["UpdateAvailable"] update_available = self._pending_params["UpdateAvailable"]
update_alert_data = next((alert_data for alert_data in self.sorted_alerts if alert_data.key == "UpdateAvailable"), None) update_alert_data = next((alert_data for alert_data in self.sorted_alerts if alert_data.key == "UpdateAvailable"), None)
if update_alert_data: if update_alert_data:
@@ -263,7 +263,7 @@ class MiciOffroadAlerts(Scroller):
version_string = "" version_string = ""
# Get new version description and parse version and date # Get new version description and parse version and date
new_desc = pending_params["UpdaterNewDescription"] or "" new_desc = self._pending_params["UpdaterNewDescription"] or ""
if new_desc: if new_desc:
# format: "version / branch / commit / date" # format: "version / branch / commit / date"
parts = new_desc.split(" / ") parts = new_desc.split(" / ")
@@ -284,7 +284,7 @@ class MiciOffroadAlerts(Scroller):
continue # Skip, already handled above continue # Skip, already handled above
text = "" text = ""
alert_json = pending_params[alert_data.key] alert_json = self._pending_params[alert_data.key]
if alert_json: if alert_json:
text = alert_json.get("text", "").replace("%1", alert_json.get("extra", "")) text = alert_json.get("text", "").replace("%1", alert_json.get("extra", ""))
@@ -311,9 +311,8 @@ class MiciOffroadAlerts(Scroller):
def _update_state(self): def _update_state(self):
"""Periodically refresh alerts.""" """Periodically refresh alerts."""
# Refresh alerts when thread updates params # Refresh alerts when thread updates params
pending_params = self._pending_params if self._pending_params is not None:
if pending_params is not None: self._refresh()
self._refresh(pending_params)
self._pending_params = None self._pending_params = None
def _render(self, rect: rl.Rectangle): def _render(self, rect: rl.Rectangle):
@@ -16,7 +16,7 @@ from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets import Widget
from openpilot.selfdrive.ui.ui_state import device, ui_state from openpilot.selfdrive.ui.ui_state import device, ui_state
from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.label import UnifiedLabel
from openpilot.system.ui.widgets.html_render import HtmlRenderer from openpilot.system.ui.widgets.html_render import HtmlModal, HtmlRenderer
from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
@@ -160,7 +160,7 @@ class DeviceLayoutMici(NavScroller):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._fcc_dialog: MiciFccModal | None = None self._fcc_dialog: HtmlModal | None = None
def power_off_callback(): def power_off_callback():
ui_state.params.put_bool("DoShutdown", True, block=True) ui_state.params.put_bool("DoShutdown", True, block=True)
@@ -172,6 +172,7 @@ class DeviceLayoutMici(NavScroller):
params = ui_state.params params = ui_state.params
params.remove("CalibrationParams") params.remove("CalibrationParams")
params.remove("LiveTorqueParameters") params.remove("LiveTorqueParameters")
params.remove("LiveParameters")
params.remove("LiveParametersV2") params.remove("LiveParametersV2")
params.remove("LiveDelay") params.remove("LiveDelay")
params.put_bool("OnroadCycleRequested", True, block=True) params.put_bool("OnroadCycleRequested", True, block=True)
@@ -219,7 +219,7 @@ class CameraView(Widget):
[0.0, 0.0, 1.0] [0.0, 0.0, 1.0]
]) ])
def _render(self, rect: rl.Rectangle, /): def _render(self, rect: rl.Rectangle):
if self._switching: if self._switching:
self._handle_switch() self._handle_switch()
@@ -47,8 +47,8 @@ class ModelPoints:
@dataclass @dataclass
class LeadVehicle: class LeadVehicle:
glow: list[tuple[float, float]] = field(default_factory=list) glow: list[float] = field(default_factory=list)
chevron: list[tuple[float, float]] = field(default_factory=list) chevron: list[float] = field(default_factory=list)
fill_alpha: int = 0 fill_alpha: int = 0
@@ -41,81 +41,80 @@ def get_child_widgets(widget) -> list:
return children return children
class TestWidgetLeaks: @pytest.mark.skip(reason="segfaults")
@pytest.mark.skip(reason="segfaults") def test_dialogs_do_not_leak():
def test_dialogs_do_not_leak(self): import pyray as rl
import pyray as rl rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN)
rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN) from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.application import gui_app
# mici dialogs # mici dialogs
from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide as MiciTrainingGuide, OnboardingWindow as MiciOnboardingWindow from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide as MiciTrainingGuide, OnboardingWindow as MiciOnboardingWindow
from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog as MiciDriverCameraDialog from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog as MiciDriverCameraDialog
from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog as MiciPairingDialog from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog as MiciPairingDialog
from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialog, BigInputDialog from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialog, BigInputDialog
from openpilot.selfdrive.ui.mici.layouts.settings.device import MiciFccModal from openpilot.selfdrive.ui.mici.layouts.settings.device import MiciFccModal
# tici dialogs # tici dialogs
from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog as TiciDriverCameraDialog from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog as TiciDriverCameraDialog
from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow as TiciOnboardingWindow from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow as TiciOnboardingWindow
from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog as TiciPairingDialog from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog as TiciPairingDialog
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
from openpilot.system.ui.widgets.html_render import HtmlModal from openpilot.system.ui.widgets.html_render import HtmlModal
from openpilot.system.ui.widgets.keyboard import Keyboard from openpilot.system.ui.widgets.keyboard import Keyboard
gui_app.init_window("ref-test") gui_app.init_window("ref-test")
leaked_widgets = set() leaked_widgets = set()
for ctor in ( for ctor in (
# mici # mici
MiciDriverCameraDialog, MiciPairingDialog, MiciDriverCameraDialog, MiciPairingDialog,
lambda: MiciTrainingGuide(lambda: None), lambda: MiciTrainingGuide(lambda: None),
lambda: MiciOnboardingWindow(lambda: None), lambda: MiciOnboardingWindow(lambda: None),
lambda: BigDialog("test", "test"), lambda: BigDialog("test", "test"),
lambda: BigConfirmationDialog("test", gui_app.texture("icons_mici/settings/network/new/trash.png", 54, 64), lambda: None), lambda: BigConfirmationDialog("test", gui_app.texture("icons_mici/settings/network/new/trash.png", 54, 64), lambda: None),
lambda: BigInputDialog("test"), lambda: BigInputDialog("test"),
lambda: MiciFccModal(text="test"), lambda: MiciFccModal(text="test"),
# tici # tici
TiciDriverCameraDialog, TiciOnboardingWindow, TiciPairingDialog, Keyboard, TiciDriverCameraDialog, TiciOnboardingWindow, TiciPairingDialog, Keyboard,
lambda: ConfirmDialog("test", "ok"), lambda: ConfirmDialog("test", "ok"),
lambda: MultiOptionDialog("test", ["a", "b"]), lambda: MultiOptionDialog("test", ["a", "b"]),
lambda: HtmlModal(text="test"), lambda: HtmlModal(text="test"),
): ):
widget = ctor() widget = ctor()
all_refs = [weakref.ref(w) for w in get_child_widgets(widget) + [widget]] all_refs = [weakref.ref(w) for w in get_child_widgets(widget) + [widget]]
del widget del widget
for ref in all_refs: for ref in all_refs:
if ref() is not None: if ref() is not None:
obj = ref() obj = ref()
name = f"{type(obj).__module__}.{type(obj).__qualname__}" name = f"{type(obj).__module__}.{type(obj).__qualname__}"
leaked_widgets.add(name) leaked_widgets.add(name)
print(f"\n=== Widget {name} alive after del") print(f"\n=== Widget {name} alive after del")
print(" Referrers:") print(" Referrers:")
for r in gc.get_referrers(obj): for r in gc.get_referrers(obj):
if r is obj: if r is obj:
continue continue
if hasattr(r, '__self__') and r.__self__ is not obj: if hasattr(r, '__self__') and r.__self__ is not obj:
print(f" bound method: {type(r.__self__).__qualname__}.{r.__name__}") print(f" bound method: {type(r.__self__).__qualname__}.{r.__name__}")
elif hasattr(r, '__func__'): elif hasattr(r, '__func__'):
print(f" method: {r.__name__}") print(f" method: {r.__name__}")
else: else:
print(f" {type(r).__module__}.{type(r).__qualname__}") print(f" {type(r).__module__}.{type(r).__qualname__}")
del obj del obj
gui_app.close() gui_app.close()
unexpected = leaked_widgets - KNOWN_LEAKS unexpected = leaked_widgets - KNOWN_LEAKS
assert not unexpected, f"New leaked widgets: {unexpected}" assert not unexpected, f"New leaked widgets: {unexpected}"
fixed = KNOWN_LEAKS - leaked_widgets fixed = KNOWN_LEAKS - leaked_widgets
assert not fixed, f"These leaks are fixed, remove from KNOWN_LEAKS: {fixed}" assert not fixed, f"These leaks are fixed, remove from KNOWN_LEAKS: {fixed}"
if __name__ == "__main__": if __name__ == "__main__":
TestWidgetLeaks().test_dialogs_do_not_leak() test_dialogs_do_not_leak()
@@ -375,7 +375,6 @@ class GreyBigButton(BigButton):
class BigMultiParamToggle(BigMultiToggle): class BigMultiParamToggle(BigMultiToggle):
def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable | None = None, def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable | None = None,
select_callback: Callable | None = None): select_callback: Callable | None = None):
assert Params is not None
super().__init__(text, options, toggle_callback, select_callback) super().__init__(text, options, toggle_callback, select_callback)
self._param = param self._param = param
@@ -393,7 +392,6 @@ class BigMultiParamToggle(BigMultiToggle):
class BigParamControl(BigToggle): class BigParamControl(BigToggle):
def __init__(self, text: str, param: str, toggle_callback: Callable | None = None): def __init__(self, text: str, param: str, toggle_callback: Callable | None = None):
assert Params is not None
super().__init__(text, "", toggle_callback=toggle_callback) super().__init__(text, "", toggle_callback=toggle_callback)
self.param = param self.param = param
self.params = Params() self.params = Params()
@@ -411,7 +409,6 @@ class BigParamControl(BigToggle):
class BigCircleParamControl(BigCircleToggle): class BigCircleParamControl(BigCircleToggle):
def __init__(self, icon: rl.Texture, param: str, toggle_callback: Callable | None = None, def __init__(self, icon: rl.Texture, param: str, toggle_callback: Callable | None = None,
icon_offset: tuple[int, int] = (0, 0)): icon_offset: tuple[int, int] = (0, 0)):
assert Params is not None
super().__init__(icon, toggle_callback, icon_offset=icon_offset) super().__init__(icon, toggle_callback, icon_offset=icon_offset)
self._param = param self._param = param
self.params = Params() self.params = Params()
@@ -39,8 +39,8 @@ class ModelPoints:
@dataclass @dataclass
class LeadVehicle: class LeadVehicle:
glow: list[tuple[float, float]] = field(default_factory=list) glow: list[float] = field(default_factory=list)
chevron: list[tuple[float, float]] = field(default_factory=list) chevron: list[float] = field(default_factory=list)
fill_alpha: int = 0 fill_alpha: int = 0
+6 -21
View File
@@ -24,7 +24,7 @@ ALERT_RAMP_TIME = 4 # seconds to ramp to max volume for warningImmediate
SELFDRIVE_STATE_TIMEOUT = 5 # 5 seconds SELFDRIVE_STATE_TIMEOUT = 5 # 5 seconds
FILTER_DT = 1. / (micd.SAMPLE_RATE / micd.FFT_SAMPLES) FILTER_DT = 1. / (micd.SAMPLE_RATE / micd.FFT_SAMPLES)
AMBIENT_DB = 26 # DB where MIN_VOLUME is applied AMBIENT_DB = 24 # DB where MIN_VOLUME is applied
DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied
VOLUME_BASE = 20 VOLUME_BASE = 20
@@ -48,15 +48,14 @@ sound_list: dict[int, tuple[str, int | None, float]] = {
AudibleAlert.disengage: ("disengage.wav", 1, MAX_VOLUME), AudibleAlert.disengage: ("disengage.wav", 1, MAX_VOLUME),
AudibleAlert.refuse: ("refuse.wav", 1, MAX_VOLUME), AudibleAlert.refuse: ("refuse.wav", 1, MAX_VOLUME),
AudibleAlert.prompt: ("warning.wav", 1, MAX_VOLUME), AudibleAlert.prompt: ("prompt.wav", 1, MAX_VOLUME),
AudibleAlert.promptRepeat: ("warning.wav", None, MAX_VOLUME), AudibleAlert.promptRepeat: ("prompt.wav", None, MAX_VOLUME),
AudibleAlert.promptDistracted: ("dm_warning.wav", None, MAX_VOLUME), AudibleAlert.promptDistracted: ("prompt_distracted.wav", None, MAX_VOLUME),
AudibleAlert.preAlert: ("pre_alert.wav", 1, MAX_VOLUME), AudibleAlert.preAlert: ("pre_alert.wav", 1, MAX_VOLUME),
AudibleAlert.complete: ("complete.wav", 1, MAX_VOLUME),
AudibleAlert.warningSoft: ("critical.wav", None, MAX_VOLUME), AudibleAlert.warningSoft: ("warning_soft.wav", None, MAX_VOLUME),
AudibleAlert.warningImmediate: ("dm_critical.wav", None, MAX_VOLUME), AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME),
**sound_list_sp, **sound_list_sp,
} }
@@ -90,7 +89,6 @@ class Soundd(QuietMode):
self.ramp_start_time = 0. self.ramp_start_time = 0.
self.selfdrive_timeout_alert = False self.selfdrive_timeout_alert = False
self.pending_stop = False
self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False) self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False)
@@ -129,10 +127,6 @@ class Soundd(QuietMode):
self.current_sound_frame += frames_to_write self.current_sound_frame += frames_to_write
current_sound_frame = self.current_sound_frame % len(sound_data) current_sound_frame = self.current_sound_frame % len(sound_data)
loops = self.current_sound_frame // len(sound_data) loops = self.current_sound_frame // len(sound_data)
if self.pending_stop and current_sound_frame == 0:
self.current_alert = AudibleAlert.none
self.pending_stop = False
break
return ret * self.current_volume return ret * self.current_volume
@@ -143,15 +137,6 @@ class Soundd(QuietMode):
def update_alert(self, new_alert): def update_alert(self, new_alert):
current_alert_played_once = self.current_alert == AudibleAlert.none or self.current_sound_frame >= len(self.loaded_sounds[self.current_alert]) current_alert_played_once = self.current_alert == AudibleAlert.none or self.current_sound_frame >= len(self.loaded_sounds[self.current_alert])
# let looping sounds finish the current loop instead of cutting off mid tone
if new_alert == AudibleAlert.none and self.current_alert != AudibleAlert.none and sound_list[self.current_alert][1] is None:
if current_alert_played_once:
self.pending_stop = True
else:
self.current_alert = AudibleAlert.none
self.current_sound_frame = 0
return
self.pending_stop = False
if self.current_alert != new_alert and (new_alert != AudibleAlert.none or current_alert_played_once): if self.current_alert != new_alert and (new_alert != AudibleAlert.none or current_alert_played_once):
if new_alert == AudibleAlert.warningImmediate: if new_alert == AudibleAlert.warningImmediate:
self.ramp_start_volume = self.current_volume self.ramp_start_volume = self.current_volume
@@ -20,6 +20,7 @@ from openpilot.system.ui.widgets.list_view import button_item
from openpilot.system.ui.sunnypilot.widgets.html_render import HtmlModalSP from openpilot.system.ui.sunnypilot.widgets.html_render import HtmlModalSP
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.external_storage import external_storage_item
PREBUILT_PATH = os.path.join(Paths.comma_home(), "prebuilt") if PC else "/data/openpilot/prebuilt" PREBUILT_PATH = os.path.join(Paths.comma_home(), "prebuilt") if PC else "/data/openpilot/prebuilt"
@@ -52,7 +53,11 @@ class DeveloperLayoutSP(DeveloperLayout):
self.error_log_btn = button_item(tr("Error Log"), tr("VIEW"), tr("View the error log for sunnypilot crashes."), callback=self._on_error_log_clicked) self.error_log_btn = button_item(tr("Error Log"), tr("VIEW"), tr("View the error log for sunnypilot crashes."), callback=self._on_error_log_clicked)
self.items: list = [self.show_advanced_controls, self.enable_github_runner_toggle, self.enable_copyparty_toggle, self.prebuilt_toggle, self.error_log_btn,] self.external_storage = external_storage_item(tr("External Storage"), description=tr("Extend your comma device's storage by inserting a USB drive " +
"into the aux port."))
self.items: list = [self.show_advanced_controls, self.enable_github_runner_toggle, self.enable_copyparty_toggle, self.prebuilt_toggle,
self.external_storage, self.error_log_btn,]
@staticmethod @staticmethod
def _on_prebuilt_toggled(state): def _on_prebuilt_toggled(state):
@@ -0,0 +1,261 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
import threading
import subprocess
import copy
from enum import Enum
from collections.abc import Callable
from openpilot.common.params import Params
from openpilot.common.hardware import PC
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets.button import Button, ButtonStyle
from openpilot.system.ui.widgets.confirm_dialog import alert_dialog, ConfirmDialog
from openpilot.system.ui.widgets.list_view import (
ItemAction,
ListItem,
BUTTON_HEIGHT,
BUTTON_BORDER_RADIUS,
BUTTON_FONT_SIZE,
BUTTON_WIDTH,
)
VALUE_FONT_SIZE = 48
class ExternalStorageState(Enum):
DISABLED = tr_noop("DISABLED")
LOADING = tr_noop("LOADING")
CHECK = tr_noop("CHECK")
MOUNT = tr_noop("MOUNT")
UNMOUNT = tr_noop("UNMOUNT")
FORMAT = tr_noop("FORMAT")
class ExternalStorageAction(ItemAction):
MAX_WIDTH = 500
def __init__(self):
super().__init__(self.MAX_WIDTH, True)
self._params = Params()
self._error_message = ""
self._text_font = gui_app.font(FontWeight.NORMAL)
self._button = Button(
"",
click_callback=self._handle_button_click,
button_style=ButtonStyle.LIST_ACTION,
border_radius=BUTTON_BORDER_RADIUS,
font_size=BUTTON_FONT_SIZE,
)
self._value_text = ""
self._formatting = False
self._refresh_pending = False
self._state = ExternalStorageState.CHECK
self._refresh_state()
self.refresh()
def set_touch_valid_callback(self, callback):
def wrapped():
if self._state == ExternalStorageState.DISABLED:
return False
return callback()
super().set_touch_valid_callback(wrapped)
self._button.set_touch_valid_callback(wrapped)
def _run(self, cmd: str) -> bool:
return subprocess.call(["sh", "-c", cmd]) == 0
def _run_output(self, cmd: str) -> str:
try:
out = subprocess.check_output(["sh", "-c", cmd], universal_newlines=True)
return out.strip()
except Exception:
return ""
def _render(self, rect: rl.Rectangle) -> bool:
if self._error_message:
msg = copy.copy(self._error_message)
gui_app.set_modal_overlay(alert_dialog(msg))
self._error_message = ""
if self._value_text:
text_size = measure_text_cached(self._text_font, self._value_text, VALUE_FONT_SIZE)
rl.draw_text_ex(
self._text_font,
self._value_text,
(rect.x + rect.width - BUTTON_WIDTH - text_size.x - 30,
rect.y + (rect.height - text_size.y) / 2),
VALUE_FONT_SIZE,
1.0,
rl.Color(170, 170, 170, 255),
)
button_rect = rl.Rectangle(
rect.x + rect.width - BUTTON_WIDTH,
rect.y + (rect.height - BUTTON_HEIGHT) / 2,
BUTTON_WIDTH,
BUTTON_HEIGHT
)
self._button.set_rect(button_rect)
self._button.set_text(tr(self._state.value))
self._button.set_enabled(self._state not in (ExternalStorageState.LOADING,
ExternalStorageState.DISABLED))
self._button.render(button_rect)
return False
def _refresh_state(self):
if PC:
self._state = ExternalStorageState.DISABLED
self._button.set_enabled(False)
self._value_text = ""
def debounced_refresh(self):
if self._refresh_pending:
return
self._refresh_pending = True
def _timer():
import time
time.sleep(0.25)
self._refresh_pending = False
self.refresh()
threading.Thread(target=_timer, daemon=True).start()
def refresh(self):
def _work():
is_mounted = self._run("findmnt -n /mnt/external_realdata")
has_drive = self._run("lsblk -f /dev/sdg")
has_fs = self._run("lsblk -f /dev/sdg1 | grep -q ext4")
has_label = self._run("blkid /dev/sdg1 | grep -q 'LABEL=\"openpilot\"'")
info = ""
if is_mounted and has_label:
info = self._run_output(
"df -h /mnt/external_realdata | awk 'NR==2 {print $3 \"/\" $2}'"
)
def apply():
if self._formatting:
self._value_text = tr("formatting")
self._state = ExternalStorageState.FORMAT
return
if not has_drive:
self._value_text = tr("insert drive")
self._state = ExternalStorageState.CHECK
elif not has_fs or not has_label:
self._value_text = tr("needs format")
self._state = ExternalStorageState.FORMAT
elif is_mounted:
self._value_text = info
self._state = ExternalStorageState.UNMOUNT
else:
self._value_text = tr("drive detected")
self._state = ExternalStorageState.MOUNT
apply()
threading.Thread(target=_work, daemon=True).start()
def _handle_button_click(self):
st = self._state
if st == ExternalStorageState.DISABLED:
return
if st in (ExternalStorageState.CHECK, ExternalStorageState.MOUNT):
self.mount_storage()
elif st == ExternalStorageState.UNMOUNT:
self.unmount_storage()
elif st == ExternalStorageState.FORMAT:
dialog = ConfirmDialog(
tr("Are you sure you want to format this drive? This will erase all data."),
confirm_text=tr("Format"),
cancel_text=tr("Cancel"),
)
gui_app.set_modal_overlay(dialog, callback=self._confirm_format)
def _confirm_format(self, result: DialogResult):
if result == DialogResult.CONFIRM:
self.format_storage()
def mount_storage(self):
self._value_text = tr("mounting")
self._state = ExternalStorageState.LOADING
def _work():
cmd = """
sudo mount -o remount,rw / &&
sudo mkdir -p /mnt/external_realdata &&
(grep -q '/dev/sdg1 /mnt/external_realdata' /etc/fstab ||
echo '/dev/sdg1 /mnt/external_realdata ext4 defaults,nofail 0 2' >> /etc/fstab) &&
sudo systemctl daemon-reexec &&
sudo mount /mnt/external_realdata &&
sudo chown -R comma:comma /mnt/external_realdata &&
sudo chmod -R 775 /mnt/external_realdata &&
sudo mount -o remount,ro /
"""
subprocess.call(["sh", "-c", cmd])
self.debounced_refresh()
threading.Thread(target=_work, daemon=True).start()
def unmount_storage(self):
self._value_text = tr("unmounting")
self._state = ExternalStorageState.LOADING
def _work():
subprocess.call(["sh", "-c", "sudo umount /mnt/external_realdata"])
self.debounced_refresh()
threading.Thread(target=_work, daemon=True).start()
def format_storage(self):
self._formatting = True
self._value_text = tr("formatting")
self._state = ExternalStorageState.LOADING
def _work():
cmd = """
sudo wipefs -a /dev/sdg &&
sudo parted -s /dev/sdg mklabel gpt mkpart primary ext4 0% 100% &&
sudo mkfs.ext4 -F -L openpilot /dev/sdg1
"""
exitcode = subprocess.call(["sh", "-c", cmd])
def apply():
self._formatting = False
if exitcode == 0:
self.mount_storage()
else:
self._value_text = tr("needs format")
self._state = ExternalStorageState.FORMAT
apply()
threading.Thread(target=_work, daemon=True).start()
def external_storage_item(title: str | Callable[[], str], description: str | Callable[[], str]) -> ListItem:
return ListItem(
title=title,
description=description,
action_item=ExternalStorageAction()
)
@@ -238,7 +238,7 @@ class ModelsLayout(Widget):
self.lagd_toggle.action_item.set_state(live_delay) self.lagd_toggle.action_item.set_state(live_delay)
self.delay_control.set_visible(not live_delay and advanced_controls) self.delay_control.set_visible(not live_delay and advanced_controls)
new_step = int(round(100 / CV.MPH_TO_KPH)) if ui_state.is_metric else 100 new_step = int(round(100 / CV.MPH_TO_KPH)) if ui_state.is_metric else 100
if self.lane_turn_value_control.action_item is not None and self.lane_turn_value_control.action_item.value_change_step != new_step: if self.lane_turn_value_control.action_item.value_change_step != new_step:
self.lane_turn_value_control.action_item.value_change_step = new_step self.lane_turn_value_control.action_item.value_change_step = new_step
self._update_lagd_description(live_delay) self._update_lagd_description(live_delay)
@@ -38,8 +38,8 @@ class NetworkUISP(NetworkUI):
self.scan_button.set_text(tr("Scan")) self.scan_button.set_text(tr("Scan"))
self.scan_button.set_enabled(True) self.scan_button.set_enabled(True)
def _render(self, _): def _render(self, rect: rl.Rectangle):
super()._render(_) super()._render(rect)
if self._current_panel == PanelType.WIFI: if self._current_panel == PanelType.WIFI:
self.scan_button.set_position(self._rect.x, self._rect.y + 20) self.scan_button.set_position(self._rect.x, self._rect.y + 20)
@@ -37,7 +37,7 @@ from openpilot.system.ui.widgets.scroller_tici import Scroller
OP.PANEL_COLOR = rl.Color(10, 10, 10, 255) OP.PANEL_COLOR = rl.Color(10, 10, 10, 255)
ICON_SIZE = 70 ICON_SIZE = 70
OP.PanelType = IntEnum( # type: ignore[assignment] # ty: ignore[invalid-assignment] OP.PanelType = IntEnum(
"PanelType", "PanelType",
[es.name for es in OP.PanelType] + [ [es.name for es in OP.PanelType] + [
"SUNNYLINK", "SUNNYLINK",
@@ -180,18 +180,20 @@ class SettingsLayoutSP(OP.SettingsLayout):
self._sidebar_scroller.render(nav_rect) self._sidebar_scroller.render(nav_rect)
return return
def _handle_mouse_release(self, mouse_pos: MousePos) -> None: def _handle_mouse_release(self, mouse_pos: MousePos) -> bool:
# Check close button # Check close button
if rl.check_collision_point_rec(mouse_pos, self._close_btn_rect): if rl.check_collision_point_rec(mouse_pos, self._close_btn_rect):
if self._close_callback: if self._close_callback:
self._close_callback() self._close_callback()
return return True
# Check navigation buttons # Check navigation buttons
for panel_type, panel_info in self._panels.items(): for panel_type, panel_info in self._panels.items():
if rl.check_collision_point_rec(mouse_pos, panel_info.button_rect) and self._sidebar_scroller.scroll_panel.is_touch_valid(): if rl.check_collision_point_rec(mouse_pos, panel_info.button_rect) and self._sidebar_scroller.scroll_panel.is_touch_valid():
self.set_current_panel(panel_type) self.set_current_panel(panel_type)
return return True
return False
def show_event(self): def show_event(self):
super().show_event() super().show_event()
@@ -138,7 +138,7 @@ class ModelsLayoutMici(NavScroller):
self._show_selection_view(btns, self._show_folders) self._show_selection_view(btns, self._show_folders)
def _reset_main_view(self): def _reset_main_view(self):
self._scroller._items = self.main_items # type: ignore[assignment] # ty: ignore[invalid-assignment] self._scroller._items = self.main_items
self.set_back_callback(self.original_back_callback) self.set_back_callback(self.original_back_callback)
self._scroller.scroll_panel.set_offset(0) self._scroller.scroll_panel.set_offset(0)
self._scroller.scroll_to(0) self._scroller.scroll_to(0)
@@ -16,9 +16,6 @@ class SunnylinkConsentPage(NavScroller):
def __init__(self, on_accept: Callable | None = None, on_decline: Callable | None = None): def __init__(self, on_accept: Callable | None = None, on_decline: Callable | None = None):
super().__init__() super().__init__()
assert on_accept is not None and callable(on_accept)
assert on_decline is not None and callable(on_decline)
self._accept_button = BigConfirmationCircleButton("enable\nsunnylink", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64), self._accept_button = BigConfirmationCircleButton("enable\nsunnylink", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64),
on_accept, exit_on_confirm=False) on_accept, exit_on_confirm=False)
@@ -198,7 +198,7 @@ class SpeedLimitRenderer(Widget, SpeedLimitAlertRenderer):
self._draw_ahead_info(sign_rect) self._draw_ahead_info(sign_rect)
def _draw_sign_main(self, rect, alpha=1.0): def _draw_sign_main(self, rect, alpha=1.0):
speed_limit_warning_enabled = ui_state.speed_limit_mode is not None and ui_state.speed_limit_mode >= SpeedLimitMode.warning speed_limit_warning_enabled = ui_state.speed_limit_mode >= SpeedLimitMode.warning
has_limit = self.speed_limit_valid or self.speed_limit_last_valid has_limit = self.speed_limit_valid or self.speed_limit_last_valid
is_overspeed = has_limit and round(self.speed_limit_final_last) < round(self.speed) is_overspeed = has_limit and round(self.speed_limit_final_last) < round(self.speed)
@@ -224,7 +224,8 @@ class UIStateSP:
class DeviceSP: class DeviceSP:
def _set_awake(self, on: bool, _ui_state=None): @staticmethod
def _set_awake(on: bool, _ui_state):
if _ui_state.boot_offroad_mode == 1 and not on: if _ui_state.boot_offroad_mode == 1 and not on:
_ui_state.params.put_bool("OffroadMode", True) _ui_state.params.put_bool("OffroadMode", True)
@@ -357,7 +357,7 @@ def build_mici_script(pm: PubMaster, main_layout, script: Script) -> None:
params = Params() params = Params()
main_layout._alerts_layout._pending_params = ({"UpdaterNewDescription": params.get("UpdaterNewDescription")} | main_layout._alerts_layout._pending_params = ({"UpdaterNewDescription": params.get("UpdaterNewDescription")} |
{alert_data.key: params.get(alert_data.key) for alert_data in main_layout._alerts_layout.sorted_alerts}) {alert_data.key: params.get(alert_data.key) for alert_data in main_layout._alerts_layout.sorted_alerts})
main_layout._alerts_layout._update_state() main_layout._alerts_layout._refresh()
swipe_right(width, wait_after=WAIT_SHORT) # open alerts swipe_right(width, wait_after=WAIT_SHORT) # open alerts
script.setup(setup_offroad_alerts_and_refresh) # show alerts script.setup(setup_offroad_alerts_and_refresh) # show alerts
@@ -49,7 +49,7 @@ def patch_submaster(message_chunks):
sm.recv_frame[service] = sm.frame sm.recv_frame[service] = sm.frame
sm.valid[service] = True sm.valid[service] = True
sm.frame += 1 sm.frame += 1
ui_state.sm.update = mock_update # ty: ignore[invalid-assignment] # profiling hook ui_state.sm.update = mock_update
if __name__ == "__main__": if __name__ == "__main__":
@@ -92,7 +92,7 @@ if __name__ == "__main__":
vipc.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 5, W, H) vipc.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 5, W, H)
vipc.start_listener() vipc.start_listener()
yuv_buffer_size = W * H + (W // 2) * (H // 2) * 2 yuv_buffer_size = W * H + (W // 2) * (H // 2) * 2
yuv_data = np.random.default_rng().integers(0, 256, yuv_buffer_size, dtype=np.uint8).tobytes() yuv_data = np.random.randint(0, 256, yuv_buffer_size, dtype=np.uint8).tobytes()
with cProfile.Profile() as pr: with cProfile.Profile() as pr:
for _ in gui_app.render(): for _ in gui_app.render():
if ui_state.sm.frame >= len(message_chunks): if ui_state.sm.frame >= len(message_chunks):
@@ -2,8 +2,7 @@ import time
from openpilot.selfdrive.test.helpers import with_processes from openpilot.selfdrive.test.helpers import with_processes
class TestRaylibUi: @with_processes(["ui"])
@with_processes(["ui"]) def test_raylib_ui():
def test_raylib_ui(self): """Test initialization of the UI widgets is successful."""
"""Test initialization of the UI widgets is successful.""" time.sleep(1)
time.sleep(1)
@@ -46,59 +46,61 @@ def load_po_text(po_path: Path) -> str:
return po_path.read_text(encoding='utf-8') return po_path.read_text(encoding='utf-8')
class TestTranslations: @pytest.mark.parametrize("language_code", sorted(TRANSLATION_LANGUAGES.values()))
@pytest.mark.parametrize("language_code", sorted(TRANSLATION_LANGUAGES.values())) def test_translation_file_exists(language_code: str):
def test_translation_file_exists(self, language_code: str): po_path = PO_DIR / f"app_{language_code}.po"
po_path = PO_DIR / f"app_{language_code}.po" assert po_path.exists(), f"missing translation file: {po_path}"
assert po_path.exists(), f"missing translation file: {po_path}"
@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name)
def test_translation_placeholders_are_preserved(self, po_path: Path):
_, entries = parse_po(po_path)
language = po_path.stem.removeprefix("app_")
for entry in entries: @pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name)
source_placeholders = extract_placeholders(entry.msgid) def test_translation_placeholders_are_preserved(po_path: Path):
_, entries = parse_po(po_path)
language = po_path.stem.removeprefix("app_")
if entry.is_plural: for entry in entries:
plural_placeholders = extract_placeholders(entry.msgid_plural) source_placeholders = extract_placeholders(entry.msgid)
message = (
f"{language}: source plural placeholders do not match singular for "
+ f"{entry.msgid!r}: {source_placeholders} vs {plural_placeholders}"
)
assert plural_placeholders == source_placeholders, message
for idx, msgstr in sorted(entry.msgstr_plural.items()): if entry.is_plural:
if not msgstr: plural_placeholders = extract_placeholders(entry.msgid_plural)
continue message = (
f"{language}: source plural placeholders do not match singular for "
+ f"{entry.msgid!r}: {source_placeholders} vs {plural_placeholders}"
)
assert plural_placeholders == source_placeholders, message
translated_placeholders = extract_placeholders(msgstr) for idx, msgstr in sorted(entry.msgstr_plural.items()):
message = ( if not msgstr:
f"{language}: plural form {idx} changes placeholders for {entry.msgid!r}: "
+ f"expected {source_placeholders}, got {translated_placeholders}"
)
assert translated_placeholders == source_placeholders, message
else:
if not entry.msgstr:
continue continue
translated_placeholders = extract_placeholders(entry.msgstr) translated_placeholders = extract_placeholders(msgstr)
message = ( message = (
f"{language}: translation changes placeholders for {entry.msgid!r}: " f"{language}: plural form {idx} changes placeholders for {entry.msgid!r}: "
+ f"expected {source_placeholders}, got {translated_placeholders}" + f"expected {source_placeholders}, got {translated_placeholders}"
) )
assert translated_placeholders == source_placeholders, message assert translated_placeholders == source_placeholders, message
else:
if not entry.msgstr:
continue
@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name) translated_placeholders = extract_placeholders(entry.msgstr)
def test_translation_refs_do_not_include_line_numbers(self, po_path: Path): message = (
for line in load_po_text(po_path).splitlines(): f"{language}: translation changes placeholders for {entry.msgid!r}: "
assert not LINE_NUMBER_REF_RE.match(line), ( + f"expected {source_placeholders}, got {translated_placeholders}"
f"{po_path.name}: line-number source reference found: {line}"
) )
assert translated_placeholders == source_placeholders, message
@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name)
def test_translation_entities_are_valid(self, po_path: Path): @pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name)
matches = BAD_ENTITY_RE.findall(load_po_text(po_path)) def test_translation_refs_do_not_include_line_numbers(po_path: Path):
assert not matches, ( for line in load_po_text(po_path).splitlines():
f"{po_path.name}: found '@...;' entity typo(s): {', '.join(sorted(set(matches)))}" assert not LINE_NUMBER_REF_RE.match(line), (
f"{po_path.name}: line-number source reference found: {line}"
) )
@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name)
def test_translation_entities_are_valid(po_path: Path):
matches = BAD_ENTITY_RE.findall(load_po_text(po_path))
assert not matches, (
f"{po_path.name}: found '@...;' entity typo(s): {', '.join(sorted(set(matches)))}"
)
+10 -12
View File
@@ -67,22 +67,22 @@ def parse_po(path: str | Path) -> tuple[POEntry | None, list[POEntry]]:
cur_field: str | None = None cur_field: str | None = None
plural_idx = 0 plural_idx = 0
def finish(entry: POEntry | None): def finish():
nonlocal header nonlocal cur, header
if entry is None: if cur is None:
return return
if entry.msgid == "" and entry.msgstr: if cur.msgid == "" and cur.msgstr:
header = entry header = cur
elif entry.msgid != "" or entry.is_plural: elif cur.msgid != "" or cur.is_plural:
entries.append(entry) entries.append(cur)
cur = None
for raw in lines: for raw in lines:
line = raw.rstrip('\n') line = raw.rstrip('\n')
stripped = line.strip() stripped = line.strip()
if not stripped: if not stripped:
finish(cur) finish()
cur = None
cur_field = None cur_field = None
continue continue
@@ -123,8 +123,6 @@ def parse_po(path: str | Path) -> tuple[POEntry | None, list[POEntry]]:
continue continue
if stripped.startswith('msgstr '): if stripped.startswith('msgstr '):
if cur is None:
cur = POEntry()
cur.msgstr = _parse_quoted(stripped[len('msgstr '):]) cur.msgstr = _parse_quoted(stripped[len('msgstr '):])
cur_field = 'msgstr' cur_field = 'msgstr'
continue continue
@@ -140,7 +138,7 @@ def parse_po(path: str | Path) -> tuple[POEntry | None, list[POEntry]]:
elif cur_field == 'msgstr_plural': elif cur_field == 'msgstr_plural':
cur.msgstr_plural[plural_idx] += val cur.msgstr_plural[plural_idx] += val
finish(cur) finish()
return header, entries return header, entries
@@ -12,9 +12,9 @@ POT_FILE = os.path.join(str(TRANSLATIONS_DIR), "app.pot")
def update_translations(): def update_translations():
files = [] files = []
for root, _, filenames in chain(os.walk(SYSTEM_UI_DIR), for root, _, filenames in chain(os.walk(SYSTEM_UI_DIR),
os.walk(os.path.join(str(UI_DIR), "widgets")), os.walk(os.path.join(UI_DIR, "widgets")),
os.walk(os.path.join(str(UI_DIR), "layouts")), os.walk(os.path.join(UI_DIR, "layouts")),
os.walk(os.path.join(str(UI_DIR), "onroad"))): os.walk(os.path.join(UI_DIR, "onroad"))):
for filename in filenames: for filename in filenames:
if filename.endswith(".py"): if filename.endswith(".py"):
files.append(os.path.relpath(os.path.join(root, filename), BASEDIR)) files.append(os.path.relpath(os.path.join(root, filename), BASEDIR))
@@ -25,7 +25,7 @@ def update_translations():
# Generate/update translation files for each language # Generate/update translation files for each language
for name in multilang.languages.values(): for name in multilang.languages.values():
po_file = os.path.join(str(TRANSLATIONS_DIR), f"app_{name}.po") po_file = os.path.join(TRANSLATIONS_DIR, f"app_{name}.po")
if os.path.exists(po_file): if os.path.exists(po_file):
merge_po(po_file, POT_FILE) merge_po(po_file, POT_FILE)
else: else:
+4 -4
View File
@@ -314,9 +314,9 @@ class Device(DeviceSP):
brightness = 0 brightness = 0
if brightness != self._last_brightness: if brightness != self._last_brightness:
self._brightness_target = int(brightness) self._brightness_target = brightness
self._brightness_event.set() self._brightness_event.set()
self._last_brightness = int(brightness) self._last_brightness = brightness
def _update_wakefulness(self): def _update_wakefulness(self):
# Handle interactive timeout # Handle interactive timeout
@@ -337,9 +337,9 @@ class Device(DeviceSP):
self._set_awake(ui_state.ignition or not interaction_timeout or PC) self._set_awake(ui_state.ignition or not interaction_timeout or PC)
def _set_awake(self, on: bool, _ui_state=None): def _set_awake(self, on: bool):
if on != self._awake: if on != self._awake:
super()._set_awake(on, _ui_state or ui_state) DeviceSP._set_awake(on, ui_state)
self._awake = on self._awake = on
cloudlog.debug(f"setting display power {int(on)}") cloudlog.debug(f"setting display power {int(on)}")
HARDWARE.set_display_power(on) HARDWARE.set_display_power(on)
@@ -29,7 +29,7 @@ def make_event(event_types):
event = {} event = {}
for ev in event_types: for ev in event_types:
event[ev] = NormalPermanentAlert("alert") event[ev] = NormalPermanentAlert("alert")
EVENTS_SP[0] = event # type: ignore[assignment] # ty: ignore[invalid-assignment] EVENTS_SP[0] = event
return 0 return 0
+1 -1
View File
@@ -128,7 +128,7 @@ def main_thread():
cloudlog.exception(f"mapd: failed to make {Paths.mapd_root()}") cloudlog.exception(f"mapd: failed to make {Paths.mapd_root()}")
while True: while True:
show_alert = bool(get_files_for_cleanup() and params.get_bool("OsmLocal")) show_alert = get_files_for_cleanup() and params.get_bool("OsmLocal")
set_offroad_alert("Offroad_OSMUpdateRequired", show_alert, "This alert will be cleared when new maps are downloaded.") set_offroad_alert("Offroad_OSMUpdateRequired", show_alert, "This alert will be cleared when new maps are downloaded.")
update_osm_db() update_osm_db()
@@ -129,7 +129,7 @@ def compile_split_policy(nv12: NV12Frame, model_w, model_h, prepare_only, frame_
def random_inputs_run_fn(fn, seed, test_val=None, test_buffers=None, expect_match=True): def random_inputs_run_fn(fn, seed, test_val=None, test_buffers=None, expect_match=True):
input_queues, npy = make_split_input_queues(vision_input_shapes, policy_input_shapes, frame_skip, Device.DEFAULT) input_queues, npy = make_split_input_queues(vision_input_shapes, policy_input_shapes, frame_skip, Device.DEFAULT)
rng = np.random.default_rng(seed) np.random.seed(seed)
Tensor.manual_seed(seed) Tensor.manual_seed(seed)
testing = test_val is not None or test_buffers is not None testing = test_val is not None or test_buffers is not None
@@ -139,7 +139,7 @@ def compile_split_policy(nv12: NV12Frame, model_w, model_h, prepare_only, frame_
frame = Tensor.randint(nv12.size, low=0, high=256, dtype='uint8').realize() frame = Tensor.randint(nv12.size, low=0, high=256, dtype='uint8').realize()
big_frame = Tensor.randint(nv12.size, low=0, high=256, dtype='uint8').realize() big_frame = Tensor.randint(nv12.size, low=0, high=256, dtype='uint8').realize()
for v in npy.values(): for v in npy.values():
v[:] = rng.standard_normal(v.shape).astype(v.dtype) v[:] = np.random.randn(*v.shape).astype(v.dtype)
Device.default.synchronize() Device.default.synchronize()
st = time.perf_counter() st = time.perf_counter()
outs = fn(**input_queues, frame=frame, big_frame=big_frame) outs = fn(**input_queues, frame=frame, big_frame=big_frame)
@@ -319,11 +319,11 @@ def make_run_vision_multi_policy(vision_runner, policy_runners, nv12: NV12Frame,
def _warmup_and_serialize(run_jit, input_queues, npy, nv12): def _warmup_and_serialize(run_jit, input_queues, npy, nv12):
for i in range(3): for i in range(3):
rng = np.random.default_rng(42 + i) np.random.seed(42 + i)
frame = Tensor.randint(nv12.size, low=0, high=256, dtype='uint8').realize() frame = Tensor.randint(nv12.size, low=0, high=256, dtype='uint8').realize()
big_frame = Tensor.randint(nv12.size, low=0, high=256, dtype='uint8').realize() big_frame = Tensor.randint(nv12.size, low=0, high=256, dtype='uint8').realize()
for v in npy.values(): for v in npy.values():
v[:] = rng.standard_normal(v.shape).astype(v.dtype) v[:] = np.random.randn(*v.shape).astype(v.dtype)
Device.default.synchronize() Device.default.synchronize()
st = time.perf_counter() st = time.perf_counter()
run_jit(**input_queues, frame=frame, big_frame=big_frame) run_jit(**input_queues, frame=frame, big_frame=big_frame)
@@ -61,7 +61,6 @@ class Parser:
weights[fidx] = weights[fidx][idxs] weights[fidx] = weights[fidx][idxs]
pred_mu[fidx] = pred_mu[fidx][idxs] pred_mu[fidx] = pred_mu[fidx][idxs]
pred_std[fidx] = pred_std[fidx][idxs] pred_std[fidx] = pred_std[fidx][idxs]
assert out_shape is not None
full_shape = tuple([raw.shape[0], in_N] + list(out_shape)) full_shape = tuple([raw.shape[0], in_N] + list(out_shape))
outs[name + '_weights'] = weights outs[name + '_weights'] = weights
outs[name + '_hypotheses'] = pred_mu.reshape(full_shape) outs[name + '_hypotheses'] = pred_mu.reshape(full_shape)
@@ -79,10 +78,8 @@ class Parser:
pred_std_final = pred_std pred_std_final = pred_std
if out_N > 1: if out_N > 1:
assert out_shape is not None
final_shape = tuple([raw.shape[0], out_N] + list(out_shape)) final_shape = tuple([raw.shape[0], out_N] + list(out_shape))
else: else:
assert out_shape is not None
final_shape = tuple([raw.shape[0],] + list(out_shape)) final_shape = tuple([raw.shape[0],] + list(out_shape))
outs[name] = pred_mu_final.reshape(final_shape) outs[name] = pred_mu_final.reshape(final_shape)
outs[name + '_stds'] = pred_std_final.reshape(final_shape) outs[name + '_stds'] = pred_std_final.reshape(final_shape)
@@ -65,7 +65,6 @@ class Parser:
weights[fidx] = weights[fidx][idxs] weights[fidx] = weights[fidx][idxs]
pred_mu[fidx] = pred_mu[fidx][idxs] pred_mu[fidx] = pred_mu[fidx][idxs]
pred_std[fidx] = pred_std[fidx][idxs] pred_std[fidx] = pred_std[fidx][idxs]
assert out_shape is not None
full_shape = tuple([raw.shape[0], in_N] + list(out_shape)) full_shape = tuple([raw.shape[0], in_N] + list(out_shape))
outs[name + '_weights'] = weights outs[name + '_weights'] = weights
outs[name + '_hypotheses'] = pred_mu.reshape(full_shape) outs[name + '_hypotheses'] = pred_mu.reshape(full_shape)
@@ -83,10 +82,8 @@ class Parser:
pred_std_final = pred_std pred_std_final = pred_std
if out_N > 1: if out_N > 1:
assert out_shape is not None
final_shape = tuple([raw.shape[0], out_N] + list(out_shape)) final_shape = tuple([raw.shape[0], out_N] + list(out_shape))
else: else:
assert out_shape is not None
final_shape = tuple([raw.shape[0],] + list(out_shape)) final_shape = tuple([raw.shape[0],] + list(out_shape))
outs[name] = pred_mu_final.reshape(final_shape) outs[name] = pred_mu_final.reshape(final_shape)
outs[name + '_stds'] = pred_std_final.reshape(final_shape) outs[name + '_stds'] = pred_std_final.reshape(final_shape)
@@ -61,7 +61,7 @@ class TestFrameSkipBufferLengthEquivalence:
class TestTemporalSamplingEquivalence: class TestTemporalSamplingEquivalence:
def test_non20hz_desire_sampling_identity(self): def test_non20hz_desire_sampling_identity(self):
buf = np.random.default_rng(0).standard_normal((100, 1, 8)).astype(np.float32) buf = np.random.randn(100, 1, 8).astype(np.float32)
frame_skip = 1 frame_skip = 1
sampled = buf[::frame_skip].reshape(-1, 8) sampled = buf[::frame_skip].reshape(-1, 8)
assert sampled.shape == (100, 8) assert sampled.shape == (100, 8)
@@ -150,7 +150,7 @@ class TestOutputSlicePreservation:
def test_vision_hidden_state_slice_used_for_features(self): def test_vision_hidden_state_slice_used_for_features(self):
mock_slices = {'hidden_state': slice(0, 512), 'plan': slice(512, 1024)} mock_slices = {'hidden_state': slice(0, 512), 'plan': slice(512, 1024)}
features_slice = mock_slices['hidden_state'] features_slice = mock_slices['hidden_state']
fake_output = np.random.default_rng(0).standard_normal((1, 1024)).astype(np.float32) fake_output = np.random.randn(1, 1024).astype(np.float32)
features = fake_output[:, features_slice] features = fake_output[:, features_slice]
assert features.shape == (1, 512) assert features.shape == (1, 512)
@@ -1,5 +1,3 @@
from typing import Any
import numpy as np import numpy as np
from openpilot.cereal import log from openpilot.cereal import log
@@ -16,7 +14,7 @@ class MockStruct:
def test_recovery_power_scaling(): def test_recovery_power_scaling():
state: Any = MockStruct( state = MockStruct(
PLANPLUS_CONTROL=0.75, PLANPLUS_CONTROL=0.75,
LONG_SMOOTH_SECONDS=0.3, LONG_SMOOTH_SECONDS=0.3,
LAT_SMOOTH_SECONDS=0.1, LAT_SMOOTH_SECONDS=0.1,
@@ -37,10 +35,10 @@ def test_recovery_power_scaling():
recorded_curv_plans.append(plan.copy()) recorded_curv_plans.append(plan.copy())
return 0.0 return 0.0
modeld.get_accel_from_plan = mock_accel # ty: ignore[invalid-assignment] modeld.get_accel_from_plan = mock_accel
modeld.get_curvature_from_output = mock_curvature # ty: ignore[invalid-assignment] modeld.get_curvature_from_output = mock_curvature
plan = np.random.default_rng(0).random((1, 100, 15)).astype(np.float32) plan = np.random.rand(1, 100, 15).astype(np.float32)
planplus = np.random.default_rng(1).random((1, 100, 15)).astype(np.float32) planplus = np.random.rand(1, 100, 15).astype(np.float32)
merged_plan = plan + planplus merged_plan = plan + planplus
model_output: dict = { model_output: dict = {
@@ -62,7 +60,7 @@ def test_recovery_power_scaling():
state.PLANPLUS_CONTROL = control state.PLANPLUS_CONTROL = control
recorded_vel.clear() recorded_vel.clear()
recorded_curv_plans.clear() recorded_curv_plans.clear()
ModelState.get_action_from_model(state, model_output, prev_action, 0.0, 0.0, v_ego) # type: ignore[arg-type] ModelState.get_action_from_model(state, model_output, prev_action, 0.0, 0.0, v_ego)
expected_accel_plan_vel = plan[0, :, Plan.VELOCITY][:, 0] + planplus[0, :, Plan.VELOCITY][:, 0] expected_accel_plan_vel = plan[0, :, Plan.VELOCITY][:, 0] + planplus[0, :, Plan.VELOCITY][:, 0]
np.testing.assert_allclose(recorded_vel[0], expected_accel_plan_vel, rtol=1e-5, atol=1e-6) np.testing.assert_allclose(recorded_vel[0], expected_accel_plan_vel, rtol=1e-5, atol=1e-6)
+4 -4
View File
@@ -66,8 +66,8 @@ def compile_v2_warp(cam_w, cam_h, buffer_length):
full_buffer = Tensor.zeros(img_buffer_shape, dtype='uint8').contiguous().realize() full_buffer = Tensor.zeros(img_buffer_shape, dtype='uint8').contiguous().realize()
big_full_buffer = Tensor.zeros(img_buffer_shape, dtype='uint8').contiguous().realize() big_full_buffer = Tensor.zeros(img_buffer_shape, dtype='uint8').contiguous().realize()
new_frame_np = np.random.default_rng(0).integers(0, 256, yuv_size, dtype=np.uint8) new_frame_np = np.random.randint(0, 256, yuv_size, dtype=np.uint8)
new_big_frame_np = np.random.default_rng(1).integers(0, 256, yuv_size, dtype=np.uint8) new_big_frame_np = np.random.randint(0, 256, yuv_size, dtype=np.uint8)
for i in range(10): for i in range(10):
img_inputs = [full_buffer, img_inputs = [full_buffer,
Tensor.from_blob(new_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), Tensor.from_blob(new_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(),
@@ -91,8 +91,8 @@ def compile_v2_warp(cam_w, cam_h, buffer_length):
print(f" Saved to {pkl_path}") print(f" Saved to {pkl_path}")
jit = pickle.load(open(pkl_path, "rb")) jit = pickle.load(open(pkl_path, "rb"))
verify_frame = np.random.default_rng(0).integers(0, 256, yuv_size, dtype=np.uint8) verify_frame = np.random.randint(0, 256, yuv_size, dtype=np.uint8)
verify_big_frame = np.random.default_rng(1).integers(0, 256, yuv_size, dtype=np.uint8) verify_big_frame = np.random.randint(0, 256, yuv_size, dtype=np.uint8)
fresh_inputs = [ fresh_inputs = [
Tensor.zeros(img_buffer_shape, dtype='uint8').contiguous().realize(), Tensor.zeros(img_buffer_shape, dtype='uint8').contiguous().realize(),
Tensor.from_blob(verify_frame.ctypes.data, (yuv_size,), dtype='uint8').realize(), Tensor.from_blob(verify_frame.ctypes.data, (yuv_size,), dtype='uint8').realize(),
+1 -1
View File
@@ -126,7 +126,7 @@ def get_active_bundle(params: Params | None = None, raw_bundle_dict: dict | byte
params = params or Params() params = params or Params()
try: try:
active_bundle_dict = raw_bundle_dict if raw_bundle_dict is not None else (params.get("ModelManager_ActiveBundle") or {}) active_bundle_dict = raw_bundle_dict if raw_bundle_dict is not None else (params.get("ModelManager_ActiveBundle") or {})
if isinstance(active_bundle_dict, dict) and active_bundle_dict and is_bundle_version_compatible(active_bundle_dict): if active_bundle_dict and is_bundle_version_compatible(active_bundle_dict):
return custom.ModelManagerSP.ModelBundle(**active_bundle_dict) return custom.ModelManagerSP.ModelBundle(**active_bundle_dict)
except Exception: except Exception:
pass pass

Some files were not shown because too many files have changed in this diff Show More