Merge remote-tracking branch 'upstream/master' into civic22_long

pull/25364/head
royjr 3 years ago
commit 63e1b53e27
  1. 15
      RELEASES.md
  2. 2
      cereal
  3. 2
      common/params.cc
  4. 9
      common/util.cc
  5. 1
      common/util.h
  6. 7
      docs/CARS.md
  7. 1
      release/files_common
  8. 4
      selfdrive/boardd/SConscript
  9. 6
      selfdrive/boardd/panda.cc
  10. 22
      selfdrive/boardd/panda_comms.h
  11. 252
      selfdrive/boardd/spi.cc
  12. 14
      selfdrive/car/gm/carstate.py
  13. 16
      selfdrive/car/hyundai/carstate.py
  14. 10
      selfdrive/car/hyundai/interface.py
  15. 23
      selfdrive/car/hyundai/values.py
  16. 2
      selfdrive/car/interfaces.py
  17. 1
      selfdrive/car/subaru/values.py
  18. 1
      selfdrive/car/tests/routes.py
  19. 21
      selfdrive/car/tests/test_car_interfaces.py
  20. 1
      selfdrive/car/torque_data/override.yaml
  21. 3
      selfdrive/car/toyota/carstate.py
  22. 15
      selfdrive/car/toyota/interface.py
  23. 6
      selfdrive/car/toyota/values.py
  24. 1
      selfdrive/car/volkswagen/carstate.py
  25. 2
      selfdrive/controls/controlsd.py
  26. 2
      selfdrive/controls/lib/desire_helper.py
  27. 8
      selfdrive/controls/lib/latcontrol_torque.py
  28. 5
      selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py
  29. 17
      selfdrive/controls/lib/longitudinal_planner.py
  30. 2
      selfdrive/controls/tests/test_cruise_speed.py
  31. 2
      selfdrive/controls/tests/test_following_distance.py
  32. 11
      selfdrive/modeld/models/driving.cc
  33. 9
      selfdrive/modeld/models/driving.h
  34. 4
      selfdrive/modeld/models/supercombo.onnx
  35. 3
      selfdrive/monitoring/driver_monitor.py
  36. 4
      selfdrive/test/longitudinal_maneuvers/test_longitudinal.py
  37. 2
      selfdrive/test/process_replay/model_replay_ref_commit
  38. 2
      selfdrive/test/process_replay/ref_commit
  39. 4
      selfdrive/test/process_replay/test_processes.py
  40. 44
      selfdrive/ui/qt/offroad/settings.cc
  41. 37
      selfdrive/ui/qt/onroad.cc
  42. 1
      selfdrive/ui/qt/onroad.h
  43. 6
      selfdrive/ui/qt/widgets/controls.h
  44. 3
      selfdrive/ui/tests/test_translations.py
  45. 24
      selfdrive/ui/translations/create_badges.py
  46. 4
      selfdrive/ui/translations/main_ar.ts
  47. 40
      selfdrive/ui/translations/main_ja.ts
  48. 48
      selfdrive/ui/translations/main_ko.ts
  49. 4
      selfdrive/ui/translations/main_nl.ts
  50. 4
      selfdrive/ui/translations/main_pl.ts
  51. 48
      selfdrive/ui/translations/main_pt-BR.ts
  52. 4
      selfdrive/ui/translations/main_th.ts
  53. 42
      selfdrive/ui/translations/main_zh-CHS.ts
  54. 40
      selfdrive/ui/translations/main_zh-CHT.ts
  55. 4
      selfdrive/ui/ui.cc
  56. 2
      selfdrive/ui/ui.h
  57. 28
      system/camerad/cameras/camera_qcom2.cc
  58. 3
      system/camerad/cameras/camera_qcom2.h
  59. 13
      system/camerad/test/test_camerad.py
  60. 3
      system/hardware/tici/hardware.py
  61. 2
      tools/cabana/README.md
  62. 153
      tools/cabana/binaryview.cc
  63. 14
      tools/cabana/binaryview.h
  64. 17
      tools/cabana/canmessages.cc
  65. 6
      tools/cabana/canmessages.h
  66. 236
      tools/cabana/chartswidget.cc
  67. 57
      tools/cabana/chartswidget.h
  68. 53
      tools/cabana/dbcmanager.cc
  69. 19
      tools/cabana/dbcmanager.h
  70. 101
      tools/cabana/detailwidget.cc
  71. 10
      tools/cabana/detailwidget.h
  72. 6
      tools/cabana/historylog.cc
  73. 3
      tools/cabana/historylog.h
  74. 162
      tools/cabana/mainwin.cc
  75. 21
      tools/cabana/mainwin.h
  76. 242
      tools/cabana/messageswidget.cc
  77. 42
      tools/cabana/messageswidget.h
  78. 7
      tools/cabana/settings.cc
  79. 14
      tools/cabana/signaledit.cc
  80. 13
      tools/cabana/videowidget.cc
  81. 1
      tools/cabana/videowidget.h
  82. 2
      tools/gpstest/run_unittest.sh
  83. 33
      tools/gpstest/test_gps_qcom.py

@ -1,9 +1,17 @@
Version 0.8.17 (2022-XX-XX)
Version 0.8.17 (2022-11-XX)
========================
* New driving model
* Internal feature space accuracy increased tenfold during training, this makes the model dramatically more accurate.
* Internal feature space information content increased tenfold during training (to ~700 bits), this makes the model dramatically more accurate
* Less reliance on previous frames makes model more reactive and snappy
* Trained in new reprojective simulator
* Model trained in openpilot was trained in 36hrs from scratch, compared to around 1 week of previous releases
* Model training now simulates lateral and longitudinal behavior, this allows openpilot to slow down for turns, stop at traffic lights, etc,... in experimental mode
* New driver monitoring model
* New end-to-end distracted trigger
* Experimental driving mode
* End-to-end longitudinal control
* Stops for red lights and stop signs
* openpilot defaults to chill mode, enable experimental in settings
* Self-tuning torque lateral controller parameters
* Parameters learned live for each car
* Torque controller used on all Toyota, Lexus, Hyundai, Kia, and Genesis models
@ -12,9 +20,10 @@ Version 0.8.17 (2022-XX-XX)
* Matched speeds shown on car's dash
* Improved update experience
* Border turns grey while overriding steering
* Added button to bookmark events while driving; view them later in comma connect
* Bookmark events while driving; view them in comma connect
* AGNOS 6
* tools: new and improved cabana thanks to deanlee!
* Genesis GV70 2022-23 support thanks to zunichky and sunnyhaibin!
* Hyundai Santa Cruz 2021-22 support thanks to sunnyhaibin!
* Kia Sportage 2023 support thanks to sunnyhaibin!
* Kia Sportage Hybrid 2023 support thanks to sunnyhaibin!

@ -1 +1 @@
Subproject commit 1d25fc3f202d5ddeee97848480323e9b14f9bdfa
Subproject commit cdba1aafec5e36505ef6ace675568e1f15003c47

@ -102,7 +102,7 @@ std::unordered_map<std::string, uint32_t> keys = {
{"DashcamOverride", PERSISTENT},
{"DisableLogging", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON},
{"DisablePowerDown", PERSISTENT},
{"EndToEndLong", PERSISTENT},
{"ExperimentalMode", PERSISTENT},
{"ExperimentalLongitudinalEnabled", PERSISTENT}, // WARNING: THIS MAY DISABLE AEB
{"DisableUpdates", PERSISTENT},
{"DisengageOnAccelerator", PERSISTENT},

@ -1,5 +1,6 @@
#include "common/util.h"
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <dirent.h>
@ -149,6 +150,14 @@ int safe_fflush(FILE *stream) {
return ret;
}
int safe_ioctl(int fd, unsigned long request, void *argp) {
int ret;
do {
ret = ioctl(fd, request, argp);
} while ((ret == -1) && (errno == EINTR));
return ret;
}
std::string readlink(const std::string &path) {
char buff[4096];
ssize_t len = ::readlink(path.c_str(), buff, sizeof(buff)-1);

@ -86,6 +86,7 @@ int write_file(const char* path, const void* data, size_t size, int flags = O_WR
FILE* safe_fopen(const char* filename, const char* mode);
size_t safe_fwrite(const void * ptr, size_t size, size_t count, FILE * stream);
int safe_fflush(FILE *stream);
int safe_ioctl(int fd, unsigned long request, void *argp);
std::string readlink(const std::string& path);
bool file_exists(const std::string& fn);

@ -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.
# 213 Supported Cars
# 214 Supported Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|
@ -31,6 +31,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|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|GV70 2022-23|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|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|
|Honda|Accord 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|
@ -60,7 +61,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|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|Ioniq 5 (with HDA II) 2022-23|Highway Driving Assist II|Stock|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|Highway Driving Assist|Stock|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|Highway Driving Assist|Stock|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 2020|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|
|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|
@ -166,7 +167,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|Toyota|Camry Hybrid 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Corolla 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Corolla Cross (Non-US only) 2020-21|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|

@ -90,6 +90,7 @@ selfdrive/boardd/boardd_api_impl.pyx
selfdrive/boardd/can_list_to_can_capnp.cc
selfdrive/boardd/panda.cc
selfdrive/boardd/panda.h
selfdrive/boardd/spi.cc
selfdrive/boardd/panda_comms.h
selfdrive/boardd/panda_comms.cc
selfdrive/boardd/set_time.py

@ -1,9 +1,9 @@
Import('env', 'envCython', 'common', 'cereal', 'messaging')
libs = ['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj']
env.Program('boardd', ['main.cc', 'boardd.cc', 'panda.cc', 'panda_comms.cc'], LIBS=libs)
env.Program('boardd', ['main.cc', 'boardd.cc', 'panda.cc', 'panda_comms.cc', 'spi.cc'], LIBS=libs)
env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc'])
envCython.Program('boardd_api_impl.so', 'boardd_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"])
if GetOption('test'):
env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc', 'panda.cc', 'panda_comms.cc'], LIBS=libs)
env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc', 'panda.cc', 'panda_comms.cc', 'spi.cc'], LIBS=libs)

@ -12,7 +12,11 @@
Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) {
// TODO: support SPI here one day...
handle = std::make_unique<PandaUsbHandle>(serial);
if (serial.find("spi") != std::string::npos) {
handle = std::make_unique<PandaSpiHandle>(serial);
} else {
handle = std::make_unique<PandaUsbHandle>(serial);
}
hw_type = get_hw_type();

@ -8,6 +8,7 @@
#include <libusb-1.0/libusb.h>
#define TIMEOUT 0
#define SPI_BUF_SIZE 1024
// comms base class
@ -49,3 +50,24 @@ private:
std::vector<uint8_t> recv_buf;
void handle_usb_issue(int err, const char func[]);
};
class PandaSpiHandle : public PandaCommsHandle {
public:
PandaSpiHandle(std::string serial);
~PandaSpiHandle();
int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT);
int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT);
int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT);
int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT);
void cleanup();
static std::vector<std::string> list();
private:
int spi_fd = -1;
int spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len);
int wait_for_ack();
uint8_t tx_buf[SPI_BUF_SIZE];
uint8_t rx_buf[SPI_BUF_SIZE];
};

@ -0,0 +1,252 @@
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include <cassert>
#include <cstring>
#include "common/util.h"
#include "common/swaglog.h"
#include "selfdrive/boardd/panda_comms.h"
#define SPI_SYNC 0x5AU
#define SPI_HACK 0x79U
#define SPI_DACK 0x85U
#define SPI_NACK 0x1FU
#define SPI_CHECKSUM_START 0xABU
struct __attribute__((packed)) spi_header {
uint8_t sync;
uint8_t endpoint;
uint16_t tx_len;
uint16_t max_rx_len;
};
struct __attribute__((packed)) spi_control_packet {
uint16_t request;
uint16_t param1;
uint16_t param2;
uint16_t length;
};
PandaSpiHandle::PandaSpiHandle(std::string serial) : PandaCommsHandle(serial) {
LOGD("opening SPI panda: %s", serial.c_str());
int err;
uint32_t spi_mode = SPI_MODE_0;
uint32_t spi_speed = 30000000;
uint8_t spi_bits_per_word = 8;
spi_fd = open(serial.c_str(), O_RDWR);
if (spi_fd < 0) {
LOGE("failed setting SPI mode %d", err);
goto fail;
}
// SPI settings
err = util::safe_ioctl(spi_fd, SPI_IOC_WR_MODE, &spi_mode);
if (err < 0) {
LOGE("failed setting SPI mode %d", err);
goto fail;
}
err = util::safe_ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed);
if (err < 0) {
LOGE("failed setting SPI speed");
goto fail;
}
err = util::safe_ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits_per_word);
if (err < 0) {
LOGE("failed setting SPI bits per word");
goto fail;
}
return;
fail:
cleanup();
throw std::runtime_error("Error connecting to panda");
}
PandaSpiHandle::~PandaSpiHandle() {
std::lock_guard lk(hw_lock);
cleanup();
}
void PandaSpiHandle::cleanup() {
if (spi_fd != -1) {
close(spi_fd);
spi_fd = -1;
}
}
int PandaSpiHandle::control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout) {
int err;
std::lock_guard lk(hw_lock);
do {
spi_control_packet packet = {
.request = request,
.param1 = param1,
.param2 = param2,
.length = 0
};
// TODO: handle error
err = spi_transfer(0, (uint8_t *) &packet, sizeof(packet), NULL, 0);
} while (err < 0 && connected);
return err;
}
int PandaSpiHandle::control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout) {
int err;
std::lock_guard lk(hw_lock);
do {
spi_control_packet packet = {
.request = request,
.param1 = param1,
.param2 = param2,
.length = length
};
// TODO: handle error
err = spi_transfer(0, (uint8_t *) &packet, sizeof(packet), data, length);
} while (err < 0 && connected);
return err;
}
int PandaSpiHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
return 0;
}
int PandaSpiHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
return 0;
}
std::vector<std::string> PandaSpiHandle::list() {
// TODO: list all pandas available over SPI
return {};
}
void add_checksum(uint8_t *data, int data_len) {
data[data_len] = SPI_CHECKSUM_START;
for (int i=0; i < data_len; i++) {
data[data_len] ^= data[i];
}
}
int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len) {
int ret;
uint16_t rx_data_len;
// needs to be less, since we need to have space for the checksum
assert(tx_len < SPI_BUF_SIZE);
assert(max_rx_len < SPI_BUF_SIZE);
spi_header header = {
.sync = SPI_SYNC,
.endpoint = endpoint,
.tx_len = tx_len,
.max_rx_len = max_rx_len
};
spi_ioc_transfer transfer = {
.tx_buf = (uint64_t)tx_buf,
.rx_buf = (uint64_t)rx_buf
};
// Send header
memcpy(tx_buf, &header, sizeof(header));
add_checksum(tx_buf, sizeof(header));
transfer.len = sizeof(header) + 1;
ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
if (ret < 0) {
LOGE("SPI: failed to send header");
goto transfer_fail;
}
// Wait for (N)ACK
tx_buf[0] = 0x12;
transfer.len = 1;
while (true) {
ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
if (ret < 0) {
LOGE("SPI: failed to send ACK request");
goto transfer_fail;
}
if (rx_buf[0] == SPI_HACK) {
break;
} else if (rx_buf[0] == SPI_NACK) {
LOGW("SPI: got header NACK");
goto transfer_fail;
}
}
// Send data
if (tx_data != NULL) {
memcpy(tx_buf, tx_data, tx_len);
}
add_checksum(tx_buf, tx_len);
transfer.len = tx_len + 1;
ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
if (ret < 0) {
LOGE("SPI: failed to send data");
goto transfer_fail;
}
// Wait for (N)ACK
tx_buf[0] = 0xab;
transfer.len = 1;
while (true) {
ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
if (ret < 0) {
LOGE("SPI: failed to send ACK request");
goto transfer_fail;
}
if (rx_buf[0] == SPI_DACK) {
break;
} else if (rx_buf[0] == SPI_NACK) {
LOGE("SPI: got data NACK");
goto transfer_fail;
}
}
// Read data len
transfer.len = 2;
ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
if (ret < 0) {
LOGE("SPI: failed to read rx data len");
goto transfer_fail;
}
rx_data_len = *(uint16_t *)rx_buf;
assert(rx_data_len < SPI_BUF_SIZE);
// Read data
transfer.len = rx_data_len + 1;
ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
if (ret < 0) {
LOGE("SPI: failed to read rx data");
goto transfer_fail;
}
// TODO: check checksum
if (rx_data != NULL) {
memcpy(rx_data, rx_buf, rx_data_len);
}
ret = rx_data_len;
transfer_fail:
return ret;
}

