mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-07-23 12:12:06 +08:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f39d46319 | |||
| fdd1df79fb | |||
| 031b1ad0a3 | |||
| b1f2e638de | |||
| c20263d985 | |||
| 5472e69e35 | |||
| 78909dac73 | |||
| 24a9b6dae5 | |||
| d9596fa998 | |||
| e124d6df9b | |||
| 3dbf02f803 | |||
| fef29ad225 | |||
| 157c7080ce | |||
| caa9e770cc | |||
| 24893ebadb | |||
| 39e12c8bb1 | |||
| f0d93eb32d | |||
| 19ecc37de8 | |||
| ecac2d386b | |||
| e475d10adc | |||
| 7d74c3c99b | |||
| cefcf10ec3 | |||
| 1c07e00759 | |||
| f0841b827a | |||
| 3f49e2d33c | |||
| 3f93b00120 | |||
| c21b0821da | |||
| b9f25f8a43 | |||
| c8786d930d | |||
| 3a55f31dc5 | |||
| a04c045cd7 | |||
| 9aa5c3d2c6 | |||
| 5d23a78c77 | |||
| 06a73f538e | |||
| 60716edc37 | |||
| 61608db786 |
@@ -93,6 +93,14 @@ 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,
|
||||||
|
|||||||
@@ -166,6 +166,11 @@ 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
@@ -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.4"
|
export AGNOS_VERSION="18.5"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export STAGING_ROOT="/data/safe_staging"
|
export STAGING_ROOT="/data/safe_staging"
|
||||||
|
|||||||
@@ -812,6 +812,7 @@ struct SelfdriveState {
|
|||||||
promptDistracted @8;
|
promptDistracted @8;
|
||||||
|
|
||||||
preAlert @9;
|
preAlert @9;
|
||||||
|
complete @10;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum OpenpilotState @0xdbe58b96d2d1ac61 {
|
enum OpenpilotState @0xdbe58b96d2d1ac61 {
|
||||||
@@ -2160,7 +2161,8 @@ struct DriverMonitoringStateDEPRECATED @0xb83cda094a1da284 {
|
|||||||
|
|
||||||
struct DriverMonitoringState {
|
struct DriverMonitoringState {
|
||||||
lockout @0 :Bool;
|
lockout @0 :Bool;
|
||||||
lockoutRecoveryPercent @11 :Int8;
|
lockoutCount @15 :Int8;
|
||||||
|
lockoutMinutesRemaining @11 :Int8;
|
||||||
alert3Count @12 :Int8;
|
alert3Count @12 :Int8;
|
||||||
noResponseCount @13 :Int8;
|
noResponseCount @13 :Int8;
|
||||||
noResponseForceDecel @14 :Bool;
|
noResponseForceDecel @14 :Bool;
|
||||||
|
|||||||
@@ -7,12 +7,43 @@ import os
|
|||||||
import capnp
|
import capnp
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from typing import Optional, List, Union, Dict
|
from typing import Union
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
@@ -22,8 +53,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: Optional[Poller] = None, addr: str = "127.0.0.1",
|
def sub_sock(endpoint: str, poller: Poller | None = None, addr: str = "127.0.0.1",
|
||||||
conflate: bool = False, timeout: Optional[int] = None) -> SubSocket:
|
conflate: bool = False, timeout: int | None = 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,
|
||||||
@@ -39,7 +70,7 @@ def log_from_bytes(dat: bytes, struct: capnp.lib.capnp._StructModule = log.Event
|
|||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
def new_message(service: Optional[str], size: Optional[int] = None, **kwargs) -> capnp.lib.capnp._DynamicStructBuilder:
|
def new_message(service: str | None, size: int | None = None, **kwargs) -> capnp.lib.capnp._DynamicStructBuilder:
|
||||||
args = {
|
args = {
|
||||||
'valid': False,
|
'valid': False,
|
||||||
'logMonoTime': int(time.monotonic() * 1e9),
|
'logMonoTime': int(time.monotonic() * 1e9),
|
||||||
@@ -54,14 +85,14 @@ def new_message(service: Optional[str], size: Optional[int] = None, **kwargs) ->
|
|||||||
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) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
def recv_sock(sock: SubSocket, wait: bool = False) -> capnp.lib.capnp._DynamicStructReader | None:
|
||||||
"""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
|
||||||
|
|
||||||
@@ -82,14 +113,14 @@ def recv_sock(sock: SubSocket, wait: bool = False) -> Optional[capnp.lib.capnp._
|
|||||||
return dat
|
return dat
|
||||||
|
|
||||||
|
|
||||||
def recv_one(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
def recv_one(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader | None:
|
||||||
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) -> Optional[capnp.lib.capnp._DynamicStructReader]:
|
def recv_one_or_none(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader | None:
|
||||||
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)
|
||||||
@@ -148,27 +179,27 @@ class FrequencyTracker:
|
|||||||
|
|
||||||
|
|
||||||
class SubMaster:
|
class SubMaster:
|
||||||
def __init__(self, services: List[str], poll: Optional[str] = None,
|
def __init__(self, services: list[str], poll: str | None = None,
|
||||||
ignore_alive: Optional[List[str]] = None, ignore_avg_freq: Optional[List[str]] = None,
|
ignore_alive: list[str] | None = None, ignore_avg_freq: list[str] | None = None,
|
||||||
ignore_valid: Optional[List[str]] = None, addr: str = "127.0.0.1", frequency: Optional[float] = None):
|
ignore_valid: list[str] | None = None, addr: str = "127.0.0.1", frequency: float | None = None):
|
||||||
self.frame = -1
|
self.frame = -1
|
||||||
self.services = services
|
self.services = services
|
||||||
self.seen = {s: False for s in services}
|
self.seen = dict.fromkeys(services, False)
|
||||||
self.updated = {s: False for s in services}
|
self.updated = dict.fromkeys(services, False)
|
||||||
self.recv_time = {s: 0. for s in services}
|
self.recv_time = dict.fromkeys(services, 0.0)
|
||||||
self.recv_frame = {s: 0 for s in services}
|
self.recv_frame = dict.fromkeys(services, 0)
|
||||||
self.sock = {}
|
self.sock = {}
|
||||||
self.data = {}
|
self.data = {}
|
||||||
self.logMonoTime = {s: 0 for s in services}
|
self.logMonoTime = dict.fromkeys(services, 0)
|
||||||
|
|
||||||
# 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 = set(s for s in services if not on_demand[s])
|
self.static_freq_services = {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
|
||||||
@@ -211,7 +242,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:
|
||||||
@@ -234,21 +265,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: Optional[List[str]] = None) -> bool:
|
def all_alive(self, service_list: list[str] | None = 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: Optional[List[str]] = None) -> bool:
|
def all_freq_ok(self, service_list: list[str] | None = 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: Optional[List[str]] = None) -> bool:
|
def all_valid(self, service_list: list[str] | None = 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: Optional[List[str]] = None) -> bool:
|
def all_checks(self, service_list: list[str] | None = 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,4 +1,3 @@
|
|||||||
import os
|
|
||||||
import capnp
|
import capnp
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import numbers
|
import numbers
|
||||||
@@ -6,7 +5,6 @@ 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
|
||||||
@@ -24,10 +22,6 @@ 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():
|
||||||
@@ -53,16 +47,6 @@ 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:
|
||||||
@@ -89,7 +73,6 @@ 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)
|
||||||
@@ -110,7 +93,6 @@ 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)
|
||||||
@@ -129,7 +111,6 @@ 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)
|
||||||
@@ -142,12 +123,10 @@ 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)
|
||||||
@@ -165,16 +144,13 @@ 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()
|
|
||||||
|
|
||||||
# this test doesn't work with ZMQ since multiprocessing interrupts it
|
# wait 5 socket timeouts and make sure it's still retrying
|
||||||
if "ZMQ" not in os.environ:
|
p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,))
|
||||||
# wait 5 socket timeouts and make sure it's still retrying
|
p.start()
|
||||||
p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,))
|
time.sleep(sock_timeout*5)
|
||||||
p.start()
|
assert p.is_alive()
|
||||||
time.sleep(sock_timeout*5)
|
p.terminate()
|
||||||
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,21 +1,16 @@
|
|||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
from typing import Sized, cast
|
from typing import 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,
|
||||||
@@ -42,7 +37,6 @@ 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())
|
||||||
@@ -54,7 +48,6 @@ 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)
|
||||||
@@ -91,21 +84,13 @@ 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"
|
||||||
@@ -124,11 +109,6 @@ 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)
|
||||||
|
|
||||||
@@ -136,7 +116,6 @@ 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]:
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#!/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
|
||||||
@@ -11,7 +10,7 @@ class QueueSize(IntEnum):
|
|||||||
|
|
||||||
|
|
||||||
class Service:
|
class Service:
|
||||||
def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] = None,
|
def __init__(self, should_log: bool, frequency: float, decimation: int | None = 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
|
||||||
@@ -125,8 +124,7 @@ 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 += ' { "%s", {"%s", %s, %f, %d, %d}},\n' % \
|
h += f' {{ "{k}", {{"{k}", {should_log}, {v.frequency:f}, {decimation:d}, {v.queue_size:d}}}}},\n'
|
||||||
(k, k, should_log, v.frequency, decimation, v.queue_size)
|
|
||||||
h += "};\n"
|
h += "};\n"
|
||||||
|
|
||||||
h += "#endif\n"
|
h += "#endif\n"
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ 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')
|
||||||
|
|||||||
@@ -42,25 +42,26 @@ 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._buf = memoryview(b'')
|
self._f = None
|
||||||
|
|
||||||
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 not self._buf:
|
if self._f is None:
|
||||||
p = next(self._paths, None)
|
p = next(self._paths, None)
|
||||||
if p is None:
|
if p is None:
|
||||||
break
|
break
|
||||||
with open(p, 'rb') as f:
|
self._f = open(p, 'rb')
|
||||||
self._buf = memoryview(f.read())
|
count = self._f.readinto(view[n:])
|
||||||
|
if not count:
|
||||||
|
self._f.close()
|
||||||
|
self._f = None
|
||||||
continue
|
continue
|
||||||
take = min(len(b) - n, len(self._buf))
|
n += count
|
||||||
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):
|
||||||
|
|||||||
@@ -56,28 +56,28 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "boot",
|
"name": "boot",
|
||||||
"url": "https://commadist.azureedge.net/agnosupdate/boot-8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8.img.xz",
|
"url": "https://commadist.azureedge.net/agnosupdate/boot-19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39.img.xz",
|
||||||
"hash": "8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8",
|
"hash": "19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39",
|
||||||
"hash_raw": "8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8",
|
"hash_raw": "19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39",
|
||||||
"size": 17487872,
|
"size": 17487872,
|
||||||
"sparse": false,
|
"sparse": false,
|
||||||
"full_check": true,
|
"full_check": true,
|
||||||
"has_ab": true,
|
"has_ab": true,
|
||||||
"ondevice_hash": "edca8bee1531e66953d107eeceeed2dc7b3ca46417e49d55508f94e58bf95db8"
|
"ondevice_hash": "ddfe93cc6a8531af92ee331d9bbaeae2f1d933bdb38e579769dc9fe7998eb626"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "system",
|
"name": "system",
|
||||||
"url": "https://commadist.azureedge.net/agnosupdate/system-ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f.img.xz",
|
"url": "https://commadist.azureedge.net/agnosupdate/system-a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724.img.xz",
|
||||||
"hash": "78acfe16a7b62a3a91fc7a81f40a693e4468cec1c69df7d0b1e550aacc646113",
|
"hash": "4dc41c2c072f5f5d5cd484cd6173049cd96acfb9a67bc20049775585fe881539",
|
||||||
"hash_raw": "ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f",
|
"hash_raw": "a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724",
|
||||||
"size": 4718592000,
|
"size": 4718592000,
|
||||||
"sparse": true,
|
"sparse": true,
|
||||||
"full_check": false,
|
"full_check": false,
|
||||||
"has_ab": true,
|
"has_ab": true,
|
||||||
"ondevice_hash": "743142c5a898f27b2a1029cca42c8a5d5d1fc0096414422b850fe84c8d0b8342",
|
"ondevice_hash": "cf1229630b7a2b8497705bca4ba947dbf0c217418ff4febff571aa4f4a878134",
|
||||||
"alt": {
|
"alt": {
|
||||||
"hash": "ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f",
|
"hash": "a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724",
|
||||||
"url": "https://commadist.azureedge.net/agnosupdate/system-ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f.img",
|
"url": "https://commadist.azureedge.net/agnosupdate/system-a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724.img",
|
||||||
"size": 4718592000
|
"size": 4718592000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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': None}, timeout=60)
|
self.req = requests.get(url, stream=True, headers={'Accept-Encoding': 'identity'}, 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
|
||||||
|
|||||||
@@ -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, 1)
|
gpio_set(GPIO.SOM_ST_IO, True)
|
||||||
|
|
||||||
# *** 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, 1)
|
gpio_set(GPIO.STM_RST_N, True)
|
||||||
gpio_set(GPIO.STM_BOOT0, 0)
|
gpio_set(GPIO.STM_BOOT0, False)
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
gpio_set(GPIO.STM_RST_N, 0)
|
gpio_set(GPIO.STM_RST_N, False)
|
||||||
|
|
||||||
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, 1)
|
gpio_set(GPIO.STM_RST_N, True)
|
||||||
gpio_set(GPIO.STM_BOOT0, 1)
|
gpio_set(GPIO.STM_BOOT0, True)
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
gpio_set(GPIO.STM_RST_N, 0)
|
gpio_set(GPIO.STM_RST_N, False)
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
gpio_set(GPIO.STM_BOOT0, 0)
|
gpio_set(GPIO.STM_BOOT0, False)
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -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 = {
|
INITIAL_STATE: dict[str, object] = {
|
||||||
"seconds_since_boot": 0,
|
"seconds_since_boot": 0,
|
||||||
"state": "INITIALIZING",
|
"state": "INITIALIZING",
|
||||||
"connected": False, "ip_address": "",
|
"connected": False, "ip_address": "",
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ 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}},
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from numbers import Number
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
Gain = int | float | tuple[Sequence[float], Sequence[float]] | list[list[float]]
|
||||||
|
|
||||||
class PIDController:
|
class PIDController:
|
||||||
def __init__(self, k_p, k_i, k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100):
|
def __init__(self, k_p: Gain, k_i: Gain, k_d: Gain = 0., pos_limit=1e308, neg_limit=-1e308, rate=100):
|
||||||
self._k_p: list[list[float]] = [[0], [k_p]] if isinstance(k_p, Number) else k_p
|
self._k_p = ([0], [k_p]) if isinstance(k_p, (int, float)) else k_p
|
||||||
self._k_i: list[list[float]] = [[0], [k_i]] if isinstance(k_i, Number) else k_i
|
self._k_i = ([0], [k_i]) if isinstance(k_i, (int, float)) else k_i
|
||||||
self._k_d: list[list[float]] = [[0], [k_d]] if isinstance(k_d, Number) else k_d
|
self._k_d = ([0], [k_d]) if isinstance(k_d, (int, float)) else k_d
|
||||||
|
|
||||||
self.set_limits(pos_limit, neg_limit)
|
self.set_limits(pos_limit, neg_limit)
|
||||||
|
|
||||||
|
|||||||
@@ -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 = None
|
self.last_rollover = 0.0
|
||||||
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 = list()
|
log_files = []
|
||||||
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)
|
||||||
|
|||||||
@@ -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("LiveParameters")
|
self.params.remove("LiveParametersV2")
|
||||||
|
|
||||||
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("LiveParameters") is None
|
assert self.params.get("LiveParametersV2") is None
|
||||||
assert self.params.get("LiveParameters", return_default=True) is None
|
assert self.params.get("LiveParametersV2", return_default=True) is None
|
||||||
|
|
||||||
def test_params_get_type(self):
|
def test_params_get_type(self):
|
||||||
# json
|
# json
|
||||||
|
|||||||
@@ -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 = {
|
DEVICE_CAMERAS: dict[tuple[str, str], DeviceCameraConfig] = {
|
||||||
# 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,4 +176,3 @@ 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]
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:3027da9834adf9c71177bc3086f8de3018ce22e4725b694d635723c5b3c860cd
|
||||||
|
size 97120
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
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:42bd04a57b527c787a0555503e02a203f7d672c12d448769a3f41f17befbf013
|
oid sha256:6f47633b5082b911e79dd13fc2d1f4c2a9d44d2fe0860f3d390ffc4e3d772f7d
|
||||||
size 48044
|
size 97120
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:538271820d618046009bb456e930598f2736dd1a6174c11925ec3912581b05c1
|
||||||
|
size 52374
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:634fad6590295ef2e73d5f7e5aecbb5dd245a0f7692f1e300c2ebac95aabb8e3
|
||||||
|
size 73026
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:b1e177499d9439367179cc57a6301b6162393972e3a136cc35c5fdac026bf10a
|
oid sha256:a380fe0a022856b302841a0dd19b71eab318c6b08ec851f128942758a7f1631a
|
||||||
size 48044
|
size 97120
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:2c0af7f5fe57bb36ab96fae868e20feca763541c97a61b3a3a84a0e7fcb81163
|
oid sha256:cc9e67cfaba77e8e4f4049f0add9238660e4cf8f886c6915600d8f47a30c98db
|
||||||
size 83350
|
size 97120
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:ad19268e4aaaeac8dd21f6b26c16a121e7b3f50bba867748e7226727643ae682
|
|
||||||
size 144642
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:412ef25d2fb103c1ebd55c667313a5921493305fb4e1f4e1dafc08d3b95d86ab
|
|
||||||
size 73026
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4af81cbf1d96a42cc351878b015298aee82874b46baaf1a615ca91ec36c0ced6
|
oid sha256:081d64ca28a84a59ff5e6aea4732865b8687585938b8a287bc7e6a49d0646508
|
||||||
size 83228
|
size 97120
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:1bb440f424c989c06e203bfba0b32d39aede8f6c78a9b335e5e778460202b601
|
||||||
|
size 73026
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:5a390831afca3bfc6ea3c2739b872ebf866e70df8ae30653f8587e5cd3993959
|
|
||||||
size 68306
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:67e636d072703e6b1233a12344c0a6304fd43d64dbb31c66b71c2c8870a339c1
|
|
||||||
size 153764
|
|
||||||
@@ -3,7 +3,7 @@ import copy
|
|||||||
import os
|
import os
|
||||||
import pytest
|
import pytest
|
||||||
import random
|
import random
|
||||||
import unittest # noqa: TID251
|
import unittest
|
||||||
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,6 +22,7 @@ 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
|
||||||
@@ -131,7 +132,7 @@ class TestCarModelBase(unittest.TestCase):
|
|||||||
segment_range = f"{cls.test_route.route}/{seg}"
|
segment_range = f"{cls.test_route.route}/{seg}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sources = [internal_source] if len(INTERNAL_SEG_LIST) else [openpilotci_source, comma_api_source]
|
sources: list[Source] = [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):
|
||||||
@@ -257,7 +258,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.openpilot_enabled and t / 1e4 > self.car_safety_mode_frame:
|
if self.car_safety_mode_frame is not None 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)
|
||||||
@@ -451,7 +452,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.brakeDEPRECATED > 0.05:
|
if self.CP.carFingerprint in (HONDA.HONDA_PILOT, HONDA.HONDA_RIDGELINE) and CS.deprecated.brake > 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,6 +147,7 @@ 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,6 +15,9 @@ 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
|
||||||
@@ -40,7 +43,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, vEgoStopping=0.3):
|
def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL):
|
||||||
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]
|
||||||
@@ -53,8 +56,7 @@ def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL, vEgoStopping=0.
|
|||||||
v_now = 0.0
|
v_now = 0.0
|
||||||
v_target = 0.0
|
v_target = 0.0
|
||||||
a_target = 0.0
|
a_target = 0.0
|
||||||
should_stop = (v_now < vEgoStopping and a_target < 0.1)
|
return a_target, should_stop(v_now, a_target)
|
||||||
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, CP_SP, active, long_control_state, v_ego,
|
def long_control_state_trans(CP_SP, active, long_control_state,
|
||||||
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,22 +26,17 @@ def long_control_state_trans(CP, CP_SP, active, long_control_state, v_ego,
|
|||||||
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 and CP.startingState:
|
if starting_condition:
|
||||||
long_control_state = LongCtrlState.starting
|
|
||||||
elif starting_condition:
|
|
||||||
long_control_state = LongCtrlState.pid
|
long_control_state = LongCtrlState.pid
|
||||||
|
|
||||||
elif long_control_state in [LongCtrlState.starting, LongCtrlState.pid]:
|
elif long_control_state == 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:
|
||||||
@@ -62,7 +57,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, self.CP_SP, active, self.long_control_state, CS.vEgo,
|
self.long_control_state = long_control_state_trans(self.CP_SP, active, self.long_control_state,
|
||||||
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:
|
||||||
@@ -73,11 +68,8 @@ 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)
|
||||||
output_accel -= self.CP.stoppingDecelRate * DT_CTRL
|
# TODO: can we just go straight to stopAccel?
|
||||||
self.reset()
|
output_accel -= 1.0 * DT_CTRL # m/s^2/s while trying to stop
|
||||||
|
|
||||||
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, vEgoStopping=self.CP.vEgoStopping)
|
action_t=action_t)
|
||||||
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,60 +1,43 @@
|
|||||||
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, CP_SP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||||
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, CP_SP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||||
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, CP_SP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||||
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, CP_SP, active, current_state, v_ego=1.0,
|
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||||
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, CP_SP, active, current_state, v_ego=1.0,
|
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||||
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, CP_SP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||||
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, CP_SP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||||
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, CP_SP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||||
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, CP_SP, active, current_state, v_ego=0.1,
|
next_state = long_control_state_trans(CP_SP, active, current_state,
|
||||||
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,8 +7,6 @@ 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
|
||||||
@@ -58,16 +56,17 @@ 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)
|
||||||
|
|
||||||
def test_estimated_offset():
|
class TestTorquedLatAccelOffset:
|
||||||
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE_BIASED, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
|
def test_estimated_offset(self):
|
||||||
est = get_warmed_up_estimator(steer_torques, lat_accels)
|
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE_BIASED, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
|
||||||
msg = est.get_msg()
|
est = get_warmed_up_estimator(steer_torques, lat_accels)
|
||||||
# TODO add lataccelfactor and friction check when we have more accurate estimates
|
msg = est.get_msg()
|
||||||
assert abs(msg.liveTorqueParameters.latAccelOffsetRaw - TORQUE_TUNE_BIASED.latAccelOffset) < 0.1
|
# TODO add lataccelfactor and friction check when we have more accurate estimates
|
||||||
|
assert abs(msg.liveTorqueParameters.latAccelOffsetRaw - TORQUE_TUNE_BIASED.latAccelOffset) < 0.1
|
||||||
|
|
||||||
def test_straight_road_roll_bias():
|
def test_straight_road_roll_bias(self):
|
||||||
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)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
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
|
||||||
|
|
||||||
@@ -68,7 +69,8 @@ class NPQueue:
|
|||||||
|
|
||||||
|
|
||||||
class PointBuckets:
|
class PointBuckets:
|
||||||
def __init__(self, x_bounds: list[tuple[float, float]], min_points: list[float], min_points_total: int, points_per_bucket: int, rowsize: int) -> None:
|
def __init__(self, x_bounds: list[tuple[float, float]], min_points: Sequence[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))
|
||||||
@@ -98,9 +100,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[np.random.choice(np.arange(len(points)), min(len(points), num_points), replace=False)]
|
return points[self._rng.choice(np.arange(len(points)), min(len(points), num_points), replace=False)]
|
||||||
|
|
||||||
def load_points(self, points: list[list[float]]) -> None:
|
def load_points(self, points: Sequence[Sequence[float]]) -> None:
|
||||||
for point in points:
|
for point in points:
|
||||||
self.add_point(*point)
|
self.add_point(*point)
|
||||||
|
|
||||||
|
|||||||
@@ -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, x_initial: np.ndarray = PoseKalman.initial_x, P_initial: np.ndarray = PoseKalman.initial_P):
|
def reset(self, t: float | None, 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):
|
||||||
|
|||||||
@@ -200,25 +200,6 @@ 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")
|
||||||
@@ -273,8 +254,6 @@ 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,6 +81,7 @@ 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))
|
||||||
@@ -91,15 +92,15 @@ class TestLagd:
|
|||||||
assert np.argmax(corr) == lag_frames
|
assert np.argmax(corr) == lag_frames
|
||||||
|
|
||||||
# add some noise
|
# add some noise
|
||||||
desired_sig += np.random.normal(0, 0.05, len(desired_sig))
|
desired_sig += rng.normal(0, 0.05, len(desired_sig))
|
||||||
actual_sig += np.random.normal(0, 0.05, len(actual_sig))
|
actual_sig += rng.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 = np.random.choice([True, False], size=len(desired_sig), p=[0.6, 0.4])
|
mask = rng.choice([True, False], size=len(desired_sig), p=[0.6, 0.4])
|
||||||
desired_sig[~mask] = np.random.normal(0, 1, size=np.sum(~mask))
|
desired_sig[~mask] = rng.normal(0, 1, size=np.sum(~mask))
|
||||||
actual_sig[~mask] = np.random.normal(0, 1, size=np.sum(~mask))
|
actual_sig[~mask] = rng.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 = None
|
val = msg
|
||||||
for key in keys:
|
for key in keys:
|
||||||
val = getattr(msg if val is None else val, key) if isinstance(key, str) else val[key]
|
val = getattr(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, migrate_cached_vehicle_params_if_needed
|
from openpilot.selfdrive.locationd.paramsd import retrieve_initial_vehicle_params
|
||||||
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,38 +30,9 @@ 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,24 +2,25 @@ from opendbc.car.structs import car
|
|||||||
from openpilot.selfdrive.locationd.torqued import TorqueEstimator
|
from openpilot.selfdrive.locationd.torqued import TorqueEstimator
|
||||||
|
|
||||||
|
|
||||||
def test_cal_percent():
|
class TestTorqued:
|
||||||
est = TorqueEstimator(car.CarParams())
|
def test_cal_percent(self):
|
||||||
msg = est.get_msg()
|
est = TorqueEstimator(car.CarParams())
|
||||||
assert msg.liveTorqueParameters.calPerc == 0
|
msg = est.get_msg()
|
||||||
|
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
|
||||||
|
|||||||
@@ -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 = MIN_BUCKET_POINTS / 10
|
self.min_bucket_points: list[float] = (MIN_BUCKET_POINTS / 10).tolist()
|
||||||
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
|
self.min_bucket_points = MIN_BUCKET_POINTS.tolist()
|
||||||
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,9 +120,10 @@ class TorqueEstimator(ParameterEstimator, TorqueEstimatorExt):
|
|||||||
'latAccelOffset': cache_ltp.latAccelOffsetFiltered,
|
'latAccelOffset': cache_ltp.latAccelOffsetFiltered,
|
||||||
'frictionCoefficient': cache_ltp.frictionCoefficientFiltered
|
'frictionCoefficient': cache_ltp.frictionCoefficientFiltered
|
||||||
}
|
}
|
||||||
initial_params['points'] = cache_ltp.points
|
cached_points: list[list[float]] = [list(point) for point in cache_ltp.points]
|
||||||
|
initial_params['points'] = cached_points
|
||||||
self.decay = cache_ltp.decay
|
self.decay = cache_ltp.decay
|
||||||
self.filtered_points.load_points(initial_params['points'])
|
self.filtered_points.load_points(cached_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")
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ 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
|
||||||
@@ -242,7 +243,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)
|
||||||
np.random.seed(seed)
|
rng = np.random.default_rng(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
|
||||||
@@ -250,7 +251,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[:] = np.random.randn(*v.shape).astype(v.dtype)
|
v[:] = rng.standard_normal(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()
|
||||||
@@ -311,6 +312,9 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ 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
|
||||||
@@ -94,7 +95,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[int, Tensor] = {}
|
self._blob_cache: dict[tuple[str, 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']
|
||||||
@@ -173,6 +174,8 @@ 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=None):
|
def parse_mdn(self, name, outs, in_N=0, out_N=1, out_shape=()):
|
||||||
if self.check_missing(outs, name):
|
if self.check_missing(outs, name):
|
||||||
return
|
return
|
||||||
raw = outs[name]
|
raw = outs[name]
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
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")
|
||||||
@@ -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_TIME = int(1800 / DT_DMON)
|
self._LOCKOUT_TIMES = [int(60 * n_min / DT_DMON) for n_min in [1, 5, 15, 30]]
|
||||||
|
|
||||||
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,7 +152,10 @@ 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_time = 0
|
self.lockout_active = Params().get_bool("DriverTooDistracted")
|
||||||
|
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
|
||||||
@@ -163,7 +166,6 @@ 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)
|
||||||
@@ -310,16 +312,20 @@ 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:
|
||||||
self.too_distracted = True
|
if not self.lockout_active:
|
||||||
|
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.too_distracted:
|
if self.lockout_active:
|
||||||
self.lockout_time += 1
|
self.lockout_time_elapsed += 1
|
||||||
if self.lockout_time > self.settings._LOCKOUT_TIME:
|
if self.lockout_time_elapsed > self.lockout_duration:
|
||||||
self.too_distracted = False
|
self.lockout_active = 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 = 0
|
self.lockout_time_elapsed = 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 \
|
||||||
@@ -379,8 +385,10 @@ 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.too_distracted
|
dm.lockout = self.lockout_active
|
||||||
dm.lockoutRecoveryPercent = to_percent(self.lockout_time / self.settings._LOCKOUT_TIME)
|
dm.lockoutCount = self.lockout_count
|
||||||
|
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,21 +86,17 @@ 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.alert_3_cnt == 1
|
assert d_status.lockout_active
|
||||||
assert d_status.no_response_cnt == s._MAX_NO_RESPONSE
|
assert d_status.lockout_time_elapsed > 0
|
||||||
assert d_status.too_distracted
|
assert d_status.lockout_count >= 1
|
||||||
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.alert_3_cnt == 1
|
assert d_status.lockout_active
|
||||||
assert d_status.no_response_cnt == s._MAX_NO_RESPONSE
|
assert d_status.lockout_count >= 1
|
||||||
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):
|
||||||
@@ -275,7 +271,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)
|
||||||
|
|
||||||
dm._update_events = spy
|
object.__setattr__(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, 1)
|
gpio_set(GPIO.STM_RST_N, True)
|
||||||
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: list() for bus in range(3)}
|
sent_msgs = {bus: [] for bus in range(3)}
|
||||||
|
|
||||||
st = time.monotonic()
|
st = time.monotonic()
|
||||||
ts = {s: list() for s in socks.keys()}
|
ts = {s: [] 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:
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ 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, DT_DMON
|
from openpilot.common.realtime import DT_CTRL
|
||||||
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
|
||||||
@@ -25,7 +24,6 @@ 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()}
|
||||||
@@ -101,7 +99,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 = max(1, round((100 - sm['driverMonitoringState'].lockoutRecoveryPercent) / 100 * DMON_LOCKOUT_TIME * DT_DMON / 60.))
|
mins_left = sm['driverMonitoringState'].lockoutMinutesRemaining
|
||||||
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):
|
def make_event(event_types: list[str | None]):
|
||||||
event = {}
|
EVENTS[0] = {}
|
||||||
for ev in event_types:
|
for ev in event_types:
|
||||||
event[ev] = NormalPermanentAlert("alert")
|
if ev is not None:
|
||||||
EVENTS[0] = event
|
EVENTS[0][ev] = NormalPermanentAlert("alert")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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 = dict(tablefmt="simple_grid", stralign="center", numalign="center")
|
TABULATE_OPTS = {"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.actuatorsOutputDEPRECATED
|
co.carOutput.actuatorsOutput = msg.carControl.deprecated.actuatorsOutput
|
||||||
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].safetyParamDEPRECATED != 0:
|
if CP.safetyConfigs[0].deprecated.safetyParam != 0:
|
||||||
safety_param = CP.safetyConfigs[0].safetyParamDEPRECATED
|
safety_param = CP.safetyConfigs[0].deprecated.safetyParam
|
||||||
else:
|
else:
|
||||||
safety_param = CP.safetyParamDEPRECATED
|
safety_param = CP.deprecated.safetyParam
|
||||||
|
|
||||||
ops = []
|
ops = []
|
||||||
for index, msg in msgs:
|
for index, msg in msgs:
|
||||||
|
|||||||
@@ -216,8 +216,7 @@ 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)
|
self.process.launcher = LauncherWithCapture(self.capture, self.process.launcher) # ty: ignore[invalid-assignment] # intentional wrapper
|
||||||
self.process.prepare()
|
|
||||||
self.process.start()
|
self.process.start()
|
||||||
|
|
||||||
def start(
|
def start(
|
||||||
@@ -635,10 +634,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, Iterable):
|
if isinstance(cfg, ProcessConfig):
|
||||||
cfgs = list(cfg)
|
|
||||||
else:
|
|
||||||
cfgs = [cfg]
|
cfgs = [cfg]
|
||||||
|
else:
|
||||||
|
cfgs = list(cfg)
|
||||||
|
|
||||||
all_msgs = migrate_all(lr,
|
all_msgs = migrate_all(lr,
|
||||||
manager_states=True,
|
manager_states=True,
|
||||||
|
|||||||
@@ -204,7 +204,10 @@ 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:
|
||||||
return self._switch_to_next(now)
|
if self._next is None:
|
||||||
|
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)
|
||||||
@@ -223,7 +226,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)
|
return self._switch_to_next(now, self._next)
|
||||||
# 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
|
||||||
@@ -232,8 +235,8 @@ class FaceAnimator:
|
|||||||
|
|
||||||
return self._animation.frames[frame_index]
|
return self._animation.frames[frame_index]
|
||||||
|
|
||||||
def _switch_to_next(self, now: float) -> list[tuple[int, int]]:
|
def _switch_to_next(self, now: float, animation: Animation) -> list[tuple[int, int]]:
|
||||||
self._animation = self._next
|
self._animation = animation
|
||||||
self._next = None
|
self._next = None
|
||||||
self._rewinding = False
|
self._rewinding = False
|
||||||
self._seen_nonzero = False
|
self._seen_nonzero = False
|
||||||
|
|||||||
@@ -34,7 +34,11 @@ 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 = {MainState.HOME: self._home_layout, MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView()}
|
self._layouts: dict[MainState, Widget] = {
|
||||||
|
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,7 +105,6 @@ 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
|
from dataclasses import dataclass, field
|
||||||
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 = rl.Rectangle(0, 0, 0, 0)
|
button_rect: rl.Rectangle = field(default_factory=lambda: rl.Rectangle(0, 0, 0, 0))
|
||||||
|
|
||||||
|
|
||||||
class SettingsLayout(Widget):
|
class SettingsLayout(Widget):
|
||||||
|
|||||||
@@ -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.get(NetworkType.none)
|
self._net_type = NETWORK_TYPES[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)
|
||||||
|
|||||||
@@ -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(ui_state.is_body)
|
self._body_icon.set_visible(bool(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)
|
||||||
|
|||||||
@@ -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(ui_state.is_body)
|
self._body_onroad_layout.set_visible(bool(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) -> int:
|
def _refresh(self, pending_params: dict) -> 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 = self._pending_params["UpdateAvailable"]
|
update_available = 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 = self._pending_params["UpdaterNewDescription"] or ""
|
new_desc = 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 = self._pending_params[alert_data.key]
|
alert_json = 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,8 +311,9 @@ 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
|
||||||
if self._pending_params is not None:
|
pending_params = self._pending_params
|
||||||
self._refresh()
|
if pending_params is not None:
|
||||||
|
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 HtmlModal, HtmlRenderer
|
from openpilot.system.ui.widgets.html_render import 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: HtmlModal | None = None
|
self._fcc_dialog: MiciFccModal | 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,7 +172,6 @@ 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[float] = field(default_factory=list)
|
glow: list[tuple[float, float]] = field(default_factory=list)
|
||||||
chevron: list[float] = field(default_factory=list)
|
chevron: list[tuple[float, float]] = field(default_factory=list)
|
||||||
fill_alpha: int = 0
|
fill_alpha: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -41,80 +41,81 @@ def get_child_widgets(widget) -> list:
|
|||||||
return children
|
return children
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="segfaults")
|
class TestWidgetLeaks:
|
||||||
def test_dialogs_do_not_leak():
|
@pytest.mark.skip(reason="segfaults")
|
||||||
import pyray as rl
|
def test_dialogs_do_not_leak(self):
|
||||||
rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN)
|
import pyray as rl
|
||||||
from openpilot.system.ui.lib.application import gui_app
|
rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN)
|
||||||
|
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__":
|
||||||
test_dialogs_do_not_leak()
|
TestWidgetLeaks().test_dialogs_do_not_leak()
|
||||||
|
|||||||
@@ -375,6 +375,7 @@ 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
|
||||||
|
|
||||||
@@ -392,6 +393,7 @@ 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()
|
||||||
@@ -409,6 +411,7 @@ 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[float] = field(default_factory=list)
|
glow: list[tuple[float, float]] = field(default_factory=list)
|
||||||
chevron: list[float] = field(default_factory=list)
|
chevron: list[tuple[float, float]] = field(default_factory=list)
|
||||||
fill_alpha: int = 0
|
fill_alpha: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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 = 24 # DB where MIN_VOLUME is applied
|
AMBIENT_DB = 26 # 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,14 +48,15 @@ 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: ("prompt.wav", 1, MAX_VOLUME),
|
AudibleAlert.prompt: ("warning.wav", 1, MAX_VOLUME),
|
||||||
AudibleAlert.promptRepeat: ("prompt.wav", None, MAX_VOLUME),
|
AudibleAlert.promptRepeat: ("warning.wav", None, MAX_VOLUME),
|
||||||
AudibleAlert.promptDistracted: ("prompt_distracted.wav", None, MAX_VOLUME),
|
AudibleAlert.promptDistracted: ("dm_warning.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: ("warning_soft.wav", None, MAX_VOLUME),
|
AudibleAlert.warningSoft: ("critical.wav", None, MAX_VOLUME),
|
||||||
AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME),
|
AudibleAlert.warningImmediate: ("dm_critical.wav", None, MAX_VOLUME),
|
||||||
|
|
||||||
**sound_list_sp,
|
**sound_list_sp,
|
||||||
}
|
}
|
||||||
@@ -89,6 +90,7 @@ 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)
|
||||||
|
|
||||||
@@ -127,6 +129,10 @@ 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
|
||||||
|
|
||||||
@@ -137,6 +143,15 @@ 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
|
||||||
|
|||||||
@@ -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.value_change_step != new_step:
|
if self.lane_turn_value_control.action_item is not None and 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, rect: rl.Rectangle):
|
def _render(self, _):
|
||||||
super()._render(rect)
|
super()._render(_)
|
||||||
|
|
||||||
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(
|
OP.PanelType = IntEnum( # type: ignore[assignment] # ty: ignore[invalid-assignment]
|
||||||
"PanelType",
|
"PanelType",
|
||||||
[es.name for es in OP.PanelType] + [
|
[es.name for es in OP.PanelType] + [
|
||||||
"SUNNYLINK",
|
"SUNNYLINK",
|
||||||
@@ -180,20 +180,18 @@ 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) -> bool:
|
def _handle_mouse_release(self, mouse_pos: MousePos) -> None:
|
||||||
# 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 True
|
return
|
||||||
|
|
||||||
# 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 True
|
return
|
||||||
|
|
||||||
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
|
self._scroller._items = self.main_items # type: ignore[assignment] # ty: ignore[invalid-assignment]
|
||||||
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,6 +16,9 @@ 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 >= SpeedLimitMode.warning
|
speed_limit_warning_enabled = ui_state.speed_limit_mode is not None and 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,8 +224,7 @@ class UIStateSP:
|
|||||||
|
|
||||||
|
|
||||||
class DeviceSP:
|
class DeviceSP:
|
||||||
@staticmethod
|
def _set_awake(self, on: bool, _ui_state=None):
|
||||||
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._refresh()
|
main_layout._alerts_layout._update_state()
|
||||||
|
|
||||||
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
|
ui_state.sm.update = mock_update # ty: ignore[invalid-assignment] # profiling hook
|
||||||
|
|
||||||
|
|
||||||
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.randint(0, 256, yuv_buffer_size, dtype=np.uint8).tobytes()
|
yuv_data = np.random.default_rng().integers(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,7 +2,8 @@ import time
|
|||||||
from openpilot.selfdrive.test.helpers import with_processes
|
from openpilot.selfdrive.test.helpers import with_processes
|
||||||
|
|
||||||
|
|
||||||
@with_processes(["ui"])
|
class TestRaylibUi:
|
||||||
def test_raylib_ui():
|
@with_processes(["ui"])
|
||||||
"""Test initialization of the UI widgets is successful."""
|
def test_raylib_ui(self):
|
||||||
time.sleep(1)
|
"""Test initialization of the UI widgets is successful."""
|
||||||
|
time.sleep(1)
|
||||||
|
|||||||
@@ -46,61 +46,59 @@ def load_po_text(po_path: Path) -> str:
|
|||||||
return po_path.read_text(encoding='utf-8')
|
return po_path.read_text(encoding='utf-8')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("language_code", sorted(TRANSLATION_LANGUAGES.values()))
|
class TestTranslations:
|
||||||
def test_translation_file_exists(language_code: str):
|
@pytest.mark.parametrize("language_code", sorted(TRANSLATION_LANGUAGES.values()))
|
||||||
po_path = PO_DIR / f"app_{language_code}.po"
|
def test_translation_file_exists(self, language_code: str):
|
||||||
assert po_path.exists(), f"missing translation file: {po_path}"
|
po_path = PO_DIR / f"app_{language_code}.po"
|
||||||
|
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_")
|
||||||
|
|
||||||
@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name)
|
for entry in entries:
|
||||||
def test_translation_placeholders_are_preserved(po_path: Path):
|
source_placeholders = extract_placeholders(entry.msgid)
|
||||||
_, entries = parse_po(po_path)
|
|
||||||
language = po_path.stem.removeprefix("app_")
|
|
||||||
|
|
||||||
for entry in entries:
|
if entry.is_plural:
|
||||||
source_placeholders = extract_placeholders(entry.msgid)
|
plural_placeholders = extract_placeholders(entry.msgid_plural)
|
||||||
|
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
|
||||||
|
|
||||||
if entry.is_plural:
|
for idx, msgstr in sorted(entry.msgstr_plural.items()):
|
||||||
plural_placeholders = extract_placeholders(entry.msgid_plural)
|
if not msgstr:
|
||||||
message = (
|
continue
|
||||||
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()):
|
translated_placeholders = extract_placeholders(msgstr)
|
||||||
if not msgstr:
|
message = (
|
||||||
|
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(msgstr)
|
translated_placeholders = extract_placeholders(entry.msgstr)
|
||||||
message = (
|
message = (
|
||||||
f"{language}: plural form {idx} changes placeholders for {entry.msgid!r}: "
|
f"{language}: translation 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
|
|
||||||
|
|
||||||
translated_placeholders = extract_placeholders(entry.msgstr)
|
@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name)
|
||||||
message = (
|
def test_translation_refs_do_not_include_line_numbers(self, po_path: Path):
|
||||||
f"{language}: translation changes placeholders for {entry.msgid!r}: "
|
for line in load_po_text(po_path).splitlines():
|
||||||
+ f"expected {source_placeholders}, got {translated_placeholders}"
|
assert not LINE_NUMBER_REF_RE.match(line), (
|
||||||
|
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)
|
||||||
@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):
|
||||||
def test_translation_refs_do_not_include_line_numbers(po_path: Path):
|
matches = BAD_ENTITY_RE.findall(load_po_text(po_path))
|
||||||
for line in load_po_text(po_path).splitlines():
|
assert not matches, (
|
||||||
assert not LINE_NUMBER_REF_RE.match(line), (
|
f"{po_path.name}: found '@...;' entity typo(s): {', '.join(sorted(set(matches)))}"
|
||||||
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)))}"
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -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():
|
def finish(entry: POEntry | None):
|
||||||
nonlocal cur, header
|
nonlocal header
|
||||||
if cur is None:
|
if entry is None:
|
||||||
return
|
return
|
||||||
if cur.msgid == "" and cur.msgstr:
|
if entry.msgid == "" and entry.msgstr:
|
||||||
header = cur
|
header = entry
|
||||||
elif cur.msgid != "" or cur.is_plural:
|
elif entry.msgid != "" or entry.is_plural:
|
||||||
entries.append(cur)
|
entries.append(entry)
|
||||||
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()
|
finish(cur)
|
||||||
|
cur = None
|
||||||
cur_field = None
|
cur_field = None
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -123,6 +123,8 @@ 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
|
||||||
@@ -138,7 +140,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()
|
finish(cur)
|
||||||
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(UI_DIR, "widgets")),
|
os.walk(os.path.join(str(UI_DIR), "widgets")),
|
||||||
os.walk(os.path.join(UI_DIR, "layouts")),
|
os.walk(os.path.join(str(UI_DIR), "layouts")),
|
||||||
os.walk(os.path.join(UI_DIR, "onroad"))):
|
os.walk(os.path.join(str(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(TRANSLATIONS_DIR, f"app_{name}.po")
|
po_file = os.path.join(str(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:
|
||||||
|
|||||||
@@ -314,9 +314,9 @@ class Device(DeviceSP):
|
|||||||
brightness = 0
|
brightness = 0
|
||||||
|
|
||||||
if brightness != self._last_brightness:
|
if brightness != self._last_brightness:
|
||||||
self._brightness_target = brightness
|
self._brightness_target = int(brightness)
|
||||||
self._brightness_event.set()
|
self._brightness_event.set()
|
||||||
self._last_brightness = brightness
|
self._last_brightness = int(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):
|
def _set_awake(self, on: bool, _ui_state=None):
|
||||||
if on != self._awake:
|
if on != self._awake:
|
||||||
DeviceSP._set_awake(on, ui_state)
|
super()._set_awake(on, _ui_state or 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
|
EVENTS_SP[0] = event # type: ignore[assignment] # ty: ignore[invalid-assignment]
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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 = get_files_for_cleanup() and params.get_bool("OsmLocal")
|
show_alert = bool(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)
|
||||||
np.random.seed(seed)
|
rng = np.random.default_rng(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[:] = np.random.randn(*v.shape).astype(v.dtype)
|
v[:] = rng.standard_normal(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):
|
||||||
np.random.seed(42 + i)
|
rng = np.random.default_rng(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[:] = np.random.randn(*v.shape).astype(v.dtype)
|
v[:] = rng.standard_normal(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,6 +61,7 @@ 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)
|
||||||
@@ -78,8 +79,10 @@ 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,6 +65,7 @@ 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)
|
||||||
@@ -82,8 +83,10 @@ 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.randn(100, 1, 8).astype(np.float32)
|
buf = np.random.default_rng(0).standard_normal((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.randn(1, 1024).astype(np.float32)
|
fake_output = np.random.default_rng(0).standard_normal((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,3 +1,5 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from openpilot.cereal import log
|
from openpilot.cereal import log
|
||||||
@@ -14,7 +16,7 @@ class MockStruct:
|
|||||||
|
|
||||||
|
|
||||||
def test_recovery_power_scaling():
|
def test_recovery_power_scaling():
|
||||||
state = MockStruct(
|
state: Any = 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,
|
||||||
@@ -35,10 +37,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
|
modeld.get_accel_from_plan = mock_accel # ty: ignore[invalid-assignment]
|
||||||
modeld.get_curvature_from_output = mock_curvature
|
modeld.get_curvature_from_output = mock_curvature # ty: ignore[invalid-assignment]
|
||||||
plan = np.random.rand(1, 100, 15).astype(np.float32)
|
plan = np.random.default_rng(0).random((1, 100, 15)).astype(np.float32)
|
||||||
planplus = np.random.rand(1, 100, 15).astype(np.float32)
|
planplus = np.random.default_rng(1).random((1, 100, 15)).astype(np.float32)
|
||||||
merged_plan = plan + planplus
|
merged_plan = plan + planplus
|
||||||
|
|
||||||
model_output: dict = {
|
model_output: dict = {
|
||||||
@@ -60,7 +62,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)
|
ModelState.get_action_from_model(state, model_output, prev_action, 0.0, 0.0, v_ego) # type: ignore[arg-type]
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -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.randint(0, 256, yuv_size, dtype=np.uint8)
|
new_frame_np = np.random.default_rng(0).integers(0, 256, yuv_size, dtype=np.uint8)
|
||||||
new_big_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)
|
||||||
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.randint(0, 256, yuv_size, dtype=np.uint8)
|
verify_frame = np.random.default_rng(0).integers(0, 256, yuv_size, dtype=np.uint8)
|
||||||
verify_big_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)
|
||||||
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(),
|
||||||
|
|||||||
@@ -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 active_bundle_dict and is_bundle_version_compatible(active_bundle_dict):
|
if isinstance(active_bundle_dict, dict) and 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
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class ModelManagerSP:
|
|||||||
total_size = int(response.headers.get("content-length", 0))
|
total_size = int(response.headers.get("content-length", 0))
|
||||||
bytes_downloaded = 0
|
bytes_downloaded = 0
|
||||||
|
|
||||||
with open(path, 'wb') as f:
|
with open(path, 'wb') as f: # noqa: ASYNC230
|
||||||
async for chunk in response.content.iter_chunked(self._chunk_size): # type: bytes
|
async for chunk in response.content.iter_chunked(self._chunk_size): # type: bytes
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
bytes_downloaded += len(chunk)
|
bytes_downloaded += len(chunk)
|
||||||
@@ -110,7 +110,7 @@ class ModelManagerSP:
|
|||||||
async with session.get(chunk_url) as response:
|
async with session.get(chunk_url) as response:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
chunk_size = int(response.headers.get("content-length", 0))
|
chunk_size = int(response.headers.get("content-length", 0))
|
||||||
with open(chunk_path, 'wb') as f:
|
with open(chunk_path, 'wb') as f: # noqa: ASYNC230
|
||||||
async for data in response.content.iter_chunked(self._chunk_size):
|
async for data in response.content.iter_chunked(self._chunk_size):
|
||||||
f.write(data)
|
f.write(data)
|
||||||
chunk_downloaded += len(data)
|
chunk_downloaded += len(data)
|
||||||
@@ -124,9 +124,9 @@ class ModelManagerSP:
|
|||||||
self._sync_artifact_progress(artifact)
|
self._sync_artifact_progress(artifact)
|
||||||
self._report_status()
|
self._report_status()
|
||||||
|
|
||||||
with open(manifest_path, 'w') as f:
|
with open(manifest_path, 'w') as f: # noqa: ASYNC230
|
||||||
f.write(str(num_chunks))
|
f.write(str(num_chunks))
|
||||||
if os.path.isfile(base_path):
|
if os.path.isfile(base_path): # noqa: ASYNC240
|
||||||
os.remove(base_path)
|
os.remove(base_path)
|
||||||
del self._download_start_times[artifact.fileName]
|
del self._download_start_times[artifact.fileName]
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ class ModelManagerSP:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
cloudlog.error(f"Error downloading {filename}: {str(e)}")
|
cloudlog.error(f"Error downloading {filename}: {str(e)}")
|
||||||
for f in [full_path] + [p for p in (os.path.join(destination_path, f) for f in os.listdir(destination_path)) if filename in p]:
|
for f in [full_path] + [p for p in (os.path.join(destination_path, f) for f in os.listdir(destination_path)) if filename in p]:
|
||||||
if os.path.isfile(f):
|
if os.path.isfile(f): # noqa: ASYNC240
|
||||||
os.remove(f)
|
os.remove(f)
|
||||||
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.failed
|
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.failed
|
||||||
artifact.downloadProgress.eta = 0
|
artifact.downloadProgress.eta = 0
|
||||||
@@ -222,7 +222,8 @@ class ModelManagerSP:
|
|||||||
self.selected_bundle = None
|
self.selected_bundle = None
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
self.selected_bundle.status = custom.ModelManagerSP.DownloadStatus.failed
|
if self.selected_bundle is not None:
|
||||||
|
self.selected_bundle.status = custom.ModelManagerSP.DownloadStatus.failed
|
||||||
raise
|
raise
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ def test_radarless_slowdown_triggers_blended(mock_cp, mock_mpc, default_sm):
|
|||||||
controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams())
|
controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams())
|
||||||
|
|
||||||
# Force conditions to simulate slowdown
|
# Force conditions to simulate slowdown
|
||||||
controller._slow_down_filter = FakeKalman(value=1.0) # Ensure urgency triggers slowdown
|
controller._slow_down_filter = FakeKalman(value=1.0) # ty: ignore[invalid-assignment]
|
||||||
controller._v_ego_kph = 35.0
|
controller._v_ego_kph = 35.0
|
||||||
default_sm['modelV2'] = MockModelData(valid=False) # Incomplete trajectory
|
default_sm['modelV2'] = MockModelData(valid=False) # Incomplete trajectory
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user