Merge remote-tracking branch 'upstream/master' into new-nav-settings-button

pull/29068/head
Shane Smiskol 2 years ago
commit 52fcc9320e
  1. 2
      .python-version
  2. 2
      Dockerfile.openpilot_base
  3. 1
      RELEASES.md
  4. 1
      SConstruct
  5. 2
      cereal
  6. 2
      docs/CARS.md
  7. 2
      launch_env.sh
  8. 2
      mypy.ini
  9. 2
      opendbc
  10. 2
      panda
  11. 6275
      poetry.lock
  12. 37
      pyproject.toml
  13. 4
      selfdrive/athena/athenad.py
  14. 2
      selfdrive/boardd/pandad.py
  15. BIN
      selfdrive/boardd/tests/bootstub.panda_h7.bin
  16. 2
      selfdrive/car/car_helpers.py
  17. 2
      selfdrive/car/docs_definitions.py
  18. 29
      selfdrive/car/gm/interface.py
  19. 3
      selfdrive/car/interfaces.py
  20. 36
      selfdrive/car/tests/test_models.py
  21. 1
      selfdrive/car/torque_data/override.yaml
  22. 1
      selfdrive/car/torque_data/params.yaml
  23. 7
      selfdrive/car/volkswagen/values.py
  24. 2
      selfdrive/controls/controlsd.py
  25. 3
      selfdrive/controls/plannerd.py
  26. 201
      selfdrive/controls/radard.py
  27. 3
      selfdrive/debug/dump.py
  28. 3
      selfdrive/debug/internal/measure_torque_time_to_max.py
  29. 2
      selfdrive/debug/run_process_on_route.py
  30. 2
      selfdrive/locationd/calibrationd.py
  31. 2
      selfdrive/locationd/laikad.py
  32. 3
      selfdrive/locationd/paramsd.py
  33. 3
      selfdrive/locationd/test/_test_locationd_lib.py
  34. 8
      selfdrive/locationd/torqued.py
  35. 8
      selfdrive/navd/navd.py
  36. 2
      selfdrive/test/process_replay/compare_logs.py
  37. 2
      selfdrive/test/process_replay/helpers.py
  38. 5
      selfdrive/test/process_replay/process_replay.py
  39. 5
      selfdrive/test/process_replay/test_fuzzy.py
  40. 3
      selfdrive/ui/SConscript
  41. 203
      selfdrive/ui/qt/maps/map.cc
  42. 41
      selfdrive/ui/qt/maps/map.h
  43. 57
      selfdrive/ui/qt/maps/map_eta.cc
  44. 23
      selfdrive/ui/qt/maps/map_eta.h
  45. 146
      selfdrive/ui/qt/maps/map_instructions.cc
  46. 27
      selfdrive/ui/qt/maps/map_instructions.h
  47. 9
      selfdrive/ui/qt/maps/map_settings.cc
  48. 4
      selfdrive/ui/translations/main_de.ts
  49. 4
      selfdrive/ui/translations/main_ja.ts
  50. 4
      selfdrive/ui/translations/main_ko.ts
  51. 4
      selfdrive/ui/translations/main_pt-BR.ts
  52. 4
      selfdrive/ui/translations/main_zh-CHS.ts
  53. 4
      selfdrive/ui/translations/main_zh-CHT.ts
  54. 44
      system/hardware/tici/agnos.json
  55. 2
      system/sensord/rawgps/compare.py

@ -1 +1 @@
3.8.10
3.11.4

@ -13,7 +13,7 @@ ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
ENV POETRY_VIRTUALENVS_CREATE=false
ENV PYENV_VERSION=3.8.10
ENV PYENV_VERSION=3.11.4
ENV PYENV_ROOT="/root/.pyenv"
ENV PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH"

@ -9,6 +9,7 @@ Version 0.9.4 (2023-XX-XX)
* UI alerts rework
* Border color always shows engagement status. Blue means disengaged, green means engaged, and grey means engaged with human overriding
* Alerts are shown inside the border. Black/grey means info, orange means warning, and red means critical alert
* Bookmarked segments are preserved on the device's storage
* Ford Focus 2018 support
* Kia Carnival 2023 support thanks to sunnyhaibin!

@ -263,6 +263,7 @@ py_include = sysconfig.get_paths()['include']
envCython = env.Clone()
envCython["CPPPATH"] += [py_include, np.get_include()]
envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-shadow", "-Wno-deprecated-declarations"]
envCython["CCFLAGS"].remove("-Werror")
envCython["LIBS"] = []
if arch == "Darwin":

@ -1 +1 @@
Subproject commit 6f7102581f57eb5074b816cc2cfd984218916773
Subproject commit aed9fd278a704816aba11f4473aafefc281ed2bc