@ -51,12 +51,15 @@ class CarState(CarStateBase):
else:
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["ECMPRDNL2"]["PRNDL2"], None))
# Some Volt 2016-17 have loose brake pedal push rod retainers which causes the ECM to believe
# that the brake is being intermittently pressed without user interaction.
# To avoid a cruise fault we need to match the ECM's brake pressed signal and threshold
# https://static.nhtsa.gov/odi/tsbs/2017/MC-10137629-9999.pdf
ret.brake = pt_cp.vl["ECMAcceleratorPos"]["BrakePedalPos"]
ret.brakePressed = ret.brake >= 8
if self.CP.networkLocation == NetworkLocation.fwdCamera:
ret.brakePressed = pt_cp.vl["ECMEngineStatus"]["BrakePressed"] != 0
else:
# Some Volt 2016-17 have loose brake pedal push rod retainers which causes the ECM to believe
# that the brake is being intermittently pressed without user interaction.
# To avoid a cruise fault we need to use a conservative brake position threshold
# https://static.nhtsa.gov/odi/tsbs/2017/MC-10137629-9999.pdf
ret.brakePressed = ret.brake >= 8
# Regen braking is braking
if self.CP.transmissionType == TransmissionType.direct:
@ -154,6 +157,7 @@ class CarState(CarStateBase):
("TractionControlOn", "ESPStatus"),
("ParkBrake", "VehicleIgnitionAlt"),
("CruiseMainOn", "ECMEngineStatus"),
("BrakePressed", "ECMEngineStatus"),
]
checks = [

@ -200,7 +200,7 @@ class CarState(CarStateBase):
self.is_metric = cp.vl["CLUSTER_INFO"]["DISTANCE_UNIT"] != 1
if not self.CP.openpilotLongitudinalControl:
speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS
cp_cruise_info = cp if self.CP.flags & HyundaiFlags.CANFD_HDA2 else cp_cam
cp_cruise_info = cp_cam if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp
ret.cruiseState.speed = cp_cruise_info.vl["SCC_CONTROL"]["VSetDis"] * speed_factor
ret.cruiseState.standstill = cp_cruise_info.vl["SCC_CONTROL"]["CRUISE_STANDSTILL"] == 1
ret.cruiseState.enabled = cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] in (1, 2)
@ -467,7 +467,7 @@ class CarState(CarStateBase):
("BLINDSPOTS_REAR_CORNERS", 20),
]
if CP.flags & HyundaiFlags.CANFD_HDA2 and not CP.openpilotLongitudinalControl:
if not (CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value) and not CP.openpilotLongitudinalControl:
signals += [
("ACCMode", "SCC_CONTROL"),
("VSetDis", "SCC_CONTROL"),
@ -504,11 +504,13 @@ class CarState(CarStateBase):
@staticmethod
def get_cam_can_parser_canfd(CP):
signals = []
checks = []
if CP.flags & HyundaiFlags.CANFD_HDA2:
signals = [(f"BYTE{i}", "CAM_0x2a4") for i in range(3, 24)]
checks = [("CAM_0x2a4", 20)]
else:
signals = [
signals += [(f"BYTE{i}", "CAM_0x2a4") for i in range(3, 24)]
checks += [("CAM_0x2a4", 20)]
elif CP.flags & HyundaiFlags.CANFD_CAMERA_SCC:
signals += [
("COUNTER", "SCC_CONTROL"),
("NEW_SIGNAL_1", "SCC_CONTROL"),
("MainMode_ACC", "SCC_CONTROL"),
@ -521,7 +523,7 @@ class CarState(CarStateBase):
("VSetDis", "SCC_CONTROL"),
]
checks = [
checks += [
("SCC_CONTROL", 50),
]

@ -2,7 +2,7 @@
from cereal import car
from panda import Panda
from common.conversions import Conversions as CV
from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons, CarControllerParams
from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, CANFD_RADAR_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons, CarControllerParams
from selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR
from selfdrive.car import STD_CARGO_KG, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config
from selfdrive.car.interfaces import CarInterfaceBase
@ -44,6 +44,8 @@ class CarInterface(CarInterfaceBase):
# ICE cars do not have 0x130; GEARS message on 0x40 instead
if 0x130 not in fingerprint[4]:
ret.flags |= HyundaiFlags.CANFD_ALT_GEARS.value
if candidate not in CANFD_RADAR_SCC_CAR:
ret.flags |= HyundaiFlags.CANFD_CAMERA_SCC.value
ret.steerActuatorDelay = 0.1 # Default delay
ret.steerLimitTimer = 0.4
@ -200,6 +202,10 @@ class CarInterface(CarInterfaceBase):
ret.mass = 3673.0 * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.83
ret.steerRatio = 12.9
elif candidate == CAR.GENESIS_GV70_1ST_GEN:
ret.mass = 1950. + STD_CARGO_KG
ret.wheelbase = 2.87
ret.steerRatio = 14.6
elif candidate == CAR.GENESIS_G80:
ret.mass = 2060. + STD_CARGO_KG
ret.wheelbase = 3.01
@ -244,6 +250,8 @@ class CarInterface(CarInterfaceBase):
ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2
if ret.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_ALT_BUTTONS
if ret.flags & HyundaiFlags.CANFD_CAMERA_SCC:
ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC
else:
if candidate in LEGACY_SAFETY_MODE_CAR:
# these cars require a special panda safety mode due to missing counters and checksums in the messages

@ -48,6 +48,7 @@ class HyundaiFlags(IntFlag):
CANFD_HDA2 = 1
CANFD_ALT_BUTTONS = 2
CANFD_ALT_GEARS = 4
CANFD_CAMERA_SCC = 8
class CAR:
@ -100,6 +101,7 @@ class CAR:
# Genesis
GENESIS_G70 = "GENESIS G70 2018"
GENESIS_G70_2020 = "GENESIS G70 2020"
GENESIS_GV70_1ST_GEN = "GENESIS GV70 1ST GEN"
GENESIS_G80 = "GENESIS G80 2017"
GENESIS_G90 = "GENESIS G90 2017"
@ -145,7 +147,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
CAR.VELOSTER: HyundaiCarInfo("Hyundai Veloster 2019-20", min_enable_speed=5. * CV.MPH_TO_MS, harness=Harness.hyundai_e),
CAR.SONATA_HYBRID: HyundaiCarInfo("Hyundai Sonata Hybrid 2020-22", "All", harness=Harness.hyundai_a),
CAR.IONIQ_5: [
HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022" , "Highway Driving Assist", harness=Harness.hyundai_k),
HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022-23" , "Highway Driving Assist", harness=Harness.hyundai_k),
HyundaiCarInfo("Hyundai Ioniq 5 (with HDA II) 2022-23", "Highway Driving Assist II", harness=Harness.hyundai_q),
],
CAR.TUCSON_HYBRID_4TH_GEN: HyundaiCarInfo("Hyundai Tucson Hybrid 2022", "All", harness=Harness.hyundai_n),
@ -188,6 +190,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
# Genesis
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_GV70_1ST_GEN: HyundaiCarInfo("Genesis GV70 2022-23", "All", harness=Harness.hyundai_l),
CAR.GENESIS_G80: HyundaiCarInfo("Genesis G80 2017-19", "All", harness=Harness.hyundai_h),
CAR.GENESIS_G90: HyundaiCarInfo("Genesis G90 2017-18", "All", harness=Harness.hyundai_c),
}
@ -465,6 +468,7 @@ FW_VERSIONS = {
b'\xf1\x82DNBWN5TMDCXXXG2E',
b'\xf1\x82DNCVN5GMCCXXXF0A',
b'\xf1\x82DNCVN5GMCCXXXG2B',
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\x82DNDWN5TMDCXXXJ1A',
b'\xf1\x87391162M003',
b'\xf1\x87391162M013',
@ -489,6 +493,7 @@ FW_VERSIONS = {
b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101',
b'\xf1\x8756310L0210\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC101',
b'\xf1\x8757700-L0000\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100',
b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP101',
],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.02 99211-L1000 190422',
@ -509,6 +514,7 @@ FW_VERSIONS = {
b'\xf1\x00HT6WA250BLHT6WA910A1SDN8G25NB1\x00\x00\x00\x00\x00\x00\x96\xa1\xf1\x92',
b'\xf1\x00HT6WA280BLHT6WAD10A1SDN8G25NB2\x00\x00\x00\x00\x00\x00\x08\xc9O:',
b'\xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5',
b'\xf1\x00T02601BL T02832A1 VDN8T25XXX832NS8G\x0e\xfeE',
b'\xf1\x87954A02N060\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5',
b'\xf1\x87SAKFBA2926554GJ2VefVww\x87xwwwww\x88\x87xww\x87wTo\xfb\xffvUo\xff\x8d\x16\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v',
b'\xf1\x87SAKFBA3030524GJ2UVugww\x97yx\x88\x87\x88vw\x87gww\x87wto\xf9\xfffUo\xff\xa2\x0c\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v',
@ -1374,6 +1380,7 @@ FW_VERSIONS = {
b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.05 99210-CV000 211027',
b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.06 99210-CV000 220328',
b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.05 99210-CV000 211027',
b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.06 99210-CV000 220328',
],
},
CAR.IONIQ_5: {
@ -1422,6 +1429,14 @@ FW_VERSIONS = {
b'\xf1\x00NQ5__ 1.00 1.03 99110-P1000 ',
],
},
CAR.GENESIS_GV70_1ST_GEN: {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.04 99211-AR000 210204',
],
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00JK1_ SCC FHCUP 1.00 1.02 99110-AR000 ',
],
},
}
CHECKSUM = {
@ -1439,7 +1454,10 @@ 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},
}
CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN}
CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, 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}
# The radar does SCC on these cars when HDA I, rather than the camera
CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, }
# The camera does SCC on these cars, rather than the radar
CAMERA_SCC_CAR = {CAR.KONA_EV_2022, }
@ -1499,4 +1517,5 @@ DBC = {
CAR.SANTA_CRUZ_1ST_GEN: dbc_dict('hyundai_canfd', None),
CAR.KIA_SPORTAGE_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),
}

