Compare commits

..

2 Commits

Author SHA1 Message Date
rav4kumar
89a3a953b2 mapd v2 2026-01-13 20:49:08 -07:00
rav4kumar
366eecc45b remove old mapd, sla, ssc 2026-01-13 20:25:37 -07:00
185 changed files with 3252 additions and 5531 deletions

View File

@@ -108,6 +108,7 @@ jobs:
build_mac:
name: build macOS
runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }}
if: false # There'll be one day that this works. That day is not today.
steps:
- uses: actions/checkout@v4
with:

1
.gitmodules vendored
View File

@@ -4,7 +4,6 @@
[submodule "opendbc"]
path = opendbc_repo
url = https://github.com/sunnypilot/opendbc.git
branch = tn
[submodule "msgq"]
path = msgq_repo
url = https://github.com/commaai/msgq.git

2
Jenkinsfile vendored
View File

@@ -22,7 +22,7 @@ shopt -s huponexit # kill all child processes when the shell exits
export CI=1
export PYTHONWARNINGS=error
#export LOGPRINT=debug # this has gotten too spammy...
export LOGPRINT=debug
export TEST_DIR=${env.TEST_DIR}
export SOURCE_DIR=${env.SOURCE_DIR}
export GIT_BRANCH=${env.GIT_BRANCH}

View File

@@ -1,7 +1,3 @@
Version 0.10.4 (2026-02-17)
========================
* Lexus LS 2018 support thanks to Hacheoy!
Version 0.10.3 (2025-12-17)
========================
* New driving model #36249

View File

