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. 4
      selfdrive/boardd/panda.cc
  10. 22
      selfdrive/boardd/panda_comms.h
  11. 252
      selfdrive/boardd/spi.cc
  12. 8
      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. 40
      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. 9
      system/camerad/test/test_camerad.py
  60. 3
      system/hardware/tici/hardware.py
  61. 2
      tools/cabana/README.md
  62. 141
      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. 91
      tools/cabana/detailwidget.cc
  71. 10
      tools/cabana/detailwidget.h
  72. 6
      tools/cabana/historylog.cc
  73. 3
      tools/cabana/historylog.h
  74. 160
      tools/cabana/mainwin.cc
  75. 21
      tools/cabana/mainwin.h
  76. 236
      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 * 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 driver monitoring model
* New end-to-end distracted trigger * 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 * Self-tuning torque lateral controller parameters
* Parameters learned live for each car * Parameters learned live for each car
* Torque controller used on all Toyota, Lexus, Hyundai, Kia, and Genesis models * 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 * Matched speeds shown on car's dash
* Improved update experience * Improved update experience
* Border turns grey while overriding steering * 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 * AGNOS 6
* tools: new and improved cabana thanks to deanlee! * 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! * Hyundai Santa Cruz 2021-22 support thanks to sunnyhaibin!
* Kia Sportage 2023 support thanks to sunnyhaibin! * Kia Sportage 2023 support thanks to sunnyhaibin!
* Kia Sportage Hybrid 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}, {"DashcamOverride", PERSISTENT},
{"DisableLogging", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"DisableLogging", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON},
{"DisablePowerDown", PERSISTENT}, {"DisablePowerDown", PERSISTENT},
{"EndToEndLong", PERSISTENT}, {"ExperimentalMode", PERSISTENT},
{"ExperimentalLongitudinalEnabled", PERSISTENT}, // WARNING: THIS MAY DISABLE AEB {"ExperimentalLongitudinalEnabled", PERSISTENT}, // WARNING: THIS MAY DISABLE AEB
{"DisableUpdates", PERSISTENT}, {"DisableUpdates", PERSISTENT},
{"DisengageOnAccelerator", PERSISTENT}, {"DisengageOnAccelerator", PERSISTENT},

