Merge remote-tracking branch 'upstream/master' into toyota-lta

pull/27494/head
Shane Smiskol 3 years ago
commit 28aa01e3e8
  1. 1
      RELEASES.md
  2. 2
      cereal
  3. 34
      common/prefix.h
  4. 13
      common/util.cc
  5. 1
      common/util.h
  6. 5
      docs/CARS.md
  7. 2
      opendbc
  8. 1
      release/files_common
  9. 221
      selfdrive/athena/athenad.py
  10. 10
      selfdrive/athena/tests/test_athenad.py
  11. 9
      selfdrive/car/gm/gmcan.py
  12. 7
      selfdrive/car/honda/hondacan.py
  13. 2
      selfdrive/car/hyundai/carcontroller.py
  14. 22
      selfdrive/car/hyundai/carstate.py
  15. 7
      selfdrive/car/hyundai/interface.py
  16. 25
      selfdrive/car/hyundai/values.py
  17. 1
      selfdrive/car/tests/routes.py
  18. 1
      selfdrive/car/torque_data/override.yaml
  19. 2
      selfdrive/car/toyota/values.py
  20. 4
      selfdrive/car/volkswagen/values.py
  21. 5
      selfdrive/controls/controlsd.py
  22. 6
      selfdrive/controls/lib/events.py
  23. 1
      selfdrive/navd/SConscript
  24. 16
      selfdrive/sensord/sensors/bmx055_accel.cc
  25. 16
      selfdrive/sensord/sensors/bmx055_gyro.cc
  26. 15
      selfdrive/sensord/sensors/bmx055_magn.cc
  27. 18
      selfdrive/sensord/sensors/bmx055_temp.cc
  28. 16
      selfdrive/sensord/sensors/i2c_sensor.h
  29. 17
      selfdrive/sensord/sensors/lsm6ds3_accel.cc
  30. 17
      selfdrive/sensord/sensors/lsm6ds3_gyro.cc
  31. 22
      selfdrive/sensord/sensors/lsm6ds3_temp.cc
  32. 16
      selfdrive/sensord/sensors/mmc5603nj_magn.cc
  33. 2
      selfdrive/test/process_replay/ref_commit
  34. 2
      selfdrive/ui/qt/offroad/driverview.cc
  35. 2
      selfdrive/ui/qt/widgets/cameraview.h
  36. 1
      selfdrive/ui/translations/languages.json
  37. 1113
      selfdrive/ui/translations/main_de.ts
  38. 10
      system/camerad/cameras/camera_qcom2.cc
  39. 2
      system/camerad/cameras/camera_qcom2.h
  40. 12
      tools/cabana/cabana.cc
  41. 62
      tools/cabana/canmessages.cc
  42. 9
      tools/cabana/canmessages.h
  43. 24
      tools/cabana/chartswidget.cc
  44. 9
      tools/cabana/chartswidget.h
  45. 1
      tools/cabana/dbcmanager.cc
  46. 12
      tools/cabana/detailwidget.cc
  47. 4
      tools/cabana/detailwidget.h
  48. 156
      tools/cabana/historylog.cc
  49. 44
      tools/cabana/historylog.h
  50. 9
      tools/cabana/settings.cc
  51. 2
      tools/cabana/settings.h
  52. 75
      tools/cabana/signaledit.cc
  53. 7
      tools/cabana/signaledit.h
  54. 4
      tools/replay/main.cc
  55. 27
      tools/replay/replay.cc
  56. 7
      tools/replay/replay.h

@ -3,6 +3,7 @@ Version 0.9.1 (2022-12-XX)
* Adjust alert volume using ambient noise level * Adjust alert volume using ambient noise level
* Removed driver monitoring timer resetting on interaction if face detected and distracted * Removed driver monitoring timer resetting on interaction if face detected and distracted
* Chevrolet Bolt EV 2022-23 support thanks to JasonJShuler! * Chevrolet Bolt EV 2022-23 support thanks to JasonJShuler!
* Genesis GV60 2023 support thanks to sunnyhaibin!
* Hyundai Tucson 2022-23 support * Hyundai Tucson 2022-23 support
* Kia Sorento Plug-in Hybrid 2022 support thanks to sunnyhaibin! * Kia Sorento Plug-in Hybrid 2022 support thanks to sunnyhaibin!

@ -1 +1 @@
Subproject commit 439429cad4d1e2ab874520cb5d4db8b8d978cbde Subproject commit 22b1431132b038253a24ab3fbbe3af36ef93b95b

@ -0,0 +1,34 @@
#pragma once
#include <cassert>
#include <string>
#include "common/params.h"
#include "common/util.h"
class OpenpilotPrefix {
public:
OpenpilotPrefix(std::string prefix = {}) {
if (prefix.empty()) {
prefix = util::random_string(15);
}
msgq_path = "/dev/shm/" + prefix;
bool ret = util::create_directories(msgq_path, 0777);
assert(ret);
setenv("OPENPILOT_PREFIX", prefix.c_str(), 1);
}
~OpenpilotPrefix() {
auto param_path = Params().getParamPath();
if (util::file_exists(param_path)) {
std::string real_path = util::readlink(param_path);
system(util::string_format("rm %s -rf", real_path.c_str()).c_str());
unlink(param_path.c_str());
}
system(util::string_format("rm %s -rf", msgq_path.c_str()).c_str());
unsetenv("OPENPILOT_PREFIX");
}
private:
std::string msgq_path;
};