@ -183,7 +183,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|Škoda|Kodiaq 2017-23[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Škoda&model=Kodiaq 2017-23">Buy Here</a></sub></details>||
|Škoda|Octavia 2015, 2018-19[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Škoda&model=Octavia 2015, 2018-19">Buy Here</a></sub></details>||
|Škoda|Octavia RS 2016[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Škoda&model=Octavia RS 2016">Buy Here</a></sub></details>||
|Škoda|Scala 2020[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Škoda&model=Scala 2020">Buy Here</a></sub></details>[<sup>13</sup>](#footnotes)||
|Škoda|Scala 2020-23[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Škoda&model=Scala 2020-23">Buy Here</a></sub></details>[<sup>13</sup>](#footnotes)||
|Škoda|Superb 2015-22[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Škoda&model=Superb 2015-22">Buy Here</a></sub></details>||
|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota connector<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Alphard 2019-20">Buy Here</a></sub></details>||
|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota connector<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Alphard Hybrid 2021">Buy Here</a></sub></details>||

@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
export VECLIB_MAXIMUM_THREADS=1
if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="7.1"
export AGNOS_VERSION="8.2"
fi
if [ -z "$PASSIVE" ]; then

@ -1,5 +1,5 @@
[mypy]
python_version = 3.8
python_version = 3.11
plugins = numpy.typing.mypy_plugin
files = body, common, docs, scripts, selfdrive, site_scons, system, tools
exclude = ^(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)

@ -1 +1 @@
Subproject commit 3ef35ed2298a3a9d199f9145409547710065884c
Subproject commit 4231b0f12d8cf10d0554c4eb513ac984defc1f90

@ -1 +1 @@
Subproject commit 5d873444b2cf801ba73f4a457993260df3a412b8
Subproject commit dd78b2bf6c9d63ef59e81d0c400e85c8b477a8be

6275
poetry.lock generated

File diff suppressed because it is too large Load Diff

@ -10,13 +10,13 @@ documentation = "https://docs.comma.ai"
[tool.poetry.dependencies]
python = "~3.8"
python = "~3.11"
atomicwrites = "^1.4.0"
casadi = "==3.6.3"
cffi = "^1.15.1"
crcmod = "^1.7"
cryptography = "^37.0.4"
Cython = "^0.29.30"
Cython = "^3.0.0"
flake8 = "^4.0.1"
Flask = "^2.1.2"
future-fstrings = "^1.2.0" # for acados
@ -27,14 +27,14 @@ Jinja2 = "^3.1.2"
json-rpc = "^1.13.0"
libusb1 = "^3.0.0"
nose = "^1.3.7"
numpy = "^1.23.0"
numpy = "==1.23.0" # locked pending deprecation fixes in xx
onnx = "^1.14.0"
onnxruntime-gpu = { version = "^1.15.1", platform = "linux", markers = "platform_machine == 'x86_64'" }
pillow = "^9.2.0"
poetry = "==1.2.2"
protobuf = "==3.20.3"
psutil = "^5.9.1"
pycapnp = "==1.1.0"
pycapnp = "^1.3.0"
pycryptodome = "^3.15.0"
PyJWT = "^2.5.0"
pyopencl = "^2022.2.4"
@ -43,7 +43,7 @@ python-dateutil = "^2.8.2"
PyYAML = "^6.0"
pyzmq = "^23.2.0"
requests = "^2.28.1"
scons = "^4.3.0"
scons = "^4.5.2"
sentry-sdk = "^1.6.0"
setproctitle = "^1.2.3"
six = "^1.16.0"
@ -51,8 +51,8 @@ smbus2 = "^0.4.2"
sounddevice = "^0.4.5"
spidev = { version = "^3.6", platform = "linux" }
spidev2 = { version = "^0.9.0", platform = "linux" }
sympy = "^1.10.1"
timezonefinder = "^6.0.1"
sympy = "^1.11.1"
timezonefinder = "^6.2.0"
tqdm = "^4.64.0"
urllib3 = "^1.26.10"
utm = "^0.7.0"
@ -62,10 +62,10 @@ sconscontrib = {git = "https://github.com/SCons/scons-contrib.git"}
[tool.poetry.group.dev.dependencies]
av = "^9.2.0"
av = "^10.0.0"
azure-storage-blob = "~2.1"
breathe = "^4.34.0"
carla = { version = "==0.9.13", platform = "linux", markers = "platform_machine == 'x86_64'" }
carla = { url = "https://github.com/commaai/carla/releases/download/3.11.4/carla-0.9.14-cp311-cp311-linux_x86_64.whl", platform = "linux", markers = "platform_machine == 'x86_64'" }
control = "^0.9.2"
coverage = "^6.4.1"
dictdiffer = "^0.9.0"
@ -79,24 +79,23 @@ lxml = "^4.9.1"
markdown-it-py = "^2.1.0"
matplotlib = "^3.5.2"
mpld3 = "^0.5.8"
mypy = "^0.961"
mypy = "^1.4.0"
myst-parser = "^0.18.0"
natsort = "^8.1.0"
numpy = "^1.23.0"
opencv-python-headless = { url = "https://github.com/commaai/opencv-python-builder/releases/download/4.5.5.64%2Bcu118/opencv_python_headless-4.5.5.64-cp38-cp38-manylinux_2_31_x86_64.whl", platform = "linux" }
opencv-python-headless = { url = "https://github.com/commaai/opencv-python-builder/releases/download/4.5.5.64%2Bcu118-cp311/opencv_python_headless-4.5.5.64-cp311-cp311-manylinux_2_31_x86_64.whl", platform = "linux" }
pandas = "^1.4.3"
parameterized = "^0.8.1"
paramiko = "^2.11.0"
pprofile = "^2.1.0"
pre-commit = "^2.19.0"
pycurl = "^7.45.1"
pygame = "^2.1.2"
pygame = "^2.4.0"
pylint = "^2.17.4"
pyprof2calltree = "^1.4.5"
pytest = "^7.1.2"
pytest-xdist = "^2.5.0"
reverse_geocoder = "^1.5.1"
scipy = "^1.8.1"
scipy = "==1.9.3" # pinned until xx refs changes can be checked
sphinx = "^5.0.2"
sphinx-rtd-theme = "^1.0.0"
sphinx-sitemap = "^2.2.0"
@ -118,7 +117,6 @@ optional = true
aenum = "^3.1.11"
aiohttp = "^3.8.1"
albumentations = "^1.2.1"
apex = { url = "https://github.com/commaai/apex/releases/download/pytorch2.0.0%2Bcu11.8/apex-0.1-cp38-cp38-linux_x86_64.whl" }
azure-cli-core = "^2.38.0"
azure-common = "^1.1.28"
azure-core = "^1.24.2"
@ -164,20 +162,21 @@ redis = "^4.3.4"
s2sphere = "^0.2.5"
scikit-image = "^0.19.3"
scikit-learn = "^1.1.1"
segmentation-models-pytorch = "==0.3.2"
segmentation-models-pytorch = "==0.3.3"
simplejson = "^3.17.6"
SQLAlchemy = "^1.4.39"
torch = { url = "https://download.pytorch.org/whl/cu118/torch-2.0.0%2Bcu118-cp38-cp38-linux_x86_64.whl" }
torch = { url = "https://download.pytorch.org/whl/cu118/torch-2.0.1%2Bcu118-cp311-cp311-linux_x86_64.whl" }
torchsummary = "^1.5.1"
torchvision = { url = "https://download.pytorch.org/whl/cu118/torchvision-0.15.1%2Bcu118-cp38-cp38-linux_x86_64.whl" }
torchvision = { url = "https://download.pytorch.org/whl/cu118/torchvision-0.15.2%2Bcu118-cp311-cp311-linux_x86_64.whl" }
triton = "^2.0.0"
Werkzeug = "^2.1.2"
zerorpc = { git = "https://github.com/commaai/zerorpc-python.git", branch = "master" }
omegaconf = "^2.3.0"
osmnx = "==1.2.2"
tritonclient = {version = "2.28.0", extras = ["http"]}
tensorrt = "^8.6.0"
transformers = "^4.29.2"
timm = "==0.9.2"
PyNvCodec = { git = "https://github.com/NVIDIA/VideoProcessingFramework.git", rev = "3347e55" }
[build-system]

@ -798,7 +798,9 @@ def main(exit_event: Optional[threading.Event] = None):
except (ConnectionError, TimeoutError, WebSocketException):
conn_retries += 1
params.remove("LastAthenaPingTime")
except socket.timeout:
# TODO: socket.timeout and TimeoutError are now the same exception since python3.10
# Remove the socket.timeout case once we have fully moved to python3.11
except socket.timeout: # pylint: disable=duplicate-except
params.remove("LastAthenaPingTime")
except Exception:
cloudlog.exception("athenad.main.exception")

@ -169,7 +169,7 @@ def main() -> NoReturn:
# sort pandas to have deterministic order
pandas.sort(key=cmp_to_key(panda_sort_cmp))
panda_serials = list(map(lambda p: p.get_usb_serial(), pandas)) # type: ignore
panda_serials = list(map(lambda p: p.get_usb_serial(), pandas))
# log panda fw versions
params.put("PandaSignatures", b','.join(p.get_signature() for p in pandas))

@ -89,7 +89,7 @@ def fingerprint(logcan, sendcan, num_pandas):
cached_params = params.get("CarParamsCache")
if cached_params is not None:
cached_params = car.CarParams.from_bytes(cached_params)
with car.CarParams.from_bytes(cached_params) as cached_params:
if cached_params.carName == "mock":
cached_params = None

@ -149,7 +149,7 @@ class CarParts:
return copy.deepcopy(self)
@classmethod
def common(cls, add: List[EnumBase] = None, remove: List[EnumBase] = None):
def common(cls, add: Optional[List[EnumBase]] = None, remove: Optional[List[EnumBase]] = None):
p = [part for part in (add or []) + DEFAULT_CAR_PARTS if part not in (remove or [])]
return cls(p)

@ -19,6 +19,12 @@ BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.D
CruiseButtons.MAIN: ButtonType.altButton3, CruiseButtons.CANCEL: ButtonType.cancel}
NON_LINEAR_TORQUE_PARAMS = {
CAR.BOLT_EUV: [2.6531724862969748, 1.0, 0.1919764879840985, 0.009054123646805178],
CAR.ACADIA: [4.78003305, 1.0, 0.3122, 0.05591772]
}
class CarInterface(CarInterfaceBase):
@staticmethod
def get_pid_accel_limits(CP, current_speed, cruise_speed):
@ -31,22 +37,13 @@ class CarInterface(CarInterfaceBase):
sigmoid = desired_angle / (1 + fabs(desired_angle))
return 0.10006696 * sigmoid * (v_ego + 3.12485927)
@staticmethod
def get_steer_feedforward_acadia(desired_angle, v_ego):
desired_angle *= 0.09760208
sigmoid = desired_angle / (1 + fabs(desired_angle))
return 0.04689655 * sigmoid * (v_ego + 10.028217)
def get_steer_feedforward_function(self):
if self.CP.carFingerprint == CAR.VOLT:
return self.get_steer_feedforward_volt
elif self.CP.carFingerprint == CAR.ACADIA:
return self.get_steer_feedforward_acadia
else:
return CarInterfaceBase.get_steer_feedforward_default
@staticmethod
def torque_from_lateral_accel_bolt(lateral_accel_value: float, torque_params: car.CarParams.LateralTorqueTuning,
def torque_from_lateral_accel_siglin(self, lateral_accel_value: float, torque_params: car.CarParams.LateralTorqueTuning,
lateral_accel_error: float, lateral_accel_deadzone: float, friction_compensation: bool) -> float:
friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation)
@ -57,14 +54,15 @@ class CarInterface(CarInterfaceBase):
# An important thing to consider is that the slope at 0 should be > 0 (ideally >1)
# This has big effect on the stability about 0 (noise when going straight)
# ToDo: To generalize to other GMs, explore tanh function as the nonlinear
a, b, c, _ = [2.6531724862969748, 1.0, 0.1919764879840985, 0.009054123646805178] # weights computed offline
non_linear_torque_params = NON_LINEAR_TORQUE_PARAMS.get(self.CP.carFingerprint)
assert non_linear_torque_params, "The params are not defined"
a, b, c, _ = non_linear_torque_params
steer_torque = (sig(lateral_accel_value * a) * b) + (lateral_accel_value * c)
return float(steer_torque) + friction
def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType:
if self.CP.carFingerprint == CAR.BOLT_EUV:
return self.torque_from_lateral_accel_bolt
if self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS:
return self.torque_from_lateral_accel_siglin
else:
return self.torque_from_lateral_accel_linear
@ -169,7 +167,8 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.86
ret.steerRatio = 14.4 # end to end is 13.46
ret.centerToFront = ret.wheelbase * 0.4
ret.lateralTuning.pid.kf = 1. # get_steer_feedforward_acadia()
ret.steerActuatorDelay = 0.2
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate == CAR.BUICK_LACROSSE:
ret.mass = 1712. + STD_CARGO_KG

@ -131,8 +131,7 @@ class CarInterfaceBase(ABC):
def get_steer_feedforward_function(self):
return self.get_steer_feedforward_default
@staticmethod
def torque_from_lateral_accel_linear(lateral_accel_value: float, torque_params: car.CarParams.LateralTorqueTuning,
def torque_from_lateral_accel_linear(self, lateral_accel_value: float, torque_params: car.CarParams.LateralTorqueTuning,
lateral_accel_error: float, lateral_accel_deadzone: float, friction_compensation: bool) -> float:
# The default is a linear relationship between torque and lateral acceleration (accounting for road roll and steering friction)
friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation)

@ -201,6 +201,42 @@ class TestCarModelBase(unittest.TestCase):
self.assertTrue(self.safety.addr_checks_valid())
self.assertFalse(len(failed_addrs), f"panda safety RX check failed: {failed_addrs}")
def test_panda_safety_tx_cases(self, data=None):
"""Asserts we can tx common messages"""
if self.CP.notCar:
self.skipTest("Skipping test for notCar")
def test_car_controller(car_control):
now_nanos = 0
msgs_sent = 0
CI = self.CarInterface(self.CP, self.CarController, self.CarState)
for _ in range(round(10.0 / DT_CTRL)): # make sure we hit the slowest messages
CI.update(car_control, [])
_, sendcan = CI.apply(car_control, now_nanos)
now_nanos += DT_CTRL * 1e9
msgs_sent += len(sendcan)
for addr, _, dat, bus in sendcan:
to_send = libpanda_py.make_CANPacket(addr, bus % 4, dat)
self.assertTrue(self.safety.safety_tx_hook(to_send), (addr, dat, bus))
# Make sure we attempted to send messages
self.assertGreater(msgs_sent, 50)
# Make sure we can send all messages while inactive
CC = car.CarControl.new_message()
test_car_controller(CC)
# Test cancel + general messages (controls_allowed=False & cruise_engaged=True)
self.safety.set_cruise_engaged_prev(True)
CC = car.CarControl.new_message(cruiseControl={'cancel': True})
test_car_controller(CC)
# Test resume + general messages (controls_allowed=True & cruise_engaged=True)
self.safety.set_controls_allowed(True)
CC = car.CarControl.new_message(cruiseControl={'resume': True})
test_car_controller(CC)
def test_panda_safety_carstate(self):
"""
Assert that panda safety matches openpilot's carState

@ -49,6 +49,7 @@ KIA NIRO HYBRID 2ND GEN: [2.42, 2.5, 0.12]
KIA NIRO EV 2ND GEN: [2.05, 2.5, 0.14]
GENESIS GV80 2023: [2.5, 2.5, 0.1]
KIA CARNIVAL 4TH GEN: [1.75, 1.75, 0.15]
GMC ACADIA DENALI 2018: [1.6, 1.6, 0.2]
# Dashcam or fallback configured as ideal car
mock: [10.0, 10, 0.0]

@ -10,7 +10,6 @@ CHRYSLER PACIFICA HYBRID 2017: [1.79422, 1.06831764583744, 0.116237]
CHRYSLER PACIFICA HYBRID 2018: [2.08887, 1.2943025830995154, 0.114818]
CHRYSLER PACIFICA HYBRID 2019: [1.90120, 1.1958788168371808, 0.131520]
GENESIS G70 2018: [3.8520195946707947, 2.354697063349854, 0.06830285485626221]
GMC ACADIA DENALI 2018: [1.3181430320331884, 1.1853735340610179, 0.3450592280031644]
HONDA ACCORD 2018: [1.7135052593468778, 0.3461280068322071, 0.21579936052863807]
HONDA ACCORD HYBRID 2018: [1.6651615004829625, 0.30322180951193245, 0.2083000440586149]
HONDA CIVIC (BOSCH) 2019: [1.691708637466905, 0.40132900729454185, 0.25460295304024094]

@ -262,7 +262,7 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = {
CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021", footnotes=[Footnote.VW_MQB_A0, Footnote.KAMIQ]),
CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019-21"),
CAR.SKODA_KODIAQ_MK1: VWCarInfo("Škoda Kodiaq 2017-23"),
CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020", footnotes=[Footnote.VW_MQB_A0]),
CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020-23", footnotes=[Footnote.VW_MQB_A0]),
CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-22"),
CAR.SKODA_OCTAVIA_MK3: [
VWCarInfo("Škoda Octavia 2015, 2018-19"),
@ -1280,19 +1280,24 @@ FW_VERSIONS = {
CAR.SKODA_SCALA_MK1: {
(Ecu.engine, 0x7e0, None): [
b'\xf1\x8704C906025AK\xf1\x897053',
b'\xf1\x8705C906032M \xf1\x892365',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x870CW300020 \xf1\x891907',
b'\xf1\x870CW300050 \xf1\x891709',
],
(Ecu.srs, 0x715, None): [
b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\x1211110411110411--04040404131111112H14',
b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\022111104111104112104040404111111112H14',
b'\xf1\x872Q0959655AS\xf1\x890411\xf1\x82\x1311150411110411210404040417151215391413',
],
(Ecu.eps, 0x712, None): [
b'\xf1\x872Q1909144M \xf1\x896041',
b'\xf1\x872Q1909144AB\xf1\x896050',
],
(Ecu.fwdRadar, 0x757, None): [
b'\xf1\x872Q0907572R \xf1\x890372',
b'\xf1\x872Q0907572AA\xf1\x890396',
],
},
CAR.SKODA_SUPERB_MK3: {

@ -206,7 +206,7 @@ class Controls:
if REPLAY:
controls_state = Params().get("ReplayControlsState")
if controls_state is not None:
controls_state = log.ControlsState.from_bytes(controls_state)
with log.ControlsState.from_bytes(controls_state) as controls_state:
self.v_cruise_helper.v_cruise_kph = controls_state.vCruise
if any(ps.controlsAllowed for ps in self.sm['pandaStates']):

@ -32,7 +32,8 @@ def plannerd_thread(sm=None, pm=None):
cloudlog.info("plannerd is waiting for CarParams")
params = Params()
CP = car.CarParams.from_bytes(params.get("CarParams", block=True))
with car.CarParams.from_bytes(params.get("CarParams", block=True)) as msg:
CP = msg
cloudlog.info("plannerd got CarParams: %s", CP.carName)
debug_mode = bool(int(os.getenv("DEBUG", "0")))

@ -1,10 +1,11 @@
#!/usr/bin/env python3
import importlib
import math
from collections import defaultdict, deque
from collections import deque
from typing import Optional, Dict, Any
import cereal.messaging as messaging
from cereal import car
import capnp
from cereal import messaging, log, car
from common.numpy_fast import interp
from common.params import Params
from common.realtime import Ratekeeper, Priority, config_realtime_process
@ -20,30 +21,36 @@ _LEAD_ACCEL_TAU = 1.5
SPEED, ACCEL = 0, 1 # Kalman filter states enum
# stationary qualification parameters
v_ego_stationary = 4. # no stationary object flag below this speed
V_EGO_STATIONARY = 4. # no stationary object flag below this speed
RADAR_TO_CENTER = 2.7 # (deprecated) RADAR is ~ 2.7m ahead from center of car
RADAR_TO_CAMERA = 1.52 # RADAR is ~ 1.5m ahead from center of mesh frame
def get_RadarState_from_vision(lead_msg, v_ego, model_v_ego):
lead_v_rel_pred = lead_msg.v[0] - model_v_ego
return {
"dRel": float(lead_msg.x[0] - RADAR_TO_CAMERA),
"yRel": float(-lead_msg.y[0]),
"vRel": float(lead_v_rel_pred),
"vLead": float(v_ego + lead_v_rel_pred),
"vLeadK": float(v_ego + lead_v_rel_pred),
"aLeadK": 0.0,
"aLeadTau": 0.3,
"fcw": False,
"modelProb": float(lead_msg.prob),
"radar": False,
"status": True
}
class KalmanParams:
def __init__(self, dt: float):
# Lead Kalman Filter params, calculating K from A, C, Q, R requires the control library.
# hardcoding a lookup table to compute K for values of radar_ts between 0.01s and 0.2s
assert dt > .01 and dt < .2, "Radar time step must be between .01s and 0.2s"
self.A = [[1.0, dt], [0.0, 1.0]]
self.C = [1.0, 0.0]
#Q = np.matrix([[10., 0.0], [0.0, 100.]])
#R = 1e3
#K = np.matrix([[ 0.05705578], [ 0.03073241]])
dts = [dt * 0.01 for dt in range(1, 21)]
K0 = [0.12287673, 0.14556536, 0.16522756, 0.18281627, 0.1988689, 0.21372394,
0.22761098, 0.24069424, 0.253096, 0.26491023, 0.27621103, 0.28705801,
0.29750003, 0.30757767, 0.31732515, 0.32677158, 0.33594201, 0.34485814,
0.35353899, 0.36200124]
K1 = [0.29666309, 0.29330885, 0.29042818, 0.28787125, 0.28555364, 0.28342219,
0.28144091, 0.27958406, 0.27783249, 0.27617149, 0.27458948, 0.27307714,
0.27162685, 0.27023228, 0.26888809, 0.26758976, 0.26633338, 0.26511557,
0.26393339, 0.26278425]
self.K = [[interp(dt, dts, K0)], [interp(dt, dts, K1)]]
class Track():
def __init__(self, v_lead, kalman_params):
class Track:
def __init__(self, v_lead: float, kalman_params: KalmanParams):
self.cnt = 0
self.aLeadTau = _LEAD_ACCEL_TAU
self.K_A = kalman_params.A
@ -51,7 +58,7 @@ class Track():
self.K_K = kalman_params.K
self.kf = KF1D([[v_lead], [0.0]], self.K_A, self.K_C, self.K_K)
def update(self, d_rel, y_rel, v_rel, v_lead, measured):
def update(self, d_rel: float, y_rel: float, v_rel: float, v_lead: float, measured: float):
# relative values, copy
self.dRel = d_rel # LONG_DIST
self.yRel = y_rel # -LAT_DIST
@ -78,13 +85,12 @@ class Track():
# Weigh y higher since radar is inaccurate in this dimension
return [self.dRel, self.yRel*2, self.vRel]
def reset_a_lead(self, aLeadK, aLeadTau):
def reset_a_lead(self, aLeadK: float, aLeadTau: float):
self.kf = KF1D([[self.vLead], [aLeadK]], self.K_A, self.K_C, self.K_K)
self.aLeadK = aLeadK
self.aLeadTau = aLeadTau
def get_RadarState(self, model_prob=0.0):
def get_RadarState(self, model_prob: float = 0.0):
return {
"dRel": float(self.dRel),
"yRel": float(self.yRel),
@ -99,47 +105,25 @@ class Track():
"aLeadTau": float(self.aLeadTau)
}
def __str__(self):
ret = f"x: {self.dRel:4.1f} y: {self.yRel:4.1f} v: {self.vRel:4.1f} a: {self.aLeadK:4.1f}"
return ret
def potential_low_speed_lead(self, v_ego):
def potential_low_speed_lead(self, v_ego: float):
# stop for stuff in front of you and low speed, even without model confirmation
# Radar points closer than 0.75, are almost always glitches on toyota radars
return abs(self.yRel) < 1.0 and (v_ego < v_ego_stationary) and (0.75 < self.dRel < 25)
return abs(self.yRel) < 1.0 and (v_ego < V_EGO_STATIONARY) and (0.75 < self.dRel < 25)
def is_potential_fcw(self, model_prob):
def is_potential_fcw(self, model_prob: float):
return model_prob > .9
class KalmanParams():
def __init__(self, dt):
# Lead Kalman Filter params, calculating K from A, C, Q, R requires the control library.
# hardcoding a lookup table to compute K for values of radar_ts between 0.01s and 0.2s
assert dt > .01 and dt < .2, "Radar time step must be between .01s and 0.2s"
self.A = [[1.0, dt], [0.0, 1.0]]
self.C = [1.0, 0.0]
#Q = np.matrix([[10., 0.0], [0.0, 100.]])
#R = 1e3
#K = np.matrix([[ 0.05705578], [ 0.03073241]])
dts = [dt * 0.01 for dt in range(1, 21)]
K0 = [0.12287673, 0.14556536, 0.16522756, 0.18281627, 0.1988689, 0.21372394,
0.22761098, 0.24069424, 0.253096, 0.26491023, 0.27621103, 0.28705801,
0.29750003, 0.30757767, 0.31732515, 0.32677158, 0.33594201, 0.34485814,
0.35353899, 0.36200124]
K1 = [0.29666309, 0.29330885, 0.29042818, 0.28787125, 0.28555364, 0.28342219,
0.28144091, 0.27958406, 0.27783249, 0.27617149, 0.27458948, 0.27307714,
0.27162685, 0.27023228, 0.26888809, 0.26758976, 0.26633338, 0.26511557,
0.26393339, 0.26278425]
self.K = [[interp(dt, dts, K0)], [interp(dt, dts, K1)]]
def __str__(self):
ret = f"x: {self.dRel:4.1f} y: {self.yRel:4.1f} v: {self.vRel:4.1f} a: {self.aLeadK:4.1f}"
return ret
def laplacian_pdf(x, mu, b):
def laplacian_pdf(x: float, mu: float, b: float):
b = max(b, 1e-4)
return math.exp(-abs(x-mu)/b)
def match_vision_to_track(v_ego, lead, tracks):
def match_vision_to_track(v_ego: float, lead: capnp._DynamicStructReader, tracks: Dict[int, Track]):
offset_vision_dist = lead.x[0] - RADAR_TO_CAMERA
def prob(c):
@ -162,7 +146,24 @@ def match_vision_to_track(v_ego, lead, tracks):
return None
def get_lead(v_ego, ready, tracks, lead_msg, model_v_ego, low_speed_override=True):
def get_RadarState_from_vision(lead_msg: capnp._DynamicStructReader, v_ego: float, model_v_ego: float):
lead_v_rel_pred = lead_msg.v[0] - model_v_ego
return {
"dRel": float(lead_msg.x[0] - RADAR_TO_CAMERA),
"yRel": float(-lead_msg.y[0]),
"vRel": float(lead_v_rel_pred),
"vLead": float(v_ego + lead_v_rel_pred),
"vLeadK": float(v_ego + lead_v_rel_pred),
"aLeadK": 0.0,
"aLeadTau": 0.3,
"fcw": False,
"modelProb": float(lead_msg.prob),
"radar": False,
"status": True
}
def get_lead(v_ego: float, ready: bool, tracks: Dict[int, Track], lead_msg: capnp._DynamicStructReader, model_v_ego: float, low_speed_override: bool = True) -> Dict[str, Any]:
# Determine leads, this is where the essential logic happens
if len(tracks) > 0 and ready and lead_msg.prob > .5:
track = match_vision_to_track(v_ego, lead_msg, tracks)
@ -187,22 +188,30 @@ def get_lead(v_ego, ready, tracks, lead_msg, model_v_ego, low_speed_override=Tru
return lead_dict
class RadarD():
def __init__(self, radar_ts, delay=0):
self.current_time = 0
class RadarD:
def __init__(self, radar_ts: float, delay: int = 0):
self.current_time = 0.0
self.tracks = defaultdict(dict)
self.tracks: Dict[int, Track] = {}
self.kalman_params = KalmanParams(radar_ts)
# v_ego
self.v_ego = 0.
self.v_ego_hist = deque([0], maxlen=delay+1)
self.v_ego = 0.0
self.v_ego_hist = deque([0.0], maxlen=delay+1)
self.radar_state: Optional[capnp._DynamicStructBuilder] = None
self.radar_state_valid = False
self.ready = False
def update(self, sm, rr):
def update(self, sm: messaging.SubMaster, rr: Optional[car.RadarData]):
self.current_time = 1e-9*max(sm.logMonoTime.values())
radar_points = []
radar_errors = []
if rr is not None:
radar_points = rr.points
radar_errors = rr.errors
if sm.updated['carState']:
self.v_ego = sm['carState'].vEgo
self.v_ego_hist.append(self.v_ego)
@ -210,7 +219,7 @@ class RadarD():
self.ready = True
ar_pts = {}
for pt in rr.points:
for pt in radar_points:
ar_pts[pt.trackId] = [pt.dRel, pt.yRel, pt.vRel, pt.measured]
# *** remove missing points from meta data ***
@ -231,12 +240,11 @@ class RadarD():
self.tracks[ids].update(rpt[0], rpt[1], rpt[2], v_lead, rpt[3])
# *** publish radarState ***
dat = messaging.new_message('radarState')
dat.valid = sm.all_checks() and len(rr.errors) == 0
radarState = dat.radarState
radarState.mdMonoTime = sm.logMonoTime['modelV2']
radarState.radarErrors = list(rr.errors)
radarState.carStateMonoTime = sm.logMonoTime['carState']
self.radar_state_valid = sm.all_checks() and len(radar_errors) == 0
self.radar_state = log.RadarState.new_message()
self.radar_state.mdMonoTime = sm.logMonoTime['modelV2']
self.radar_state.radarErrors = list(radar_errors)
self.radar_state.carStateMonoTime = sm.logMonoTime['carState']
if len(sm['modelV2'].temporalPose.trans):
model_v_ego = sm['modelV2'].temporalPose.trans[0]
@ -244,18 +252,38 @@ class RadarD():
model_v_ego = self.v_ego
leads_v3 = sm['modelV2'].leadsV3
if len(leads_v3) > 1:
radarState.leadOne = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[0], model_v_ego, low_speed_override=True)
radarState.leadTwo = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[1], model_v_ego, low_speed_override=False)
return dat
self.radar_state.leadOne = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[0], model_v_ego, low_speed_override=True)
self.radar_state.leadTwo = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[1], model_v_ego, low_speed_override=False)
def publish(self, pm: messaging.PubMaster, lag_ms: float):
assert self.radar_state is not None
radar_msg = messaging.new_message("radarState")
radar_msg.valid = self.radar_state_valid
radar_msg.radarState = self.radar_state
radar_msg.radarState.cumLagMs = lag_ms
pm.send("radarState", radar_msg)
# publish tracks for UI debugging (keep last)
tracks_msg = messaging.new_message('liveTracks', len(self.tracks))
for index, tid in enumerate(sorted(self.tracks.keys())):
tracks_msg.liveTracks[index] = {
"trackId": tid,
"dRel": float(self.tracks[tid].dRel),
"yRel": float(self.tracks[tid].yRel),
"vRel": float(self.tracks[tid].vRel),
}
pm.send('liveTracks', tracks_msg)
# fuses camera and radar data for best lead detection
def radard_thread(sm=None, pm=None, can_sock=None):
def radard_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[messaging.PubMaster] = None, can_sock: Optional[messaging.SubSocket] = None):
config_realtime_process(5, Priority.CTRL_LOW)
# wait for stats about the car to come in from controls
cloudlog.info("radard is waiting for CarParams")
CP = car.CarParams.from_bytes(Params().get("CarParams", block=True))
with car.CarParams.from_bytes(Params().get("CarParams", block=True)) as msg:
CP = msg
cloudlog.info("radard got CarParams")
# import the radar from the fingerprint
@ -284,28 +312,13 @@ def radard_thread(sm=None, pm=None, can_sock=None):
sm.update(0)
dat = RD.update(sm, rr)
dat.radarState.cumLagMs = -rk.remaining*1000.
pm.send('radarState', dat)
# *** publish tracks for UI debugging (keep last) ***
tracks = RD.tracks
dat = messaging.new_message('liveTracks', len(tracks))
for cnt, ids in enumerate(sorted(tracks.keys())):
dat.liveTracks[cnt] = {
"trackId": ids,
"dRel": float(tracks[ids].dRel),
"yRel": float(tracks[ids].yRel),
"vRel": float(tracks[ids].vRel),
}
pm.send('liveTracks', dat)
RD.update(sm, rr)
RD.publish(pm, -rk.remaining*1000.0)
rk.monitor_time()
def main(sm=None, pm=None, can_sock=None):
def main(sm: Optional[messaging.SubMaster] = None, pm: Optional[messaging.PubMaster] = None, can_sock: messaging.SubSocket = None):
radard_thread(sm, pm, can_sock)

@ -41,7 +41,8 @@ if __name__ == "__main__":
polld = poller.poll(100)
for sock in polld:
msg = sock.receive()
evt = log.Event.from_bytes(msg)
with log.Event.from_bytes(msg) as log_evt:
evt = log_evt
if not args.no_print:
if args.pipe:

@ -34,7 +34,8 @@ if __name__ == "__main__":
polld = poller.poll(1000)
for sock in polld:
msg = sock.receive()
evt = log.Event.from_bytes(msg)
with log.Event.from_bytes(msg) as log_evt:
evt = log_evt
for item in evt.can:
if item.address == 0xe4 and item.src == 128:

@ -25,7 +25,7 @@ if __name__ == "__main__":
# Remove message generated by the process under test and merge in the new messages
produces = {o.which() for o in outputs}
inputs = [i for i in inputs if i.which() not in produces]
outputs = sorted(inputs + outputs, key=lambda x: x.logMonoTime) # type: ignore
outputs = sorted(inputs + outputs, key=lambda x: x.logMonoTime)
fn = f"{args.route}_{args.process}.bz2"
save_log(fn, outputs)

@ -73,7 +73,7 @@ class Calibrator:
if param_put and calibration_params:
try:
msg = log.Event.from_bytes(calibration_params)
with log.Event.from_bytes(calibration_params) as msg:
rpy_init = np.array(msg.liveCalibration.rpyCalib)
valid_blocks = msg.liveCalibration.validBlocks
wide_from_device_euler = np.array(msg.liveCalibration.wideFromDeviceEuler)

@ -116,7 +116,7 @@ class Laikad:
nav_dict = {}
try:
ephem_cache = ephemeris_structs.EphemerisCache.from_bytes(cache_bytes)
with ephemeris_structs.EphemerisCache.from_bytes(cache_bytes) as ephem_cache:
glonass_navs = [GLONASSEphemeris(data_struct, file_name=EPHEMERIS_CACHE) for data_struct in ephem_cache.glonassEphemerides]
gps_navs = [GPSEphemeris(data_struct, file_name=EPHEMERIS_CACHE) for data_struct in ephem_cache.gpsEphemerides]
for e in sum([glonass_navs, gps_navs], []):

@ -129,7 +129,8 @@ def main(sm=None, pm=None):
params_reader = Params()
# wait for stats about the car to come in from controls
cloudlog.info("paramsd is waiting for CarParams")
CP = car.CarParams.from_bytes(params_reader.get("CarParams", block=True))
with car.CarParams.from_bytes(params_reader.get("CarParams", block=True)) as msg:
CP = msg
cloudlog.info("paramsd got CarParams")
min_sr, max_sr = 0.5 * CP.steerRatio, 2.0 * CP.steerRatio

@ -40,7 +40,8 @@ void localizer_handle_msg_bytes(Localizer_t localizer, const char *data, size_t
def localizer_get_msg(self, t=0, inputsOK=True, sensorsOK=True, gpsOK=True, msgValid=True):
self.lib.localizer_get_message_bytes(self.localizer, inputsOK, sensorsOK, gpsOK, msgValid, self.ffi.addressof(self.msg_buff, 0), self.buff_size)
return log.Event.from_bytes(self.ffi.buffer(self.msg_buff), nesting_limit=self.buff_size // 8)
with log.Event.from_bytes(self.ffi.buffer(self.msg_buff), nesting_limit=self.buff_size // 8) as log_evt:
return log_evt
def test_liblocalizer(self):
msg = messaging.new_message('liveCalibration')

@ -139,8 +139,10 @@ class TorqueEstimator:
torque_cache = params.get("LiveTorqueParameters")
if params_cache is not None and torque_cache is not None:
try:
cache_ltp = log.Event.from_bytes(torque_cache).liveTorqueParameters
cache_CP = car.CarParams.from_bytes(params_cache)
with log.Event.from_bytes(torque_cache) as log_evt:
cache_ltp = log_evt.liveTorqueParameters
with car.CarParams.from_bytes(params_cache) as msg:
cache_CP = msg
if self.get_restore_key(cache_CP, cache_ltp.version) == self.get_restore_key(CP, VERSION):
if cache_ltp.liveValid:
initial_params = {
@ -262,7 +264,7 @@ def main(sm=None, pm=None):
pm = messaging.PubMaster(['liveTorqueParameters'])
params = Params()
CP = car.CarParams.from_bytes(params.get("CarParams", block=True))
with car.CarParams.from_bytes(params.get("CarParams", block=True)) as CP:
estimator = TorqueEstimator(CP)
def cache_params(sig, frame):

@ -102,6 +102,7 @@ class RouteEngine:
new_destination = coordinate_from_param("NavDestination", self.params)
if new_destination is None:
self.clear_route()
self.reset_recompute_limits()
return
should_recompute = self.should_recompute()
@ -265,8 +266,7 @@ class RouteEngine:
if distance_to_maneuver_along_geometry < -MANEUVER_TRANSITION_THRESHOLD:
if self.step_idx + 1 < len(self.route):
self.step_idx += 1
self.recompute_backoff = 0
self.recompute_countdown = 0
self.reset_recompute_limits()
else:
cloudlog.warning("Destination reached")
Params().remove("NavDestination")
@ -293,6 +293,10 @@ class RouteEngine:
self.step_idx = None
self.nav_destination = None
def reset_recompute_limits(self):
self.recompute_backoff = 0
self.recompute_countdown = 0
def should_recompute(self):
if self.step_idx is None or self.route is None:
return True

@ -60,7 +60,7 @@ def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=Non
default_tolerance = EPSILON if tolerance is None else tolerance
log1, log2 = (
sorted((m for m in log if m.which() not in ignore_msgs), key=lambda m: (m.logMonoTime, m.which()))
[m for m in log if m.which() not in ignore_msgs]
for log in (log1, log2)
)

@ -7,7 +7,7 @@ from typing import List, Optional
from common.params import Params
class OpenpilotPrefix(object):
def __init__(self, prefix: str = None, clean_dirs_on_exit: bool = True):
def __init__(self, prefix: Optional[str] = None, clean_dirs_on_exit: bool = True):
self.prefix = prefix if prefix else str(uuid.uuid4())
self.msgq_path = os.path.join('/dev/shm', self.prefix)
self.clean_dirs_on_exit = clean_dirs_on_exit

@ -39,6 +39,7 @@ class ReplayContext:
self.pubs = cfg.pubs
self.main_pub = cfg.main_pub
self.main_pub_drained = cfg.main_pub_drained
self.unlocked_pubs = cfg.unlocked_pubs
assert(len(self.pubs) != 0 or self.main_pub is not None)
def __enter__(self):
@ -55,7 +56,8 @@ class ReplayContext:
if self.main_pub is None:
self.events = OrderedDict()
for pub in self.pubs:
pubs_with_events = [pub for pub in self.pubs if pub not in self.unlocked_pubs]
for pub in pubs_with_events:
self.events[pub] = messaging.fake_event_handle(pub, enable=True)
else:
self.events = {self.main_pub: messaging.fake_event_handle(self.main_pub, enable=True)}
@ -117,6 +119,7 @@ class ProcessConfig:
main_pub_drained: bool = True
vision_pubs: List[str] = field(default_factory=list)
ignore_alive_pubs: List[str] = field(default_factory=list)
unlocked_pubs: List[str] = field(default_factory=list)
class ProcessContainer:

@ -1,4 +1,5 @@
#!/usr/bin/env python3
import copy
from hypothesis import given, HealthCheck, Phase, settings
import hypothesis.strategies as st
from parameterized import parameterized
@ -13,7 +14,7 @@ import selfdrive.test.process_replay.process_replay as pr
# that openpilot makes causing error with NaN, inf, int size, array indexing ...
# TODO: Make each one testable
NOT_TESTED = ['controlsd', 'plannerd', 'calibrationd', 'dmonitoringd', 'paramsd', 'laikad', 'dmonitoringmodeld', 'modeld']
TEST_CASES = [(cfg.proc_name, cfg) for cfg in pr.CONFIGS if cfg.proc_name not in NOT_TESTED]
TEST_CASES = [(cfg.proc_name, copy.deepcopy(cfg)) for cfg in pr.CONFIGS if cfg.proc_name not in NOT_TESTED]
class TestFuzzProcesses(unittest.TestCase):
@ -24,7 +25,7 @@ class TestFuzzProcesses(unittest.TestCase):
msgs = FuzzyGenerator.get_random_event_msg(data.draw, events=cfg.pubs, real_floats=True)
lr = [log.Event.new_message(**m).as_reader() for m in msgs]
cfg.timeout = 5
pr.replay_process(cfg, lr, TOYOTA.COROLLA_TSS2, TOYOTA.COROLLA_TSS2, disable_progress=True)
pr.replay_process(cfg, lr, fingerprint=TOYOTA.COROLLA_TSS2, disable_progress=True)
if __name__ == "__main__":
unittest.main()

@ -29,7 +29,8 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/drive_stats.cc", "qt/
qt_env['CPPDEFINES'] = []
if maps:
base_libs += ['qmapboxgl']
widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc"]
widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc",
"qt/maps/map_eta.cc", "qt/maps/map_instructions.cc"]
qt_env['CPPDEFINES'] += ["ENABLE_MAPS"]
widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs)

@ -3,7 +3,6 @@
#include <eigen3/Eigen/Dense>
#include <QDebug>
#include <QDir>
#include "common/transformations/coordinates.hpp"
#include "selfdrive/ui/qt/maps/map_helpers.h"
@ -12,7 +11,6 @@
const int PAN_TIMEOUT = 100;
const float MANEUVER_TRANSITION_THRESHOLD = 10;
const float MAX_ZOOM = 17;
const float MIN_ZOOM = 14;
@ -20,8 +18,6 @@ const float MAX_PITCH = 50;
const float MIN_PITCH = 0;
const float MAP_SCALE = 2;
const QString ICON_SUFFIX = ".png";
MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings), velocity_filter(0, 10, 0.05) {
QObject::connect(uiState(), &UIState::uiUpdate, this, &MapWindow::updateState);
@ -121,11 +117,6 @@ void MapWindow::initLayers() {
pin["type"] = "symbol";
pin["source"] = "pinSource";
m_map->addLayer(pin);
// FIXME: solve, workaround to remove animation on visibility property
QVariantMap transition;
transition["duration"] = 0; // ms
m_map->setPaintProperty("pinLayer", "icon-opacity-transition", transition);
m_map->setLayoutProperty("pinLayer", "icon-pitch-alignment", "viewport");
m_map->setLayoutProperty("pinLayer", "icon-image", "default_marker");
m_map->setLayoutProperty("pinLayer", "icon-ignore-placement", true);
@ -214,7 +205,7 @@ void MapWindow::updateState(const UIState &s) {
if (!locationd_valid) {
setError(tr("Waiting for GPS"));
} else if (routing_problem) {
setError(tr("Waiting for internet"));
setError(tr("Waiting for route"));
} else {
setError("");
}
@ -246,6 +237,7 @@ void MapWindow::updateState(const UIState &s) {
// an invalid navInstruction packet with a nav destination is only possible if:
// - API exception/no internet
// - route response is empty
// - any time navd is waiting for recompute_countdown
auto dest = coordinate_from_param("NavDestination");
routing_problem = !sm.valid("navInstruction") && dest.has_value();
@ -309,6 +301,10 @@ void MapWindow::initializeGL() {
m_map->setStyleUrl("mapbox://styles/commaai/clj7g5vrp007b01qzb5ro0i4j");
QObject::connect(m_map.data(), &QMapboxGL::mapChanged, [=](QMapboxGL::MapChange change) {
// set global animation duration to 0 ms so visibility changes are instant
if (change == QMapboxGL::MapChange::MapChangeDidFinishLoadingStyle) {
m_map->setTransitionOptions(0, 0);
}
if (change == QMapboxGL::MapChange::MapChangeDidFinishLoadingMap) {
loaded_once = true;
}
@ -415,7 +411,7 @@ void MapWindow::offroadTransition(bool offroad) {
}
void MapWindow::updateDestinationMarker() {
m_map->setPaintProperty("pinLayer", "icon-opacity", 0);
m_map->setPaintProperty("pinLayer", "visibility", "none");
auto nav_dest = coordinate_from_param("NavDestination");
if (nav_dest.has_value()) {
@ -425,189 +421,6 @@ void MapWindow::updateDestinationMarker() {
pinSource["type"] = "geojson";
pinSource["data"] = QVariant::fromValue<QMapbox::Feature>(feature);
m_map->updateSource("pinSource", pinSource);
m_map->setPaintProperty("pinLayer", "icon-opacity", 1);
}
}
MapInstructions::MapInstructions(QWidget *parent) : QWidget(parent) {
is_rhd = Params().getBool("IsRhdDetected");
QHBoxLayout *main_layout = new QHBoxLayout(this);
main_layout->setContentsMargins(11, 50, 11, 11);
main_layout->addWidget(icon_01 = new QLabel, 0, Qt::AlignTop);
QWidget *right_container = new QWidget(this);
right_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
QVBoxLayout *layout = new QVBoxLayout(right_container);
layout->addWidget(distance = new QLabel);
distance->setStyleSheet(R"(font-size: 90px;)");
layout->addWidget(primary = new QLabel);
primary->setStyleSheet(R"(font-size: 60px;)");
primary->setWordWrap(true);
layout->addWidget(secondary = new QLabel);
secondary->setStyleSheet(R"(font-size: 50px;)");
secondary->setWordWrap(true);
layout->addLayout(lane_layout = new QHBoxLayout);
main_layout->addWidget(right_container);
setStyleSheet("color:white");
QPalette pal = palette();
pal.setColor(QPalette::Background, QColor(0, 0, 0, 150));
setAutoFillBackground(true);
setPalette(pal);
buildPixmapCache();
}
void MapInstructions::buildPixmapCache() {
QDir dir("../assets/navigation");
for (QString fn : dir.entryList({"*" + ICON_SUFFIX}, QDir::Files)) {
QPixmap pm(dir.filePath(fn));
QString key = fn.left(fn.size() - ICON_SUFFIX.length());
pm = pm.scaledToWidth(200, Qt::SmoothTransformation);
// Maneuver icons
pixmap_cache[key] = pm;
// lane direction icons
if (key.contains("turn_")) {
pixmap_cache["lane_" + key] = pm.scaled({125, 125}, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
// for rhd, reflect direction and then flip
if (key.contains("_left")) {
pixmap_cache["rhd_" + key.replace("_left", "_right")] = pm.transformed(QTransform().scale(-1, 1));
} else if (key.contains("_right")) {
pixmap_cache["rhd_" + key.replace("_right", "_left")] = pm.transformed(QTransform().scale(-1, 1));
}
}
}
QString MapInstructions::getDistance(float d) {
d = std::max(d, 0.0f);
if (uiState()->scene.is_metric) {
return (d > 500) ? QString::number(d / 1000, 'f', 1) + tr(" km")
: QString::number(50 * int(d / 50)) + tr(" m");
} else {
float feet = d * METER_TO_FOOT;
return (feet > 500) ? QString::number(d * METER_TO_MILE, 'f', 1) + tr(" mi")
: QString::number(50 * int(feet / 50)) + tr(" ft");
}
}
void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruction) {
setUpdatesEnabled(false);
// Show instruction text
QString primary_str = QString::fromStdString(instruction.getManeuverPrimaryText());
QString secondary_str = QString::fromStdString(instruction.getManeuverSecondaryText());
primary->setText(primary_str);
secondary->setVisible(secondary_str.length() > 0);
secondary->setText(secondary_str);
distance->setText(getDistance(instruction.getManeuverDistance()));
// Show arrow with direction
QString type = QString::fromStdString(instruction.getManeuverType());
QString modifier = QString::fromStdString(instruction.getManeuverModifier());
if (!type.isEmpty()) {
QString fn = "direction_" + type;
if (!modifier.isEmpty()) {
fn += "_" + modifier;
}
fn = fn.replace(' ', '_');
bool rhd = is_rhd && (fn.contains("_left") || fn.contains("_right"));
icon_01->setPixmap(pixmap_cache[!rhd ? fn : "rhd_" + fn]);
icon_01->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
icon_01->setVisible(true);
}
// Show lanes
auto lanes = instruction.getLanes();
for (int i = 0; i < lanes.size(); ++i) {
bool active = lanes[i].getActive();
// TODO: only use active direction if active
bool left = false, straight = false, right = false;
for (auto const &direction: lanes[i].getDirections()) {
left |= direction == cereal::NavInstruction::Direction::LEFT;
right |= direction == cereal::NavInstruction::Direction::RIGHT;
straight |= direction == cereal::NavInstruction::Direction::STRAIGHT;
}
// TODO: Make more images based on active direction and combined directions
QString fn = "lane_direction_";
if (left) {
fn += "turn_left";
} else if (right) {
fn += "turn_right";
} else if (straight) {
fn += "turn_straight";
}
if (!active) {
fn += "_inactive";
}
QLabel *label = (i < lane_labels.size()) ? lane_labels[i] : lane_labels.emplace_back(new QLabel);
if (!label->parentWidget()) {
lane_layout->addWidget(label);
}
label->setPixmap(pixmap_cache[fn]);
label->setVisible(true);
}
for (int i = lanes.size(); i < lane_labels.size(); ++i) {
lane_labels[i]->setVisible(false);
}
setUpdatesEnabled(true);
setVisible(true);
}
MapETA::MapETA(QWidget *parent) : QWidget(parent) {
setVisible(false);
setAttribute(Qt::WA_TranslucentBackground);
eta_doc.setUndoRedoEnabled(false);
eta_doc.setDefaultStyleSheet("body {font-family:Inter;font-size:60px;color:white;} b{font-size:70px;font-weight:600}");
}
void MapETA::paintEvent(QPaintEvent *event) {
if (!eta_doc.isEmpty()) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
p.setPen(Qt::NoPen);
p.setBrush(QColor(0, 0, 0, 150));
QSizeF txt_size = eta_doc.size();
p.drawRoundedRect((width() - txt_size.width()) / 2 - UI_BORDER_SIZE, 0, txt_size.width() + UI_BORDER_SIZE * 2, height() + 25, 25, 25);
p.translate((width() - txt_size.width()) / 2, (height() - txt_size.height()) / 2);
eta_doc.drawContents(&p);
m_map->setPaintProperty("pinLayer", "visibility", "visible");
}
}
void MapETA::updateETA(float s, float s_typical, float d) {
// ETA
auto eta_t = QDateTime::currentDateTime().addSecs(s).time();
auto eta = format_24h ? std::array{eta_t.toString("HH:mm"), tr("eta")}
: std::array{eta_t.toString("h:mm a").split(' ')[0], eta_t.toString("a")};
// Remaining time
auto remaining = s < 3600 ? std::array{QString::number(int(s / 60)), tr("min")}
: std::array{QString("%1:%2").arg((int)s / 3600).arg(((int)s % 3600) / 60, 2, 10, QLatin1Char('0')), tr("hr")};
QString color = "#25DA6E";
if (s / s_typical > 1.5) color = "#DA3025";
else if (s / s_typical > 1.2) color = "#DAA725";
// Distance
float num = uiState()->scene.is_metric ? (d / 1000.0) : (d * METER_TO_MILE);
auto distance = std::array{QString::number(num, 'f', num < 100 ? 1 : 0),
uiState()->scene.is_metric ? tr("km") : tr("mi")};
eta_doc.setHtml(QString(R"(<body><b>%1</b>%2 <span style="color:%3"><b>%4</b>%5</span> <b>%6</b>%7</body>)")
.arg(eta[0], eta[1], color, remaining[0], remaining[1], distance[0], distance[1]));
setVisible(d >= MANEUVER_TRANSITION_THRESHOLD);
update();
}

@ -4,8 +4,6 @@
#include <QGeoCoordinate>
#include <QGestureEvent>
#include <QHash>
#include <QHBoxLayout>
#include <QLabel>
#include <QMap>
#include <QMapboxGL>
@ -15,7 +13,6 @@
#include <QPushButton>
#include <QScopedPointer>
#include <QString>
#include <QTextDocument>
#include <QVBoxLayout>
#include <QWheelEvent>
@ -23,42 +20,8 @@
#include "common/params.h"
#include "common/util.h"
#include "selfdrive/ui/ui.h"
class MapInstructions : public QWidget {
Q_OBJECT
private:
QLabel *distance;
QLabel *primary;
QLabel *secondary;
QLabel *icon_01;
QHBoxLayout *lane_layout;
bool is_rhd = false;
std::vector<QLabel *> lane_labels;
QHash<QString, QPixmap> pixmap_cache;
public:
MapInstructions(QWidget * parent=nullptr);
void buildPixmapCache();
QString getDistance(float d);
void updateInstructions(cereal::NavInstruction::Reader instruction);
};
class MapETA : public QWidget {
Q_OBJECT
public:
MapETA(QWidget * parent=nullptr);
void updateETA(float seconds, float seconds_typical, float distance);
private:
void paintEvent(QPaintEvent *event) override;
void showEvent(QShowEvent *event) override { format_24h = param.getBool("NavSettingTime24h"); }
bool format_24h = false;
QTextDocument eta_doc;
Params param;
};
#include "selfdrive/ui/qt/maps/map_eta.h"
#include "selfdrive/ui/qt/maps/map_instructions.h"
class MapWindow : public QOpenGLWidget {
Q_OBJECT

@ -0,0 +1,57 @@
#include "selfdrive/ui/qt/maps/map_eta.h"
#include <QDateTime>
#include <QPainter>
#include "selfdrive/ui/ui.h"
const float MANEUVER_TRANSITION_THRESHOLD = 10;
MapETA::MapETA(QWidget *parent) : QWidget(parent) {
setVisible(false);
setAttribute(Qt::WA_TranslucentBackground);
eta_doc.setUndoRedoEnabled(false);
eta_doc.setDefaultStyleSheet("body {font-family:Inter;font-size:70px;color:white;} b{font-weight:600;} td{padding:0 3px;}");
}
void MapETA::paintEvent(QPaintEvent *event) {
if (!eta_doc.isEmpty()) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
p.setPen(Qt::NoPen);
p.setBrush(QColor(0, 0, 0, 150));
QSizeF txt_size = eta_doc.size();
p.drawRoundedRect((width() - txt_size.width()) / 2 - UI_BORDER_SIZE, 0, txt_size.width() + UI_BORDER_SIZE * 2, height() + 25, 25, 25);
p.translate((width() - txt_size.width()) / 2, (height() - txt_size.height()) / 2);
eta_doc.drawContents(&p);
}
}
void MapETA::updateETA(float s, float s_typical, float d) {
// ETA
auto eta_t = QDateTime::currentDateTime().addSecs(s).time();
auto eta = format_24h ? std::array{eta_t.toString("HH:mm"), tr("eta")}
: std::array{eta_t.toString("h:mm a").split(' ')[0], eta_t.toString("a")};
// Remaining time
auto remaining = s < 3600 ? std::array{QString::number(int(s / 60)), tr("min")}
: std::array{QString("%1:%2").arg((int)s / 3600).arg(((int)s % 3600) / 60, 2, 10, QLatin1Char('0')), tr("hr")};
QString color = "#25DA6E";
if (s / s_typical > 1.5)
color = "#DA3025";
else if (s / s_typical > 1.2)
color = "#DAA725";
// Distance
float num = uiState()->scene.is_metric ? (d / 1000.0) : (d * METER_TO_MILE);
auto distance = std::array{QString::number(num, 'f', num < 100 ? 1 : 0),
uiState()->scene.is_metric ? tr("km") : tr("mi")};
eta_doc.setHtml(QString(R"(<body><table><tr><td><b>%1</b></td><td>%2</td>
<td style="padding-left:40px;color:%3;"><b>%4</b></td><td style="padding-right:40px;color:%3;">%5</td>
<td><b>%6</b></td><td>%7</td></tr></body>)")
.arg(eta[0], eta[1], color, remaining[0], remaining[1], distance[0], distance[1]));
setVisible(d >= MANEUVER_TRANSITION_THRESHOLD);
update();
}

@ -0,0 +1,23 @@
#pragma once
#include <QPaintEvent>
#include <QTextDocument>
#include <QWidget>
#include "common/params.h"
class MapETA : public QWidget {
Q_OBJECT
public:
MapETA(QWidget * parent=nullptr);
void updateETA(float seconds, float seconds_typical, float distance);
private:
void paintEvent(QPaintEvent *event) override;
void showEvent(QShowEvent *event) override { format_24h = param.getBool("NavSettingTime24h"); }
bool format_24h = false;
QTextDocument eta_doc;
Params param;
};

@ -0,0 +1,146 @@
#include "selfdrive/ui/qt/maps/map_instructions.h"
#include <QDir>
#include <QVBoxLayout>
#include "selfdrive/ui/ui.h"
const QString ICON_SUFFIX = ".png";
MapInstructions::MapInstructions(QWidget *parent) : QWidget(parent) {
is_rhd = Params().getBool("IsRhdDetected");
QHBoxLayout *main_layout = new QHBoxLayout(this);
main_layout->setContentsMargins(11, 50, 11, 11);
main_layout->addWidget(icon_01 = new QLabel, 0, Qt::AlignTop);
QWidget *right_container = new QWidget(this);
right_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
QVBoxLayout *layout = new QVBoxLayout(right_container);
layout->addWidget(distance = new QLabel);
distance->setStyleSheet(R"(font-size: 90px;)");
layout->addWidget(primary = new QLabel);
primary->setStyleSheet(R"(font-size: 60px;)");
primary->setWordWrap(true);
layout->addWidget(secondary = new QLabel);
secondary->setStyleSheet(R"(font-size: 50px;)");
secondary->setWordWrap(true);
layout->addLayout(lane_layout = new QHBoxLayout);
main_layout->addWidget(right_container);
setStyleSheet("color:white");
QPalette pal = palette();
pal.setColor(QPalette::Background, QColor(0, 0, 0, 150));
setAutoFillBackground(true);
setPalette(pal);
buildPixmapCache();
}
void MapInstructions::buildPixmapCache() {
QDir dir("../assets/navigation");
for (QString fn : dir.entryList({"*" + ICON_SUFFIX}, QDir::Files)) {
QPixmap pm(dir.filePath(fn));
QString key = fn.left(fn.size() - ICON_SUFFIX.length());
pm = pm.scaledToWidth(200, Qt::SmoothTransformation);
// Maneuver icons
pixmap_cache[key] = pm;
// lane direction icons
if (key.contains("turn_")) {
pixmap_cache["lane_" + key] = pm.scaled({125, 125}, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
// for rhd, reflect direction and then flip
if (key.contains("_left")) {
pixmap_cache["rhd_" + key.replace("_left", "_right")] = pm.transformed(QTransform().scale(-1, 1));
} else if (key.contains("_right")) {
pixmap_cache["rhd_" + key.replace("_right", "_left")] = pm.transformed(QTransform().scale(-1, 1));
}
}
}
QString MapInstructions::getDistance(float d) {
d = std::max(d, 0.0f);
if (uiState()->scene.is_metric) {
return (d > 500) ? QString::number(d / 1000, 'f', 1) + tr(" km")
: QString::number(50 * int(d / 50)) + tr(" m");
} else {
float feet = d * METER_TO_FOOT;
return (feet > 500) ? QString::number(d * METER_TO_MILE, 'f', 1) + tr(" mi")
: QString::number(50 * int(feet / 50)) + tr(" ft");
}
}
void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruction) {
setUpdatesEnabled(false);
// Show instruction text
QString primary_str = QString::fromStdString(instruction.getManeuverPrimaryText());
QString secondary_str = QString::fromStdString(instruction.getManeuverSecondaryText());
primary->setText(primary_str);
secondary->setVisible(secondary_str.length() > 0);
secondary->setText(secondary_str);
distance->setText(getDistance(instruction.getManeuverDistance()));
// Show arrow with direction
QString type = QString::fromStdString(instruction.getManeuverType());
QString modifier = QString::fromStdString(instruction.getManeuverModifier());
if (!type.isEmpty()) {
QString fn = "direction_" + type;
if (!modifier.isEmpty()) {
fn += "_" + modifier;
}
fn = fn.replace(' ', '_');
bool rhd = is_rhd && (fn.contains("_left") || fn.contains("_right"));
icon_01->setPixmap(pixmap_cache[!rhd ? fn : "rhd_" + fn]);
icon_01->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
icon_01->setVisible(true);
}
// Show lanes
auto lanes = instruction.getLanes();
for (int i = 0; i < lanes.size(); ++i) {
bool active = lanes[i].getActive();
// TODO: only use active direction if active
bool left = false, straight = false, right = false;
for (auto const &direction : lanes[i].getDirections()) {
left |= direction == cereal::NavInstruction::Direction::LEFT;
right |= direction == cereal::NavInstruction::Direction::RIGHT;
straight |= direction == cereal::NavInstruction::Direction::STRAIGHT;
}
// TODO: Make more images based on active direction and combined directions
QString fn = "lane_direction_";
if (left) {
fn += "turn_left";
} else if (right) {
fn += "turn_right";
} else if (straight) {
fn += "turn_straight";
}
if (!active) {
fn += "_inactive";
}
QLabel *label = (i < lane_labels.size()) ? lane_labels[i] : lane_labels.emplace_back(new QLabel);
if (!label->parentWidget()) {
lane_layout->addWidget(label);
}
label->setPixmap(pixmap_cache[fn]);
label->setVisible(true);
}
for (int i = lanes.size(); i < lane_labels.size(); ++i) {
lane_labels[i]->setVisible(false);
}
setUpdatesEnabled(true);
setVisible(true);
}

@ -0,0 +1,27 @@
#pragma once
#include <QHash>
#include <QHBoxLayout>
#include <QLabel>
#include "cereal/gen/cpp/log.capnp.h"
class MapInstructions : public QWidget {
Q_OBJECT
private:
QLabel *distance;
QLabel *primary;
QLabel *secondary;
QLabel *icon_01;
QHBoxLayout *lane_layout;
bool is_rhd = false;
std::vector<QLabel *> lane_labels;
QHash<QString, QPixmap> pixmap_cache;
public:
MapInstructions(QWidget * parent=nullptr);
void buildPixmapCache();
QString getDistance(float d);
void updateInstructions(cereal::NavInstruction::Reader instruction);
};

@ -7,10 +7,6 @@
#include "selfdrive/ui/qt/request_repeater.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
static QString shorten(const QString &str, int max_len) {
return str.size() > max_len ? str.left(max_len).trimmed() + "" : str;
}
MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) {
close_icon = loadPixmap("../assets/icons/close.svg", {100, 100});
setContentsMargins(0, 0, 0, 0);
@ -253,9 +249,8 @@ void DestinationWidget::set(const QJsonObject &destination, bool current) {
icon->setPixmap(icon_pixmap);
// TODO: onroad and offroad have different dimensions
title->setText(shorten(title_text, 26));
subtitle->setText(shorten(subtitle_text, 26));
title->setText(title_text);
subtitle->setText(subtitle_text);
subtitle->setVisible(true);
// TODO: use pixmap

@ -404,8 +404,8 @@
<translation>Warten auf GPS</translation>
</message>
<message>
<source>Waiting for internet</source>
<translation type="unfinished">Auf Internet warten</translation>
<source>Waiting for route</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>

@ -403,8 +403,8 @@
<translation>GPS信号を探しています</translation>
</message>
<message>
<source>Waiting for internet</source>
<translation type="unfinished"></translation>
<source>Waiting for route</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>

@ -403,8 +403,8 @@
<translation>GPS </translation>
</message>
<message>
<source>Waiting for internet</source>
<translation type="unfinished"> </translation>
<source>Waiting for route</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>

@ -404,8 +404,8 @@
<translation>Esperando por GPS</translation>
</message>
<message>
<source>Waiting for internet</source>
<translation type="unfinished">Esperando pela internet</translation>
<source>Waiting for route</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>

@ -403,8 +403,8 @@
<translation> GPS</translation>
</message>
<message>
<source>Waiting for internet</source>
<translation type="unfinished"></translation>
<source>Waiting for route</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>

@ -403,8 +403,8 @@
<translation> GPS</translation>
</message>
<message>
<source>Waiting for internet</source>
<translation type="unfinished"></translation>
<source>Waiting for route</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>

@ -1,19 +1,19 @@
[
{
"name": "boot",
"url": "https://commadist.azureedge.net/agnosupdate/boot-7d953f5e1bc606984e4d49c6f957421a4172f72b4ebd359baa689ef43b7e911c.img.xz",
"hash": "7d953f5e1bc606984e4d49c6f957421a4172f72b4ebd359baa689ef43b7e911c",
"hash_raw": "7d953f5e1bc606984e4d49c6f957421a4172f72b4ebd359baa689ef43b7e911c",
"size": 15153152,
"url": "https://commadist.azureedge.net/agnosupdate/boot-8d8d8620de8b2687f3a8fffdb81b2abd1fe2ead5bc831361a1a212e5589ac279.img.xz",
"hash": "8d8d8620de8b2687f3a8fffdb81b2abd1fe2ead5bc831361a1a212e5589ac279",
"hash_raw": "8d8d8620de8b2687f3a8fffdb81b2abd1fe2ead5bc831361a1a212e5589ac279",
"size": 15636480,
"sparse": false,
"full_check": true,
"has_ab": true
},
{
"name": "abl",
"url": "https://commadist.azureedge.net/agnosupdate/abl-50329ac734ff7a6c20c3f552dce9b13f84b3eb2e73faa64b9810049d9b406602.img.xz",
"hash": "50329ac734ff7a6c20c3f552dce9b13f84b3eb2e73faa64b9810049d9b406602",
"hash_raw": "50329ac734ff7a6c20c3f552dce9b13f84b3eb2e73faa64b9810049d9b406602",
"url": "https://commadist.azureedge.net/agnosupdate/abl-0084fcf79fea067632a1c2d9519b6445ad484aa8b09f49f22e6b45b4dccacd2d.img.xz",
"hash": "0084fcf79fea067632a1c2d9519b6445ad484aa8b09f49f22e6b45b4dccacd2d",
"hash_raw": "0084fcf79fea067632a1c2d9519b6445ad484aa8b09f49f22e6b45b4dccacd2d",
"size": 274432,
"sparse": false,
"full_check": true,
@ -21,9 +21,9 @@
},
{
"name": "xbl",
"url": "https://commadist.azureedge.net/agnosupdate/xbl-dc297986b38f50c47584bd8549b188b37b1d6a0c77b3255859dd675c177b5c15.img.xz",
"hash": "dc297986b38f50c47584bd8549b188b37b1d6a0c77b3255859dd675c177b5c15",
"hash_raw": "dc297986b38f50c47584bd8549b188b37b1d6a0c77b3255859dd675c177b5c15",
"url": "https://commadist.azureedge.net/agnosupdate/xbl-942b9b2914d89c2a70fdf27380b59e04b549ac2fd53ecb29d6549d1a9c8daeaa.img.xz",
"hash": "942b9b2914d89c2a70fdf27380b59e04b549ac2fd53ecb29d6549d1a9c8daeaa",
"hash_raw": "942b9b2914d89c2a70fdf27380b59e04b549ac2fd53ecb29d6549d1a9c8daeaa",
"size": 3282672,
"sparse": false,
"full_check": true,
@ -31,9 +31,9 @@
},
{
"name": "xbl_config",
"url": "https://commadist.azureedge.net/agnosupdate/xbl_config-b73fbbb42934aabc6d4f16ce84ac6c8c0205bc70e0a85412a771f3cc1d62cc40.img.xz",
"hash": "b73fbbb42934aabc6d4f16ce84ac6c8c0205bc70e0a85412a771f3cc1d62cc40",
"hash_raw": "b73fbbb42934aabc6d4f16ce84ac6c8c0205bc70e0a85412a771f3cc1d62cc40",
"url": "https://commadist.azureedge.net/agnosupdate/xbl_config-6881d94599f65d94c13bcc0bd860184dfba2dfe96ec776d08fb35ac5b5f85bbf.img.xz",
"hash": "6881d94599f65d94c13bcc0bd860184dfba2dfe96ec776d08fb35ac5b5f85bbf",
"hash_raw": "6881d94599f65d94c13bcc0bd860184dfba2dfe96ec776d08fb35ac5b5f85bbf",
"size": 98124,
"sparse": false,
"full_check": true,
@ -41,9 +41,9 @@
},
{
"name": "devcfg",
"url": "https://commadist.azureedge.net/agnosupdate/devcfg-2d3063d106813006ac9ceeaf8818a31d4b33996873e81178ac5129f5e1b82bca.img.xz",
"hash": "2d3063d106813006ac9ceeaf8818a31d4b33996873e81178ac5129f5e1b82bca",
"hash_raw": "2d3063d106813006ac9ceeaf8818a31d4b33996873e81178ac5129f5e1b82bca",
"url": "https://commadist.azureedge.net/agnosupdate/devcfg-9bbf168baff6101f4890c5c95c118e30813c2610cfb35b8e19e363f04a32a262.img.xz",
"hash": "9bbf168baff6101f4890c5c95c118e30813c2610cfb35b8e19e363f04a32a262",
"hash_raw": "9bbf168baff6101f4890c5c95c118e30813c2610cfb35b8e19e363f04a32a262",
"size": 40336,
"sparse": false,
"full_check": true,
@ -51,9 +51,9 @@
},
{
"name": "aop",
"url": "https://commadist.azureedge.net/agnosupdate/aop-d69450d5438b3e5e2ba5b77db1ae49e1cf9cab17836f563aa57192b5b3a4ac3e.img.xz",
"hash": "d69450d5438b3e5e2ba5b77db1ae49e1cf9cab17836f563aa57192b5b3a4ac3e",
"hash_raw": "d69450d5438b3e5e2ba5b77db1ae49e1cf9cab17836f563aa57192b5b3a4ac3e",
"url": "https://commadist.azureedge.net/agnosupdate/aop-c1d9d712980f6b2a4b12196597f4d1bf3fe4fec6c59edf29ae63ef21f11b8222.img.xz",
"hash": "c1d9d712980f6b2a4b12196597f4d1bf3fe4fec6c59edf29ae63ef21f11b8222",
"hash_raw": "c1d9d712980f6b2a4b12196597f4d1bf3fe4fec6c59edf29ae63ef21f11b8222",
"size": 184364,
"sparse": false,
"full_check": true,
@ -61,9 +61,9 @@
},
{
"name": "system",
"url": "https://commadist.azureedge.net/agnosupdate/system-4a8311dd591006e0c2a6f60060d6ef579ceec9b3d688e8438a9aef4e230ae028.img.xz",
"hash": "23c9f111f81fc3ee83f85016cb320e03a46aad6721a85e1b4a3f04b6a764e934",
"hash_raw": "4a8311dd591006e0c2a6f60060d6ef579ceec9b3d688e8438a9aef4e230ae028",
"url": "https://commadist.azureedge.net/agnosupdate/system-e1fa3018bce9bad01c6967e5e21f1141cf5c8f02d2edfaed51c738f74a32a432.img.xz",
"hash": "611011f3e3f147bc24f371105a9dd3760ec11ba424c56d4a442a66b098c784c0",
"hash_raw": "e1fa3018bce9bad01c6967e5e21f1141cf5c8f02d2edfaed51c738f74a32a432",
"size": 10737418240,
"sparse": true,
"full_check": false,

@ -56,7 +56,7 @@ if __name__ == "__main__":
pr_err /= len(car)
speed_err /= len(car)
print("avg psuedorange err %f avg speed err %f" % (pr_err, speed_err))
for c in sorted(car, key=lambda x: abs(x[1] - x[3] - pr_err)): # type: ignore
for c in sorted(car, key=lambda x: abs(x[1] - x[3] - pr_err)):
svid, ublox_psuedorange, ublox_speed, qcom_psuedorange, qcom_speed, cno = c
print("svid: %3d pseudorange: %10.2f m speed: %8.2f m/s meas: %12.2f speed: %10.2f meas_err: %10.3f speed_err: %8.3f cno: %d" %
(svid, ublox_psuedorange, ublox_speed, qcom_psuedorange, qcom_speed,

Loading…
Cancel
Save