diff --git a/.github/ISSUE_TEMPLATE/car_bug_report.yml b/.github/ISSUE_TEMPLATE/car_bug_report.yml
index 23527c3a43..7f368f11b4 100644
--- a/.github/ISSUE_TEMPLATE/car_bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/car_bug_report.yml
@@ -1,6 +1,6 @@
name: Car bug report
description: For issues with a particular car make or model
-labels: ["car bug"]
+labels: ["car", "bug"]
body:
- type: markdown
diff --git a/docs/CARS.md b/docs/CARS.md
index 75c5ce4c05..61c3fa2ce7 100644
--- a/docs/CARS.md
+++ b/docs/CARS.md
@@ -150,7 +150,7 @@ How We Rate The Cars
|Volkswagen|Taos 2022[8](#footnotes)|Driver Assistance|
|
|
|
|
|
|Volkswagen|Touran 2017|Driver Assistance|
|
|
|
|
|
-# Bronze - 72 cars
+# Bronze - 73 cars
|Make|Model|Supported Package|openpilot ACC|Stop and Go|Steer to 0|Steering Torque|Actively Maintained|
|---|---|---|:---:|:---:|:---:|:---:|:---:|
@@ -173,6 +173,7 @@ How We Rate The Cars
|Honda|Civic 2019-20|All|
|
|
[3](#footnotes)|
|
|
|Honda|Civic 2022|All|
|
|
|
|
|
|Honda|Civic Hatchback 2017-21|Honda Sensing|
|
|
|
|
|
+|Honda|Civic Hatchback 2022|All|
|
|
|
|
|
|Honda|CR-V 2015-16|Touring|
|
|
|
|
|
|Honda|CR-V 2017-21|Honda Sensing|
|
|
|
|
|
|Honda|CR-V Hybrid 2017-19|Honda Sensing|
|
|
|
|
|
diff --git a/laika_repo b/laika_repo
index 44f048bc1f..27a0d8a776 160000
--- a/laika_repo
+++ b/laika_repo
@@ -1 +1 @@
-Subproject commit 44f048bc1f58ae9e28dfdeb98e40aea3e0f2b699
+Subproject commit 27a0d8a776fc8c1eaf8608d17ce81a00136f8bd0
diff --git a/release/files_common b/release/files_common
index 7274ee9bf6..96649b9f16 100644
--- a/release/files_common
+++ b/release/files_common
@@ -101,6 +101,7 @@ selfdrive/car/interfaces.py
selfdrive/car/vin.py
selfdrive/car/disable_ecu.py
selfdrive/car/fw_versions.py
+selfdrive/car/ecu_addrs.py
selfdrive/car/isotp_parallel_query.py
selfdrive/car/tests/__init__.py
selfdrive/car/tests/test_car_interfaces.py
diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py
index 3bfd99f88d..8c0fd7c90d 100644
--- a/selfdrive/car/car_helpers.py
+++ b/selfdrive/car/car_helpers.py
@@ -8,7 +8,7 @@ from system.version import is_comma_remote, is_tested_branch
from selfdrive.car.interfaces import get_interface_attr
from selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars
from selfdrive.car.vin import get_vin, VIN_UNKNOWN
-from selfdrive.car.fw_versions import get_fw_versions, match_fw_to_car
+from selfdrive.car.fw_versions import get_fw_versions, match_fw_to_car, get_present_ecus
from system.swaglog import cloudlog
import cereal.messaging as messaging
from selfdrive.car import gen_empty_fingerprint
@@ -79,6 +79,7 @@ interfaces = load_interfaces(interface_names)
def fingerprint(logcan, sendcan):
fixed_fingerprint = os.environ.get('FINGERPRINT', "")
skip_fw_query = os.environ.get('SKIP_FW_QUERY', False)
+ ecu_responses = set()
if not fixed_fingerprint and not skip_fw_query:
# Vin query only reliably works thorugh OBDII
@@ -97,6 +98,7 @@ def fingerprint(logcan, sendcan):
else:
cloudlog.warning("Getting VIN & FW versions")
_, vin = get_vin(logcan, sendcan, bus)
+ ecu_responses = get_present_ecus(logcan, sendcan)
car_fw = get_fw_versions(logcan, sendcan)
exact_fw_match, fw_candidates = match_fw_to_car(car_fw)
@@ -163,8 +165,8 @@ def fingerprint(logcan, sendcan):
car_fingerprint = fixed_fingerprint
source = car.CarParams.FingerprintSource.fixed
- cloudlog.event("fingerprinted", car_fingerprint=car_fingerprint,
- source=source, fuzzy=not exact_match, fw_count=len(car_fw))
+ cloudlog.event("fingerprinted", car_fingerprint=car_fingerprint, source=source, fuzzy=not exact_match,
+ fw_count=len(car_fw), ecu_responses=ecu_responses, error=True)
return car_fingerprint, finger, vin, car_fw, source, exact_match
diff --git a/selfdrive/car/ecu_addrs.py b/selfdrive/car/ecu_addrs.py
new file mode 100755
index 0000000000..267701509a
--- /dev/null
+++ b/selfdrive/car/ecu_addrs.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python3
+import capnp
+import time
+import traceback
+from typing import Optional, Set, Tuple
+
+import cereal.messaging as messaging
+from panda.python.uds import SERVICE_TYPE
+from selfdrive.car import make_can_msg
+from selfdrive.boardd.boardd import can_list_to_can_capnp
+from system.swaglog import cloudlog
+
+
+def make_tester_present_msg(addr, bus, subaddr=None):
+ dat = [0x02, SERVICE_TYPE.TESTER_PRESENT, 0x0]
+ if subaddr is not None:
+ dat.insert(0, subaddr)
+
+ dat.extend([0x0] * (8 - len(dat)))
+ return make_can_msg(addr, bytes(dat), bus)
+
+
+def is_tester_present_response(msg: capnp.lib.capnp._DynamicStructReader, subaddr: Optional[int] = None) -> bool:
+ # ISO-TP messages are always padded to 8 bytes
+ # tester present response is always a single frame
+ dat_offset = 1 if subaddr is not None else 0
+ if len(msg.dat) == 8 and 1 <= msg.dat[dat_offset] <= 7:
+ # success response
+ if msg.dat[dat_offset + 1] == (SERVICE_TYPE.TESTER_PRESENT + 0x40):
+ return True
+ # error response
+ if msg.dat[dat_offset + 1] == 0x7F and msg.dat[dat_offset + 2] == SERVICE_TYPE.TESTER_PRESENT:
+ return True
+ return False
+
+
+def get_all_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, bus: int, timeout: float = 1, debug: bool = True) -> Set[Tuple[int, Optional[int], int]]:
+ addr_list = [0x700 + i for i in range(256)] + [0x18da00f1 + (i << 8) for i in range(256)]
+ queries: Set[Tuple[int, Optional[int], int]] = {(addr, None, bus) for addr in addr_list}
+ responses = queries
+ return get_ecu_addrs(logcan, sendcan, queries, responses, timeout=timeout, debug=debug)
+
+
+def get_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, queries: Set[Tuple[int, Optional[int], int]],
+ responses: Set[Tuple[int, Optional[int], int]], timeout: float = 1, debug: bool = False) -> Set[Tuple[int, Optional[int], int]]:
+ ecu_responses: Set[Tuple[int, Optional[int], int]] = set() # set((addr, subaddr, bus),)
+ try:
+ msgs = [make_tester_present_msg(addr, bus, subaddr) for addr, subaddr, bus in queries]
+
+ messaging.drain_sock_raw(logcan)
+ sendcan.send(can_list_to_can_capnp(msgs, msgtype='sendcan'))
+ start_time = time.monotonic()
+ while time.monotonic() - start_time < timeout:
+ can_packets = messaging.drain_sock(logcan, wait_for_one=True)
+ for packet in can_packets:
+ for msg in packet.can:
+ subaddr = None if (msg.address, None, msg.src) in responses else msg.dat[0]
+ if (msg.address, subaddr, msg.src) in responses and is_tester_present_response(msg, subaddr):
+ if debug:
+ print(f"CAN-RX: {hex(msg.address)} - 0x{bytes.hex(msg.dat)}")
+ if (msg.address, subaddr, msg.src) in ecu_responses:
+ print(f"Duplicate ECU address: {hex(msg.address)}")
+ ecu_responses.add((msg.address, subaddr, msg.src))
+ except Exception:
+ cloudlog.warning(f"ECU addr scan exception: {traceback.format_exc()}")
+ return ecu_responses
+
+
+if __name__ == "__main__":
+ import argparse
+
+ parser = argparse.ArgumentParser(description='Get addresses of all ECUs')
+ parser.add_argument('--debug', action='store_true')
+ args = parser.parse_args()
+
+ logcan = messaging.sub_sock('can')
+ sendcan = messaging.pub_sock('sendcan')
+
+ time.sleep(1.0)
+
+ print("Getting ECU addresses ...")
+ ecu_addrs = get_all_ecu_addrs(logcan, sendcan, 1, debug=args.debug)
+
+ print()
+ print("Found ECUs on addresses:")
+ for addr, subaddr, bus in ecu_addrs:
+ msg = f" 0x{hex(addr)}"
+ if subaddr is not None:
+ msg += f" (sub-address: 0x{hex(subaddr)})"
+ print(msg)
diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py
index a03b6b881d..758485c393 100755
--- a/selfdrive/car/fw_versions.py
+++ b/selfdrive/car/fw_versions.py
@@ -3,11 +3,12 @@ import struct
import traceback
from collections import defaultdict
from dataclasses import dataclass, field
-from typing import Any, List
+from typing import Any, List, Optional, Set, Tuple
from tqdm import tqdm
import panda.python.uds as uds
from cereal import car
+from selfdrive.car.ecu_addrs import get_ecu_addrs
from selfdrive.car.interfaces import get_interface_attr
from selfdrive.car.fingerprints import FW_VERSIONS
from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery
@@ -15,6 +16,7 @@ from selfdrive.car.toyota.values import CAR as TOYOTA
from system.swaglog import cloudlog
Ecu = car.CarParams.Ecu
+ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.esp, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa]
def p16(val):
@@ -146,14 +148,12 @@ REQUESTS: List[Request] = [
"volkswagen",
[VOLKSWAGEN_VERSION_REQUEST_MULTI],
[VOLKSWAGEN_VERSION_RESPONSE],
- whitelist_ecus=[Ecu.srs, Ecu.eps, Ecu.fwdRadar],
rx_offset=VOLKSWAGEN_RX_OFFSET,
),
Request(
"volkswagen",
[VOLKSWAGEN_VERSION_REQUEST_MULTI],
[VOLKSWAGEN_VERSION_RESPONSE],
- whitelist_ecus=[Ecu.engine, Ecu.transmission],
),
# Mazda
Request(
@@ -261,7 +261,6 @@ def match_fw_to_car_exact(fw_versions_dict):
ecu_type = ecu[0]
addr = ecu[1:]
found_version = fw_versions_dict.get(addr, None)
- ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.esp, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa]
if ecu_type == Ecu.esp and candidate in (TOYOTA.RAV4, TOYOTA.COROLLA, TOYOTA.HIGHLANDER, TOYOTA.SIENNA, TOYOTA.LEXUS_IS) and found_version is None:
continue
@@ -299,11 +298,46 @@ def match_fw_to_car(fw_versions, allow_fuzzy=True):
return exact_match, matches
+def get_present_ecus(logcan, sendcan):
+ queries = list()
+ parallel_queries = list()
+ responses = set()
+ versions = get_interface_attr('FW_VERSIONS', ignore_none=True)
+
+ for r in REQUESTS:
+ if r.brand not in versions:
+ continue
+
+ for brand_versions in versions[r.brand].values():
+ for ecu_type, addr, sub_addr in brand_versions:
+ # Only query ecus in whitelist if whitelist is not empty
+ if len(r.whitelist_ecus) == 0 or ecu_type in r.whitelist_ecus:
+ a = (addr, sub_addr, r.bus)
+ # Build set of queries
+ if sub_addr is None:
+ if a not in parallel_queries:
+ parallel_queries.append(a)
+ else: # subaddresses must be queried one by one
+ if [a] not in queries:
+ queries.append([a])
+
+ # Build set of expected responses to filter
+ response_addr = uds.get_rx_addr_for_tx_addr(addr, r.rx_offset)
+ responses.add((response_addr, sub_addr, r.bus))
+
+ queries.insert(0, parallel_queries)
+
+ ecu_responses: Set[Tuple[int, Optional[int], int]] = set()
+ for query in queries:
+ ecu_responses.update(get_ecu_addrs(logcan, sendcan, set(query), responses, timeout=0.1))
+ return ecu_responses
+
+
def get_fw_versions(logcan, sendcan, extra=None, timeout=0.1, debug=False, progress=False):
ecu_types = {}
# Extract ECU addresses to query from fingerprints
- # ECUs using a subadress need be queried one by one, the rest can be done in parallel
+ # ECUs using a subaddress need be queried one by one, the rest can be done in parallel
addrs = []
parallel_addrs = []
diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py
index 0bdb8ccd91..c6e20f2d83 100644
--- a/selfdrive/car/honda/values.py
+++ b/selfdrive/car/honda/values.py
@@ -119,7 +119,10 @@ CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = {
HondaCarInfo("Honda Civic Hatchback 2017-21", harness=Harness.bosch_a),
],
CAR.CIVIC_BOSCH_DIESEL: None, # same platform
- CAR.CIVIC_2022: HondaCarInfo("Honda Civic 2022", "All", min_steer_speed=0., harness=Harness.bosch_b),
+ CAR.CIVIC_2022: [
+ HondaCarInfo("Honda Civic 2022", "All", min_steer_speed=0., harness=Harness.bosch_b),
+ HondaCarInfo("Honda Civic Hatchback 2022", "All", min_steer_speed=0., harness=Harness.bosch_b),
+ ],
CAR.ACURA_ILX: HondaCarInfo("Acura ILX 2016-19", "AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS, harness=Harness.nidec),
CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring", harness=Harness.nidec),
CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-21", harness=Harness.bosch_a),
diff --git a/selfdrive/car/mazda/carstate.py b/selfdrive/car/mazda/carstate.py
index 6e1ad3e481..944d79809b 100644
--- a/selfdrive/car/mazda/carstate.py
+++ b/selfdrive/car/mazda/carstate.py
@@ -79,6 +79,7 @@ class CarState(CarStateBase):
# it should be used for carState.cruiseState.nonAdaptive instead
ret.cruiseState.available = cp.vl["CRZ_CTRL"]["CRZ_AVAILABLE"] == 1
ret.cruiseState.enabled = cp.vl["CRZ_CTRL"]["CRZ_ACTIVE"] == 1
+ ret.cruiseState.standstill = cp.vl["PEDALS"]["STANDSTILL"] == 1
ret.cruiseState.speed = cp.vl["CRZ_EVENTS"]["CRZ_SPEED"] * CV.KPH_TO_MS
if ret.cruiseState.enabled:
diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py
index 27dc4ba776..cb4b868582 100755
--- a/selfdrive/car/tests/test_models.py
+++ b/selfdrive/car/tests/test_models.py
@@ -221,6 +221,7 @@ class TestCarModelBase(unittest.TestCase):
# TODO: check rest of panda's carstate (steering, ACC main on, etc.)
checks['gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev()
+ checks['cruiseState'] += CS.cruiseState.enabled and not CS.cruiseState.available
# TODO: remove this exception once this mismatch is resolved
brake_pressed = CS.brakePressed
diff --git a/selfdrive/modeld/models/driving.h b/selfdrive/modeld/models/driving.h
index a691051636..97e65fbc0e 100644
--- a/selfdrive/modeld/models/driving.h
+++ b/selfdrive/modeld/models/driving.h
@@ -245,7 +245,7 @@ struct ModelOutput {
constexpr int OUTPUT_SIZE = sizeof(ModelOutput) / sizeof(float);
#ifdef TEMPORAL
- constexpr int TEMPORAL_SIZE = 512+256;
+ constexpr int TEMPORAL_SIZE = 512;
#else
constexpr int TEMPORAL_SIZE = 0;
#endif
diff --git a/selfdrive/modeld/models/supercombo.dlc b/selfdrive/modeld/models/supercombo.dlc
index 90f7a2e65b..23f6d904fb 100644
--- a/selfdrive/modeld/models/supercombo.dlc
+++ b/selfdrive/modeld/models/supercombo.dlc
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4c2cb3a3054f3292bbe538d6b793908dc2e234c200802d41b6766d3cb51b0b44
-size 101662751
+oid sha256:027cbb1fabae369878271cb0e3505071a8bdaa07473fad9a0b2e8d695c5dc1ff
+size 76725611
diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx
index 0493398560..9023c18dd7 100644
--- a/selfdrive/modeld/models/supercombo.onnx
+++ b/selfdrive/modeld/models/supercombo.onnx
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:96b60d0bfd1386c93b4f79195aa1c5e77b23e0250578a308ee2c58857ed5eb49
-size 102570834
+oid sha256:484976ea5bd4ddcabc82e95faf30d7311a27802c1e337472558699fa2395a499
+size 77472267
diff --git a/selfdrive/modeld/thneed/compile.cc b/selfdrive/modeld/thneed/compile.cc
index a2f55ffd97..f76c63b2b9 100644
--- a/selfdrive/modeld/thneed/compile.cc
+++ b/selfdrive/modeld/thneed/compile.cc
@@ -5,7 +5,7 @@
#include "selfdrive/modeld/thneed/thneed.h"
#include "system/hardware/hw.h"
-#define TEMPORAL_SIZE 512+256
+#define TEMPORAL_SIZE 512
#define DESIRE_LEN 8
#define TRAFFIC_CONVENTION_LEN 2
diff --git a/selfdrive/modeld/thneed/optimizer.cc b/selfdrive/modeld/thneed/optimizer.cc
index 39737d3d76..03d20ff386 100644
--- a/selfdrive/modeld/thneed/optimizer.cc
+++ b/selfdrive/modeld/thneed/optimizer.cc
@@ -9,7 +9,7 @@
extern map g_program_source;
-/*static int is_same_size_image(cl_mem a, cl_mem b) {
+static int is_same_size_image(cl_mem a, cl_mem b) {
size_t a_width, a_height, a_depth, a_array_size, a_row_pitch, a_slice_pitch;
clGetImageInfo(a, CL_IMAGE_WIDTH, sizeof(a_width), &a_width, NULL);
clGetImageInfo(a, CL_IMAGE_HEIGHT, sizeof(a_height), &a_height, NULL);
@@ -29,7 +29,7 @@ extern map g_program_source;
return (a_width == b_width) && (a_height == b_height) &&
(a_depth == b_depth) && (a_array_size == b_array_size) &&
(a_row_pitch == b_row_pitch) && (a_slice_pitch == b_slice_pitch);
-}*/
+}
static cl_mem make_image_like(cl_context context, cl_mem val) {
cl_image_format format;
@@ -138,7 +138,7 @@ int Thneed::optimize() {
// delete useless copy layers
// saves ~0.7 ms
- /*if (kq[i]->name == "concatenation" || kq[i]->name == "flatten") {
+ if (kq[i]->name == "concatenation" || kq[i]->name == "flatten") {
string in = kq[i]->args[kq[i]->get_arg_num("input")];
string out = kq[i]->args[kq[i]->get_arg_num("output")];
if (is_same_size_image(*(cl_mem*)in.data(), *(cl_mem*)out.data())) {
@@ -148,7 +148,7 @@ int Thneed::optimize() {
kq.erase(kq.begin()+i); --i;
}
- }*/
+ }
// NOTE: if activations/accumulation are done in the wrong order, this will be wrong
diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit
index 0c4c2305eb..9f9b822693 100644
--- a/selfdrive/test/process_replay/model_replay_ref_commit
+++ b/selfdrive/test/process_replay/model_replay_ref_commit
@@ -1 +1 @@
-5434b3c1696554e9a889e77f794d80cd1cb0a7ec
+df0ce74929dd6b5fa7a55224baefeff4bac6d785
diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc
index 45bf41ba2d..d92cf36b89 100644
--- a/selfdrive/ui/qt/onroad.cc
+++ b/selfdrive/ui/qt/onroad.cc
@@ -196,6 +196,7 @@ void NvgWindow::updateState(const UIState &s) {
setProperty("has_eu_speed_limit", nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA);
setProperty("is_cruise_set", cruise_set);
+ setProperty("is_metric", s.scene.is_metric);
setProperty("speed", cur_speed);
setProperty("setSpeed", set_speed);
setProperty("speedUnit", s.scene.is_metric ? "km/h" : "mph");
@@ -225,8 +226,8 @@ void NvgWindow::drawHud(QPainter &p) {
// Draw outer box + border to contain set speed and speed limit
int default_rect_width = 172;
int rect_width = default_rect_width;
+ if (is_metric || has_eu_speed_limit) rect_width = 200;
if (has_us_speed_limit && speedLimitStr.size() >= 3) rect_width = 223;
- else if (has_eu_speed_limit) rect_width = 200;
int rect_height = 204;
if (has_us_speed_limit) rect_height = 402;
@@ -240,26 +241,6 @@ void NvgWindow::drawHud(QPainter &p) {
p.setBrush(blackColor(166));
drawRoundedRect(p, set_speed_rect, top_radius, top_radius, bottom_radius, bottom_radius);
- // Draw set speed
- if (is_cruise_set) {
- if (speedLimit > 0 && status != STATUS_DISENGAGED && status != STATUS_OVERRIDE) {
- p.setPen(interpColor(
- setSpeed,
- {speedLimit + 5, speedLimit + 15, speedLimit + 25},
- {whiteColor(), QColor(0xff, 0x95, 0x00, 0xff), QColor(0xff, 0x00, 0x00, 0xff)}
- ));
- } else {
- p.setPen(whiteColor());
- }
- } else {
- p.setPen(QColor(0x72, 0x72, 0x72, 0xff));
- }
- configFont(p, "Open Sans", 90, "Bold");
- QRect speed_rect = getTextRect(p, Qt::AlignCenter, setSpeedStr);
- speed_rect.moveCenter({set_speed_rect.center().x(), 0});
- speed_rect.moveTop(set_speed_rect.top() + 8);
- p.drawText(speed_rect, Qt::AlignCenter, setSpeedStr);
-
// Draw MAX
if (is_cruise_set) {
if (status == STATUS_DISENGAGED) {
@@ -281,9 +262,31 @@ void NvgWindow::drawHud(QPainter &p) {
configFont(p, "Open Sans", 40, "SemiBold");
QRect max_rect = getTextRect(p, Qt::AlignCenter, "MAX");
max_rect.moveCenter({set_speed_rect.center().x(), 0});
- max_rect.moveTop(set_speed_rect.top() + 123);
+ max_rect.moveTop(set_speed_rect.top() + 23);
p.drawText(max_rect, Qt::AlignCenter, "MAX");
+ // Draw set speed
+ if (is_cruise_set) {
+ if (speedLimit > 0 && status != STATUS_DISENGAGED && status != STATUS_OVERRIDE) {
+ p.setPen(interpColor(
+ setSpeed,
+ {speedLimit + 5, speedLimit + 15, speedLimit + 25},
+ {whiteColor(), QColor(0xff, 0x95, 0x00, 0xff), QColor(0xff, 0x00, 0x00, 0xff)}
+ ));
+ } else {
+ p.setPen(whiteColor());
+ }
+ } else {
+ p.setPen(QColor(0x72, 0x72, 0x72, 0xff));
+ }
+ configFont(p, "Open Sans", 90, "Bold");
+ QRect speed_rect = getTextRect(p, Qt::AlignCenter, setSpeedStr);
+ speed_rect.moveCenter({set_speed_rect.center().x(), 0});
+ speed_rect.moveTop(set_speed_rect.top() + 67);
+ p.drawText(speed_rect, Qt::AlignCenter, setSpeedStr);
+
+
+
// US/Canada (MUTCD style) sign
if (has_us_speed_limit) {
const int border_width = 6;
diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h
index e58d7a7972..4cd88fc9e3 100644
--- a/selfdrive/ui/qt/onroad.h
+++ b/selfdrive/ui/qt/onroad.h
@@ -34,6 +34,7 @@ class NvgWindow : public CameraViewWidget {
Q_PROPERTY(bool is_cruise_set MEMBER is_cruise_set);
Q_PROPERTY(bool has_eu_speed_limit MEMBER has_eu_speed_limit);
Q_PROPERTY(bool has_us_speed_limit MEMBER has_us_speed_limit);
+ Q_PROPERTY(bool is_metric MEMBER is_metric);
Q_PROPERTY(bool engageable MEMBER engageable);
Q_PROPERTY(bool dmActive MEMBER dmActive);
@@ -57,6 +58,7 @@ private:
float setSpeed;
float speedLimit;
bool is_cruise_set = false;
+ bool is_metric = false;
bool engageable = false;
bool dmActive = false;
bool hideDM = false;
diff --git a/selfdrive/ui/replay/replay.cc b/selfdrive/ui/replay/replay.cc
index 5eb7469c92..cf7520b361 100644
--- a/selfdrive/ui/replay/replay.cc
+++ b/selfdrive/ui/replay/replay.cc
@@ -360,7 +360,8 @@ void Replay::stream() {
setCurrentSegment(toSeconds(cur_mono_time_) / 60);
// migration for pandaState -> pandaStates to keep UI working for old segments
- if (cur_which == cereal::Event::Which::PANDA_STATE_D_E_P_R_E_C_A_T_E_D) {
+ if (cur_which == cereal::Event::Which::PANDA_STATE_D_E_P_R_E_C_A_T_E_D &&
+ sockets_[cereal::Event::Which::PANDA_STATES] != nullptr) {
MessageBuilder msg;
auto ps = msg.initEvent().initPandaStates(1);
ps[0].setIgnitionLine(true);