@ -10,6 +10,7 @@
#include <dirent.h> #include <dirent.h>
#include <fstream> #include <fstream>
#include <iomanip> #include <iomanip>
#include <random>
#include <sstream> #include <sstream>
#ifdef __linux__ #ifdef __linux__
@ -228,6 +229,18 @@ std::string hexdump(const uint8_t* in, const size_t size) {
return ss.str(); return ss.str();
} }
std::string random_string(std::string::size_type length) {
const char* chrs = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
std::mt19937 rg{std::random_device{}()};
std::uniform_int_distribution<std::string::size_type> pick(0, sizeof(chrs) - 2);
std::string s;
s.reserve(length);
while (length--) {
s += chrs[pick(rg)];
}
return s;
}
std::string dir_name(std::string const &path) { std::string dir_name(std::string const &path) {
size_t pos = path.find_last_of("/"); size_t pos = path.find_last_of("/");
if (pos == std::string::npos) return ""; if (pos == std::string::npos) return "";

@ -75,6 +75,7 @@ int getenv(const char* key, int default_val);
float getenv(const char* key, float default_val); float getenv(const char* key, float default_val);
std::string hexdump(const uint8_t* in, const size_t size); std::string hexdump(const uint8_t* in, const size_t size);
std::string random_string(std::string::size_type length);
std::string dir_name(std::string const& path); std::string dir_name(std::string const& path);
// **** file fhelpers ***** // **** file fhelpers *****

@ -4,7 +4,7 @@
A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system.
# 219 Supported Cars # 220 Supported Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness| |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|
@ -32,6 +32,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|Genesis|G70 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| |Genesis|G70 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F|
|Genesis|G80 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Genesis|G80 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|
|Genesis|G90 2017-18|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Genesis|G90 2017-18|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|
|Genesis|GV60 2023[<sup>5</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|
|Genesis|GV70 2022-23[<sup>5</sup>](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| |Genesis|GV70 2022-23[<sup>5</sup>](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|
|GMC|Acadia 2018[<sup>3</sup>](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| |GMC|Acadia 2018[<sup>3</sup>](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|
|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| |GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|
@ -60,7 +61,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| |Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|
|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|
|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai J| |Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai J|
|Hyundai|i30 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| |Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|
|Hyundai|Ioniq 5 (with HDA II) 2022-23[<sup>5</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q| |Hyundai|Ioniq 5 (with HDA II) 2022-23[<sup>5</sup>](#footnotes)|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q|
|Hyundai|Ioniq 5 (without HDA II) 2022-23[<sup>5</sup>](#footnotes)|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |Hyundai|Ioniq 5 (without HDA II) 2022-23[<sup>5</sup>](#footnotes)|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|
|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|

@ -1 +1 @@
Subproject commit 1f8aa057bc1c96fcf8a2b612a9897ce91e627381 Subproject commit 4a7ad636ff806146a93f7ae541e463a7dfa5696d

@ -149,6 +149,7 @@ selfdrive/debug/vw_mqb_config.py
common/SConscript common/SConscript
common/version.h common/version.h
common/prefix.h
common/swaglog.h common/swaglog.h
common/swaglog.cc common/swaglog.cc
common/statlog.h common/statlog.h

@ -1,4 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import base64 import base64
import bz2 import bz2
import hashlib import hashlib
@ -14,14 +16,15 @@ import sys
import tempfile import tempfile
import threading import threading
import time import time
from collections import namedtuple from dataclasses import asdict, dataclass, replace
from datetime import datetime from datetime import datetime
from functools import partial from functools import partial
from typing import Any, Dict from queue import Queue
from typing import BinaryIO, Callable, Dict, List, Optional, Set, Union, cast
import requests import requests
from jsonrpc import JSONRPCResponseManager, dispatcher from jsonrpc import JSONRPCResponseManager, dispatcher
from websocket import (ABNF, WebSocketException, WebSocketTimeoutException, from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutException,
create_connection) create_connection)
import cereal.messaging as messaging import cereal.messaging as messaging
@ -54,19 +57,54 @@ WS_FRAME_SIZE = 4096
NetworkType = log.DeviceState.NetworkType NetworkType = log.DeviceState.NetworkType
UploadFileDict = Dict[str, Union[str, int, float, bool]]
UploadItemDict = Dict[str, Union[str, bool, int, float, Dict[str, str]]]
UploadFilesToUrlResponse = Dict[str, Union[int, List[UploadItemDict], List[str]]]
@dataclass
class UploadFile:
fn: str
url: str
headers: Dict[str, str]
allow_cellular: bool
@classmethod
def from_dict(cls, d: Dict) -> UploadFile:
return cls(d.get("fn", ""), d.get("url", ""), d.get("headers", {}), d.get("allow_cellular", False))
@dataclass
class UploadItem:
path: str
url: str
headers: Dict[str, str]
created_at: int
id: Optional[str]
retry_count: int = 0
current: bool = False
progress: float = 0
allow_cellular: bool = False
@classmethod
def from_dict(cls, d: Dict) -> UploadItem:
return cls(d["path"], d["url"], d["headers"], d["created_at"], d["id"], d["retry_count"], d["current"],
d["progress"], d["allow_cellular"])
dispatcher["echo"] = lambda s: s dispatcher["echo"] = lambda s: s
recv_queue: Any = queue.Queue() recv_queue: Queue[str] = queue.Queue()
send_queue: Any = queue.Queue() send_queue: Queue[str] = queue.Queue()
upload_queue: Any = queue.Queue() upload_queue: Queue[UploadItem] = queue.Queue()
low_priority_send_queue: Any = queue.Queue() low_priority_send_queue: Queue[str] = queue.Queue()
log_recv_queue: Any = queue.Queue() log_recv_queue: Queue[str] = queue.Queue()
cancelled_uploads: Any = set() cancelled_uploads: Set[str] = set()
UploadItem = namedtuple('UploadItem', ['path', 'url', 'headers', 'created_at', 'id', 'retry_count', 'current', 'progress', 'allow_cellular'], defaults=(0, False, 0, False))
cur_upload_items: Dict[int, Any] = {} cur_upload_items: Dict[int, Optional[UploadItem]] = {}
def strip_bz2_extension(fn): def strip_bz2_extension(fn: str) -> str:
if fn.endswith('.bz2'): if fn.endswith('.bz2'):
return fn[:-4] return fn[:-4]
return fn return fn
@ -76,29 +114,30 @@ class AbortTransferException(Exception):
pass pass
class UploadQueueCache(): class UploadQueueCache:
params = Params() params = Params()
@staticmethod @staticmethod
def initialize(upload_queue): def initialize(upload_queue: Queue[UploadItem]) -> None:
try: try:
upload_queue_json = UploadQueueCache.params.get("AthenadUploadQueue") upload_queue_json = UploadQueueCache.params.get("AthenadUploadQueue")
if upload_queue_json is not None: if upload_queue_json is not None:
for item in json.loads(upload_queue_json): for item in json.loads(upload_queue_json):
upload_queue.put(UploadItem(**item)) upload_queue.put(UploadItem.from_dict(item))
except Exception: except Exception:
cloudlog.exception("athena.UploadQueueCache.initialize.exception") cloudlog.exception("athena.UploadQueueCache.initialize.exception")
@staticmethod @staticmethod
def cache(upload_queue): def cache(upload_queue: Queue[UploadItem]) -> None:
try: try:
items = [i._asdict() for i in upload_queue.queue if i.id not in cancelled_uploads] queue: List[Optional[UploadItem]] = list(upload_queue.queue)
items = [asdict(i) for i in queue if i is not None and (i.id not in cancelled_uploads)]
UploadQueueCache.params.put("AthenadUploadQueue", json.dumps(items)) UploadQueueCache.params.put("AthenadUploadQueue", json.dumps(items))
except Exception: except Exception:
cloudlog.exception("athena.UploadQueueCache.cache.exception") cloudlog.exception("athena.UploadQueueCache.cache.exception")
def handle_long_poll(ws): def handle_long_poll(ws: WebSocket) -> None:
end_event = threading.Event() end_event = threading.Event()
threads = [ threads = [
@ -126,7 +165,7 @@ def handle_long_poll(ws):
thread.join() thread.join()
def jsonrpc_handler(end_event): def jsonrpc_handler(end_event: threading.Event) -> None:
dispatcher["startLocalProxy"] = partial(startLocalProxy, end_event) dispatcher["startLocalProxy"] = partial(startLocalProxy, end_event)
while not end_event.is_set(): while not end_event.is_set():
try: try:
@ -147,11 +186,12 @@ def jsonrpc_handler(end_event):
def retry_upload(tid: int, end_event: threading.Event, increase_count: bool = True) -> None: def retry_upload(tid: int, end_event: threading.Event, increase_count: bool = True) -> None:
if cur_upload_items[tid].retry_count < MAX_RETRY_COUNT:
item = cur_upload_items[tid] item = cur_upload_items[tid]
if item is not None and item.retry_count < MAX_RETRY_COUNT:
new_retry_count = item.retry_count + 1 if increase_count else item.retry_count new_retry_count = item.retry_count + 1 if increase_count else item.retry_count
item = item._replace( item = replace(
item,
retry_count=new_retry_count, retry_count=new_retry_count,
progress=0, progress=0,
current=False current=False
@ -175,44 +215,44 @@ def upload_handler(end_event: threading.Event) -> None:
cur_upload_items[tid] = None cur_upload_items[tid] = None
try: try:
cur_upload_items[tid] = upload_queue.get(timeout=1)._replace(current=True) cur_upload_items[tid] = item = replace(upload_queue.get(timeout=1), current=True)
if cur_upload_items[tid].id in cancelled_uploads: if item.id in cancelled_uploads:
cancelled_uploads.remove(cur_upload_items[tid].id) cancelled_uploads.remove(item.id)
continue continue
# Remove item if too old # Remove item if too old
age = datetime.now() - datetime.fromtimestamp(cur_upload_items[tid].created_at / 1000) age = datetime.now() - datetime.fromtimestamp(item.created_at / 1000)
if age.total_seconds() > MAX_AGE: if age.total_seconds() > MAX_AGE:
cloudlog.event("athena.upload_handler.expired", item=cur_upload_items[tid], error=True) cloudlog.event("athena.upload_handler.expired", item=item, error=True)
continue continue
# Check if uploading over metered connection is allowed # Check if uploading over metered connection is allowed
sm.update(0) sm.update(0)
metered = sm['deviceState'].networkMetered metered = sm['deviceState'].networkMetered
network_type = sm['deviceState'].networkType.raw network_type = sm['deviceState'].networkType.raw
if metered and (not cur_upload_items[tid].allow_cellular): if metered and (not item.allow_cellular):
retry_upload(tid, end_event, False) retry_upload(tid, end_event, False)
continue continue
try: try:
def cb(sz, cur): def cb(sz: int, cur: int) -> None:
# Abort transfer if connection changed to metered after starting upload # Abort transfer if connection changed to metered after starting upload
sm.update(0) sm.update(0)
metered = sm['deviceState'].networkMetered metered = sm['deviceState'].networkMetered
if metered and (not cur_upload_items[tid].allow_cellular): if metered and (not item.allow_cellular):
raise AbortTransferException raise AbortTransferException
cur_upload_items[tid] = cur_upload_items[tid]._replace(progress=cur / sz if sz else 1) cur_upload_items[tid] = replace(item, progress=cur / sz if sz else 1)
fn = cur_upload_items[tid].path fn = item.path
try: try:
sz = os.path.getsize(fn) sz = os.path.getsize(fn)
except OSError: except OSError:
sz = -1 sz = -1
cloudlog.event("athena.upload_handler.upload_start", fn=fn, sz=sz, network_type=network_type, metered=metered, retry_count=cur_upload_items[tid].retry_count) cloudlog.event("athena.upload_handler.upload_start", fn=fn, sz=sz, network_type=network_type, metered=metered, retry_count=item.retry_count)
response = _do_upload(cur_upload_items[tid], cb) response = _do_upload(item, cb)
if response.status_code not in (200, 201, 401, 403, 412): if response.status_code not in (200, 201, 401, 403, 412):
cloudlog.event("athena.upload_handler.retry", status_code=response.status_code, fn=fn, sz=sz, network_type=network_type, metered=metered) cloudlog.event("athena.upload_handler.retry", status_code=response.status_code, fn=fn, sz=sz, network_type=network_type, metered=metered)
@ -234,7 +274,7 @@ def upload_handler(end_event: threading.Event) -> None:
cloudlog.exception("athena.upload_handler.exception") cloudlog.exception("athena.upload_handler.exception")
def _do_upload(upload_item, callback=None): def _do_upload(upload_item: UploadItem, callback: Optional[Callable] = None) -> requests.Response:
path = upload_item.path path = upload_item.path
compress = False compress = False
@ -244,27 +284,25 @@ def _do_upload(upload_item, callback=None):
compress = True compress = True
with open(path, "rb") as f: with open(path, "rb") as f:
data: BinaryIO
if compress: if compress:
cloudlog.event("athena.upload_handler.compress", fn=path, fn_orig=upload_item.path) cloudlog.event("athena.upload_handler.compress", fn=path, fn_orig=upload_item.path)
data = bz2.compress(f.read()) compressed = bz2.compress(f.read())
size = len(data) size = len(compressed)
data = io.BytesIO(data) data = io.BytesIO(compressed)
else: else:
size = os.fstat(f.fileno()).st_size size = os.fstat(f.fileno()).st_size
data = f data = f
if callback:
data = CallbackReader(data, callback, size)
return requests.put(upload_item.url, return requests.put(upload_item.url,
data=data, data=CallbackReader(data, callback, size) if callback else data,
headers={**upload_item.headers, 'Content-Length': str(size)}, headers={**upload_item.headers, 'Content-Length': str(size)},
timeout=30) timeout=30)
# security: user should be able to request any message from their car # security: user should be able to request any message from their car
@dispatcher.add_method @dispatcher.add_method
def getMessage(service=None, timeout=1000): def getMessage(service: str, timeout: int = 1000) -> Dict:
if service is None or service not in service_list: if service is None or service not in service_list:
raise Exception("invalid service") raise Exception("invalid service")
@ -274,7 +312,8 @@ def getMessage(service=None, timeout=1000):
if ret is None: if ret is None:
raise TimeoutError raise TimeoutError
return ret.to_dict() # this is because capnp._DynamicStructReader doesn't have typing information
return cast(Dict, ret.to_dict())
@dispatcher.add_method @dispatcher.add_method
@ -288,7 +327,7 @@ def getVersion() -> Dict[str, str]:
@dispatcher.add_method @dispatcher.add_method
def setNavDestination(latitude=0, longitude=0, place_name=None, place_details=None): def setNavDestination(latitude: int = 0, longitude: int = 0, place_name: Optional[str] = None, place_details: Optional[str] = None) -> Dict[str, int]:
destination = { destination = {
"latitude": latitude, "latitude": latitude,
"longitude": longitude, "longitude": longitude,
@ -300,8 +339,8 @@ def setNavDestination(latitude=0, longitude=0, place_name=None, place_details=No
return {"success": 1} return {"success": 1}
def scan_dir(path, prefix): def scan_dir(path: str, prefix: str) -> List[str]:
files = list() files = []
# only walk directories that match the prefix # only walk directories that match the prefix
# (glob and friends traverse entire dir tree) # (glob and friends traverse entire dir tree)
with os.scandir(path) as i: with os.scandir(path) as i:
@ -320,18 +359,18 @@ def scan_dir(path, prefix):
return files return files
@dispatcher.add_method @dispatcher.add_method
def listDataDirectory(prefix=''): def listDataDirectory(prefix='') -> List[str]:
return scan_dir(ROOT, prefix) return scan_dir(ROOT, prefix)
@dispatcher.add_method @dispatcher.add_method
def reboot(): def reboot() -> Dict[str, int]:
sock = messaging.sub_sock("deviceState", timeout=1000) sock = messaging.sub_sock("deviceState", timeout=1000)
ret = messaging.recv_one(sock) ret = messaging.recv_one(sock)
if ret is None or ret.deviceState.started: if ret is None or ret.deviceState.started:
raise Exception("Reboot unavailable") raise Exception("Reboot unavailable")
def do_reboot(): def do_reboot() -> None:
time.sleep(2) time.sleep(2)
HARDWARE.reboot() HARDWARE.reboot()
@ -341,50 +380,53 @@ def reboot():
@dispatcher.add_method @dispatcher.add_method
def uploadFileToUrl(fn, url, headers): def uploadFileToUrl(fn: str, url: str, headers: Dict[str, str]) -> UploadFilesToUrlResponse:
return uploadFilesToUrls([{ # this is because mypy doesn't understand that the decorator doesn't change the return type
response: UploadFilesToUrlResponse = uploadFilesToUrls([{
"fn": fn, "fn": fn,
"url": url, "url": url,
"headers": headers, "headers": headers,
}]) }])
return response
@dispatcher.add_method @dispatcher.add_method
def uploadFilesToUrls(files_data): def uploadFilesToUrls(files_data: List[UploadFileDict]) -> UploadFilesToUrlResponse:
items = [] files = map(UploadFile.from_dict, files_data)
failed = []
for file in files_data: items: List[UploadItemDict] = []
fn = file.get('fn', '') failed: List[str] = []
if len(fn) == 0 or fn[0] == '/' or '..' in fn or 'url' not in file: for file in files:
failed.append(fn) if len(file.fn) == 0 or file.fn[0] == '/' or '..' in file.fn or len(file.url) == 0:
failed.append(file.fn)
continue continue
path = os.path.join(ROOT, fn) path = os.path.join(ROOT, file.fn)
if not os.path.exists(path) and not os.path.exists(strip_bz2_extension(path)): if not os.path.exists(path) and not os.path.exists(strip_bz2_extension(path)):
failed.append(fn) failed.append(file.fn)
continue continue
# Skip item if already in queue # Skip item if already in queue
url = file['url'].split('?')[0] url = file.url.split('?')[0]
if any(url == item['url'].split('?')[0] for item in listUploadQueue()): if any(url == item['url'].split('?')[0] for item in listUploadQueue()):
continue continue
item = UploadItem( item = UploadItem(
path=path, path=path,
url=file['url'], url=file.url,
headers=file.get('headers', {}), headers=file.headers,
created_at=int(time.time() * 1000), created_at=int(time.time() * 1000),
id=None, id=None,
allow_cellular=file.get('allow_cellular', False), allow_cellular=file.allow_cellular,
) )
upload_id = hashlib.sha1(str(item).encode()).hexdigest() upload_id = hashlib.sha1(str(item).encode()).hexdigest()
item = item._replace(id=upload_id) item = replace(item, id=upload_id)
upload_queue.put_nowait(item) upload_queue.put_nowait(item)
items.append(item._asdict()) items.append(asdict(item))
UploadQueueCache.cache(upload_queue) UploadQueueCache.cache(upload_queue)
resp = {"enqueued": len(items), "items": items} resp: UploadFilesToUrlResponse = {"enqueued": len(items), "items": items}
if failed: if failed:
resp["failed"] = failed resp["failed"] = failed
@ -392,32 +434,32 @@ def uploadFilesToUrls(files_data):
@dispatcher.add_method @dispatcher.add_method
def listUploadQueue(): def listUploadQueue() -> List[UploadItemDict]:
items = list(upload_queue.queue) + list(cur_upload_items.values()) items = list(upload_queue.queue) + list(cur_upload_items.values())
return [i._asdict() for i in items if (i is not None) and (i.id not in cancelled_uploads)] return [asdict(i) for i in items if (i is not None) and (i.id not in cancelled_uploads)]
@dispatcher.add_method @dispatcher.add_method
def cancelUpload(upload_id): def cancelUpload(upload_id: Union[str, List[str]]) -> Dict[str, Union[int, str]]:
if not isinstance(upload_id, list): if not isinstance(upload_id, list):
upload_id = [upload_id] upload_id = [upload_id]
uploading_ids = {item.id for item in list(upload_queue.queue)} uploading_ids = {item.id for item in list(upload_queue.queue)}
cancelled_ids = uploading_ids.intersection(upload_id) cancelled_ids = uploading_ids.intersection(upload_id)
if len(cancelled_ids) == 0: if len(cancelled_ids) == 0:
return 404 return {"success": 0, "error": "not found"}
cancelled_uploads.update(cancelled_ids) cancelled_uploads.update(cancelled_ids)
return {"success": 1} return {"success": 1}
@dispatcher.add_method @dispatcher.add_method
def primeActivated(activated): def primeActivated(activated: bool) -> Dict[str, int]:
return {"success": 1} return {"success": 1}
@dispatcher.add_method @dispatcher.add_method
def setBandwithLimit(upload_speed_kbps, download_speed_kbps): def setBandwithLimit(upload_speed_kbps: int, download_speed_kbps: int) -> Dict[str, Union[int, str]]:
if not AGNOS: if not AGNOS:
return {"success": 0, "error": "only supported on AGNOS"} return {"success": 0, "error": "only supported on AGNOS"}
@ -428,7 +470,7 @@ def setBandwithLimit(upload_speed_kbps, download_speed_kbps):
return {"success": 0, "error": "failed to set limit", "stdout": e.stdout, "stderr": e.stderr} return {"success": 0, "error": "failed to set limit", "stdout": e.stdout, "stderr": e.stderr}
def startLocalProxy(global_end_event, remote_ws_uri, local_port): def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local_port: int) -> Dict[str, int]:
try: try:
if local_port not in LOCAL_PORT_WHITELIST: if local_port not in LOCAL_PORT_WHITELIST:
raise Exception("Requested local port not whitelisted") raise Exception("Requested local port not whitelisted")
@ -462,7 +504,7 @@ def startLocalProxy(global_end_event, remote_ws_uri, local_port):
@dispatcher.add_method @dispatcher.add_method
def getPublicKey(): def getPublicKey() -> Optional[str]:
if not os.path.isfile(PERSIST + '/comma/id_rsa.pub'): if not os.path.isfile(PERSIST + '/comma/id_rsa.pub'):
return None return None
@ -471,7 +513,7 @@ def getPublicKey():
@dispatcher.add_method @dispatcher.add_method
def getSshAuthorizedKeys(): def getSshAuthorizedKeys() -> str:
return Params().get("GithubSshKeys", encoding='utf8') or '' return Params().get("GithubSshKeys", encoding='utf8') or ''
@ -486,7 +528,7 @@ def getNetworkType():
@dispatcher.add_method @dispatcher.add_method
def getNetworkMetered(): def getNetworkMetered() -> bool:
network_type = HARDWARE.get_network_type() network_type = HARDWARE.get_network_type()
return HARDWARE.get_network_metered(network_type) return HARDWARE.get_network_metered(network_type)
@ -497,7 +539,7 @@ def getNetworks():
@dispatcher.add_method @dispatcher.add_method
def takeSnapshot(): def takeSnapshot() -> Optional[Union[str, Dict[str, str]]]:
from system.camerad.snapshot.snapshot import jpeg_write, snapshot from system.camerad.snapshot.snapshot import jpeg_write, snapshot
ret = snapshot() ret = snapshot()
if ret is not None: if ret is not None:
@ -514,16 +556,19 @@ def takeSnapshot():
raise Exception("not available while camerad is started") raise Exception("not available while camerad is started")
def get_logs_to_send_sorted(): def get_logs_to_send_sorted() -> List[str]:
# TODO: scan once then use inotify to detect file creation/deletion # TODO: scan once then use inotify to detect file creation/deletion
curr_time = int(time.time()) curr_time = int(time.time())
logs = [] logs = []
for log_entry in os.listdir(SWAGLOG_DIR): for log_entry in os.listdir(SWAGLOG_DIR):
log_path = os.path.join(SWAGLOG_DIR, log_entry) log_path = os.path.join(SWAGLOG_DIR, log_entry)
time_sent = 0
try: try:
time_sent = int.from_bytes(getxattr(log_path, LOG_ATTR_NAME), sys.byteorder) value = getxattr(log_path, LOG_ATTR_NAME)
if value is not None:
time_sent = int.from_bytes(value, sys.byteorder)
except (ValueError, TypeError): except (ValueError, TypeError):
time_sent = 0 pass
# assume send failed and we lost the response if sent more than one hour ago # assume send failed and we lost the response if sent more than one hour ago
if not time_sent or curr_time - time_sent > 3600: if not time_sent or curr_time - time_sent > 3600:
logs.append(log_entry) logs.append(log_entry)
@ -531,7 +576,7 @@ def get_logs_to_send_sorted():
return sorted(logs)[:-1] return sorted(logs)[:-1]
def log_handler(end_event): def log_handler(end_event: threading.Event) -> None:
if PC: if PC:
return return
@ -593,7 +638,7 @@ def log_handler(end_event):
cloudlog.exception("athena.log_handler.exception") cloudlog.exception("athena.log_handler.exception")
def stat_handler(end_event): def stat_handler(end_event: threading.Event) -> None:
while not end_event.is_set(): while not end_event.is_set():
last_scan = 0 last_scan = 0
curr_scan = sec_since_boot() curr_scan = sec_since_boot()
@ -619,7 +664,7 @@ def stat_handler(end_event):
time.sleep(0.1) time.sleep(0.1)
def ws_proxy_recv(ws, local_sock, ssock, end_event, global_end_event): def ws_proxy_recv(ws: WebSocket, local_sock: socket.socket, ssock: socket.socket, end_event: threading.Event, global_end_event: threading.Event) -> None:
while not (end_event.is_set() or global_end_event.is_set()): while not (end_event.is_set() or global_end_event.is_set()):
try: try:
data = ws.recv() data = ws.recv()
@ -638,7 +683,7 @@ def ws_proxy_recv(ws, local_sock, ssock, end_event, global_end_event):
end_event.set() end_event.set()
def ws_proxy_send(ws, local_sock, signal_sock, end_event): def ws_proxy_send(ws: WebSocket, local_sock: socket.socket, signal_sock: socket.socket, end_event: threading.Event) -> None:
while not end_event.is_set(): while not end_event.is_set():
try: try:
r, _, _ = select.select((local_sock, signal_sock), (), ()) r, _, _ = select.select((local_sock, signal_sock), (), ())
@ -663,7 +708,7 @@ def ws_proxy_send(ws, local_sock, signal_sock, end_event):
cloudlog.debug("athena.ws_proxy_send done closing sockets") cloudlog.debug("athena.ws_proxy_send done closing sockets")
def ws_recv(ws, end_event): def ws_recv(ws: WebSocket, end_event: threading.Event) -> None:
last_ping = int(sec_since_boot() * 1e9) last_ping = int(sec_since_boot() * 1e9)
while not end_event.is_set(): while not end_event.is_set():
try: try:
@ -685,7 +730,7 @@ def ws_recv(ws, end_event):
end_event.set() end_event.set()
def ws_send(ws, end_event): def ws_send(ws: WebSocket, end_event: threading.Event) -> None:
while not end_event.is_set(): while not end_event.is_set():
try: try:
try: try:
@ -704,7 +749,7 @@ def ws_send(ws, end_event):
end_event.set() end_event.set()
def backoff(retries): def backoff(retries: int) -> int:
return random.randrange(0, min(128, int(2 ** retries))) return random.randrange(0, min(128, int(2 ** retries)))

@ -8,6 +8,7 @@ import time
import threading import threading
import queue import queue
import unittest import unittest
from dataclasses import asdict, replace
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Optional from typing import Optional
@ -226,7 +227,7 @@ class TestAthenadMethods(unittest.TestCase):
"""When an upload times out or fails to connect it should be placed back in the queue""" """When an upload times out or fails to connect it should be placed back in the queue"""
fn = self._create_file('qlog.bz2') fn = self._create_file('qlog.bz2')
item = athenad.UploadItem(path=fn, url="http://localhost:44444/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True) item = athenad.UploadItem(path=fn, url="http://localhost:44444/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
item_no_retry = item._replace(retry_count=MAX_RETRY_COUNT) item_no_retry = replace(item, retry_count=MAX_RETRY_COUNT)
end_event = threading.Event() end_event = threading.Event()
thread = threading.Thread(target=athenad.upload_handler, args=(end_event,)) thread = threading.Thread(target=athenad.upload_handler, args=(end_event,))
@ -296,7 +297,7 @@ class TestAthenadMethods(unittest.TestCase):
self.assertEqual(len(items), 0) self.assertEqual(len(items), 0)
@with_http_server @with_http_server
def test_listUploadQueueCurrent(self, host): def test_listUploadQueueCurrent(self, host: str):
fn = self._create_file('qlog.bz2') fn = self._create_file('qlog.bz2')
item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True) item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
@ -321,7 +322,7 @@ class TestAthenadMethods(unittest.TestCase):
items = dispatcher["listUploadQueue"]() items = dispatcher["listUploadQueue"]()
self.assertEqual(len(items), 1) self.assertEqual(len(items), 1)
self.assertDictEqual(items[0], item._asdict()) self.assertDictEqual(items[0], asdict(item))
self.assertFalse(items[0]['current']) self.assertFalse(items[0]['current'])
athenad.cancelled_uploads.add(item.id) athenad.cancelled_uploads.add(item.id)
@ -346,7 +347,7 @@ class TestAthenadMethods(unittest.TestCase):
athenad.UploadQueueCache.initialize(athenad.upload_queue) athenad.UploadQueueCache.initialize(athenad.upload_queue)
self.assertEqual(athenad.upload_queue.qsize(), 1) self.assertEqual(athenad.upload_queue.qsize(), 1)
self.assertDictEqual(athenad.upload_queue.queue[-1]._asdict(), item1._asdict()) self.assertDictEqual(asdict(athenad.upload_queue.queue[-1]), asdict(item1))
@mock.patch('selfdrive.athena.athenad.create_connection') @mock.patch('selfdrive.athena.athenad.create_connection')
def test_startLocalProxy(self, mock_create_connection): def test_startLocalProxy(self, mock_create_connection):
@ -417,5 +418,6 @@ class TestAthenadMethods(unittest.TestCase):
sl = athenad.get_logs_to_send_sorted() sl = athenad.get_logs_to_send_sorted()
self.assertListEqual(sl, fl[:-1]) self.assertListEqual(sl, fl[:-1])
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

@ -6,7 +6,16 @@ def create_buttons(packer, bus, idx, button):
values = { values = {
"ACCButtons": button, "ACCButtons": button,
"RollingCounter": idx, "RollingCounter": idx,
"ACCAlwaysOne": 1,
"DistanceButton": 0,
} }
checksum = 240 + int(values["ACCAlwaysOne"] * 0xf)
checksum += values["RollingCounter"] * (0x4ef if values["ACCAlwaysOne"] != 0 else 0x3f0)
checksum -= int(values["ACCButtons"] - 1) << 4 # not correct if value is 0
checksum -= 2 * values["DistanceButton"]
values["SteeringButtonChecksum"] = checksum
return packer.make_can_msg("ASCMSteeringButton", bus, values) return packer.make_can_msg("ASCMSteeringButton", bus, values)

@ -13,7 +13,8 @@ def get_pt_bus(car_fingerprint):
def get_lkas_cmd_bus(car_fingerprint, radar_disabled=False): def get_lkas_cmd_bus(car_fingerprint, radar_disabled=False):
if radar_disabled: no_radar = car_fingerprint in HONDA_BOSCH_RADARLESS
if radar_disabled or no_radar:
# when radar is disabled, steering commands are sent directly to powertrain bus # when radar is disabled, steering commands are sent directly to powertrain bus
return get_pt_bus(car_fingerprint) return get_pt_bus(car_fingerprint)
# normally steering commands are sent to radar, which forwards them to powertrain bus # normally steering commands are sent to radar, which forwards them to powertrain bus
@ -104,7 +105,7 @@ def create_bosch_supplemental_1(packer, car_fingerprint):
def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, acc_hud, lkas_hud): def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, acc_hud, lkas_hud):
commands = [] commands = []
bus_pt = get_pt_bus(CP.carFingerprint) bus_pt = get_pt_bus(CP.carFingerprint)
radar_disabled = CP.carFingerprint in HONDA_BOSCH and CP.openpilotLongitudinalControl radar_disabled = CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) and CP.openpilotLongitudinalControl
bus_lkas = get_lkas_cmd_bus(CP.carFingerprint, radar_disabled) bus_lkas = get_lkas_cmd_bus(CP.carFingerprint, radar_disabled)
if CP.openpilotLongitudinalControl: if CP.openpilotLongitudinalControl:
@ -153,7 +154,7 @@ def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, acc_hud,
else: else:
commands.append(packer.make_can_msg('LKAS_HUD', bus_lkas, lkas_hud_values)) commands.append(packer.make_can_msg('LKAS_HUD', bus_lkas, lkas_hud_values))
if radar_disabled and CP.carFingerprint in HONDA_BOSCH: if radar_disabled:
radar_hud_values = { radar_hud_values = {
'CMBS_OFF': 0x01, 'CMBS_OFF': 0x01,
'SET_TO_1': 0x01, 'SET_TO_1': 0x01,

@ -125,7 +125,7 @@ class CarController:
# blinkers # blinkers
if hda2 and self.CP.flags & HyundaiFlags.ENABLE_BLINKERS: if hda2 and self.CP.flags & HyundaiFlags.ENABLE_BLINKERS:
can_sends.extend(hyundaicanfd.create_spas_messages(self.packer, self.frame, False, False)) can_sends.extend(hyundaicanfd.create_spas_messages(self.packer, self.frame, CC.leftBlinker, CC.rightBlinker))
if self.CP.openpilotLongitudinalControl: if self.CP.openpilotLongitudinalControl:
if hda2: if hda2:

@ -32,7 +32,6 @@ class CarState(CarStateBase):
self.shifter_values = can_define.dv["LVR12"]["CF_Lvr_Gear"] self.shifter_values = can_define.dv["LVR12"]["CF_Lvr_Gear"]
self.is_metric = False self.is_metric = False
self.brake_error = False
self.buttons_counter = 0 self.buttons_counter = 0
self.cruise_info = {} self.cruise_info = {}
@ -107,6 +106,7 @@ class CarState(CarStateBase):
ret.brakePressed = cp.vl["TCS13"]["DriverBraking"] != 0 ret.brakePressed = cp.vl["TCS13"]["DriverBraking"] != 0
ret.brakeHoldActive = cp.vl["TCS15"]["AVH_LAMP"] == 2 # 0 OFF, 1 ERROR, 2 ACTIVE, 3 READY ret.brakeHoldActive = cp.vl["TCS15"]["AVH_LAMP"] == 2 # 0 OFF, 1 ERROR, 2 ACTIVE, 3 READY
ret.parkingBrake = cp.vl["TCS13"]["PBRAKE_ACT"] == 1 ret.parkingBrake = cp.vl["TCS13"]["PBRAKE_ACT"] == 1
ret.accFaulted = cp.vl["TCS13"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED
if self.CP.carFingerprint in (HYBRID_CAR | EV_CAR): if self.CP.carFingerprint in (HYBRID_CAR | EV_CAR):
if self.CP.carFingerprint in HYBRID_CAR: if self.CP.carFingerprint in HYBRID_CAR:
@ -147,7 +147,6 @@ class CarState(CarStateBase):
self.lkas11 = copy.copy(cp_cam.vl["LKAS11"]) self.lkas11 = copy.copy(cp_cam.vl["LKAS11"])
self.clu11 = copy.copy(cp.vl["CLU11"]) self.clu11 = copy.copy(cp.vl["CLU11"])
self.steer_state = cp.vl["MDPS12"]["CF_Mdps_ToiActive"] # 0 NOT ACTIVE, 1 ACTIVE self.steer_state = cp.vl["MDPS12"]["CF_Mdps_ToiActive"] # 0 NOT ACTIVE, 1 ACTIVE
self.brake_error = cp.vl["TCS13"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED
self.prev_cruise_buttons = self.cruise_buttons[-1] self.prev_cruise_buttons = self.cruise_buttons[-1]
self.cruise_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwState"]) self.cruise_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwState"])
self.main_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwMain"]) self.main_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwMain"])
@ -199,9 +198,7 @@ class CarState(CarStateBase):
ret.rightBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["FR_INDICATOR"] != 0 ret.rightBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["FR_INDICATOR"] != 0
ret.cruiseState.available = True ret.cruiseState.available = True
cruise_btn_msg = "CRUISE_BUTTONS_ALT" if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS" self.is_metric = cp.vl["CRUISE_BUTTONS_ALT"]["DISTANCE_UNIT"] != 1
distance_unit_msg = cruise_btn_msg if self.CP.carFingerprint == CAR.KIA_SORENTO_PHEV_4TH_GEN else "CLUSTER_INFO"
self.is_metric = cp.vl[distance_unit_msg]["DISTANCE_UNIT"] != 1
if not self.CP.openpilotLongitudinalControl: if not self.CP.openpilotLongitudinalControl:
speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS
cp_cruise_info = cp_cam if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp cp_cruise_info = cp_cam if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp
@ -210,10 +207,12 @@ class CarState(CarStateBase):
ret.cruiseState.enabled = cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] in (1, 2) ret.cruiseState.enabled = cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] in (1, 2)
self.cruise_info = copy.copy(cp_cruise_info.vl["SCC_CONTROL"]) self.cruise_info = copy.copy(cp_cruise_info.vl["SCC_CONTROL"])
cruise_btn_msg = "CRUISE_BUTTONS_ALT" if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS"
self.prev_cruise_buttons = self.cruise_buttons[-1] self.prev_cruise_buttons = self.cruise_buttons[-1]
self.cruise_buttons.extend(cp.vl_all[cruise_btn_msg]["CRUISE_BUTTONS"]) self.cruise_buttons.extend(cp.vl_all[cruise_btn_msg]["CRUISE_BUTTONS"])
self.main_buttons.extend(cp.vl_all[cruise_btn_msg]["ADAPTIVE_CRUISE_MAIN_BTN"]) self.main_buttons.extend(cp.vl_all[cruise_btn_msg]["ADAPTIVE_CRUISE_MAIN_BTN"])
self.buttons_counter = cp.vl[cruise_btn_msg]["COUNTER"] self.buttons_counter = cp.vl[cruise_btn_msg]["COUNTER"]
ret.accFaulted = cp.vl["TCS"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED
if self.CP.flags & HyundaiFlags.CANFD_HDA2: if self.CP.flags & HyundaiFlags.CANFD_HDA2:
self.cam_0x2a4 = copy.copy(cp_cam.vl["CAM_0x2a4"]) self.cam_0x2a4 = copy.copy(cp_cam.vl["CAM_0x2a4"])
@ -435,12 +434,12 @@ class CarState(CarStateBase):
("LKA_FAULT", "MDPS"), ("LKA_FAULT", "MDPS"),
("DriverBraking", "TCS"), ("DriverBraking", "TCS"),
("ACCEnable", "TCS"),
("COUNTER", cruise_btn_msg), ("COUNTER", cruise_btn_msg),
("CRUISE_BUTTONS", cruise_btn_msg), ("CRUISE_BUTTONS", cruise_btn_msg),
("ADAPTIVE_CRUISE_MAIN_BTN", cruise_btn_msg), ("ADAPTIVE_CRUISE_MAIN_BTN", cruise_btn_msg),
("DISTANCE_UNIT", "CRUISE_BUTTONS_ALT"),
("DISTANCE_UNIT", "CLUSTER_INFO"),
("LEFT_LAMP", "BLINKERS"), ("LEFT_LAMP", "BLINKERS"),
("RIGHT_LAMP", "BLINKERS"), ("RIGHT_LAMP", "BLINKERS"),
@ -449,21 +448,20 @@ class CarState(CarStateBase):
("DRIVER_SEATBELT_LATCHED", "DOORS_SEATBELTS"), ("DRIVER_SEATBELT_LATCHED", "DOORS_SEATBELTS"),
] ]
if CP.carFingerprint == CAR.KIA_SORENTO_PHEV_4TH_GEN:
signals.append(("DISTANCE_UNIT", cruise_btn_msg))
checks = [ checks = [
("WHEEL_SPEEDS", 100), ("WHEEL_SPEEDS", 100),
(gear_msg, 100), (gear_msg, 100),
("STEERING_SENSORS", 100), ("STEERING_SENSORS", 100),
("MDPS", 100), ("MDPS", 100),
("TCS", 50), ("TCS", 50),
(cruise_btn_msg, 50), ("CRUISE_BUTTONS_ALT", 50),
("CLUSTER_INFO", 4),
("BLINKERS", 4), ("BLINKERS", 4),
("DOORS_SEATBELTS", 4), ("DOORS_SEATBELTS", 4),
] ]
if not (CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS):
checks.append(("CRUISE_BUTTONS", 50))
if CP.enableBsm: if CP.enableBsm:
signals += [ signals += [
("FL_INDICATOR", "BLINDSPOTS_REAR_CORNERS"), ("FL_INDICATOR", "BLINDSPOTS_REAR_CORNERS"),

@ -191,6 +191,10 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 13.27 # steering ratio according to Kia News https://www.kiamedia.com/us/en/models/sorento-phev/2022/specifications ret.steerRatio = 13.27 # steering ratio according to Kia News https://www.kiamedia.com/us/en/models/sorento-phev/2022/specifications
# Genesis # Genesis
elif candidate == CAR.GENESIS_GV60_EV_1ST_GEN:
ret.mass = 2205 + STD_CARGO_KG
ret.wheelbase = 2.9
ret.steerRatio = 12.6 # https://www.motor1.com/reviews/586376/2023-genesis-gv60-first-drive/#:~:text=Relative%20to%20the%20related%20Ioniq,5%2FEV6%27s%2014.3%3A1.
elif candidate == CAR.GENESIS_G70: elif candidate == CAR.GENESIS_G70:
ret.steerActuatorDelay = 0.1 ret.steerActuatorDelay = 0.1
ret.mass = 1640.0 + STD_CARGO_KG ret.mass = 1640.0 + STD_CARGO_KG
@ -308,9 +312,6 @@ class CarInterface(CarInterfaceBase):
allow_enable = any(btn in ENABLE_BUTTONS for btn in self.CS.cruise_buttons) or any(self.CS.main_buttons) allow_enable = any(btn in ENABLE_BUTTONS for btn in self.CS.cruise_buttons) or any(self.CS.main_buttons)
events = self.create_common_events(ret, pcm_enable=self.CS.CP.pcmCruise, allow_enable=allow_enable) events = self.create_common_events(ret, pcm_enable=self.CS.CP.pcmCruise, allow_enable=allow_enable)
if self.CS.brake_error:
events.add(EventName.brakeUnavailable)
# low speed steer alert hysteresis logic (only for cars with steer cut off above 10 m/s) # low speed steer alert hysteresis logic (only for cars with steer cut off above 10 m/s)
if ret.vEgo < (self.CP.minSteerSpeed + 2.) and self.CP.minSteerSpeed > 10.: if ret.vEgo < (self.CP.minSteerSpeed + 2.) and self.CP.minSteerSpeed > 10.:
self.low_speed_alert = True self.low_speed_alert = True

@ -112,6 +112,7 @@ class CAR:
KIA_EV6 = "KIA EV6 2022" KIA_EV6 = "KIA EV6 2022"
# Genesis # Genesis
GENESIS_GV60_EV_1ST_GEN = "GENESIS GV60 ELECTRIC 1ST GEN"
GENESIS_G70 = "GENESIS G70 2018" GENESIS_G70 = "GENESIS G70 2018"
GENESIS_G70_2020 = "GENESIS G70 2020" GENESIS_G70_2020 = "GENESIS G70 2020"
GENESIS_GV70_1ST_GEN = "GENESIS GV70 1ST GEN" GENESIS_GV70_1ST_GEN = "GENESIS GV70 1ST GEN"
@ -139,7 +140,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
CAR.ELANTRA: [ CAR.ELANTRA: [
HyundaiCarInfo("Hyundai Elantra 2017-19", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_b), HyundaiCarInfo("Hyundai Elantra 2017-19", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_b),
HyundaiCarInfo("Hyundai Elantra GT 2017-19", harness=Harness.hyundai_e), HyundaiCarInfo("Hyundai Elantra GT 2017-19", harness=Harness.hyundai_e),
HyundaiCarInfo("Hyundai i30 2019", harness=Harness.hyundai_e), HyundaiCarInfo("Hyundai i30 2017-19", harness=Harness.hyundai_e),
], ],
CAR.ELANTRA_2021: HyundaiCarInfo("Hyundai Elantra 2021-22", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), CAR.ELANTRA_2021: HyundaiCarInfo("Hyundai Elantra 2021-22", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k),
CAR.ELANTRA_HEV_2021: HyundaiCarInfo("Hyundai Elantra Hybrid 2021-23", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), CAR.ELANTRA_HEV_2021: HyundaiCarInfo("Hyundai Elantra Hybrid 2021-23", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k),
@ -218,6 +219,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
], ],
# Genesis # Genesis
CAR.GENESIS_GV60_EV_1ST_GEN: HyundaiCarInfo("Genesis GV60 2023", "All", harness=Harness.hyundai_k),
CAR.GENESIS_G70: HyundaiCarInfo("Genesis G70 2018-19", "All", harness=Harness.hyundai_f), CAR.GENESIS_G70: HyundaiCarInfo("Genesis G70 2018-19", "All", harness=Harness.hyundai_f),
CAR.GENESIS_G70_2020: HyundaiCarInfo("Genesis G70 2020", "All", harness=Harness.hyundai_f), CAR.GENESIS_G70_2020: HyundaiCarInfo("Genesis G70 2020", "All", harness=Harness.hyundai_f),
CAR.GENESIS_GV70_1ST_GEN: HyundaiCarInfo("Genesis GV70 2022-23", "All", harness=Harness.hyundai_l), CAR.GENESIS_GV70_1ST_GEN: HyundaiCarInfo("Genesis GV70 2022-23", "All", harness=Harness.hyundai_l),
@ -713,12 +715,14 @@ FW_VERSIONS = {
b'\xf1\x8758910-S1DA0\xf1\x00TM ESC \x1e 102 \x08\x08 58910-S1DA0', b'\xf1\x8758910-S1DA0\xf1\x00TM ESC \x1e 102 \x08\x08 58910-S1DA0',
b'\xf1\x8758910-S2GA0\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0', b'\xf1\x8758910-S2GA0\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0',
b'\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0', b'\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0',
b'\xf1\x00TM ESC \x04 101 \x08\x04 58910-S2GA0',
], ],
(Ecu.engine, 0x7e0, None): [ (Ecu.engine, 0x7e0, None): [
b'\xf1\x82TACVN5GMI3XXXH0A', b'\xf1\x82TACVN5GMI3XXXH0A',
b'\xf1\x82TMBZN5TMD3XXXG2E', b'\xf1\x82TMBZN5TMD3XXXG2E',
b'\xf1\x82TACVN5GSI3XXXH0A', b'\xf1\x82TACVN5GSI3XXXH0A',
b'\xf1\x82TMCFD5MMCXXXXG0A', b'\xf1\x82TMCFD5MMCXXXXG0A',
b'\xf1\x81HM6M1_0a0_G20',
b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82TMDWN5TMD3TXXJ1A', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82TMDWN5TMD3TXXJ1A',
b'\xf1\x81HM6M2_0a0_G00', b'\xf1\x81HM6M2_0a0_G00',
b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10',
@ -743,6 +747,7 @@ FW_VERSIONS = {
b'\xf1\x00HT6TA290BLHT6TAF00A1STM0M25GS1\x00\x00\x00\x00\x00\x006\xd8\x97\x15', b'\xf1\x00HT6TA290BLHT6TAF00A1STM0M25GS1\x00\x00\x00\x00\x00\x006\xd8\x97\x15',
b'\xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc', b'\xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc',
b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02900A1 \xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc', b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02900A1 \xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc',
b'\xf1\x00T02601BL T02800A1 VTMPT25XXX800NS4\xed\xaf\xed\xf5',
], ],
}, },
CAR.SANTA_FE_HEV_2022: { CAR.SANTA_FE_HEV_2022: {
@ -1276,18 +1281,23 @@ FW_VERSIONS = {
CAR.ELANTRA: { CAR.ELANTRA: {
(Ecu.fwdCamera, 0x7c4, None): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00PD LKAS AT USA LHD 1.01 1.01 95740-G3100 A54', b'\xf1\x00PD LKAS AT USA LHD 1.01 1.01 95740-G3100 A54',
b'\xf1\x00PD LKAS AT KOR LHD 1.00 1.02 95740-G3000 A51',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DPD0H16NS0e\x0e\xcd\x8e', b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DPD0H16NS0e\x0e\xcd\x8e',
b'\xf1\x006U2U0_C2\x00\x006U2T0051\x00\x00DPD0D16KS0u\xce\x1fk',
], ],
(Ecu.eps, 0x7d4, None): [ (Ecu.eps, 0x7d4, None): [
b'\xf1\x00PD MDPS C 1.00 1.04 56310/G3300 4PDDC104', b'\xf1\x00PD MDPS C 1.00 1.04 56310/G3300 4PDDC104',
b'\xf1\x00PD MDPS C 1.00 1.00 56310G3300\x00 4PDDC100',
], ],
(Ecu.abs, 0x7d1, None): [ (Ecu.abs, 0x7d1, None): [
b'\xf1\x00PD ESC \x0b 104\x18\t\x03 58920-G3350', b'\xf1\x00PD ESC \x0b 104\x18\t\x03 58920-G3350',
b'\xf1\x00PD ESC \t 104\x18\t\x03 58920-G3350',
], ],
(Ecu.fwdRadar, 0x7d0, None): [ (Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00PD__ SCC F-CUP 1.00 1.00 96400-G3300 ', b'\xf1\x00PD__ SCC F-CUP 1.00 1.00 96400-G3300 ',
b'\xf1\x00PD__ SCC FNCUP 1.01 1.00 96400-G3000 ',
], ],
}, },
CAR.ELANTRA_2021: { CAR.ELANTRA_2021: {
@ -1509,6 +1519,14 @@ FW_VERSIONS = {
b'\xf1\x00JK1_ SCC FHCUP 1.00 1.02 99110-AR000 ', b'\xf1\x00JK1_ SCC FHCUP 1.00 1.02 99110-AR000 ',
], ],
}, },
CAR.GENESIS_GV60_EV_1ST_GEN: {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.02 99211-CU100 211215',
],
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00JW1_ RDR ----- 1.00 1.00 99110-CU000 ',
],
},
} }
CHECKSUM = { CHECKSUM = {
@ -1526,7 +1544,7 @@ FEATURES = {
"use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022, CAR.KIA_STINGER_2022}, "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022, CAR.KIA_STINGER_2022},
} }
CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN} CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.GENESIS_GV60_EV_1ST_GEN}
# The radar does SCC on these cars when HDA I, rather than the camera # The radar does SCC on these cars when HDA I, rather than the camera
CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN} CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN}
@ -1535,7 +1553,7 @@ CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN}
CAMERA_SCC_CAR = {CAR.KONA_EV_2022, } CAMERA_SCC_CAR = {CAR.KONA_EV_2022, }
HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN} # these cars use a different gas signal HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN} # these cars use a different gas signal
EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5} EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5, CAR.GENESIS_GV60_EV_1ST_GEN}
# these cars require a special panda safety mode due to missing counters and checksums in the messages # these cars require a special panda safety mode due to missing counters and checksums in the messages
LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.VELOSTER, CAR.KIA_STINGER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022} LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.VELOSTER, CAR.KIA_STINGER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022}
@ -1593,4 +1611,5 @@ DBC = {
CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: dbc_dict('hyundai_canfd', None),
CAR.GENESIS_GV70_1ST_GEN: dbc_dict('hyundai_canfd', None), CAR.GENESIS_GV70_1ST_GEN: dbc_dict('hyundai_canfd', None),
CAR.KIA_SORENTO_PHEV_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_SORENTO_PHEV_4TH_GEN: dbc_dict('hyundai_canfd', None),
CAR.GENESIS_GV60_EV_1ST_GEN: dbc_dict('hyundai_canfd', None),
} }