@ -1,5 +1,6 @@
#include "common/util.h" #include "common/util.h"
#include <sys/ioctl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <dirent.h> #include <dirent.h>
@ -149,6 +150,14 @@ int safe_fflush(FILE *stream) {
return ret; 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) { std::string readlink(const std::string &path) {
char buff[4096]; char buff[4096];
ssize_t len = ::readlink(path.c_str(), buff, sizeof(buff)-1); 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); FILE* safe_fopen(const char* filename, const char* mode);
size_t safe_fwrite(const void * ptr, size_t size, size_t count, FILE * stream); size_t safe_fwrite(const void * ptr, size_t size, size_t count, FILE * stream);
int safe_fflush(FILE *stream); int safe_fflush(FILE *stream);
int safe_ioctl(int fd, unsigned long request, void *argp);
std::string readlink(const std::string& path); std::string readlink(const std::string& path);
bool file_exists(const std::string& fn); 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. 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| |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|G70 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F|
|Genesis|G80 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Genesis|G80 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|
|Genesis|G90 2017-18|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Genesis|G90 2017-18|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|
|Genesis|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|Acadia 2018[<sup>3</sup>](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|
|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| |GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|
|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| |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|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai J|
|Hyundai|i30 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| |Hyundai|i30 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 (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 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 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| |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|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 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 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 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 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| |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/can_list_to_can_capnp.cc
selfdrive/boardd/panda.cc selfdrive/boardd/panda.cc
selfdrive/boardd/panda.h selfdrive/boardd/panda.h
selfdrive/boardd/spi.cc
selfdrive/boardd/panda_comms.h selfdrive/boardd/panda_comms.h
selfdrive/boardd/panda_comms.cc selfdrive/boardd/panda_comms.cc
selfdrive/boardd/set_time.py selfdrive/boardd/set_time.py

@ -1,9 +1,9 @@
Import('env', 'envCython', 'common', 'cereal', 'messaging') Import('env', 'envCython', 'common', 'cereal', 'messaging')
libs = ['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj'] 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']) 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"]) envCython.Program('boardd_api_impl.so', 'boardd_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"])
if GetOption('test'): 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) { Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) {
// TODO: support SPI here one day... // TODO: support SPI here one day...
if (serial.find("spi") != std::string::npos) {
handle = std::make_unique<PandaSpiHandle>(serial);
} else {
handle = std::make_unique<PandaUsbHandle>(serial); handle = std::make_unique<PandaUsbHandle>(serial);
}
hw_type = get_hw_type(); hw_type = get_hw_type();

@ -8,6 +8,7 @@
#include <libusb-1.0/libusb.h> #include <libusb-1.0/libusb.h>
#define TIMEOUT 0 #define TIMEOUT 0
#define SPI_BUF_SIZE 1024
// comms base class // comms base class
@ -49,3 +50,24 @@ private:
std::vector<uint8_t> recv_buf; std::vector<uint8_t> recv_buf;
void handle_usb_issue(int err, const char func[]); 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,11 +51,14 @@ class CarState(CarStateBase):
else: else:
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["ECMPRDNL2"]["PRNDL2"], None)) ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["ECMPRDNL2"]["PRNDL2"], None))
ret.brake = pt_cp.vl["ECMAcceleratorPos"]["BrakePedalPos"]
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 # 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. # 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 # 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 # https://static.nhtsa.gov/odi/tsbs/2017/MC-10137629-9999.pdf
ret.brake = pt_cp.vl["ECMAcceleratorPos"]["BrakePedalPos"]
ret.brakePressed = ret.brake >= 8 ret.brakePressed = ret.brake >= 8
# Regen braking is braking # Regen braking is braking
@ -154,6 +157,7 @@ class CarState(CarStateBase):
("TractionControlOn", "ESPStatus"), ("TractionControlOn", "ESPStatus"),
("ParkBrake", "VehicleIgnitionAlt"), ("ParkBrake", "VehicleIgnitionAlt"),
("CruiseMainOn", "ECMEngineStatus"), ("CruiseMainOn", "ECMEngineStatus"),
("BrakePressed", "ECMEngineStatus"),
] ]
checks = [ checks = [

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

@ -2,7 +2,7 @@
from cereal import car from cereal import car
from panda import Panda from panda import Panda
from common.conversions import Conversions as CV 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.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 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 from selfdrive.car.interfaces import CarInterfaceBase
@ -44,6 +44,8 @@ class CarInterface(CarInterfaceBase):
# ICE cars do not have 0x130; GEARS message on 0x40 instead # ICE cars do not have 0x130; GEARS message on 0x40 instead
if 0x130 not in fingerprint[4]: if 0x130 not in fingerprint[4]:
ret.flags |= HyundaiFlags.CANFD_ALT_GEARS.value 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.steerActuatorDelay = 0.1 # Default delay
ret.steerLimitTimer = 0.4 ret.steerLimitTimer = 0.4
@ -200,6 +202,10 @@ class CarInterface(CarInterfaceBase):
ret.mass = 3673.0 * CV.LB_TO_KG + STD_CARGO_KG ret.mass = 3673.0 * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.83 ret.wheelbase = 2.83
ret.steerRatio = 12.9 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: elif candidate == CAR.GENESIS_G80:
ret.mass = 2060. + STD_CARGO_KG ret.mass = 2060. + STD_CARGO_KG
ret.wheelbase = 3.01 ret.wheelbase = 3.01
@ -244,6 +250,8 @@ class CarInterface(CarInterfaceBase):
ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2 ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2
if ret.flags & HyundaiFlags.CANFD_ALT_BUTTONS: if ret.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_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: else:
if candidate in LEGACY_SAFETY_MODE_CAR: if candidate in LEGACY_SAFETY_MODE_CAR:
# these cars require a special panda safety mode due to missing counters and checksums in the messages # these cars require a special panda safety mode due to missing counters and checksums in the messages

@ -48,6 +48,7 @@ class HyundaiFlags(IntFlag):
CANFD_HDA2 = 1 CANFD_HDA2 = 1
CANFD_ALT_BUTTONS = 2 CANFD_ALT_BUTTONS = 2
CANFD_ALT_GEARS = 4 CANFD_ALT_GEARS = 4
CANFD_CAMERA_SCC = 8
class CAR: class CAR:
@ -100,6 +101,7 @@ class CAR:
# Genesis # Genesis
GENESIS_G70 = "GENESIS G70 2018" GENESIS_G70 = "GENESIS G70 2018"
GENESIS_G70_2020 = "GENESIS G70 2020" GENESIS_G70_2020 = "GENESIS G70 2020"
GENESIS_GV70_1ST_GEN = "GENESIS GV70 1ST GEN"
GENESIS_G80 = "GENESIS G80 2017" GENESIS_G80 = "GENESIS G80 2017"
GENESIS_G90 = "GENESIS G90 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.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.SONATA_HYBRID: HyundaiCarInfo("Hyundai Sonata Hybrid 2020-22", "All", harness=Harness.hyundai_a),
CAR.IONIQ_5: [ 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), 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), 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 # Genesis
CAR.GENESIS_G70: HyundaiCarInfo("Genesis G70 2018-19", "All", harness=Harness.hyundai_f), CAR.GENESIS_G70: HyundaiCarInfo("Genesis G70 2018-19", "All", harness=Harness.hyundai_f),
CAR.GENESIS_G70_2020: HyundaiCarInfo("Genesis G70 2020", "All", harness=Harness.hyundai_f), CAR.GENESIS_G70_2020: HyundaiCarInfo("Genesis G70 2020", "All", harness=Harness.hyundai_f),
CAR.GENESIS_GV70_1ST_GEN: HyundaiCarInfo("Genesis GV70 2022-23", "All", harness=Harness.hyundai_l),
CAR.GENESIS_G80: HyundaiCarInfo("Genesis G80 2017-19", "All", harness=Harness.hyundai_h), 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), CAR.GENESIS_G90: HyundaiCarInfo("Genesis G90 2017-18", "All", harness=Harness.hyundai_c),
} }
@ -465,6 +468,7 @@ FW_VERSIONS = {
b'\xf1\x82DNBWN5TMDCXXXG2E', b'\xf1\x82DNBWN5TMDCXXXG2E',
b'\xf1\x82DNCVN5GMCCXXXF0A', b'\xf1\x82DNCVN5GMCCXXXF0A',
b'\xf1\x82DNCVN5GMCCXXXG2B', 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\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DNDWN5TMDCXXXJ1A',
b'\xf1\x87391162M003', b'\xf1\x87391162M003',
b'\xf1\x87391162M013', 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\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\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\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): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.02 99211-L1000 190422', 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\x00HT6WA250BLHT6WA910A1SDN8G25NB1\x00\x00\x00\x00\x00\x00\x96\xa1\xf1\x92',
b'\xf1\x00HT6WA280BLHT6WAD10A1SDN8G25NB2\x00\x00\x00\x00\x00\x00\x08\xc9O:', b'\xf1\x00HT6WA280BLHT6WAD10A1SDN8G25NB2\x00\x00\x00\x00\x00\x00\x08\xc9O:',
b'\xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5', 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\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\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', 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.05 99210-CV000 211027',
b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.06 99210-CV000 220328', 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.05 99210-CV000 211027',
b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.06 99210-CV000 220328',
], ],
}, },
CAR.IONIQ_5: { CAR.IONIQ_5: {
@ -1422,6 +1429,14 @@ FW_VERSIONS = {
b'\xf1\x00NQ5__ 1.00 1.03 99110-P1000 ', 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 = { 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}, "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 # The camera does SCC on these cars, rather than the radar
CAMERA_SCC_CAR = {CAR.KONA_EV_2022, } CAMERA_SCC_CAR = {CAR.KONA_EV_2022, }
@ -1499,4 +1517,5 @@ DBC = {
CAR.SANTA_CRUZ_1ST_GEN: dbc_dict('hyundai_canfd', None), CAR.SANTA_CRUZ_1ST_GEN: dbc_dict('hyundai_canfd', None),
CAR.KIA_SPORTAGE_5TH_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.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 MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS
ACCEL_MAX = 2.0 ACCEL_MAX = 2.0
ACCEL_MIN = -3.5 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_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') TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/override.yaml')

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

@ -81,6 +81,7 @@ routes = [
CarTestRoute("6fe86b4e410e4c37|2020-07-22--16-27-13", HYUNDAI.HYUNDAI_GENESIS), CarTestRoute("6fe86b4e410e4c37|2020-07-22--16-27-13", HYUNDAI.HYUNDAI_GENESIS),
CarTestRoute("70c5bec28ec8e345|2020-08-08--12-22-23", HYUNDAI.GENESIS_G70), CarTestRoute("70c5bec28ec8e345|2020-08-08--12-22-23", HYUNDAI.GENESIS_G70),
CarTestRoute("ca4de5b12321bd98|2022-10-18--21-15-59", HYUNDAI.GENESIS_GV70_1ST_GEN),
CarTestRoute("6b301bf83f10aa90|2020-11-22--16-45-07", HYUNDAI.GENESIS_G80), 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("f0709d2bc6ca451f|2022-10-15--08-13-54", HYUNDAI.SANTA_CRUZ_1ST_GEN),
CarTestRoute("4dbd55df87507948|2022-03-01--09-45-38", HYUNDAI.SANTA_FE), 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) self.assertGreater(car_params.maxLateralAccel, 0)
if car_params.steerControlType != car.CarParams.SteerControlType.angle: if car_params.steerControlType != car.CarParams.SteerControlType.angle:
tuning = car_params.lateralTuning.which() tune = car_params.lateralTuning
if tuning == 'pid': if tune.which() == 'pid':
self.assertTrue(len(car_params.lateralTuning.pid.kpV)) self.assertTrue(not math.isnan(tune.pid.kf) and tune.pid.kf > 0)
elif tuning == 'torque': self.assertTrue(len(tune.pid.kpV) > 0 and len(tune.pid.kpV) == len(tune.pid.kpBP))
kf = car_params.lateralTuning.torque.kf self.assertTrue(len(tune.pid.kiV) > 0 and len(tune.pid.kiV) == len(tune.pid.kiBP))
self.assertTrue(not math.isnan(kf) and kf > 0)
self.assertTrue(not math.isnan(car_params.lateralTuning.torque.friction)) elif tune.which() == 'torque':
elif tuning == 'indi': self.assertTrue(not math.isnan(tune.torque.kf) and tune.torque.kf > 0)
self.assertTrue(len(car_params.lateralTuning.indi.outerLoopGainV)) self.assertTrue(not math.isnan(tune.torque.friction))
elif tune.which() == 'indi':
self.assertTrue(len(tune.indi.outerLoopGainV))
# Run car interface # Run car interface
CC = car.CarControl.new_message() 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] HYUNDAI SANTA CRUZ 1ST GEN: [2.7, 2.7, 0.0]
KIA SPORTAGE 5TH 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] 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 # Dashcam or fallback configured as ideal car
mock: [10.0, 10, 0.0] mock: [10.0, 10, 0.0]

@ -15,6 +15,8 @@ class CarState(CarStateBase):
can_define = CANDefine(DBC[CP.carFingerprint]["pt"]) can_define = CANDefine(DBC[CP.carFingerprint]["pt"])
self.shifter_values = can_define.dv["GEAR_PACKET"]["GEAR"] self.shifter_values = can_define.dv["GEAR_PACKET"]["GEAR"]
self.eps_torque_scale = EPS_SCALE[CP.carFingerprint] / 100. 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"] # On cars with cp.vl["STEER_TORQUE_SENSOR"]["STEER_ANGLE"]
# the signal is zeroed to where the steering angle is at start. # 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.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.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 ret.standstill = ret.vEgoRaw == 0

@ -110,6 +110,21 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 14.3 ret.steerRatio = 14.3
tire_stiffness_factor = 0.7933 tire_stiffness_factor = 0.7933
ret.mass = 3585. * CV.LB_TO_KG + STD_CARGO_KG # Average between ICE and Hybrid 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): elif candidate in (CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2):
stop_and_go = True 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: ToyotaCarInfo("Toyota Corolla 2017-19"),
CAR.COROLLA_TSS2: [ CAR.COROLLA_TSS2: [
ToyotaCarInfo("Toyota Corolla 2020-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"), 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"), ToyotaCarInfo("Toyota Corolla Hatchback 2019-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"),
], ],
CAR.COROLLAH_TSS2: [ CAR.COROLLAH_TSS2: [
@ -713,6 +713,7 @@ FW_VERSIONS = {
}, },
CAR.COROLLA_TSS2: { CAR.COROLLA_TSS2: {
(Ecu.engine, 0x700, None): [ (Ecu.engine, 0x700, None): [
b'\x01896630A22000\x00\x00\x00\x00',
b'\x01896630ZG2000\x00\x00\x00\x00', b'\x01896630ZG2000\x00\x00\x00\x00',
b'\x01896630ZG5000\x00\x00\x00\x00', b'\x01896630ZG5000\x00\x00\x00\x00',
b'\x01896630ZG5100\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'F152602191\x00\x00\x00\x00\x00\x00',
b'\x01F152612862\x00\x00\x00\x00\x00\x00', b'\x01F152612862\x00\x00\x00\x00\x00\x00',
b'\x01F152612B91\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): [ (Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F3301100\x00\x00\x00\x00', b'\x018821F3301100\x00\x00\x00\x00',
@ -809,6 +811,7 @@ FW_VERSIONS = {
b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
b'\x028646F1202200\x00\x00\x00\x008646G2601500\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'\x028646F1601100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
b'\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
], ],
}, },
CAR.COROLLAH_TSS2: { CAR.COROLLAH_TSS2: {
@ -1389,6 +1392,7 @@ FW_VERSIONS = {
b'\x02896634A14001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', 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'\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'\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'\x02896634A14001\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00',
b'\x02896634A14101\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) super().__init__(CP)
self.CCP = CarControllerParams(CP) self.CCP = CarControllerParams(CP)
self.button_states = {button.event_type: False for button in self.CCP.BUTTONS} 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): def create_button_events(self, pt_cp, buttons):
button_events = [] button_events = []

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

@ -5,7 +5,7 @@ from common.realtime import DT_MDL
LaneChangeState = log.LateralPlan.LaneChangeState LaneChangeState = log.LateralPlan.LaneChangeState
LaneChangeDirection = log.LateralPlan.LaneChangeDirection 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. LANE_CHANGE_TIME_MAX = 10.
DESIRES = { 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 # friction in the steering wheel that needs to be overcome to
# move it at all, this is compensated for too. # 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): class LatControlTorque(LatControl):
@ -57,8 +58,9 @@ class LatControlTorque(LatControl):
actual_lateral_accel = actual_curvature * CS.vEgo ** 2 actual_lateral_accel = actual_curvature * CS.vEgo ** 2
lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2
setpoint = desired_lateral_accel + LOW_SPEED_FACTOR * desired_curvature low_speed_factor = interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y)**2
measurement = actual_lateral_accel + LOW_SPEED_FACTOR * actual_curvature setpoint = desired_lateral_accel + low_speed_factor * desired_curvature
measurement = actual_lateral_accel + low_speed_factor * actual_curvature
error = setpoint - measurement error = setpoint - measurement
gravity_adjusted_lateral_accel = desired_lateral_accel - params.roll * ACCELERATION_DUE_TO_GRAVITY 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, 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 J_EGO_COST = 5.0
A_CHANGE_COST = 200. A_CHANGE_COST = 200.
DANGER_ZONE_COST = 100. DANGER_ZONE_COST = 100.
CRASH_DISTANCE = .5 CRASH_DISTANCE = .25
LEAD_DANGER_FACTOR = 0.75 LEAD_DANGER_FACTOR = 0.75
LIMIT_COST = 1e6 LIMIT_COST = 1e6
ACADOS_SOLVER_TYPE = 'SQP_RTI' 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_LST = [index_function(idx, max_val=MAX_T, max_idx=N) for idx in range(N+1)]
T_IDXS = np.array(T_IDXS_LST) T_IDXS = np.array(T_IDXS_LST)
FCW_IDXS = T_IDXS < 5.0
T_DIFFS = np.diff(T_IDXS, prepend=[0.]) T_DIFFS = np.diff(T_IDXS, prepend=[0.])
MIN_ACCEL = -3.5 MIN_ACCEL = -3.5
MAX_ACCEL = 2.0 MAX_ACCEL = 2.0
@ -369,7 +370,7 @@ class LongitudinalMpc:
self.params[:,4] = T_FOLLOW self.params[:,4] = T_FOLLOW
self.run() 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): radarstate.leadOne.modelProb > 0.9):
self.crash_cnt += 1 self.crash_cnt += 1
else: else:

@ -58,6 +58,7 @@ class LongitudinalPlanner:
self.a_desired = init_a self.a_desired = init_a
self.v_desired_filter = FirstOrderFilter(init_v, 2.0, DT_MDL) 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.v_desired_trajectory = np.zeros(CONTROL_N)
self.a_desired_trajectory = np.zeros(CONTROL_N) self.a_desired_trajectory = np.zeros(CONTROL_N)
@ -65,15 +66,15 @@ class LongitudinalPlanner:
self.solverExecutionTime = 0.0 self.solverExecutionTime = 0.0
def read_param(self): 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' 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 if (len(model_msg.position.x) == 33 and
len(model_msg.velocity.x) == 33 and len(model_msg.velocity.x) == 33 and
len(model_msg.acceleration.x) == 33): len(model_msg.acceleration.x) == 33):
x = np.interp(T_IDXS_MPC, T_IDXS, model_msg.position.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) 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) a = np.interp(T_IDXS_MPC, T_IDXS, model_msg.acceleration.x)
j = np.zeros(len(T_IDXS_MPC)) j = np.zeros(len(T_IDXS_MPC))
else: else:
@ -112,6 +113,9 @@ class LongitudinalPlanner:
# Prevent divergence, smooth in current v_ego # Prevent divergence, smooth in current v_ego
self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(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 force_slow_decel:
# if required so, force a smooth deceleration # if required so, force a smooth deceleration
@ -124,7 +128,7 @@ class LongitudinalPlanner:
self.mpc.set_weights(prev_accel_constraint) self.mpc.set_weights(prev_accel_constraint)
self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) 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) 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.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) 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) 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 counter is only needed because radar is glitchy, remove once radar is gone
# TODO write fcw in e2e_long mode self.fcw = self.mpc.crash_cnt > 2 and not sm['carState'].standstill
self.fcw = self.mpc.mode == 'acc' and self.mpc.crash_cnt > 5 and not sm['carState'].standstill
if self.fcw: if self.fcw:
cloudlog.info("FCW triggered") cloudlog.info("FCW triggered")

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