@@ -192,7 +192,6 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
aTarget @5 :Float32;
events @6 :List(OnroadEventSP.Event);
e2eAlerts @7 :E2eAlerts;
accelPersonality @8 :AccelerationPersonality;
struct DynamicExperimentalControl {
state @0 :DynamicExperimentalControlState;
@@ -204,11 +203,7 @@ struct LongitudinalPlanSP @0xf35cc4560bbf6ec2 {
blended @1;
}
}
enum AccelerationPersonality {
sport @0;
normal @1;
eco @2;
}
struct SmartCruiseControl {
vision @0 :Vision;
map @1 :Map;
@@ -346,7 +341,6 @@ struct OnroadEventSP @0xda96579883444c35 {
speedLimitChanged @21;
speedLimitPending @22;
e2eChime @23;
laneChangeRoadEdge @24;
}
}
@@ -453,8 +447,6 @@ struct LiveMapDataSP @0xf416ec09499d9d19 {
struct ModelDataV2SP @0xa1680744031fdb2d {
laneTurnDirection @0 :TurnDirection;
leftLaneChangeEdgeBlock @1 :Bool;
rightLaneChangeEdgeBlock @2 :Bool;
enum TurnDirection {
none @0;

View File

@@ -87,7 +87,6 @@ struct OnroadEvent @0xc4fa6047f024e718 {
laneChange @50;
lowMemory @51;
stockAeb @52;
stockLkas @98;
ldw @53;
carUnrecognized @54;
invalidLkasSetting @55;

View File

@@ -13,7 +13,7 @@ from typing import Optional, List, Union, Dict
from cereal import log
from cereal.services import SERVICE_LIST
from openpilot.common.utils import MovingAverage
from openpilot.common.util import MovingAverage
NO_TRAVERSAL_LIMIT = 2**64-1

View File

@@ -19,6 +19,11 @@ if GetOption('extras'):
# Cython bindings
params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11'])
common_python = [params_python]
SConscript([
'transformations/SConscript',
])
Import('transformations_python')
common_python = [params_python, transformations_python]
Export('common_python')

View File

@@ -18,8 +18,8 @@ class Api:
return self.service.get_token(payload_extra, expiry_hours)
def api_get(endpoint, method='GET', timeout=None, access_token=None, session=None, **params):
return CommaConnectApi(None).api_get(endpoint, method, timeout, access_token, session, **params)
def api_get(endpoint, method='GET', timeout=None, access_token=None, **params):
return CommaConnectApi(None).api_get(endpoint, method, timeout, access_token, **params)
def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:

View File

@@ -51,7 +51,7 @@ class BaseApi:
ascii_encoded_text = normalized_text.encode('ascii', 'ignore')
return ascii_encoded_text.decode()
def api_get(self, endpoint, method='GET', timeout=None, access_token=None, session=None, json=None, **params):
def api_get(self, endpoint, method='GET', timeout=None, access_token=None, json=None, **params):
headers = {}
if access_token is not None:
headers['Authorization'] = "JWT " + access_token
@@ -59,9 +59,7 @@ class BaseApi:
version = self.remove_non_ascii_chars(get_version())
headers['User-Agent'] = self.user_agent + version
# TODO: add session to Api
req = requests if session is None else session
return req.request(method, f"{self.api_host}/{endpoint}", timeout=timeout, headers=headers, json=json, params=params)
return requests.request(method, f"{self.api_host}/{endpoint}", timeout=timeout, headers=headers, json=json, params=params)
@staticmethod
def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:

View File

@@ -4,27 +4,27 @@ from openpilot.common.utils import run_cmd, run_cmd_default
@cache
def get_commit(cwd: str | None = None, branch: str = "HEAD") -> str:
def get_commit(cwd: str = None, branch: str = "HEAD") -> str:
return run_cmd_default(["git", "rev-parse", branch], cwd=cwd)
@cache
def get_commit_date(cwd: str | None = None, commit: str = "HEAD") -> str:
def get_commit_date(cwd: str = None, commit: str = "HEAD") -> str:
return run_cmd_default(["git", "show", "--no-patch", "--format='%ct %ci'", commit], cwd=cwd)
@cache
def get_short_branch(cwd: str | None = None) -> str:
def get_short_branch(cwd: str = None) -> str:
return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=cwd)
@cache
def get_branch(cwd: str | None = None) -> str:
def get_branch(cwd: str = None) -> str:
return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], cwd=cwd)
@cache
def get_origin(cwd: str | None = None) -> str:
def get_origin(cwd: str = None) -> str:
try:
local_branch = run_cmd(["git", "name-rev", "--name-only", "HEAD"], cwd=cwd)
tracking_remote = run_cmd(["git", "config", "branch." + local_branch + ".remote"], cwd=cwd)
@@ -34,7 +34,7 @@ def get_origin(cwd: str | None = None) -> str:
@cache
def get_normalized_origin(cwd: str | None = None) -> str:
def get_normalized_origin(cwd: str = None) -> str:
return get_origin(cwd) \
.replace("git@", "", 1) \
.replace(".git", "", 1) \

View File

@@ -1 +1 @@
#define DEFAULT_MODEL "WMI (Default)"
#define DEFAULT_MODEL "Dark Souls 2 (Default)"

View File

@@ -133,8 +133,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"Version", {PERSISTENT, STRING}},
// --- sunnypilot params --- //
{"AccelPersonality", {PERSISTENT | BACKUP, INT, std::to_string(static_cast<int>(cereal::LongitudinalPlanSP::AccelerationPersonality::NORMAL))}},
{"AccelPersonalityEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ApiCache_DriveStats", {PERSISTENT, JSON}},
{"AutoLaneChangeBsmDelay", {PERSISTENT | BACKUP, BOOL, "0"}},
{"AutoLaneChangeTimer", {PERSISTENT | BACKUP, INT, "0"}},
@@ -153,7 +151,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"CustomAccShortPressIncrement", {PERSISTENT | BACKUP, INT, "1"}},
{"DeviceBootMode", {PERSISTENT | BACKUP, INT, "0"}},
{"DevUIInfo", {PERSISTENT | BACKUP, INT, "0"}},
{"DynamicFollow", {PERSISTENT | BACKUP, BOOL, "0"}},
{"EnableCopyparty", {PERSISTENT | BACKUP, BOOL}},
{"EnableGithubRunner", {PERSISTENT | BACKUP, BOOL}},
{"GreenLightAlert", {PERSISTENT | BACKUP, BOOL, "0"}},
@@ -171,23 +168,17 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}},
{"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}},
{"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "0"}},
{"OnroadScreenOffControl", {PERSISTENT | BACKUP, BOOL}},
{"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "15"}},
{"OnroadUploads", {PERSISTENT | BACKUP, BOOL, "1"}},
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
{"RainbowMode", {PERSISTENT | BACKUP, BOOL, "0"}},
{"RoadEdgeLaneChangeEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}},
{"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}},
{"TrueVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}},
// toyota specific params
{"ToyotaAutoHold", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ToyotaEnhancedBsm", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ToyotaTSS2Long", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ToyotaDriveMode", {PERSISTENT | BACKUP, BOOL, "0"}},
// MADS params
{"Mads", {PERSISTENT | BACKUP, BOOL, "1"}},
{"MadsMainCruiseAllowed", {PERSISTENT | BACKUP, BOOL, "1"}},

View File

@@ -3,9 +3,15 @@ from numbers import Number
class PIDController:
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._k_p = k_p
self._k_i = k_i
self._k_d = k_d
if isinstance(self._k_p, Number):
self._k_p = [[0], [self._k_p]]
if isinstance(self._k_i, Number):
self._k_i = [[0], [self._k_i]]
if isinstance(self._k_d, Number):
self._k_d = [[0], [self._k_d]]
self.set_limits(pos_limit, neg_limit)

View File

@@ -13,11 +13,7 @@ public:
if (prefix.empty()) {
prefix = util::random_string(15);
}
#ifdef __APPLE__
msgq_path = "/tmp/msgq_" + prefix;
#else
msgq_path = "/dev/shm/msgq_" + prefix;
#endif
msgq_path = Path::shm_path() + "/" + prefix;
bool ret = util::create_directories(msgq_path, 0777);
assert(ret);
setenv("OPENPILOT_PREFIX", prefix.c_str(), 1);

View File

@@ -1,5 +1,4 @@
import os
import platform
import shutil
import uuid
@@ -10,10 +9,9 @@ from openpilot.system.hardware.hw import Paths
from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
class OpenpilotPrefix:
def __init__(self, prefix: str | None = None, create_dirs_on_enter: bool = True, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False):
def __init__(self, prefix: str = None, create_dirs_on_enter: bool = True, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False):
self.prefix = prefix if prefix else str(uuid.uuid4().hex[0:15])
shm_path = "/tmp" if platform.system() == "Darwin" else "/dev/shm"
self.msgq_path = os.path.join(shm_path, "msgq_" + self.prefix)
self.msgq_path = os.path.join(Paths.shm_path(), "msgq_" + self.prefix)
self.create_dirs_on_enter = create_dirs_on_enter
self.clean_dirs_on_exit = clean_dirs_on_exit
self.shared_download_cache = shared_download_cache

View File

@@ -6,7 +6,7 @@ import time
from setproctitle import getproctitle
from openpilot.common.utils import MovingAverage
from openpilot.common.util import MovingAverage
from openpilot.system.hardware import PC

View File

@@ -0,0 +1,5 @@
Import('env', 'envCython')
transformations = env.Library('transformations', ['orientation.cc', 'coordinates.cc'])
transformations_python = envCython.Program('transformations.so', 'transformations.pyx')
Export('transformations', 'transformations_python')

View File

@@ -1,6 +1,6 @@
#define _USE_MATH_DEFINES
#include "sunnypilot/common/transformations/coordinates.hpp"
#include "common/transformations/coordinates.hpp"
#include <iostream>
#include <cmath>

View File

@@ -4,8 +4,8 @@
#include <cmath>
#include <eigen3/Eigen/Dense>
#include "sunnypilot/common/transformations/orientation.hpp"
#include "sunnypilot/common/transformations/coordinates.hpp"
#include "common/transformations/orientation.hpp"
#include "common/transformations/coordinates.hpp"
Eigen::Quaterniond ensure_unique(const Eigen::Quaterniond &quat) {
if (quat.w() > 0){
@@ -141,3 +141,4 @@ Eigen::Vector3d ned_euler_from_ecef(const ECEF &ecef_init, const Eigen::Vector3d
return {phi, theta, psi};
}

View File

@@ -1,6 +1,6 @@
#pragma once
#include <eigen3/Eigen/Dense>
#include "sunnypilot/common/transformations/coordinates.hpp"
#include "common/transformations/coordinates.hpp"
Eigen::Quaterniond ensure_unique(const Eigen::Quaterniond &quat);

View File

@@ -102,36 +102,3 @@ class TestNED:
np.testing.assert_allclose(converter.ned2ecef(ned_offsets_batch),
ecef_positions_offset_batch,
rtol=1e-9, atol=1e-7)
def test_errors(self):
# Test wrong shape/type for geodetic2ecef
# numpy_wrap raises IndexError for scalar input
with np.testing.assert_raises(IndexError):
coord.geodetic2ecef(1.0)
with np.testing.assert_raises_regex(ValueError, "Geodetic must be size 3"):
coord.geodetic2ecef([0, 0])
with np.testing.assert_raises_regex(ValueError, "Geodetic must be size 3"):
coord.geodetic2ecef([0, 0, 0, 0])
with np.testing.assert_raises(TypeError):
coord.geodetic2ecef(['a', 'b', 'c'])
# Test LocalCoord constructor errors
with np.testing.assert_raises(ValueError):
coord.LocalCoord.from_geodetic([0, 0])
with np.testing.assert_raises(ValueError):
coord.LocalCoord.from_geodetic(1)
with np.testing.assert_raises(TypeError):
coord.LocalCoord.from_geodetic(['a', 'b', 'c'])
# Test wrong shape/type for ecef2geodetic
with np.testing.assert_raises(ValueError):
coord.ecef2geodetic([1, 2])
with np.testing.assert_raises(ValueError):
coord.ecef2geodetic([1, 2, 3, 4])
with np.testing.assert_raises(IndexError):
coord.ecef2geodetic(1.0)

View File

@@ -1,5 +1,4 @@
import numpy as np
import pytest
from openpilot.common.transformations.orientation import euler2quat, quat2euler, euler2rot, rot2euler, \
rot2quat, quat2rot, \
@@ -60,32 +59,3 @@ class TestOrientation:
np.testing.assert_allclose(ned_eulers[i], ned_euler_from_ecef(ecef_positions[i], eulers[i]), rtol=1e-7)
#np.testing.assert_allclose(eulers[i], ecef_euler_from_ned(ecef_positions[i], ned_eulers[i]), rtol=1e-7)
# np.testing.assert_allclose(ned_eulers, ned_euler_from_ecef(ecef_positions, eulers), rtol=1e-7)
def test_inputs(self):
with pytest.raises(ValueError):
euler2quat([1, 2])
with pytest.raises(ValueError):
quat2rot([1, 2, 3])
with pytest.raises(IndexError):
rot2quat(np.zeros((2, 2)))
def test_euler_rot_consistency(self):
rpy = [0.1, 0.2, 0.3]
R = euler2rot(rpy)
# R -> q -> R
q = rot2quat(R)
R_new = quat2rot(q)
np.testing.assert_allclose(R, R_new, atol=1e-15)
# q -> R -> Euler (quat2euler) -> R
rpy_new = quat2euler(q)
R_new2 = euler2rot(rpy_new)
np.testing.assert_allclose(R, R_new2, atol=1e-15)
# R -> Euler (rot2euler) -> R
rpy_from_rot = rot2euler(R)
R_new3 = euler2rot(rpy_from_rot)
np.testing.assert_allclose(R, R_new3, atol=1e-15)

View File

@@ -0,0 +1,72 @@
# cython: language_level=3
from libcpp cimport bool
cdef extern from "orientation.cc":
pass
cdef extern from "orientation.hpp":
cdef cppclass Quaternion "Eigen::Quaterniond":
Quaternion()
Quaternion(double, double, double, double)
double w()
double x()
double y()
double z()
cdef cppclass Vector3 "Eigen::Vector3d":
Vector3()
Vector3(double, double, double)
double operator()(int)
cdef cppclass Matrix3 "Eigen::Matrix3d":
Matrix3()
Matrix3(double*)
double operator()(int, int)
Quaternion euler2quat(const Vector3 &)
Vector3 quat2euler(const Quaternion &)
Matrix3 quat2rot(const Quaternion &)
Quaternion rot2quat(const Matrix3 &)
Vector3 rot2euler(const Matrix3 &)
Matrix3 euler2rot(const Vector3 &)
Matrix3 rot_matrix(double, double, double)
Vector3 ecef_euler_from_ned(const ECEF &, const Vector3 &)
Vector3 ned_euler_from_ecef(const ECEF &, const Vector3 &)
cdef extern from "coordinates.cc":
cdef struct ECEF:
double x
double y
double z
cdef struct NED:
double n
double e
double d
cdef struct Geodetic:
double lat
double lon
double alt
bool radians
ECEF geodetic2ecef(const Geodetic &)
Geodetic ecef2geodetic(const ECEF &)
cdef cppclass LocalCoord_c "LocalCoord":
Matrix3 ned2ecef_matrix
Matrix3 ecef2ned_matrix
LocalCoord_c(const Geodetic &, const ECEF &)
LocalCoord_c(const Geodetic &)
LocalCoord_c(const ECEF &)
NED ecef2ned(const ECEF &)
ECEF ned2ecef(const NED &)
NED geodetic2ned(const Geodetic &)
Geodetic ned2geodetic(const NED &)
cdef extern from "coordinates.hpp":
pass

View File

@@ -1,342 +0,0 @@
import numpy as np
# Constants
a = 6378137.0
b = 6356752.3142
esq = 6.69437999014e-3
e1sq = 6.73949674228e-3
def geodetic2ecef_single(g):
"""
Convert geodetic coordinates (latitude, longitude, altitude) to ECEF.
"""
try:
if len(g) != 3:
raise ValueError("Geodetic must be size 3")
except TypeError:
raise ValueError("Geodetic must be a sequence of length 3") from None
lat, lon, alt = g
lat = np.radians(lat)
lon = np.radians(lon)
xi = np.sqrt(1.0 - esq * np.sin(lat)**2)
x = (a / xi + alt) * np.cos(lat) * np.cos(lon)
y = (a / xi + alt) * np.cos(lat) * np.sin(lon)
z = (a / xi * (1.0 - esq) + alt) * np.sin(lat)
return np.array([x, y, z])
def ecef2geodetic_single(e):
"""
Convert ECEF to geodetic coordinates using Ferrari's solution.
"""
x, y, z = e
r = np.sqrt(x**2 + y**2)
Esq = a**2 - b**2
F = 54 * b**2 * z**2
G = r**2 + (1 - esq) * z**2 - esq * Esq
C = (esq**2 * F * r**2) / (G**3)
S = np.cbrt(1 + C + np.sqrt(C**2 + 2 * C))
P = F / (3 * (S + 1 / S + 1)**2 * G**2)
Q = np.sqrt(1 + 2 * esq**2 * P)
r_0 = -(P * esq * r) / (1 + Q) + np.sqrt(0.5 * a**2 * (1 + 1.0 / Q) - P * (1 - esq) * z**2 / (Q * (1 + Q)) - 0.5 * P * r**2)
U = np.sqrt((r - esq * r_0)**2 + z**2)
V = np.sqrt((r - esq * r_0)**2 + (1 - esq) * z**2)
Z_0 = b**2 * z / (a * V)
h = U * (1 - b**2 / (a * V))
lat = np.arctan((z + e1sq * Z_0) / r)
lon = np.arctan2(y, x)
return np.array([np.degrees(lat), np.degrees(lon), h])
def euler2quat_single(euler):
"""
Convert Euler angles (roll, pitch, yaw) to a quaternion.
Rotation order: Z-Y-X (yaw, pitch, roll).
"""
phi, theta, psi = euler
c_phi, s_phi = np.cos(phi / 2), np.sin(phi / 2)
c_theta, s_theta = np.cos(theta / 2), np.sin(theta / 2)
c_psi, s_psi = np.cos(psi / 2), np.sin(psi / 2)
w = c_phi * c_theta * c_psi + s_phi * s_theta * s_psi
x = s_phi * c_theta * c_psi - c_phi * s_theta * s_psi
y = c_phi * s_theta * c_psi + s_phi * c_theta * s_psi
z = c_phi * c_theta * s_psi - s_phi * s_theta * c_psi
if w < 0:
return np.array([-w, -x, -y, -z])
return np.array([w, x, y, z])
def quat2euler_single(q):
"""
Convert a quaternion to Euler angles (roll, pitch, yaw).
"""
w, x, y, z = q
gamma = np.arctan2(2 * (w * x + y * z), 1 - 2 * (x**2 + y**2))
sin_arg = 2 * (w * y - z * x)
sin_arg = np.clip(sin_arg, -1.0, 1.0)
theta = np.arcsin(sin_arg)
psi = np.arctan2(2 * (w * z + x * y), 1 - 2 * (y**2 + z**2))
return np.array([gamma, theta, psi])
def quat2rot_single(q):
"""
Convert a quaternion to a 3x3 rotation matrix.
"""
w, x, y, z = q
xx, yy, zz = x * x, y * y, z * z
xy, xz, yz = x * y, x * z, y * z
wx, wy, wz = w * x, w * y, w * z
mat = np.array([
[1 - 2 * (yy + zz), 2 * (xy - wz), 2 * (xz + wy)],
[2 * (xy + wz), 1 - 2 * (xx + zz), 2 * (yz - wx)],
[2 * (xz - wy), 2 * (yz + wx), 1 - 2 * (xx + yy)]
])
return mat
def rot2quat_single(rot):
"""
Convert a 3x3 rotation matrix to a quaternion.
"""
trace = np.trace(rot)
if trace > 0:
s = 0.5 / np.sqrt(trace + 1.0)
w = 0.25 / s
x = (rot[2, 1] - rot[1, 2]) * s
y = (rot[0, 2] - rot[2, 0]) * s
z = (rot[1, 0] - rot[0, 1]) * s
else:
if rot[0, 0] > rot[1, 1] and rot[0, 0] > rot[2, 2]:
s = 2.0 * np.sqrt(1.0 + rot[0, 0] - rot[1, 1] - rot[2, 2])
w = (rot[2, 1] - rot[1, 2]) / s
x = 0.25 * s
y = (rot[0, 1] + rot[1, 0]) / s
z = (rot[0, 2] + rot[2, 0]) / s
elif rot[1, 1] > rot[2, 2]:
s = 2.0 * np.sqrt(1.0 + rot[1, 1] - rot[0, 0] - rot[2, 2])
w = (rot[0, 2] - rot[2, 0]) / s
x = (rot[0, 1] + rot[1, 0]) / s
y = 0.25 * s
z = (rot[1, 2] + rot[2, 1]) / s
else:
s = 2.0 * np.sqrt(1.0 + rot[2, 2] - rot[0, 0] - rot[1, 1])
w = (rot[1, 0] - rot[0, 1]) / s
x = (rot[0, 2] + rot[2, 0]) / s
y = (rot[1, 2] + rot[2, 1]) / s
z = 0.25 * s
if w < 0:
return np.array([-w, -x, -y, -z])
return np.array([w, x, y, z])
def euler2rot_single(euler):
"""
Convert Euler angles (roll, pitch, yaw) to a 3x3 rotation matrix.
Rotation order: Z-Y-X (yaw, pitch, roll).
"""
phi, theta, psi = euler
cx, sx = np.cos(phi), np.sin(phi)
cy, sy = np.cos(theta), np.sin(theta)
cz, sz = np.cos(psi), np.sin(psi)
Rx = np.array([[1, 0, 0], [0, cx, -sx], [0, sx, cx]])
Ry = np.array([[cy, 0, sy], [0, 1, 0], [-sy, 0, cy]])
Rz = np.array([[cz, -sz, 0], [sz, cz, 0], [0, 0, 1]])
return Rz @ Ry @ Rx
def rot2euler_single(rot):
"""
Convert a 3x3 rotation matrix to Euler angles (roll, pitch, yaw).
"""
return quat2euler_single(rot2quat_single(rot))
def rot_matrix(roll, pitch, yaw):
"""
Create a 3x3 rotation matrix from roll, pitch, and yaw angles.
"""
return euler2rot_single([roll, pitch, yaw])
def axis_angle_to_rot(axis, angle):
"""
Convert an axis-angle representation to a 3x3 rotation matrix.
"""
c = np.cos(angle / 2)
s = np.sin(angle / 2)
q = np.array([c, s*axis[0], s*axis[1], s*axis[2]])
return quat2rot_single(q)
class LocalCoord:
"""
A class to handle conversions between ECEF and local NED coordinates.
"""
def __init__(self, geodetic=None, ecef=None):
"""
Initialize LocalCoord with either geodetic or ECEF coordinates.
"""
if geodetic is not None:
self.init_ecef = geodetic2ecef_single(geodetic)
lat, lon, _ = geodetic
elif ecef is not None:
self.init_ecef = np.array(ecef)
lat, lon, _ = ecef2geodetic_single(ecef)
else:
raise ValueError("Must provide geodetic or ecef")
lat = np.radians(lat)
lon = np.radians(lon)
self.ned2ecef_matrix = np.array([
[-np.sin(lat) * np.cos(lon), -np.sin(lon), -np.cos(lat) * np.cos(lon)],
[-np.sin(lat) * np.sin(lon), np.cos(lon), -np.cos(lat) * np.sin(lon)],
[np.cos(lat), 0, -np.sin(lat)]
])
self.ecef2ned_matrix = self.ned2ecef_matrix.T
@classmethod
def from_geodetic(cls, geodetic):
"""
Create a LocalCoord instance from geodetic coordinates.
"""
return cls(geodetic=geodetic)
@classmethod
def from_ecef(cls, ecef):
"""
Create a LocalCoord instance from ECEF coordinates.
"""
return cls(ecef=ecef)
def ecef2ned_single(self, ecef):
"""
Convert a single ECEF point to NED coordinates relative to the origin.
"""
return self.ecef2ned_matrix @ (ecef - self.init_ecef)
def ned2ecef_single(self, ned):
"""
Convert a single NED point to ECEF coordinates.
"""
return self.ned2ecef_matrix @ ned + self.init_ecef
def geodetic2ned_single(self, geodetic):
"""
Convert a single geodetic point to NED coordinates.
"""
ecef = geodetic2ecef_single(geodetic)
return self.ecef2ned_single(ecef)
def ned2geodetic_single(self, ned):
"""
Convert a single NED point to geodetic coordinates.
"""
ecef = self.ned2ecef_single(ned)
return ecef2geodetic_single(ecef)
@property
def ned_from_ecef_matrix(self):
"""
Returns the rotation matrix from ECEF to NED coordinates.
"""
return self.ecef2ned_matrix
@property
def ecef_from_ned_matrix(self):
"""
Returns the rotation matrix from NED to ECEF coordinates.
"""
return self.ned2ecef_matrix
def ecef_euler_from_ned_single(ecef_init, ned_pose):
"""
Convert NED Euler angles (roll, pitch, yaw) at a given ECEF origin
to equivalent ECEF Euler angles.
"""
converter = LocalCoord(ecef=ecef_init)
zero = np.array(ecef_init)
x0 = converter.ned2ecef_single([1, 0, 0]) - zero
y0 = converter.ned2ecef_single([0, 1, 0]) - zero
z0 = converter.ned2ecef_single([0, 0, 1]) - zero
phi, theta, psi = ned_pose
x1 = axis_angle_to_rot(z0, psi) @ x0
y1 = axis_angle_to_rot(z0, psi) @ y0
z1 = axis_angle_to_rot(z0, psi) @ z0
x2 = axis_angle_to_rot(y1, theta) @ x1
y2 = axis_angle_to_rot(y1, theta) @ y1
z2 = axis_angle_to_rot(y1, theta) @ z1
x3 = axis_angle_to_rot(x2, phi) @ x2
y3 = axis_angle_to_rot(x2, phi) @ y2
x0 = np.array([1.0, 0, 0])
y0 = np.array([0, 1.0, 0])
z0 = np.array([0, 0, 1.0])
psi_out = np.arctan2(np.dot(x3, y0), np.dot(x3, x0))
theta_out = np.arctan2(-np.dot(x3, z0), np.sqrt(np.dot(x3, x0)**2 + np.dot(x3, y0)**2))
y2 = axis_angle_to_rot(z0, psi_out) @ y0
z2 = axis_angle_to_rot(y2, theta_out) @ z0
phi_out = np.arctan2(np.dot(y3, z2), np.dot(y3, y2))
return np.array([phi_out, theta_out, psi_out])
def ned_euler_from_ecef_single(ecef_init, ecef_pose):
"""
Convert ECEF Euler angles (roll, pitch, yaw) at a given ECEF origin
to equivalent NED Euler angles.
"""
converter = LocalCoord(ecef=ecef_init)
x0 = np.array([1.0, 0, 0])
y0 = np.array([0, 1.0, 0])
z0 = np.array([0, 0, 1.0])
phi, theta, psi = ecef_pose
x1 = axis_angle_to_rot(z0, psi) @ x0
y1 = axis_angle_to_rot(z0, psi) @ y0
z1 = axis_angle_to_rot(z0, psi) @ z0
x2 = axis_angle_to_rot(y1, theta) @ x1
y2 = axis_angle_to_rot(y1, theta) @ y1
z2 = axis_angle_to_rot(y1, theta) @ z1
x3 = axis_angle_to_rot(x2, phi) @ x2
y3 = axis_angle_to_rot(x2, phi) @ y2
zero = np.array(ecef_init)
x0 = converter.ned2ecef_single([1, 0, 0]) - zero
y0 = converter.ned2ecef_single([0, 1, 0]) - zero
z0 = converter.ned2ecef_single([0, 0, 1]) - zero
psi_out = np.arctan2(np.dot(x3, y0), np.dot(x3, x0))
theta_out = np.arctan2(-np.dot(x3, z0), np.sqrt(np.dot(x3, x0)**2 + np.dot(x3, y0)**2))
y2 = axis_angle_to_rot(z0, psi_out) @ y0
z2 = axis_angle_to_rot(y2, theta_out) @ z0
phi_out = np.arctan2(np.dot(y3, z2), np.dot(y3, y2))
return np.array([phi_out, theta_out, psi_out])

View File

@@ -0,0 +1,173 @@
# distutils: language = c++
# cython: language_level = 3
from openpilot.common.transformations.transformations cimport Matrix3, Vector3, Quaternion
from openpilot.common.transformations.transformations cimport ECEF, NED, Geodetic
from openpilot.common.transformations.transformations cimport euler2quat as euler2quat_c
from openpilot.common.transformations.transformations cimport quat2euler as quat2euler_c
from openpilot.common.transformations.transformations cimport quat2rot as quat2rot_c
from openpilot.common.transformations.transformations cimport rot2quat as rot2quat_c
from openpilot.common.transformations.transformations cimport euler2rot as euler2rot_c
from openpilot.common.transformations.transformations cimport rot2euler as rot2euler_c
from openpilot.common.transformations.transformations cimport rot_matrix as rot_matrix_c
from openpilot.common.transformations.transformations cimport ecef_euler_from_ned as ecef_euler_from_ned_c
from openpilot.common.transformations.transformations cimport ned_euler_from_ecef as ned_euler_from_ecef_c
from openpilot.common.transformations.transformations cimport geodetic2ecef as geodetic2ecef_c
from openpilot.common.transformations.transformations cimport ecef2geodetic as ecef2geodetic_c
from openpilot.common.transformations.transformations cimport LocalCoord_c
import numpy as np
cimport numpy as np
cdef np.ndarray[double, ndim=2] matrix2numpy(Matrix3 m):
return np.array([
[m(0, 0), m(0, 1), m(0, 2)],
[m(1, 0), m(1, 1), m(1, 2)],
[m(2, 0), m(2, 1), m(2, 2)],
])
cdef Matrix3 numpy2matrix(np.ndarray[double, ndim=2, mode="fortran"] m):
assert m.shape[0] == 3
assert m.shape[1] == 3
return Matrix3(<double*>m.data)
cdef ECEF list2ecef(ecef):
cdef ECEF e
e.x = ecef[0]
e.y = ecef[1]
e.z = ecef[2]
return e
cdef NED list2ned(ned):
cdef NED n
n.n = ned[0]
n.e = ned[1]
n.d = ned[2]
return n
cdef Geodetic list2geodetic(geodetic):
cdef Geodetic g
g.lat = geodetic[0]
g.lon = geodetic[1]
g.alt = geodetic[2]
return g
def euler2quat_single(euler):
cdef Vector3 e = Vector3(euler[0], euler[1], euler[2])
cdef Quaternion q = euler2quat_c(e)
return [q.w(), q.x(), q.y(), q.z()]
def quat2euler_single(quat):
cdef Quaternion q = Quaternion(quat[0], quat[1], quat[2], quat[3])
cdef Vector3 e = quat2euler_c(q)
return [e(0), e(1), e(2)]
def quat2rot_single(quat):
cdef Quaternion q = Quaternion(quat[0], quat[1], quat[2], quat[3])
cdef Matrix3 r = quat2rot_c(q)
return matrix2numpy(r)
def rot2quat_single(rot):
cdef Matrix3 r = numpy2matrix(np.asfortranarray(rot, dtype=np.double))
cdef Quaternion q = rot2quat_c(r)
return [q.w(), q.x(), q.y(), q.z()]
def euler2rot_single(euler):
cdef Vector3 e = Vector3(euler[0], euler[1], euler[2])
cdef Matrix3 r = euler2rot_c(e)
return matrix2numpy(r)
def rot2euler_single(rot):
cdef Matrix3 r = numpy2matrix(np.asfortranarray(rot, dtype=np.double))
cdef Vector3 e = rot2euler_c(r)
return [e(0), e(1), e(2)]
def rot_matrix(roll, pitch, yaw):
return matrix2numpy(rot_matrix_c(roll, pitch, yaw))
def ecef_euler_from_ned_single(ecef_init, ned_pose):
cdef ECEF init = list2ecef(ecef_init)
cdef Vector3 pose = Vector3(ned_pose[0], ned_pose[1], ned_pose[2])
cdef Vector3 e = ecef_euler_from_ned_c(init, pose)
return [e(0), e(1), e(2)]
def ned_euler_from_ecef_single(ecef_init, ecef_pose):
cdef ECEF init = list2ecef(ecef_init)
cdef Vector3 pose = Vector3(ecef_pose[0], ecef_pose[1], ecef_pose[2])
cdef Vector3 e = ned_euler_from_ecef_c(init, pose)
return [e(0), e(1), e(2)]
def geodetic2ecef_single(geodetic):
cdef Geodetic g = list2geodetic(geodetic)
cdef ECEF e = geodetic2ecef_c(g)
return [e.x, e.y, e.z]
def ecef2geodetic_single(ecef):
cdef ECEF e = list2ecef(ecef)
cdef Geodetic g = ecef2geodetic_c(e)
return [g.lat, g.lon, g.alt]
cdef class LocalCoord:
cdef LocalCoord_c * lc
def __init__(self, geodetic=None, ecef=None):
assert (geodetic is not None) or (ecef is not None)
if geodetic is not None:
self.lc = new LocalCoord_c(list2geodetic(geodetic))
elif ecef is not None:
self.lc = new LocalCoord_c(list2ecef(ecef))
@property
def ned2ecef_matrix(self):
return matrix2numpy(self.lc.ned2ecef_matrix)
@property
def ecef2ned_matrix(self):
return matrix2numpy(self.lc.ecef2ned_matrix)
@property
def ned_from_ecef_matrix(self):
return self.ecef2ned_matrix
@property
def ecef_from_ned_matrix(self):
return self.ned2ecef_matrix
@classmethod
def from_geodetic(cls, geodetic):
return cls(geodetic=geodetic)
@classmethod
def from_ecef(cls, ecef):
return cls(ecef=ecef)
def ecef2ned_single(self, ecef):
assert self.lc
cdef ECEF e = list2ecef(ecef)
cdef NED n = self.lc.ecef2ned(e)
return [n.n, n.e, n.d]
def ned2ecef_single(self, ned):
assert self.lc
cdef NED n = list2ned(ned)
cdef ECEF e = self.lc.ned2ecef(n)
return [e.x, e.y, e.z]
def geodetic2ned_single(self, geodetic):
assert self.lc
cdef Geodetic g = list2geodetic(geodetic)
cdef NED n = self.lc.geodetic2ned(g)
return [n.n, n.e, n.d]
def ned2geodetic_single(self, ned):
assert self.lc
cdef NED n = list2ned(ned)
cdef Geodetic g = self.lc.ned2geodetic(n)
return [g.lat, g.lon, g.alt]
def __dealloc__(self):
del self.lc

46
common/util.py Normal file
View File

@@ -0,0 +1,46 @@
import os
import subprocess
def sudo_write(val: str, path: str) -> None:
try:
with open(path, 'w') as f:
f.write(str(val))
except PermissionError:
os.system(f"sudo chmod a+w {path}")
try:
with open(path, 'w') as f:
f.write(str(val))
except PermissionError:
# fallback for debugfs files
os.system(f"sudo su -c 'echo {val} > {path}'")
def sudo_read(path: str) -> str:
try:
return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8').strip()
except Exception:
return ""
class MovingAverage:
def __init__(self, window_size: int):
self.window_size: int = window_size
self.buffer: list[float] = [0.0] * window_size
self.index: int = 0
self.count: int = 0
self.sum: float = 0.0
def add_value(self, new_value: float):
# Update the sum: subtract the value being replaced and add the new value
self.sum -= self.buffer[self.index]
self.buffer[self.index] = new_value
self.sum += new_value
# Update the index in a circular manner
self.index = (self.index + 1) % self.window_size
# Track the number of added values (for partial windows)
self.count = min(self.count + 1, self.window_size)
def get_average(self) -> float:
if self.count == 0:
return float('nan')
return self.sum / self.count

View File

@@ -7,61 +7,14 @@ import time
import functools
from subprocess import Popen, PIPE, TimeoutExpired
import zstandard as zstd
from openpilot.common.swaglog import cloudlog
LOG_COMPRESSION_LEVEL = 10 # little benefit up to level 15. level ~17 is a small step change
def sudo_write(val: str, path: str) -> None:
try:
with open(path, 'w') as f:
f.write(str(val))
except PermissionError:
os.system(f"sudo chmod a+w {path}")
try:
with open(path, 'w') as f:
f.write(str(val))
except PermissionError:
# fallback for debugfs files
os.system(f"sudo su -c 'echo {val} > {path}'")
def sudo_read(path: str) -> str:
try:
return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8').strip()
except Exception:
return ""
class MovingAverage:
def __init__(self, window_size: int):
self.window_size: int = window_size
self.buffer: list[float] = [0.0] * window_size
self.index: int = 0
self.count: int = 0
self.sum: float = 0.0
def add_value(self, new_value: float):
# Update the sum: subtract the value being replaced and add the new value
self.sum -= self.buffer[self.index]
self.buffer[self.index] = new_value
self.sum += new_value
# Update the index in a circular manner
self.index = (self.index + 1) % self.window_size
# Track the number of added values (for partial windows)
self.count = min(self.count + 1, self.window_size)
def get_average(self) -> float:
if self.count == 0:
return float('nan')
return self.sum / self.count
class CallbackReader:
"""Wraps a file, but overrides the read method to also
call a callback function with the number of bytes read so far."""
def __init__(self, f, callback, *args):
self.f = f
self.callback = callback
@@ -154,11 +107,11 @@ def retry(attempts=3, delay=1.0, ignore_failure=False):
try:
return func(*args, **kwargs)
except Exception:
print(f"{func.__name__} failed, trying again")
cloudlog.exception(f"{func.__name__} failed, trying again")
time.sleep(delay)
if ignore_failure:
print(f"{func.__name__} failed after retry")
cloudlog.error(f"{func.__name__} failed after retry")
else:
raise Exception(f"{func.__name__} failed after retry")
return wrapper

View File

@@ -1 +1 @@
#define COMMA_VERSION "0.10.4"
#define COMMA_VERSION "0.10.3"

View File

@@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1
export QCOM_PRIORITY=12
if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="16"
export AGNOS_VERSION="15.1"
fi
export STAGING_ROOT="/data/safe_staging"

2
panda

Submodule panda updated: f9cdec7f7b...5f3c09c910

View File

@@ -14,7 +14,7 @@ dependencies = [
"pyserial", # pigeond + qcomgpsd
"requests", # many one-off uses
"sympy", # rednose + friends
"crcmod-plus", # cars + qcomgpsd
"crcmod", # cars + qcomgpsd
"tqdm", # cars (fw_versions.py) on start + many one-off uses
# hardwared
@@ -49,7 +49,7 @@ dependencies = [
# logging
"pyzmq",
"sentry-sdk",
"xattr", # used in place of 'os.getxattr' for macOS compatibility
"xattr", # used in place of 'os.getxattr' for macos compatibility
# athena
"PyJWT",
@@ -72,7 +72,7 @@ dependencies = [
"zstandard",
# ui
"raylib > 5.5.0.3",
"raylib < 5.5.0.3", # TODO: unpin when they fix https://github.com/electronstudio/raylib-python-cffi/issues/186
"qrcode",
"mapbox-earcut",
]
@@ -87,7 +87,7 @@ docs = [
testing = [
"coverage",
"hypothesis ==6.47.*",
"ty",
"mypy",
"pytest",
"pytest-cpp",
"pytest-subtests",
@@ -107,13 +107,15 @@ dev = [
"av",
"azure-identity",
"azure-storage-blob",
"dbus-next", # TODO: remove once we moved everything to jeepney
"dictdiffer",
"jeepney",
"matplotlib",
"opencv-python-headless",
"parameterized >=0.8, <0.9",
"pyautogui",
"pyopencl",
"pygame",
"pyopencl; platform_machine != 'aarch64'", # broken on arm64
"pytools>=2025.1.6; platform_machine != 'aarch64'",
"pywinctl",
"pyprof2calltree",
@@ -179,6 +181,42 @@ ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,w
builtin = "clear,rare,informal,code,names,en-GB_to_en-US"
skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.po, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*, selfdrive/assets/offroad/mici_fcc.html"
[tool.mypy]
python_version = "3.11"
exclude = [
"cereal/",
"msgq/",
"msgq_repo/",
"opendbc/",
"opendbc_repo/",
"panda/",
"rednose/",
"rednose_repo/",
"tinygrad/",
"tinygrad_repo/",
"teleoprtc/",
"teleoprtc_repo/",
"third_party/",
]
# third-party packages
ignore_missing_imports=true
# helpful warnings
warn_redundant_casts=true
warn_unreachable=true
warn_unused_ignores=true
# restrict dynamic typing
warn_return_any=true
# allow implicit optionals for default args
implicit_optional = true
local_partial_types=true
explicit_package_bases=true
disable_error_code = "annotation-unchecked"
# https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml
[tool.ruff]
indent-width = 2
@@ -237,43 +275,3 @@ lint.flake8-implicit-str-concat.allow-multiline = false
[tool.ruff.format]
quote-style = "preserve"
[tool.ty.src]
exclude = [
"cereal/",
"msgq/",
"msgq_repo/",
"opendbc/",
"opendbc_repo/",
"panda/",
"rednose/",
"rednose_repo/",
"tinygrad/",
"tinygrad_repo/",
"teleoprtc/",
"teleoprtc_repo/",
"third_party/",
]
[tool.ty.rules]
# Ignore unresolved imports for Cython-compiled modules (.pyx)
unresolved-import = "ignore"
# Ignore unresolved attributes - many from capnp and Cython modules
unresolved-attribute = "ignore"
# Ignore invalid method overrides - signature variance issues
invalid-method-override = "ignore"
# Ignore possibly-missing-attribute - too many false positives
possibly-missing-attribute = "ignore"
# Ignore invalid assignment - often intentional monkey-patching
invalid-assignment = "ignore"
# Ignore no-matching-overload - numpy/ctypes overload matching issues
no-matching-overload = "ignore"
# Ignore invalid-argument-type - many false positives from raylib, ctypes, numpy
invalid-argument-type = "ignore"
# Ignore call-non-callable - false positives from dynamic types
call-non-callable = "ignore"
# Ignore unsupported-operator - false positives from dynamic types
unsupported-operator = "ignore"
# Ignore not-subscriptable - false positives from dynamic types
not-subscriptable = "ignore"
# not-iterable errors are now fixed

View File

@@ -4,17 +4,18 @@
## release checklist
### Go to staging
- [ ] make a GitHub issue to track release with this checklist
- [ ] make a GitHub issue to track release
- [ ] create release master branch
- [ ] create a branch from upstream master named `zerotentwo` for release `v0.10.2`
- [ ] revert risky commits (double check with autonomy team)
- [ ] push the new branch
- [ ] push to staging:
- [ ] make sure you are on the newly created release master branch (`zerotentwo`)
- [ ] run `BRANCH=devel-staging release/build_stripped.sh`. Jenkins will then automatically build staging on device, run `test_onroad` and update the staging branch
- [ ] update RELEASES.md
- [ ] bump version on master: `common/version.h` and `RELEASES.md`
- [ ] build new userdata partition from `release3-staging`
- [ ] post on Discord, tag `@release crew`
Updating staging:
1. either rebase on master or cherry-pick changes
2. run this to update: `BRANCH=devel-staging release/build_devel.sh`
3. build new userdata partition from `release3-staging`
### Go to release
- [ ] before going to release, test the following:
- [ ] update from previous release -> new release
@@ -25,7 +26,7 @@
- [ ] check sentry, MTBF, etc.
- [ ] stress test passes in production
- [ ] publish the blog post
- [ ] `git reset --hard origin/release-mici-staging`
- [ ] `git reset --hard origin/release3-staging`
- [ ] tag the release: `git tag v0.X.X <commit-hash> && git push origin v0.X.X`
- [ ] create GitHub release
- [ ] final test install on `openpilot.comma.ai`

View File

@@ -55,7 +55,7 @@ function run_tests() {
run "check_nomerge_comments" $DIR/check_nomerge_comments.sh $ALL_FILES
if [[ -z "$FAST" ]]; then
run "ty" ty check
run "mypy" mypy $PYTHON_FILES
run "codespell" codespell $ALL_FILES --ignore-words=$ROOT/.codespellignore
fi
@@ -69,7 +69,7 @@ function help() {
echo ""
echo -e "${BOLD}${UNDERLINE}Tests:${NC}"
echo -e " ${BOLD}ruff${NC}"
echo -e " ${BOLD}ty${NC}"
echo -e " ${BOLD}mypy${NC}"
echo -e " ${BOLD}codespell${NC}"
echo -e " ${BOLD}check_added_large_files${NC}"
echo -e " ${BOLD}check_shebang_scripts_are_executable${NC}"
@@ -81,11 +81,11 @@ function help() {
echo " Specify tests to skip separated by spaces"
echo ""
echo -e "${BOLD}${UNDERLINE}Examples:${NC}"
echo " op lint ty ruff"
echo " Only run the ty and ruff tests"
echo " op lint mypy ruff"
echo " Only run the mypy and ruff tests"
echo ""
echo " op lint --skip ty ruff"
echo " Skip the ty and ruff tests"
echo " op lint --skip mypy ruff"
echo " Skip the mypy and ruff tests"
echo ""
echo " op lint"
echo " Run all the tests"

View File

@@ -1,8 +1,7 @@
from cereal import car, log
from cereal import car, log, custom
import cereal.messaging as messaging
from opendbc.car import DT_CTRL, structs
from opendbc.car.car_helpers import interfaces
from opendbc.car.interfaces import MAX_CTRL_SPEED
from opendbc.car.toyota.values import ToyotaFlags
from openpilot.selfdrive.selfdrived.events import Events
@@ -12,6 +11,33 @@ EventName = log.OnroadEvent.EventName
NetworkLocation = structs.CarParams.NetworkLocation
# TODO: the goal is to abstract this file into the CarState struct and make events generic
class MockCarState:
def __init__(self):
self.sm = messaging.SubMaster(['gpsLocation', 'gpsLocationExternal'])
def update(self, CS: car.CarState, CS_SP: custom.CarStateSP):
self.sm.update(0)
gps_sock = 'gpsLocationExternal' if self.sm.recv_frame['gpsLocationExternal'] > 1 else 'gpsLocation'
CS.vEgo = self.sm[gps_sock].speed
CS.vEgoRaw = self.sm[gps_sock].speed
return CS, CS_SP
BRAND_EXTRA_GEARS = {
'ford': [GearShifter.low, GearShifter.manumatic],
'nissan': [GearShifter.brake],
'chrysler': [GearShifter.low],
'honda': [GearShifter.sport],
'toyota': [GearShifter.sport],
'gm': [GearShifter.sport, GearShifter.low, GearShifter.eco, GearShifter.manumatic],
'volkswagen': [GearShifter.eco, GearShifter.sport, GearShifter.manumatic],
'hyundai': [GearShifter.sport, GearShifter.manumatic]
}
class CarSpecificEvents:
def __init__(self, CP: structs.CarParams):
self.CP = CP
@@ -22,12 +48,14 @@ class CarSpecificEvents:
self.silent_steer_warning = True
def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl):
extra_gears = BRAND_EXTRA_GEARS.get(self.CP.brand, None)
if self.CP.brand in ('body', 'mock'):
return Events()
events = Events()
events = self.create_common_events(CS, CS_prev)
elif self.CP.brand == 'chrysler':
events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears)
if self.CP.brand == 'chrysler':
# Low speed steer alert hysteresis logic
if self.CP.minSteerSpeed > 0. and CS.vEgo < (self.CP.minSteerSpeed + 0.5):
self.low_speed_alert = True
@@ -37,6 +65,8 @@ class CarSpecificEvents:
events.add(EventName.belowSteerSpeed)
elif self.CP.brand == 'honda':
events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=False)
if self.CP.pcmCruise and CS.vEgo < self.CP.minEnableSpeed:
events.add(EventName.belowEngageSpeed)
@@ -57,9 +87,11 @@ class CarSpecificEvents:
elif self.CP.brand == 'toyota':
# TODO: when we check for unexpected disengagement, check gear not S1, S2, S3
events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears)
if self.CP.openpilotLongitudinalControl:
# Only can leave standstill when planner wants to move
if CS.cruiseState.standstill and not CS.brakePressed and (CC.cruiseControl.resume or self.CP.flags & ToyotaFlags.HYBRID.value):
if CS.cruiseState.standstill and not CS.brakePressed and CC.cruiseControl.resume:
events.add(EventName.resumeRequired)
if CS.vEgo < self.CP.minEnableSpeed:
events.add(EventName.belowEngageSpeed)
@@ -71,6 +103,8 @@ class CarSpecificEvents:
events.add(EventName.manualRestart)
elif self.CP.brand == 'gm':
events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=self.CP.pcmCruise)
# Enabling at a standstill with brake is allowed
# TODO: verify 17 Volt can enable for the first time at a stop and allow for all GMs
if CS.vEgo < self.CP.minEnableSpeed and not (CS.standstill and CS.brake >= 20 and
@@ -80,6 +114,8 @@ class CarSpecificEvents:
events.add(EventName.resumeRequired)
elif self.CP.brand == 'volkswagen':
events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=self.CP.pcmCruise)
if self.CP.openpilotLongitudinalControl:
if CS.vEgo < self.CP.minEnableSpeed + 0.5:
events.add(EventName.belowEngageSpeed)
@@ -87,26 +123,27 @@ class CarSpecificEvents:
events.add(EventName.speedTooLow)
# TODO: this needs to be implemented generically in carState struct
# if CC.eps_timer_soft_disable_alert:
# if CC.eps_timer_soft_disable_alert: # type: ignore[attr-defined]
# events.add(EventName.steerTimeLimit)
elif self.CP.brand == 'hyundai':
events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=self.CP.pcmCruise, allow_button_cancel=False)
else:
events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears)
return events
def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState):
def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState, extra_gears: list | None = None, pcm_enable=True,
allow_button_cancel=True):
events = Events()
CI = interfaces[self.CP.carFingerprint]
# TODO: cleanup the honda-specific logic
pcm_enable = self.CP.pcmCruise and self.CP.brand != 'honda'
# TODO: on some hyundai cars, the cancel button is also the pause/resume button,
# so only use it for cancel when running openpilot longitudinal
allow_button_cancel = self.CP.brand != 'hyundai'
if CS.doorOpen:
events.add(EventName.doorOpen)
if CS.seatbeltUnlatched:
events.add(EventName.seatbeltNotLatched)
if CS.gearShifter != GearShifter.drive and CS.gearShifter not in CI.DRIVABLE_GEARS:
if CS.gearShifter != GearShifter.drive and (extra_gears is None or
CS.gearShifter not in extra_gears):
events.add(EventName.wrongGear)
if CS.gearShifter == GearShifter.reverse:
events.add(EventName.reverseGear)
@@ -120,8 +157,6 @@ class CarSpecificEvents:
events.add(EventName.stockFcw)
if CS.stockAeb:
events.add(EventName.stockAeb)
if CS.stockLkas:
events.add(EventName.stockLkas)
if CS.vEgo > MAX_CTRL_SPEED:
events.add(EventName.speedTooHigh)
if CS.cruiseState.nonAdaptive:

View File

@@ -10,7 +10,7 @@ from cereal import car, log, custom
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper
from openpilot.common.swaglog import cloudlog, ForwardingHandler
from opendbc.safety import ALTERNATIVE_EXPERIENCE
from opendbc.car import DT_CTRL, structs
from opendbc.car.can_definitions import CanData, CanRecvCallable, CanSendCallable
from opendbc.car.carlog import carlog
@@ -19,6 +19,7 @@ from opendbc.car.car_helpers import get_car, interfaces
from opendbc.car.interfaces import CarInterfaceBase, RadarInterfaceBase
from openpilot.selfdrive.pandad import can_capnp_to_list, can_list_to_can_capnp
from openpilot.selfdrive.car.cruise import VCruiseHelper
from openpilot.selfdrive.car.car_specific import MockCarState
from openpilot.selfdrive.car.helpers import convert_carControlSP, convert_to_capnp
from openpilot.sunnypilot.mads.helpers import set_alternative_experience, set_car_specific_params
@@ -122,13 +123,7 @@ class Car:
self.CI, self.CP, self.CP_SP = CI, CI.CP, CI.CP_SP
self.RI = RI
# set alternative experiences from parameters
sp_toyota_auto_brake_hold = self.params.get_bool("ToyotaAutoHold")
self.CP.alternativeExperience = 0
if sp_toyota_auto_brake_hold:
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ALLOW_AEB
# mads
set_alternative_experience(self.CP, self.CP_SP, self.params)
set_car_specific_params(self.CP, self.CP_SP, self.params)
@@ -144,7 +139,7 @@ class Car:
safety_config.safetyModel = structs.CarParams.SafetyModel.noOutput
self.CP.safetyConfigs = [safety_config]
if self.CP.secOcRequired:
if self.CP.secOcRequired and not is_release:
# Copy user key if available
try:
with open("/cache/params/SecOCKey") as f:
@@ -184,6 +179,7 @@ class Car:
self.params.put_nonblocking("CarParamsSPCache", cp_sp_bytes)
self.params.put_nonblocking("CarParamsSPPersistent", cp_sp_bytes)
self.mock_carstate = MockCarState()
self.v_cruise_helper = VCruiseHelper(self.CP, self.CP_SP)
self.is_metric = self.params.get_bool("IsMetric")
@@ -204,6 +200,8 @@ class Car:
# Update carState from CAN
CS, CS_SP = self.CI.update(can_list)
CS_SP = convert_to_capnp(CS_SP)
if self.CP.brand == 'mock':
CS, CS_SP = self.mock_carstate.update(CS, CS_SP)
# Update radar tracks from CAN
RD: structs.RadarDataT | None = self.RI.update(can_list)

View File

@@ -56,7 +56,7 @@ class DesireHelper:
def get_lane_change_direction(CS):
return LaneChangeDirection.left if CS.leftBlinker else LaneChangeDirection.right
def update(self, carstate, lateral_active, lane_change_prob, left_edge_detected, right_edge_detected):
def update(self, carstate, lateral_active, lane_change_prob):
self.alc.update_params()
self.lane_turn_controller.update_params()
v_ego = carstate.vEgo
@@ -88,8 +88,8 @@ class DesireHelper:
((carstate.steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
(carstate.steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right))
blindspot_detected = (((carstate.leftBlindspot or left_edge_detected) and self.lane_change_direction == LaneChangeDirection.left) or
((carstate.rightBlindspot or right_edge_detected) and self.lane_change_direction == LaneChangeDirection.right))
blindspot_detected = ((carstate.leftBlindspot and self.lane_change_direction == LaneChangeDirection.left) or
(carstate.rightBlindspot and self.lane_change_direction == LaneChangeDirection.right))
self.alc.update_lane_change(blindspot_detected, carstate.brakePressed)

View File

@@ -327,10 +327,8 @@ class LongitudinalMpc:
lead_xv = self.extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau)
return lead_xv
def update(self, radarstate, v_cruise, x, v, a, j, personality=log.LongitudinalPersonality.standard, a_cruise_min_override=None, t_follow_override=None):
t_follow = t_follow_override if t_follow_override is not None else get_T_FOLLOW(personality)
a_cruise_min = a_cruise_min_override if a_cruise_min_override is not None else CRUISE_MIN_ACCEL
def update(self, radarstate, v_cruise, x, v, a, j, personality=log.LongitudinalPersonality.standard):
t_follow = get_T_FOLLOW(personality)
v_ego = self.x0[1]
self.status = radarstate.leadOne.status or radarstate.leadTwo.status
@@ -352,7 +350,7 @@ class LongitudinalMpc:
# Fake an obstacle for cruise, this ensures smooth acceleration to set speed
# when the leads are no factor.
v_lower = v_ego + (T_IDXS * a_cruise_min * 1.05)
v_lower = v_ego + (T_IDXS * CRUISE_MIN_ACCEL * 1.05)
# TODO does this make sense when max_a is negative?
v_upper = v_ego + (T_IDXS * CRUISE_MAX_ACCEL * 1.05)
v_cruise_clipped = np.clip(v_cruise * np.ones(N+1),

View File

@@ -124,11 +124,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
prev_accel_constraint = not (reset_state or sm['carState'].standstill)
if mode == 'acc':
if sp_accel_clip := LongitudinalPlannerSP.get_accel_clip(self, v_ego, mode):
accel_clip = sp_accel_clip
else:
accel_clip = [ACCEL_MIN, get_max_accel(v_ego)]
accel_clip = [ACCEL_MIN, get_max_accel(v_ego)]
steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg
accel_clip = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_clip, self.CP)
else:
@@ -158,9 +154,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
self.mpc.set_weights(prev_accel_constraint, personality=sm['selfdriveState'].personality)
self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired)
a_cruise_min_override = LongitudinalPlannerSP.get_cruise_min_accel(self, v_ego)
t_follow_override = LongitudinalPlannerSP.get_t_follow(self, v_ego)
self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=sm['selfdriveState'].personality, a_cruise_min_override=a_cruise_min_override, t_follow_override=t_follow_override)
self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=sm['selfdriveState'].personality)
self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution)
self.a_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution)

View File

@@ -108,11 +108,11 @@ if __name__ == "__main__":
uds_client = UdsClient(panda, 0x7D0, bus=args.bus)
print("\n[START DIAGNOSTIC SESSION]")
session_type : SESSION_TYPE = 0x07
session_type : SESSION_TYPE = 0x07 # type: ignore
uds_client.diagnostic_session_control(session_type)
print("[HARDWARE/SOFTWARE VERSION]")
fw_version_data_id : DATA_IDENTIFIER_TYPE = 0xf100
fw_version_data_id : DATA_IDENTIFIER_TYPE = 0xf100 # type: ignore
fw_version = uds_client.read_data_by_identifier(fw_version_data_id)
print(fw_version)
if fw_version not in SUPPORTED_FW_VERSIONS.keys():
@@ -120,7 +120,7 @@ if __name__ == "__main__":
sys.exit(1)
print("[GET CONFIGURATION]")
config_data_id : DATA_IDENTIFIER_TYPE = 0x0142
config_data_id : DATA_IDENTIFIER_TYPE = 0x0142 # type: ignore
current_config = uds_client.read_data_by_identifier(config_data_id)
config_values = SUPPORTED_FW_VERSIONS[fw_version]
new_config = config_values.default_config if args.default else config_values.tracks_enabled

View File

@@ -55,7 +55,7 @@ if __name__ == "__main__":
sw_ver = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_VERSION_NUMBER).decode("utf-8")
component = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.SYSTEM_NAME_OR_ENGINE_TYPE).decode("utf-8")
odx_file = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.ODX_FILE).decode("utf-8").rstrip('\x00')
current_coding = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING)
current_coding = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING) # type: ignore
coding_text = current_coding.hex()
print("\nEPS diagnostic data\n")
@@ -126,9 +126,9 @@ if __name__ == "__main__":
new_coding = current_coding[0:coding_byte] + new_byte.to_bytes(1, "little") + current_coding[coding_byte+1:]
try:
seed = uds_client.security_access(ACCESS_TYPE_LEVEL_1.REQUEST_SEED)
seed = uds_client.security_access(ACCESS_TYPE_LEVEL_1.REQUEST_SEED) # type: ignore
key = struct.unpack("!I", seed)[0] + 28183 # yeah, it's like that
uds_client.security_access(ACCESS_TYPE_LEVEL_1.SEND_KEY, struct.pack("!I", key))
uds_client.security_access(ACCESS_TYPE_LEVEL_1.SEND_KEY, struct.pack("!I", key)) # type: ignore
except (NegativeResponseError, MessageTimeoutError):
print("Security access failed!")
print("Open the hood and retry (disables the \"diagnostic firewall\" on newer vehicles)")
@@ -148,7 +148,7 @@ if __name__ == "__main__":
uds_client.write_data_by_identifier(DATA_IDENTIFIER_TYPE.PROGRAMMING_DATE, prog_date)
tester_num = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.CALIBRATION_REPAIR_SHOP_CODE_OR_CALIBRATION_EQUIPMENT_SERIAL_NUMBER)
uds_client.write_data_by_identifier(DATA_IDENTIFIER_TYPE.REPAIR_SHOP_CODE_OR_TESTER_SERIAL_NUMBER, tester_num)
uds_client.write_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING, new_coding)
uds_client.write_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING, new_coding) # type: ignore
except (NegativeResponseError, MessageTimeoutError):
print("Writing new configuration failed!")
print("Make sure the comma processes are stopped: tmux kill-session -t comma")
@@ -156,7 +156,7 @@ if __name__ == "__main__":
try:
# Read back result just to make 100% sure everything worked
current_coding_text = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING).hex()
current_coding_text = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING).hex() # type: ignore
print(f" New coding: {current_coding_text}")
except (NegativeResponseError, MessageTimeoutError):
print("Reading back updated coding failed!")

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python3
# type: ignore
'''
System tools like top/htop can only show current cpu usage values, so I write this script to do statistics jobs.
Features:

View File

@@ -99,7 +99,8 @@ def cycle_alerts(duration=200, is_metric=False):
alert = AM.process_alerts(frame, [])
print(alert)
for _ in range(duration):
dat = messaging.new_message('selfdriveState')
dat = messaging.new_message()
dat.init('selfdriveState')
dat.selfdriveState.enabled = False
if alert:
@@ -111,7 +112,8 @@ def cycle_alerts(duration=200, is_metric=False):
dat.selfdriveState.alertSound = alert.audible_alert
pm.send('selfdriveState', dat)
dat = messaging.new_message('deviceState')
dat = messaging.new_message()
dat.init('deviceState')
dat.deviceState.started = True
pm.send('deviceState', dat)

View File

@@ -28,12 +28,11 @@ def get_fingerprint(lr):
# TODO: also print the fw fingerprint merged with the existing ones
# show FW fingerprint
if fw:
print("\nFW fingerprint:\n")
for f in fw:
print(f" (Ecu.{f.ecu}, {hex(f.address)}, {None if f.subAddress == 0 else f.subAddress}): [")
print(f" {f.fwVersion},")
print(" ],")
print("\nFW fingerprint:\n")
for f in fw:
print(f" (Ecu.{f.ecu}, {hex(f.address)}, {None if f.subAddress == 0 else f.subAddress}): [")
print(f" {f.fwVersion},")
print(" ],")
print()
print(f"VIN: {vin}")

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python3
# type: ignore
import random
from collections import defaultdict

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python3
# type: ignore
import os
import argparse

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python3
# type: ignore
from collections import defaultdict
import argparse

View File

@@ -47,7 +47,7 @@ DEBUG = os.getenv("DEBUG") is not None
def is_calibration_valid(rpy: np.ndarray) -> bool:
return (PITCH_LIMITS[0] < rpy[1] < PITCH_LIMITS[1]) and (YAW_LIMITS[0] < rpy[2] < YAW_LIMITS[1])
return (PITCH_LIMITS[0] < rpy[1] < PITCH_LIMITS[1]) and (YAW_LIMITS[0] < rpy[2] < YAW_LIMITS[1]) # type: ignore
def sanity_clip(rpy: np.ndarray) -> np.ndarray:
@@ -92,7 +92,7 @@ class Calibrator:
valid_blocks: int = 0,
wide_from_device_euler_init: np.ndarray = WIDE_FROM_DEVICE_EULER_INIT,
height_init: np.ndarray = HEIGHT_INIT,
smooth_from: np.ndarray | None = None) -> None:
smooth_from: np.ndarray = None) -> None:
if not np.isfinite(rpy_init).all():
self.rpy = RPY_INIT.copy()
else:

View File

@@ -94,7 +94,7 @@ class PointBuckets:
def add_point(self, x: float, y: float) -> None:
raise NotImplementedError
def get_points(self, num_points: int | None = None) -> Any:
def get_points(self, num_points: int = None) -> Any:
points = np.vstack([x.arr for x in self.buckets.values()])
if num_points is None:
return points

View File

@@ -127,8 +127,8 @@ class VehicleParamsLearner:
if not self.active:
# Reset time when stopped so uncertainty doesn't grow
self.kf.filter.set_filter_time(t)
self.kf.filter.reset_rewind()
self.kf.filter.set_filter_time(t) # type: ignore
self.kf.filter.reset_rewind() # type: ignore
def get_msg(self, valid: bool, debug: bool = False) -> capnp._DynamicStructBuilder:
x = self.kf.x

View File

@@ -35,7 +35,7 @@ MIN_BUCKET_POINTS = np.array([100, 300, 500, 500, 500, 500, 300, 100])
MIN_ENGAGE_BUFFER = 2 # secs
VERSION = 1 # bump this to invalidate old parameter caches
ALLOWED_CARS = ['toyota', 'hyundai', 'rivian', 'honda', 'volkswagen']
ALLOWED_CARS = ['toyota', 'hyundai', 'rivian', 'honda']
def slope2rot(slope):

View File

@@ -1,7 +1,7 @@
import os
import glob
Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc')
Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc', 'transformations')
lenv = env.Clone()
lenvCython = envCython.Clone()

View File

@@ -33,7 +33,7 @@ from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
from openpilot.sunnypilot.selfdrive.controls.lib.relc import RoadEdgeLaneChangeController
PROCESS_NAME = "selfdrive.modeld.modeld"
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
@@ -298,7 +298,6 @@ def main(demo=False):
prev_action = log.ModelDataV2.Action()
DH = DesireHelper()
RELC = RoadEdgeLaneChangeController(params.get_bool("RoadEdgeLaneChangeEnabled"))
while True:
# Keep receiving frames until we are at least 1 frame ahead of previous extra frame
@@ -396,10 +395,7 @@ def main(demo=False):
l_lane_change_prob = desire_state[log.Desire.laneChangeLeft]
r_lane_change_prob = desire_state[log.Desire.laneChangeRight]
lane_change_prob = l_lane_change_prob + r_lane_change_prob
RELC.update(modelv2_send.modelV2.roadEdgeStds, modelv2_send.modelV2.laneLineProbs)
mdv2sp_send.modelDataV2SP.leftLaneChangeEdgeBlock = RELC.left_edge_detected
mdv2sp_send.modelDataV2SP.rightLaneChangeEdgeBlock = RELC.right_edge_detected
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob, RELC.left_edge_detected, RELC.right_edge_detected)
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction

View File

@@ -14,7 +14,7 @@ with open(os.path.join(BASEDIR, "selfdrive/selfdrived/alerts_offroad.json")) as
OFFROAD_ALERTS = json.load(f)
def set_offroad_alert(alert: str, show_alert: bool, extra_text: str | None = None) -> None:
def set_offroad_alert(alert: str, show_alert: bool, extra_text: str = None) -> None:
if show_alert:
a = copy.copy(OFFROAD_ALERTS[alert])
a['extra'] = extra_text or ''

View File

@@ -303,15 +303,6 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
ET.NO_ENTRY: NoEntryAlert("Stock AEB: Risk of Collision"),
},
EventName.stockLkas: {
ET.PERMANENT: Alert(
"TAKE CONTROL",
"Stock LKAS: Lane Departure Detected",
AlertStatus.critical, AlertSize.full,
Priority.HIGH, VisualAlert.fcw, AudibleAlert.none, 2.),
ET.NO_ENTRY: NoEntryAlert("Stock LKAS: Lane Departure Detected"),
},
EventName.fcw: {
ET.PERMANENT: Alert(
"BRAKE!",
@@ -767,13 +758,13 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
# - CAN data is received, but some message are not received at the right frequency
# If you're not writing a new car port, this is usually cause by faulty wiring
EventName.canError: {
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Unknown Vehicle Variant"),
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("CAN Error"),
ET.PERMANENT: Alert(
"Unknown Vehicle Variant",
"CAN Error: Check Connections",
"",
AlertStatus.normal, AlertSize.small,
Priority.LOW, VisualAlert.none, AudibleAlert.none, 1., creation_delay=1.),
ET.NO_ENTRY: NoEntryAlert("Unknown Vehicle Variant"),
ET.NO_ENTRY: NoEntryAlert("CAN Error: Check Connections"),
},
EventName.canBusMissing: {

View File

@@ -233,8 +233,8 @@ class SelfdriveD(CruiseHelper):
# Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0
if (CS.gasPressed and not self.CS_prev.gasPressed and self.disengage_on_accelerator) or \
(CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \
(CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)):
(CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \
(CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)):
self.events.add(EventName.pedalPressed)
# Create events for temperature, disk space, and memory
@@ -295,22 +295,16 @@ class SelfdriveD(CruiseHelper):
# Handle lane change
if self.sm['modelV2'].meta.laneChangeState == LaneChangeState.preLaneChange:
direction = self.sm['modelV2'].meta.laneChangeDirection
mdv2sp = self.sm['modelDataV2SP']
if (CS.leftBlindspot and direction == LaneChangeDirection.left) or \
(CS.rightBlindspot and direction == LaneChangeDirection.right):
(CS.rightBlindspot and direction == LaneChangeDirection.right):
self.events.add(EventName.laneChangeBlocked)
elif mdv2sp.leftLaneChangeEdgeBlock or mdv2sp.rightLaneChangeEdgeBlock:
self.events_sp.add(custom.OnroadEventSP.EventName.laneChangeRoadEdge)
else:
if direction == LaneChangeDirection.left:
self.events.add(EventName.preLaneChangeLeft)
else:
self.events.add(EventName.preLaneChangeRight)
elif self.sm['modelV2'].meta.laneChangeState in (LaneChangeState.laneChangeStarting,
LaneChangeState.laneChangeFinishing):
LaneChangeState.laneChangeFinishing):
self.events.add(EventName.laneChange)
# Handle lane turn
@@ -505,7 +499,7 @@ class SelfdriveD(CruiseHelper):
# All pandas not in silent mode must have controlsAllowed when openpilot is enabled
if self.enabled and any(not ps.controlsAllowed for ps in self.sm['pandaStates']
if ps.safetyModel not in IGNORED_SAFETY_MODES):
if ps.safetyModel not in IGNORED_SAFETY_MODES):
self.mismatch_counter += 1
return CS

View File

@@ -44,7 +44,7 @@ class FuzzyGenerator:
except capnp.lib.capnp.KjException:
return self.generate_struct(field.schema)
def generate_struct(self, schema: capnp.lib.capnp._StructSchema, event: str | None = None) -> st.SearchStrategy[dict[str, Any]]:
def generate_struct(self, schema: capnp.lib.capnp._StructSchema, event: str = None) -> st.SearchStrategy[dict[str, Any]]:
single_fill: tuple[str, ...] = (event,) if event else (self.draw(st.sampled_from(schema.union_fields)),) if schema.union_fields else ()
fields_to_generate = schema.non_union_fields + single_fill
return st.fixed_dictionaries({field: self.generate_field(schema.fields[field]) for field in fields_to_generate if not field.endswith('DEPRECATED')})

View File

@@ -1,6 +1,5 @@
from collections import defaultdict
from collections.abc import Callable
from typing import cast
import capnp
import functools
import traceback
@@ -70,7 +69,7 @@ def migrate(lr: LogIterable, migration_funcs: list[MigrationFunc]):
if migration.product in grouped: # skip if product already exists
continue
sorted_indices = sorted(ii for i in cast(list[str], migration.inputs) for ii in grouped.get(i, []))
sorted_indices = sorted(ii for i in migration.inputs for ii in grouped[i])
msg_gen = [(i, lr[i]) for i in sorted_indices]
r_ops, a_ops, d_ops = migration(msg_gen)
replace_ops.extend(r_ops)

View File

@@ -614,9 +614,9 @@ def replay_process_with_name(name: str | Iterable[str], lr: LogIterable, *args,
def replay_process(
cfg: ProcessConfig | Iterable[ProcessConfig], lr: LogIterable, frs: dict[str, FrameReader] | None = None,
fingerprint: str | None = None, return_all_logs: bool = False, custom_params: dict[str, Any] | None = None,
captured_output_store: dict[str, dict[str, str]] | None = None, disable_progress: bool = False
cfg: ProcessConfig | Iterable[ProcessConfig], lr: LogIterable, frs: dict[str, FrameReader] = None,
fingerprint: str = None, return_all_logs: bool = False, custom_params: dict[str, Any] = None,
captured_output_store: dict[str, dict[str, str]] = None, disable_progress: bool = False
) -> list[capnp._DynamicStructReader]:
if isinstance(cfg, Iterable):
cfgs = list(cfg)

View File

@@ -16,7 +16,7 @@ from openpilot.tools.lib.openpilotci import get_url
def regen_segment(
lr: LogIterable, frs: dict[str, Any] | None = None,
lr: LogIterable, frs: dict[str, Any] = None,
processes: Iterable[ProcessConfig] = CONFIGS, disable_tqdm: bool = False
) -> list[capnp._DynamicStructReader]:
all_msgs = sorted(lr, key=lambda m: m.logMonoTime)

View File

@@ -19,7 +19,7 @@ SOURCES: list[AzureContainer] = [
DEST = OpenpilotCIContainer
def upload_route(path: str, exclude_patterns: Iterable[str] | None = None) -> None:
def upload_route(path: str, exclude_patterns: Iterable[str] = None) -> None:
if exclude_patterns is None:
exclude_patterns = [r'dcamera\.hevc']

View File

@@ -39,7 +39,7 @@ class HomeLayout(Widget):
self.current_state = HomeLayoutState.HOME
self.last_refresh = 0
self.settings_callback: Callable[[], None] | None = None
self.settings_callback: callable | None = None
self.update_available = False
self.alert_count = 0

View File

@@ -145,18 +145,20 @@ class SettingsLayout(Widget):
if panel.instance:
panel.instance.render(content_rect)
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):
self.set_current_panel(panel_type)
return
return True
return False
def set_current_panel(self, panel_type: PanelType):
if panel_type != self._current_panel:

View File

@@ -1,6 +1,5 @@
from enum import IntEnum
import os
import requests
import threading
import time
@@ -30,7 +29,6 @@ class PrimeState:
def __init__(self):
self._params = Params()
self._lock = threading.Lock()
self._session = requests.Session() # reuse session to reduce SSL handshake overhead
self.prime_type: PrimeType = self._load_initial_state()
self._running = False
@@ -52,7 +50,7 @@ class PrimeState:
try:
identity_token = get_token(dongle_id)
response = api_get(f"v1.1/devices/{dongle_id}", timeout=self.API_TIMEOUT, access_token=identity_token, session=self._session)
response = api_get(f"v1.1/devices/{dongle_id}", timeout=self.API_TIMEOUT, access_token=identity_token)
if response.status_code == 200:
data = response.json()
is_paired = data.get("is_paired", False)

View File

@@ -169,8 +169,8 @@ class TrainingGuideDMTutorial(Widget):
def _update_state(self):
super()._update_state()
if device.awake and not ui_state.params.get_bool("IsDriverViewEnabled"):
ui_state.params.put_bool_nonblocking("IsDriverViewEnabled", True)
if device.awake:
ui_state.params.put_bool("IsDriverViewEnabled", True)
sm = ui_state.sm
if sm.recv_frame.get("driverMonitoringState", 0) == 0:
@@ -240,20 +240,19 @@ class TrainingGuideDMTutorial(Widget):
ring_color,
)
if self._dialog._camera_view.frame:
self._back_button.render(rl.Rectangle(
self._rect.x + 8,
self._rect.y + self._rect.height - self._back_button.rect.height,
self._back_button.rect.width,
self._back_button.rect.height,
))
self._back_button.render(rl.Rectangle(
self._rect.x + 8,
self._rect.y + self._rect.height - self._back_button.rect.height,
self._back_button.rect.width,
self._back_button.rect.height,
))
self._good_button.render(rl.Rectangle(
self._rect.x + self._rect.width - self._good_button.rect.width - 8,
self._rect.y + self._rect.height - self._good_button.rect.height,
self._good_button.rect.width,
self._good_button.rect.height,
))
self._good_button.render(rl.Rectangle(
self._rect.x + self._rect.width - self._good_button.rect.width - 8,
self._rect.y + self._rect.height - self._good_button.rect.height,
self._good_button.rect.width,
self._good_button.rect.height,
))
# rounded border
rl.draw_rectangle_rounded_lines_ex(self._rect, 0.2 * 1.02, 10, 50, rl.BLACK)

View File

@@ -1,4 +1,3 @@
import requests
import threading
import time
import pyray as rl
@@ -45,7 +44,6 @@ class FirehoseLayoutBase(Widget):
def __init__(self):
super().__init__()
self._params = Params()
self._session = requests.Session() # reuse session to reduce SSL handshake overhead
self._segment_count = self._get_segment_count()
self._scroll_panel = GuiScrollPanel2(horizontal=False)
@@ -205,7 +203,7 @@ class FirehoseLayoutBase(Widget):
if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID:
return
identity_token = get_token(dongle_id)
response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token, session=self._session)
response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token)
if response.status_code == 200:
data = response.json()
self._segment_count = data.get("firehose", 0)

View File

@@ -36,6 +36,8 @@ class WifiIcon(Widget):
super().__init__()
self.set_rect(rl.Rectangle(0, 0, 89, 64))
self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 89, 64)
self._wifi_none_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_none.png", 89, 64)
self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 89, 64)
self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 89, 64)
self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 89, 64)
@@ -55,13 +57,17 @@ class WifiIcon(Widget):
return
# Determine which wifi strength icon to use
strength = round(self._network.strength / 100 * 2)
if strength == 2:
strength = round(self._network.strength / 100 * 4)
if strength == 4:
strength_icon = self._wifi_full_txt
elif strength == 1:
elif strength == 3:
strength_icon = self._wifi_medium_txt
else:
elif strength == 2:
strength_icon = self._wifi_low_txt
elif self._network.strength < 0:
strength_icon = self._wifi_slash_txt
else:
strength_icon = self._wifi_none_txt
icon_x = int(self._rect.x + (self._rect.width - strength_icon.width * self._scale) // 2)
icon_y = int(self._rect.y + (self._rect.height - strength_icon.height * self._scale) // 2)
@@ -382,7 +388,7 @@ class WifiUIMici(BigMultiOptionDialog):
else:
network_button = WifiItem(network)
self._scroller.add_widget(network_button)
self.add_button(network_button)
# remove networks no longer present
self._scroller._items[:] = [btn for btn in self._scroller._items if btn.option in self._networks]
@@ -396,10 +402,11 @@ class WifiUIMici(BigMultiOptionDialog):
self._wifi_manager.connect_to_network(ssid, password)
self._update_buttons()
def _on_option_selected(self, option: str):
super()._on_option_selected(option)
def _on_option_selected(self, option: str, smooth_scroll: bool = True):
super()._on_option_selected(option, smooth_scroll)
if option in self._networks:
# only open if button is already selected
if option in self._networks and option == self._selected_option:
self._network_info_page.set_current_network(self._networks[option])
self._open_network_manage_page()
@@ -446,7 +453,7 @@ class WifiUIMici(BigMultiOptionDialog):
current_selection = self.get_selected_option()
if self._restore_selection and current_selection in self._networks:
self._scroller._layout()
BigMultiOptionDialog._on_option_selected(self, current_selection)
BigMultiOptionDialog._on_option_selected(self, current_selection, smooth_scroll=False)
self._restore_selection = None
super()._render(_)

View File

@@ -222,9 +222,6 @@ class AlertRenderer(Widget):
self._alert_y_filter.update(self._rect.y - 50 if alert is None else self._rect.y)
self._alpha_filter.update(0 if alert is None else 1)
if gui_app.sunnypilot_ui():
ui_state.onroad_brightness_handle_alerts(ui_state.started, alert)
if alert is None:
# If still animating out, keep the previous alert
if self._alpha_filter.x > 0.01 and self._prev_alert is not None:

View File

@@ -19,9 +19,6 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCamera
from openpilot.common.transformations.orientation import rot_from_euler
from enum import IntEnum
if gui_app.sunnypilot_ui():
from openpilot.selfdrive.ui.sunnypilot.ui_state import OnroadTimerStatus
OpState = log.SelfdriveState.OpenpilotState
CALIBRATED = log.LiveCalibrationData.Status.calibrated
ROAD_CAM = VisionStreamType.VISION_STREAM_ROAD
@@ -354,14 +351,6 @@ class AugmentedRoadView(CameraView):
return self._cached_matrix
def show_event(self):
if gui_app.sunnypilot_ui():
ui_state.reset_onroad_sleep_timer(OnroadTimerStatus.RESUME)
def hide_event(self):
if gui_app.sunnypilot_ui():
ui_state.reset_onroad_sleep_timer(OnroadTimerStatus.PAUSE)
if __name__ == "__main__":
gui_app.init_window("OnRoad Camera View")

View File

@@ -121,17 +121,12 @@ class HudRenderer(Widget):
self._txt_wheel: rl.Texture = gui_app.texture('icons_mici/wheel.png', 50, 50)
self._txt_wheel_critical: rl.Texture = gui_app.texture('icons_mici/wheel_critical.png', 50, 50)
self._txt_exclamation_point: rl.Texture = gui_app.texture('icons_mici/exclamation_point.png', 44, 44)
self._txt_blind_spot_left: rl.Texture = gui_app.texture('icons_mici/onroad/blind_spot_left.png', 108, 128)
self._txt_blind_spot_right: rl.Texture = gui_app.texture('icons_mici/onroad/blind_spot_right.png', 108, 128)
self._wheel_alpha_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps)
self._wheel_y_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps)
self._set_speed_alpha_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps)
self._blind_spot_left_alpha_filter = FirstOrderFilter(0, 0.15, 1 / gui_app.target_fps)
self._blind_spot_right_alpha_filter = FirstOrderFilter(0, 0.15, 1 / gui_app.target_fps)
def set_wheel_critical_icon(self, critical: bool):
"""Set the wheel icon to critical or normal state."""
self._show_wheel_critical = critical
@@ -180,79 +175,19 @@ class HudRenderer(Widget):
if ui_state.sm['controlsState'].lateralControlState.which() != 'angleState':
self._torque_bar.render(rect)
draw_set_speed = False
if self.is_cruise_set:
alpha = self._set_speed_alpha_filter.update(
0 < rl.get_time() - self._set_speed_changed_time < SET_SPEED_PERSISTENCE and
self._can_draw_top_icons and self._engaged
)
draw_set_speed = alpha >= 1e-2
if draw_set_speed:
self._draw_set_speed(rect)
self._draw_steering_wheel(rect)
self._draw_blind_spot_indicators(rect)
def _draw_blind_spot_indicators(self, rect: rl.Rectangle) -> None:
sm = ui_state.sm
car_state = sm['carState']
try:
left_detected = car_state.leftBlindspot
except AttributeError:
left_detected = False
try:
right_detected = car_state.rightBlindspot
except AttributeError:
right_detected = False
self._blind_spot_left_alpha_filter.update(1.0 if left_detected else 0.0)
self._blind_spot_right_alpha_filter.update(1.0 if right_detected else 0.0)
BLIND_SPOT_MARGIN_X = 20 # Distance from edge of screen
BLIND_SPOT_Y_OFFSET = 100 # Distance from top of screen
if self._blind_spot_left_alpha_filter.x > 0.01:
pos_x = int(rect.x + BLIND_SPOT_MARGIN_X)
pos_y = int(rect.y + BLIND_SPOT_Y_OFFSET)
alpha = int(255 * self._blind_spot_left_alpha_filter.x)
color = rl.Color(255, 255, 255, alpha)
rl.draw_texture(self._txt_blind_spot_left, pos_x, pos_y, color)
if self._blind_spot_right_alpha_filter.x > 0.01:
pos_x = int(rect.x + rect.width - BLIND_SPOT_MARGIN_X - self._txt_blind_spot_right.width)
pos_y = int(rect.y + BLIND_SPOT_Y_OFFSET)
alpha = int(255 * self._blind_spot_right_alpha_filter.x)
color = rl.Color(255, 255, 255, alpha)
rl.draw_texture(self._txt_blind_spot_right, pos_x, pos_y, color)
def _has_blind_spot_detected(self) -> bool:
car_state = ui_state.sm['carState']
try:
left_detected = car_state.leftBlindspot
except AttributeError:
left_detected = False
try:
right_detected = car_state.rightBlindspot
except AttributeError:
right_detected = False
return left_detected or right_detected
def _draw_steering_wheel(self, rect: rl.Rectangle) -> None:
wheel_txt = self._txt_wheel_critical if self._show_wheel_critical else self._txt_wheel
has_blind_spot = self._has_blind_spot_detected()
if self._show_wheel_critical:
self._wheel_alpha_filter.update(255)
self._wheel_y_filter.update(0)
else:
if ui_state.status == UIStatus.DISENGAGED or has_blind_spot:
if ui_state.status == UIStatus.DISENGAGED:
self._wheel_alpha_filter.update(0)
self._wheel_y_filter.update(wheel_txt.height / 2)
else:

View File

@@ -1,3 +1,9 @@
"""
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
from dataclasses import dataclass
from openpilot.common.constants import CV
@@ -68,9 +74,6 @@ class SpeedLimitRenderer(Widget):
def _render(self, rect: rl.Rectangle) -> None:
self._update_state()
if not self.speed_limit_valid:
return
sign_width = SPEED_LIMIT_WIDTH_METRIC if ui_state.is_metric else SPEED_LIMIT_WIDTH_IMPERIAL
sign_height = SPEED_LIMIT_HEIGHT
sign_margin = 12
@@ -79,18 +82,19 @@ class SpeedLimitRenderer(Widget):
sign_rect = rl.Rectangle(sign_x, sign_y, sign_width, sign_height)
speed_str = str(round(self.speed_limit))
speed_color = COLORS.white if self.current_speed <= self.speed_limit else COLORS.red
speed_str = str(round(self.speed_limit)) if self.speed_limit_valid else "--"
speed_color = COLORS.white if not self.speed_limit_valid or self.current_speed <= self.speed_limit else COLORS.red
if ui_state.is_metric:
self._draw_vienna_sign(sign_rect, speed_str, "", speed_color, True)
self._draw_vienna_sign(sign_rect, speed_str, "", speed_color, self.speed_limit_valid)
else:
self._draw_mutcd_sign(sign_rect, speed_str, "", speed_color, True)
self._draw_mutcd_sign(sign_rect, speed_str, "", speed_color, self.speed_limit_valid)
if self.next_speed_limit > 0 and self.next_speed_limit != self.speed_limit:
self._draw_upcoming_speed_limit(sign_rect)
def _draw_vienna_sign(self, sign_rect: rl.Rectangle, speed_str: str, sub_text: str, speed_color: rl.Color, has_speed_limit: bool) -> None:
"""Draw EU/Vienna sign."""
"""Draw EU/Vienna"""
center = rl.Vector2(sign_rect.x + sign_rect.width / 2, sign_rect.y + sign_rect.height / 2)
outer_radius = min(sign_rect.width, sign_rect.height) / 2
circle_size = outer_radius * 2
@@ -133,7 +137,7 @@ class SpeedLimitRenderer(Widget):
rl.draw_text_ex(self._font_bold, speed_str, speed_value_pos, font_size, 0, speed_color)
def _draw_upcoming_speed_limit(self, sign_rect: rl.Rectangle) -> None:
"""Draw upcoming speed limit."""
"""Draw upcoming speed limit indicator."""
speed_str = str(round(self.next_speed_limit))
distance_str = self._format_distance(self.next_speed_limit_distance)

View File

@@ -71,7 +71,7 @@ class BigCircleButton(Widget):
class BigCircleToggle(BigCircleButton):
def __init__(self, icon: str, toggle_callback: Callable | None = None):
def __init__(self, icon: str, toggle_callback: Callable = None):
super().__init__(icon, False)
self._toggle_callback = toggle_callback
@@ -251,7 +251,7 @@ class BigButton(Widget):
class BigToggle(BigButton):
def __init__(self, text: str, value: str = "", initial_state: bool = False, toggle_callback: Callable | None = None):
def __init__(self, text: str, value: str = "", initial_state: bool = False, toggle_callback: Callable = None):
super().__init__(text, value, "")
self._checked = initial_state
self._toggle_callback = toggle_callback
@@ -288,8 +288,8 @@ class BigToggle(BigButton):
class BigMultiToggle(BigToggle):
def __init__(self, text: str, options: list[str], toggle_callback: Callable | None = None,
select_callback: Callable | None = None):
def __init__(self, text: str, options: list[str], toggle_callback: Callable = None,
select_callback: Callable = None):
super().__init__(text, "", toggle_callback=toggle_callback)
assert len(options) > 0
self._options = options
@@ -327,8 +327,8 @@ class BigMultiToggle(BigToggle):
class BigMultiParamToggle(BigMultiToggle):
def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable | None = None,
select_callback: Callable | None = None):
def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable = None,
select_callback: Callable = None):
super().__init__(text, options, toggle_callback, select_callback)
self._param = param
@@ -345,7 +345,7 @@ class BigMultiParamToggle(BigMultiToggle):
class BigParamControl(BigToggle):
def __init__(self, text: str, param: str, toggle_callback: Callable | None = None):
def __init__(self, text: str, param: str, toggle_callback: Callable = None):
super().__init__(text, "", toggle_callback=toggle_callback)
self.param = param
self.params = Params()
@@ -361,7 +361,7 @@ class BigParamControl(BigToggle):
# TODO: param control base class
class BigCircleParamControl(BigCircleToggle):
def __init__(self, icon: str, param: str, toggle_callback: Callable | None = None):
def __init__(self, icon: str, param: str, toggle_callback: Callable = None):
super().__init__(icon, toggle_callback)
self._param = param
self.params = Params()

View File

@@ -4,17 +4,17 @@ import pyray as rl
from typing import Union
from collections.abc import Callable
from typing import cast
from openpilot.selfdrive.ui.mici.widgets.side_button import SideButton
from openpilot.system.ui.widgets import Widget, NavWidget, DialogResult
from openpilot.system.ui.widgets.label import UnifiedLabel, gui_label
from openpilot.system.ui.widgets.mici_keyboard import MiciKeyboard
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.lib.wrap_text import wrap_text
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, MouseEvent
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
from openpilot.system.ui.widgets.scroller import Scroller
from openpilot.system.ui.widgets.slider import RedBigSlider, BigSlider
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.selfdrive.ui.mici.widgets.button import BigButton
from openpilot.selfdrive.ui.mici.widgets.side_button import SideButton
DEBUG = False
@@ -137,7 +137,7 @@ class BigInputDialog(BigDialogBase):
hint: str,
default_text: str = "",
minimum_length: int = 1,
confirm_callback: Callable[[str], None] | None = None):
confirm_callback: Callable[[str], None] = None):
super().__init__(None, None)
self._hint_label = UnifiedLabel(hint, font_size=35, text_color=rl.Color(255, 255, 255, int(255 * 0.35)),
font_weight=FontWeight.MEDIUM)
@@ -317,36 +317,39 @@ class BigMultiOptionDialog(BigDialogBase):
BACK_TOUCH_AREA_PERCENTAGE = 0.1
def __init__(self, options: list[str], default: str | None,
right_btn: str | None = 'check', right_btn_callback: Callable[[], None] | None = None):
right_btn: str | None = 'check', right_btn_callback: Callable[[], None] = None):
super().__init__(right_btn, right_btn_callback=right_btn_callback)
self._options = options
if default is not None:
assert default in options
self._default_option: str | None = default
self._selected_option: str = self._default_option or (options[0] if len(options) > 0 else "")
self._default_option: str = default or (options[0] if len(options) > 0 else "")
self._selected_option: str = self._default_option
self._last_selected_option: str = self._selected_option
# Widget doesn't differentiate between click and drag
self._can_click = True
self._scroller = Scroller([], horizontal=False, pad_start=100, pad_end=100, spacing=0, snap_items=True)
if self._right_btn is not None:
self._scroller.set_enabled(lambda: not cast(Widget, self._right_btn).is_pressed)
for option in options:
self._scroller.add_widget(BigDialogOptionButton(option))
self.add_button(BigDialogOptionButton(option))
def add_button(self, button: BigDialogOptionButton):
def click_callback(_btn=button):
self._on_option_selected(_btn.option)
button.set_click_callback(click_callback)
self._scroller.add_widget(button)
def show_event(self):
super().show_event()
self._scroller.show_event()
if self._default_option is not None:
self._on_option_selected(self._default_option)
self._on_option_selected(self._default_option)
def get_selected_option(self) -> str:
return self._selected_option
def _on_option_selected(self, option: str):
def _on_option_selected(self, option: str, smooth_scroll: bool = True):
y_pos = 0.0
for btn in self._scroller._items:
btn = cast(BigDialogOptionButton, btn)
@@ -362,35 +365,11 @@ class BigMultiOptionDialog(BigDialogBase):
y_pos = rect_center_y - (btn.rect.y + height / 2)
break
self._scroller.scroll_to(-y_pos)
self._scroller.scroll_to(-y_pos, smooth=smooth_scroll)
def _selected_option_changed(self):
pass
def _handle_mouse_press(self, mouse_pos: MousePos):
super()._handle_mouse_press(mouse_pos)
self._can_click = True
def _handle_mouse_event(self, mouse_event: MouseEvent) -> None:
super()._handle_mouse_event(mouse_event)
# # TODO: add generic _handle_mouse_click handler to Widget
if not self._scroller.scroll_panel.is_touch_valid():
self._can_click = False
def _handle_mouse_release(self, mouse_pos: MousePos):
super()._handle_mouse_release(mouse_pos)
if not self._can_click:
return
# select current option
for btn in self._scroller._items:
btn = cast(BigDialogOptionButton, btn)
if btn.option == self._selected_option:
self._on_option_selected(btn.option)
break
def _update_state(self):
super()._update_state()

View File

@@ -116,10 +116,6 @@ class AlertRenderer(Widget):
def _render(self, rect: rl.Rectangle):
alert = self.get_alert(ui_state.sm)
if gui_app.sunnypilot_ui():
ui_state.onroad_brightness_handle_alerts(ui_state.started, alert)
if not alert:
return

View File

@@ -15,10 +15,10 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCamera
from openpilot.common.transformations.orientation import rot_from_euler
if gui_app.sunnypilot_ui():
from openpilot.selfdrive.ui.sunnypilot.onroad.augmented_road_view import BORDER_COLORS_SP
from openpilot.selfdrive.ui.sunnypilot.onroad.driver_state import DriverStateRendererSP as DriverStateRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.hud_renderer import HudRendererSP as HudRenderer
from openpilot.selfdrive.ui.sunnypilot.ui_state import OnroadTimerStatus
from openpilot.selfdrive.ui.sunnypilot.onroad.driver_state import DriverStateRendererSP as DriverStateRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.augmented_road_view import BORDER_COLORS_SP
OpState = log.SelfdriveState.OpenpilotState
CALIBRATED = log.LiveCalibrationData.Status.calibrated
@@ -224,14 +224,6 @@ class AugmentedRoadView(CameraView):
return self._cached_matrix
def show_event(self):
if gui_app.sunnypilot_ui():
ui_state.reset_onroad_sleep_timer(OnroadTimerStatus.RESUME)
def hide_event(self):
if gui_app.sunnypilot_ui():
ui_state.reset_onroad_sleep_timer(OnroadTimerStatus.PAUSE)
if __name__ == "__main__":
gui_app.init_window("OnRoad Camera View")

View File

@@ -27,7 +27,7 @@ class SpeedLimitColors:
grey: rl.Color = rl.Color(145, 155, 149, 241)
light_grey: rl.Color = rl.Color(200, 200, 200, 255)
dark_grey: rl.Color = rl.Color(100, 100, 100, 255)
bg_dark: rl.Color = rl.Color(20, 20, 20, 230)
bg_dark: rl.Color = rl.Color(30, 30, 30, 200)
card_bg: rl.Color = rl.Color(50, 50, 50, 200)
@@ -60,7 +60,6 @@ class SpeedLimitRenderer(Widget):
return
speed_conv = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH
if sm.valid["mapdOut"]:
mapd = sm["mapdOut"]
self.speed_limit = mapd.speedLimit * speed_conv
@@ -89,52 +88,52 @@ class SpeedLimitRenderer(Widget):
def _render(self, rect: rl.Rectangle) -> None:
self._update_state()
panel_width = 420
panel_height = 280
panel_margin = 20
panel_x = rect.x + panel_margin
panel_y = rect.y + (rect.height - panel_height) / 2
panel_width = 320
panel_height = 340
panel_x = rect.x + 12
panel_y = rect.y + rect.height / 2 - panel_height / 2
panel_rect = rl.Rectangle(panel_x, panel_y, panel_width, panel_height)
rl.draw_rectangle_rounded(panel_rect, 0.08, 10, COLORS.bg_dark)
rl.draw_rectangle_rounded_lines_ex(panel_rect, 0.08, 10, 2, rl.Color(80, 80, 80, 200))
rl.draw_rectangle_rounded(panel_rect, 0.1, 10, COLORS.bg_dark)
padding = 20
sign_width = 140 if ui_state.is_metric else 130
sign_height = 150 if ui_state.is_metric else 140
sign_x = panel_x + padding
sign_y = panel_y + padding
margin = 15
sign_width = 110 if ui_state.is_metric else 100
sign_height = 120 if ui_state.is_metric else 110
sign_x = panel_x + margin
sign_y = panel_y + margin
self._draw_speed_limit_sign(sign_x, sign_y, sign_width, sign_height)
info_x = sign_x + sign_width + 20
info_width = panel_width - sign_width - padding * 2 - 20
info_y = panel_y + padding + 5
info_x = sign_x + sign_width + 15
info_width = panel_width - sign_width - margin * 2 - 15
info_y = panel_y + 20
# Road name
self._draw_road_name(info_x, info_y, info_width)
info_y += 50
info_y += 40
# Road context and lanes
self._draw_road_details(info_x, info_y, info_width)
info_y += 40
info_y += 35
# Next speed limit
self._draw_next_speed_limit(info_x, info_y, info_width)
info_y += 40
info_y = panel_y + sign_height + margin + 25
# Curve speeds
self._draw_curve_speeds(info_x, info_y, info_width)
self._draw_curve_speeds(panel_x + margin, info_y, panel_width - margin * 2)
info_y += 35
# Hazard warning beta
if self.hazard:
hazard_y = panel_y + panel_height - 60
self._draw_hazard(panel_x + padding, hazard_y, panel_width - padding * 2)
self._draw_hazard(panel_x + margin, info_y, panel_width - margin * 2)
info_y += 35
status_text = tr("Map Loaded") if self.tile_loaded else tr("No Map Data")
status_color = COLORS.green if self.tile_loaded else COLORS.red
status_size = measure_text_cached(self._font_medium, status_text, 22)
status_pos = rl.Vector2(panel_x + (panel_width - status_size.x) / 2, panel_y + panel_height - 30)
rl.draw_text_ex(self._font_medium, status_text, status_pos, 22, 0, status_color)
status_size = measure_text_cached(self._font_medium, status_text, 18)
status_pos = rl.Vector2(panel_x + (panel_width - status_size.x) / 2, panel_y + panel_height - 28)
rl.draw_text_ex(self._font_medium, status_text, status_pos, 18, 0, status_color)
def _draw_speed_limit_sign(self, x: float, y: float, width: float, height: float) -> None:
speed_str = str(round(self.speed_limit)) if self.speed_limit_valid else "--"
@@ -153,7 +152,7 @@ class SpeedLimitRenderer(Widget):
ring_width = outer_radius * 0.18
rl.draw_ring(center, outer_radius - ring_width, outer_radius, 0, 360, 36, COLORS.red)
font_size = outer_radius * (0.75 if len(speed_str) >= 3 else 0.95)
font_size = outer_radius * (0.7 if len(speed_str) >= 3 else 0.9)
text_size = measure_text_cached(self._font_bold, speed_str, int(font_size))
text_pos = rl.Vector2(center.x - text_size.x / 2, center.y - text_size.y / 2)
rl.draw_text_ex(self._font_bold, speed_str, text_pos, font_size, 0, speed_color)
@@ -161,34 +160,31 @@ class SpeedLimitRenderer(Widget):
def _draw_mutcd_sign(self, x: float, y: float, width: float, height: float, speed_str: str, speed_color: rl.Color) -> None:
sign_rect = rl.Rectangle(x, y, width, height)
rl.draw_rectangle_rounded(sign_rect, 0.2, 10, COLORS.white)
rl.draw_rectangle_rounded_lines_ex(sign_rect, 0.2, 10, 4, COLORS.black)
rl.draw_rectangle_rounded_lines_ex(sign_rect, 0.2, 10, 3, COLORS.black)
speed_label = tr("SPEED")
limit_label = tr("LIMIT")
label_size = 22
label_size = 16
speed_text_size = measure_text_cached(self._font_semi_bold, speed_label, label_size)
speed_pos = rl.Vector2(x + (width - speed_text_size.x) / 2, y + 12)
speed_pos = rl.Vector2(x + (width - speed_text_size.x) / 2, y + 8)
rl.draw_text_ex(self._font_semi_bold, speed_label, speed_pos, label_size, 0, COLORS.black)
limit_text_size = measure_text_cached(self._font_semi_bold, limit_label, label_size)
limit_pos = rl.Vector2(x + (width - limit_text_size.x) / 2, y + 36)
limit_pos = rl.Vector2(x + (width - limit_text_size.x) / 2, y + 26)
rl.draw_text_ex(self._font_semi_bold, limit_label, limit_pos, label_size, 0, COLORS.black)
font_size = 55 if len(speed_str) >= 3 else 70
font_size = 40 if len(speed_str) >= 3 else 50
num_size = measure_text_cached(self._font_bold, speed_str, font_size)
num_pos = rl.Vector2(x + (width - num_size.x) / 2, y + 65)
num_pos = rl.Vector2(x + (width - num_size.x) / 2, y + 45)
rl.draw_text_ex(self._font_bold, speed_str, num_pos, font_size, 0, speed_color)
def _draw_road_name(self, x: float, y: float, width: float) -> None:
road_display = self.road_name if self.road_name else "--"
font_size = 32
road_size = measure_text_cached(self._font_semi_bold, road_display, font_size)
road_size = measure_text_cached(self._font_semi_bold, road_display, 26)
if road_size.x > width:
while len(road_display) > 3 and measure_text_cached(self._font_semi_bold, road_display + "...", font_size).x > width:
road_display = road_display[:-1]
road_display = road_display + "..."
rl.draw_text_ex(self._font_semi_bold, road_display, rl.Vector2(x, y), font_size, 0, COLORS.white)
road_display = road_display[:15] + "..."
rl.draw_text_ex(self._font_semi_bold, road_display, rl.Vector2(x, y), 26, 0, COLORS.white)
def _draw_road_details(self, x: float, y: float, width: float) -> None:
details = []
@@ -197,25 +193,25 @@ class SpeedLimitRenderer(Widget):
if self.lanes > 0:
details.append(f"{self.lanes} {tr('lanes')}")
details_text = "".join(details) if details else "--"
rl.draw_text_ex(self._font_medium, details_text, rl.Vector2(x, y), 26, 0, COLORS.light_grey)
rl.draw_text_ex(self._font_medium, details_text, rl.Vector2(x, y), 20, 0, COLORS.light_grey)
def _draw_next_speed_limit(self, x: float, y: float, width: float) -> None:
if self.next_speed_limit > 0 and self.next_speed_limit != self.speed_limit:
next_text = f"{tr('Next')}: {round(self.next_speed_limit)} ({self._format_distance(self.next_speed_limit_distance)})"
else:
next_text = f"{tr('Next')}: --"
rl.draw_text_ex(self._font_medium, next_text, rl.Vector2(x, y), 26, 0, COLORS.light_grey)
rl.draw_text_ex(self._font_medium, next_text, rl.Vector2(x, y), 20, 0, COLORS.light_grey)
def _draw_curve_speeds(self, x: float, y: float, width: float) -> None:
unit = tr("km/h") if ui_state.is_metric else tr("mph")
vision_val = str(round(self.vision_curve_speed)) if self.vision_curve_speed > 0 else "--"
map_val = str(round(self.map_curve_speed)) if self.map_curve_speed > 0 else "--"
curve_text = f"{tr('Curve')}: V:{vision_val} M:{map_val} {unit}"
rl.draw_text_ex(self._font_medium, curve_text, rl.Vector2(x, y), 24, 0, COLORS.light_grey)
rl.draw_text_ex(self._font_medium, curve_text, rl.Vector2(x, y), 20, 0, COLORS.light_grey)
def _draw_hazard(self, x: float, y: float, width: float) -> None:
hazard_text = f"{self.hazard}"
rl.draw_text_ex(self._font_semi_bold, hazard_text, rl.Vector2(x, y), 28, 0, COLORS.red)
rl.draw_text_ex(self._font_semi_bold, hazard_text, rl.Vector2(x, y), 22, 0, COLORS.red)
def _format_distance(self, distance: float) -> str:
if ui_state.is_metric:

View File

@@ -4,21 +4,9 @@ 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.
"""
from enum import IntEnum
from openpilot.common.params import Params
from openpilot.system.ui.sunnypilot.widgets.option_control import OptionControlSP
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.sunnypilot.widgets.list_view import option_item_sp, ToggleActionSP
ONROAD_BRIGHTNESS_TIMER_VALUES = {0: 15, 1: 30, **{i: (i - 1) * 60 for i in range(2, 12)}}
class OnroadBrightness(IntEnum):
AUTO = 0
AUTO_DARK = 1
from openpilot.system.ui.widgets import Widget
class DisplayLayout(Widget):
@@ -30,69 +18,11 @@ class DisplayLayout(Widget):
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _initialize_items(self):
self._onroad_brightness = option_item_sp(
param="OnroadScreenOffBrightness",
title=lambda: tr("Onroad Brightness"),
description="",
min_value=0,
max_value=21,
value_change_step=1,
label_callback=lambda value: self.update_onroad_brightness(value),
inline=True
)
self._onroad_brightness_timer = option_item_sp(
param="OnroadScreenOffTimer",
title=lambda: tr("Onroad Brightness Delay"),
description="",
min_value=0,
max_value=11,
value_change_step=1,
value_map=ONROAD_BRIGHTNESS_TIMER_VALUES,
label_callback=lambda value: f"{value} s" if value < 60 else f"{int(value/60)} m",
inline=True
)
self._interactivity_timeout = option_item_sp(
param="InteractivityTimeout",
title=lambda: tr("Interactivity Timeout"),
description=lambda: tr("Apply a custom timeout for settings UI." +
"<br>This is the time after which settings UI closes automatically " +
"if user is not interacting with the screen."),
min_value=0,
max_value=120,
value_change_step=10,
label_callback=lambda value: (tr("Default") if not value or value == 0 else
f"{value} s" if value < 60 else f"{int(value/60)} m"),
inline=True
)
items = [
self._onroad_brightness,
self._onroad_brightness_timer,
self._interactivity_timeout,
]
return items
@staticmethod
def update_onroad_brightness(val):
if val == OnroadBrightness.AUTO:
return tr("Auto (Default)")
if val == OnroadBrightness.AUTO_DARK:
return tr("Auto (Dark)")
return f"{(val - 1) * 5} %"
def _update_state(self):
super()._update_state()
for _item in self._scroller._items:
if isinstance(_item.action_item, ToggleActionSP) and _item.action_item.toggle.param_key is not None:
_item.action_item.set_state(self._params.get_bool(_item.action_item.toggle.param_key))
elif isinstance(_item.action_item, OptionControlSP) and _item.action_item.param_key is not None:
_item.action_item.set_value(self._params.get(_item.action_item.param_key, return_default=True))
brightness_val = self._params.get("OnroadScreenOffBrightness", return_default=True)
self._onroad_brightness_timer.action_item.set_enabled(brightness_val not in (OnroadBrightness.AUTO, OnroadBrightness.AUTO_DARK))
def _render(self, rect):
self._scroller.render(rect)

View File

@@ -4,6 +4,7 @@ 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.
"""
from dataclasses import dataclass
from enum import IntEnum
@@ -36,9 +37,10 @@ from openpilot.system.ui.widgets.scroller_tici import Scroller
OP.PANEL_COLOR = rl.Color(10, 10, 10, 255)
ICON_SIZE = 70
OP.PanelType = IntEnum(
OP.PanelType = IntEnum( # type: ignore
"PanelType",
[es.name for es in OP.PanelType] + [
[es.name for es in OP.PanelType]
+ [
"SUNNYLINK",
"MODELS",
"STEERING",
@@ -73,22 +75,16 @@ class NavButton(Widget):
# Draw background if selected
if is_selected:
self.container_rect = rl.Rectangle(
content_x - 50, rect.y, OP.SIDEBAR_WIDTH - 50, OP.NAV_BTN_HEIGHT
)
self.container_rect = rl.Rectangle(content_x - 50, rect.y, OP.SIDEBAR_WIDTH - 50, OP.NAV_BTN_HEIGHT)
rl.draw_rectangle_rounded(self.container_rect, 0.2, 5, OP.CLOSE_BTN_COLOR)
if self.panel_info.icon:
icon_texture = gui_app.texture(self.panel_info.icon, ICON_SIZE, ICON_SIZE, keep_aspect_ratio=True)
rl.draw_texture(icon_texture, int(content_x), int(rect.y + (OP.NAV_BTN_HEIGHT - icon_texture.height) / 2),
rl.WHITE)
rl.draw_texture(icon_texture, int(content_x), int(rect.y + (OP.NAV_BTN_HEIGHT - icon_texture.height) / 2), rl.WHITE)
content_x += ICON_SIZE + 20
# Draw button text (right-aligned)
text_pos = rl.Vector2(
content_x,
rect.y + (OP.NAV_BTN_HEIGHT - text_size.y) / 2
)
text_pos = rl.Vector2(content_x, rect.y + (OP.NAV_BTN_HEIGHT - text_size.y) / 2)
rl.draw_text_ex(self.parent._font_medium, self.panel_info.name, text_pos, 55, 0, text_color)
# Store button rect for click detection
@@ -129,12 +125,9 @@ class SettingsLayoutSP(OP.SettingsLayout):
rl.draw_rectangle_rec(rect, OP.SIDEBAR_COLOR)
# Close button
close_btn_rect = rl.Rectangle(
rect.x + style.ITEM_PADDING * 3, rect.y + style.ITEM_PADDING * 2, style.CLOSE_BTN_SIZE, style.CLOSE_BTN_SIZE
)
close_btn_rect = rl.Rectangle(rect.x + style.ITEM_PADDING * 3, rect.y + style.ITEM_PADDING * 2, style.CLOSE_BTN_SIZE, style.CLOSE_BTN_SIZE)
pressed = (rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and
rl.check_collision_point_rec(rl.get_mouse_position(), close_btn_rect))
pressed = rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and rl.check_collision_point_rec(rl.get_mouse_position(), close_btn_rect)
close_color = OP.CLOSE_BTN_PRESSED if pressed else OP.CLOSE_BTN_COLOR
rl.draw_rectangle_rounded(close_btn_rect, 1.0, 20, close_color)
@@ -171,7 +164,7 @@ class SettingsLayoutSP(OP.SettingsLayout):
rect.x,
self._close_btn_rect.height + style.ITEM_PADDING * 4, # Starting Y position for nav items
rect.width,
rect.height - 300 # Remaining height after close button
rect.height - 300, # Remaining height after close button
)
if self._nav_items:

View File

@@ -12,7 +12,7 @@ from openpilot.selfdrive.ui.sunnypilot.mici.layouts.sunnylink import SunnylinkLa
ICON_SIZE = 70
OP.PanelType = IntEnum(
OP.PanelType = IntEnum( # type: ignore
"PanelType",
[es.name for es in OP.PanelType] + [
"SUNNYLINK",

View File

@@ -18,12 +18,11 @@ class RainbowPath:
BASE_ALPHA = 0.8
ALPHA_FADE = 0.3 # Alpha reduction from bottom to top
def __init__(self, num_segments: int = DEFAULT_NUM_SEGMENTS, speed: float = DEFAULT_SPEED,
saturation: float = DEFAULT_SATURATION, lightness: float = DEFAULT_LIGHTNESS):
self.num_segments = num_segments
self.speed = speed
self.saturation = saturation
self.lightness = lightness
def __init__(self, num_segments: int = None, speed: float = None, saturation: float = None, lightness: float = None):
self.num_segments = num_segments if num_segments is not None else self.DEFAULT_NUM_SEGMENTS
self.speed = speed if speed is not None else self.DEFAULT_SPEED
self.saturation = saturation if saturation is not None else self.DEFAULT_SATURATION
self.lightness = lightness if lightness is not None else self.DEFAULT_LIGHTNESS
def set_speed(self, speed: float):
self.speed = speed

View File

@@ -4,25 +4,13 @@ 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.
"""
from enum import Enum
from cereal import messaging, log, custom
from openpilot.common.params import Params
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.display import OnroadBrightness
from openpilot.sunnypilot.sunnylink.sunnylink_state import SunnylinkState
from openpilot.system.ui.lib.application import gui_app
OpenpilotState = log.SelfdriveState.OpenpilotState
MADSState = custom.ModularAssistiveDrivingSystem.ModularAssistiveDrivingSystemState
ONROAD_BRIGHTNESS_TIMER_PAUSED = -1
class OnroadTimerStatus(Enum):
NONE = 0
PAUSE = 1
RESUME = 2
class UIStateSP:
def __init__(self):
@@ -34,11 +22,8 @@ class UIStateSP:
]
self.sunnylink_state = SunnylinkState()
self.update_params()
self.onroad_brightness_timer: int = 0
self.custom_interactive_timeout: int = self.params.get("InteractivityTimeout", return_default=True)
self.reset_onroad_sleep_timer()
def update(self) -> None:
if self.sunnylink_enabled:
@@ -46,40 +31,6 @@ class UIStateSP:
else:
self.sunnylink_state.stop()
def onroad_brightness_handle_alerts(self, started: bool, alert):
has_alert = started and self.onroad_brightness != OnroadBrightness.AUTO and alert is not None
self.update_onroad_brightness(has_alert)
if has_alert:
self.reset_onroad_sleep_timer()
def update_onroad_brightness(self, has_alert: bool) -> None:
if has_alert:
return
if self.onroad_brightness_timer > 0:
self.onroad_brightness_timer -= 1
def reset_onroad_sleep_timer(self, timer_status: OnroadTimerStatus = OnroadTimerStatus.NONE) -> None:
# Toggling from active state to inactive
if timer_status == OnroadTimerStatus.PAUSE and self.onroad_brightness_timer != ONROAD_BRIGHTNESS_TIMER_PAUSED:
self.onroad_brightness_timer = ONROAD_BRIGHTNESS_TIMER_PAUSED
# Toggling from a previously inactive state or resetting an active timer
elif (self.onroad_brightness_timer_param >= 0 and self.onroad_brightness != OnroadBrightness.AUTO and
self.onroad_brightness_timer != ONROAD_BRIGHTNESS_TIMER_PAUSED) or timer_status == OnroadTimerStatus.RESUME:
if self.onroad_brightness == OnroadBrightness.AUTO_DARK:
self.onroad_brightness_timer = 15 * gui_app.target_fps
else:
self.onroad_brightness_timer = self.onroad_brightness_timer_param * gui_app.target_fps
@property
def onroad_brightness_timer_expired(self) -> bool:
return self.onroad_brightness != OnroadBrightness.AUTO and self.onroad_brightness_timer == 0
@property
def auto_onroad_brightness(self) -> bool:
return self.onroad_brightness in (OnroadBrightness.AUTO, OnroadBrightness.AUTO_DARK)
@staticmethod
def update_status(ss, ss_sp, onroad_evt) -> str:
state = ss.state
@@ -128,10 +79,6 @@ class UIStateSP:
self.active_bundle = self.params.get("ModelManager_ActiveBundle")
self.custom_interactive_timeout = self.params.get("InteractivityTimeout", return_default=True)
# Onroad Screen Brightness
self.onroad_brightness = int(float(self.params.get("OnroadScreenOffBrightness", return_default=True)))
self.onroad_brightness_timer_param = self.params.get("OnroadScreenOffTimer", return_default=True)
class DeviceSP:
def __init__(self):
@@ -140,38 +87,3 @@ class DeviceSP:
def _set_awake(self, on: bool):
if on and self._params.get("DeviceBootMode", return_default=True) == 1:
self._params.put_bool("OffroadMode", True)
@staticmethod
def set_onroad_brightness(_ui_state, awake: bool, cur_brightness: float) -> float:
if not awake or not _ui_state.started:
return cur_brightness
if _ui_state.onroad_brightness_timer != 0:
if _ui_state.onroad_brightness == OnroadBrightness.AUTO_DARK:
return max(30.0, cur_brightness)
# For AUTO (Default) and Manual modes (while timer running), use standard brightness
return cur_brightness
# 0: Auto (Default), 1: Auto (Dark)
if _ui_state.onroad_brightness == OnroadBrightness.AUTO:
return cur_brightness
elif _ui_state.onroad_brightness == OnroadBrightness.AUTO_DARK:
return cur_brightness
# 2-21: 5% - 100%
return float((_ui_state.onroad_brightness - 1) * 5)
@staticmethod
def set_min_onroad_brightness(_ui_state, min_brightness: int) -> int:
if _ui_state.onroad_brightness == OnroadBrightness.AUTO_DARK:
min_brightness = 10
return min_brightness
@staticmethod
def wake_from_dimmed_onroad_brightness(_ui_state, evs) -> None:
if _ui_state.started and (_ui_state.onroad_brightness_timer_expired or _ui_state.onroad_brightness == OnroadBrightness.AUTO_DARK):
if any(ev.left_down for ev in evs):
if _ui_state.onroad_brightness_timer_expired:
gui_app.mouse_events.clear()
_ui_state.reset_onroad_sleep_timer()

View File

@@ -1,3 +0,0 @@
# Multilanguage
[![languages](https://raw.githubusercontent.com/commaai/openpilot/badges/translation_badge.svg)](#)

View File

@@ -200,7 +200,7 @@ msgstr "CONNECT"
#: system/ui/widgets/network.py:369
#, python-format
msgid "CONNECTING..."
msgstr "连接中..."
msgstr "CONNECTING..."
#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35
#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318
@@ -1001,7 +1001,7 @@ msgstr "用于“{}”"
#: selfdrive/ui/onroad/hud_renderer.py:177
#, python-format
msgid "km/h"
msgstr "km/h"
msgstr "公里/时"
#: system/ui/widgets/network.py:204
#, python-format
@@ -1021,7 +1021,7 @@ msgstr "计量"
#: selfdrive/ui/onroad/hud_renderer.py:177
#, python-format
msgid "mph"
msgstr "mph"
msgstr "英里/时"
#: selfdrive/ui/layouts/settings/software.py:20
#, python-format

View File

@@ -200,7 +200,7 @@ msgstr "CONNECT"
#: system/ui/widgets/network.py:369
#, python-format
msgid "CONNECTING..."
msgstr "連線中..."
msgstr "CONNECTING..."
#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35
#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318
@@ -1000,7 +1000,7 @@ msgstr "適用於「{}」"
#: selfdrive/ui/onroad/hud_renderer.py:177
#, python-format
msgid "km/h"
msgstr "km/h"
msgstr "公里/時"
#: system/ui/widgets/network.py:204
#, python-format
@@ -1020,7 +1020,7 @@ msgstr "計量"
#: selfdrive/ui/onroad/hud_renderer.py:177
#, python-format
msgid "mph"
msgstr "mph"
msgstr "英里/時"
#: selfdrive/ui/layouts/settings/software.py:20
#, python-format

View File

@@ -18,7 +18,7 @@ OPENAI_PROMPT = "You are a professional translator from English to {language} (I
"The following sentence or word is in the GUI of a software called openpilot, translate it accordingly."
def get_language_files(languages: list[str] | None = None) -> dict[str, pathlib.Path]:
def get_language_files(languages: list[str] = None) -> dict[str, pathlib.Path]:
files = {}
with open(TRANSLATIONS_LANGUAGES) as fp:

View File

@@ -258,17 +258,9 @@ class Device(DeviceSP):
else:
clipped_brightness = ((clipped_brightness + 16.0) / 116.0) ** 3.0
min_brightness = 30
if gui_app.sunnypilot_ui():
min_brightness = DeviceSP.set_min_onroad_brightness(ui_state, min_brightness)
clipped_brightness = float(np.interp(clipped_brightness, [0, 1], [min_brightness, 100]))
clipped_brightness = float(np.interp(clipped_brightness, [0, 1], [30, 100]))
brightness = round(self._brightness_filter.update(clipped_brightness))
if gui_app.sunnypilot_ui():
brightness = DeviceSP.set_onroad_brightness(ui_state, self._awake, brightness)
if not self._awake:
brightness = 0
@@ -284,9 +276,6 @@ class Device(DeviceSP):
self._ignition = ui_state.ignition
if ignition_just_turned_off or any(ev.left_down for ev in gui_app.mouse_events):
if gui_app.sunnypilot_ui():
DeviceSP.wake_from_dimmed_onroad_brightness(ui_state, gui_app.mouse_events)
self._reset_interactive_timeout()
interaction_timeout = time.monotonic() > self._interaction_time

View File

@@ -1,4 +1,3 @@
SConscript(['common/transformations/SConscript'])
SConscript(['modeld/SConscript'])
SConscript(['modeld_v2/SConscript'])
SConscript(['selfdrive/locationd/SConscript'])
SConscript(['selfdrive/locationd/SConscript'])

View File

@@ -1,4 +0,0 @@
Import('env')
transformations = env.Library('transformations', ['orientation.cc', 'coordinates.cc'])
Export('transformations')

View File

@@ -1,4 +1,4 @@
Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc')
Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc', 'transformations')
lenv = env.Clone()
lenvCython = envCython.Clone()

View File

@@ -29,7 +29,6 @@ from openpilot.sunnypilot.modeld.constants import ModelConstants, Plan
from openpilot.sunnypilot.models.helpers import get_active_bundle, get_model_path, load_metadata, prepare_inputs, load_meta_constants
from openpilot.sunnypilot.modeld.models.commonmodel_pyx import ModelFrame, CLContext
from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
from openpilot.sunnypilot.selfdrive.controls.lib.relc import RoadEdgeLaneChangeController
PROCESS_NAME = "selfdrive.modeld.modeld_snpe"
@@ -212,7 +211,6 @@ def main(demo=False):
prev_action = log.ModelDataV2.Action()
DH = DesireHelper()
RELC = RoadEdgeLaneChangeController(params.get_bool("RoadEdgeLaneChangeEnabled"))
while True:
# Keep receiving frames until we are at least 1 frame ahead of previous extra frame
@@ -320,10 +318,7 @@ def main(demo=False):
l_lane_change_prob = desire_state[log.Desire.laneChangeLeft]
r_lane_change_prob = desire_state[log.Desire.laneChangeRight]
lane_change_prob = l_lane_change_prob + r_lane_change_prob
RELC.update(modelv2_send.modelV2.roadEdgeStds, modelv2_send.modelV2.laneLineProbs)
mdv2sp_send.modelDataV2SP.leftLaneChangeEdgeBlock = RELC.left_edge_detected
mdv2sp_send.modelDataV2SP.rightLaneChangeEdgeBlock = RELC.right_edge_detected
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob, RELC.left_edge_detected, RELC.right_edge_detected)
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction

View File

@@ -1,7 +1,7 @@
import os
import glob
Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc')
Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc', 'transformations')
lenv = env.Clone()
lenvCython = envCython.Clone()

View File

@@ -27,7 +27,6 @@ from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
from openpilot.sunnypilot.models.helpers import get_active_bundle
from openpilot.sunnypilot.models.runners.helpers import get_model_runner
from openpilot.sunnypilot.selfdrive.controls.lib.relc import RoadEdgeLaneChangeController
PROCESS_NAME = "selfdrive.modeld.modeld_tinygrad"
@@ -246,9 +245,6 @@ def main(demo=False):
prev_action = log.ModelDataV2.Action()
DH = DesireHelper()
RELC = RoadEdgeLaneChangeController(params.get_bool("RoadEdgeLaneChangeEnabled"))
while True:
# Keep receiving frames until we are at least 1 frame ahead of previous extra frame
@@ -352,10 +348,7 @@ def main(demo=False):
l_lane_change_prob = desire_state[log.Desire.laneChangeLeft]
r_lane_change_prob = desire_state[log.Desire.laneChangeRight]
lane_change_prob = l_lane_change_prob + r_lane_change_prob
RELC.update(modelv2_send.modelV2.roadEdgeStds, modelv2_send.modelV2.laneLineProbs)
mdv2sp_send.modelDataV2SP.leftLaneChangeEdgeBlock = RELC.left_edge_detected
mdv2sp_send.modelDataV2SP.rightLaneChangeEdgeBlock = RELC.right_edge_detected
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob, RELC.left_edge_detected, RELC.right_edge_detected)
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python3
# type: ignore
import os
import time

View File

@@ -1 +1 @@
0cde93a9d20f35619182e704261ef8f6a849149fe5849ba2d355b0b2c89af53c
6168bc755ea17aececa535e8b94e6c798e5e855bfc47be19220d5cbc08483332

View File

@@ -1,139 +0,0 @@
"""
Copyright (c) 2021-, rav4kumar, 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.
"""
from cereal import custom
import numpy as np
from openpilot.common.realtime import DT_MDL
from openpilot.common.params import Params
AccelPersonality = custom.LongitudinalPlanSP.AccelerationPersonality
ACCEL_PERSONALITY_OPTIONS = [AccelPersonality.eco, AccelPersonality.normal, AccelPersonality.sport]
# Acceleration Profiles
MAX_ACCEL_PROFILES = {
AccelPersonality.eco: [1.85, 1.80, 1.55, 0.94, 0.72, 0.58, 0.34, 0.120, 0.09, 0.07],
AccelPersonality.normal: [2.00, 1.95, 1.80, 1.06, 0.81, 0.69, 0.42, 0.160, 0.10, 0.08],
AccelPersonality.sport: [2.00, 1.99, 1.95, 1.45, 1.10, 0.82, 0.53, 0.240, 0.13, 0.09],
#AccelPersonality.eco: [1.30, 1.25, 1.15, 0.69, 0.60, 0.49, 0.28, 0.107, 0.08, 0.06],
#AccelPersonality.normal: [1.85, 1.80, 1.55, 0.94, 0.72, 0.58, 0.34, 0.120, 0.09, 0.07],
#AccelPersonality.sport: [2.00, 1.95, 1.80, 1.06, 0.81, 0.69, 0.42, 0.160, 0.10, 0.08],
}
MAX_ACCEL_BREAKPOINTS = [0., 3., 5., 8., 12., 18., 24., 32., 42., 55.]
# Braking Profiles
MIN_ACCEL_PROFILES = {
AccelPersonality.eco: [-.002, -0.24, -0.34, -0.44, -1.2],
AccelPersonality.normal: [-.003, -0.26, -0.36, -0.46, -1.3],
AccelPersonality.sport: [-.004, -0.28, -0.38, -0.48, -1.4],
}
MIN_ACCEL_BREAKPOINTS = [3, 4.5, 7., 9., 25]
DECEL_SMOOTH_ALPHA = 0.40 # Very aggressive smoothing for decel (lower = smoother)
ACCEL_SMOOTH_ALPHA = 0.90 # Less aggressive for accel (higher = more responsive)
# Asymmetric rate limiting
MAX_DECEL_INCREASE_RATE = 1.3 # When braking harder (m/s² per second)
MAX_DECEL_DECREASE_RATE = 1.0 # When releasing brake (m/s² per second)
class AccelPersonalityController:
def __init__(self):
self.params = Params()
self.frame = 0
self.last_max_accel = 2.0
self.last_min_accel = -0.01
self.first_run = True
self._accel_personality = self.params.get('AccelPersonality') or AccelPersonality.normal
self._enabled = self.params.get_bool('AccelPersonalityEnabled')
def update(self, sm=None):
self.frame += 1
if self.frame % int(1.0 / DT_MDL) == 0:
self._accel_personality = self.params.get('AccelPersonality') or AccelPersonality.normal
self._enabled = self.params.get_bool('AccelPersonalityEnabled')
@property
def accel_personality(self) -> int:
return self._accel_personality
def get_accel_personality(self) -> int:
return int(self._accel_personality)
def set_accel_personality(self, personality: int):
if personality in ACCEL_PERSONALITY_OPTIONS:
self._accel_personality = personality
self.params.put('AccelPersonality', personality)
def cycle_accel_personality(self) -> int:
current = self._accel_personality
next_personality = ACCEL_PERSONALITY_OPTIONS[(ACCEL_PERSONALITY_OPTIONS.index(current) + 1) % len(ACCEL_PERSONALITY_OPTIONS)]
self.set_accel_personality(next_personality)
return int(next_personality)
def get_accel_limits(self, v_ego: float) -> tuple[float, float]:
v_ego = max(0.0, v_ego)
target_max = np.interp(v_ego, MAX_ACCEL_BREAKPOINTS, MAX_ACCEL_PROFILES[self.accel_personality])
target_min = np.interp(v_ego, MIN_ACCEL_BREAKPOINTS, MIN_ACCEL_PROFILES[self.accel_personality])
if self.first_run:
self.last_max_accel, self.last_min_accel = target_max, target_min
self.first_run = False
return float(target_min), float(target_max)
# Smoothing
self.last_max_accel = ACCEL_SMOOTH_ALPHA * target_max + (1 - ACCEL_SMOOTH_ALPHA) * self.last_max_accel
smoothed_decel = DECEL_SMOOTH_ALPHA * target_min + (1 - DECEL_SMOOTH_ALPHA) * self.last_min_accel
# Rate Limiting (Asymmetric)
raw_change = smoothed_decel - self.last_min_accel
if raw_change < 0:
limit = MAX_DECEL_INCREASE_RATE * DT_MDL
decel_change = np.clip(raw_change, -limit, limit)
else:
limit = MAX_DECEL_DECREASE_RATE * DT_MDL
decel_change = np.clip(raw_change, -limit, limit)
self.last_min_accel += decel_change
# Dynamic Safety Corridor: Ensure min is always strictly less than max.
# We maintain a gap of at least 0.1, or 5% of the current max acceleration.
# This scaling gap prevents solver crashes at high acceleration values.
gap = max(0.1, abs(self.last_max_accel) * 0.05)
if self.last_min_accel > self.last_max_accel - gap:
self.last_min_accel = self.last_max_accel - gap
return float(self.last_min_accel), float(self.last_max_accel)
def get_min_accel(self, v_ego: float) -> float:
return self.get_accel_limits(v_ego)[0]
def get_max_accel(self, v_ego: float) -> float:
return self.get_accel_limits(v_ego)[1]
def is_enabled(self) -> bool:
return self._enabled
def set_enabled(self, enabled: bool):
self._enabled = enabled
self.params.put_bool('AccelPersonalityEnabled', enabled)
def toggle_enabled(self) -> bool:
current = self._enabled
self.set_enabled(not current)
return not current
def reset(self):
self._accel_personality = AccelPersonality.normal
self.params.put('AccelPersonality', AccelPersonality.normal)
self.frame = 0
self.last_max_accel = 2.0
self.last_min_accel = -0.01
self.first_run = True

View File

@@ -1,106 +0,0 @@
"""
Copyright (c) 2021-, rav4kumar, 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.
"""
from cereal import log
import numpy as np
from openpilot.common.realtime import DT_MDL
from openpilot.common.params import Params
LongPersonality = log.LongitudinalPersonality
# Follow distance profiles mapped to LongPersonality
FOLLOW_PROFILES = {
LongPersonality.relaxed: [1.70, 1.70, 1.75, 1.75, 1.80, 1.80, 1.80],
LongPersonality.standard: [1.40, 1.40, 1.45, 1.45, 1.50, 1.50, 1.50],
LongPersonality.aggressive: [1.05, 1.05, 1.15, 1.15, 1.20, 1.20, 1.20],
}
FOLLOW_BREAKPOINTS = [0., 3., 6., 30., 40., 50., 60.]
SMOOTHING_BASE = 0.55 # Base smoothing factor (higher = smoother)
SMOOTHING_RANGE = 0.20 # Additional smoothing at high speeds
SMOOTHING_SPEED_THRESHOLD = 36.0 # m/s (~80 mph) for max smoothing
PERSONALITY_CHANGE_COOLDOWN_S = 2.0
class FollowDistanceController:
def __init__(self):
self.params = Params()
self.frame = 0
self.current_multiplier = None
self.first_run = True
self.personality_change_cooldown = 0
self.personality_cooldown_frames = int(PERSONALITY_CHANGE_COOLDOWN_S / DT_MDL)
self._personality = self.params.get('LongitudinalPersonality') or LongPersonality.standard
self._enabled = self.params.get_bool('DynamicFollow')
def _get_smoothing_factor(self, v_ego: float) -> float:
speed_factor = np.clip(v_ego / SMOOTHING_SPEED_THRESHOLD, 0.3, 1.0)
return SMOOTHING_BASE + (SMOOTHING_RANGE * speed_factor)
def is_enabled(self) -> bool:
return self._enabled
def set_enabled(self, enabled: bool):
self._enabled = enabled
self.params.put_bool('DynamicFollow', enabled)
def toggle(self) -> bool:
current = self._enabled
self.set_enabled(not current)
return not current
@property
def personality(self) -> int:
return self._personality
def get_personality(self) -> int:
return int(self._personality)
def set_personality(self, personality: int):
if personality not in [LongPersonality.relaxed, LongPersonality.standard, LongPersonality.aggressive]:
return
self._personality = personality
self.params.put('LongitudinalPersonality', personality)
self.personality_change_cooldown = self.personality_cooldown_frames
def cycle_personality(self) -> int:
personalities = [LongPersonality.relaxed, LongPersonality.standard, LongPersonality.aggressive]
current_idx = personalities.index(self._personality)
next_personality = personalities[(current_idx + 1) % len(personalities)]
self.set_personality(next_personality)
return int(next_personality)
def get_follow_distance_multiplier(self, v_ego: float) -> float:
v_ego = max(0.0, v_ego)
target = float(np.interp(v_ego, FOLLOW_BREAKPOINTS, FOLLOW_PROFILES[self._personality]))
if self.first_run:
self.current_multiplier = target
self.first_run = False
return self.current_multiplier
# exponential smoothing with speedadaptive factor
alpha = self._get_smoothing_factor(v_ego)
self.current_multiplier = alpha * self.current_multiplier + (1.0 - alpha) * target
return self.current_multiplier
def reset(self):
self._personality = LongPersonality.standard
self.params.put('LongitudinalPersonality', LongPersonality.standard)
self.frame = 0
self.current_multiplier = None
self.first_run = True
self.personality_change_cooldown = 0
def update(self):
self.frame += 1
if self.personality_change_cooldown > 0:
self.personality_change_cooldown -= 1
if self.frame % int(1.0 / DT_MDL) == 0:
self._personality = self.params.get('LongitudinalPersonality') or LongPersonality.standard
self._enabled = self.params.get_bool('DynamicFollow')

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