@ -80,6 +80,7 @@ routes = [
CarTestRoute("f44aa96ace22f34a|2021-12-22--06-22-31", HONDA.CIVIC_2022), CarTestRoute("f44aa96ace22f34a|2021-12-22--06-22-31", HONDA.CIVIC_2022),
CarTestRoute("6fe86b4e410e4c37|2020-07-22--16-27-13", HYUNDAI.HYUNDAI_GENESIS), CarTestRoute("6fe86b4e410e4c37|2020-07-22--16-27-13", HYUNDAI.HYUNDAI_GENESIS),
CarTestRoute("b5d6dc830ad63071|2022-12-12--21-28-25", HYUNDAI.GENESIS_GV60_EV_1ST_GEN, segment=12),
CarTestRoute("70c5bec28ec8e345|2020-08-08--12-22-23", HYUNDAI.GENESIS_G70), CarTestRoute("70c5bec28ec8e345|2020-08-08--12-22-23", HYUNDAI.GENESIS_G70),
CarTestRoute("ca4de5b12321bd98|2022-10-18--21-15-59", HYUNDAI.GENESIS_GV70_1ST_GEN), CarTestRoute("ca4de5b12321bd98|2022-10-18--21-15-59", HYUNDAI.GENESIS_GV70_1ST_GEN),
CarTestRoute("6b301bf83f10aa90|2020-11-22--16-45-07", HYUNDAI.GENESIS_G80), CarTestRoute("6b301bf83f10aa90|2020-11-22--16-45-07", HYUNDAI.GENESIS_G80),

@ -35,6 +35,7 @@ KIA SPORTAGE 5TH GEN: [2.7, 2.7, 0.1]
KIA SPORTAGE HYBRID 5TH GEN: [2.5, 2.5, 0.1] KIA SPORTAGE HYBRID 5TH GEN: [2.5, 2.5, 0.1]
GENESIS GV70 1ST GEN: [2.42, 2.42, 0.1] GENESIS GV70 1ST GEN: [2.42, 2.42, 0.1]
KIA SORENTO PLUG-IN HYBRID 4TH GEN: [2.5, 2.5, 0.1] KIA SORENTO PLUG-IN HYBRID 4TH GEN: [2.5, 2.5, 0.1]
GENESIS GV60 ELECTRIC 1ST GEN: [2.5, 2.5, 0.1]
# Dashcam or fallback configured as ideal car # Dashcam or fallback configured as ideal car
mock: [10.0, 10, 0.0] mock: [10.0, 10, 0.0]

@ -749,6 +749,7 @@ FW_VERSIONS = {
b'\x018966312Q8000\x00\x00\x00\x00', b'\x018966312Q8000\x00\x00\x00\x00',
b'\x018966312R0000\x00\x00\x00\x00', b'\x018966312R0000\x00\x00\x00\x00',
b'\x018966312R0100\x00\x00\x00\x00', b'\x018966312R0100\x00\x00\x00\x00',
b'\x018966312R0200\x00\x00\x00\x00',
b'\x018966312R1000\x00\x00\x00\x00', b'\x018966312R1000\x00\x00\x00\x00',
b'\x018966312R1100\x00\x00\x00\x00', b'\x018966312R1100\x00\x00\x00\x00',
b'\x018966312R3100\x00\x00\x00\x00', b'\x018966312R3100\x00\x00\x00\x00',
@ -802,6 +803,7 @@ FW_VERSIONS = {
b'\x01F152612B60\x00\x00\x00\x00\x00\x00', b'\x01F152612B60\x00\x00\x00\x00\x00\x00',
b'\x01F152612B61\x00\x00\x00\x00\x00\x00', b'\x01F152612B61\x00\x00\x00\x00\x00\x00',
b'\x01F152612B62\x00\x00\x00\x00\x00\x00', b'\x01F152612B62\x00\x00\x00\x00\x00\x00',
b'\x01F152612B70\x00\x00\x00\x00\x00\x00',
b'\x01F152612B71\x00\x00\x00\x00\x00\x00', b'\x01F152612B71\x00\x00\x00\x00\x00\x00',
b'\x01F152612B81\x00\x00\x00\x00\x00\x00', b'\x01F152612B81\x00\x00\x00\x00\x00\x00',
b'\x01F152612B90\x00\x00\x00\x00\x00\x00', b'\x01F152612B90\x00\x00\x00\x00\x00\x00',

@ -807,6 +807,7 @@ FW_VERSIONS = {
b'\xf1\x875G0906259L \xf1\x890002', b'\xf1\x875G0906259L \xf1\x890002',
b'\xf1\x875G0906259Q \xf1\x890002', b'\xf1\x875G0906259Q \xf1\x890002',
b'\xf1\x878V0906259F \xf1\x890002', b'\xf1\x878V0906259F \xf1\x890002',
b'\xf1\x878V0906259H \xf1\x890002',
b'\xf1\x878V0906259J \xf1\x890002', b'\xf1\x878V0906259J \xf1\x890002',
b'\xf1\x878V0906259K \xf1\x890001', b'\xf1\x878V0906259K \xf1\x890001',
b'\xf1\x878V0906264B \xf1\x890003', b'\xf1\x878V0906264B \xf1\x890003',
@ -817,6 +818,7 @@ FW_VERSIONS = {
b'\xf1\x870CW300044T \xf1\x895245', b'\xf1\x870CW300044T \xf1\x895245',
b'\xf1\x870CW300048 \xf1\x895201', b'\xf1\x870CW300048 \xf1\x895201',
b'\xf1\x870D9300012 \xf1\x894912', b'\xf1\x870D9300012 \xf1\x894912',
b'\xf1\x870D9300012 \xf1\x894931',
b'\xf1\x870D9300012K \xf1\x894513', b'\xf1\x870D9300012K \xf1\x894513',
b'\xf1\x870D9300013B \xf1\x894931', b'\xf1\x870D9300013B \xf1\x894931',
b'\xf1\x870D9300041N \xf1\x894512', b'\xf1\x870D9300041N \xf1\x894512',
@ -839,12 +841,14 @@ FW_VERSIONS = {
b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\x13111112111111--241115141112221291163221', b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\x13111112111111--241115141112221291163221',
b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\023111112111111--171115141112221291163221', b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\023111112111111--171115141112221291163221',
b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\023121111111211--261117141112231291163221', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\023121111111211--261117141112231291163221',
b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13111112111111--241115141112221291163221',
b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13121111111111--341117141212231291163221', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13121111111111--341117141212231291163221',
b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\0211212001112110004110411111421149114', b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\0211212001112110004110411111421149114',
b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\0211212001112111104110411111521159114', b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\0211212001112111104110411111521159114',
], ],
(Ecu.eps, 0x712, None): [ (Ecu.eps, 0x712, None): [
b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\00566G0HA14A1', b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\00566G0HA14A1',
b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566G0HA14A1',
b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G01A16A1', b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G01A16A1',
b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G0HA16A1', b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G0HA16A1',
b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571G0JA14A1', b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571G0JA14A1',

@ -578,6 +578,11 @@ class Controls:
actuators = CC.actuators actuators = CC.actuators
actuators.longControlState = self.LoC.long_control_state actuators.longControlState = self.LoC.long_control_state
# Enable blinkers while lane changing
if self.sm['lateralPlan'].laneChangeState != LaneChangeState.off:
CC.leftBlinker = self.sm['lateralPlan'].laneChangeDirection == LaneChangeDirection.left
CC.rightBlinker = self.sm['lateralPlan'].laneChangeDirection == LaneChangeDirection.right
if CS.leftBlinker or CS.rightBlinker: if CS.leftBlinker or CS.rightBlinker:
self.last_blinker_frame = self.sm.frame self.last_blinker_frame = self.sm.frame

@ -806,9 +806,9 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
}, },
EventName.accFaulted: { EventName.accFaulted: {
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Cruise Faulted"), ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Cruise Fault: Restart the Car"),
ET.PERMANENT: NormalPermanentAlert("Cruise Faulted", ""), ET.PERMANENT: NormalPermanentAlert("Cruise Fault: Restart the car to engage"),
ET.NO_ENTRY: NoEntryAlert("Cruise Faulted"), ET.NO_ENTRY: NoEntryAlert("Cruise Fault: Restart the Car"),
}, },
EventName.accFaultedTemp: { EventName.accFaultedTemp: {

@ -18,5 +18,4 @@ if arch in ['larch64', 'x86_64']:
nav_src = ["main.cc", "map_renderer.cc"] nav_src = ["main.cc", "map_renderer.cc"]
qt_env.Program("map_renderer", nav_src, LIBS=qt_libs + ['common', 'json11']) qt_env.Program("map_renderer", nav_src, LIBS=qt_libs + ['common', 'json11'])
if GetOption('extras'):
qt_env.SharedLibrary("map_renderer", ["map_renderer.cc"], LIBS=qt_libs + ['common', 'messaging']) qt_env.SharedLibrary("map_renderer", ["map_renderer.cc"], LIBS=qt_libs + ['common', 'messaging'])

@ -9,20 +9,8 @@
BMX055_Accel::BMX055_Accel(I2CBus *bus) : I2CSensor(bus) {} BMX055_Accel::BMX055_Accel(I2CBus *bus) : I2CSensor(bus) {}
int BMX055_Accel::init() { int BMX055_Accel::init() {
int ret = 0; int ret = verify_chip_id(BMX055_ACCEL_I2C_REG_ID, {BMX055_ACCEL_CHIP_ID});
uint8_t buffer[1]; if (ret == -1) return -1;
ret = read_register(BMX055_ACCEL_I2C_REG_ID, buffer, 1);
if(ret < 0) {
LOGE("Reading chip ID failed: %d", ret);
goto fail;
}
if(buffer[0] != BMX055_ACCEL_CHIP_ID) {
LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], BMX055_ACCEL_CHIP_ID);
ret = -1;
goto fail;
}
ret = set_register(BMX055_ACCEL_I2C_REG_PMU, BMX055_ACCEL_NORMAL_MODE); ret = set_register(BMX055_ACCEL_I2C_REG_PMU, BMX055_ACCEL_NORMAL_MODE);
if (ret < 0) { if (ret < 0) {

@ -12,20 +12,8 @@
BMX055_Gyro::BMX055_Gyro(I2CBus *bus) : I2CSensor(bus) {} BMX055_Gyro::BMX055_Gyro(I2CBus *bus) : I2CSensor(bus) {}
int BMX055_Gyro::init() { int BMX055_Gyro::init() {
int ret = 0; int ret = verify_chip_id(BMX055_GYRO_I2C_REG_ID, {BMX055_GYRO_CHIP_ID});
uint8_t buffer[1]; if (ret == -1) return -1;
ret =read_register(BMX055_GYRO_I2C_REG_ID, buffer, 1);
if(ret < 0) {
LOGE("Reading chip ID failed: %d", ret);
goto fail;
}
if(buffer[0] != BMX055_GYRO_CHIP_ID) {
LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], BMX055_GYRO_CHIP_ID);
ret = -1;
goto fail;
}
ret = set_register(BMX055_GYRO_I2C_REG_LPM1, BMX055_GYRO_NORMAL_MODE); ret = set_register(BMX055_GYRO_I2C_REG_LPM1, BMX055_GYRO_NORMAL_MODE);
if (ret < 0) { if (ret < 0) {

@ -66,8 +66,6 @@ static int16_t compensate_z(trim_data_t trim_data, int16_t mag_data_z, uint16_t
BMX055_Magn::BMX055_Magn(I2CBus *bus) : I2CSensor(bus) {} BMX055_Magn::BMX055_Magn(I2CBus *bus) : I2CSensor(bus) {}
int BMX055_Magn::init() { int BMX055_Magn::init() {
int ret;
uint8_t buffer[1];
uint8_t trim_x1y1[2] = {0}; uint8_t trim_x1y1[2] = {0};
uint8_t trim_x2y2[2] = {0}; uint8_t trim_x2y2[2] = {0};
uint8_t trim_xy1xy2[2] = {0}; uint8_t trim_xy1xy2[2] = {0};
@ -78,25 +76,18 @@ int BMX055_Magn::init() {
uint8_t trim_xyz1[2] = {0}; uint8_t trim_xyz1[2] = {0};
// suspend -> sleep // suspend -> sleep
ret = set_register(BMX055_MAGN_I2C_REG_PWR_0, 0x01); int ret = set_register(BMX055_MAGN_I2C_REG_PWR_0, 0x01);
if(ret < 0) { if(ret < 0) {
LOGE("Enabling power failed: %d", ret); LOGE("Enabling power failed: %d", ret);
goto fail; goto fail;
} }
util::sleep_for(5); // wait until the chip is powered on util::sleep_for(5); // wait until the chip is powered on
// read chip ID ret = verify_chip_id(BMX055_MAGN_I2C_REG_ID, {BMX055_MAGN_CHIP_ID});
ret = read_register(BMX055_MAGN_I2C_REG_ID, buffer, 1); if (ret == -1) {
if(ret < 0) {
LOGE("Reading chip ID failed: %d", ret);
goto fail; goto fail;
} }
if(buffer[0] != BMX055_MAGN_CHIP_ID) {
LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], BMX055_MAGN_CHIP_ID);
return -1;
}
// Load magnetometer trim // Load magnetometer trim
ret = read_register(BMX055_MAGN_I2C_REG_DIG_X1, trim_x1y1, 2); ret = read_register(BMX055_MAGN_I2C_REG_DIG_X1, trim_x1y1, 2);
if(ret < 0) goto fail; if(ret < 0) goto fail;

@ -9,23 +9,7 @@
BMX055_Temp::BMX055_Temp(I2CBus *bus) : I2CSensor(bus) {} BMX055_Temp::BMX055_Temp(I2CBus *bus) : I2CSensor(bus) {}
int BMX055_Temp::init() { int BMX055_Temp::init() {
int ret = 0; return verify_chip_id(BMX055_ACCEL_I2C_REG_ID, {BMX055_ACCEL_CHIP_ID}) == -1 ? -1 : 0;
uint8_t buffer[1];
ret = read_register(BMX055_ACCEL_I2C_REG_ID, buffer, 1);
if(ret < 0) {
LOGE("Reading chip ID failed: %d", ret);
goto fail;
}
if(buffer[0] != BMX055_ACCEL_CHIP_ID) {
LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], BMX055_ACCEL_CHIP_ID);
ret = -1;
goto fail;
}
fail:
return ret;
} }
bool BMX055_Temp::get_event(MessageBuilder &msg, uint64_t ts) { bool BMX055_Temp::get_event(MessageBuilder &msg, uint64_t ts) {

@ -2,12 +2,12 @@
#include <cstdint> #include <cstdint>
#include <unistd.h> #include <unistd.h>
#include "cereal/gen/cpp/log.capnp.h" #include "cereal/gen/cpp/log.capnp.h"
#include "common/i2c.h" #include "common/i2c.h"
#include "common/gpio.h" #include "common/gpio.h"
#include "common/swaglog.h"
#include "selfdrive/sensord/sensors/constants.h" #include "selfdrive/sensord/sensors/constants.h"
#include "selfdrive/sensord/sensors/sensor.h" #include "selfdrive/sensord/sensors/sensor.h"
@ -33,4 +33,18 @@ public:
virtual int init() = 0; virtual int init() = 0;
virtual bool get_event(MessageBuilder &msg, uint64_t ts = 0) = 0; virtual bool get_event(MessageBuilder &msg, uint64_t ts = 0) = 0;
virtual int shutdown() = 0; virtual int shutdown() = 0;
int verify_chip_id(uint8_t address, const std::vector<uint8_t> &expected_ids) {
uint8_t chip_id = 0;
int ret = read_register(address, &chip_id, 1);
if (ret < 0) {
LOGE("Reading chip ID failed: %d", ret);
return -1;
}
for (int i = 0; i < expected_ids.size(); ++i) {
if (chip_id == expected_ids[i]) return chip_id;
}
LOGE("Chip ID wrong. Got: %d, Expected %d", chip_id, expected_ids[0]);
return -1;
}
}; };

@ -118,8 +118,6 @@ int LSM6DS3_Accel::self_test(int test_type) {
} }
int LSM6DS3_Accel::init() { int LSM6DS3_Accel::init() {
int ret = 0;
uint8_t buffer[1];
uint8_t value = 0; uint8_t value = 0;
bool do_self_test = false; bool do_self_test = false;
@ -128,19 +126,10 @@ int LSM6DS3_Accel::init() {
do_self_test = true; do_self_test = true;
} }
ret = read_register(LSM6DS3_ACCEL_I2C_REG_ID, buffer, 1); int ret = verify_chip_id(LSM6DS3_ACCEL_I2C_REG_ID, {LSM6DS3_ACCEL_CHIP_ID, LSM6DS3TRC_ACCEL_CHIP_ID});
if(ret < 0) { if (ret == -1) return -1;
LOGE("Reading chip ID failed: %d", ret);
goto fail;
}
if(buffer[0] != LSM6DS3_ACCEL_CHIP_ID && buffer[0] != LSM6DS3TRC_ACCEL_CHIP_ID) {
LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], LSM6DS3_ACCEL_CHIP_ID);
ret = -1;
goto fail;
}
if (buffer[0] == LSM6DS3TRC_ACCEL_CHIP_ID) { if (ret == LSM6DS3TRC_ACCEL_CHIP_ID) {
source = cereal::SensorEventData::SensorSource::LSM6DS3TRC; source = cereal::SensorEventData::SensorSource::LSM6DS3TRC;
} }

@ -107,8 +107,6 @@ int LSM6DS3_Gyro::self_test(int test_type) {
} }
int LSM6DS3_Gyro::init() { int LSM6DS3_Gyro::init() {
int ret = 0;
uint8_t buffer[1];
uint8_t value = 0; uint8_t value = 0;
bool do_self_test = false; bool do_self_test = false;
@ -117,19 +115,10 @@ int LSM6DS3_Gyro::init() {
do_self_test = true; do_self_test = true;
} }
ret = read_register(LSM6DS3_GYRO_I2C_REG_ID, buffer, 1); int ret = verify_chip_id(LSM6DS3_GYRO_I2C_REG_ID, {LSM6DS3_GYRO_CHIP_ID, LSM6DS3TRC_GYRO_CHIP_ID});
if(ret < 0) { if (ret == -1) return -1;
LOGE("Reading chip ID failed: %d", ret);
goto fail;
}
if(buffer[0] != LSM6DS3_GYRO_CHIP_ID && buffer[0] != LSM6DS3TRC_GYRO_CHIP_ID) {
LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], LSM6DS3_GYRO_CHIP_ID);
ret = -1;
goto fail;
}
if (buffer[0] == LSM6DS3TRC_GYRO_CHIP_ID) { if (ret == LSM6DS3TRC_GYRO_CHIP_ID) {
source = cereal::SensorEventData::SensorSource::LSM6DS3TRC; source = cereal::SensorEventData::SensorSource::LSM6DS3TRC;
} }

@ -8,27 +8,13 @@
LSM6DS3_Temp::LSM6DS3_Temp(I2CBus *bus) : I2CSensor(bus) {} LSM6DS3_Temp::LSM6DS3_Temp(I2CBus *bus) : I2CSensor(bus) {}
int LSM6DS3_Temp::init() { int LSM6DS3_Temp::init() {
int ret = 0; int ret = verify_chip_id(LSM6DS3_TEMP_I2C_REG_ID, {LSM6DS3_TEMP_CHIP_ID, LSM6DS3TRC_TEMP_CHIP_ID});
uint8_t buffer[1]; if (ret == -1) return -1;
ret = read_register(LSM6DS3_TEMP_I2C_REG_ID, buffer, 1); if (ret == LSM6DS3TRC_TEMP_CHIP_ID) {
if(ret < 0) {
LOGE("Reading chip ID failed: %d", ret);
goto fail;
}
if(buffer[0] != LSM6DS3_TEMP_CHIP_ID && buffer[0] != LSM6DS3TRC_TEMP_CHIP_ID) {
LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], LSM6DS3_TEMP_CHIP_ID);
ret = -1;
goto fail;
}
if (buffer[0] == LSM6DS3TRC_TEMP_CHIP_ID) {
source = cereal::SensorEventData::SensorSource::LSM6DS3TRC; source = cereal::SensorEventData::SensorSource::LSM6DS3TRC;
} }
return 0;
fail:
return ret;
} }
bool LSM6DS3_Temp::get_event(MessageBuilder &msg, uint64_t ts) { bool LSM6DS3_Temp::get_event(MessageBuilder &msg, uint64_t ts) {

@ -8,20 +8,8 @@
MMC5603NJ_Magn::MMC5603NJ_Magn(I2CBus *bus) : I2CSensor(bus) {} MMC5603NJ_Magn::MMC5603NJ_Magn(I2CBus *bus) : I2CSensor(bus) {}
int MMC5603NJ_Magn::init() { int MMC5603NJ_Magn::init() {
int ret = 0; int ret = verify_chip_id(MMC5603NJ_I2C_REG_ID, {MMC5603NJ_CHIP_ID});
uint8_t buffer[1]; if (ret == -1) return -1;
ret = read_register(MMC5603NJ_I2C_REG_ID, buffer, 1);
if(ret < 0) {
LOGE("Reading chip ID failed: %d", ret);
goto fail;
}
if(buffer[0] != MMC5603NJ_CHIP_ID) {
LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], MMC5603NJ_CHIP_ID);
ret = -1;
goto fail;
}
// Set 100 Hz // Set 100 Hz
ret = set_register(MMC5603NJ_I2C_REG_ODR, 100); ret = set_register(MMC5603NJ_I2C_REG_ODR, 100);

@ -1 +1 @@
6681ca22053b019a65930e76a396535d0cddf39c faa85c0cb3609fc43674b263ae885c60f136ba6c

@ -22,6 +22,7 @@ DriverViewWindow::DriverViewWindow(QWidget* parent) : QWidget(parent) {
} }
void DriverViewWindow::mouseReleaseEvent(QMouseEvent* e) { void DriverViewWindow::mouseReleaseEvent(QMouseEvent* e) {
cameraView->stopVipcThread();
emit done(); emit done();
} }
@ -35,7 +36,6 @@ void DriverViewScene::showEvent(QShowEvent* event) {
} }
void DriverViewScene::hideEvent(QHideEvent* event) { void DriverViewScene::hideEvent(QHideEvent* event) {
// TODO: stop vipc thread ?
params.putBool("IsDriverViewEnabled", false); params.putBool("IsDriverViewEnabled", false);
} }

@ -35,6 +35,7 @@ public:
void setFrameId(int frame_id) { draw_frame_id = frame_id; } void setFrameId(int frame_id) { draw_frame_id = frame_id; }
void setStreamType(VisionStreamType type) { requested_stream_type = type; } void setStreamType(VisionStreamType type) { requested_stream_type = type; }
VisionStreamType getStreamType() { return active_stream_type; } VisionStreamType getStreamType() { return active_stream_type; }
void stopVipcThread();
signals: signals:
void clicked(); void clicked();
@ -51,7 +52,6 @@ protected:
void updateCalibration(const mat3 &calib); void updateCalibration(const mat3 &calib);
void vipcThread(); void vipcThread();
void clearFrames(); void clearFrames();
void stopVipcThread();
bool zoomed_view; bool zoomed_view;
GLuint frame_vao, frame_vbo, frame_ibo; GLuint frame_vao, frame_vbo, frame_ibo;

@ -1,5 +1,6 @@
{ {
"English": "main_en", "English": "main_en",
"Deutsch": "main_de",
"Português": "main_pt-BR", "Português": "main_pt-BR",
"中文(繁體)": "main_zh-CHT", "中文(繁體)": "main_zh-CHT",
"中文(简体)": "main_zh-CHS", "中文(简体)": "main_zh-CHS",

File diff suppressed because it is too large Load Diff

@ -97,11 +97,15 @@ const int ANALOG_GAIN_MIN_IDX_AR0231 = 0x1; // 0.25x
const int ANALOG_GAIN_REC_IDX_AR0231 = 0x6; // 0.8x const int ANALOG_GAIN_REC_IDX_AR0231 = 0x6; // 0.8x
const int ANALOG_GAIN_MAX_IDX_AR0231 = 0xD; // 4.0x const int ANALOG_GAIN_MAX_IDX_AR0231 = 0xD; // 4.0x
const int ANALOG_GAIN_COST_DELTA_AR0231 = 0; const int ANALOG_GAIN_COST_DELTA_AR0231 = 0;
const float ANALOG_GAIN_COST_LOW_AR0231 = 0.1;
const float ANALOG_GAIN_COST_HIGH_AR0231 = 5.0;
const int ANALOG_GAIN_MIN_IDX_OX03C10 = 0x0; const int ANALOG_GAIN_MIN_IDX_OX03C10 = 0x0;
const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x11; // 2.5x const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x11; // 2.5x
const int ANALOG_GAIN_MAX_IDX_OX03C10 = 0x36; const int ANALOG_GAIN_MAX_IDX_OX03C10 = 0x36;
const int ANALOG_GAIN_COST_DELTA_OX03C10 = -1; const int ANALOG_GAIN_COST_DELTA_OX03C10 = -1;
const float ANALOG_GAIN_COST_LOW_OX03C10 = 0.05;
const float ANALOG_GAIN_COST_HIGH_OX03C10 = 0.8;
const int EXPOSURE_TIME_MIN_AR0231 = 2; // with HDR, fastest ss const int EXPOSURE_TIME_MIN_AR0231 = 2; // with HDR, fastest ss
const int EXPOSURE_TIME_MAX_AR0231 = 0x0855; // with HDR, slowest ss, 40ms const int EXPOSURE_TIME_MAX_AR0231 = 0x0855; // with HDR, slowest ss, 40ms
@ -535,6 +539,8 @@ void CameraState::camera_set_parameters() {
analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_AR0231; analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_AR0231;
analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_AR0231; analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_AR0231;
analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_AR0231; analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_AR0231;
analog_gain_cost_low = ANALOG_GAIN_COST_LOW_AR0231;
analog_gain_cost_high = ANALOG_GAIN_COST_HIGH_AR0231;
for (int i=0; i<=analog_gain_max_idx; i++) { for (int i=0; i<=analog_gain_max_idx; i++) {
sensor_analog_gains[i] = sensor_analog_gains_AR0231[i]; sensor_analog_gains[i] = sensor_analog_gains_AR0231[i];
} }
@ -552,6 +558,8 @@ void CameraState::camera_set_parameters() {
analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_OX03C10; analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_OX03C10;
analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_OX03C10; analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_OX03C10;
analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_OX03C10; analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_OX03C10;
analog_gain_cost_low = ANALOG_GAIN_COST_LOW_OX03C10;
analog_gain_cost_high = ANALOG_GAIN_COST_HIGH_OX03C10;
for (int i=0; i<=analog_gain_max_idx; i++) { for (int i=0; i<=analog_gain_max_idx; i++) {
sensor_analog_gains[i] = sensor_analog_gains_OX03C10[i]; sensor_analog_gains[i] = sensor_analog_gains_OX03C10[i];
} }
@ -1108,7 +1116,7 @@ void CameraState::set_camera_exposure(float grey_frac) {
float score = std::abs(desired_ev - (t * gain)) * 10; float score = std::abs(desired_ev - (t * gain)) * 10;
// Going below recommended gain needs lower penalty to not overexpose // Going below recommended gain needs lower penalty to not overexpose
float m = g > analog_gain_rec_idx ? 5.0 : 0.1; float m = g > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low;
score += std::abs(g - (int)analog_gain_rec_idx) * m; score += std::abs(g - (int)analog_gain_rec_idx) * m;
// LOGE("cam: %d - gain: %d, t: %d (%.2f), score %.2f, score + gain %.2f, %.3f, %.3f", camera_num, g, t, desired_ev / gain, score, score + std::abs(g - gain_idx) * (score + 1.0) / 10.0, desired_ev, min_ev); // LOGE("cam: %d - gain: %d, t: %d (%.2f), score %.2f, score + gain %.2f, %.3f, %.3f", camera_num, g, t, desired_ev / gain, score, score + std::abs(g - gain_idx) * (score + 1.0) / 10.0, desired_ev, min_ev);

@ -42,6 +42,8 @@ public:
int analog_gain_max_idx; int analog_gain_max_idx;
int analog_gain_rec_idx; int analog_gain_rec_idx;
int analog_gain_cost_delta; int analog_gain_cost_delta;
float analog_gain_cost_low;
float analog_gain_cost_high;
float cur_ev[3]; float cur_ev[3];
float min_ev, max_ev; float min_ev, max_ev;

@ -1,8 +1,7 @@
#include <QApplication> #include <QApplication>
#include <QDir>
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QUuid>
#include "common/prefix.h"
#include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/util.h"
#include "tools/cabana/mainwin.h" #include "tools/cabana/mainwin.h"
@ -23,12 +22,6 @@ int main(int argc, char *argv[]) {
cmd_parser.showHelp(); cmd_parser.showHelp();
} }
QString uuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
QString msgq_path = "/dev/shm/" + uuid;
QDir dir;
dir.mkdir(msgq_path);
setenv("OPENPILOT_PREFIX", qPrintable(uuid), 1);
const QString route = args.empty() ? DEMO_ROUTE : args.first(); const QString route = args.empty() ? DEMO_ROUTE : args.first();
uint32_t replay_flags = REPLAY_FLAG_NONE; uint32_t replay_flags = REPLAY_FLAG_NONE;
@ -38,6 +31,7 @@ int main(int argc, char *argv[]) {
replay_flags |= REPLAY_FLAG_QCAMERA; replay_flags |= REPLAY_FLAG_QCAMERA;
} }
OpenpilotPrefix op_prefix;
CANMessages p(&app); CANMessages p(&app);
int ret = 0; int ret = 0;
if (p.loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) { if (p.loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) {
@ -45,7 +39,5 @@ int main(int argc, char *argv[]) {
w.showMaximized(); w.showMaximized();
ret = app.exec(); ret = app.exec();
} }
dir.rmdir(msgq_path);
return ret; return ret;
} }

@ -1,7 +1,4 @@
#include "tools/cabana/canmessages.h" #include "tools/cabana/canmessages.h"
#include <QSettings>
#include "tools/cabana/dbcmanager.h" #include "tools/cabana/dbcmanager.h"
CANMessages *can = nullptr; CANMessages *can = nullptr;
@ -25,39 +22,21 @@ bool CANMessages::loadRoute(const QString &route, const QString &data_dir, uint3
replay = new Replay(route, {"can", "roadEncodeIdx", "wideRoadEncodeIdx", "carParams"}, {}, nullptr, replay_flags, data_dir, this); replay = new Replay(route, {"can", "roadEncodeIdx", "wideRoadEncodeIdx", "carParams"}, {}, nullptr, replay_flags, data_dir, this);
replay->setSegmentCacheLimit(settings.cached_segment_limit); replay->setSegmentCacheLimit(settings.cached_segment_limit);
replay->installEventFilter(event_filter, this); replay->installEventFilter(event_filter, this);
QObject::connect(replay, &Replay::seekedTo, this, &CANMessages::seekedTo);
QObject::connect(replay, &Replay::segmentsMerged, this, &CANMessages::eventsMerged); QObject::connect(replay, &Replay::segmentsMerged, this, &CANMessages::eventsMerged);
QObject::connect(replay, &Replay::streamStarted, this, &CANMessages::streamStarted); QObject::connect(replay, &Replay::streamStarted, this, &CANMessages::streamStarted);
if (replay->load()) { if (replay->load()) {
const auto &segments = replay->route()->segments();
if (std::none_of(segments.begin(), segments.end(), [](auto &s) { return s.second.rlog.length() > 0; })) {
qWarning() << "no rlogs in route" << route;
return false;
}
replay->start(); replay->start();
return true; return true;
} }
return false; return false;
} }
QList<QPointF> CANMessages::findSignalValues(const QString &id, const Signal *signal, double value, FindFlags flag, int max_count) {
auto evts = events();
if (!evts) return {};
QList<QPointF> ret;
ret.reserve(max_count);
auto [bus, address] = DBCManager::parseId(id);
for (auto &evt : *evts) {
if (evt->which != cereal::Event::Which::CAN) continue;
for (const auto &c : evt->event.getCan()) {
if (bus == c.getSrc() && address == c.getAddress()) {
double val = get_raw_value((uint8_t *)c.getDat().begin(), c.getDat().size(), *signal);
if ((flag == EQ && val == value) || (flag == LT && val < value) || (flag == GT && val > value)) {
ret.push_back({(evt->mono_time / (double)1e9) - can->routeStartTime(), val});
if (ret.size() >= max_count)
return ret;
}
}
}
}
return ret;
}
void CANMessages::process(QHash<QString, CanData> *messages) { void CANMessages::process(QHash<QString, CanData> *messages) {
for (auto it = messages->begin(); it != messages->end(); ++it) { for (auto it = messages->begin(); it != messages->end(); ++it) {
can_msgs[it.key()] = it.value(); can_msgs[it.key()] = it.value();
@ -69,17 +48,13 @@ void CANMessages::process(QHash<QString, CanData> *messages) {
} }
bool CANMessages::eventFilter(const Event *event) { bool CANMessages::eventFilter(const Event *event) {
static std::unique_ptr<QHash<QString, CanData>> new_msgs; static std::unique_ptr new_msgs = std::make_unique<QHash<QString, CanData>>();
static double prev_update_ts = 0; static double prev_update_ts = 0;
if (event->which == cereal::Event::Which::CAN) { if (event->which == cereal::Event::Which::CAN) {
if (!new_msgs) {
new_msgs.reset(new QHash<QString, CanData>);
new_msgs->reserve(1000);
}
double current_sec = replay->currentSeconds(); double current_sec = replay->currentSeconds();
if (counters_begin_sec == 0 || counters_begin_sec >= current_sec) { if (counters_begin_sec == 0 || counters_begin_sec >= current_sec) {
new_msgs->clear();
counters.clear(); counters.clear();
counters_begin_sec = current_sec; counters_begin_sec = current_sec;
} }
@ -87,40 +62,29 @@ bool CANMessages::eventFilter(const Event *event) {
auto can_events = event->event.getCan(); auto can_events = event->event.getCan();
for (const auto &c : can_events) { for (const auto &c : can_events) {
QString id = QString("%1:%2").arg(c.getSrc()).arg(c.getAddress(), 1, 16); QString id = QString("%1:%2").arg(c.getSrc()).arg(c.getAddress(), 1, 16);
CanData &data = (*new_msgs)[id];
std::lock_guard lk(lock);
auto &list = received_msgs[id];
while (list.size() > settings.can_msg_log_size) {
list.pop_back();
}
CanData &data = list.emplace_front();
data.ts = current_sec; data.ts = current_sec;
data.dat.append((char *)c.getDat().begin(), c.getDat().size()); data.dat = QByteArray((char *)c.getDat().begin(), c.getDat().size());
data.count = ++counters[id]; data.count = ++counters[id];
if (double delta = (current_sec - counters_begin_sec); delta > 0) { if (double delta = (current_sec - counters_begin_sec); delta > 0) {
data.freq = data.count / delta; data.freq = data.count / delta;
} }
(*new_msgs)[id] = data;
} }
double ts = millis_since_boot(); double ts = millis_since_boot();
if ((ts - prev_update_ts) > (1000.0 / settings.fps) && !processing) { if ((ts - prev_update_ts) > (1000.0 / settings.fps) && !processing && !new_msgs->isEmpty()) {
// delay posting CAN message if UI thread is busy // delay posting CAN message if UI thread is busy
processing = true; processing = true;
prev_update_ts = ts; prev_update_ts = ts;
// use pointer to avoid data copy in queued connection. // use pointer to avoid data copy in queued connection.
emit received(new_msgs.release()); emit received(new_msgs.release());
new_msgs.reset(new QHash<QString, CanData>);
new_msgs->reserve(100);
} }
} }
return true; return true;
} }
const std::deque<CanData> CANMessages::messages(const QString &id) {
std::lock_guard lk(lock);
return received_msgs[id];
}
void CANMessages::seekTo(double ts) { void CANMessages::seekTo(double ts) {
replay->seekTo(std::max(double(0), ts), false); replay->seekTo(std::max(double(0), ts), false);
counters_begin_sec = 0; counters_begin_sec = 0;

@ -1,12 +1,9 @@
#pragma once #pragma once
#include <atomic> #include <atomic>
#include <deque>
#include <mutex>
#include <QColor> #include <QColor>
#include <QHash> #include <QHash>
#include <QList>
#include "opendbc/can/common_dbc.h" #include "opendbc/can/common_dbc.h"
#include "tools/cabana/settings.h" #include "tools/cabana/settings.h"
@ -23,12 +20,10 @@ class CANMessages : public QObject {
Q_OBJECT Q_OBJECT
public: public:
enum FindFlags{ EQ, LT, GT };
CANMessages(QObject *parent); CANMessages(QObject *parent);
~CANMessages(); ~CANMessages();
bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE); bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE);
void seekTo(double ts); void seekTo(double ts);
QList<QPointF> findSignalValues(const QString&id, const Signal* signal, double value, FindFlags flag, int max_count);
bool eventFilter(const Event *event); bool eventFilter(const Event *event);
inline QString routeName() const { return replay->route()->name(); } inline QString routeName() const { return replay->route()->name(); }
@ -37,7 +32,6 @@ public:
inline double totalSeconds() const { return replay->totalSeconds(); } inline double totalSeconds() const { return replay->totalSeconds(); }
inline double routeStartTime() const { return replay->routeStartTime() / (double)1e9; } inline double routeStartTime() const { return replay->routeStartTime() / (double)1e9; }
inline double currentSec() const { return replay->currentSeconds(); } inline double currentSec() const { return replay->currentSeconds(); }
const std::deque<CanData> messages(const QString &id);
inline const CanData &lastMessage(const QString &id) { return can_msgs[id]; } inline const CanData &lastMessage(const QString &id) { return can_msgs[id]; }
inline const Route* route() const { return replay->route(); } inline const Route* route() const { return replay->route(); }
@ -48,6 +42,7 @@ public:
inline const std::vector<std::tuple<int, int, TimelineType>> getTimeline() { return replay->getTimeline(); } inline const std::vector<std::tuple<int, int, TimelineType>> getTimeline() { return replay->getTimeline(); }
signals: signals:
void seekedTo(double sec);
void streamStarted(); void streamStarted();
void eventsMerged(); void eventsMerged();
void updated(); void updated();
@ -62,11 +57,9 @@ protected:
void settingChanged(); void settingChanged();
Replay *replay = nullptr; Replay *replay = nullptr;
std::mutex lock;
std::atomic<double> counters_begin_sec = 0; std::atomic<double> counters_begin_sec = 0;
std::atomic<bool> processing = false; std::atomic<bool> processing = false;
QHash<QString, uint32_t> counters; QHash<QString, uint32_t> counters;
QHash<QString, std::deque<CanData>> received_msgs;
}; };
inline QString toHex(const QByteArray &dat) { inline QString toHex(const QByteArray &dat) {

@ -152,12 +152,12 @@ void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show, bo
QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); }); QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); });
QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn); QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn);
QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset); QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset);
QObject::connect(chart, &ChartView::seriesRemoved, this, &ChartsWidget::chartClosed); QObject::connect(chart, &ChartView::seriesRemoved, this, &ChartsWidget::seriesChanged);
QObject::connect(chart, &ChartView::seriesAdded, this, &ChartsWidget::seriesChanged);
charts_layout->insertWidget(0, chart); charts_layout->insertWidget(0, chart);
charts.push_back(chart); charts.push_back(chart);
} }
chart->addSeries(id, sig); chart->addSeries(id, sig);
emit chartOpened(id, sig);
} else if (ChartView *chart = findChart(id, sig)) { } else if (ChartView *chart = findChart(id, sig)) {
chart->removeSeries(id, sig); chart->removeSeries(id, sig);
} }
@ -169,11 +169,16 @@ void ChartsWidget::removeChart(ChartView *chart) {
charts.removeOne(chart); charts.removeOne(chart);
chart->deleteLater(); chart->deleteLater();
updateToolBar(); updateToolBar();
emit seriesChanged();
} }
void ChartsWidget::removeAll() { void ChartsWidget::removeAll() {
for (auto c : charts.toVector()) for (auto c : charts) {
removeChart(c); c->deleteLater();
}
charts.clear();
updateToolBar();
emit seriesChanged();
} }
bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) { bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) {
@ -227,11 +232,6 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
QObject::connect(manage_btn, &QToolButton::clicked, this, &ChartView::manageSeries); QObject::connect(manage_btn, &QToolButton::clicked, this, &ChartView::manageSeries);
} }
ChartView::~ChartView() {
for (auto &s : sigs)
emit seriesRemoved(s.msg_id, s.sig);
}
void ChartView::addSeries(const QString &msg_id, const Signal *sig) { void ChartView::addSeries(const QString &msg_id, const Signal *sig) {
QLineSeries *series = new QLineSeries(this); QLineSeries *series = new QLineSeries(this);
series->setUseOpenGL(true); series->setUseOpenGL(true);
@ -243,6 +243,7 @@ void ChartView::addSeries(const QString &msg_id, const Signal *sig) {
updateTitle(); updateTitle();
updateSeries(sig); updateSeries(sig);
updateAxisY(); updateAxisY();
emit seriesAdded(msg_id, sig);
} }
void ChartView::removeSeries(const QString &msg_id, const Signal *sig) { void ChartView::removeSeries(const QString &msg_id, const Signal *sig) {
@ -259,9 +260,10 @@ bool ChartView::hasSeries(const QString &msg_id, const Signal *sig) const {
QList<ChartView::SigItem>::iterator ChartView::removeSeries(const QList<ChartView::SigItem>::iterator &it) { QList<ChartView::SigItem>::iterator ChartView::removeSeries(const QList<ChartView::SigItem>::iterator &it) {
chart()->removeSeries(it->series); chart()->removeSeries(it->series);
it->series->deleteLater(); it->series->deleteLater();
emit seriesRemoved(it->msg_id, it->sig); QString msg_id = it->msg_id;
const Signal *sig = it->sig;
auto ret = sigs.erase(it); auto ret = sigs.erase(it);
emit seriesRemoved(msg_id, sig);
if (!sigs.isEmpty()) { if (!sigs.isEmpty()) {
updateAxisY(); updateAxisY();
} else { } else {

@ -20,7 +20,6 @@ class ChartView : public QChartView {
public: public:
ChartView(QWidget *parent = nullptr); ChartView(QWidget *parent = nullptr);
~ChartView();
void addSeries(const QString &msg_id, const Signal *sig); void addSeries(const QString &msg_id, const Signal *sig);
void removeSeries(const QString &msg_id, const Signal *sig); void removeSeries(const QString &msg_id, const Signal *sig);
bool hasSeries(const QString &msg_id, const Signal *sig) const; bool hasSeries(const QString &msg_id, const Signal *sig) const;
@ -41,6 +40,7 @@ public:
signals: signals:
void seriesRemoved(const QString &id, const Signal *sig); void seriesRemoved(const QString &id, const Signal *sig);
void seriesAdded(const QString &id, const Signal *sig);
void zoomIn(double min, double max); void zoomIn(double min, double max);
void zoomReset(); void zoomReset();
void remove(); void remove();
@ -81,16 +81,15 @@ class ChartsWidget : public QWidget {
public: public:
ChartsWidget(QWidget *parent = nullptr); ChartsWidget(QWidget *parent = nullptr);
void showChart(const QString &id, const Signal *sig, bool show, bool merge); void showChart(const QString &id, const Signal *sig, bool show, bool merge);
void removeChart(ChartView *chart); inline bool hasSignal(const QString &id, const Signal *sig) { return findChart(id, sig) != nullptr; }
inline bool isChartOpened(const QString &id, const Signal *sig) { return findChart(id, sig) != nullptr; }
signals: signals:
void dock(bool floating); void dock(bool floating);
void rangeChanged(double min, double max, bool is_zommed); void rangeChanged(double min, double max, bool is_zommed);
void chartOpened(const QString &id, const Signal *sig); void seriesChanged();
void chartClosed(const QString &id, const Signal *sig);
private: private:
void removeChart(ChartView *chart);
void eventsMerged(); void eventsMerged();
void updateState(); void updateState();
void updateDisplayRange(); void updateDisplayRange();

@ -99,6 +99,7 @@ void DBCManager::removeSignal(const QString &id, const QString &sig_name) {
std::pair<uint8_t, uint32_t> DBCManager::parseId(const QString &id) { std::pair<uint8_t, uint32_t> DBCManager::parseId(const QString &id) {
const auto list = id.split(':'); const auto list = id.split(':');
if (list.size() != 2) return {0, 0};
return {list[0].toInt(), list[1].toUInt(nullptr, 16)}; return {list[0].toInt(), list[1].toUInt(nullptr, 16)};
} }

@ -86,7 +86,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
tab_widget = new QTabWidget(this); tab_widget = new QTabWidget(this);
tab_widget->setTabPosition(QTabWidget::South); tab_widget->setTabPosition(QTabWidget::South);
tab_widget->addTab(scroll, "&Msg"); tab_widget->addTab(scroll, "&Msg");
history_log = new HistoryLog(this); history_log = new LogsWidget(this);
tab_widget->addTab(history_log, "&Logs"); tab_widget->addTab(history_log, "&Logs");
main_layout->addWidget(tab_widget); main_layout->addWidget(tab_widget);
@ -107,8 +107,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
} }
}); });
QObject::connect(tabbar, &QTabBar::tabCloseRequested, tabbar, &QTabBar::removeTab); QObject::connect(tabbar, &QTabBar::tabCloseRequested, tabbar, &QTabBar::removeTab);
QObject::connect(charts, &ChartsWidget::chartOpened, [this](const QString &id, const Signal *sig) { updateChartState(id, sig, true); }); QObject::connect(charts, &ChartsWidget::seriesChanged, this, &DetailWidget::updateChartState);
QObject::connect(charts, &ChartsWidget::chartClosed, [this](const QString &id, const Signal *sig) { updateChartState(id, sig, false); });
QObject::connect(undo_stack, &QUndoStack::indexChanged, [this]() { QObject::connect(undo_stack, &QUndoStack::indexChanged, [this]() {
if (undo_stack->count() > 0) if (undo_stack->count() > 0)
dbcMsgChanged(); dbcMsgChanged();
@ -169,7 +168,7 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) {
signal_list.push_back(form); signal_list.push_back(form);
} }
form->setSignal(msg_id, sig); form->setSignal(msg_id, sig);
form->setChartOpened(charts->isChartOpened(msg_id, sig)); form->setChartOpened(charts->hasSignal(msg_id, sig));
++i; ++i;
} }
if (msg->size != can->lastMessage(msg_id).dat.size()) if (msg->size != can->lastMessage(msg_id).dat.size())
@ -212,9 +211,9 @@ void DetailWidget::showForm(const Signal *sig) {
setUpdatesEnabled(true); setUpdatesEnabled(true);
} }
void DetailWidget::updateChartState(const QString &id, const Signal *sig, bool opened) { void DetailWidget::updateChartState() {
for (auto f : signal_list) for (auto f : signal_list)
if (f->msg_id == id && f->sig == sig) f->setChartOpened(opened); f->setChartOpened(charts->hasSignal(f->msg_id, f->sig));
} }
void DetailWidget::editMsg() { void DetailWidget::editMsg() {
@ -334,4 +333,3 @@ WelcomeWidget::WelcomeWidget(QWidget *parent) : QWidget(parent) {
setStyleSheet("QLabel{color:darkGray;}"); setStyleSheet("QLabel{color:darkGray;}");
} }

@ -35,7 +35,7 @@ public:
private: private:
void showForm(const Signal *sig); void showForm(const Signal *sig);
void updateChartState(const QString &id, const Signal *sig, bool opened); void updateChartState();
void showTabBarContextMenu(const QPoint &pt); void showTabBarContextMenu(const QPoint &pt);
void addSignal(int start_bit, int size, bool little_endian); void addSignal(int start_bit, int size, bool little_endian);
void resizeSignal(const Signal *sig, int from, int to); void resizeSignal(const Signal *sig, int from, int to);
@ -53,7 +53,7 @@ private:
QTabWidget *tab_widget; QTabWidget *tab_widget;
QToolBar *toolbar; QToolBar *toolbar;
QAction *remove_msg_act; QAction *remove_msg_act;
HistoryLog *history_log; LogsWidget *history_log;
BinaryView *binary_view; BinaryView *binary_view;
QScrollArea *scroll; QScrollArea *scroll;
ChartsWidget *charts; ChartsWidget *charts;

@ -2,17 +2,24 @@
#include <QFontDatabase> #include <QFontDatabase>
#include <QPainter> #include <QPainter>
#include <QPushButton>
#include <QVBoxLayout>
// HistoryLogModel // HistoryLogModel
HistoryLogModel::HistoryLogModel(QObject *parent) : QAbstractTableModel(parent) {
QObject::connect(can, &CANMessages::seekedTo, [this]() {
if (!msg_id.isEmpty()) setMessage(msg_id);
});
}
QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { QVariant HistoryLogModel::data(const QModelIndex &index, int role) const {
if (role == Qt::DisplayRole) { if (role == Qt::DisplayRole) {
const auto &m = messages[index.row()]; const auto &m = messages[index.row()];
if (index.column() == 0) { if (index.column() == 0) {
return QString::number(m.ts, 'f', 2); return QString::number((m.mono_time / (double)1e9) - can->routeStartTime(), 'f', 2);
} }
return !sigs.empty() ? QString::number(get_raw_value((uint8_t *)m.dat.data(), m.dat.size(), *sigs[index.column() - 1])) return !sigs.empty() ? QString::number(m.sig_values[index.column() - 1]) : m.data;
: toHex(m.dat);
} else if (role == Qt::FontRole && index.column() == 1 && sigs.empty()) { } else if (role == Qt::FontRole && index.column() == 1 && sigs.empty()) {
return QFontDatabase::systemFont(QFontDatabase::FixedFont); return QFontDatabase::systemFont(QFontDatabase::FixedFont);
} }
@ -21,14 +28,18 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const {
void HistoryLogModel::setMessage(const QString &message_id) { void HistoryLogModel::setMessage(const QString &message_id) {
beginResetModel(); beginResetModel();
msg_id = message_id;
sigs.clear(); sigs.clear();
messages.clear(); messages.clear();
has_more_data = true;
if (auto dbc_msg = dbc()->msg(message_id)) { if (auto dbc_msg = dbc()->msg(message_id)) {
sigs = dbc_msg->getSignals(); sigs = dbc_msg->getSignals();
} }
endResetModel(); if (msg_id != message_id || sigs.empty()) {
filter_cmp = nullptr;
}
msg_id = message_id;
updateState(); updateState();
endResetModel();
} }
QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, int role) const { QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, int role) const {
@ -47,24 +58,76 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i
return {}; return {};
} }
void HistoryLogModel::setFilter(int sig_idx, const QString &value, std::function<bool(double, double)> cmp) {
if (sig_idx < sigs.size()) {
filter_sig_idx = sig_idx;
filter_value = value.toDouble();
filter_cmp = value.isEmpty() ? nullptr : cmp;
beginResetModel();
messages.clear();
updateState();
endResetModel();
}
}
void HistoryLogModel::updateState() { void HistoryLogModel::updateState() {
int prev_row_count = messages.size();
if (!msg_id.isEmpty()) { if (!msg_id.isEmpty()) {
messages = can->messages(msg_id); uint64_t last_mono_time = messages.empty() ? 0 : messages.front().mono_time;
} auto new_msgs = fetchData(last_mono_time, (can->currentSec() + can->routeStartTime()) * 1e9);
int delta = messages.size() - prev_row_count; if ((has_more_data = !new_msgs.empty())) {
if (delta > 0) { beginInsertRows({}, 0, new_msgs.size() - 1);
beginInsertRows({}, prev_row_count, messages.size() - 1); messages.insert(messages.begin(), std::move_iterator(new_msgs.begin()), std::move_iterator(new_msgs.end()));
endInsertRows(); endInsertRows();
} else if (delta < 0) {
beginRemoveRows({}, messages.size(), prev_row_count - 1);
endRemoveRows();
} }
}
}
void HistoryLogModel::fetchMore(const QModelIndex &parent) {
if (!messages.empty()) { if (!messages.empty()) {
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1), {Qt::DisplayRole}); auto new_msgs = fetchData(0, messages.back().mono_time);
if ((has_more_data = !new_msgs.empty())) {
beginInsertRows({}, messages.size(), messages.size() + new_msgs.size() - 1);
messages.insert(messages.end(), std::move_iterator(new_msgs.begin()), std::move_iterator(new_msgs.end()));
endInsertRows();
}
} }
} }
std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(uint64_t min_mono_time, uint64_t max_mono_time) {
auto events = can->events();
auto it = std::lower_bound(events->begin(), events->end(), max_mono_time, [=](auto &e, uint64_t ts) {
return e->mono_time < ts;
});
if (it == events->end() || it == events->begin())
return {};
std::deque<HistoryLogModel::Message> msgs;
const auto [src, address] = DBCManager::parseId(msg_id);
uint32_t cnt = 0;
QVector<double> values(sigs.size());
for (--it; it != events->begin() && (*it)->mono_time > min_mono_time; --it) {
if ((*it)->which == cereal::Event::Which::CAN) {
for (const auto &c : (*it)->event.getCan()) {
if (src == c.getSrc() && address == c.getAddress()) {
const auto dat = c.getDat();
for (int i = 0; i < sigs.size(); ++i) {
values[i] = get_raw_value((uint8_t *)dat.begin(), dat.size(), *(sigs[i]));
}
if (!filter_cmp || filter_cmp(values[filter_sig_idx], filter_value)) {
auto &m = msgs.emplace_back();
m.mono_time = (*it)->mono_time;
m.data = toHex(QByteArray((char *)dat.begin(), dat.size()));
m.sig_values = values;
if (++cnt >= batch_size && min_mono_time == 0)
return msgs;
}
}
}
}
}
return msgs;
}
// HeaderView // HeaderView
QSize HeaderView::sectionSizeFromContents(int logicalIndex) const { QSize HeaderView::sectionSizeFromContents(int logicalIndex) const {
@ -89,16 +152,69 @@ void HeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalI
// HistoryLog // HistoryLog
HistoryLog::HistoryLog(QWidget *parent) : QTableView(parent) { HistoryLog::HistoryLog(QWidget *parent) : QTableView(parent) {
model = new HistoryLogModel(this);
setModel(model);
setHorizontalHeader(new HeaderView(Qt::Horizontal, this)); setHorizontalHeader(new HeaderView(Qt::Horizontal, this));
horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | (Qt::Alignment)Qt::TextWordWrap); horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | (Qt::Alignment)Qt::TextWordWrap);
horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
verticalHeader()->setVisible(false); verticalHeader()->setVisible(false);
setFrameShape(QFrame::NoFrame);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
} }
int HistoryLog::sizeHintForColumn(int column) const { // LogsWidget
return -1;
LogsWidget::LogsWidget(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
filter_container = new QWidget(this);
QHBoxLayout *h = new QHBoxLayout(filter_container);
signals_cb = new QComboBox(this);
h->addWidget(signals_cb);
comp_box = new QComboBox();
comp_box->addItems({">", "=", "!=", "<"});
h->addWidget(comp_box);
value_edit = new QLineEdit(this);
value_edit->setClearButtonEnabled(true);
value_edit->setValidator(new QDoubleValidator(-500000, 500000, 6, this));
h->addWidget(value_edit);
main_layout->addWidget(filter_container);
model = new HistoryLogModel(this);
logs = new HistoryLog(this);
logs->setModel(model);
main_layout->addWidget(logs);
QObject::connect(signals_cb, SIGNAL(currentIndexChanged(int)), this, SLOT(setFilter()));
QObject::connect(comp_box, SIGNAL(currentIndexChanged(int)), this, SLOT(setFilter()));
QObject::connect(value_edit, &QLineEdit::textChanged, this, &LogsWidget::setFilter);
}
void LogsWidget::setMessage(const QString &message_id) {
blockSignals(true);
value_edit->setText("");
signals_cb->clear();
comp_box->setCurrentIndex(0);
sigs.clear();
if (auto dbc_msg = dbc()->msg(message_id)) {
sigs = dbc_msg->getSignals();
for (auto s : sigs) {
signals_cb->addItem(s->name.c_str());
}
}
filter_container->setVisible(!sigs.empty());
model->setMessage(message_id);
blockSignals(false);
}
static bool not_equal(double l, double r) {
return l != r;
}
void LogsWidget::setFilter() {
std::function<bool(double, double)> cmp;
switch (comp_box->currentIndex()) {
case 0: cmp = std::greater<double>{}; break;
case 1: cmp = std::equal_to<double>{}; break;
case 2: cmp = not_equal; break;
case 3: cmp = std::less<double>{}; break;
}
model->setFilter(signals_cb->currentIndex(), value_edit->text(), cmp);
} }

@ -1,6 +1,9 @@
#pragma once #pragma once
#include <deque>
#include <QComboBox>
#include <QHeaderView> #include <QHeaderView>
#include <QLineEdit>
#include <QTableView> #include <QTableView>
#include "tools/cabana/canmessages.h" #include "tools/cabana/canmessages.h"
@ -15,27 +18,58 @@ public:
class HistoryLogModel : public QAbstractTableModel { class HistoryLogModel : public QAbstractTableModel {
public: public:
HistoryLogModel(QObject *parent) : QAbstractTableModel(parent) {} HistoryLogModel(QObject *parent);
void setMessage(const QString &message_id); void setMessage(const QString &message_id);
void updateState(); void updateState();
void setFilter(int sig_idx, const QString &value, std::function<bool(double, double)> cmp);
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void fetchMore(const QModelIndex &parent) override;
inline bool canFetchMore(const QModelIndex &parent) const override { return has_more_data; }
int rowCount(const QModelIndex &parent = QModelIndex()) const override { return messages.size(); } int rowCount(const QModelIndex &parent = QModelIndex()) const override { return messages.size(); }
int columnCount(const QModelIndex &parent = QModelIndex()) const override { return std::max(1ul, sigs.size()) + 1; } int columnCount(const QModelIndex &parent = QModelIndex()) const override { return std::max(1ul, sigs.size()) + 1; }
private: struct Message {
uint64_t mono_time = 0;
QVector<double> sig_values;
QString data;
};
std::deque<Message> fetchData(uint64_t min_mono_time, uint64_t max_mono_time);
QString msg_id; QString msg_id;
std::deque<CanData> messages; bool has_more_data = true;
const int batch_size = 50;
int filter_sig_idx = -1;
double filter_value = 0;
std::function<bool(double, double)> filter_cmp = nullptr;
std::deque<Message> messages;
std::vector<const Signal*> sigs; std::vector<const Signal*> sigs;
}; };
class HistoryLog : public QTableView { class HistoryLog : public QTableView {
public: public:
HistoryLog(QWidget *parent); HistoryLog(QWidget *parent);
void setMessage(const QString &message_id) { model->setMessage(message_id); } int sizeHintForColumn(int column) const override { return -1; };
};
class LogsWidget : public QWidget {
Q_OBJECT
public:
LogsWidget(QWidget *parent);
void setMessage(const QString &message_id);
void updateState() { model->updateState(); } void updateState() { model->updateState(); }
private slots:
void setFilter();
private: private:
int sizeHintForColumn(int column) const override; void showEvent(QShowEvent *event) override { model->setMessage(model->msg_id); };
HistoryLog *logs;
HistoryLogModel *model; HistoryLogModel *model;
QWidget *filter_container;
QComboBox *signals_cb, *comp_box;
QLineEdit *value_edit;
std::vector<const Signal*> sigs;
}; };

@ -15,7 +15,6 @@ Settings::Settings() {
void Settings::save() { void Settings::save() {
QSettings s("settings", QSettings::IniFormat); QSettings s("settings", QSettings::IniFormat);
s.setValue("fps", fps); s.setValue("fps", fps);
s.setValue("log_size", can_msg_log_size);
s.setValue("cached_segment", cached_segment_limit); s.setValue("cached_segment", cached_segment_limit);
s.setValue("chart_height", chart_height); s.setValue("chart_height", chart_height);
s.setValue("max_chart_x_range", max_chart_x_range); s.setValue("max_chart_x_range", max_chart_x_range);
@ -26,7 +25,6 @@ void Settings::save() {
void Settings::load() { void Settings::load() {
QSettings s("settings", QSettings::IniFormat); QSettings s("settings", QSettings::IniFormat);
fps = s.value("fps", 10).toInt(); fps = s.value("fps", 10).toInt();
can_msg_log_size = s.value("log_size", 50).toInt();
cached_segment_limit = s.value("cached_segment", 3).toInt(); cached_segment_limit = s.value("cached_segment", 3).toInt();
chart_height = s.value("chart_height", 200).toInt(); chart_height = s.value("chart_height", 200).toInt();
max_chart_x_range = s.value("max_chart_x_range", 3 * 60).toInt(); max_chart_x_range = s.value("max_chart_x_range", 3 * 60).toInt();
@ -46,12 +44,6 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
fps->setValue(settings.fps); fps->setValue(settings.fps);
form_layout->addRow("FPS", fps); form_layout->addRow("FPS", fps);
log_size = new QSpinBox(this);
log_size->setRange(50, 500);
log_size->setSingleStep(10);
log_size->setValue(settings.can_msg_log_size);
form_layout->addRow(tr("Signal history log size"), log_size);
cached_segment = new QSpinBox(this); cached_segment = new QSpinBox(this);
cached_segment->setRange(3, 60); cached_segment->setRange(3, 60);
cached_segment->setSingleStep(1); cached_segment->setSingleStep(1);
@ -80,7 +72,6 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
void SettingsDlg::save() { void SettingsDlg::save() {
settings.fps = fps->value(); settings.fps = fps->value();
settings.can_msg_log_size = log_size->value();
settings.cached_segment_limit = cached_segment->value(); settings.cached_segment_limit = cached_segment->value();
settings.chart_height = chart_height->value(); settings.chart_height = chart_height->value();
settings.max_chart_x_range = max_chart_x_range->value() * 60; settings.max_chart_x_range = max_chart_x_range->value() * 60;

@ -14,7 +14,6 @@ public:
void load(); void load();
int fps = 10; int fps = 10;
int can_msg_log_size = 50;
int cached_segment_limit = 3; int cached_segment_limit = 3;
int chart_height = 200; int chart_height = 200;
int max_chart_x_range = 3 * 60; // 3 minutes int max_chart_x_range = 3 * 60; // 3 minutes
@ -32,7 +31,6 @@ public:
SettingsDlg(QWidget *parent); SettingsDlg(QWidget *parent);
void save(); void save();
QSpinBox *fps; QSpinBox *fps;
QSpinBox *log_size ;
QSpinBox *cached_segment; QSpinBox *cached_segment;
QSpinBox *chart_height; QSpinBox *chart_height;
QSpinBox *max_chart_x_range; QSpinBox *max_chart_x_range;

@ -4,12 +4,8 @@
#include <QFormLayout> #include <QFormLayout>
#include <QGuiApplication> #include <QGuiApplication>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QScrollArea>
#include <QToolBar>
#include <QVBoxLayout> #include <QVBoxLayout>
#include "selfdrive/ui/qt/util.h"
// SignalForm // SignalForm
SignalForm::SignalForm(QWidget *parent) : QWidget(parent) { SignalForm::SignalForm(QWidget *parent) : QWidget(parent) {
@ -94,11 +90,6 @@ SignalEdit::SignalEdit(int index, QWidget *parent) : form_idx(index), QWidget(pa
plot_btn->setCheckable(true); plot_btn->setCheckable(true);
plot_btn->setAutoRaise(true); plot_btn->setAutoRaise(true);
title_layout->addWidget(plot_btn); title_layout->addWidget(plot_btn);
auto seek_btn = new QToolButton(this);
seek_btn->setText("🔍");
seek_btn->setAutoRaise(true);
seek_btn->setToolTip(tr("Find signal values"));
title_layout->addWidget(seek_btn);
auto remove_btn = new QToolButton(this); auto remove_btn = new QToolButton(this);
remove_btn->setAutoRaise(true); remove_btn->setAutoRaise(true);
remove_btn->setText("x"); remove_btn->setText("x");
@ -126,7 +117,6 @@ SignalEdit::SignalEdit(int index, QWidget *parent) : form_idx(index), QWidget(pa
QObject::connect(plot_btn, &QToolButton::clicked, [this](bool checked) { QObject::connect(plot_btn, &QToolButton::clicked, [this](bool checked) {
emit showChart(msg_id, sig, checked, QGuiApplication::keyboardModifiers() & Qt::ShiftModifier); emit showChart(msg_id, sig, checked, QGuiApplication::keyboardModifiers() & Qt::ShiftModifier);
}); });
QObject::connect(seek_btn, &QToolButton::clicked, [this]() { SignalFindDlg(msg_id, sig, this).exec(); });
QObject::connect(remove_btn, &QToolButton::clicked, [this]() { emit remove(sig); }); QObject::connect(remove_btn, &QToolButton::clicked, [this]() { emit remove(sig); });
QObject::connect(form, &SignalForm::changed, [this]() { save_timer->start(); }); QObject::connect(form, &SignalForm::changed, [this]() { save_timer->start(); });
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
@ -212,68 +202,3 @@ void SignalEdit::leaveEvent(QEvent *event) {
emit highlight(nullptr); emit highlight(nullptr);
QWidget::leaveEvent(event); QWidget::leaveEvent(event);
} }
// SignalFindDlg
SignalFindDlg::SignalFindDlg(const QString &id, const Signal *signal, QWidget *parent) : QDialog(parent) {
setWindowTitle(tr("Find signal values"));
QVBoxLayout *main_layout = new QVBoxLayout(this);
QHBoxLayout *h = new QHBoxLayout();
h->addWidget(new QLabel(signal->name.c_str()));
QComboBox *comp_box = new QComboBox();
comp_box->addItems({">", "=", "<"});
h->addWidget(comp_box);
QLineEdit *value_edit = new QLineEdit("0", this);
value_edit->setValidator(new QDoubleValidator(-500000, 500000, 6, this));
h->addWidget(value_edit, 1);
QPushButton *search_btn = new QPushButton(tr("Find"), this);
h->addWidget(search_btn);
main_layout->addLayout(h);
QWidget *container = new QWidget(this);
QVBoxLayout *signals_layout = new QVBoxLayout(container);
QScrollArea *scroll = new QScrollArea(this);
scroll->setWidget(container);
scroll->setWidgetResizable(true);
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
main_layout->addWidget(scroll);
QObject::connect(search_btn, &QPushButton::clicked, [=]() {
clearLayout(signals_layout);
CANMessages::FindFlags comp = CANMessages::EQ;
if (comp_box->currentIndex() == 0) {
comp = CANMessages::GT;
} else if (comp_box->currentIndex() == 2) {
comp = CANMessages::LT;
}
double value = value_edit->text().toDouble();
const int limit_results = 50;
auto values = can->findSignalValues(id, signal, value, comp, limit_results);
for (auto &v : values) {
QHBoxLayout *item_layout = new QHBoxLayout();
item_layout->addWidget(new QLabel(QString::number(v.x(), 'f', 2)));
item_layout->addWidget(new QLabel(QString::number(v.y())));
item_layout->addStretch(1);
QPushButton *goto_btn = new QPushButton(tr("Goto"), this);
QObject::connect(goto_btn, &QPushButton::clicked, [sec = v.x()]() { can->seekTo(sec); });
item_layout->addWidget(goto_btn);
signals_layout->addLayout(item_layout);
}
if (values.size() == limit_results) {
QFrame *hline = new QFrame();
hline->setFrameShape(QFrame::HLine);
hline->setFrameShadow(QFrame::Sunken);
signals_layout->addWidget(hline);
QLabel *info = new QLabel(tr("Only display the first %1 results").arg(limit_results));
info->setAlignment(Qt::AlignCenter);
signals_layout->addWidget(info);
}
if (values.size() * 30 > container->height()) {
scroll->setFixedHeight(std::min(values.size() * 30, 300));
}
});
}

@ -1,7 +1,6 @@
#pragma once #pragma once
#include <QComboBox> #include <QComboBox>
#include <QDialog>
#include <QLabel> #include <QLabel>
#include <QLineEdit> #include <QLineEdit>
#include <QSpinBox> #include <QSpinBox>
@ -9,7 +8,6 @@
#include <QToolButton> #include <QToolButton>
#include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/controls.h"
#include "tools/cabana/canmessages.h" #include "tools/cabana/canmessages.h"
#include "tools/cabana/dbcmanager.h" #include "tools/cabana/dbcmanager.h"
@ -59,8 +57,3 @@ protected:
QToolButton *plot_btn; QToolButton *plot_btn;
QTimer *save_timer; QTimer *save_timer;
}; };
class SignalFindDlg : public QDialog {
public:
SignalFindDlg(const QString &id, const Signal *signal, QWidget *parent);
};

@ -23,6 +23,7 @@ int main(int argc, char *argv[]) {
parser.addPositionalArgument("route", "the drive to replay. find your drives at connect.comma.ai"); parser.addPositionalArgument("route", "the drive to replay. find your drives at connect.comma.ai");
parser.addOption({{"a", "allow"}, "whitelist of services to send", "allow"}); parser.addOption({{"a", "allow"}, "whitelist of services to send", "allow"});
parser.addOption({{"b", "block"}, "blacklist of services to send", "block"}); parser.addOption({{"b", "block"}, "blacklist of services to send", "block"});
parser.addOption({{"c", "cache"}, "cache <n> segments in memory. default is 5", "n"});
parser.addOption({{"s", "start"}, "start from <seconds>", "seconds"}); parser.addOption({{"s", "start"}, "start from <seconds>", "seconds"});
parser.addOption({"demo", "use a demo route instead of providing your own"}); parser.addOption({"demo", "use a demo route instead of providing your own"});
parser.addOption({"data_dir", "local directory with routes", "data_dir"}); parser.addOption({"data_dir", "local directory with routes", "data_dir"});
@ -47,6 +48,9 @@ int main(int argc, char *argv[]) {
} }
} }
Replay *replay = new Replay(route, allow, block, nullptr, replay_flags, parser.value("data_dir"), &app); Replay *replay = new Replay(route, allow, block, nullptr, replay_flags, parser.value("data_dir"), &app);
if (!parser.value("c").isEmpty()) {
replay->setSegmentCacheLimit(parser.value("c").toInt());
}
if (!replay->load()) { if (!replay->load()) {
return 0; return 0;
} }

@ -51,9 +51,9 @@ void Replay::stop() {
stream_thread_->wait(); stream_thread_->wait();
stream_thread_ = nullptr; stream_thread_ = nullptr;
} }
segments_.clear();
camera_server_.reset(nullptr); camera_server_.reset(nullptr);
timeline_future.waitForFinished(); timeline_future.waitForFinished();
segments_.clear();
rInfo("shutdown: done"); rInfo("shutdown: done");
} }
@ -109,6 +109,7 @@ void Replay::seekTo(double seconds, bool relative) {
cur_mono_time_ = route_start_ts_ + seconds * 1e9; cur_mono_time_ = route_start_ts_ + seconds * 1e9;
return isSegmentMerged(seg); return isSegmentMerged(seg);
}); });
emit seekedTo(seconds);
queueSegment(); queueSegment();
} }
@ -209,11 +210,17 @@ void Replay::segmentLoadFinished(bool success) {
void Replay::queueSegment() { void Replay::queueSegment() {
if (segments_.empty()) return; if (segments_.empty()) return;
SegmentMap::iterator cur, end; SegmentMap::iterator begin, cur;
cur = end = segments_.lower_bound(std::min(current_segment_.load(), segments_.rbegin()->first)); begin = cur = segments_.lower_bound(std::min(current_segment_.load(), segments_.rbegin()->first));
for (int i = 0; end != segments_.end() && i <= segment_cache_limit + FORWARD_FETCH_SEGS; ++i) { int distance = std::max<int>(std::ceil(segment_cache_limit / 2.0) - 1, segment_cache_limit - std::distance(cur, segments_.end()));
for (int i = 0; begin != segments_.begin() && i < distance; ++i) {
--begin;
}
auto end = begin;
for (int i = 0; end != segments_.end() && i < segment_cache_limit; ++i) {
++end; ++end;
} }
// load one segment at a time // load one segment at a time
for (auto it = cur; it != end; ++it) { for (auto it = cur; it != end; ++it) {
auto &[n, seg] = *it; auto &[n, seg] = *it;
@ -227,12 +234,6 @@ void Replay::queueSegment() {
} }
} }
const auto &cur_segment = cur->second;
// merge the previous adjacent segment if it's loaded
auto begin = segments_.find(cur_segment->seg_num - 1);
if (begin == segments_.end() || !(begin->second && begin->second->isLoaded())) {
begin = cur;
}
mergeSegments(begin, end); mergeSegments(begin, end);
// free segments out of current semgnt window. // free segments out of current semgnt window.
@ -240,6 +241,7 @@ void Replay::queueSegment() {
std::for_each(end, segments_.end(), [](auto &e) { e.second.reset(nullptr); }); std::for_each(end, segments_.end(), [](auto &e) { e.second.reset(nullptr); });
// start stream thread // start stream thread
const auto &cur_segment = cur->second;
if (stream_thread_ == nullptr && cur_segment->isLoaded()) { if (stream_thread_ == nullptr && cur_segment->isLoaded()) {
startStream(cur_segment.get()); startStream(cur_segment.get());
emit streamStarted(); emit streamStarted();
@ -247,13 +249,14 @@ void Replay::queueSegment() {
} }
void Replay::mergeSegments(const SegmentMap::iterator &begin, const SegmentMap::iterator &end) { void Replay::mergeSegments(const SegmentMap::iterator &begin, const SegmentMap::iterator &end) {
// merge 3 segments in sequence.
std::vector<int> segments_need_merge; std::vector<int> segments_need_merge;
size_t new_events_size = 0; size_t new_events_size = 0;
for (auto it = begin; it != end && it->second && it->second->isLoaded() && segments_need_merge.size() < segment_cache_limit; ++it) { for (auto it = begin; it != end; ++it) {
if (it->second && it->second->isLoaded()) {
segments_need_merge.push_back(it->first); segments_need_merge.push_back(it->first);
new_events_size += it->second->log->events.size(); new_events_size += it->second->log->events.size();
} }
}
if (segments_need_merge != segments_merged_) { if (segments_need_merge != segments_merged_) {
std::string s; std::string s;

@ -10,7 +10,7 @@
const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36";
// one segment uses about 100M of memory // one segment uses about 100M of memory
constexpr int FORWARD_FETCH_SEGS = 3; constexpr int MIN_SEGMENTS_CACHE = 5;
enum REPLAY_FLAGS { enum REPLAY_FLAGS {
REPLAY_FLAG_NONE = 0x0000, REPLAY_FLAG_NONE = 0x0000,
@ -58,7 +58,7 @@ public:
event_filter = filter; event_filter = filter;
} }
inline int segmentCacheLimit() const { return segment_cache_limit; } inline int segmentCacheLimit() const { return segment_cache_limit; }
inline void setSegmentCacheLimit(int n) { segment_cache_limit = std::max(3, n); } inline void setSegmentCacheLimit(int n) { segment_cache_limit = std::max(MIN_SEGMENTS_CACHE, n); }
inline bool hasFlag(REPLAY_FLAGS flag) const { return flags_ & flag; } inline bool hasFlag(REPLAY_FLAGS flag) const { return flags_ & flag; }
inline void addFlag(REPLAY_FLAGS flag) { flags_ |= flag; } inline void addFlag(REPLAY_FLAGS flag) { flags_ |= flag; }
inline void removeFlag(REPLAY_FLAGS flag) { flags_ &= ~flag; } inline void removeFlag(REPLAY_FLAGS flag) { flags_ &= ~flag; }
@ -79,6 +79,7 @@ public:
signals: signals:
void streamStarted(); void streamStarted();
void segmentsMerged(); void segmentsMerged();
void seekedTo(double sec);
protected slots: protected slots:
void segmentLoadFinished(bool success); void segmentLoadFinished(bool success);
@ -133,5 +134,5 @@ protected:
float speed_ = 1.0; float speed_ = 1.0;
replayEventFilter event_filter = nullptr; replayEventFilter event_filter = nullptr;
void *filter_opaque = nullptr; void *filter_opaque = nullptr;
int segment_cache_limit = 3; int segment_cache_limit = MIN_SEGMENTS_CACHE;
}; };

Loading…
Cancel
Save