@ -27,7 +27,7 @@ class TestFollowingDistance(unittest.TestCase):
def test_following_distance(self): def test_following_distance(self):
params = Params() params = Params()
for e2e in [False, True]: for e2e in [False, True]:
params.put_bool("EndToEndLong", e2e) params.put_bool("ExperimentalMode", e2e)
for speed in np.arange(0, 40, 5): for speed in np.arange(0, 40, 5):
print(f'Testing {speed} m/s') print(f'Testing {speed} m/s')
v_lead = float(speed) 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++) { for (int i=0; i<LEAD_MHP_SELECTION; i++) {
fill_lead(leads[i], net_outputs.leads, i, t_offsets[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, 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); 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 { struct ModelOutputDisengageProb {
float gas_disengage; float gas_disengage;
float brake_disengage; float brake_disengage;
@ -225,6 +233,7 @@ struct ModelOutput {
const ModelOutputMeta meta; const ModelOutputMeta meta;
const ModelOutputPose pose; const ModelOutputPose pose;
const ModelOutputWideFromDeviceEuler wide_from_device_euler; const ModelOutputWideFromDeviceEuler wide_from_device_euler;
const ModelOutputTemporalPose temporal_pose;
}; };
constexpr int OUTPUT_SIZE = sizeof(ModelOutput) / sizeof(float); constexpr int OUTPUT_SIZE = sizeof(ModelOutput) / sizeof(float);

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

@ -34,6 +34,7 @@ class DRIVER_MONITOR_SETTINGS():
self._EE_THRESH11 = 0.275 self._EE_THRESH11 = 0.275
self._EE_THRESH12 = 5.5 self._EE_THRESH12 = 5.5
self._EE_MAX_OFFSET1 = 0.06 self._EE_MAX_OFFSET1 = 0.06
self._EE_MIN_OFFSET1 = 0.025
self._EE_THRESH21 = 0.01 self._EE_THRESH21 = 0.01
self._EE_THRESH22 = 0.35 self._EE_THRESH22 = 0.35
@ -206,7 +207,7 @@ class DriverStatus():
distracted_types.append(DistractedType.DISTRACTED_BLINK) distracted_types.append(DistractedType.DISTRACTED_BLINK)
if self.ee1_calibrated: 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: else:
ee1_dist = self.eev1 > self.settings._EE_THRESH11 ee1_dist = self.eev1 > self.settings._EE_THRESH11
# if self.ee2_calibrated: # if self.ee2_calibrated:

@ -143,11 +143,11 @@ def run_maneuver_worker(k):
params = Params() params = Params()
man = maneuvers[k] man = maneuvers[k]
params.put_bool("EndToEndLong", True) params.put_bool("ExperimentalMode", True)
print(man.title, ' in e2e mode') print(man.title, ' in e2e mode')
valid, _ = man.evaluate() valid, _ = man.evaluate()
self.assertTrue(valid, msg=man.title) self.assertTrue(valid, msg=man.title)
params.put_bool("EndToEndLong", False) params.put_bool("ExperimentalMode", False)
print(man.title, ' in acc mode') print(man.title, ' in acc mode')
valid, _ = man.evaluate() valid, _ = man.evaluate()
self.assertTrue(valid, msg=man.title) 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 = [ source_segments = [
("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY ("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY
("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.SONATA ("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) ("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.PRIUS (INDI)
("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.RAV4 (LQR) ("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.RAV4 (LQR)
("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2 ("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2
@ -41,7 +41,7 @@ source_segments = [
segments = [ segments = [
("BODY", "regenFA002A80700|2022-09-27--15-37-02--0"), ("BODY", "regenFA002A80700|2022-09-27--15-37-02--0"),
("HYUNDAI", "regenBE53A59065B|2022-09-27--16-52-03--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"), ("TOYOTA", "regen929C5790007|2022-09-27--16-27-47--0"),
("TOYOTA2", "regenEA3950D7F22|2022-09-27--15-43-24--0"), ("TOYOTA2", "regenEA3950D7F22|2022-09-27--15-43-24--0"),
("TOYOTA3", "regen89026F6BD8D|2022-09-27--15-45-37--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", "../assets/offroad/icon_openpilot.png",
false, 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", "IsLdwEnabled",
tr("Enable Lane Departure Warnings"), tr("Enable Lane Departure Warnings"),
@ -59,25 +75,11 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
}, },
{ {
"DisengageOnAccelerator", "DisengageOnAccelerator",
tr("Disengage On Accelerator Pedal"), tr("Disengage on Accelerator Pedal"),
tr("When enabled, pressing the accelerator pedal will disengage openpilot."), tr("When enabled, pressing the accelerator pedal will disengage openpilot."),
"../assets/offroad/icon_disengage_on_accelerator.svg", "../assets/offroad/icon_disengage_on_accelerator.svg",
false, 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 #ifdef ENABLE_MAPS
{ {
"NavSettingTime24h", "NavSettingTime24h",
@ -116,9 +118,15 @@ void TogglesPanel::showEvent(QShowEvent *event) {
} }
void TogglesPanel::updateToggles() { void TogglesPanel::updateToggles() {
auto e2e_toggle = toggles["EndToEndLong"]; auto e2e_toggle = toggles["ExperimentalMode"];
auto op_long_toggle = toggles["ExperimentalLongitudinalEnabled"]; 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"); auto cp_bytes = params.get("CarParamsPersistent");
if (!cp_bytes.empty()) { if (!cp_bytes.empty()) {
@ -140,7 +148,7 @@ void TogglesPanel::updateToggles() {
} else { } else {
// no long for now // no long for now
e2e_toggle->setEnabled(false); 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 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."); 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"}); pm = std::make_unique<PubMaster, const std::initializer_list<const char *>>({"uiDebug"});
engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size}); 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) { 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.setPen(Qt::NoPen);
p.setBrush(bg); p.setBrush(bg);
p.drawEllipse(x - radius / 2, y - radius / 2, radius, radius); p.drawEllipse(x - radius / 2, y - radius / 2, radius, radius);
@ -460,37 +461,26 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) {
// paint path // paint path
QLinearGradient bg(0, height(), 0, height() / 4); QLinearGradient bg(0, height(), 0, height() / 4);
float start_hue, end_hue; float start_hue, end_hue;
if (scene.end_to_end_long) { if (scene.experimental_mode) {
const auto &acceleration = (*s->sm)["modelV2"].getModelV2().getAcceleration(); const auto &acceleration = (*s->sm)["modelV2"].getModelV2().getAcceleration();
float acceleration_future = 0; float acceleration_future = 0;
if (acceleration.getZ().size() > 10 && (*s->sm)["carControl"].getCarControl().getLongActive()) { if (acceleration.getZ().size() > 16) {
acceleration_future = acceleration.getX()[10]; // 1.0 second acceleration_future = acceleration.getX()[16]; // 2.5 seconds
} }
// speed up: 148, slow down: 0 start_hue = 60;
start_hue = fmax(fmin(60 + accel_filter.update(acceleration_future) * 80, 148), 0); // 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
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));
// FIXME: painter.drawPolygon can be slow if hue is not rounded // FIXME: painter.drawPolygon can be slow if hue is not rounded
end_hue = int(end_hue * 100 + 0.5) / 100; 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(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)); 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); painter.setBrush(bg);
@ -565,6 +555,7 @@ void AnnotatedCameraWidget::paintGL() {
} else if (v_ego > 15) { } else if (v_ego > 15) {
wide_cam_requested = false; wide_cam_requested = false;
} }
wide_cam_requested = wide_cam_requested && s->scene.experimental_mode;
// TODO: also detect when ecam vision stream isn't available // TODO: also detect when ecam vision stream isn't available
// for replay of old routes, never go to widecam // for replay of old routes, never go to widecam
wide_cam_requested = wide_cam_requested && s->scene.calibration_wide_valid; wide_cam_requested = wide_cam_requested && s->scene.calibration_wide_valid;

@ -87,7 +87,6 @@ protected:
double prev_draw_t = 0; double prev_draw_t = 0;
FirstOrderFilter fps_filter; FirstOrderFilter fps_filter;
FirstOrderFilter accel_filter;
}; };
// container for all onroad widgets // 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) { 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(); key = param.toStdString();
QObject::connect(this, &ParamControl::toggleFlipped, [=](bool state) { QObject::connect(this, &ParamControl::toggleFlipped, [=](bool state) {
QString content("<body><h2>" + title + "</h2><br><br>" QString content("<body><h2 style=\"text-align: center;\">" + title + "</h2><br>"
"<p style=\"text-align: center; margin: 0 128px;\">" + getDescription() + "</p></body>"); "<p style=\"text-align: center; margin: 0 128px; font-size: 50px;\">" + getDescription() + "</p></body>");
ConfirmationDialog dialog(content, tr("Ok"), tr("Cancel"), true, this); ConfirmationDialog dialog(content, tr("Enable"), tr("Cancel"), true, this);
if (!confirm || !state || dialog.exec()) { if (!confirm || !state || dialog.exec()) {
params.putBool(key, state); params.putBool(key, state);
} else { } else {

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

@ -2,19 +2,22 @@
import json import json
import os import os
import requests import requests
import xml.etree.ElementTree as ET
from common.basedir import BASEDIR 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 from selfdrive.ui.update_translations import LANGUAGES_FILE, TRANSLATIONS_DIR
TRANSLATION_TAG = "<translation" TRANSLATION_TAG = "<translation"
UNFINISHED_TRANSLATION_TAG = "<translation type=\"unfinished\""
BADGE_HEIGHT = 20 + 8 BADGE_HEIGHT = 20 + 8
SHIELDS_URL = "https://img.shields.io/badge"
if __name__ == "__main__": if __name__ == "__main__":
with open(LANGUAGES_FILE, "r") as f: with open(LANGUAGES_FILE, "r") as f:
translation_files = json.load(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()): for idx, (name, file) in enumerate(translation_files.items()):
with open(os.path.join(TRANSLATIONS_DIR, f"{file}.ts"), "r") as tr_f: with open(os.path.join(TRANSLATIONS_DIR, f"{file}.ts"), "r") as tr_f:
tr_file = tr_f.read() tr_file = tr_f.read()
@ -28,19 +31,30 @@ if __name__ == "__main__":
unfinished_translations += 1 unfinished_translations += 1
percent_finished = int(100 - (unfinished_translations / total_translations * 100.)) 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" assert r.status_code == 200, "Error downloading badge"
content_svg = r.content.decode("utf-8") 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"): for tag in ("r", "s"):
content_svg = content_svg.replace(f'id="{tag}"', f'id="{tag}{idx}"') content_svg = content_svg.replace(f'id="{tag}"', f'id="{tag}{idx}"')
content_svg = content_svg.replace(f'"url(#{tag})"', f'"url(#{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.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>") badge_svg.append("</svg>")
with open(os.path.join(BASEDIR, "translation_badge.svg"), "w") as badge_f: with open(os.path.join(BASEDIR, "translation_badge.svg"), "w") as badge_f:

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

@ -474,12 +474,12 @@ location set</source>
<context> <context>
<name>ParamControl</name> <name>ParamControl</name>
<message> <message>
<source>Ok</source> <source>Cancel</source>
<translation type="unfinished">OK</translation> <translation></translation>
</message> </message>
<message> <message>
<source>Cancel</source> <source>Enable</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
</context> </context>
<context> <context>
@ -967,21 +967,9 @@ location set</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>🌮 End-to-end longitudinal (extremely alpha) 🌮</source> <source>Experimental openpilot Longitudinal Control</source>
<translation>🌮 (α) 🌮</translation>
</message>
<message>
<source>Experimental openpilot longitudinal control</source>
<translation>openpilotによるアクセル制御</translation> <translation>openpilotによるアクセル制御</translation>
</message> </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> <message>
<source>openpilot longitudinal control is not currently available for this car.</source> <source>openpilot longitudinal control is not currently available for this car.</source>
<translation>openpilotによるアクセル制御は</translation> <translation>openpilotによるアクセル制御は</translation>
@ -991,7 +979,7 @@ location set</source>
<translation>使openpilotによるアクセル制御</translation> <translation>使openpilotによるアクセル制御</translation>
</message> </message>
<message> <message>
<source>Disengage On Accelerator Pedal</source> <source>Disengage on Accelerator Pedal</source>
<translation> openpilot </translation> <translation> openpilot </translation>
</message> </message>
<message> <message>
@ -1014,6 +1002,22 @@ location set</source>
<source>Show map on left side when in split screen view.</source> <source>Show map on left side when in split screen view.</source>
<translation></translation> <translation></translation>
</message> </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>
<context> <context>
<name>Updater</name> <name>Updater</name>

@ -240,11 +240,11 @@
</message> </message>
<message> <message>
<source>Reset</source> <source>Reset</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Review</source> <source>Review</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
</context> </context>
<context> <context>
@ -474,12 +474,12 @@ location set</source>
<context> <context>
<name>ParamControl</name> <name>ParamControl</name>
<message> <message>
<source>Ok</source> <source>Cancel</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Cancel</source> <source>Enable</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
</context> </context>
<context> <context>
@ -864,7 +864,7 @@ location set</source>
</message> </message>
<message> <message>
<source>Uninstall</source> <source>Uninstall</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
</context> </context>
<context> <context>
@ -967,21 +967,9 @@ location set</source>
<translation> .</translation> <translation> .</translation>
</message> </message>
<message> <message>
<source>🌮 End-to-end longitudinal (extremely alpha) 🌮</source> <source>Experimental openpilot Longitudinal Control</source>
<translation>🌮 e2e ( ) 🌮 </translation>
</message>
<message>
<source>Experimental openpilot longitudinal control</source>
<translation>openpilot ()</translation> <translation>openpilot ()</translation>
</message> </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> <message>
<source>openpilot longitudinal control is not currently available for this car.</source> <source>openpilot longitudinal control is not currently available for this car.</source>
<translation> openpilot .</translation> <translation> openpilot .</translation>
@ -991,7 +979,7 @@ location set</source>
<translation>openpilot . ()</translation> <translation>openpilot . ()</translation>
</message> </message>
<message> <message>
<source>Disengage On Accelerator Pedal</source> <source>Disengage on Accelerator Pedal</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
@ -1014,6 +1002,22 @@ location set</source>
<source>Show map on left side when in split screen view.</source> <source>Show map on left side when in split screen view.</source>
<translation> .</translation> <translation> .</translation>
</message> </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>
<context> <context>
<name>Updater</name> <name>Updater</name>
@ -1070,7 +1074,7 @@ location set</source>
</message> </message>
<message> <message>
<source>Forget</source> <source>Forget</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
</context> </context>
</TS> </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> <translation>Upload gegevens van de bestuurders camera en help het algoritme voor het monitoren van de bestuurder te verbeteren.</translation>
</message> </message>
<message> <message>
<source>Disengage On Accelerator Pedal</source> <source>Disengage on Accelerator Pedal</source>
<translation>Deactiveren Met Gaspedaal</translation> <translation>Deactiveren Met Gaspedaal</translation>
</message> </message>
<message> <message>
@ -1043,7 +1043,7 @@ ingesteld</translation>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Experimental openpilot longitudinal control</source> <source>Experimental openpilot Longitudinal Control</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<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> <translation>Prześlij dane z kamery skierowanej na kierowcę i pomóż poprawiać algorytm monitorowania kierowcy.</translation>
</message> </message>
<message> <message>
<source>Disengage On Accelerator Pedal</source> <source>Disengage on Accelerator Pedal</source>
<translation>Odłącz poprzez naciśnięcie gazu</translation> <translation>Odłącz poprzez naciśnięcie gazu</translation>
</message> </message>
<message> <message>
@ -1047,7 +1047,7 @@ nie zostało ustawione</translation>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Experimental openpilot longitudinal control</source> <source>Experimental openpilot Longitudinal Control</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>

@ -240,11 +240,11 @@
</message> </message>
<message> <message>
<source>Reset</source> <source>Reset</source>
<translation type="unfinished"></translation> <translation>Resetar</translation>
</message> </message>
<message> <message>
<source>Review</source> <source>Review</source>
<translation type="unfinished"></translation> <translation>Revisar</translation>
</message> </message>
</context> </context>
<context> <context>
@ -475,12 +475,12 @@ trabalho definido</translation>
<context> <context>
<name>ParamControl</name> <name>ParamControl</name>
<message> <message>
<source>Ok</source> <source>Cancel</source>
<translation type="unfinished">OK</translation> <translation>Cancelar</translation>
</message> </message>
<message> <message>
<source>Cancel</source> <source>Enable</source>
<translation type="unfinished">Cancelar</translation> <translation>Ativar</translation>
</message> </message>
</context> </context>
<context> <context>
@ -868,7 +868,7 @@ trabalho definido</translation>
</message> </message>
<message> <message>
<source>Uninstall</source> <source>Uninstall</source>
<translation type="unfinished"></translation> <translation>Desinstalar</translation>
</message> </message>
</context> </context>
<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> <translation>Upload dados da câmera voltada para o motorista e ajude a melhorar o algoritmo de monitoramentor.</translation>
</message> </message>
<message> <message>
<source>🌮 End-to-end longitudinal (extremely alpha) 🌮</source> <source>Experimental openpilot Longitudinal Control</source>
<translation>🌮 End-to-end longitudinal (experimental) 🌮</translation>
</message>
<message>
<source>Experimental openpilot longitudinal control</source>
<translation>Controle longitudinal experimental openpilot</translation> <translation>Controle longitudinal experimental openpilot</translation>
</message> </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> <message>
<source>openpilot longitudinal control is not currently available for this car.</source> <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> <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> <translation>Habilite o controle longitudinal experimental para habilitar isso.</translation>
</message> </message>
<message> <message>
<source>Disengage On Accelerator Pedal</source> <source>Disengage on Accelerator Pedal</source>
<translation>Desacionar Com Pedal Do Acelerador</translation> <translation>Desacionar Com Pedal Do Acelerador</translation>
</message> </message>
<message> <message>
@ -1018,6 +1006,22 @@ trabalho definido</translation>
<source>Show map on left side when in split screen view.</source> <source>Show map on left side when in split screen view.</source>
<translation>Exibir mapa do lado esquerdo quando a tela for dividida.</translation> <translation>Exibir mapa do lado esquerdo quando a tela for dividida.</translation>
</message> </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>
<context> <context>
<name>Updater</name> <name>Updater</name>
@ -1074,7 +1078,7 @@ trabalho definido</translation>
</message> </message>
<message> <message>
<source>Forget</source> <source>Forget</source>
<translation type="unfinished"></translation> <translation>Esquecer</translation>
</message> </message>
</context> </context>
</TS> </TS>

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

@ -472,12 +472,12 @@ location set</source>
<context> <context>
<name>ParamControl</name> <name>ParamControl</name>
<message> <message>
<source>Ok</source> <source>Cancel</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Cancel</source> <source>Enable</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
</context> </context>
<context> <context>
@ -965,21 +965,9 @@ location set</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>🌮 End-to-end longitudinal (extremely alpha) 🌮</source> <source>Experimental openpilot Longitudinal Control</source>
<translation>🌮 🌮</translation>
</message>
<message>
<source>Experimental openpilot longitudinal control</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </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> <message>
<source>openpilot longitudinal control is not currently available for this car.</source> <source>openpilot longitudinal control is not currently available for this car.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@ -989,7 +977,7 @@ location set</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Disengage On Accelerator Pedal</source> <source>Disengage on Accelerator Pedal</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@ -1012,6 +1000,22 @@ location set</source>
<source>Show map on left side when in split screen view.</source> <source>Show map on left side when in split screen view.</source>
<translation></translation> <translation></translation>
</message> </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>
<context> <context>
<name>Updater</name> <name>Updater</name>

@ -474,12 +474,12 @@ location set</source>
<context> <context>
<name>ParamControl</name> <name>ParamControl</name>
<message> <message>
<source>Ok</source> <source>Cancel</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Cancel</source> <source>Enable</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
</context> </context>
<context> <context>
@ -967,21 +967,9 @@ location set</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>🌮 End-to-end longitudinal (extremely alpha) 🌮</source> <source>Experimental openpilot Longitudinal Control</source>
<translation>🌮 🌮</translation>
</message>
<message>
<source>Experimental openpilot longitudinal control</source>
<translation>使 openpilot </translation> <translation>使 openpilot </translation>
</message> </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> <message>
<source>openpilot longitudinal control is not currently available for this car.</source> <source>openpilot longitudinal control is not currently available for this car.</source>
<translation>openpilot </translation> <translation>openpilot </translation>
@ -991,7 +979,7 @@ location set</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Disengage On Accelerator Pedal</source> <source>Disengage on Accelerator Pedal</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@ -1014,6 +1002,22 @@ location set</source>
<source>Show map on left side when in split screen view.</source> <source>Show map on left side when in split screen view.</source>
<translation></translation> <translation></translation>
</message> </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>
<context> <context>
<name>Updater</name> <name>Updater</name>

@ -174,7 +174,7 @@ void ui_update_params(UIState *s) {
auto params = Params(); auto params = Params();
s->scene.is_metric = params.getBool("IsMetric"); s->scene.is_metric = params.getBool("IsMetric");
s->scene.map_on_left = params.getBool("NavSettingLeftSide"); 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() { void UIState::updateStatus() {
@ -214,7 +214,7 @@ void UIState::updateStatus() {
UIState::UIState(QObject *parent) : QObject(parent) { UIState::UIState(QObject *parent) : QObject(parent) {
sm = std::make_unique<SubMaster, const std::initializer_list<const char *>>({ 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", "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman",
"wideRoadCameraState", "managerState", "navInstruction", "navRoute", "gnssMeasurements", "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "gnssMeasurements",
}); });

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

@ -12,6 +12,7 @@
#include "common/util.h" #include "common/util.h"
#define FRAME_BUF_COUNT 4 #define FRAME_BUF_COUNT 4
#define ANALOG_GAIN_MAX_CNT 55
class CameraState { class CameraState {
public: public:
@ -36,7 +37,7 @@ public:
float dc_gain_on_grey; float dc_gain_on_grey;
float dc_gain_off_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_min_idx;
int analog_gain_max_idx; int analog_gain_max_idx;
int analog_gain_rec_idx; int analog_gain_rec_idx;

@ -68,11 +68,16 @@ 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()} 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()} diffs = {frame_id: (max(ts) - min(ts))/1e6 for frame_id, ts in frame_times.items()}
def get_desc(fid, diff): def get_desc(fid, diff):
cam_times = [(m.which(), getattr(m, m.which()).timestampSof/1e6) for m in self.log_by_frame_id[fid]] 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]} laggy_frames = {k: get_desc(k, v) for k, v in diffs.items() if v > LAG_FRAME_TOLERANCE[sensor_type]}
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=}" assert len(laggy_frames) == 0, f"Frames not synced properly: {laggy_frames=}"
if __name__ == "__main__": if __name__ == "__main__":

@ -431,6 +431,9 @@ class Tici(HardwareBase):
def initialize_hardware(self): def initialize_hardware(self):
self.amplifier.initialize_configuration() 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 # Allow thermald to write engagement status to kmsg
os.system("sudo chmod a+w /dev/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 ```bash
$ ./cabana -h $ ./cabana -h
Usage: ./_cabana [options] route Usage: ./cabana [options] route
Options: Options:
-h, --help Displays this help. -h, --help Displays this help.

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

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

@ -1,6 +1,5 @@
#include "tools/cabana/canmessages.h" #include "tools/cabana/canmessages.h"
#include <QDebug>
#include <QSettings> #include <QSettings>
#include "tools/cabana/dbcmanager.h" #include "tools/cabana/dbcmanager.h"
@ -9,7 +8,6 @@ CANMessages *can = nullptr;
CANMessages::CANMessages(QObject *parent) : QObject(parent) { CANMessages::CANMessages(QObject *parent) : QObject(parent) {
can = this; can = this;
QObject::connect(this, &CANMessages::received, this, &CANMessages::process, Qt::QueuedConnection); QObject::connect(this, &CANMessages::received, this, &CANMessages::process, Qt::QueuedConnection);
QObject::connect(&settings, &Settings::changed, this, &CANMessages::settingChanged); 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) { 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 = new Replay(route, {"can", "roadEncodeIdx", "carParams"}, {}, nullptr, use_qcam ? REPLAY_FLAG_QCAMERA : 0, data_dir, this);
replay->setSegmentCacheLimit(settings.cached_segment_limit); replay->setSegmentCacheLimit(settings.cached_segment_limit);
replay->installEventFilter(event_filter, this); replay->installEventFilter(event_filter, this);
QObject::connect(replay, &Replay::segmentsMerged, this, &CANMessages::eventsMerged); QObject::connect(replay, &Replay::segmentsMerged, this, &CANMessages::eventsMerged);
QObject::connect(replay, &Replay::streamStarted, this, &CANMessages::streamStarted);
if (replay->load()) { if (replay->load()) {
replay->start(); replay->start();
return true; return true;
@ -40,12 +38,9 @@ QList<QPointF> CANMessages::findSignalValues(const QString &id, const Signal *si
auto evts = events(); auto evts = events();
if (!evts) return {}; if (!evts) return {};
auto l = id.split(':');
int bus = l[0].toInt();
uint32_t address = l[1].toUInt(nullptr, 16);
QList<QPointF> ret; QList<QPointF> ret;
ret.reserve(max_count); ret.reserve(max_count);
auto [bus, address] = DBCManager::parseId(id);
for (auto &evt : *evts) { for (auto &evt : *evts) {
if (evt->which != cereal::Event::Which::CAN) continue; 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) { for (auto it = messages->begin(); it != messages->end(); ++it) {
can_msgs[it.key()] = it.value(); can_msgs[it.key()] = it.value();
} }
delete messages;
emit updated(); emit updated();
emit msgsReceived(messages);
delete messages;
} }
bool CANMessages::eventFilter(const Event *event) { bool CANMessages::eventFilter(const Event *event) {
@ -101,10 +97,9 @@ bool CANMessages::eventFilter(const Event *event) {
data.bus_time = c.getBusTime(); data.bus_time = c.getBusTime();
data.dat.append((char *)c.getDat().begin(), c.getDat().size()); data.dat.append((char *)c.getDat().begin(), c.getDat().size());
auto &count = counters[id]; data.count = ++counters[id];
data.count = ++count;
if (double delta = (current_sec - counters_begin_sec); delta > 0) { if (double delta = (current_sec - counters_begin_sec); delta > 0) {
data.freq = count / delta; data.freq = data.count / delta;
} }
(*new_msgs)[id] = data; (*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); QList<QPointF> findSignalValues(const QString&id, const Signal* signal, double value, FindFlags flag, int max_count);
bool eventFilter(const Event *event); bool eventFilter(const Event *event);
inline QString route() const { return routeName; } inline QString route() const { return replay->route()->name(); }
inline QString carFingerprint() const { return replay->carFingerprint().c_str(); } inline QString carFingerprint() const { return replay->carFingerprint().c_str(); }
inline double totalSeconds() const { return replay->totalSeconds(); } inline double totalSeconds() const { return replay->totalSeconds(); }
inline double routeStartTime() const { return replay->routeStartTime() / (double)1e9; } inline double routeStartTime() const { return replay->routeStartTime() / (double)1e9; }
@ -47,8 +47,10 @@ public:
inline const std::vector<std::tuple<int, int, TimelineType>> getTimeline() { return replay->getTimeline(); } inline const std::vector<std::tuple<int, int, TimelineType>> getTimeline() { return replay->getTimeline(); }
signals: signals:
void streamStarted();
void eventsMerged(); void eventsMerged();
void updated(); void updated();
void msgsReceived(const QHash<QString, CanData> *);
void received(QHash<QString, CanData> *); void received(QHash<QString, CanData> *);
public: public:
@ -58,9 +60,7 @@ protected:
void process(QHash<QString, CanData> *); void process(QHash<QString, CanData> *);
void settingChanged(); void settingChanged();
QString routeName;
Replay *replay = nullptr; Replay *replay = nullptr;
std::mutex lock; std::mutex lock;
std::atomic<double> counters_begin_sec = 0; std::atomic<double> counters_begin_sec = 0;
QHash<QString, uint32_t> counters; QHash<QString, uint32_t> counters;

@ -2,12 +2,13 @@
#include <QFutureSynchronizer> #include <QFutureSynchronizer>
#include <QGraphicsLayout> #include <QGraphicsLayout>
#include <QGridLayout>
#include <QRubberBand> #include <QRubberBand>
#include <QTimer> #include <QTimer>
#include <QToolButton>
#include <QtCharts/QLineSeries> #include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis> #include <QtCharts/QValueAxis>
#include <QtConcurrent> #include <QtConcurrent>
#include <QToolBar>
// ChartsWidget // ChartsWidget
@ -15,41 +16,24 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this); QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0); main_layout->setContentsMargins(0, 0, 0, 0);
// title bar // toolbar
title_bar = new QWidget(this); QToolBar *toolbar = new QToolBar(tr("Charts"), this);
title_bar->setVisible(false); title_label = new QLabel();
QHBoxLayout *title_layout = new QHBoxLayout(title_bar); title_label->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred);
title_layout->setContentsMargins(0, 0, 0, 0); toolbar->addWidget(title_label);
title_label = new QLabel(tr("Charts")); toolbar->addWidget(range_label = new QLabel());
reset_zoom_btn = toolbar->addAction("");
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);
reset_zoom_btn->setToolTip(tr("Reset zoom (drag on chart to zoom X-Axis)")); reset_zoom_btn->setToolTip(tr("Reset zoom (drag on chart to zoom X-Axis)"));
title_layout->addWidget(reset_zoom_btn); remove_all_btn = toolbar->addAction("");
remove_all_btn = new QPushButton("", this);
remove_all_btn->setToolTip(tr("Remove all charts")); remove_all_btn->setToolTip(tr("Remove all charts"));
remove_all_btn->setFixedSize(30, 30); dock_btn = toolbar->addAction("");
title_layout->addWidget(remove_all_btn); main_layout->addWidget(toolbar);
updateToolBar();
dock_btn = new QPushButton();
dock_btn->setFixedSize(30, 30);
title_layout->addWidget(dock_btn);
main_layout->addWidget(title_bar, 0, Qt::AlignTop);
// charts // charts
QWidget *charts_container = new QWidget(this); QWidget *charts_container = new QWidget(this);
QVBoxLayout *charts_main = new QVBoxLayout(charts_container); charts_layout = new QVBoxLayout(charts_container);
charts_layout = new QVBoxLayout(); charts_layout->addStretch();
charts_main->addLayout(charts_layout);
charts_main->addStretch();
QScrollArea *charts_scroll = new QScrollArea(this); QScrollArea *charts_scroll = new QScrollArea(this);
charts_scroll->setWidgetResizable(true); 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::DBCFileChanged, [this]() { removeAll(nullptr); });
QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartsWidget::removeAll); QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartsWidget::removeAll);
QObject::connect(dbc(), &DBCManager::signalUpdated, this, &ChartsWidget::signalUpdated); 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) { 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::eventsMerged, this, &ChartsWidget::eventsMerged);
QObject::connect(can, &CANMessages::updated, this, &ChartsWidget::updateState); QObject::connect(can, &CANMessages::updated, this, &ChartsWidget::updateState);
QObject::connect(remove_all_btn, &QPushButton::clicked, [this]() { removeAll(); }); QObject::connect(remove_all_btn, &QAction::triggered, [this]() { removeAll(); });
QObject::connect(reset_zoom_btn, &QPushButton::clicked, this, &ChartsWidget::zoomReset); QObject::connect(reset_zoom_btn, &QAction::triggered, this, &ChartsWidget::zoomReset);
QObject::connect(dock_btn, &QPushButton::clicked, [this]() { QObject::connect(dock_btn, &QAction::triggered, [this]() {
emit dock(!docking); emit dock(!docking);
docking = !docking; docking = !docking;
updateTitleBar(); updateToolBar();
}); });
} }
@ -92,7 +80,7 @@ void ChartsWidget::eventsMerged() {
void ChartsWidget::zoomIn(double min, double max) { void ChartsWidget::zoomIn(double min, double max) {
zoomed_range = {min, max}; zoomed_range = {min, max};
is_zoomed = zoomed_range != display_range; is_zoomed = zoomed_range != display_range;
updateTitleBar(); updateToolBar();
emit rangeChanged(min, max, is_zoomed); emit rangeChanged(min, max, is_zoomed);
updateState(); updateState();
} }
@ -120,27 +108,22 @@ void ChartsWidget::updateState() {
if (prev_range != display_range) { if (prev_range != display_range) {
QFutureSynchronizer<void> future_synchronizer; QFutureSynchronizer<void> future_synchronizer;
for (auto c : charts) 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; const auto &range = is_zoomed ? zoomed_range : display_range;
for (auto c : charts) { for (auto c : charts) {
c->chart_view->setRange(range.first, range.second); c->setRange(range.first, range.second);
c->chart_view->updateLineMarker(current_sec); c->updateLineMarker(current_sec);
} }
} }
void ChartsWidget::updateTitleBar() { void ChartsWidget::updateToolBar() {
title_bar->setVisible(!charts.isEmpty()); remove_all_btn->setEnabled(!charts.isEmpty());
if (charts.isEmpty()) return;
range_label->setVisible(is_zoomed);
reset_zoom_btn->setEnabled(is_zoomed); reset_zoom_btn->setEnabled(is_zoomed);
if (is_zoomed) { range_label->setText(is_zoomed ? tr("%1 - %2").arg(zoomed_range.first, 0, 'f', 2).arg(zoomed_range.second, 0, 'f', 2) : "");
range_label->setText(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"));
}
title_label->setText(tr("Charts (%1)").arg(charts.size()));
dock_btn->setText(docking ? "" : ""); dock_btn->setText(docking ? "" : "");
dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); 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 (it != charts.end()) {
if (!show) removeChart((*it)); if (!show) removeChart((*it));
} else if (show) { } else if (show) {
auto chart = new ChartWidget(id, sig, this); auto chart = new ChartView(id, sig, this);
chart->chart_view->updateSeries(display_range); chart->updateSeries(display_range);
QObject::connect(chart, &ChartWidget::remove, [=]() { removeChart(chart); }); QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); });
QObject::connect(chart->chart_view, &ChartView::zoomIn, this, &ChartsWidget::zoomIn); QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn);
QObject::connect(chart->chart_view, &ChartView::zoomReset, this, &ChartsWidget::zoomReset); QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset);
charts_layout->insertWidget(0, chart); charts_layout->insertWidget(0, chart);
charts.push_back(chart); charts.push_back(chart);
emit chartOpened(chart->id, chart->signal); emit chartOpened(chart->id, chart->signal);
updateState(); updateState();
} }
updateTitleBar(); updateToolBar();
} }
bool ChartsWidget::isChartOpened(const QString &id, const Signal *sig) { 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(); return it != charts.end();
} }
void ChartsWidget::removeChart(ChartWidget *chart) { void ChartsWidget::removeChart(ChartView *chart) {
charts.removeOne(chart); charts.removeOne(chart);
chart->deleteLater(); chart->deleteLater();
updateTitleBar(); updateToolBar();
emit chartClosed(chart->id, chart->signal); emit chartClosed(chart->id, chart->signal);
} }
void ChartsWidget::removeAll(const Signal *sig) { void ChartsWidget::removeAll(const Signal *sig) {
QMutableListIterator<ChartWidget *> it(charts); for (auto c : charts.toVector())
while (it.hasNext()) { if (!sig || c->signal == sig) removeChart(c);
auto c = it.next();
if (sig == nullptr || c->signal == sig) {
c->deleteLater();
emit chartClosed(c->id, c->signal);
it.remove();
}
}
updateTitleBar();
} }
void ChartsWidget::signalUpdated(const Signal *sig) { void ChartsWidget::signalUpdated(const Signal *sig) {
for (auto c : charts) { for (auto c : charts) {
if (c->signal == sig) { if (c->signal == sig) {
c->updateTitle(); c->updateTitle();
c->chart_view->updateSeries(display_range); c->updateSeries(display_range);
c->chart_view->setRange(display_range.first, display_range.second, true); c->setRange(display_range.first, display_range.second, true);
} }
} }
} }
bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) { bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) {
if (obj != this && event->type() == QEvent::Close) { if (obj != this && event->type() == QEvent::Close) {
emit dock_btn->clicked(); emit dock_btn->triggered();
return true; return true;
} }
return false; 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::ChartView(const QString &id, const Signal *sig, QWidget *parent) 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->addSeries(series);
chart->createDefaultAxes(); chart->createDefaultAxes();
chart->legend()->hide(); chart->legend()->hide();
chart->setMargins({0, 0, 0, 0});
chart->layout()->setContentsMargins(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 = new QGraphicsLineItem(chart);
track_line->setZValue(chart->zValue() + 10);
track_line->setPen(QPen(Qt::darkGray, 1, Qt::DashLine)); track_line->setPen(QPen(Qt::darkGray, 1, Qt::DashLine));
track_ellipse = new QGraphicsEllipseItem(chart); track_ellipse = new QGraphicsEllipseItem(chart);
track_ellipse->setZValue(chart->zValue() + 10);
track_ellipse->setBrush(Qt::darkGray); track_ellipse->setBrush(Qt::darkGray);
value_text = new QGraphicsTextItem(chart); value_text = new QGraphicsTextItem(chart);
value_text->setZValue(chart->zValue() + 10); item_group = scene()->createItemGroup({track_line, track_ellipse, value_text});
line_marker = new QGraphicsLineItem(chart); item_group->setZValue(chart->zValue() + 10);
line_marker->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); setChart(chart);
setRenderHint(QPainter::Antialiasing); setRenderHint(QPainter::Antialiasing);
setRubberBand(QChartView::HorizontalRubberBand); setRubberBand(QChartView::HorizontalRubberBand);
if (auto rubber = findChild<QRubberBand *>()) { updateFromSettings();
QPalette pal; updateTitle();
pal.setBrush(QPalette::Base, QColor(0, 0, 0, 80));
rubber->setPalette(pal);
}
QTimer *timer = new QTimer(this); QTimer *timer = new QTimer(this);
timer->setInterval(100); timer->setInterval(100);
timer->setSingleShot(true); timer->setSingleShot(true);
timer->callOnTimeout(this, &ChartView::adjustChartMargins); 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) { QObject::connect(chart, &QChart::plotAreaChanged, [=](const QRectF &plotArea) {
// use a singleshot timer to avoid recursion call. // use a singleshot timer to avoid recursion call.
timer->start(); 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() { void ChartView::updateFromSettings() {
setFixedHeight(settings.chart_height); setFixedHeight(settings.chart_height);
chart()->setTheme(settings.chart_theme == 0 ? QChart::ChartThemeLight : QChart::QChart::ChartThemeDark); 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) { void ChartView::setRange(double min, double max, bool force_update) {
@ -318,7 +264,7 @@ void ChartView::adjustChartMargins() {
const int aligned_pos = 60; const int aligned_pos = 60;
if (chart()->plotArea().left() != aligned_pos) { if (chart()->plotArea().left() != aligned_pos) {
const float left_margin = chart()->margins().left() + aligned_pos - chart()->plotArea().left(); 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() + int x = chart()->plotArea().left() +
chart()->plotArea().width() * (current_sec - axis_x->min()) / (axis_x->max() - axis_x->min()); chart()->plotArea().width() * (current_sec - axis_x->min()) / (axis_x->max() - axis_x->min());
if (int(line_marker->line().x1()) != x) { 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(); auto events = can->events();
if (!events) return; if (!events) return;
auto l = id.split(':');
int bus = l[0].toInt();
uint32_t address = l[1].toUInt(nullptr, 16);
vals.clear(); vals.clear();
vals.reserve((range.second - range.first) * 1000); // [n]seconds * 1000hz vals.reserve((range.second - range.first) * 1000); // [n]seconds * 1000hz
auto [bus, address] = DBCManager::parseId(id);
double route_start_time = can->routeStartTime(); double route_start_time = can->routeStartTime();
Event begin_event(cereal::Event::Which::INIT_DATA, (route_start_time + range.first) * 1e9); 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()); 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) { void ChartView::leaveEvent(QEvent *event) {
track_line->setVisible(false); item_group->setVisible(false);
value_text->setVisible(false);
track_ellipse->setVisible(false);
QChartView::leaveEvent(event); QChartView::leaveEvent(event);
} }
@ -427,7 +361,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
if (!is_zooming) { if (!is_zooming) {
const auto plot_area = chart()->plotArea(); const auto plot_area = chart()->plotArea();
auto axis_x = dynamic_cast<QValueAxis *>(chart()->axisX()); 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(); 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(); }); auto value = std::upper_bound(vals.begin(), vals.end(), sec, [](double x, auto &p) { return x < p.x(); });
if (value != vals.end()) { if (value != vals.end()) {
@ -442,9 +376,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
} }
value_text->setPos(text_x, pos.y() - 10); value_text->setPos(text_x, pos.y() - 10);
} }
track_line->setVisible(value != vals.end()); item_group->setVisible(value != vals.end());
value_text->setVisible(value != vals.end());
track_ellipse->setVisible(value != vals.end());
} else { } else {
setViewportUpdateMode(QGraphicsView::FullViewportUpdate); setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
} }

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

@ -9,24 +9,22 @@ DBCManager::DBCManager(QObject *parent) : QObject(parent) {}
DBCManager::~DBCManager() {} DBCManager::~DBCManager() {}
void DBCManager::open(const QString &dbc_file_name) { void DBCManager::open(const QString &dbc_file_name) {
dbc_name = dbc_file_name; dbc = const_cast<DBC *>(dbc_lookup(dbc_file_name.toStdString()));
dbc = const_cast<DBC *>(dbc_lookup(dbc_name.toStdString())); updateMsgMap();
msg_map.clear();
for (auto &msg : dbc->msgs) {
msg_map[msg.address] = &msg;
}
emit DBCFileChanged(); emit DBCFileChanged();
} }
void DBCManager::open(const QString &name, const QString &content) { void DBCManager::open(const QString &name, const QString &content) {
this->dbc_name = name;
std::istringstream stream(content.toStdString()); std::istringstream stream(content.toStdString());
dbc = const_cast<DBC *>(dbc_parse_from_stream(name.toStdString(), stream)); dbc = const_cast<DBC *>(dbc_parse_from_stream(name.toStdString(), stream));
updateMsgMap();
emit DBCFileChanged();
}
void DBCManager::updateMsgMap() {
msg_map.clear(); msg_map.clear();
for (auto &msg : dbc->msgs) { for (auto &msg : dbc->msgs)
msg_map[msg.address] = &msg; msg_map[msg.address] = &msg;
}
emit DBCFileChanged();
} }
QString DBCManager::generateDBC() { QString DBCManager::generateDBC() {
@ -51,22 +49,30 @@ QString DBCManager::generateDBC() {
} }
void DBCManager::updateMsg(const QString &id, const QString &name, uint32_t size) { void DBCManager::updateMsg(const QString &id, const QString &name, uint32_t size) {
auto m = const_cast<Msg *>(msg(id)); auto [bus, address] = parseId(id);
if (m) { if (auto m = const_cast<Msg *>(msg(address))) {
m->name = name.toStdString(); m->name = name.toStdString();
m->size = size; m->size = size;
} else { } else {
uint32_t address = addressFromId(id); m = &dbc->msgs.emplace_back(Msg{.address = address, .name = name.toStdString(), .size = size});
dbc->msgs.push_back({.address = address, .name = name.toStdString(), .size = size}); msg_map[address] = m;
msg_map[address] = &dbc->msgs.back(); }
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) { void DBCManager::addSignal(const QString &id, const Signal &sig) {
if (Msg *m = const_cast<Msg *>(msg(id))) { if (Msg *m = const_cast<Msg *>(msg(id))) {
m->sigs.push_back(sig); emit signalAdded(&m->sigs.emplace_back(sig));
emit signalAdded(&m->sigs.back());
} }
} }
@ -90,8 +96,9 @@ void DBCManager::removeSignal(const QString &id, const QString &sig_name) {
} }
} }
uint32_t DBCManager::addressFromId(const QString &id) { std::pair<uint8_t, uint32_t> DBCManager::parseId(const QString &id) {
return id.mid(id.indexOf(':') + 1).toUInt(nullptr, 16); const auto list = id.split(':');
return {list[0].toInt(), list[1].toUInt(nullptr, 16)};
} }
DBCManager *dbc() { DBCManager *dbc() {
@ -140,9 +147,9 @@ double get_raw_value(uint8_t *data, size_t data_size, const Signal &sig) {
return value; return value;
} }
void updateSigSizeParamsFromRange(Signal &s, int from, int to) { void updateSigSizeParamsFromRange(Signal &s, int start_bit, int size) {
s.start_bit = s.is_little_endian ? from : bigEndianBitIndex(from); s.start_bit = s.is_little_endian ? start_bit : bigEndianBitIndex(start_bit);
s.size = to - from + 1; s.size = size;
if (s.is_little_endian) { if (s.is_little_endian) {
s.lsb = s.start_bit; s.lsb = s.start_bit;
s.msb = s.start_bit + s.size - 1; 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 updateSignal(const QString &id, const QString &sig_name, const Signal &sig);
void removeSignal(const QString &id, const QString &sig_name); 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 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 updateMsg(const QString &id, const QString &name, uint32_t size);
void removeMsg(const QString &id);
inline const DBC *getDBC() const { return dbc; } 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 { inline const Msg *msg(uint32_t address) const {
auto it = msg_map.find(address); auto it = msg_map.find(address);
return it != msg_map.end() ? it->second : nullptr; return it != msg_map.end() ? it->second : nullptr;
@ -34,11 +35,12 @@ signals:
void signalAdded(const Signal *sig); void signalAdded(const Signal *sig);
void signalRemoved(const Signal *sig); void signalRemoved(const Signal *sig);
void signalUpdated(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(); void DBCFileChanged();
private: private:
QString dbc_name; void updateMsgMap();
DBC *dbc = nullptr; DBC *dbc = nullptr;
std::unordered_map<uint32_t, const Msg *> msg_map; 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); double get_raw_value(uint8_t *data, size_t data_size, const Signal &sig);
int bigEndianStartBitsIndex(int start_bit); int bigEndianStartBitsIndex(int start_bit);
int bigEndianBitIndex(int index); 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); std::pair<int, int> getSignalRange(const Signal *s);
DBCManager *dbc(); 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); title_frame->setFrameShape(QFrame::StyledPanel);
// message title // message title
QHBoxLayout *title_layout = new QHBoxLayout(); toolbar = new QToolBar(this);
title_layout->addWidget(new QLabel("time:")); toolbar->addWidget(new QLabel("time:"));
time_label = new QLabel(this); time_label = new QLabel(this);
time_label->setStyleSheet("font-weight:bold"); time_label->setStyleSheet("font-weight:bold");
title_layout->addWidget(time_label); toolbar->addWidget(time_label);
title_layout->addStretch();
name_label = new QLabel(this); name_label = new QLabel(this);
name_label->setStyleSheet("font-weight:bold;"); name_label->setStyleSheet("font-weight:bold;");
title_layout->addWidget(name_label); name_label->setAlignment(Qt::AlignCenter);
title_layout->addStretch(); name_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
edit_btn = new QPushButton(tr("Edit"), this); toolbar->addWidget(name_label);
edit_btn->setVisible(false); toolbar->addAction("🖍", this, &DetailWidget::editMsg)->setToolTip(tr("Edit Message"));
title_layout->addWidget(edit_btn); remove_msg_act = toolbar->addAction("X", this, &DetailWidget::removeMsg);
frame_layout->addLayout(title_layout); remove_msg_act->setToolTip(tr("Remove Message"));
toolbar->setVisible(false);
frame_layout->addWidget(toolbar);
// warning // warning
warning_widget = new QWidget(this); warning_widget = new QWidget(this);
@ -85,7 +86,6 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
history_log = new HistoryLog(this); history_log = new HistoryLog(this);
container_layout->addWidget(history_log); 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::resizeSignal, this, &DetailWidget::resizeSignal);
QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal); QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal);
QObject::connect(can, &CANMessages::updated, this, &DetailWidget::updateState); QObject::connect(can, &CANMessages::updated, this, &DetailWidget::updateState);
@ -130,16 +130,13 @@ void DetailWidget::setMessage(const QString &message_id) {
break; break;
} }
} }
msg_id = message_id;
if (index == -1) { if (index == -1) {
index = tabbar->addTab(message_id); index = tabbar->addTab(message_id);
auto msg = dbc()->msg(message_id); tabbar->setTabToolTip(index, msgName(message_id));
tabbar->setTabToolTip(index, msg ? msg->name.c_str() : "untitled");
} }
tabbar->setCurrentIndex(index); tabbar->setCurrentIndex(index);
msg_id = message_id;
dbcMsgChanged(); dbcMsgChanged();
scroll->verticalScrollBar()->setValue(0);
} }
void DetailWidget::dbcMsgChanged(int show_form_idx) { 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)); warnings.push_back(tr("Message size (%1) is incorrect.").arg(msg->size));
} }
edit_btn->setVisible(true); toolbar->setVisible(!msg_id.isEmpty());
name_label->setText(msg ? msg->name.c_str() : "untitled"); remove_msg_act->setEnabled(msg != nullptr);
name_label->setText(msgName(msg_id));
binary_view->setMessage(msg_id); binary_view->setMessage(msg_id);
history_log->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_label->setText(warnings.join('\n'));
warning_widget->setVisible(!warnings.isEmpty()); warning_widget->setVisible(!warnings.isEmpty());
setUpdatesEnabled(true); setUpdatesEnabled(true);
scroll->verticalScrollBar()->setValue(0);
} }
void DetailWidget::updateState() { void DetailWidget::updateState() {
@ -211,34 +210,55 @@ void DetailWidget::updateChartState(const QString &id, const Signal *sig, bool o
} }
void DetailWidget::editMsg() { void DetailWidget::editMsg() {
auto msg = dbc()->msg(msg_id); QString id = msg_id;
QString name = msg ? msg->name.c_str() : "untitled"; auto msg = dbc()->msg(id);
int size = msg ? msg->size : can->lastMessage(msg_id).dat.size(); int size = msg ? msg->size : can->lastMessage(id).dat.size();
EditMessageDialog dlg(msg_id, name, size, this); EditMessageDialog dlg(id, msgName(id), size, this);
if (dlg.exec()) { 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(); dbcMsgChanged();
} }
} }
void DetailWidget::addSignal(int from, int to) { void DetailWidget::removeMsg() {
if (auto msg = dbc()->msg(msg_id)) { 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) {
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;
}
}
}
Signal sig = {}; Signal sig = {};
for (int i = 1; /**/; ++i) { for (int i = 1; /**/; ++i) {
sig.name = "NEW_SIGNAL_" + std::to_string(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; }); auto it = std::find_if(msg->sigs.begin(), msg->sigs.end(), [&](auto &s) { return sig.name == s.name; });
if (it == msg->sigs.end()) break; if (it == msg->sigs.end()) break;
} }
sig.is_little_endian = false, sig.is_little_endian = little_endian;
updateSigSizeParamsFromRange(sig, from, to); updateSigSizeParamsFromRange(sig, start_bit, size);
dbc()->addSignal(msg_id, sig); dbc()->addSignal(msg_id, sig);
dbcMsgChanged(msg->sigs.size() - 1); 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; Signal s = *sig;
updateSigSizeParamsFromRange(s, from, to); updateSigSizeParamsFromRange(s, start_bit, size);
saveSignal(sig, s); 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); dbc()->updateSignal(msg_id, sig->name.c_str(), new_sig);
// update binary view and history log dbcMsgChanged();
updateState();
} }
void DetailWidget::removeSignal(const Signal *sig) { 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) { EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title, int size, QWidget *parent) : QDialog(parent) {
setWindowTitle(tr("Edit message")); setWindowTitle(tr("Edit message"));
QVBoxLayout *main_layout = new QVBoxLayout(this); QFormLayout *form_layout = new QFormLayout(this);
QFormLayout *form_layout = new QFormLayout();
form_layout->addRow("ID", new QLabel(msg_id)); form_layout->addRow("ID", new QLabel(msg_id));
name_edit = new QLineEdit(title, this); name_edit = new QLineEdit(title, this);
@ -291,10 +308,8 @@ EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title
size_spin->setValue(size); size_spin->setValue(size);
form_layout->addRow(tr("Size"), size_spin); form_layout->addRow(tr("Size"), size_spin);
main_layout->addLayout(form_layout);
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
main_layout->addWidget(buttonBox); form_layout->addRow(buttonBox);
setFixedWidth(parent->width() * 0.9); setFixedWidth(parent->width() * 0.9);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);

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

@ -1,7 +1,6 @@
#include "tools/cabana/historylog.h" #include "tools/cabana/historylog.h"
#include <QFontDatabase> #include <QFontDatabase>
#include <QVBoxLayout>
// HistoryLogModel // HistoryLogModel
@ -89,3 +88,8 @@ HistoryLog::HistoryLog(QWidget *parent) : QTableView(parent) {
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
setStyleSheet("QTableView::item { border:0px; padding-left:5px; padding-right:5px; }"); 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 { class HeaderView : public QHeaderView {
public: public:
HeaderView(Qt::Orientation orientation, QWidget *parent = nullptr) : QHeaderView(orientation, parent) {} 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 { class HistoryLogModel : public QAbstractTableModel {
@ -40,5 +40,6 @@ public:
void setMessage(const QString &message_id) { model->setMessage(message_id); } void setMessage(const QString &message_id) { model->setMessage(message_id); }
void updateState() { model->updateState(); } void updateState() { model->updateState(); }
private: private:
int sizeHintForColumn(int column) const override;
HistoryLogModel *model; HistoryLogModel *model;
}; };

@ -1,8 +1,17 @@
#include "tools/cabana/mainwin.h" #include "tools/cabana/mainwin.h"
#include <QApplication> #include <QApplication>
#include <QClipboard>
#include <QCompleter>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QScreen> #include <QScreen>
#include <QToolBar>
#include <QVBoxLayout> #include <QVBoxLayout>
#include "tools/replay/util.h" #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); if (main_win) emit main_win->showMessage(msg, 0);
} }
MainWindow::MainWindow() : QWidget() { MainWindow::MainWindow() : QMainWindow() {
QVBoxLayout *main_layout = new QVBoxLayout(this); setWindowTitle("Cabana");
main_layout->setContentsMargins(11, 11, 11, 5); QWidget *central_widget = new QWidget(this);
QHBoxLayout *main_layout = new QHBoxLayout(central_widget);
main_layout->setContentsMargins(11, 11, 11, 0);
main_layout->setSpacing(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 = new QSplitter(Qt::Horizontal, this);
splitter->setHandleWidth(11); 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); messages_widget = new MessagesWidget(this);
splitter->addWidget(messages_widget); messages_layout->addWidget(messages_widget);
splitter->addWidget(messages_container);
charts_widget = new ChartsWidget(this); charts_widget = new ChartsWidget(this);
detail_widget = new DetailWidget(charts_widget, this); detail_widget = new DetailWidget(charts_widget, this);
splitter->addWidget(detail_widget); splitter->addWidget(detail_widget);
main_layout->addWidget(splitter);
h_layout->addWidget(splitter);
// right widgets // right widgets
QWidget *right_container = new QWidget(this); QWidget *right_container = new QWidget(this);
@ -38,35 +61,21 @@ MainWindow::MainWindow() : QWidget() {
r_layout = new QVBoxLayout(right_container); r_layout = new QVBoxLayout(right_container);
r_layout->setContentsMargins(11, 0, 0, 0); r_layout->setContentsMargins(11, 0, 0, 0);
QHBoxLayout *right_hlayout = new QHBoxLayout(); QHBoxLayout *right_hlayout = new QHBoxLayout();
QLabel *fingerprint_label = new QLabel(this); fingerprint_label = new QLabel(this);
right_hlayout->addWidget(fingerprint_label); right_hlayout->addWidget(fingerprint_label, 0, Qt::AlignLeft);
// TODO: click to select another route. // TODO: click to select another route.
right_hlayout->addWidget(new QLabel(can->route())); right_hlayout->addWidget(new QLabel(can->route()), 0, Qt::AlignRight);
QPushButton *settings_btn = new QPushButton("Settings");
right_hlayout->addWidget(settings_btn, 0, Qt::AlignRight);
r_layout->addLayout(right_hlayout); r_layout->addLayout(right_hlayout);
video_widget = new VideoWidget(this); video_widget = new VideoWidget(this);
r_layout->addWidget(video_widget, 0, Qt::AlignTop); r_layout->addWidget(video_widget, 0, Qt::AlignTop);
r_layout->addWidget(charts_widget); r_layout->addWidget(charts_widget);
main_layout->addWidget(right_container);
h_layout->addWidget(right_container); setCentralWidget(central_widget);
createActions();
// status bar createStatusBar();
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);
qRegisterMetaType<uint64_t>("uint64_t"); qRegisterMetaType<uint64_t>("uint64_t");
qRegisterMetaType<ReplyMsgType>("ReplyMsgType"); qRegisterMetaType<ReplyMsgType>("ReplyMsgType");
@ -78,17 +87,94 @@ MainWindow::MainWindow() : QWidget() {
emit updateProgressBar(cur, total, success); 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(this, &MainWindow::updateProgressBar, this, &MainWindow::updateDownloadProgress);
QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, detail_widget, &DetailWidget::setMessage); 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::dock, this, &MainWindow::dockCharts);
QObject::connect(charts_widget, &ChartsWidget::rangeChanged, video_widget, &VideoWidget::rangeChanged); QObject::connect(charts_widget, &ChartsWidget::rangeChanged, video_widget, &VideoWidget::rangeChanged);
QObject::connect(settings_btn, &QPushButton::clicked, this, &MainWindow::setOption); QObject::connect(can, &CANMessages::streamStarted, this, &MainWindow::loadDBCFromFingerprint);
QObject::connect(can, &CANMessages::eventsMerged, [=]() { fingerprint_label->setText(can->carFingerprint() ); }); QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() {
dbc_combo->setCurrentText(QFileInfo(dbc()->name()).baseName());
setWindowTitle(tr("%1 - Cabana").arg(dbc()->name()));
});
}
main_win = this; void MainWindow::createActions() {
qInstallMessageHandler(qLogMessageHandler); 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) { void MainWindow::updateDownloadProgress(uint64_t cur, uint64_t total, bool success) {

@ -1,5 +1,8 @@
#pragma once #pragma once
#include <QComboBox>
#include <QJsonDocument>
#include <QMainWindow>
#include <QProgressBar> #include <QProgressBar>
#include <QSplitter> #include <QSplitter>
#include <QStatusBar> #include <QStatusBar>
@ -9,19 +12,29 @@
#include "tools/cabana/messageswidget.h" #include "tools/cabana/messageswidget.h"
#include "tools/cabana/videowidget.h" #include "tools/cabana/videowidget.h"
class MainWindow : public QWidget { class MainWindow : public QMainWindow {
Q_OBJECT Q_OBJECT
public: public:
MainWindow(); MainWindow();
void dockCharts(bool dock); 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: signals:
void showMessage(const QString &msg, int timeout); void showMessage(const QString &msg, int timeout);
void updateProgressBar(uint64_t cur, uint64_t total, bool success); void updateProgressBar(uint64_t cur, uint64_t total, bool success);
protected: protected:
void createActions();
void createStatusBar();
void closeEvent(QCloseEvent *event) override; void closeEvent(QCloseEvent *event) override;
void updateDownloadProgress(uint64_t cur, uint64_t total, bool success); void updateDownloadProgress(uint64_t cur, uint64_t total, bool success);
void setOption(); void setOption();
@ -34,5 +47,7 @@ protected:
QWidget *floating_window = nullptr; QWidget *floating_window = nullptr;
QVBoxLayout *r_layout; QVBoxLayout *r_layout;
QProgressBar *progress_bar; 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 "tools/cabana/messageswidget.h"
#include <QCompleter>
#include <QDialogButtonBox>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QFontDatabase> #include <QFontDatabase>
#include <QHeaderView> #include <QHeaderView>
#include <QLineEdit> #include <QLineEdit>
#include <QPushButton>
#include <QTextEdit>
#include <QVBoxLayout> #include <QVBoxLayout>
#include "tools/cabana/dbcmanager.h" #include "tools/cabana/dbcmanager.h"
@ -18,31 +11,6 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this); QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0); 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 // message filter
QLineEdit *filter = new QLineEdit(this); QLineEdit *filter = new QLineEdit(this);
filter->setClearButtonEnabled(true); filter->setClearButtonEnabled(true);
@ -67,55 +35,22 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
// signals/slots // signals/slots
QObject::connect(filter, &QLineEdit::textChanged, model, &MessageListModel::setFilterString); QObject::connect(filter, &QLineEdit::textChanged, model, &MessageListModel::setFilterString);
QObject::connect(can, &CANMessages::eventsMerged, this, &MessagesWidget::loadDBCFromFingerprint); QObject::connect(can, &CANMessages::msgsReceived, model, &MessageListModel::msgsReceived);
QObject::connect(can, &CANMessages::updated, [this]() { model->updateState(); }); QObject::connect(dbc(), &DBCManager::DBCFileChanged, model, &MessageListModel::sortMessages);
QObject::connect(dbc_combo, SIGNAL(activated(const QString &)), SLOT(loadDBCFromName(const QString &))); QObject::connect(dbc(), &DBCManager::msgUpdated, model, &MessageListModel::sortMessages);
QObject::connect(load_from_paste, &QPushButton::clicked, this, &MessagesWidget::loadDBCFromPaste); QObject::connect(dbc(), &DBCManager::msgRemoved, model, &MessageListModel::sortMessages);
QObject::connect(save_btn, &QPushButton::clicked, this, &MessagesWidget::saveDBC);
QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex &current, const QModelIndex &previous) { QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex &current, const QModelIndex &previous) {
if (current.isValid()) { if (current.isValid() && current.row() < model->msgs.size()) {
emit msgSelectionChanged(current.data(Qt::UserRole).toString()); 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);
} }
} });
QObject::connect(model, &MessageListModel::modelReset, [this]() {
void MessagesWidget::loadDBCFromFingerprint() { if (int row = model->msgs.indexOf(current_msg_id); row != -1)
auto fingerprint = can->carFingerprint(); table_widget->selectionModel()->setCurrentIndex(model->index(row, 0), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect);
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();
} }
// MessageListModel // MessageListModel
@ -128,103 +63,71 @@ QVariant MessageListModel::headerData(int section, Qt::Orientation orientation,
QVariant MessageListModel::data(const QModelIndex &index, int role) const { QVariant MessageListModel::data(const QModelIndex &index, int role) const {
if (role == Qt::DisplayRole) { if (role == Qt::DisplayRole) {
const auto &m = msgs[index.row()]; const auto &id = msgs[index.row()];
auto &can_data = can->lastMessage(m->id); auto &can_data = can->lastMessage(id);
switch (index.column()) { switch (index.column()) {
case 0: return m->name; case 0: return msgName(id);
case 1: return m->id; case 1: return id;
case 2: return can_data.freq; case 2: return can_data.freq;
case 3: return can_data.count; case 3: return can_data.count;
case 4: return toHex(can_data.dat); case 4: return toHex(can_data.dat);
} }
} else if (role == Qt::UserRole) { } else if (role == Qt::FontRole && index.column() == columnCount() - 1) {
return msgs[index.row()]->id;
} else if (role == Qt::FontRole) {
if (index.column() == columnCount() - 1) {
return QFontDatabase::systemFont(QFontDatabase::FixedFont); return QFontDatabase::systemFont(QFontDatabase::FixedFont);
} }
}
return {}; return {};
} }
void MessageListModel::setFilterString(const QString &string) { void MessageListModel::setFilterString(const QString &string) {
filter_str = 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(':'); bool search_id = filter_str.contains(':');
msgs.clear();
for (auto it = can->can_msgs.begin(); it != can->can_msgs.end(); ++it) { for (auto it = can->can_msgs.begin(); it != can->can_msgs.end(); ++it) {
const Msg *msg = dbc()->msg(it.key()); if ((search_id ? it.key() : msgName(it.key())).contains(filter_str, Qt::CaseInsensitive))
QString msg_name = msg ? msg->name.c_str() : "untitled"; msgs.push_back(it.key());
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;
} }
msgs.resize(i); sortMessages();
}
void MessageListModel::sortMessages() {
beginResetModel();
if (sort_column == 0) { if (sort_column == 0) {
std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { 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; return sort_order == Qt::AscendingOrder ? ret : !ret;
}); });
} else if (sort_column == 1) { } else if (sort_column == 1) {
std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { 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) { } else if (sort_column == 2) {
// sort by frequency
std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) {
uint32_t lfreq = can->lastMessage(l->id).freq; bool ret = std::pair{can->lastMessage(l).freq, l} < std::pair{can->lastMessage(r).freq, r};
uint32_t rfreq = can->lastMessage(r->id).freq;
bool ret = lfreq < rfreq || (lfreq == rfreq && l->id < r->id);
return sort_order == Qt::AscendingOrder ? ret : !ret; return sort_order == Qt::AscendingOrder ? ret : !ret;
}); });
} else if (sort_column == 3) { } else if (sort_column == 3) {
// sort by count
std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) {
uint32_t lcount = can->lastMessage(l->id).count; bool ret = std::pair{can->lastMessage(l).count, l} < std::pair{can->lastMessage(r).count, r};
uint32_t rcount = can->lastMessage(r->id).count;
bool ret = lcount < rcount || (lcount == rcount && l->id < r->id);
return sort_order == Qt::AscendingOrder ? ret : !ret; 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(); int prev_row_count = msgs.size();
auto prev_idx = persistentIndexList(); if (filter_str.isEmpty() && msgs.size() != can->can_msgs.size()) {
QString selected_msg_id = prev_idx.empty() ? "" : prev_idx[0].data(Qt::UserRole).toString(); msgs = can->can_msgs.keys();
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 (msgs.size() != prev_row_count) {
if (!msgs.empty()) { sortMessages();
if (msg_updated && !prev_idx.isEmpty()) { return;
// 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()));
} }
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) { if (column != columnCount() - 1) {
sort_column = column; sort_column = column;
sort_order = order; sort_order = order;
updateState(true); sortMessages();
}
}
// 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();
} }
} }

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

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