@ -23,7 +23,7 @@ TorqueFromLateralAccelCallbackType = Callable[[float, car.CarParams.LateralTorqu
MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS
ACCEL_MAX = 2.0
ACCEL_MIN = -3.5
FRICTION_THRESHOLD = 0.2
FRICTION_THRESHOLD = 0.3
TORQUE_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/params.yaml')
TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/override.yaml')

@ -196,6 +196,7 @@ FW_VERSIONS = {
b'\xaa!dt\a',
b'\xc5!ar\a',
b'\xbe!as\a',
b'\xc5!as\x07',
b'\xc5!ds\a',
b'\xc5!`s\a',
b'\xaa!au\a',

@ -81,6 +81,7 @@ routes = [
CarTestRoute("6fe86b4e410e4c37|2020-07-22--16-27-13", HYUNDAI.HYUNDAI_GENESIS),
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("6b301bf83f10aa90|2020-11-22--16-45-07", HYUNDAI.GENESIS_G80),
CarTestRoute("f0709d2bc6ca451f|2022-10-15--08-13-54", HYUNDAI.SANTA_CRUZ_1ST_GEN),
CarTestRoute("4dbd55df87507948|2022-03-01--09-45-38", HYUNDAI.SANTA_FE),

@ -34,15 +34,18 @@ class TestCarInterfaces(unittest.TestCase):
self.assertGreater(car_params.maxLateralAccel, 0)
if car_params.steerControlType != car.CarParams.SteerControlType.angle:
tuning = car_params.lateralTuning.which()
if tuning == 'pid':
self.assertTrue(len(car_params.lateralTuning.pid.kpV))
elif tuning == 'torque':
kf = car_params.lateralTuning.torque.kf
self.assertTrue(not math.isnan(kf) and kf > 0)
self.assertTrue(not math.isnan(car_params.lateralTuning.torque.friction))
elif tuning == 'indi':
self.assertTrue(len(car_params.lateralTuning.indi.outerLoopGainV))
tune = car_params.lateralTuning
if tune.which() == 'pid':
self.assertTrue(not math.isnan(tune.pid.kf) and tune.pid.kf > 0)
self.assertTrue(len(tune.pid.kpV) > 0 and len(tune.pid.kpV) == len(tune.pid.kpBP))
self.assertTrue(len(tune.pid.kiV) > 0 and len(tune.pid.kiV) == len(tune.pid.kiBP))
elif tune.which() == 'torque':
self.assertTrue(not math.isnan(tune.torque.kf) and tune.torque.kf > 0)
self.assertTrue(not math.isnan(tune.torque.friction))
elif tune.which() == 'indi':
self.assertTrue(len(tune.indi.outerLoopGainV))
# Run car interface
CC = car.CarControl.new_message()

@ -34,6 +34,7 @@ HYUNDAI TUCSON HYBRID 4TH GEN: [2.5, 2.5, 0.0]
HYUNDAI SANTA CRUZ 1ST GEN: [2.7, 2.7, 0.0]
KIA SPORTAGE 5TH GEN: [2.7, 2.7, 0.0]
KIA SPORTAGE HYBRID 5TH GEN: [2.5, 2.5, 0.0]
GENESIS GV70 1ST GEN: [2.42, 2.42, 0.01]
# Dashcam or fallback configured as ideal car
mock: [10.0, 10, 0.0]

@ -15,6 +15,8 @@ class CarState(CarStateBase):
can_define = CANDefine(DBC[CP.carFingerprint]["pt"])
self.shifter_values = can_define.dv["GEAR_PACKET"]["GEAR"]
self.eps_torque_scale = EPS_SCALE[CP.carFingerprint] / 100.
self.cluster_speed_hyst_gap = CV.KPH_TO_MS / 2.
self.cluster_min_speed = CV.KPH_TO_MS / 2.
# On cars with cp.vl["STEER_TORQUE_SENSOR"]["STEER_ANGLE"]
# the signal is zeroed to where the steering angle is at start.
@ -52,6 +54,7 @@ class CarState(CarStateBase):
)
ret.vEgoRaw = mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr])
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
ret.vEgoCluster = ret.vEgo * 1.015 # minimum of all the cars
ret.standstill = ret.vEgoRaw == 0

@ -110,6 +110,21 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 14.3
tire_stiffness_factor = 0.7933
ret.mass = 3585. * CV.LB_TO_KG + STD_CARGO_KG # Average between ICE and Hybrid
ret.lateralTuning.init('pid')
ret.lateralTuning.pid.kiBP = [0.0]
ret.lateralTuning.pid.kpBP = [0.0]
ret.lateralTuning.pid.kpV = [0.6]
ret.lateralTuning.pid.kiV = [0.1]
ret.lateralTuning.pid.kf = 0.00007818594
# 2019+ RAV4 TSS2 uses two different steering racks and specific tuning seems to be necessary.
# See https://github.com/commaai/openpilot/pull/21429#issuecomment-873652891
for fw in car_fw:
if fw.ecu == "eps" and (fw.fwVersion.startswith(b'\x02') or fw.fwVersion in [b'8965B42181\x00\x00\x00\x00\x00\x00']):
ret.lateralTuning.pid.kpV = [0.15]
ret.lateralTuning.pid.kiV = [0.05]
ret.lateralTuning.pid.kf = 0.00004
break
elif candidate in (CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2):
stop_and_go = True

@ -119,7 +119,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = {
CAR.COROLLA: ToyotaCarInfo("Toyota Corolla 2017-19"),
CAR.COROLLA_TSS2: [
ToyotaCarInfo("Toyota Corolla 2020-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"),
ToyotaCarInfo("Toyota Corolla Cross (Non-US only) 2020-21", min_enable_speed=7.5),
ToyotaCarInfo("Toyota Corolla Cross (Non-US only) 2020-23", min_enable_speed=7.5),
ToyotaCarInfo("Toyota Corolla Hatchback 2019-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"),
],
CAR.COROLLAH_TSS2: [
@ -713,6 +713,7 @@ FW_VERSIONS = {
},
CAR.COROLLA_TSS2: {
(Ecu.engine, 0x700, None): [
b'\x01896630A22000\x00\x00\x00\x00',
b'\x01896630ZG2000\x00\x00\x00\x00',
b'\x01896630ZG5000\x00\x00\x00\x00',
b'\x01896630ZG5100\x00\x00\x00\x00',
@ -792,6 +793,7 @@ FW_VERSIONS = {
b'F152602191\x00\x00\x00\x00\x00\x00',
b'\x01F152612862\x00\x00\x00\x00\x00\x00',
b'\x01F152612B91\x00\x00\x00\x00\x00\x00',
b'\x01F15260A070\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F3301100\x00\x00\x00\x00',
@ -809,6 +811,7 @@ FW_VERSIONS = {
b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
b'\x028646F1202200\x00\x00\x00\x008646G2601500\x00\x00\x00\x00',
b'\x028646F1601100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
b'\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
],
},
CAR.COROLLAH_TSS2: {
@ -1389,6 +1392,7 @@ FW_VERSIONS = {
b'\x02896634A14001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00',
b'\x02896634A23000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x02896634A23001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00',
b'\x02896634A23101\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00',
b'\x02896634A14001\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
b'\x02896634A14101\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
],

@ -12,6 +12,7 @@ class CarState(CarStateBase):
super().__init__(CP)
self.CCP = CarControllerParams(CP)
self.button_states = {button.event_type: False for button in self.CCP.BUTTONS}
self.esp_hold_confirmation = False
def create_button_events(self, pt_cp, buttons):
button_events = []

@ -147,7 +147,7 @@ class Controls:
if not self.CP.experimentalLongitudinalAvailable:
self.params.remove("ExperimentalLongitudinalEnabled")
if not self.CP.openpilotLongitudinalControl:
self.params.remove("EndToEndLong")
self.params.remove("ExperimentalMode")
self.CC = car.CarControl.new_message()
self.CS_prev = car.CarState.new_message()

@ -5,7 +5,7 @@ from common.realtime import DT_MDL
LaneChangeState = log.LateralPlan.LaneChangeState
LaneChangeDirection = log.LateralPlan.LaneChangeDirection
LANE_CHANGE_SPEED_MIN = 15 * CV.MPH_TO_MS
LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS
LANE_CHANGE_TIME_MAX = 10.
DESIRES = {

@ -17,7 +17,8 @@ from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
# friction in the steering wheel that needs to be overcome to
# move it at all, this is compensated for too.
LOW_SPEED_FACTOR = 200
LOW_SPEED_X = [0, 10, 20, 30]
LOW_SPEED_Y = [15, 13, 10, 5]
class LatControlTorque(LatControl):
@ -57,8 +58,9 @@ class LatControlTorque(LatControl):
actual_lateral_accel = actual_curvature * CS.vEgo ** 2
lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2
setpoint = desired_lateral_accel + LOW_SPEED_FACTOR * desired_curvature
measurement = actual_lateral_accel + LOW_SPEED_FACTOR * actual_curvature
low_speed_factor = interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y)**2
setpoint = desired_lateral_accel + low_speed_factor * desired_curvature
measurement = actual_lateral_accel + low_speed_factor * actual_curvature
error = setpoint - measurement
gravity_adjusted_lateral_accel = desired_lateral_accel - params.roll * ACCELERATION_DUE_TO_GRAVITY
pid_log.error = self.torque_from_lateral_accel(error, self.torque_params, error,

@ -36,7 +36,7 @@ A_EGO_COST = 0.
J_EGO_COST = 5.0
A_CHANGE_COST = 200.
DANGER_ZONE_COST = 100.
CRASH_DISTANCE = .5
CRASH_DISTANCE = .25
LEAD_DANGER_FACTOR = 0.75
LIMIT_COST = 1e6
ACADOS_SOLVER_TYPE = 'SQP_RTI'
@ -49,6 +49,7 @@ MAX_T = 10.0
T_IDXS_LST = [index_function(idx, max_val=MAX_T, max_idx=N) for idx in range(N+1)]
T_IDXS = np.array(T_IDXS_LST)
FCW_IDXS = T_IDXS < 5.0
T_DIFFS = np.diff(T_IDXS, prepend=[0.])
MIN_ACCEL = -3.5
MAX_ACCEL = 2.0
@ -369,7 +370,7 @@ class LongitudinalMpc:
self.params[:,4] = T_FOLLOW
self.run()
if (np.any(lead_xv_0[:,0] - self.x_sol[:,0] < CRASH_DISTANCE) and
if (np.any(lead_xv_0[FCW_IDXS,0] - self.x_sol[FCW_IDXS,0] < CRASH_DISTANCE) and
radarstate.leadOne.modelProb > 0.9):
self.crash_cnt += 1
else:

@ -58,6 +58,7 @@ class LongitudinalPlanner:
self.a_desired = init_a
self.v_desired_filter = FirstOrderFilter(init_v, 2.0, DT_MDL)
self.v_model_error = 0.0
self.v_desired_trajectory = np.zeros(CONTROL_N)
self.a_desired_trajectory = np.zeros(CONTROL_N)
@ -65,15 +66,15 @@ class LongitudinalPlanner:
self.solverExecutionTime = 0.0
def read_param(self):
e2e = self.params.get_bool('EndToEndLong') and self.CP.openpilotLongitudinalControl
e2e = self.params.get_bool('ExperimentalMode') and self.CP.openpilotLongitudinalControl
self.mpc.mode = 'blended' if e2e else 'acc'
def parse_model(self, model_msg):
def parse_model(self, model_msg, model_error):
if (len(model_msg.position.x) == 33 and
len(model_msg.velocity.x) == 33 and
len(model_msg.acceleration.x) == 33):
x = np.interp(T_IDXS_MPC, T_IDXS, model_msg.position.x)
v = np.interp(T_IDXS_MPC, T_IDXS, model_msg.velocity.x)
x = np.interp(T_IDXS_MPC, T_IDXS, model_msg.position.x) - model_error * T_IDXS_MPC
v = np.interp(T_IDXS_MPC, T_IDXS, model_msg.velocity.x) - model_error
a = np.interp(T_IDXS_MPC, T_IDXS, model_msg.acceleration.x)
j = np.zeros(len(T_IDXS_MPC))
else:
@ -112,6 +113,9 @@ class LongitudinalPlanner:
# Prevent divergence, smooth in current v_ego
self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego))
# Compute model v_ego error
if len(sm['modelV2'].temporalPose.trans):
self.v_model_error = sm['modelV2'].temporalPose.trans[0] - v_ego
if force_slow_decel:
# if required so, force a smooth deceleration
@ -124,7 +128,7 @@ class LongitudinalPlanner:
self.mpc.set_weights(prev_accel_constraint)
self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1])
self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired)
x, v, a, j = self.parse_model(sm['modelV2'])
x, v, a, j = self.parse_model(sm['modelV2'], self.v_model_error)
self.mpc.update(sm['carState'], sm['radarState'], v_cruise, x, v, a, j)
self.v_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.v_solution)
@ -132,8 +136,7 @@ class LongitudinalPlanner:
self.j_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC[:-1], self.mpc.j_solution)
# TODO counter is only needed because radar is glitchy, remove once radar is gone
# TODO write fcw in e2e_long mode
self.fcw = self.mpc.mode == 'acc' and self.mpc.crash_cnt > 5 and not sm['carState'].standstill
self.fcw = self.mpc.crash_cnt > 2 and not sm['carState'].standstill
if self.fcw:
cloudlog.info("FCW triggered")

@ -26,7 +26,7 @@ class TestCruiseSpeed(unittest.TestCase):
def test_cruise_speed(self):
params = Params()
for e2e in [False, True]:
params.put_bool("EndToEndLong", e2e)
params.put_bool("ExperimentalMode", e2e)
for speed in np.arange(5, 40, 5):
print(f'Testing {speed} m/s')
cruise_speed = float(speed)

@ -27,7 +27,7 @@ class TestFollowingDistance(unittest.TestCase):
def test_following_distance(self):
params = Params()
for e2e in [False, True]:
params.put_bool("EndToEndLong", e2e)
params.put_bool("ExperimentalMode", e2e)
for speed in np.arange(0, 40, 5):
print(f'Testing {speed} m/s')
v_lead = float(speed)

@ -337,6 +337,17 @@ void fill_model(cereal::ModelDataV2::Builder &framed, const ModelOutput &net_out
for (int i=0; i<LEAD_MHP_SELECTION; i++) {
fill_lead(leads[i], net_outputs.leads, i, t_offsets[i]);
}
// temporal pose
const auto &v_mean = net_outputs.temporal_pose.velocity_mean;
const auto &r_mean = net_outputs.temporal_pose.rotation_mean;
const auto &v_std = net_outputs.temporal_pose.velocity_std;
const auto &r_std = net_outputs.temporal_pose.rotation_std;
auto temporal_pose = framed.initTemporalPose();
temporal_pose.setTrans({v_mean.x, v_mean.y, v_mean.z});
temporal_pose.setRot({r_mean.x, r_mean.y, r_mean.z});
temporal_pose.setTransStd({exp(v_std.x), exp(v_std.y), exp(v_std.z)});
temporal_pose.setRotStd({exp(r_std.x), exp(r_std.y), exp(r_std.z)});
}
void model_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_frame_id_extra, uint32_t frame_id, float frame_drop,

@ -167,6 +167,14 @@ struct ModelOutputWideFromDeviceEuler {
};
static_assert(sizeof(ModelOutputWideFromDeviceEuler) == sizeof(ModelOutputXYZ)*2);
struct ModelOutputTemporalPose {
ModelOutputXYZ velocity_mean;
ModelOutputXYZ rotation_mean;
ModelOutputXYZ velocity_std;
ModelOutputXYZ rotation_std;
};
static_assert(sizeof(ModelOutputTemporalPose) == sizeof(ModelOutputXYZ)*4);
struct ModelOutputDisengageProb {
float gas_disengage;
float brake_disengage;
@ -225,6 +233,7 @@ struct ModelOutput {
const ModelOutputMeta meta;
const ModelOutputPose pose;
const ModelOutputWideFromDeviceEuler wide_from_device_euler;
const ModelOutputTemporalPose temporal_pose;
};
constexpr int OUTPUT_SIZE = sizeof(ModelOutput) / sizeof(float);

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dcfad22cecf37275d01a339d96174800c109e9a70f853fdef3e4ef62ed3f4bbe
size 45922983
oid sha256:db746e3753de84367595fedd089c2bd41b06bd401ea28e085663533d0e63d74b
size 45962473

@ -34,6 +34,7 @@ class DRIVER_MONITOR_SETTINGS():
self._EE_THRESH11 = 0.275
self._EE_THRESH12 = 5.5
self._EE_MAX_OFFSET1 = 0.06
self._EE_MIN_OFFSET1 = 0.025
self._EE_THRESH21 = 0.01
self._EE_THRESH22 = 0.35
@ -206,7 +207,7 @@ class DriverStatus():
distracted_types.append(DistractedType.DISTRACTED_BLINK)
if self.ee1_calibrated:
ee1_dist = self.eev1 > min(self.ee1_offseter.filtered_stat.M, self.settings._EE_MAX_OFFSET1) * self.settings._EE_THRESH12
ee1_dist = self.eev1 > max(min(self.ee1_offseter.filtered_stat.M, self.settings._EE_MAX_OFFSET1), self.settings._EE_MIN_OFFSET1) * self.settings._EE_THRESH12
else:
ee1_dist = self.eev1 > self.settings._EE_THRESH11
# if self.ee2_calibrated:

@ -143,11 +143,11 @@ def run_maneuver_worker(k):
params = Params()
man = maneuvers[k]
params.put_bool("EndToEndLong", True)
params.put_bool("ExperimentalMode", True)
print(man.title, ' in e2e mode')
valid, _ = man.evaluate()
self.assertTrue(valid, msg=man.title)
params.put_bool("EndToEndLong", False)
params.put_bool("ExperimentalMode", False)
print(man.title, ' in acc mode')
valid, _ = man.evaluate()
self.assertTrue(valid, msg=man.title)

@ -1 +1 @@
49ea844254883ac61caa2ac425f453799aeb28a6
30efb4238327d723e17a3bda7e7c19c18f8a3b18

@ -1 +1 @@
24a8d02b148b7f6d20f641d56a7bed71c244b6e3
a36f7e2fd922fcadca6f8a3d777f4db787cba016

@ -18,7 +18,7 @@ from tools.lib.logreader import LogReader
source_segments = [
("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY
("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.SONATA
("HYUNDAI2", "d545129f3ca90f28|2022-10-19--09-22-54--9"), # HYUNDAI.KIA_EV6
("HYUNDAI2", "d545129f3ca90f28|2022-11-07--20-43-08--3"), # HYUNDAI.KIA_EV6
("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.PRIUS (INDI)
("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.RAV4 (LQR)
("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2
@ -41,7 +41,7 @@ source_segments = [
segments = [
("BODY", "regenFA002A80700|2022-09-27--15-37-02--0"),
("HYUNDAI", "regenBE53A59065B|2022-09-27--16-52-03--0"),
("HYUNDAI2", "d545129f3ca90f28|2022-10-19--09-22-54--9"),
("HYUNDAI2", "d545129f3ca90f28|2022-11-07--20-43-08--3"),
("TOYOTA", "regen929C5790007|2022-09-27--16-27-47--0"),
("TOYOTA2", "regenEA3950D7F22|2022-09-27--15-43-24--0"),
("TOYOTA3", "regen89026F6BD8D|2022-09-27--15-45-37--0"),

@ -36,6 +36,22 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
"../assets/offroad/icon_openpilot.png",
false,
},
{
"ExperimentalMode",
tr("Experimental Mode"),
"",
"../assets/offroad/icon_road.png",
false,
},
{
"ExperimentalLongitudinalEnabled",
tr("Experimental openpilot Longitudinal Control"),
QString("<b>%1</b><br>%2")
.arg(tr("WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB)."))
.arg(tr("openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control.")),
"../assets/offroad/icon_speed_limit.png",
true,
},
{
"IsLdwEnabled",
tr("Enable Lane Departure Warnings"),
@ -59,25 +75,11 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
},
{
"DisengageOnAccelerator",
tr("Disengage On Accelerator Pedal"),
tr("Disengage on Accelerator Pedal"),
tr("When enabled, pressing the accelerator pedal will disengage openpilot."),
"../assets/offroad/icon_disengage_on_accelerator.svg",
false,
},
{
"EndToEndLong",
tr("🌮 End-to-end longitudinal (extremely alpha) 🌮"),
"",
"../assets/offroad/icon_road.png",
false,
},
{
"ExperimentalLongitudinalEnabled",
tr("Experimental openpilot longitudinal control"),
tr("<b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b>"),
"../assets/offroad/icon_speed_limit.png",
true,
},
#ifdef ENABLE_MAPS
{
"NavSettingTime24h",
@ -116,9 +118,15 @@ void TogglesPanel::showEvent(QShowEvent *event) {
}
void TogglesPanel::updateToggles() {
auto e2e_toggle = toggles["EndToEndLong"];
auto e2e_toggle = toggles["ExperimentalMode"];
auto op_long_toggle = toggles["ExperimentalLongitudinalEnabled"];
const QString e2e_description = tr("Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental.");
const QString e2e_description = tr("\
openpilot defaults to driving in <b>chill mode</b>.\
Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. \
Experimental features are listed below:\
<br> \
<h4>🌮 End-to-End Longitudinal Control 🌮</h4> \
Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound.");
auto cp_bytes = params.get("CarParamsPersistent");
if (!cp_bytes.empty()) {
@ -140,7 +148,7 @@ void TogglesPanel::updateToggles() {
} else {
// no long for now
e2e_toggle->setEnabled(false);
params.remove("EndToEndLong");
params.remove("ExperimentalMode");
const QString no_long = tr("openpilot longitudinal control is not currently available for this car.");
const QString exp_long = tr("Enable experimental longitudinal control to enable this.");

@ -170,7 +170,7 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) {
}
AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), accel_filter(UI_FREQ, .5, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) {
AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) {
pm = std::make_unique<PubMaster, const std::initializer_list<const char *>>({"uiDebug"});
engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size});
@ -404,6 +404,7 @@ void AnnotatedCameraWidget::drawText(QPainter &p, int x, int y, const QString &t
}
void AnnotatedCameraWidget::drawIcon(QPainter &p, int x, int y, QPixmap &img, QBrush bg, float opacity) {
p.setOpacity(1.0); // bg dictates opacity of ellipse
p.setPen(Qt::NoPen);
p.setBrush(bg);
p.drawEllipse(x - radius / 2, y - radius / 2, radius, radius);
@ -460,37 +461,26 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) {
// paint path
QLinearGradient bg(0, height(), 0, height() / 4);
float start_hue, end_hue;
if (scene.end_to_end_long) {
if (scene.experimental_mode) {
const auto &acceleration = (*s->sm)["modelV2"].getModelV2().getAcceleration();
float acceleration_future = 0;
if (acceleration.getZ().size() > 10 && (*s->sm)["carControl"].getCarControl().getLongActive()) {
acceleration_future = acceleration.getX()[10]; // 1.0 second
if (acceleration.getZ().size() > 16) {
acceleration_future = acceleration.getX()[16]; // 2.5 seconds
}
// speed up: 148, slow down: 0
start_hue = fmax(fmin(60 + accel_filter.update(acceleration_future) * 80, 148), 0);
// FIXME: painter.drawPolygon can be slow if hue is not rounded
start_hue = int(start_hue * 100 + 0.5) / 100;
bg.setColorAt(0.0, QColor::fromHslF(start_hue / 360., 0.97, 0.56, 0.4));
bg.setColorAt(0.75, QColor::fromHslF(63 / 360., 1.0, 0.68, 0.35));
bg.setColorAt(1.0, QColor::fromHslF(63 / 360., 1.0, 0.68, 0.0));
} else {
const auto &orientation = (*s->sm)["modelV2"].getModelV2().getOrientation();
float orientation_future = 0;
if (orientation.getZ().size() > 16) {
orientation_future = std::abs(orientation.getZ()[16]); // 2.5 seconds
}
start_hue = 148;
// straight: 112, in turns: 70
end_hue = fmax(70, 112 - (orientation_future * 420));
start_hue = 60;
// speed up: 120, slow down: 0
end_hue = fmax(fmin(start_hue + acceleration_future * 45, 148), 0);
// FIXME: painter.drawPolygon can be slow if hue is not rounded
end_hue = int(end_hue * 100 + 0.5) / 100;
bg.setColorAt(0.0, QColor::fromHslF(start_hue / 360., 0.94, 0.51, 0.4));
bg.setColorAt(0.0, QColor::fromHslF(start_hue / 360., 0.97, 0.56, 0.4));
bg.setColorAt(0.5, QColor::fromHslF(end_hue / 360., 1.0, 0.68, 0.35));
bg.setColorAt(1.0, QColor::fromHslF(end_hue / 360., 1.0, 0.68, 0.0));
} else {
bg.setColorAt(0.0, QColor::fromHslF(148 / 360., 0.94, 0.51, 0.4));
bg.setColorAt(0.5, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.35));
bg.setColorAt(1.0, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.0));
}
painter.setBrush(bg);
@ -565,6 +555,7 @@ void AnnotatedCameraWidget::paintGL() {
} else if (v_ego > 15) {
wide_cam_requested = false;
}
wide_cam_requested = wide_cam_requested && s->scene.experimental_mode;
// TODO: also detect when ecam vision stream isn't available
// for replay of old routes, never go to widecam
wide_cam_requested = wide_cam_requested && s->scene.calibration_wide_valid;

@ -87,7 +87,6 @@ protected:
double prev_draw_t = 0;
FirstOrderFilter fps_filter;
FirstOrderFilter accel_filter;
};
// container for all onroad widgets

@ -142,9 +142,9 @@ public:
ParamControl(const QString &param, const QString &title, const QString &desc, const QString &icon, const bool confirm, QWidget *parent = nullptr) : ToggleControl(title, desc, icon, false, parent) {
key = param.toStdString();
QObject::connect(this, &ParamControl::toggleFlipped, [=](bool state) {
QString content("<body><h2>" + title + "</h2><br><br>"
"<p style=\"text-align: center; margin: 0 128px;\">" + getDescription() + "</p></body>");
ConfirmationDialog dialog(content, tr("Ok"), tr("Cancel"), true, this);
QString content("<body><h2 style=\"text-align: center;\">" + title + "</h2><br>"
"<p style=\"text-align: center; margin: 0 128px; font-size: 50px;\">" + getDescription() + "</p></body>");
ConfirmationDialog dialog(content, tr("Enable"), tr("Cancel"), true, this);
if (!confirm || !state || dialog.exec()) {
params.putBool(key, state);
} else {

@ -9,6 +9,7 @@ import xml.etree.ElementTree as ET
from selfdrive.ui.update_translations import TRANSLATIONS_DIR, LANGUAGES_FILE, update_translations
TMP_TRANSLATIONS_DIR = os.path.join(TRANSLATIONS_DIR, "tmp")
UNFINISHED_TRANSLATION_TAG = "<translation type=\"unfinished\"" # non-empty translations can be marked unfinished
LOCATION_TAG = "<location "
@ -56,7 +57,7 @@ class TestTranslations(unittest.TestCase):
for name, file in self.translation_files.items():
with self.subTest(name=name, file=file):
cur_translations = self._read_translation_file(TRANSLATIONS_DIR, file)
self.assertTrue("<translation type=\"unfinished\">" not in cur_translations,
self.assertTrue(UNFINISHED_TRANSLATION_TAG not in cur_translations,
f"{file} ({name}) translation file has unfinished translations. Finish translations or mark them as completed in Qt Linguist")
def test_vanished_translations(self):

@ -2,19 +2,22 @@
import json
import os
import requests
import xml.etree.ElementTree as ET
from common.basedir import BASEDIR
from selfdrive.ui.tests.test_translations import UNFINISHED_TRANSLATION_TAG
from selfdrive.ui.update_translations import LANGUAGES_FILE, TRANSLATIONS_DIR
TRANSLATION_TAG = "<translation"
UNFINISHED_TRANSLATION_TAG = "<translation type=\"unfinished\""
BADGE_HEIGHT = 20 + 8
SHIELDS_URL = "https://img.shields.io/badge"
if __name__ == "__main__":
with open(LANGUAGES_FILE, "r") as f:
translation_files = json.load(f)
badge_svg = [f'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="{len(translation_files) * BADGE_HEIGHT}">']
badge_svg = []
max_badge_width = 0 # keep track of max width to set parent element
for idx, (name, file) in enumerate(translation_files.items()):
with open(os.path.join(TRANSLATIONS_DIR, f"{file}.ts"), "r") as tr_f:
tr_file = tr_f.read()
@ -28,19 +31,30 @@ if __name__ == "__main__":
unfinished_translations += 1
percent_finished = int(100 - (unfinished_translations / total_translations * 100.))
color = "green" if percent_finished == 100 else "orange" if percent_finished >= 70 else "red"
color = "green" if percent_finished == 100 else "orange" if percent_finished > 90 else "red"
r = requests.get(f"https://img.shields.io/badge/LANGUAGE {name}-{percent_finished}%25 complete-{color}", timeout=10)
# Download badge
badge_label = f"LANGUAGE {name}"
badge_message = f"{percent_finished}% complete"
if unfinished_translations != 0:
badge_message += f" ({unfinished_translations} unfinished)"
r = requests.get(f"{SHIELDS_URL}/{badge_label}-{badge_message}-{color}", timeout=10)
assert r.status_code == 200, "Error downloading badge"
content_svg = r.content.decode("utf-8")
# make tag ids in each badge unique
xml = ET.fromstring(content_svg)
assert "width" in xml.attrib
max_badge_width = max(max_badge_width, int(xml.attrib["width"]))
# Make tag ids in each badge unique to combine them into one svg
for tag in ("r", "s"):
content_svg = content_svg.replace(f'id="{tag}"', f'id="{tag}{idx}"')
content_svg = content_svg.replace(f'"url(#{tag})"', f'"url(#{tag}{idx})"')
badge_svg.extend([f'<g transform="translate(0, {idx * BADGE_HEIGHT})">', content_svg, "</g>"])
badge_svg.insert(0, f'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="{len(translation_files) * BADGE_HEIGHT}" width="{max_badge_width}">')
badge_svg.append("</svg>")
with open(os.path.join(BASEDIR, "translation_badge.svg"), "w") as badge_f:

@ -1023,7 +1023,7 @@ location set</source>
<translation>قم بتحميل البيانات من الكاميرا المواجهة للسائق وساعد في تحسين خوارزمية مراقبة السائق.</translation>
</message>
<message>
<source>Disengage On Accelerator Pedal</source>
<source>Disengage on Accelerator Pedal</source>
<translation>فك الارتباط على دواسة التسريع</translation>
</message>
<message>
@ -1059,7 +1059,7 @@ location set</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Experimental openpilot longitudinal control</source>
<source>Experimental openpilot Longitudinal Control</source>
<translation type="unfinished"></translation>
</message>
<message>

@ -474,12 +474,12 @@ location set</source>
<context>
<name>ParamControl</name>
<message>
<source>Ok</source>
<translation type="unfinished">OK</translation>
<source>Cancel</source>
<translation></translation>
</message>
<message>
<source>Cancel</source>
<translation type="unfinished"></translation>
<source>Enable</source>
<translation></translation>
</message>
</context>
<context>
@ -967,21 +967,9 @@ location set</source>
<translation></translation>
</message>
<message>
<source>🌮 End-to-end longitudinal (extremely alpha) 🌮</source>
<translation>🌮 (α) 🌮</translation>
</message>
<message>
<source>Experimental openpilot longitudinal control</source>
<source>Experimental openpilot Longitudinal Control</source>
<translation>openpilotによるアクセル制御</translation>
</message>
<message>
<source>&lt;b&gt;WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.&lt;/b&gt;</source>
<translation>&lt;b&gt;警告: openpilotによるアクセル制御は実験段階でありAEBを無効化します&lt;/b&gt;</translation>
</message>
<message>
<source>Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental.</source>
<translation>openpilotに任せますopenpilotが人間と同じように運転します</translation>
</message>
<message>
<source>openpilot longitudinal control is not currently available for this car.</source>
<translation>openpilotによるアクセル制御は</translation>
@ -991,7 +979,7 @@ location set</source>
<translation>使openpilotによるアクセル制御</translation>
</message>
<message>
<source>Disengage On Accelerator Pedal</source>
<source>Disengage on Accelerator Pedal</source>
<translation> openpilot </translation>
</message>
<message>
@ -1014,6 +1002,22 @@ location set</source>
<source>Show map on left side when in split screen view.</source>
<translation></translation>
</message>
<message>
<source>Experimental Mode</source>
<translation type="unfinished"></translation>
</message>
<message>
<source> openpilot defaults to driving in &lt;b&gt;chill mode&lt;/b&gt;. Experimental mode enables &lt;b&gt;alpha-level features&lt;/b&gt; that aren&apos;t ready for chill mode. Experimental features are listed below: &lt;br&gt; &lt;h4&gt;🌮 End-to-End Longitudinal Control 🌮&lt;/h4&gt; Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control on this car. Enable this to switch to openpilot longitudinal control.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Updater</name>

@ -240,11 +240,11 @@
</message>
<message>
<source>Reset</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<source>Review</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
</context>
<context>
@ -474,12 +474,12 @@ location set</source>
<context>
<name>ParamControl</name>
<message>
<source>Ok</source>
<translation type="unfinished"></translation>
<source>Cancel</source>
<translation></translation>
</message>
<message>
<source>Cancel</source>
<translation type="unfinished"></translation>
<source>Enable</source>
<translation></translation>
</message>
</context>
<context>
@ -864,7 +864,7 @@ location set</source>
</message>
<message>
<source>Uninstall</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
</context>
<context>
@ -967,21 +967,9 @@ location set</source>
<translation> .</translation>
</message>
<message>
<source>🌮 End-to-end longitudinal (extremely alpha) 🌮</source>
<translation>🌮 e2e ( ) 🌮 </translation>
</message>
<message>
<source>Experimental openpilot longitudinal control</source>
<source>Experimental openpilot Longitudinal Control</source>
<translation>openpilot ()</translation>
</message>
<message>
<source>&lt;b&gt;WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.&lt;/b&gt;</source>
<translation>&lt;b&gt;경고: openpilot AEB를 .&lt;/b&gt;</translation>
</message>
<message>
<source>Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental.</source>
<translation> openpilot은 . ( )</translation>
</message>
<message>
<source>openpilot longitudinal control is not currently available for this car.</source>
<translation> openpilot .</translation>
@ -991,7 +979,7 @@ location set</source>
<translation>openpilot . ()</translation>
</message>
<message>
<source>Disengage On Accelerator Pedal</source>
<source>Disengage on Accelerator Pedal</source>
<translation> </translation>
</message>
<message>
@ -1014,6 +1002,22 @@ location set</source>
<source>Show map on left side when in split screen view.</source>
<translation> .</translation>
</message>
<message>
<source>Experimental Mode</source>
<translation> </translation>
</message>
<message>
<source> openpilot defaults to driving in &lt;b&gt;chill mode&lt;/b&gt;. Experimental mode enables &lt;b&gt;alpha-level features&lt;/b&gt; that aren&apos;t ready for chill mode. Experimental features are listed below: &lt;br&gt; &lt;h4&gt;🌮 End-to-End Longitudinal Control 🌮&lt;/h4&gt; Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control on this car. Enable this to switch to openpilot longitudinal control.</source>
<translation>openpilot은 ACC로 . .</translation>
</message>
<message>
<source>WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Updater</name>
@ -1070,7 +1074,7 @@ location set</source>
</message>
<message>
<source>Forget</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
</context>
</TS>

@ -1007,7 +1007,7 @@ ingesteld</translation>
<translation>Upload gegevens van de bestuurders camera en help het algoritme voor het monitoren van de bestuurder te verbeteren.</translation>
</message>
<message>
<source>Disengage On Accelerator Pedal</source>
<source>Disengage on Accelerator Pedal</source>
<translation>Deactiveren Met Gaspedaal</translation>
</message>
<message>
@ -1043,7 +1043,7 @@ ingesteld</translation>
<translation type="unfinished"></translation>
</message>
<message>
<source>Experimental openpilot longitudinal control</source>
<source>Experimental openpilot Longitudinal Control</source>
<translation type="unfinished"></translation>
</message>
<message>

@ -1011,7 +1011,7 @@ nie zostało ustawione</translation>
<translation>Prześlij dane z kamery skierowanej na kierowcę i pomóż poprawiać algorytm monitorowania kierowcy.</translation>
</message>
<message>
<source>Disengage On Accelerator Pedal</source>
<source>Disengage on Accelerator Pedal</source>
<translation>Odłącz poprzez naciśnięcie gazu</translation>
</message>
<message>
@ -1047,7 +1047,7 @@ nie zostało ustawione</translation>
<translation type="unfinished"></translation>
</message>
<message>
<source>Experimental openpilot longitudinal control</source>
<source>Experimental openpilot Longitudinal Control</source>
<translation type="unfinished"></translation>
</message>
<message>

@ -240,11 +240,11 @@
</message>
<message>
<source>Reset</source>
<translation type="unfinished"></translation>
<translation>Resetar</translation>
</message>
<message>
<source>Review</source>
<translation type="unfinished"></translation>
<translation>Revisar</translation>
</message>
</context>
<context>
@ -475,12 +475,12 @@ trabalho definido</translation>
<context>
<name>ParamControl</name>
<message>
<source>Ok</source>
<translation type="unfinished">OK</translation>
<source>Cancel</source>
<translation>Cancelar</translation>
</message>
<message>
<source>Cancel</source>
<translation type="unfinished">Cancelar</translation>
<source>Enable</source>
<translation>Ativar</translation>
</message>
</context>
<context>
@ -868,7 +868,7 @@ trabalho definido</translation>
</message>
<message>
<source>Uninstall</source>
<translation type="unfinished"></translation>
<translation>Desinstalar</translation>
</message>
</context>
<context>
@ -971,21 +971,9 @@ trabalho definido</translation>
<translation>Upload dados da câmera voltada para o motorista e ajude a melhorar o algoritmo de monitoramentor.</translation>
</message>
<message>
<source>🌮 End-to-end longitudinal (extremely alpha) 🌮</source>
<translation>🌮 End-to-end longitudinal (experimental) 🌮</translation>
</message>
<message>
<source>Experimental openpilot longitudinal control</source>
<source>Experimental openpilot Longitudinal Control</source>
<translation>Controle longitudinal experimental openpilot</translation>
</message>
<message>
<source>&lt;b&gt;WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.&lt;/b&gt;</source>
<translation>&lt;b&gt;AVISO: o controle longitudinal openpilot é experimental para este carro e irá desabilitar AEB.&lt;/b&gt;</translation>
</message>
<message>
<source>Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental.</source>
<translation>Deixe o modelo controlar o acelerador e os freios. openpilot irá conduzir como pensa que um humano faria. Super experimental.</translation>
</message>
<message>
<source>openpilot longitudinal control is not currently available for this car.</source>
<translation>controle longitudinal openpilot não está disponível para este carro.</translation>
@ -995,7 +983,7 @@ trabalho definido</translation>
<translation>Habilite o controle longitudinal experimental para habilitar isso.</translation>
</message>
<message>
<source>Disengage On Accelerator Pedal</source>
<source>Disengage on Accelerator Pedal</source>
<translation>Desacionar Com Pedal Do Acelerador</translation>
</message>
<message>
@ -1018,6 +1006,22 @@ trabalho definido</translation>
<source>Show map on left side when in split screen view.</source>
<translation>Exibir mapa do lado esquerdo quando a tela for dividida.</translation>
</message>
<message>
<source>Experimental Mode</source>
<translation>Modo Experimental</translation>
</message>
<message>
<source> openpilot defaults to driving in &lt;b&gt;chill mode&lt;/b&gt;. Experimental mode enables &lt;b&gt;alpha-level features&lt;/b&gt; that aren&apos;t ready for chill mode. Experimental features are listed below: &lt;br&gt; &lt;h4&gt;🌮 End-to-End Longitudinal Control 🌮&lt;/h4&gt; Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control on this car. Enable this to switch to openpilot longitudinal control.</source>
<translation>O padrão do openpilot é o ACC integrado do carro em vez do controle longitudinal do openpilot neste carro. Habilite isto para alternar para controle longitudinal do openpilot.</translation>
</message>
<message>
<source>WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Updater</name>
@ -1074,7 +1078,7 @@ trabalho definido</translation>
</message>
<message>
<source>Forget</source>
<translation type="unfinished"></translation>
<translation>Esquecer</translation>
</message>
</context>
</TS>

@ -951,7 +951,7 @@ location set</source>
<translation> </translation>
</message>
<message>
<source>Disengage On Accelerator Pedal</source>
<source>Disengage on Accelerator Pedal</source>
<translation></translation>
</message>
<message>
@ -979,7 +979,7 @@ location set</source>
<translation>🌮 / End-to-end () 🌮</translation>
</message>
<message>
<source>Experimental openpilot longitudinal control</source>
<source>Experimental openpilot Longitudinal Control</source>
<translation>/ openpilot</translation>
</message>
<message>

@ -376,7 +376,7 @@
<message>
<source>Get turn-by-turn directions displayed and more with a comma
prime subscription. Sign up now: https://connect.comma.ai</source>
<translation>comma prime以获取导航
<translation>comma prime以获取导航
https://connect.comma.ai</translation>
</message>
<message>
@ -472,12 +472,12 @@ location set</source>
<context>
<name>ParamControl</name>
<message>
<source>Ok</source>
<translation type="unfinished"></translation>
<source>Cancel</source>
<translation></translation>
</message>
<message>
<source>Cancel</source>
<translation type="unfinished"></translation>
<source>Enable</source>
<translation></translation>
</message>
</context>
<context>
@ -965,21 +965,9 @@ location set</source>
<translation></translation>
</message>
<message>
<source>🌮 End-to-end longitudinal (extremely alpha) 🌮</source>
<translation>🌮 🌮</translation>
</message>
<message>
<source>Experimental openpilot longitudinal control</source>
<source>Experimental openpilot Longitudinal Control</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&lt;b&gt;WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.&lt;/b&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental.</source>
<translation>openpilot将会模仿人类司机的驾驶方式</translation>
</message>
<message>
<source>openpilot longitudinal control is not currently available for this car.</source>
<translation type="unfinished"></translation>
@ -989,7 +977,7 @@ location set</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Disengage On Accelerator Pedal</source>
<source>Disengage on Accelerator Pedal</source>
<translation></translation>
</message>
<message>
@ -1012,6 +1000,22 @@ location set</source>
<source>Show map on left side when in split screen view.</source>
<translation></translation>
</message>
<message>
<source>Experimental Mode</source>
<translation type="unfinished"></translation>
</message>
<message>
<source> openpilot defaults to driving in &lt;b&gt;chill mode&lt;/b&gt;. Experimental mode enables &lt;b&gt;alpha-level features&lt;/b&gt; that aren&apos;t ready for chill mode. Experimental features are listed below: &lt;br&gt; &lt;h4&gt;🌮 End-to-End Longitudinal Control 🌮&lt;/h4&gt; Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control on this car. Enable this to switch to openpilot longitudinal control.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Updater</name>

@ -474,12 +474,12 @@ location set</source>
<context>
<name>ParamControl</name>
<message>
<source>Ok</source>
<translation type="unfinished"></translation>
<source>Cancel</source>
<translation></translation>
</message>
<message>
<source>Cancel</source>
<translation type="unfinished"></translation>
<source>Enable</source>
<translation></translation>
</message>
</context>
<context>
@ -967,21 +967,9 @@ location set</source>
<translation></translation>
</message>
<message>
<source>🌮 End-to-end longitudinal (extremely alpha) 🌮</source>
<translation>🌮 🌮</translation>
</message>
<message>
<source>Experimental openpilot longitudinal control</source>
<source>Experimental openpilot Longitudinal Control</source>
<translation>使 openpilot </translation>
</message>
<message>
<source>&lt;b&gt;WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.&lt;/b&gt;</source>
<translation>&lt;b&gt; openpilot (AEB)&lt;/b&gt;</translation>
</message>
<message>
<source>Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental.</source>
<translation>openpilot將會模仿人類司機的駕駛方式</translation>
</message>
<message>
<source>openpilot longitudinal control is not currently available for this car.</source>
<translation>openpilot </translation>
@ -991,7 +979,7 @@ location set</source>
<translation></translation>
</message>
<message>
<source>Disengage On Accelerator Pedal</source>
<source>Disengage on Accelerator Pedal</source>
<translation></translation>
</message>
<message>
@ -1014,6 +1002,22 @@ location set</source>
<source>Show map on left side when in split screen view.</source>
<translation></translation>
</message>
<message>
<source>Experimental Mode</source>
<translation type="unfinished"></translation>
</message>
<message>
<source> openpilot defaults to driving in &lt;b&gt;chill mode&lt;/b&gt;. Experimental mode enables &lt;b&gt;alpha-level features&lt;/b&gt; that aren&apos;t ready for chill mode. Experimental features are listed below: &lt;br&gt; &lt;h4&gt;🌮 End-to-End Longitudinal Control 🌮&lt;/h4&gt; Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control on this car. Enable this to switch to openpilot longitudinal control.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Updater</name>

@ -174,7 +174,7 @@ void ui_update_params(UIState *s) {
auto params = Params();
s->scene.is_metric = params.getBool("IsMetric");
s->scene.map_on_left = params.getBool("NavSettingLeftSide");
s->scene.end_to_end_long = params.getBool("EndToEndLong");
s->scene.experimental_mode = params.getBool("ExperimentalMode");
}
void UIState::updateStatus() {
@ -214,7 +214,7 @@ void UIState::updateStatus() {
UIState::UIState(QObject *parent) : QObject(parent) {
sm = std::make_unique<SubMaster, const std::initializer_list<const char *>>({
"modelV2", "carControl", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState",
"modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState",
"pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman",
"wideRoadCameraState", "managerState", "navInstruction", "navRoute", "gnssMeasurements",
});

@ -104,7 +104,7 @@ typedef struct UIScene {
QPointF lead_vertices[2];
float light_sensor;
bool started, ignition, is_metric, map_on_left, longitudinal_control, end_to_end_long;
bool started, ignition, is_metric, map_on_left, longitudinal_control, experimental_mode;
uint64_t started_frame;
} UIScene;

@ -80,30 +80,26 @@ const float sensor_analog_gains_AR0231[] = {
7.0/2.0, 8.0/2.0, 8.0/1.0}; // 12, 13, 14, 15 = bypass
const float sensor_analog_gains_OX03C10[] = {
1.0, 1.125, 1.25, 1.3125, 1.5625,
1.6875, 2.0, 2.25, 2.625, 3.125,
3.625, 4.0, 4.5, 5.0, 5.5,
6.0, 6.5, 7.0, 7.5, 8.0,
8.5, 9.0, 9.5, 10.0, 10.5,
11.0, 11.5, 12.0, 12.5, 13.0,
13.5, 14.0, 14.5, 15.0, 15.5};
1.0, 1.0625, 1.125, 1.1875, 1.25, 1.3125, 1.375, 1.4375, 1.5, 1.5625, 1.6875,
1.8125, 1.9375, 2.0, 2.125, 2.25, 2.375, 2.5, 2.625, 2.75, 2.875, 3.0,
3.125, 3.375, 3.625, 3.875, 4.0, 4.25, 4.5, 4.75, 5.0, 5.25, 5.5,
5.75, 6.0, 6.25, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0,
10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5, 15.0, 15.5};
const uint32_t ox03c10_analog_gains_reg[] = {
0x100, 0x120, 0x140, 0x150, 0x190,
0x1B0, 0x200, 0x240, 0x2A0, 0x320,
0x3A0, 0x400, 0x480, 0x500, 0x580,
0x600, 0x680, 0x700, 0x780, 0x800,
0x880, 0x900, 0x980, 0xA00, 0xA80,
0xB00, 0xB80, 0xC00, 0xC80, 0xD00,
0xD80, 0xE00, 0xE80, 0xF00, 0xF80};
0x100, 0x110, 0x120, 0x130, 0x140, 0x150, 0x160, 0x170, 0x180, 0x190, 0x1B0,
0x1D0, 0x1F0, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, 0x2E0, 0x300,
0x320, 0x360, 0x3A0, 0x3E0, 0x400, 0x440, 0x480, 0x4C0, 0x500, 0x540, 0x580,
0x5C0, 0x600, 0x640, 0x680, 0x700, 0x780, 0x800, 0x880, 0x900, 0x980, 0xA00,
0xA80, 0xB00, 0xB80, 0xC00, 0xC80, 0xD00, 0xD80, 0xE00, 0xE80, 0xF00, 0xF80};
const int ANALOG_GAIN_MIN_IDX_AR0231 = 0x1; // 0.25x
const int ANALOG_GAIN_REC_IDX_AR0231 = 0x6; // 0.8x
const int ANALOG_GAIN_MAX_IDX_AR0231 = 0xD; // 4.0x
const int ANALOG_GAIN_MIN_IDX_OX03C10 = 0x0;
const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x6; // 2x
const int ANALOG_GAIN_MAX_IDX_OX03C10 = 0x22;
const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x11; // 2.5x
const int ANALOG_GAIN_MAX_IDX_OX03C10 = 0x36;
const int EXPOSURE_TIME_MIN_AR0231 = 2; // with HDR, fastest ss
const int EXPOSURE_TIME_MAX_AR0231 = 0x0855; // with HDR, slowest ss, 40ms

@ -12,6 +12,7 @@
#include "common/util.h"
#define FRAME_BUF_COUNT 4
#define ANALOG_GAIN_MAX_CNT 55
class CameraState {
public:
@ -36,7 +37,7 @@ public:
float dc_gain_on_grey;
float dc_gain_off_grey;
float sensor_analog_gains[35];
float sensor_analog_gains[ANALOG_GAIN_MAX_CNT];
int analog_gain_min_idx;
int analog_gain_max_idx;
int analog_gain_rec_idx;

@ -11,7 +11,7 @@ from system.hardware import TICI
TEST_TIMESPAN = 30
LAG_FRAME_TOLERANCE = {log.FrameData.ImageSensor.ar0321: 0.5, # ARs use synced pulses for frame starts
log.FrameData.ImageSensor.ox03c10: 1.0} # OXs react to out-of-sync at next frame
log.FrameData.ImageSensor.ox03c10: 1.0} # OXs react to out-of-sync at next frame
CAMERAS = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState')
@ -68,12 +68,17 @@ class TestCamerad(unittest.TestCase):
frame_times = {frame_id: [getattr(m, m.which()).timestampSof for m in msgs] for frame_id, msgs in self.log_by_frame_id.items()}
diffs = {frame_id: (max(ts) - min(ts))/1e6 for frame_id, ts in frame_times.items()}
def get_desc(fid, diff):
cam_times = [(m.which(), getattr(m, m.which()).timestampSof/1e6) for m in self.log_by_frame_id[fid]]
return f"{diff=} {cam_times=}"
return (diff, cam_times)
laggy_frames = {k: get_desc(k, v) for k, v in diffs.items() if v > LAG_FRAME_TOLERANCE[sensor_type]}
assert len(laggy_frames) == 0, f"Frames not synced properly: {laggy_frames=}"
def in_tol(diff):
return 50 - LAG_FRAME_TOLERANCE[sensor_type] < diff and diff < 50 + LAG_FRAME_TOLERANCE[sensor_type]
if len(laggy_frames) != 0 and all( in_tol(laggy_frames[lf][0]) for lf in laggy_frames):
print("TODO: handle camera out of sync")
else:
assert len(laggy_frames) == 0, f"Frames not synced properly: {laggy_frames=}"
if __name__ == "__main__":
unittest.main()

@ -431,6 +431,9 @@ class Tici(HardwareBase):
def initialize_hardware(self):
self.amplifier.initialize_configuration()
# TODO: this should go in AGNOS
os.system("sudo chmod 666 /dev/spidev0.0")
# Allow thermald to write engagement status to kmsg
os.system("sudo chmod a+w /dev/kmsg")

@ -8,7 +8,7 @@ Cabana is a tool developed to view raw CAN data. One use for this is creating an
```bash
$ ./cabana -h
Usage: ./_cabana [options] route
Usage: ./cabana [options] route
Options:
-h, --help Displays this help.

@ -13,6 +13,10 @@
const int CELL_HEIGHT = 26;
inline int get_bit_index(const QModelIndex &index, bool little_endian) {
return index.row() * 8 + (little_endian ? 7 - index.column() : index.column());
}
BinaryView::BinaryView(QWidget *parent) : QTableView(parent) {
model = new BinaryViewModel(this);
setModel(model);
@ -37,31 +41,49 @@ void BinaryView::highlight(const Signal *sig) {
}
void BinaryView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) {
QModelIndex tl = indexAt({qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom())});
QModelIndex br = indexAt({qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom())});
if (!tl.isValid() || !br.isValid())
auto index = indexAt(viewport()->mapFromGlobal(QCursor::pos()));
if (!anchor_index.isValid() || !index.isValid())
return;
if (tl < anchor_index) {
br = anchor_index;
} else if (anchor_index < br) {
tl = anchor_index;
}
QItemSelection selection;
for (int row = tl.row(); row <= br.row(); ++row) {
int left_col = (row == tl.row()) ? tl.column() : 0;
int right_col = (row == br.row()) ? br.column() : 7;
selection.merge({model->index(row, left_col), model->index(row, right_col)}, flags);
auto [tl, br] = std::minmax(anchor_index, index);
if ((resize_sig && resize_sig->is_little_endian) || (!resize_sig && index < anchor_index)) {
// little_endian selection
if (tl.row() == br.row()) {
selection.merge({model->index(tl.row(), tl.column()), model->index(tl.row(), br.column())}, flags);
} else {
for (int row = tl.row(); row <= br.row(); ++row) {
int left_col = (row == br.row()) ? br.column() : 0;
int right_col = (row == tl.row()) ? tl.column() : 7;
selection.merge({model->index(row, left_col), model->index(row, right_col)}, flags);
}
}
} else {
// big endian selection
for (int row = tl.row(); row <= br.row(); ++row) {
int left_col = (row == tl.row()) ? tl.column() : 0;
int right_col = (row == br.row()) ? br.column() : 7;
selection.merge({model->index(row, left_col), model->index(row, right_col)}, flags);
}
}
selectionModel()->select(selection, flags);
}
void BinaryView::mousePressEvent(QMouseEvent *event) {
delegate->setSelectionColor(style()->standardPalette().color(QPalette::Active, QPalette::Highlight));
anchor_index = indexAt(event->pos());
if (getResizingSignal() != nullptr) {
if (auto index = indexAt(event->pos()); index.isValid() && index.column() != 8) {
anchor_index = index;
auto item = (const BinaryViewModel::Item *)anchor_index.internalPointer();
delegate->setSelectionColor(item->bg_color);
if (item && item->sigs.size() > 0) {
int bit_idx = get_bit_index(anchor_index, true);
for (auto s : item->sigs) {
if (bit_idx == s->lsb || bit_idx == s->msb) {
resize_sig = s;
delegate->setSelectionColor(item->bg_color);
break;
}
}
}
}
QTableView::mousePressEvent(event);
}
@ -80,23 +102,35 @@ void BinaryView::mouseMoveEvent(QMouseEvent *event) {
void BinaryView::mouseReleaseEvent(QMouseEvent *event) {
QTableView::mouseReleaseEvent(event);
if (auto indexes = selectedIndexes(); !indexes.isEmpty()) {
int from = indexes.first().row() * 8 + indexes.first().column();
int to = indexes.back().row() * 8 + indexes.back().column();
if (auto sig = getResizingSignal()) {
auto [sig_from, sig_to] = getSignalRange(sig);
if (from >= sig_from && to <= sig_to) { // reduce size
emit(from == sig_from ? resizeSignal(sig, std::min(to + 1, sig_to), sig_to)
: resizeSignal(sig, sig_from, std::max(from - 1, sig_from)));
} else { // increase size
emit resizeSignal(sig, std::min(from, sig_from), std::max(to, sig_to));
auto release_index = indexAt(event->pos());
if (release_index.isValid() && anchor_index.isValid()) {
if (release_index.column() == 8) {
release_index = model->index(release_index.row(), 7);
}
bool little_endian = (resize_sig && resize_sig->is_little_endian) || (!resize_sig && release_index < anchor_index);
int release_bit_idx = get_bit_index(release_index, little_endian);
int archor_bit_idx = get_bit_index(anchor_index, little_endian);
if (resize_sig) {
auto [sig_from, sig_to] = getSignalRange(resize_sig);
int start_bit, end_bit;
if (archor_bit_idx == sig_from) {
std::tie(start_bit, end_bit) = std::minmax(release_bit_idx, sig_to);
if (start_bit >= sig_from && start_bit <= sig_to)
start_bit = std::min(start_bit + 1, sig_to);
} else {
std::tie(start_bit, end_bit) = std::minmax(release_bit_idx, sig_from);
if (end_bit >= sig_from && end_bit <= sig_to)
end_bit = std::max(end_bit - 1, sig_from);
}
emit resizeSignal(resize_sig, start_bit, end_bit - start_bit + 1);
} else {
emit addSignal(from, to);
auto [sart_bit, end_bit] = std::minmax(archor_bit_idx, release_bit_idx);
emit addSignal(sart_bit, end_bit - sart_bit + 1, little_endian);
}
clearSelection();
}
clearSelection();
anchor_index = QModelIndex();
resize_sig = nullptr;
}
void BinaryView::leaveEvent(QEvent *event) {
@ -107,34 +141,17 @@ void BinaryView::leaveEvent(QEvent *event) {
void BinaryView::setMessage(const QString &message_id) {
model->setMessage(message_id);
clearSelection();
anchor_index = QModelIndex();
resize_sig = nullptr;
hovered_sig = nullptr;
updateState();
}
const Signal *BinaryView::getResizingSignal() const {
if (anchor_index.isValid()) {
auto item = (const BinaryViewModel::Item *)anchor_index.internalPointer();
if (item && item->sigs.size() > 0) {
int archor_pos = anchor_index.row() * 8 + anchor_index.column();
for (auto s : item->sigs) {
auto [sig_from, sig_to] = getSignalRange(s);
if (archor_pos == sig_from || archor_pos == sig_to)
return s;
}
}
}
return nullptr;
}
QSet<const Signal *> BinaryView::getOverlappingSignals() const {
QSet<const Signal *> overlapping;
for (int i = 0; i < model->rowCount(); ++i) {
for (int j = 0; j < model->columnCount() - 1; ++j) {
auto item = (const BinaryViewModel::Item *)model->index(i, j).internalPointer();
if (item && item->sigs.size() > 1) {
for (auto s : item->sigs)
overlapping.insert(s);
}
}
for (auto &item : model->items) {
if (item.sigs.size() > 1)
for (auto s : item.sigs) overlapping += s;
}
return overlapping;
}
@ -142,53 +159,37 @@ QSet<const Signal *> BinaryView::getOverlappingSignals() const {
// BinaryViewModel
void BinaryViewModel::setMessage(const QString &message_id) {
msg_id = message_id;
beginResetModel();
msg_id = message_id;
items.clear();
row_count = 0;
dbc_msg = dbc()->msg(msg_id);
if (dbc_msg) {
if ((dbc_msg = dbc()->msg(msg_id))) {
row_count = dbc_msg->size;
items.resize(row_count * column_count);
for (int i = 0; i < dbc_msg->sigs.size(); ++i) {
const auto &sig = dbc_msg->sigs[i];
auto [start, end] = getSignalRange(&sig);
for (int j = start; j <= end; ++j) {
int idx = column_count * (j / 8) + j % 8;
int bit_index = sig.is_little_endian ? bigEndianBitIndex(j) : j;
int idx = column_count * (bit_index / 8) + bit_index % 8;
if (idx >= items.size()) {
qWarning() << "signal " << sig.name.c_str() << "out of bounds.start_bit:" << sig.start_bit << "size:" << sig.size;
break;
}
if (j == start) {
sig.is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true;
} else if (j == end) {
sig.is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true;
}
if (j == start) sig.is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true;
if (j == end) sig.is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true;
items[idx].bg_color = getColor(i);
items[idx].sigs.push_back(&dbc_msg->sigs[i]);
items[idx].sigs.push_back(&sig);
}
}
} else {
row_count = can->lastMessage(msg_id).dat.size();
items.resize(row_count * column_count);
}
endResetModel();
}
QModelIndex BinaryViewModel::index(int row, int column, const QModelIndex &parent) const {
return createIndex(row, column, (void *)&items[row * column_count + column]);
}
Qt::ItemFlags BinaryViewModel::flags(const QModelIndex &index) const {
return (index.column() == column_count - 1) ? Qt::ItemIsEnabled : Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
void BinaryViewModel::updateState() {
auto prev_items = items;
const auto &binary = can->lastMessage(msg_id).dat;
// data size may changed.
if (!dbc_msg && binary.size() != row_count) {
@ -198,7 +199,6 @@ void BinaryViewModel::updateState() {
items.resize(row_count * column_count);
endResetModel();
}
char hex[3] = {'\0'};
for (int i = 0; i < std::min(binary.size(), row_count); ++i) {
for (int j = 0; j < column_count - 1; ++j) {
@ -248,9 +248,8 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
BinaryView *bin_view = (BinaryView *)parent();
painter->save();
bool hover = std::find_if(item->sigs.begin(), item->sigs.end(), [=](auto s) { return s == bin_view->hoveredSignal(); }) !=
item->sigs.end();
// background
bool hover = item->sigs.contains(bin_view->hoveredSignal());
QColor bg_color = hover ? hoverColor(item->bg_color) : item->bg_color;
if (option.state & QStyle::State_Selected) {
bg_color = selection_color;
@ -258,7 +257,7 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
painter->fillRect(option.rect, bg_color);
// text
if (index.column() == 8) { // hex column
if (index.column() == 8) { // hex column
painter->setFont(hex_font);
} else if (hover) {
painter->setPen(Qt::white);

@ -28,12 +28,16 @@ public:
BinaryViewModel(QObject *parent) : QAbstractTableModel(parent) {}
void setMessage(const QString &message_id);
void updateState();
Qt::ItemFlags flags(const QModelIndex &index) const;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const { return {}; }
int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; }
int columnCount(const QModelIndex &parent = QModelIndex()) const override { return column_count; }
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override {
return createIndex(row, column, (void *)&items[row * column_count + column]);
}
Qt::ItemFlags flags(const QModelIndex &index) const override {
return (index.column() == column_count - 1) ? Qt::ItemIsEnabled : Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
struct Item {
QColor bg_color = QColor(Qt::white);
@ -42,13 +46,13 @@ public:
QString val = "0";
QList<const Signal *> sigs;
};
std::vector<Item> items;
private:
QString msg_id;
const Msg *dbc_msg;
int row_count = 0;
const int column_count = 9;
std::vector<Item> items;
};
class BinaryView : public QTableView {
@ -64,7 +68,7 @@ public:
signals:
void signalHovered(const Signal *sig);
void addSignal(int from, int size);
void addSignal(int start_bit, int size, bool little_endian);
void resizeSignal(const Signal *sig, int from, int size);
private:
@ -73,10 +77,10 @@ private:
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void leaveEvent(QEvent *event) override;
const Signal *getResizingSignal() const;
QModelIndex anchor_index;
BinaryViewModel *model;
BinaryItemDelegate *delegate;
const Signal *resize_sig = nullptr;
const Signal *hovered_sig = nullptr;
};

@ -1,6 +1,5 @@
#include "tools/cabana/canmessages.h"
#include <QDebug>
#include <QSettings>
#include "tools/cabana/dbcmanager.h"
@ -9,7 +8,6 @@ CANMessages *can = nullptr;
CANMessages::CANMessages(QObject *parent) : QObject(parent) {
can = this;
QObject::connect(this, &CANMessages::received, this, &CANMessages::process, Qt::QueuedConnection);
QObject::connect(&settings, &Settings::changed, this, &CANMessages::settingChanged);
}
@ -24,11 +22,11 @@ static bool event_filter(const Event *e, void *opaque) {
}
bool CANMessages::loadRoute(const QString &route, const QString &data_dir, bool use_qcam) {
routeName = route;
replay = new Replay(route, {"can", "roadEncodeIdx", "carParams"}, {}, nullptr, use_qcam ? REPLAY_FLAG_QCAMERA : 0, data_dir, this);
replay->setSegmentCacheLimit(settings.cached_segment_limit);
replay->installEventFilter(event_filter, this);
QObject::connect(replay, &Replay::segmentsMerged, this, &CANMessages::eventsMerged);
QObject::connect(replay, &Replay::streamStarted, this, &CANMessages::streamStarted);
if (replay->load()) {
replay->start();
return true;
@ -40,12 +38,9 @@ QList<QPointF> CANMessages::findSignalValues(const QString &id, const Signal *si
auto evts = events();
if (!evts) return {};
auto l = id.split(':');
int bus = l[0].toInt();
uint32_t address = l[1].toUInt(nullptr, 16);
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;
@ -67,8 +62,9 @@ void CANMessages::process(QHash<QString, CanData> *messages) {
for (auto it = messages->begin(); it != messages->end(); ++it) {
can_msgs[it.key()] = it.value();
}
delete messages;
emit updated();
emit msgsReceived(messages);
delete messages;
}
bool CANMessages::eventFilter(const Event *event) {
@ -101,10 +97,9 @@ bool CANMessages::eventFilter(const Event *event) {
data.bus_time = c.getBusTime();
data.dat.append((char *)c.getDat().begin(), c.getDat().size());
auto &count = counters[id];
data.count = ++count;
data.count = ++counters[id];
if (double delta = (current_sec - counters_begin_sec); delta > 0) {
data.freq = count / delta;
data.freq = data.count / delta;
}
(*new_msgs)[id] = data;
}

@ -32,7 +32,7 @@ public:
QList<QPointF> findSignalValues(const QString&id, const Signal* signal, double value, FindFlags flag, int max_count);
bool eventFilter(const Event *event);
inline QString route() const { return routeName; }
inline QString route() const { return replay->route()->name(); }
inline QString carFingerprint() const { return replay->carFingerprint().c_str(); }
inline double totalSeconds() const { return replay->totalSeconds(); }
inline double routeStartTime() const { return replay->routeStartTime() / (double)1e9; }
@ -47,8 +47,10 @@ public:
inline const std::vector<std::tuple<int, int, TimelineType>> getTimeline() { return replay->getTimeline(); }
signals:
void streamStarted();
void eventsMerged();
void updated();
void msgsReceived(const QHash<QString, CanData> *);
void received(QHash<QString, CanData> *);
public:
@ -58,9 +60,7 @@ protected:
void process(QHash<QString, CanData> *);
void settingChanged();
QString routeName;
Replay *replay = nullptr;
std::mutex lock;
std::atomic<double> counters_begin_sec = 0;
QHash<QString, uint32_t> counters;

@ -2,12 +2,13 @@
#include <QFutureSynchronizer>
#include <QGraphicsLayout>
#include <QGridLayout>
#include <QRubberBand>
#include <QTimer>
#include <QToolButton>
#include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis>
#include <QtConcurrent>
#include <QToolBar>
// ChartsWidget
@ -15,41 +16,24 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0);
// title bar
title_bar = new QWidget(this);
title_bar->setVisible(false);
QHBoxLayout *title_layout = new QHBoxLayout(title_bar);
title_layout->setContentsMargins(0, 0, 0, 0);
title_label = new QLabel(tr("Charts"));
title_layout->addWidget(title_label);
title_layout->addStretch();
range_label = new QLabel();
title_layout->addWidget(range_label);
reset_zoom_btn = new QPushButton("", this);
reset_zoom_btn->setFixedSize(30, 30);
// toolbar
QToolBar *toolbar = new QToolBar(tr("Charts"), this);
title_label = new QLabel();
title_label->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred);
toolbar->addWidget(title_label);
toolbar->addWidget(range_label = new QLabel());
reset_zoom_btn = toolbar->addAction("");
reset_zoom_btn->setToolTip(tr("Reset zoom (drag on chart to zoom X-Axis)"));
title_layout->addWidget(reset_zoom_btn);
remove_all_btn = new QPushButton("", this);
remove_all_btn = toolbar->addAction("");
remove_all_btn->setToolTip(tr("Remove all charts"));
remove_all_btn->setFixedSize(30, 30);
title_layout->addWidget(remove_all_btn);
dock_btn = new QPushButton();
dock_btn->setFixedSize(30, 30);
title_layout->addWidget(dock_btn);
main_layout->addWidget(title_bar, 0, Qt::AlignTop);
dock_btn = toolbar->addAction("");
main_layout->addWidget(toolbar);
updateToolBar();
// charts
QWidget *charts_container = new QWidget(this);
QVBoxLayout *charts_main = new QVBoxLayout(charts_container);
charts_layout = new QVBoxLayout();
charts_main->addLayout(charts_layout);
charts_main->addStretch();
charts_layout = new QVBoxLayout(charts_container);
charts_layout->addStretch();
QScrollArea *charts_scroll = new QScrollArea(this);
charts_scroll->setWidgetResizable(true);
@ -61,19 +45,23 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { removeAll(nullptr); });
QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartsWidget::removeAll);
QObject::connect(dbc(), &DBCManager::signalUpdated, this, &ChartsWidget::signalUpdated);
QObject::connect(dbc(), &DBCManager::msgUpdated, [this](const QString &msg_id) {
QObject::connect(dbc(), &DBCManager::msgRemoved, [this](uint32_t address) {
for (auto c : charts.toVector())
if (DBCManager::parseId(c->id).second == address) removeChart(c);
});
QObject::connect(dbc(), &DBCManager::msgUpdated, [this](uint32_t address) {
for (auto c : charts) {
if (c->id == msg_id) c->updateTitle();
if (DBCManager::parseId(c->id).second == address) c->updateTitle();
}
});
QObject::connect(can, &CANMessages::eventsMerged, this, &ChartsWidget::eventsMerged);
QObject::connect(can, &CANMessages::updated, this, &ChartsWidget::updateState);
QObject::connect(remove_all_btn, &QPushButton::clicked, [this]() { removeAll(); });
QObject::connect(reset_zoom_btn, &QPushButton::clicked, this, &ChartsWidget::zoomReset);
QObject::connect(dock_btn, &QPushButton::clicked, [this]() {
QObject::connect(remove_all_btn, &QAction::triggered, [this]() { removeAll(); });
QObject::connect(reset_zoom_btn, &QAction::triggered, this, &ChartsWidget::zoomReset);
QObject::connect(dock_btn, &QAction::triggered, [this]() {
emit dock(!docking);
docking = !docking;
updateTitleBar();
updateToolBar();
});
}
@ -92,7 +80,7 @@ void ChartsWidget::eventsMerged() {
void ChartsWidget::zoomIn(double min, double max) {
zoomed_range = {min, max};
is_zoomed = zoomed_range != display_range;
updateTitleBar();
updateToolBar();
emit rangeChanged(min, max, is_zoomed);
updateState();
}
@ -120,27 +108,22 @@ void ChartsWidget::updateState() {
if (prev_range != display_range) {
QFutureSynchronizer<void> future_synchronizer;
for (auto c : charts)
future_synchronizer.addFuture(QtConcurrent::run(c->chart_view, &ChartView::updateSeries, display_range));
future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, display_range));
}
}
const auto &range = is_zoomed ? zoomed_range : display_range;
for (auto c : charts) {
c->chart_view->setRange(range.first, range.second);
c->chart_view->updateLineMarker(current_sec);
c->setRange(range.first, range.second);
c->updateLineMarker(current_sec);
}
}
void ChartsWidget::updateTitleBar() {
title_bar->setVisible(!charts.isEmpty());
if (charts.isEmpty()) return;
range_label->setVisible(is_zoomed);
void ChartsWidget::updateToolBar() {
remove_all_btn->setEnabled(!charts.isEmpty());
reset_zoom_btn->setEnabled(is_zoomed);
if (is_zoomed) {
range_label->setText(tr("%1 - %2").arg(zoomed_range.first, 0, 'f', 2).arg(zoomed_range.second, 0, 'f', 2));
}
title_label->setText(tr("Charts (%1)").arg(charts.size()));
range_label->setText(is_zoomed ? tr("%1 - %2").arg(zoomed_range.first, 0, 'f', 2).arg(zoomed_range.second, 0, 'f', 2) : "");
title_label->setText(charts.size() > 0 ? tr("Charts (%1)").arg(charts.size()) : tr("Charts"));
dock_btn->setText(docking ? "" : "");
dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts"));
}
@ -150,17 +133,17 @@ void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show) {
if (it != charts.end()) {
if (!show) removeChart((*it));
} else if (show) {
auto chart = new ChartWidget(id, sig, this);
chart->chart_view->updateSeries(display_range);
QObject::connect(chart, &ChartWidget::remove, [=]() { removeChart(chart); });
QObject::connect(chart->chart_view, &ChartView::zoomIn, this, &ChartsWidget::zoomIn);
QObject::connect(chart->chart_view, &ChartView::zoomReset, this, &ChartsWidget::zoomReset);
auto chart = new ChartView(id, sig, this);
chart->updateSeries(display_range);
QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); });
QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn);
QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset);
charts_layout->insertWidget(0, chart);
charts.push_back(chart);
emit chartOpened(chart->id, chart->signal);
updateState();
}
updateTitleBar();
updateToolBar();
}
bool ChartsWidget::isChartOpened(const QString &id, const Signal *sig) {
@ -168,92 +151,36 @@ bool ChartsWidget::isChartOpened(const QString &id, const Signal *sig) {
return it != charts.end();
}
void ChartsWidget::removeChart(ChartWidget *chart) {
void ChartsWidget::removeChart(ChartView *chart) {
charts.removeOne(chart);
chart->deleteLater();
updateTitleBar();
updateToolBar();
emit chartClosed(chart->id, chart->signal);
}
void ChartsWidget::removeAll(const Signal *sig) {
QMutableListIterator<ChartWidget *> it(charts);
while (it.hasNext()) {
auto c = it.next();
if (sig == nullptr || c->signal == sig) {
c->deleteLater();
emit chartClosed(c->id, c->signal);
it.remove();
}
}
updateTitleBar();
for (auto c : charts.toVector())
if (!sig || c->signal == sig) removeChart(c);
}
void ChartsWidget::signalUpdated(const Signal *sig) {
for (auto c : charts) {
if (c->signal == sig) {
c->updateTitle();
c->chart_view->updateSeries(display_range);
c->chart_view->setRange(display_range.first, display_range.second, true);
c->updateSeries(display_range);
c->setRange(display_range.first, display_range.second, true);
}
}
}
bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) {
if (obj != this && event->type() == QEvent::Close) {
emit dock_btn->clicked();
emit dock_btn->triggered();
return true;
}
return false;
}
// ChartWidget
ChartWidget::ChartWidget(const QString &id, const Signal *sig, QWidget *parent) : id(id), signal(sig), QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setSpacing(0);
main_layout->setContentsMargins(0, 0, 0, 0);
header = new QWidget(this);
QGridLayout *header_layout = new QGridLayout(header);
header_layout->setContentsMargins(11, 11, 11, 0);
msg_name_label = new QLabel(this);
msg_name_label->setTextFormat(Qt::RichText);
header_layout->addWidget(msg_name_label, 0, 0, Qt::AlignLeft);
sig_name_label = new QLabel(this);
header_layout->addWidget(sig_name_label, 0, 1, Qt::AlignCenter); //, 0, Qt::AlignCenter);
remove_btn = new QPushButton("", this);
remove_btn->setFixedSize(20, 20);
remove_btn->setToolTip(tr("Remove chart"));
header_layout->addWidget(remove_btn, 0, 2, Qt::AlignRight);
main_layout->addWidget(header);
chart_view = new ChartView(id, sig, this);
main_layout->addWidget(chart_view);
main_layout->addStretch();
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
updateTitle();
updateFromSettings();
QObject::connect(remove_btn, &QPushButton::clicked, [=]() { emit remove(id, sig); });
QObject::connect(&settings, &Settings::changed, this, &ChartWidget::updateFromSettings);
}
void ChartWidget::updateTitle() {
msg_name_label->setText(tr("%1 <font color=\"gray\">%2</font>").arg(dbc()->msg(id)->name.c_str()).arg(id));
sig_name_label->setText(signal->name.c_str());
}
void ChartWidget::updateFromSettings() {
header->setStyleSheet(settings.chart_theme == 0 ? "background-color:white" : "background-color:#23242c");
QString color_style = settings.chart_theme == 0 ? "color:black" : "color:white";
sig_name_label->setStyleSheet("font-weight:bold;" + color_style);
msg_name_label->setStyleSheet(color_style);
remove_btn->setStyleSheet(color_style);
chart_view->updateFromSettings();
}
// ChartView
ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent)
@ -264,45 +191,64 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent)
chart->addSeries(series);
chart->createDefaultAxes();
chart->legend()->hide();
chart->setMargins({0, 0, 0, 0});
chart->layout()->setContentsMargins(0, 0, 0, 0);
line_marker = new QGraphicsLineItem(chart);
line_marker->setZValue(chart->zValue() + 10);
track_line = new QGraphicsLineItem(chart);
track_line->setZValue(chart->zValue() + 10);
track_line->setPen(QPen(Qt::darkGray, 1, Qt::DashLine));
track_ellipse = new QGraphicsEllipseItem(chart);
track_ellipse->setZValue(chart->zValue() + 10);
track_ellipse->setBrush(Qt::darkGray);
value_text = new QGraphicsTextItem(chart);
value_text->setZValue(chart->zValue() + 10);
line_marker = new QGraphicsLineItem(chart);
line_marker->setZValue(chart->zValue() + 10);
item_group = scene()->createItemGroup({track_line, track_ellipse, value_text});
item_group->setZValue(chart->zValue() + 10);
// title
msg_title = new QGraphicsTextItem(chart);
QToolButton *remove_btn = new QToolButton();
remove_btn->setText("X");
remove_btn->setAutoRaise(true);
remove_btn->setToolTip(tr("Remove Chart"));
close_btn_proxy = new QGraphicsProxyWidget(chart);
close_btn_proxy->setWidget(remove_btn);
setChart(chart);
setRenderHint(QPainter::Antialiasing);
setRubberBand(QChartView::HorizontalRubberBand);
if (auto rubber = findChild<QRubberBand *>()) {
QPalette pal;
pal.setBrush(QPalette::Base, QColor(0, 0, 0, 80));
rubber->setPalette(pal);
}
updateFromSettings();
updateTitle();
QTimer *timer = new QTimer(this);
timer->setInterval(100);
timer->setSingleShot(true);
timer->callOnTimeout(this, &ChartView::adjustChartMargins);
QObject::connect(&settings, &Settings::changed, this, &ChartView::updateFromSettings);
QObject::connect(remove_btn, &QToolButton::clicked, [=]() { emit remove(id, sig); });
QObject::connect(chart, &QChart::plotAreaChanged, [=](const QRectF &plotArea) {
// use a singleshot timer to avoid recursion call.
timer->start();
});
}
void ChartView::resizeEvent(QResizeEvent *event) {
QChartView::resizeEvent(event);
msg_title->setPos(11, 6);
close_btn_proxy->setPos(event->size().width() - close_btn_proxy->size().width() - 11, 8);
}
void ChartView::updateTitle() {
chart()->setTitle(signal->name.c_str());
msg_title->setHtml(tr("%1 <font color=\"gray\">%2</font>").arg(dbc()->msg(id)->name.c_str()).arg(id));
}
void ChartView::updateFromSettings() {
setFixedHeight(settings.chart_height);
chart()->setTheme(settings.chart_theme == 0 ? QChart::ChartThemeLight : QChart::QChart::ChartThemeDark);
line_marker->setPen(QPen(settings.chart_theme == 0 ? Qt::black : Qt::white, 2));
auto color = chart()->titleBrush().color();
line_marker->setPen(QPen(color, 2));
msg_title->setDefaultTextColor(color);
}
void ChartView::setRange(double min, double max, bool force_update) {
@ -318,7 +264,7 @@ void ChartView::adjustChartMargins() {
const int aligned_pos = 60;
if (chart()->plotArea().left() != aligned_pos) {
const float left_margin = chart()->margins().left() + aligned_pos - chart()->plotArea().left();
chart()->setMargins(QMargins(left_margin, 0, 0, 0));
chart()->setMargins(QMargins(left_margin, 11, 0, 0));
}
}
@ -327,7 +273,7 @@ void ChartView::updateLineMarker(double current_sec) {
int x = chart()->plotArea().left() +
chart()->plotArea().width() * (current_sec - axis_x->min()) / (axis_x->max() - axis_x->min());
if (int(line_marker->line().x1()) != x) {
line_marker->setLine(x, 0, x, height());
line_marker->setLine(x, chart()->plotArea().top() - chart()->margins().top() + 3, x, height());
}
}
@ -335,12 +281,9 @@ void ChartView::updateSeries(const std::pair<double, double> range) {
auto events = can->events();
if (!events) return;
auto l = id.split(':');
int bus = l[0].toInt();
uint32_t address = l[1].toUInt(nullptr, 16);
vals.clear();
vals.reserve((range.second - range.first) * 1000); // [n]seconds * 1000hz
auto [bus, address] = DBCManager::parseId(id);
double route_start_time = can->routeStartTime();
Event begin_event(cereal::Event::Which::INIT_DATA, (route_start_time + range.first) * 1e9);
auto begin = std::lower_bound(events->begin(), events->end(), &begin_event, Event::lessThan());
@ -380,17 +323,8 @@ void ChartView::updateAxisY() {
}
}
void ChartView::enterEvent(QEvent *event) {
track_line->setVisible(true);
value_text->setVisible(true);
track_ellipse->setVisible(true);
QChartView::enterEvent(event);
}
void ChartView::leaveEvent(QEvent *event) {
track_line->setVisible(false);
value_text->setVisible(false);
track_ellipse->setVisible(false);
item_group->setVisible(false);
QChartView::leaveEvent(event);
}
@ -427,7 +361,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
if (!is_zooming) {
const auto plot_area = chart()->plotArea();
auto axis_x = dynamic_cast<QValueAxis *>(chart()->axisX());
double x = std::clamp((double)ev->pos().x(), plot_area.left(), plot_area.right()-1);
double x = std::clamp((double)ev->pos().x(), plot_area.left(), plot_area.right() - 1);
double sec = axis_x->min() + (axis_x->max() - axis_x->min()) * (x - plot_area.left()) / plot_area.width();
auto value = std::upper_bound(vals.begin(), vals.end(), sec, [](double x, auto &p) { return x < p.x(); });
if (value != vals.end()) {
@ -442,9 +376,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
}
value_text->setPos(text_x, pos.y() - 10);
}
track_line->setVisible(value != vals.end());
value_text->setVisible(value != vals.end());
track_ellipse->setVisible(value != vals.end());
item_group->setVisible(value != vals.end());
} else {
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
}

@ -1,14 +1,12 @@
#pragma once
#include <map>
#include <QLabel>
#include <QGraphicsEllipseItem>
#include <QGraphicsLineItem>
#include <QGraphicsProxyWidget>
#include <QGraphicsTextItem>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QtCharts/QChartView>
#include "tools/cabana/canmessages.h"
@ -25,48 +23,31 @@ public:
void setRange(double min, double max, bool force_update = false);
void updateLineMarker(double current_sec);
void updateFromSettings();
void updateTitle();
QString id;
const Signal *signal;
signals:
void zoomIn(double min, double max);
void zoomReset();
void remove(const QString &msg_id, const Signal *sig);
private:
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *ev) override;
void enterEvent(QEvent *event) override;
void leaveEvent(QEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void adjustChartMargins();
void updateAxisY();
QGraphicsLineItem *track_line;
QGraphicsItemGroup *item_group;
QGraphicsLineItem *line_marker, *track_line;
QGraphicsEllipseItem *track_ellipse;
QGraphicsTextItem *value_text;
QGraphicsLineItem *line_marker;
QGraphicsTextItem *value_text, *msg_title;
QGraphicsProxyWidget *close_btn_proxy;
QVector<QPointF> vals;
QString id;
const Signal *signal;
};
class ChartWidget : public QWidget {
Q_OBJECT
public:
ChartWidget(const QString &id, const Signal *sig, QWidget *parent);
void updateTitle();
void updateFromSettings();
signals:
void remove(const QString &msg_id, const Signal *sig);
public:
QString id;
const Signal *signal;
QWidget *header;
QLabel *msg_name_label;
QLabel *sig_name_label;
QPushButton *remove_btn;
ChartView *chart_view = nullptr;
};
};
class ChartsWidget : public QWidget {
Q_OBJECT
@ -74,7 +55,7 @@ class ChartsWidget : public QWidget {
public:
ChartsWidget(QWidget *parent = nullptr);
void showChart(const QString &id, const Signal *sig, bool show);
void removeChart(ChartWidget *chart);
void removeChart(ChartView *chart);
bool isChartOpened(const QString &id, const Signal *sig);
signals:
@ -89,20 +70,18 @@ private:
void zoomIn(double min, double max);
void zoomReset();
void signalUpdated(const Signal *sig);
void updateTitleBar();
void updateToolBar();
void removeAll(const Signal *sig = nullptr);
bool eventFilter(QObject *obj, QEvent *event) override;
QWidget *title_bar;
QLabel *title_label;
QLabel *range_label;
bool docking = true;
QPushButton *dock_btn;
QPushButton *reset_zoom_btn;
QPushButton *remove_all_btn;
QAction *dock_btn;
QAction *reset_zoom_btn;
QAction *remove_all_btn;
QVBoxLayout *charts_layout;
QList<ChartWidget *> charts;
QList<ChartView *> charts;
bool is_zoomed = false;
std::pair<double, double> event_range;
std::pair<double, double> display_range;

@ -9,24 +9,22 @@ DBCManager::DBCManager(QObject *parent) : QObject(parent) {}
DBCManager::~DBCManager() {}
void DBCManager::open(const QString &dbc_file_name) {
dbc_name = dbc_file_name;
dbc = const_cast<DBC *>(dbc_lookup(dbc_name.toStdString()));
msg_map.clear();
for (auto &msg : dbc->msgs) {
msg_map[msg.address] = &msg;
}
dbc = const_cast<DBC *>(dbc_lookup(dbc_file_name.toStdString()));
updateMsgMap();
emit DBCFileChanged();
}
void DBCManager::open(const QString &name, const QString &content) {
this->dbc_name = name;
std::istringstream stream(content.toStdString());
dbc = const_cast<DBC *>(dbc_parse_from_stream(name.toStdString(), stream));
updateMsgMap();
emit DBCFileChanged();
}
void DBCManager::updateMsgMap() {
msg_map.clear();
for (auto &msg : dbc->msgs) {
for (auto &msg : dbc->msgs)
msg_map[msg.address] = &msg;
}
emit DBCFileChanged();
}
QString DBCManager::generateDBC() {
@ -51,22 +49,30 @@ QString DBCManager::generateDBC() {
}
void DBCManager::updateMsg(const QString &id, const QString &name, uint32_t size) {
auto m = const_cast<Msg *>(msg(id));
if (m) {
auto [bus, address] = parseId(id);
if (auto m = const_cast<Msg *>(msg(address))) {
m->name = name.toStdString();
m->size = size;
} else {
uint32_t address = addressFromId(id);
dbc->msgs.push_back({.address = address, .name = name.toStdString(), .size = size});
msg_map[address] = &dbc->msgs.back();
m = &dbc->msgs.emplace_back(Msg{.address = address, .name = name.toStdString(), .size = size});
msg_map[address] = m;
}
emit msgUpdated(address);
}
void DBCManager::removeMsg(const QString &id) {
uint32_t address = parseId(id).second;
auto it = std::find_if(dbc->msgs.begin(), dbc->msgs.end(), [address](auto &m) { return m.address == address; });
if (it != dbc->msgs.end()) {
dbc->msgs.erase(it);
updateMsgMap();
emit msgRemoved(address);
}
emit msgUpdated(id);
}
void DBCManager::addSignal(const QString &id, const Signal &sig) {
if (Msg *m = const_cast<Msg *>(msg(id))) {
m->sigs.push_back(sig);
emit signalAdded(&m->sigs.back());
emit signalAdded(&m->sigs.emplace_back(sig));
}
}
@ -90,8 +96,9 @@ void DBCManager::removeSignal(const QString &id, const QString &sig_name) {
}
}
uint32_t DBCManager::addressFromId(const QString &id) {
return id.mid(id.indexOf(':') + 1).toUInt(nullptr, 16);
std::pair<uint8_t, uint32_t> DBCManager::parseId(const QString &id) {
const auto list = id.split(':');
return {list[0].toInt(), list[1].toUInt(nullptr, 16)};
}
DBCManager *dbc() {
@ -140,9 +147,9 @@ double get_raw_value(uint8_t *data, size_t data_size, const Signal &sig) {
return value;
}
void updateSigSizeParamsFromRange(Signal &s, int from, int to) {
s.start_bit = s.is_little_endian ? from : bigEndianBitIndex(from);
s.size = to - from + 1;
void updateSigSizeParamsFromRange(Signal &s, int start_bit, int size) {
s.start_bit = s.is_little_endian ? start_bit : bigEndianBitIndex(start_bit);
s.size = size;
if (s.is_little_endian) {
s.lsb = s.start_bit;
s.msb = s.start_bit + s.size - 1;

@ -18,13 +18,14 @@ public:
void updateSignal(const QString &id, const QString &sig_name, const Signal &sig);
void removeSignal(const QString &id, const QString &sig_name);
static uint32_t addressFromId(const QString &id);
static std::pair<uint8_t, uint32_t> parseId(const QString &id);
inline static std::vector<std::string> allDBCNames() { return get_dbc_names(); }
inline QString name() const { return dbc_name; }
inline QString name() const { return dbc ? dbc->name.c_str() : ""; }
void updateMsg(const QString &id, const QString &name, uint32_t size);
void removeMsg(const QString &id);
inline const DBC *getDBC() const { return dbc; }
inline const Msg *msg(const QString &id) const { return msg(addressFromId(id)); }
inline const Msg *msg(const QString &id) const { return msg(parseId(id).second); }
inline const Msg *msg(uint32_t address) const {
auto it = msg_map.find(address);
return it != msg_map.end() ? it->second : nullptr;
@ -34,11 +35,12 @@ signals:
void signalAdded(const Signal *sig);
void signalRemoved(const Signal *sig);
void signalUpdated(const Signal *sig);
void msgUpdated(const QString &id);
void msgUpdated(uint32_t address);
void msgRemoved(uint32_t address);
void DBCFileChanged();
private:
QString dbc_name;
void updateMsgMap();
DBC *dbc = nullptr;
std::unordered_map<uint32_t, const Msg *> msg_map;
};
@ -47,7 +49,10 @@ private:
double get_raw_value(uint8_t *data, size_t data_size, const Signal &sig);
int bigEndianStartBitsIndex(int start_bit);
int bigEndianBitIndex(int index);
void updateSigSizeParamsFromRange(Signal &s, int from, int to);
void updateSigSizeParamsFromRange(Signal &s, int start_bit, int size);
std::pair<int, int> getSignalRange(const Signal *s);
DBCManager *dbc();
inline QString msgName(const QString &id, const char *def = "untitled") {
auto msg = dbc()->msg(id);
return msg ? msg->name.c_str() : def;
}

@ -33,20 +33,21 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
title_frame->setFrameShape(QFrame::StyledPanel);
// message title
QHBoxLayout *title_layout = new QHBoxLayout();
title_layout->addWidget(new QLabel("time:"));
toolbar = new QToolBar(this);
toolbar->addWidget(new QLabel("time:"));
time_label = new QLabel(this);
time_label->setStyleSheet("font-weight:bold");
title_layout->addWidget(time_label);
title_layout->addStretch();
toolbar->addWidget(time_label);
name_label = new QLabel(this);
name_label->setStyleSheet("font-weight:bold;");
title_layout->addWidget(name_label);
title_layout->addStretch();
edit_btn = new QPushButton(tr("Edit"), this);
edit_btn->setVisible(false);
title_layout->addWidget(edit_btn);
frame_layout->addLayout(title_layout);
name_label->setAlignment(Qt::AlignCenter);
name_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
toolbar->addWidget(name_label);
toolbar->addAction("🖍", this, &DetailWidget::editMsg)->setToolTip(tr("Edit Message"));
remove_msg_act = toolbar->addAction("X", this, &DetailWidget::removeMsg);
remove_msg_act->setToolTip(tr("Remove Message"));
toolbar->setVisible(false);
frame_layout->addWidget(toolbar);
// warning
warning_widget = new QWidget(this);
@ -85,7 +86,6 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
history_log = new HistoryLog(this);
container_layout->addWidget(history_log);
QObject::connect(edit_btn, &QPushButton::clicked, this, &DetailWidget::editMsg);
QObject::connect(binary_view, &BinaryView::resizeSignal, this, &DetailWidget::resizeSignal);
QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal);
QObject::connect(can, &CANMessages::updated, this, &DetailWidget::updateState);
@ -130,16 +130,13 @@ void DetailWidget::setMessage(const QString &message_id) {
break;
}
}
msg_id = message_id;
if (index == -1) {
index = tabbar->addTab(message_id);
auto msg = dbc()->msg(message_id);
tabbar->setTabToolTip(index, msg ? msg->name.c_str() : "untitled");
tabbar->setTabToolTip(index, msgName(message_id));
}
tabbar->setCurrentIndex(index);
msg_id = message_id;
dbcMsgChanged();
scroll->verticalScrollBar()->setValue(0);
}
void DetailWidget::dbcMsgChanged(int show_form_idx) {
@ -172,8 +169,9 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) {
warnings.push_back(tr("Message size (%1) is incorrect.").arg(msg->size));
}
edit_btn->setVisible(true);
name_label->setText(msg ? msg->name.c_str() : "untitled");
toolbar->setVisible(!msg_id.isEmpty());
remove_msg_act->setEnabled(msg != nullptr);
name_label->setText(msgName(msg_id));
binary_view->setMessage(msg_id);
history_log->setMessage(msg_id);
@ -187,6 +185,7 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) {
warning_label->setText(warnings.join('\n'));
warning_widget->setVisible(!warnings.isEmpty());
setUpdatesEnabled(true);
scroll->verticalScrollBar()->setValue(0);
}
void DetailWidget::updateState() {
@ -211,34 +210,55 @@ void DetailWidget::updateChartState(const QString &id, const Signal *sig, bool o
}
void DetailWidget::editMsg() {
auto msg = dbc()->msg(msg_id);
QString name = msg ? msg->name.c_str() : "untitled";
int size = msg ? msg->size : can->lastMessage(msg_id).dat.size();
EditMessageDialog dlg(msg_id, name, size, this);
QString id = msg_id;
auto msg = dbc()->msg(id);
int size = msg ? msg->size : can->lastMessage(id).dat.size();
EditMessageDialog dlg(id, msgName(id), size, this);
if (dlg.exec()) {
dbc()->updateMsg(msg_id, dlg.name_edit->text(), dlg.size_spin->value());
dbc()->updateMsg(id, dlg.name_edit->text(), dlg.size_spin->value());
dbcMsgChanged();
}
}
void DetailWidget::addSignal(int from, int to) {
if (auto msg = dbc()->msg(msg_id)) {
Signal sig = {};
void DetailWidget::removeMsg() {
QString id = msg_id;
if (auto msg = dbc()->msg(id)) {
QString text = tr("Are you sure you want to remove '%1'").arg(msg->name.c_str());
if (QMessageBox::Yes == QMessageBox::question(this, tr("Remove Message"), text)) {
dbc()->removeMsg(id);
dbcMsgChanged();
}
}
}
void DetailWidget::addSignal(int start_bit, int size, bool little_endian) {
auto msg = dbc()->msg(msg_id);
if (!msg) {
for (int i = 1; /**/; ++i) {
sig.name = "NEW_SIGNAL_" + std::to_string(i);
auto it = std::find_if(msg->sigs.begin(), msg->sigs.end(), [&](auto &s) { return sig.name == s.name; });
if (it == msg->sigs.end()) break;
std::string name = "NEW_MSG_" + std::to_string(i);
auto it = std::find_if(dbc()->getDBC()->msgs.begin(), dbc()->getDBC()->msgs.end(), [&](auto &m) { return m.name == name; });
if (it == dbc()->getDBC()->msgs.end()) {
dbc()->updateMsg(msg_id, name.c_str(), can->lastMessage(msg_id).dat.size());
msg = dbc()->msg(msg_id);
break;
}
}
sig.is_little_endian = false,
updateSigSizeParamsFromRange(sig, from, to);
dbc()->addSignal(msg_id, sig);
dbcMsgChanged(msg->sigs.size() - 1);
}
Signal sig = {};
for (int i = 1; /**/; ++i) {
sig.name = "NEW_SIGNAL_" + std::to_string(i);
auto it = std::find_if(msg->sigs.begin(), msg->sigs.end(), [&](auto &s) { return sig.name == s.name; });
if (it == msg->sigs.end()) break;
}
sig.is_little_endian = little_endian;
updateSigSizeParamsFromRange(sig, start_bit, size);
dbc()->addSignal(msg_id, sig);
dbcMsgChanged(msg->sigs.size() - 1);
}
void DetailWidget::resizeSignal(const Signal *sig, int from, int to) {
void DetailWidget::resizeSignal(const Signal *sig, int start_bit, int size) {
Signal s = *sig;
updateSigSizeParamsFromRange(s, from, to);
updateSigSizeParamsFromRange(s, start_bit, size);
saveSignal(sig, s);
}
@ -261,8 +281,7 @@ void DetailWidget::saveSignal(const Signal *sig, const Signal &new_sig) {
}
dbc()->updateSignal(msg_id, sig->name.c_str(), new_sig);
// update binary view and history log
updateState();
dbcMsgChanged();
}
void DetailWidget::removeSignal(const Signal *sig) {
@ -277,9 +296,7 @@ void DetailWidget::removeSignal(const Signal *sig) {
EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title, int size, QWidget *parent) : QDialog(parent) {
setWindowTitle(tr("Edit message"));
QVBoxLayout *main_layout = new QVBoxLayout(this);
QFormLayout *form_layout = new QFormLayout();
QFormLayout *form_layout = new QFormLayout(this);
form_layout->addRow("ID", new QLabel(msg_id));
name_edit = new QLineEdit(title, this);
@ -291,10 +308,8 @@ EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title
size_spin->setValue(size);
form_layout->addRow(tr("Size"), size_spin);
main_layout->addLayout(form_layout);
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
main_layout->addWidget(buttonBox);
form_layout->addRow(buttonBox);
setFixedWidth(parent->width() * 0.9);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);

@ -2,6 +2,7 @@
#include <QScrollArea>
#include <QTabBar>
#include <QToolBar>
#include "tools/cabana/binaryview.h"
#include "tools/cabana/chartswidget.h"
@ -26,26 +27,25 @@ public:
void setMessage(const QString &message_id);
void dbcMsgChanged(int show_form_idx = -1);
signals:
void binaryViewMoved(bool in);
private:
void updateChartState(const QString &id, const Signal *sig, bool opened);
void showTabBarContextMenu(const QPoint &pt);
void addSignal(int start_bit, int to);
void addSignal(int start_bit, int size, bool little_endian);
void resizeSignal(const Signal *sig, int from, int to);
void saveSignal(const Signal *sig, const Signal &new_sig);
void removeSignal(const Signal *sig);
void editMsg();
void removeMsg();
void showForm();
void updateState();
QString msg_id;
QLabel *name_label, *time_label, *warning_label;
QWidget *warning_widget;
QPushButton *edit_btn;
QWidget *signals_container;
QTabBar *tabbar;
QToolBar *toolbar;
QAction *remove_msg_act;
HistoryLog *history_log;
BinaryView *binary_view;
QScrollArea *scroll;

@ -1,7 +1,6 @@
#include "tools/cabana/historylog.h"
#include <QFontDatabase>
#include <QVBoxLayout>
// HistoryLogModel
@ -89,3 +88,8 @@ HistoryLog::HistoryLog(QWidget *parent) : QTableView(parent) {
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
setStyleSheet("QTableView::item { border:0px; padding-left:5px; padding-right:5px; }");
}
int HistoryLog::sizeHintForColumn(int column) const {
// sizeHintForColumn is only called for column 0 (ResizeToContents)
return itemDelegate()->sizeHint(viewOptions(), model->index(0, 0)).width() + 1; // +1 for grid
}

@ -9,7 +9,7 @@
class HeaderView : public QHeaderView {
public:
HeaderView(Qt::Orientation orientation, QWidget *parent = nullptr) : QHeaderView(orientation, parent) {}
QSize sectionSizeFromContents(int logicalIndex) const;
QSize sectionSizeFromContents(int logicalIndex) const override;
};
class HistoryLogModel : public QAbstractTableModel {
@ -40,5 +40,6 @@ public:
void setMessage(const QString &message_id) { model->setMessage(message_id); }
void updateState() { model->updateState(); }
private:
int sizeHintForColumn(int column) const override;
HistoryLogModel *model;
};

@ -1,8 +1,17 @@
#include "tools/cabana/mainwin.h"
#include <QApplication>
#include <QClipboard>
#include <QCompleter>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QHBoxLayout>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QScreen>
#include <QToolBar>
#include <QVBoxLayout>
#include "tools/replay/util.h"
@ -12,25 +21,39 @@ void qLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const
if (main_win) emit main_win->showMessage(msg, 0);
}
MainWindow::MainWindow() : QWidget() {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(11, 11, 11, 5);
MainWindow::MainWindow() : QMainWindow() {
setWindowTitle("Cabana");
QWidget *central_widget = new QWidget(this);
QHBoxLayout *main_layout = new QHBoxLayout(central_widget);
main_layout->setContentsMargins(11, 11, 11, 0);
main_layout->setSpacing(0);
QHBoxLayout *h_layout = new QHBoxLayout();
h_layout->setContentsMargins(0, 0, 0, 0);
main_layout->addLayout(h_layout);
splitter = new QSplitter(Qt::Horizontal, this);
splitter->setHandleWidth(11);
// DBC file selector
QWidget *messages_container = new QWidget(this);
QVBoxLayout *messages_layout = new QVBoxLayout(messages_container);
messages_layout->setContentsMargins(0, 0, 0, 0);
dbc_combo = new QComboBox(this);
auto dbc_names = dbc()->allDBCNames();
for (const auto &name : dbc_names) {
dbc_combo->addItem(QString::fromStdString(name));
}
dbc_combo->model()->sort(0);
dbc_combo->setEditable(true);
dbc_combo->setInsertPolicy(QComboBox::NoInsert);
dbc_combo->completer()->setCompletionMode(QCompleter::PopupCompletion);
messages_layout->addWidget(dbc_combo);
messages_widget = new MessagesWidget(this);
splitter->addWidget(messages_widget);
messages_layout->addWidget(messages_widget);
splitter->addWidget(messages_container);
charts_widget = new ChartsWidget(this);
detail_widget = new DetailWidget(charts_widget, this);
splitter->addWidget(detail_widget);
h_layout->addWidget(splitter);
main_layout->addWidget(splitter);
// right widgets
QWidget *right_container = new QWidget(this);
@ -38,35 +61,21 @@ MainWindow::MainWindow() : QWidget() {
r_layout = new QVBoxLayout(right_container);
r_layout->setContentsMargins(11, 0, 0, 0);
QHBoxLayout *right_hlayout = new QHBoxLayout();
QLabel *fingerprint_label = new QLabel(this);
right_hlayout->addWidget(fingerprint_label);
fingerprint_label = new QLabel(this);
right_hlayout->addWidget(fingerprint_label, 0, Qt::AlignLeft);
// TODO: click to select another route.
right_hlayout->addWidget(new QLabel(can->route()));
QPushButton *settings_btn = new QPushButton("Settings");
right_hlayout->addWidget(settings_btn, 0, Qt::AlignRight);
right_hlayout->addWidget(new QLabel(can->route()), 0, Qt::AlignRight);
r_layout->addLayout(right_hlayout);
video_widget = new VideoWidget(this);
r_layout->addWidget(video_widget, 0, Qt::AlignTop);
r_layout->addWidget(charts_widget);
main_layout->addWidget(right_container);
h_layout->addWidget(right_container);
// status bar
status_bar = new QStatusBar(this);
status_bar->setFixedHeight(20);
status_bar->setContentsMargins(0, 0, 0, 0);
status_bar->setSizeGripEnabled(true);
progress_bar = new QProgressBar();
progress_bar->setRange(0, 100);
progress_bar->setTextVisible(true);
progress_bar->setFixedSize({230, 16});
progress_bar->setVisible(false);
status_bar->addPermanentWidget(progress_bar);
main_layout->addWidget(status_bar);
setCentralWidget(central_widget);
createActions();
createStatusBar();
qRegisterMetaType<uint64_t>("uint64_t");
qRegisterMetaType<ReplyMsgType>("ReplyMsgType");
@ -78,21 +87,98 @@ MainWindow::MainWindow() : QWidget() {
emit updateProgressBar(cur, total, success);
});
QObject::connect(this, &MainWindow::showMessage, status_bar, &QStatusBar::showMessage);
main_win = this;
qInstallMessageHandler(qLogMessageHandler);
QFile json_file("./car_fingerprint_to_dbc.json");
if (json_file.open(QIODevice::ReadOnly)) {
fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll());
}
QObject::connect(dbc_combo, SIGNAL(activated(const QString &)), SLOT(loadDBCFromName(const QString &)));
QObject::connect(this, &MainWindow::showMessage, statusBar(), &QStatusBar::showMessage);
QObject::connect(this, &MainWindow::updateProgressBar, this, &MainWindow::updateDownloadProgress);
QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, detail_widget, &DetailWidget::setMessage);
QObject::connect(detail_widget, &DetailWidget::binaryViewMoved, [this](bool in) { splitter->setSizes({in ? 100 : 0, 500}); });
QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts);
QObject::connect(charts_widget, &ChartsWidget::rangeChanged, video_widget, &VideoWidget::rangeChanged);
QObject::connect(settings_btn, &QPushButton::clicked, this, &MainWindow::setOption);
QObject::connect(can, &CANMessages::eventsMerged, [=]() { fingerprint_label->setText(can->carFingerprint() ); });
QObject::connect(can, &CANMessages::streamStarted, this, &MainWindow::loadDBCFromFingerprint);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() {
dbc_combo->setCurrentText(QFileInfo(dbc()->name()).baseName());
setWindowTitle(tr("%1 - Cabana").arg(dbc()->name()));
});
}
main_win = this;
qInstallMessageHandler(qLogMessageHandler);
void MainWindow::createActions() {
QMenu *file_menu = menuBar()->addMenu(tr("&File"));
file_menu->addAction(tr("Open DBC File..."), this, &MainWindow::loadDBCFromFile);
file_menu->addAction(tr("Load DBC From Clipboard"), this, &MainWindow::loadDBCFromClipboard);
file_menu->addSeparator();
file_menu->addAction(tr("Save DBC As..."), this, &MainWindow::saveDBCToFile);
file_menu->addAction(tr("Copy DBC To Clipboard"), this, &MainWindow::saveDBCToClipboard);
file_menu->addSeparator();
file_menu->addAction(tr("Settings..."), this, &MainWindow::setOption);
QMenu *help_menu = menuBar()->addMenu(tr("&Help"));
help_menu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
}
void MainWindow::createStatusBar() {
progress_bar = new QProgressBar();
progress_bar->setRange(0, 100);
progress_bar->setTextVisible(true);
progress_bar->setFixedSize({230, 16});
progress_bar->setVisible(false);
statusBar()->addPermanentWidget(progress_bar);
}
void MainWindow::loadDBCFromName(const QString &name) {
if (name != dbc()->name())
dbc()->open(name);
}
void MainWindow::loadDBCFromFile() {
QString file_name = QFileDialog::getOpenFileName(this, tr("Open File"), QDir::homePath(), "DBC (*.dbc)");
if (!file_name.isEmpty()) {
QFile file(file_name);
if (file.open(QIODevice::ReadOnly)) {
auto dbc_name = QFileInfo(file_name).baseName();
dbc()->open(dbc_name, file.readAll());
}
}
}
void MainWindow::loadDBCFromClipboard() {
QString dbc_str = QGuiApplication::clipboard()->text();
dbc()->open("From Clipboard", dbc_str);
QMessageBox::information(this, tr("Load From Clipboard"), tr("DBC Successfully Loaded!"));
}
void MainWindow::loadDBCFromFingerprint() {
auto fingerprint = can->carFingerprint();
fingerprint_label->setText(fingerprint);
if (!fingerprint.isEmpty() && dbc()->name().isEmpty()) {
auto dbc_name = fingerprint_to_dbc[fingerprint];
if (dbc_name != QJsonValue::Undefined) {
loadDBCFromName(dbc_name.toString());
}
}
}
void MainWindow::saveDBCToFile() {
QString file_name = QFileDialog::getSaveFileName(this, tr("Save File"),
QDir::homePath() + "/untitled.dbc", tr("DBC (*.dbc)"));
if (!file_name.isEmpty()) {
QFile file(file_name);
if (file.open(QIODevice::WriteOnly))
file.write(dbc()->generateDBC().toUtf8());
}
}
void MainWindow::saveDBCToClipboard() {
QGuiApplication::clipboard()->setText(dbc()->generateDBC());
QMessageBox::information(this, tr("Copy To Clipboard"), tr("DBC Successfully copied!"));
}
void MainWindow::updateDownloadProgress(uint64_t cur, uint64_t total, bool success) {
if (success && cur < total) {
if (success && cur < total) {
progress_bar->setValue((cur / (double)total) * 100);
progress_bar->setFormat(tr("Downloading %p% (%1)").arg(formattedDataSize(total).c_str()));
progress_bar->show();

@ -1,5 +1,8 @@
#pragma once
#include <QComboBox>
#include <QJsonDocument>
#include <QMainWindow>
#include <QProgressBar>
#include <QSplitter>
#include <QStatusBar>
@ -9,19 +12,29 @@
#include "tools/cabana/messageswidget.h"
#include "tools/cabana/videowidget.h"
class MainWindow : public QWidget {
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow();
void dockCharts(bool dock);
void showStatusMessage(const QString &msg, int timeout = 0) { status_bar->showMessage(msg, timeout); }
void showStatusMessage(const QString &msg, int timeout = 0) { statusBar()->showMessage(msg, timeout); }
public slots:
void loadDBCFromName(const QString &name);
void loadDBCFromFingerprint();
void loadDBCFromFile();
void loadDBCFromClipboard();
void saveDBCToFile();
void saveDBCToClipboard();
signals:
void showMessage(const QString &msg, int timeout);
void updateProgressBar(uint64_t cur, uint64_t total, bool success);
protected:
void createActions();
void createStatusBar();
void closeEvent(QCloseEvent *event) override;
void updateDownloadProgress(uint64_t cur, uint64_t total, bool success);
void setOption();
@ -34,5 +47,7 @@ protected:
QWidget *floating_window = nullptr;
QVBoxLayout *r_layout;
QProgressBar *progress_bar;
QStatusBar *status_bar;
QLabel *fingerprint_label;
QJsonDocument fingerprint_to_dbc;
QComboBox *dbc_combo;
};

@ -1,15 +1,8 @@
#include "tools/cabana/messageswidget.h"
#include <QCompleter>
#include <QDialogButtonBox>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QFontDatabase>
#include <QHeaderView>
#include <QLineEdit>
#include <QPushButton>
#include <QTextEdit>
#include <QVBoxLayout>
#include "tools/cabana/dbcmanager.h"
@ -18,31 +11,6 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0);
// DBC file selector
QHBoxLayout *dbc_file_layout = new QHBoxLayout();
dbc_combo = new QComboBox(this);
auto dbc_names = dbc()->allDBCNames();
for (const auto &name : dbc_names) {
dbc_combo->addItem(QString::fromStdString(name));
}
dbc_combo->model()->sort(0);
dbc_combo->setEditable(true);
dbc_combo->setCurrentText(QString());
dbc_combo->setInsertPolicy(QComboBox::NoInsert);
dbc_combo->completer()->setCompletionMode(QCompleter::PopupCompletion);
QFont font;
font.setBold(true);
dbc_combo->lineEdit()->setFont(font);
dbc_file_layout->addWidget(dbc_combo);
QPushButton *load_from_paste = new QPushButton(tr("Load from paste"), this);
dbc_file_layout->addWidget(load_from_paste);
dbc_file_layout->addStretch();
QPushButton *save_btn = new QPushButton(tr("Save DBC"), this);
dbc_file_layout->addWidget(save_btn);
main_layout->addLayout(dbc_file_layout);
// message filter
QLineEdit *filter = new QLineEdit(this);
filter->setClearButtonEnabled(true);
@ -67,55 +35,22 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
// signals/slots
QObject::connect(filter, &QLineEdit::textChanged, model, &MessageListModel::setFilterString);
QObject::connect(can, &CANMessages::eventsMerged, this, &MessagesWidget::loadDBCFromFingerprint);
QObject::connect(can, &CANMessages::updated, [this]() { model->updateState(); });
QObject::connect(dbc_combo, SIGNAL(activated(const QString &)), SLOT(loadDBCFromName(const QString &)));
QObject::connect(load_from_paste, &QPushButton::clicked, this, &MessagesWidget::loadDBCFromPaste);
QObject::connect(save_btn, &QPushButton::clicked, this, &MessagesWidget::saveDBC);
QObject::connect(can, &CANMessages::msgsReceived, model, &MessageListModel::msgsReceived);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, model, &MessageListModel::sortMessages);
QObject::connect(dbc(), &DBCManager::msgUpdated, model, &MessageListModel::sortMessages);
QObject::connect(dbc(), &DBCManager::msgRemoved, model, &MessageListModel::sortMessages);
QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex &current, const QModelIndex &previous) {
if (current.isValid()) {
emit msgSelectionChanged(current.data(Qt::UserRole).toString());
if (current.isValid() && current.row() < model->msgs.size()) {
if (model->msgs[current.row()] != current_msg_id) {
current_msg_id = model->msgs[current.row()];
emit msgSelectionChanged(current_msg_id);
}
}
});
QFile json_file("./car_fingerprint_to_dbc.json");
if (json_file.open(QIODevice::ReadOnly)) {
fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll());
}
}
void MessagesWidget::loadDBCFromName(const QString &name) {
if (name != dbc()->name()) {
dbc()->open(name);
dbc_combo->setCurrentText(name);
// re-sort model to refresh column 'Name'
model->updateState(true);
}
}
void MessagesWidget::loadDBCFromPaste() {
LoadDBCDialog dlg(this);
if (dlg.exec()) {
dbc()->open("from paste", dlg.dbc_edit->toPlainText());
dbc_combo->setCurrentText("loaded from paste");
model->updateState(true);
}
}
void MessagesWidget::loadDBCFromFingerprint() {
auto fingerprint = can->carFingerprint();
if (!fingerprint.isEmpty() && dbc()->name().isEmpty()) {
auto dbc_name = fingerprint_to_dbc[fingerprint];
if (dbc_name != QJsonValue::Undefined) {
loadDBCFromName(dbc_name.toString());
}
}
}
void MessagesWidget::saveDBC() {
SaveDBCDialog dlg(this);
dlg.dbc_edit->setText(dbc()->generateDBC());
dlg.exec();
QObject::connect(model, &MessageListModel::modelReset, [this]() {
if (int row = model->msgs.indexOf(current_msg_id); row != -1)
table_widget->selectionModel()->setCurrentIndex(model->index(row, 0), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect);
});
}
// MessageListModel
@ -128,103 +63,71 @@ QVariant MessageListModel::headerData(int section, Qt::Orientation orientation,
QVariant MessageListModel::data(const QModelIndex &index, int role) const {
if (role == Qt::DisplayRole) {
const auto &m = msgs[index.row()];
auto &can_data = can->lastMessage(m->id);
const auto &id = msgs[index.row()];
auto &can_data = can->lastMessage(id);
switch (index.column()) {
case 0: return m->name;
case 1: return m->id;
case 0: return msgName(id);
case 1: return id;
case 2: return can_data.freq;
case 3: return can_data.count;
case 4: return toHex(can_data.dat);
}
} else if (role == Qt::UserRole) {
return msgs[index.row()]->id;
} else if (role == Qt::FontRole) {
if (index.column() == columnCount() - 1) {
return QFontDatabase::systemFont(QFontDatabase::FixedFont);
}
} else if (role == Qt::FontRole && index.column() == columnCount() - 1) {
return QFontDatabase::systemFont(QFontDatabase::FixedFont);
}
return {};
}
void MessageListModel::setFilterString(const QString &string) {
void MessageListModel::setFilterString(const QString &string) {
filter_str = string;
updateState(true);
}
bool MessageListModel::updateMessages(bool sort) {
if (msgs.size() == can->can_msgs.size() && filter_str.isEmpty() && !sort)
return false;
// update message list
int i = 0;
bool search_id = filter_str.contains(':');
msgs.clear();
for (auto it = can->can_msgs.begin(); it != can->can_msgs.end(); ++it) {
const Msg *msg = dbc()->msg(it.key());
QString msg_name = msg ? msg->name.c_str() : "untitled";
if (!filter_str.isEmpty() && !(search_id ? it.key() : msg_name).contains(filter_str, Qt::CaseInsensitive))
continue;
auto &m = i < msgs.size() ? msgs[i] : msgs.emplace_back(new Message);
m->id = it.key();
m->name = msg_name;
++i;
if ((search_id ? it.key() : msgName(it.key())).contains(filter_str, Qt::CaseInsensitive))
msgs.push_back(it.key());
}
msgs.resize(i);
sortMessages();
}
void MessageListModel::sortMessages() {
beginResetModel();
if (sort_column == 0) {
std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) {
bool ret = l->name < r->name || (l->name == r->name && l->id < r->id);
bool ret = std::pair{msgName(l), l} < std::pair{msgName(r), r};
return sort_order == Qt::AscendingOrder ? ret : !ret;
});
} else if (sort_column == 1) {
std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) {
return sort_order == Qt::AscendingOrder ? l->id < r->id : l->id > r->id;
return sort_order == Qt::AscendingOrder ? l < r : l > r;
});
} else if (sort_column == 2) {
// sort by frequency
std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) {
uint32_t lfreq = can->lastMessage(l->id).freq;
uint32_t rfreq = can->lastMessage(r->id).freq;
bool ret = lfreq < rfreq || (lfreq == rfreq && l->id < r->id);
bool ret = std::pair{can->lastMessage(l).freq, l} < std::pair{can->lastMessage(r).freq, r};
return sort_order == Qt::AscendingOrder ? ret : !ret;
});
} else if (sort_column == 3) {
// sort by count
std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) {
uint32_t lcount = can->lastMessage(l->id).count;
uint32_t rcount = can->lastMessage(r->id).count;
bool ret = lcount < rcount || (lcount == rcount && l->id < r->id);
bool ret = std::pair{can->lastMessage(l).count, l} < std::pair{can->lastMessage(r).count, r};
return sort_order == Qt::AscendingOrder ? ret : !ret;
});
}
return true;
endResetModel();
}
void MessageListModel::updateState(bool sort) {
void MessageListModel::msgsReceived(const QHash<QString, CanData> *new_msgs) {
int prev_row_count = msgs.size();
auto prev_idx = persistentIndexList();
QString selected_msg_id = prev_idx.empty() ? "" : prev_idx[0].data(Qt::UserRole).toString();
bool msg_updated = updateMessages(sort);
int delta = msgs.size() - prev_row_count;
if (delta > 0) {
beginInsertRows({}, prev_row_count, msgs.size() - 1);
endInsertRows();
} else if (delta < 0) {
beginRemoveRows({}, msgs.size(), prev_row_count - 1);
endRemoveRows();
if (filter_str.isEmpty() && msgs.size() != can->can_msgs.size()) {
msgs = can->can_msgs.keys();
}
if (!msgs.empty()) {
if (msg_updated && !prev_idx.isEmpty()) {
// keep selection
auto it = std::find_if(msgs.begin(), msgs.end(), [&](auto &m) { return m->id == selected_msg_id; });
if (it != msgs.end()) {
for (auto &idx : prev_idx)
changePersistentIndex(idx, index(std::distance(msgs.begin(), it), idx.column()));
}
if (msgs.size() != prev_row_count) {
sortMessages();
return;
}
for (int i = 0; i < msgs.size(); ++i) {
if (new_msgs->contains(msgs[i])) {
for (int col = 2; col < columnCount(); ++col)
emit dataChanged(index(i, col), index(i, col), {Qt::DisplayRole});
}
emit dataChanged(index(0, 0), index(msgs.size() - 1, 3), {Qt::DisplayRole});
}
}
@ -232,63 +135,6 @@ void MessageListModel::sort(int column, Qt::SortOrder order) {
if (column != columnCount() - 1) {
sort_column = column;
sort_order = order;
updateState(true);
}
}
// LoadDBCDialog
LoadDBCDialog::LoadDBCDialog(QWidget *parent) : QDialog(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
dbc_edit = new QTextEdit(this);
dbc_edit->setAcceptRichText(false);
dbc_edit->setPlaceholderText(tr("paste DBC file here"));
main_layout->addWidget(dbc_edit);
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
main_layout->addWidget(buttonBox);
setMinimumSize({640, 480});
QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
// SaveDBCDialog
SaveDBCDialog::SaveDBCDialog(QWidget *parent) : QDialog(parent) {
setWindowTitle(tr("Save DBC"));
QVBoxLayout *main_layout = new QVBoxLayout(this);
dbc_edit = new QTextEdit(this);
dbc_edit->setAcceptRichText(false);
main_layout->addWidget(dbc_edit);
QPushButton *copy_to_clipboard = new QPushButton(tr("Copy To Clipboard"), this);
QPushButton *save_as = new QPushButton(tr("Save As"), this);
QHBoxLayout *btn_layout = new QHBoxLayout();
btn_layout->addStretch();
btn_layout->addWidget(copy_to_clipboard);
btn_layout->addWidget(save_as);
main_layout->addLayout(btn_layout);
setMinimumSize({640, 480});
QObject::connect(copy_to_clipboard, &QPushButton::clicked, this, &SaveDBCDialog::copytoClipboard);
QObject::connect(save_as, &QPushButton::clicked, this, &SaveDBCDialog::saveAs);
}
void SaveDBCDialog::copytoClipboard() {
dbc_edit->selectAll();
dbc_edit->copy();
QDialog::accept();
}
void SaveDBCDialog::saveAs() {
QString file_name = QFileDialog::getSaveFileName(this, tr("Save File"),
QDir::homePath() + "/untitled.dbc", tr("DBC (*.dbc)"));
if (!file_name.isEmpty()) {
QFile file(file_name);
if (file.open(QIODevice::WriteOnly)) {
file.write(dbc_edit->toPlainText().toUtf8());
}
QDialog::accept();
sortMessages();
}
}

@ -1,32 +1,10 @@
#pragma once
#include <QAbstractTableModel>
#include <QComboBox>
#include <QDialog>
#include <QJsonDocument>
#include <QTableView>
#include <QTextEdit>
#include "tools/cabana/canmessages.h"
class LoadDBCDialog : public QDialog {
Q_OBJECT
public:
LoadDBCDialog(QWidget *parent);
QTextEdit *dbc_edit;
};
class SaveDBCDialog : public QDialog {
Q_OBJECT
public:
SaveDBCDialog(QWidget *parent);
void copytoClipboard();
void saveAs();
QTextEdit *dbc_edit;
};
class MessageListModel : public QAbstractTableModel {
Q_OBJECT
@ -37,16 +15,12 @@ public:
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const override { return msgs.size(); }
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
void updateState(bool sort = false);
void setFilterString(const QString &string);
void msgsReceived(const QHash<QString, CanData> *new_msgs = nullptr);
void sortMessages();
QStringList msgs;
private:
bool updateMessages(bool sort);
struct Message {
QString id, name;
};
std::vector<std::unique_ptr<Message>> msgs;
QString filter_str;
int sort_column = 0;
Qt::SortOrder sort_order = Qt::AscendingOrder;
@ -57,19 +31,11 @@ class MessagesWidget : public QWidget {
public:
MessagesWidget(QWidget *parent);
public slots:
void loadDBCFromName(const QString &name);
void loadDBCFromFingerprint();
void loadDBCFromPaste();
void saveDBC();
signals:
void msgSelectionChanged(const QString &message_id);
protected:
QTableView *table_widget;
QComboBox *dbc_combo;
QString current_msg_id;
MessageListModel *model;
QJsonDocument fingerprint_to_dbc;
};

@ -36,8 +36,7 @@ void Settings::load() {
SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
setWindowTitle(tr("Settings"));
QVBoxLayout *main_layout = new QVBoxLayout(this);
QFormLayout *form_layout = new QFormLayout();
QFormLayout *form_layout = new QFormLayout(this);
fps = new QSpinBox(this);
fps->setRange(10, 100);
@ -74,10 +73,8 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
chart_theme->setCurrentIndex(settings.chart_theme == 1 ? 1 : 0);
form_layout->addRow(tr("Chart theme"), chart_theme);
main_layout->addLayout(form_layout);
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
main_layout->addWidget(buttonBox);
form_layout->addRow(buttonBox);
setFixedWidth(360);
connect(buttonBox, &QDialogButtonBox::accepted, this, &SettingsDlg::save);

@ -28,7 +28,6 @@ SignalForm::SignalForm(QWidget *parent) : QWidget(parent) {
endianness->addItems({"Little", "Big"});
form_layout->addRow(tr("Endianness"), endianness);
;
form_layout->addRow(tr("lsb"), lsb = new QLabel());
form_layout->addRow(tr("msb"), msb = new QLabel());
@ -130,7 +129,18 @@ void SignalEdit::saveSignal() {
s.offset = form->offset->text().toDouble();
s.factor = form->factor->text().toDouble();
s.is_signed = form->sign->currentIndex() == 0;
s.is_little_endian = form->endianness->currentIndex() == 0;
bool little_endian = form->endianness->currentIndex() == 0;
if (little_endian != s.is_little_endian) {
int start = std::floor(s.start_bit / 8);
if (little_endian) {
int end = std::floor((s.start_bit - s.size + 1) / 8);
s.start_bit = start == end ? s.start_bit - s.size + 1 : bigEndianStartBitsIndex(s.start_bit);
} else {
int end = std::floor((s.start_bit + s.size - 1) / 8);
s.start_bit = start == end ? s.start_bit + s.size - 1 : bigEndianBitIndex(s.start_bit);
}
s.is_little_endian = little_endian;
}
if (s.is_little_endian) {
s.lsb = s.start_bit;
s.msb = s.start_bit + s.size - 1;

@ -28,11 +28,9 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) {
slider = new Slider(this);
slider->setSingleStep(0);
slider->setMinimum(0);
slider->setMaximum(can->totalSeconds() * 1000);
slider_layout->addWidget(slider);
end_time_label = new QLabel(formatTime(can->totalSeconds()));
end_time_label = new QLabel(this);
slider_layout->addWidget(end_time_label);
main_layout->addLayout(slider_layout);
@ -61,6 +59,10 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) {
QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(formatTime(value / 1000)); });
QObject::connect(cam_widget, &CameraWidget::clicked, [this]() { pause(!can->isPaused()); });
QObject::connect(play_btn, &QPushButton::clicked, [=]() { pause(!can->isPaused()); });
QObject::connect(can, &CANMessages::streamStarted, [this]() {
end_time_label->setText(formatTime(can->totalSeconds()));
slider->setRange(0, can->totalSeconds() * 1000);
});
}
void VideoWidget::pause(bool pause) {
@ -74,8 +76,7 @@ void VideoWidget::rangeChanged(double min, double max, bool is_zoomed) {
max = can->totalSeconds();
}
end_time_label->setText(formatTime(max));
slider->setMinimum(min * 1000);
slider->setMaximum(max * 1000);
slider->setRange(min * 1000, max * 1000);
}
void VideoWidget::updateState() {
@ -91,7 +92,7 @@ Slider::Slider(QWidget *parent) : QSlider(Qt::Horizontal, parent) {
timeline = can->getTimeline();
update();
});
timer->start();
QObject::connect(can, SIGNAL(streamStarted()), timer, SLOT(start()));
}
void Slider::sliderChange(QAbstractSlider::SliderChange change) {

@ -3,7 +3,6 @@
#include <QLabel>
#include <QPushButton>
#include <QSlider>
#include <QWidget>
#include "selfdrive/ui/qt/widgets/cameraview.h"
#include "tools/cabana/canmessages.h"

@ -4,7 +4,7 @@
# run limeGPS with random static location
timeout 300 ./simulate_gps_signal.py 32.7518 -117.1962 &
gps_PID=$(ps -aux | grep -m 1 "timeout 300" | cut -d ' ' -f 7)
gps_PID=$(ps -aux | grep -m 1 "timeout 300" | awk '{print $2}')
echo "starting limeGPS..."
sleep 10

@ -40,14 +40,10 @@ class TestGPS(unittest.TestCase):
if ublox_available:
raise unittest.SkipTest
@unittest.skip("Skip cold start test due to time")
def test_quectel_cold_start(self):
def test_a_quectel_cold_start(self):
# delete assistance data to enforce cold start for GNSS
# testing shows that this takes up to 20min
# invalidate supl setting, cannot be reset
_, err = exec_mmcli("--location-set-supl-server=unittest:1")
_, err = exec_mmcli("--command='AT+QGPSDEL=0'")
assert len(err) == 0, f"GPSDEL failed: {err}"
@ -55,27 +51,6 @@ class TestGPS(unittest.TestCase):
start_time = time.monotonic()
glo = messaging.sub_sock("gpsLocation", timeout=0.1)
timeout = 10*60*25 # 25 minute
timedout = wait_for_location(glo, timeout)
managed_processes['rawgpsd'].stop()
assert timedout is False, "Waiting for location timed out (25min)!"
duration = time.monotonic() - start_time
assert duration < 50, f"Received GPS location {duration}!"
def test_a_quectel_cold_start_AGPS(self):
_, err = exec_mmcli("--command='AT+QGPSDEL=0'")
assert len(err) == 0, f"GPSDEL failed: {err}"
# setup AGPS
exec_mmcli("--location-set-supl-server=supl.google.com:7276")
managed_processes['rawgpsd'].start()
start_time = time.monotonic()
glo = messaging.sub_sock("gpsLocation", timeout=0.1)
timeout = 10*60*3 # 3 minute
timedout = wait_for_location(glo, timeout)
managed_processes['rawgpsd'].stop()
@ -87,15 +62,11 @@ class TestGPS(unittest.TestCase):
def test_b_quectel_startup(self):
# setup AGPS
exec_mmcli("--location-set-supl-server=supl.google.com:7276")
managed_processes['rawgpsd'].start()
start_time = time.monotonic()
glo = messaging.sub_sock("gpsLocation", timeout=0.1)
timeout = 10*60*3 # 3 minute
timeout = 10*60 # 1 minute
timedout = wait_for_location(glo, timeout)
managed_processes['rawgpsd'].stop()

Loading…
Cancel
Save