Compare commits

..

1 Commits

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

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