@ -28,7 +28,6 @@ SignalForm::SignalForm(QWidget *parent) : QWidget(parent) {
endianness->addItems({"Little", "Big"}); endianness->addItems({"Little", "Big"});
form_layout->addRow(tr("Endianness"), endianness); form_layout->addRow(tr("Endianness"), endianness);
;
form_layout->addRow(tr("lsb"), lsb = new QLabel()); form_layout->addRow(tr("lsb"), lsb = new QLabel());
form_layout->addRow(tr("msb"), msb = new QLabel()); form_layout->addRow(tr("msb"), msb = new QLabel());
@ -130,7 +129,18 @@ void SignalEdit::saveSignal() {
s.offset = form->offset->text().toDouble(); s.offset = form->offset->text().toDouble();
s.factor = form->factor->text().toDouble(); s.factor = form->factor->text().toDouble();
s.is_signed = form->sign->currentIndex() == 0; 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) { if (s.is_little_endian) {
s.lsb = s.start_bit; s.lsb = s.start_bit;
s.msb = s.start_bit + s.size - 1; s.msb = s.start_bit + s.size - 1;

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

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

@ -4,7 +4,7 @@
# run limeGPS with random static location # run limeGPS with random static location
timeout 300 ./simulate_gps_signal.py 32.7518 -117.1962 & 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..." echo "starting limeGPS..."
sleep 10 sleep 10

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

Loading…
Cancel
Save