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

pull/25364/head
royjr 3 years ago
commit f5c35f97d1
  1. 27
      Jenkinsfile
  2. 21
      RELEASES.md
  3. 2
      cereal
  4. 4
      docs/CARS.md
  5. 2
      launch_env.sh
  6. 1
      release/files_common
  7. 15
      selfdrive/boardd/panda_comms.h
  8. 179
      selfdrive/boardd/spi.cc
  9. 2
      selfdrive/car/gm/carstate.py
  10. 17
      selfdrive/car/gm/interface.py
  11. 2
      selfdrive/car/hyundai/values.py
  12. 4
      selfdrive/car/interfaces.py
  13. 4
      selfdrive/car/tests/test_models.py
  14. 5
      selfdrive/car/volkswagen/values.py
  15. 55
      selfdrive/controls/controlsd.py
  16. 151
      selfdrive/controls/lib/drive_helpers.py
  17. 1
      selfdrive/controls/lib/events.py
  18. 14
      selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py
  19. 13
      selfdrive/controls/lib/longitudinal_planner.py
  20. 95
      selfdrive/controls/tests/test_cruise_speed.py
  21. 6
      selfdrive/manager/test/test_manager.py
  22. 12
      selfdrive/test/longitudinal_maneuvers/maneuver.py
  23. 19
      selfdrive/test/longitudinal_maneuvers/plant.py
  24. 9
      selfdrive/test/longitudinal_maneuvers/test_longitudinal.py
  25. 2
      selfdrive/test/process_replay/ref_commit
  26. 6
      selfdrive/ui/translations/main_ko.ts
  27. 4
      selfdrive/ui/translations/main_pt-BR.ts
  28. 20
      selfdrive/ui/translations/main_zh-CHT.ts
  29. 12
      system/hardware/tici/agnos.json
  30. 3
      system/hardware/tici/hardware.py
  31. 2
      tools/cabana/SConscript
  32. 7
      tools/cabana/binaryview.cc
  33. 2
      tools/cabana/binaryview.h
  34. 12
      tools/cabana/canmessages.cc
  35. 2
      tools/cabana/canmessages.h
  36. 11
      tools/cabana/chartswidget.cc
  37. 2
      tools/cabana/chartswidget.h
  38. 75
      tools/cabana/commands.cc
  39. 63
      tools/cabana/commands.h
  40. 83
      tools/cabana/dbcmanager.cc
  41. 27
      tools/cabana/dbcmanager.h
  42. 130
      tools/cabana/detailwidget.cc
  43. 9
      tools/cabana/detailwidget.h
  44. 8
      tools/cabana/historylog.cc
  45. 2
      tools/cabana/historylog.h
  46. 46
      tools/cabana/mainwin.cc
  47. 5
      tools/cabana/settings.cc
  48. 1
      tools/cabana/settings.h
  49. 137
      tools/cabana/signaledit.cc
  50. 23
      tools/cabana/signaledit.h
  51. 78
      tools/cabana/tests/test_cabana.cc
  52. 2
      tools/cabana/videowidget.cc
  53. 7
      tools/camerastream/compressed_vipc.py

27
Jenkinsfile vendored

@ -111,7 +111,7 @@ pipeline {
R3_PUSH = "${env.BRANCH_NAME == 'master' ? '1' : ' '}"
}
steps {
phone_steps("tici", [
phone_steps("tici-needs-can", [
["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR EXTRA_FILES='tools/' ./build_devel.sh"],
["build openpilot", "cd selfdrive/manager && ./build.py"],
["check dirty", "release/check-dirty.sh"],
@ -122,16 +122,24 @@ pipeline {
}
}
stage('loopback-tests') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("tici-loopback", [
["build openpilot", "cd selfdrive/manager && ./build.py"],
["test boardd loopback", "python selfdrive/boardd/tests/test_boardd_loopback.py"],
])
}
}
stage('HW + Unit Tests') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("tici2", [
phone_steps("tici-common", [
["build", "cd selfdrive/manager && ./build.py"],
["test power draw", "python system/hardware/tici/test_power_draw.py"],
["test boardd loopback", "python selfdrive/boardd/tests/test_boardd_loopback.py"],
["test loggerd", "python selfdrive/loggerd/tests/test_loggerd.py"],
["test encoder", "LD_LIBRARY_PATH=/usr/local/lib python selfdrive/loggerd/tests/test_encoder.py"],
["test sensord", "python selfdrive/sensord/tests/test_sensord.py"],
["test pigeond", "python selfdrive/sensord/tests/test_pigeond.py"],
])
}
@ -159,27 +167,32 @@ pipeline {
}
}
stage('sensord (LSM-C)') {
stage('sensord') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("tici-lsmc", [
["build", "cd selfdrive/manager && ./build.py"],
["test sensord", "cd selfdrive/sensord/tests && python -m unittest test_sensord.py"],
])
phone_steps("tici-bmx-lsm", [
["build", "cd selfdrive/manager && ./build.py"],
["test sensord", "cd selfdrive/sensord/tests && python -m unittest test_sensord.py"],
])
}
}
stage('replay') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("tici3", [
phone_steps("tici-common", [
["build", "cd selfdrive/manager && ./build.py"],
["model replay", "cd selfdrive/test/process_replay && ./model_replay.py"],
])
}
}
}
}
}
}
}

@ -1,19 +1,20 @@
Version 0.8.17 (2022-11-XX)
Version 0.8.17 (2022-11-21)
========================
* New driving model
* Internal feature space information content increased tenfold during training (to ~700 bits), this makes the model dramatically more accurate
* Internal feature space information content increased tenfold during training (to ~700 bits), which makes the model dramatically more accurate
* Less reliance on previous frames makes model more reactive and snappy
* Trained in new reprojective simulator
* Model trained in openpilot was trained in 36hrs from scratch, compared to around 1 week of previous releases
* Model training now simulates lateral and longitudinal behavior, this allows openpilot to slow down for turns, stop at traffic lights, etc,... in experimental mode
* New driver monitoring model
* New end-to-end distracted trigger
* Trained in 36hrs from scratch, compared to one week for previous releases
* Training now simulates both lateral and longitudinal behavior, which allows openpilot to slow down for turns, stop at traffic lights, and more in experimental mode
* Driver monitoring updates
* New bigger model with added end-to-end distracted trigger
* Reduced false positives during driver calibration
* Experimental driving mode
* End-to-end longitudinal control
* Stops for red lights and stop signs
* Stops for traffic lights and stop signs
* Slows down for turns
* openpilot defaults to chill mode, enable experimental in settings
* Self-tuning torque lateral controller parameters
* Parameters learned live for each car
* Self-tuning torque controller: learns parameters live for each car
* Torque controller used on all Toyota, Lexus, Hyundai, Kia, and Genesis models
* UI updates
* Multi-language in navigation
@ -21,8 +22,10 @@ Version 0.8.17 (2022-11-XX)
* Improved update experience
* Border turns grey while overriding steering
* Bookmark events while driving; view them in comma connect
* New onroad visualization for experimental mode
* AGNOS 6
* tools: new and improved cabana thanks to deanlee!
* Experimental longitudinal support for Volkswagen, CAN-FD Hyundai, and new GM models
* Genesis GV70 2022-23 support thanks to zunichky and sunnyhaibin!
* Hyundai Santa Cruz 2021-22 support thanks to sunnyhaibin!
* Kia Sportage 2023 support thanks to sunnyhaibin!

@ -1 +1 @@
Subproject commit cdba1aafec5e36505ef6ace675568e1f15003c47
Subproject commit afafa0a2a537d775842ab2e1bf20cb9a33b34f9a

@ -107,7 +107,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|Kia|Sportage 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|
|Kia|Sportage Hybrid 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|
|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|
|Kia|Telluride 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 H|
|Kia|Telluride 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|
|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Lexus|ES 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Lexus|ES Hybrid 2017-18|Lexus Safety System+|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
@ -150,7 +150,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|Škoda|Octavia 2015, 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Škoda|Octavia RS 2016|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Škoda|Scala 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[<sup>9</sup>](#footnotes)|
|Škoda|Superb 2015-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Škoda|Superb 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|

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

@ -95,6 +95,7 @@ selfdrive/boardd/panda_comms.h
selfdrive/boardd/panda_comms.cc
selfdrive/boardd/set_time.py
selfdrive/boardd/pandad.py
selfdrive/boardd/tests/test_boardd_loopback.py
selfdrive/car/__init__.py
selfdrive/car/docs_definitions.py

@ -5,11 +5,16 @@
#include <cstdint>
#include <vector>
#include <linux/spi/spidev.h>
#include <libusb-1.0/libusb.h>
#define TIMEOUT 0
#define SPI_BUF_SIZE 1024
const bool PANDA_NO_RETRY = getenv("PANDA_NO_RETRY");
// comms base class
class PandaCommsHandle {
@ -29,7 +34,7 @@ public:
virtual int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0;
protected:
std::mutex hw_lock;
std::recursive_mutex hw_lock;
};
class PandaUsbHandle : public PandaCommsHandle {
@ -65,9 +70,11 @@ public:
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];
int wait_for_ack(spi_ioc_transfer &transfer, uint8_t ack);
int bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len);
int spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len);
int spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len);
};

@ -2,10 +2,12 @@
#include <linux/spi/spidev.h>
#include <cassert>
#include <cmath>
#include <cstring>
#include "common/util.h"
#include "common/swaglog.h"
#include "panda/board/comms_definitions.h"
#include "selfdrive/boardd/panda_comms.h"
@ -22,13 +24,6 @@ struct __attribute__((packed)) spi_header {
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());
@ -40,7 +35,7 @@ PandaSpiHandle::PandaSpiHandle(std::string serial) : PandaCommsHandle(serial) {
spi_fd = open(serial.c_str(), O_RDWR);
if (spi_fd < 0) {
LOGE("failed setting SPI mode %d", err);
LOGE("failed opening SPI device %d", err);
goto fail;
}
@ -85,51 +80,65 @@ void PandaSpiHandle::cleanup() {
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;
ControlPacket_t packet = {
.request = request,
.param1 = param1,
.param2 = param2,
.length = 0
};
return spi_transfer_retry(0, (uint8_t *) &packet, sizeof(packet), NULL, 0);
}
int PandaSpiHandle::control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout) {
int err;
ControlPacket_t packet = {
.request = request,
.param1 = param1,
.param2 = param2,
.length = length
};
return spi_transfer_retry(0, (uint8_t *) &packet, sizeof(packet), data, length);
}
int PandaSpiHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
return bulk_transfer(endpoint, data, length, NULL, 0);
}
int PandaSpiHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
return bulk_transfer(endpoint, NULL, 0, data, length);
}
int PandaSpiHandle::bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len) {
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);
const int xfer_size = 0x40;
int ret = 0;
uint16_t length = (tx_data != NULL) ? tx_len : rx_len;
for (int i = 0; i < (int)std::ceil((float)length / xfer_size); i++) {
int d;
if (tx_data != NULL) {
int len = std::min(xfer_size, tx_len - (xfer_size * i));
d = spi_transfer_retry(endpoint, tx_data + (xfer_size * i), len, NULL, 0);
} else {
d = spi_transfer_retry(endpoint, NULL, 0, rx_data + (xfer_size * i), xfer_size);
}
return err;
}
if (d < 0) {
LOGE("SPI: bulk transfer failed with %d", d);
comms_healthy = false;
return -1;
}
int PandaSpiHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
return 0;
}
ret += d;
if ((rx_data != NULL) && d < xfer_size) {
break;
}
}
int PandaSpiHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
return 0;
return ret;
}
std::vector<std::string> PandaSpiHandle::list() {
// TODO: list all pandas available over SPI
return {};
@ -144,6 +153,46 @@ void add_checksum(uint8_t *data, int data_len) {
}
}
bool check_checksum(uint8_t *data, int data_len) {
uint8_t checksum = SPI_CHECKSUM_START;
for (uint16_t i = 0U; i < data_len; i++) {
checksum ^= data[i];
}
return checksum == 0U;
}
int PandaSpiHandle::spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len) {
int ret;
std::lock_guard lk(hw_lock);
do {
// TODO: handle error
ret = spi_transfer(endpoint, tx_data, tx_len, rx_data, max_rx_len);
} while (ret < 0 && connected && !PANDA_NO_RETRY);
return ret;
}
int PandaSpiHandle::wait_for_ack(spi_ioc_transfer &transfer, uint8_t ack) {
// TODO: add timeout?
while (true) {
int ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
if (ret < 0) {
LOGE("SPI: failed to send ACK request");
return ret;
}
if (rx_buf[0] == ack) {
break;
} else if (rx_buf[0] == SPI_NACK) {
LOGW("SPI: got NACK");
return -1;
}
}
return 0;
}
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;
@ -178,19 +227,9 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx
// 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;
}
ret = wait_for_ack(transfer, SPI_HACK);
if (ret < 0) {
goto transfer_fail;
}
// Send data
@ -208,44 +247,40 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx
// 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;
}
ret = wait_for_ack(transfer, SPI_DACK);
if (ret < 0) {
goto transfer_fail;
}
// Read data len
transfer.len = 2;
transfer.rx_buf = (uint64_t)(rx_buf + 1);
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;
rx_data_len = *(uint16_t *)(rx_buf+1);
assert(rx_data_len < SPI_BUF_SIZE);
// Read data
transfer.len = rx_data_len + 1;
transfer.rx_buf = (uint64_t)(rx_buf + 2 + 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 (!check_checksum(rx_buf, rx_data_len + 4)) {
LOGE("SPI: bad checksum");
goto transfer_fail;
}
if (rx_data != NULL) {
memcpy(rx_data, rx_buf, rx_data_len);
memcpy(rx_data, rx_buf + 3, rx_data_len);
}
ret = rx_data_len;
return rx_data_len;
transfer_fail:
return ret;

@ -63,7 +63,7 @@ class CarState(CarStateBase):
# Regen braking is braking
if self.CP.transmissionType == TransmissionType.direct:
ret.brakePressed = ret.brakePressed or pt_cp.vl["EBCMRegenPaddle"]["RegenPaddle"] != 0
ret.regenBraking = pt_cp.vl["EBCMRegenPaddle"]["RegenPaddle"] != 0
ret.gas = pt_cp.vl["AcceleratorPedal2"]["AcceleratorPedal2"] / 254.
ret.gasPressed = ret.gas > 1e-5

@ -211,17 +211,20 @@ class CarInterface(CarInterfaceBase):
ret = self.CS.update(self.cp, self.cp_cam, self.cp_loopback)
if self.CS.cruise_buttons != self.CS.prev_cruise_buttons and self.CS.prev_cruise_buttons != CruiseButtons.INIT:
be = create_button_event(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS)
buttonEvents = [create_button_event(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS)]
# Handle ACCButtons changing buttons mid-press
if self.CS.cruise_buttons != CruiseButtons.UNPRESS and self.CS.prev_cruise_buttons != CruiseButtons.UNPRESS:
buttonEvents.append(create_button_event(CruiseButtons.UNPRESS, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS))
# Suppress resume button if we're resuming from stop so we don't adjust speed.
if be.type == ButtonType.accelCruise and (ret.cruiseState.enabled and ret.standstill):
be.type = ButtonType.unknown
ret.buttonEvents = [be]
ret.buttonEvents = buttonEvents
# The ECM allows enabling on falling edge of set, but only rising edge of resume
events = self.create_common_events(ret, extra_gears=[GearShifter.sport, GearShifter.low,
GearShifter.eco, GearShifter.manumatic],
pcm_enable=self.CP.pcmCruise)
pcm_enable=self.CP.pcmCruise, enable_buttons=(ButtonType.decelCruise,))
if not self.CP.pcmCruise:
if any(b.type == ButtonType.accelCruise and b.pressed for b in ret.buttonEvents):
events.add(EventName.buttonEnable)
# Enabling at a standstill with brake is allowed
# TODO: verify 17 Volt can enable for the first time at a stop and allow for all GMs

@ -142,7 +142,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
],
CAR.PALISADE: [
HyundaiCarInfo("Hyundai Palisade 2020-22", "All", "https://youtu.be/TAnDqjF4fDY?t=456", harness=Harness.hyundai_h),
HyundaiCarInfo("Kia Telluride 2020", "All", harness=Harness.hyundai_h),
HyundaiCarInfo("Kia Telluride 2020-22", "All", harness=Harness.hyundai_h),
],
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),

@ -250,8 +250,8 @@ class CarInterfaceBase(ABC):
# Enable OP long on falling edge of enable buttons (defaults to accelCruise and decelCruise, overridable per-port)
if not self.CP.pcmCruise and (b.type in enable_buttons and not b.pressed):
events.add(EventName.buttonEnable)
# Disable on rising edge of cancel for both stock and OP long
if b.type == ButtonType.cancel and b.pressed:
# Disable on rising and falling edge of cancel for both stock and OP long
if b.type == ButtonType.cancel:
events.add(EventName.buttonCancel)
# Handle permanent and temporary steering faults

@ -251,8 +251,8 @@ class TestCarModelBase(unittest.TestCase):
if CS.brakePressed and not self.safety.get_brake_pressed_prev():
if self.CP.carFingerprint in (HONDA.PILOT, HONDA.PASSPORT, HONDA.RIDGELINE) and CS.brake > 0.05:
brake_pressed = False
safety_brake_pressed = self.safety.get_brake_pressed_prev() or self.safety.get_regen_braking_prev()
checks['brakePressed'] += brake_pressed != safety_brake_pressed
checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev()
checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev()
if self.CP.pcmCruise:
# On most pcmCruise cars, openpilot's state is always tied to the PCM's cruise state.

@ -233,7 +233,7 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = {
CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019-21"),
CAR.SKODA_KODIAQ_MK1: VWCarInfo("Škoda Kodiaq 2018-19"),
CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0]),
CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-18"),
CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-22"),
CAR.SKODA_OCTAVIA_MK3: [
VWCarInfo("Škoda Octavia 2015, 2018-19"),
VWCarInfo("Škoda Octavia RS 2016"),
@ -1064,6 +1064,7 @@ FW_VERSIONS = {
},
CAR.SKODA_SUPERB_MK3: {
(Ecu.engine, 0x7e0, None): [
b'\xf1\x8704L906026ET\xf1\x891343',
b'\xf1\x8704L906026FP\xf1\x891196',
b'\xf1\x8704L906026KB\xf1\x894071',
b'\xf1\x8704L906026KD\xf1\x894798',
@ -1074,9 +1075,11 @@ FW_VERSIONS = {
b'\xf1\x870CW300042H \xf1\x891601',
b'\xf1\x870D9300011T \xf1\x894801',
b'\xf1\x870D9300012 \xf1\x894940',
b'\xf1\x870D9300041H \xf1\x894905',
b'\xf1\x870GC300043 \xf1\x892301',
],
(Ecu.srs, 0x715, None): [
b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\x12111200111121001121110012211292221111',
b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\022111200111121001121118112231292221111',
b'\xf1\x875Q0959655AK\xf1\x890130\xf1\x82\022111200111121001121110012211292221111',
b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\02331310031313100313131013141319331413100',

@ -16,8 +16,7 @@ from system.version import is_tested_branch, get_short_branch
from selfdrive.boardd.boardd import can_list_to_can_capnp
from selfdrive.car.car_helpers import get_car, get_startup_event, get_one_can
from selfdrive.controls.lib.lateral_planner import CAMERA_OFFSET
from selfdrive.controls.lib.drive_helpers import V_CRUISE_INITIAL, update_v_cruise, initialize_v_cruise
from selfdrive.controls.lib.drive_helpers import get_lag_adjusted_curvature
from selfdrive.controls.lib.drive_helpers import VCruiseHelper, get_lag_adjusted_curvature
from selfdrive.controls.lib.latcontrol import LatControl
from selfdrive.controls.lib.longcontrol import LongControl
from selfdrive.controls.lib.latcontrol_pid import LatControlPID
@ -49,7 +48,6 @@ Desire = log.LateralPlan.Desire
LaneChangeState = log.LateralPlan.LaneChangeState
LaneChangeDirection = log.LateralPlan.LaneChangeDirection
EventName = car.CarEvent.EventName
ButtonEvent = car.CarState.ButtonEvent
ButtonType = car.CarState.ButtonEvent.Type
SafetyModel = car.CarParams.SafetyModel
@ -173,9 +171,6 @@ class Controls:
self.active = False
self.can_rcv_timeout = False
self.soft_disable_timer = 0
self.v_cruise_kph = V_CRUISE_INITIAL
self.v_cruise_cluster_kph = V_CRUISE_INITIAL
self.v_cruise_kph_last = 0
self.mismatch_counter = 0
self.cruise_mismatch_counter = 0
self.can_rcv_timeout_counter = 0
@ -185,11 +180,11 @@ class Controls:
self.events_prev = []
self.current_alert_types = [ET.PERMANENT]
self.logged_comm_issue = None
self.button_timers = {ButtonEvent.Type.decelCruise: 0, ButtonEvent.Type.accelCruise: 0}
self.last_actuators = car.CarControl.Actuators.new_message()
self.steer_limited = False
self.desired_curvature = 0.0
self.desired_curvature_rate = 0.0
self.v_cruise_helper = VCruiseHelper(self.CP)
# TODO: no longer necessary, aside from process replay
self.sm['liveParameters'].valid = True
@ -219,7 +214,7 @@ class Controls:
controls_state = Params().get("ReplayControlsState")
if controls_state is not None:
controls_state = log.ControlsState.from_bytes(controls_state)
self.v_cruise_kph = controls_state.vCruise
self.v_cruise_helper.v_cruise_kph = controls_state.vCruise
if any(ps.controlsAllowed for ps in self.sm['pandaStates']):
self.state = State.enabled
@ -245,12 +240,13 @@ class Controls:
# Block resume if cruise never previously enabled
resume_pressed = any(be.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for be in CS.buttonEvents)
if not self.CP.pcmCruise and self.v_cruise_kph == V_CRUISE_INITIAL and resume_pressed:
if not self.CP.pcmCruise and not self.v_cruise_helper.v_cruise_initialized and resume_pressed:
self.events.add(EventName.resumeBlocked)
# Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0
if (CS.gasPressed and not self.CS_prev.gasPressed and self.disengage_on_accelerator) or \
(CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)):
(CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \
(CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)):
self.events.add(EventName.pedalPressed)
if CS.gasPressed:
@ -477,20 +473,7 @@ class Controls:
def state_transition(self, CS):
"""Compute conditional state transitions and execute actions on state transitions"""
self.v_cruise_kph_last = self.v_cruise_kph
if CS.cruiseState.available:
# if stock cruise is completely disabled, then we can use our own set speed logic
if not self.CP.pcmCruise:
self.v_cruise_kph = update_v_cruise(self.v_cruise_kph, CS.vEgo, CS.gasPressed, CS.buttonEvents,
self.button_timers, self.enabled, self.is_metric)
self.v_cruise_cluster_kph = self.v_cruise_kph
else:
self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH
self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH
else:
self.v_cruise_kph = V_CRUISE_INITIAL
self.v_cruise_cluster_kph = V_CRUISE_INITIAL
self.v_cruise_helper.update_v_cruise(CS, self.enabled, self.is_metric)
# decrement the soft disable timer at every step, as it's reset on
# entrance in SOFT_DISABLING state
@ -568,9 +551,7 @@ class Controls:
else:
self.state = State.enabled
self.current_alert_types.append(ET.ENABLE)
if not self.CP.pcmCruise:
self.v_cruise_kph = initialize_v_cruise(CS.vEgo, CS.buttonEvents, self.v_cruise_kph_last)
self.v_cruise_cluster_kph = self.v_cruise_kph
self.v_cruise_helper.initialize_v_cruise(CS)
# Check if openpilot is engaged and actuators are enabled
self.enabled = self.state in ENABLED_STATES
@ -618,7 +599,7 @@ class Controls:
if not self.joystick_mode:
# accel PID loop
pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, self.v_cruise_kph * CV.KPH_TO_MS)
pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, self.v_cruise_helper.v_cruise_kph * CV.KPH_TO_MS)
t_since_plan = (self.sm.frame - self.sm.rcv_frame['longitudinalPlan']) * DT_CTRL
actuators.accel = self.LoC.update(CC.longActive, CS, long_plan, pid_accel_limits, t_since_plan)
@ -682,16 +663,6 @@ class Controls:
return CC, lac_log
def update_button_timers(self, buttonEvents):
# increment timer for buttons still pressed
for k in self.button_timers:
if self.button_timers[k] > 0:
self.button_timers[k] += 1
for b in buttonEvents:
if b.type.raw in self.button_timers:
self.button_timers[b.type.raw] = 1 if b.pressed else 0
def publish_logs(self, CS, start_time, CC, lac_log):
"""Send actuators and hud commands to the car, send controlsstate and MPC logging"""
@ -714,7 +685,7 @@ class Controls:
CC.cruiseControl.resume = self.enabled and CS.cruiseState.standstill and speeds[-1] > 0.1
hudControl = CC.hudControl
hudControl.setSpeed = float(self.v_cruise_cluster_kph * CV.KPH_TO_MS)
hudControl.setSpeed = float(self.v_cruise_helper.v_cruise_cluster_kph * CV.KPH_TO_MS)
hudControl.speedVisible = self.enabled
hudControl.lanesVisible = self.enabled
hudControl.leadVisible = self.sm['longitudinalPlan'].hasLead
@ -797,8 +768,8 @@ class Controls:
controlsState.engageable = not self.events.any(ET.NO_ENTRY)
controlsState.longControlState = self.LoC.long_control_state
controlsState.vPid = float(self.LoC.v_pid)
controlsState.vCruise = float(self.v_cruise_kph)
controlsState.vCruiseCluster = float(self.v_cruise_cluster_kph)
controlsState.vCruise = float(self.v_cruise_helper.v_cruise_kph)
controlsState.vCruiseCluster = float(self.v_cruise_helper.v_cruise_cluster_kph)
controlsState.upAccelCmd = float(self.LoC.pid.p)
controlsState.uiAccelCmd = float(self.LoC.pid.i)
controlsState.ufAccelCmd = float(self.LoC.pid.f)
@ -879,7 +850,6 @@ class Controls:
self.publish_logs(CS, start_time, CC, lac_log)
self.prof.checkpoint("Sent")
self.update_button_timers(CS.buttonEvents)
self.CS_prev = CS
def controlsd_thread(self):
@ -888,6 +858,7 @@ class Controls:
self.rk.monitor_time()
self.prof.display()
def main(sm=None, pm=None, logcan=None):
controls = Controls(sm, pm, logcan)
controls.controlsd_thread()

@ -22,6 +22,7 @@ CAR_ROTATION_RADIUS = 0.0
# EU guidelines
MAX_LATERAL_JERK = 5.0
ButtonEvent = car.CarState.ButtonEvent
ButtonType = car.CarState.ButtonEvent.Type
CRUISE_LONG_PRESS = 50
CRUISE_NEAREST_FUNC = {
@ -34,68 +35,122 @@ CRUISE_INTERVAL_SIGN = {
}
def apply_deadzone(error, deadzone):
if error > deadzone:
error -= deadzone
elif error < - deadzone:
error += deadzone
else:
error = 0.
return error
def rate_limit(new_value, last_value, dw_step, up_step):
return clip(new_value, last_value + dw_step, last_value + up_step)
class VCruiseHelper:
def __init__(self, CP):
self.CP = CP
self.v_cruise_kph = V_CRUISE_INITIAL
self.v_cruise_cluster_kph = V_CRUISE_INITIAL
self.v_cruise_kph_last = 0
self.button_timers = {ButtonType.decelCruise: 0, ButtonType.accelCruise: 0}
self.button_change_states = {btn: {"standstill": False, "enabled": False} for btn in self.button_timers}
@property
def v_cruise_initialized(self):
return self.v_cruise_kph != V_CRUISE_INITIAL
def update_v_cruise(self, CS, enabled, is_metric):
self.v_cruise_kph_last = self.v_cruise_kph
if CS.cruiseState.available:
if not self.CP.pcmCruise:
# if stock cruise is completely disabled, then we can use our own set speed logic
self._update_v_cruise_non_pcm(CS, enabled, is_metric)
self.v_cruise_cluster_kph = self.v_cruise_kph
self.update_button_timers(CS, enabled)
else:
self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH
self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH
else:
self.v_cruise_kph = V_CRUISE_INITIAL
self.v_cruise_cluster_kph = V_CRUISE_INITIAL
def _update_v_cruise_non_pcm(self, CS, enabled, is_metric):
# handle button presses. TODO: this should be in state_control, but a decelCruise press
# would have the effect of both enabling and changing speed is checked after the state transition
if not enabled:
return
long_press = False
button_type = None
# should be CV.MPH_TO_KPH, but this causes rounding errors
v_cruise_delta = 1. if is_metric else 1.6
for b in CS.buttonEvents:
if b.type.raw in self.button_timers and not b.pressed:
if self.button_timers[b.type.raw] > CRUISE_LONG_PRESS:
return # end long press
button_type = b.type.raw
break
else:
for k in self.button_timers.keys():
if self.button_timers[k] and self.button_timers[k] % CRUISE_LONG_PRESS == 0:
button_type = k
long_press = True
break
def update_v_cruise(v_cruise_kph, v_ego, gas_pressed, buttonEvents, button_timers, enabled, metric):
# handle button presses. TODO: this should be in state_control, but a decelCruise press
# would have the effect of both enabling and changing speed is checked after the state transition
if not enabled:
return v_cruise_kph
if button_type is None:
return
long_press = False
button_type = None
# Don't adjust speed when pressing resume to exit standstill
cruise_standstill = self.button_change_states[button_type]["standstill"] or CS.cruiseState.standstill
if button_type == ButtonType.accelCruise and cruise_standstill:
return
# should be CV.MPH_TO_KPH, but this causes rounding errors
v_cruise_delta = 1. if metric else 1.6
# Don't adjust speed if we've enabled since the button was depressed (some ports enable on rising edge)
if not self.button_change_states[button_type]["enabled"]:
return
for b in buttonEvents:
if b.type.raw in button_timers and not b.pressed:
if button_timers[b.type.raw] > CRUISE_LONG_PRESS:
return v_cruise_kph # end long press
button_type = b.type.raw
break
else:
for k in button_timers.keys():
if button_timers[k] and button_timers[k] % CRUISE_LONG_PRESS == 0:
button_type = k
long_press = True
break
if button_type:
v_cruise_delta = v_cruise_delta * (5 if long_press else 1)
if long_press and v_cruise_kph % v_cruise_delta != 0: # partial interval
v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](v_cruise_kph / v_cruise_delta) * v_cruise_delta
if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval
self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta
else:
v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type]
self.v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type]
# If set is pressed while overriding, clip cruise speed to minimum of vEgo
if gas_pressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise):
v_cruise_kph = max(v_cruise_kph, v_ego * CV.MS_TO_KPH)
if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise):
self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH)
self.v_cruise_kph = clip(round(self.v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX)
v_cruise_kph = clip(round(v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX)
def update_button_timers(self, CS, enabled):
# increment timer for buttons still pressed
for k in self.button_timers:
if self.button_timers[k] > 0:
self.button_timers[k] += 1
return v_cruise_kph
for b in CS.buttonEvents:
if b.type.raw in self.button_timers:
# Start/end timer and store current state on change of button pressed
self.button_timers[b.type.raw] = 1 if b.pressed else 0
self.button_change_states[b.type.raw] = {"standstill": CS.cruiseState.standstill, "enabled": enabled}
def initialize_v_cruise(self, CS):
# initializing is handled by the PCM
if self.CP.pcmCruise:
return
def initialize_v_cruise(v_ego, buttonEvents, v_cruise_last):
for b in buttonEvents:
# 250kph or above probably means we never had a set speed
if b.type in (ButtonType.accelCruise, ButtonType.resumeCruise) and v_cruise_last < 250:
return v_cruise_last
if any(b.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for b in CS.buttonEvents) and self.v_cruise_kph_last < 250:
self.v_cruise_kph = self.v_cruise_kph_last
else:
self.v_cruise_kph = int(round(clip(CS.vEgo * CV.MS_TO_KPH, V_CRUISE_ENABLE_MIN, V_CRUISE_MAX)))
self.v_cruise_cluster_kph = self.v_cruise_kph
return int(round(clip(v_ego * CV.MS_TO_KPH, V_CRUISE_ENABLE_MIN, V_CRUISE_MAX)))
def apply_deadzone(error, deadzone):
if error > deadzone:
error -= deadzone
elif error < - deadzone:
error += deadzone
else:
error = 0.
return error
def rate_limit(new_value, last_value, dw_step, up_step):
return clip(new_value, last_value + dw_step, last_value + up_step)
def get_lag_adjusted_curvature(CP, v_ego, psis, curvatures, curvature_rates):

@ -597,6 +597,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
EventName.buttonCancel: {
ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage),
ET.NO_ENTRY: NoEntryAlert("Cancel Pressed"),
},
EventName.brakeHold: {

@ -301,8 +301,10 @@ class LongitudinalMpc:
return lead_xv
def set_accel_limits(self, min_a, max_a):
# TODO this sets a max accel limit, but the minimum limit is only for cruise decel
# needs refactor
self.cruise_min_a = min_a
self.cruise_max_a = max_a
self.max_a = max_a
def update(self, carstate, radarstate, v_cruise, x, v, a, j):
v_ego = self.x0[1]
@ -317,16 +319,17 @@ class LongitudinalMpc:
lead_0_obstacle = lead_xv_0[:,0] + get_stopped_equivalence_factor(lead_xv_0[:,1])
lead_1_obstacle = lead_xv_1[:,0] + get_stopped_equivalence_factor(lead_xv_1[:,1])
self.params[:,0] = MIN_ACCEL
self.params[:,1] = self.max_a
# Update in ACC mode or ACC/e2e blend
if self.mode == 'acc':
self.params[:,0] = MIN_ACCEL
self.params[:,1] = self.cruise_max_a
self.params[:,5] = LEAD_DANGER_FACTOR
# Fake an obstacle for cruise, this ensures smooth acceleration to set speed
# when the leads are no factor.
v_lower = v_ego + (T_IDXS * self.cruise_min_a * 1.05)
v_upper = v_ego + (T_IDXS * self.cruise_max_a * 1.05)
v_upper = v_ego + (T_IDXS * self.max_a * 1.05)
v_cruise_clipped = np.clip(v_cruise * np.ones(N+1),
v_lower,
v_upper)
@ -338,9 +341,6 @@ class LongitudinalMpc:
x[:], v[:], a[:], j[:] = 0.0, 0.0, 0.0, 0.0
elif self.mode == 'blended':
self.params[:,0] = MIN_ACCEL
self.params[:,1] = MAX_ACCEL
self.params[:,5] = 1.0
x_obstacles = np.column_stack([lead_0_obstacle,

@ -10,7 +10,7 @@ from common.params import Params
from common.realtime import DT_MDL
from selfdrive.modeld.constants import T_IDXS
from selfdrive.controls.lib.longcontrol import LongCtrlState
from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc
from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, MIN_ACCEL, MAX_ACCEL
from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC
from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N
from system.swaglog import cloudlog
@ -69,7 +69,8 @@ class LongitudinalPlanner:
e2e = self.params.get_bool('ExperimentalMode') and self.CP.openpilotLongitudinalControl
self.mpc.mode = 'blended' if e2e else 'acc'
def parse_model(self, model_msg, model_error):
@staticmethod
def parse_model(model_msg, model_error):
if (len(model_msg.position.x) == 33 and
len(model_msg.velocity.x) == 33 and
len(model_msg.acceleration.x) == 33):
@ -103,8 +104,12 @@ class LongitudinalPlanner:
# No change cost when user is controlling the speed, or when standstill
prev_accel_constraint = not (reset_state or sm['carState'].standstill)
accel_limits = [A_CRUISE_MIN, get_max_accel(v_ego)]
accel_limits_turns = limit_accel_in_turns(v_ego, sm['carState'].steeringAngleDeg, accel_limits, self.CP)
if self.mpc.mode == 'acc':
accel_limits = [A_CRUISE_MIN, get_max_accel(v_ego)]
accel_limits_turns = limit_accel_in_turns(v_ego, sm['carState'].steeringAngleDeg, accel_limits, self.CP)
else:
accel_limits = [MIN_ACCEL, MAX_ACCEL]
accel_limits_turns = [MIN_ACCEL, MAX_ACCEL]
if reset_state:
self.v_desired_filter.x = v_ego

@ -1,10 +1,17 @@
#!/usr/bin/env python3
import unittest
import numpy as np
from parameterized import parameterized_class
import unittest
from selfdrive.controls.lib.drive_helpers import VCruiseHelper, V_CRUISE_MAX, V_CRUISE_ENABLE_MIN
from cereal import car
from common.conversions import Conversions as CV
from common.params import Params
from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
ButtonEvent = car.CarState.ButtonEvent
ButtonType = car.CarState.ButtonEvent.Type
from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
def run_cruise_simulation(cruise, t_end=20.):
man = Maneuver(
@ -19,7 +26,7 @@ def run_cruise_simulation(cruise, t_end=20.):
)
valid, output = man.evaluate()
assert valid
return output[-1,3]
return output[-1, 3]
class TestCruiseSpeed(unittest.TestCase):
@ -35,5 +42,87 @@ class TestCruiseSpeed(unittest.TestCase):
self.assertAlmostEqual(simulation_steady_state, cruise_speed, delta=.01, msg=f'Did not reach {speed} m/s')
# TODO: test pcmCruise
@parameterized_class(('pcm_cruise',), [(False,)])
class TestVCruiseHelper(unittest.TestCase):
def setUp(self):
self.CP = car.CarParams(pcmCruise=self.pcm_cruise) # pylint: disable=E1101
self.v_cruise_helper = VCruiseHelper(self.CP)
self.reset_cruise_speed_state()
def reset_cruise_speed_state(self):
# Two resets previous cruise speed
for _ in range(2):
self.v_cruise_helper.update_v_cruise(car.CarState(cruiseState={"available": False}), enabled=False, is_metric=False)
def enable(self, v_ego):
# Simulates user pressing set with a current speed
self.v_cruise_helper.initialize_v_cruise(car.CarState(vEgo=v_ego))
def test_adjust_speed(self):
"""
Asserts speed changes on falling edges of buttons.
"""
self.enable(V_CRUISE_ENABLE_MIN * CV.KPH_TO_MS)
for btn in (ButtonType.accelCruise, ButtonType.decelCruise):
for pressed in (True, False):
CS = car.CarState(cruiseState={"available": True})
CS.buttonEvents = [ButtonEvent(type=btn, pressed=pressed)]
self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False)
self.assertEqual(pressed, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last)
def test_rising_edge_enable(self):
"""
Some car interfaces may enable on rising edge of a button,
ensure we don't adjust speed if enabled changes mid-press.
"""
# NOTE: enabled is always one frame behind the result from button press in controlsd
for enabled, pressed in ((False, False),
(False, True),
(True, False)):
CS = car.CarState(cruiseState={"available": True})
CS.buttonEvents = [ButtonEvent(type=ButtonType.decelCruise, pressed=pressed)]
self.v_cruise_helper.update_v_cruise(CS, enabled=enabled, is_metric=False)
if pressed:
self.enable(V_CRUISE_ENABLE_MIN * CV.KPH_TO_MS)
# Expected diff on enabling. Speed should not change on falling edge of pressed
self.assertEqual(not pressed, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last)
def test_resume_in_standstill(self):
"""
Asserts we don't increment set speed if user presses resume/accel to exit cruise standstill.
"""
self.enable(0)
for standstill in (True, False):
for pressed in (True, False):
CS = car.CarState(cruiseState={"available": True, "standstill": standstill})
CS.buttonEvents = [ButtonEvent(type=ButtonType.accelCruise, pressed=pressed)]
self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False)
# speed should only update if not at standstill and button falling edge
should_equal = standstill or pressed
self.assertEqual(should_equal, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last)
def test_initialize_v_cruise(self):
"""
Asserts allowed cruise speeds on enabling with SET.
"""
for v_ego in np.linspace(0, 100, 101):
self.reset_cruise_speed_state()
self.assertFalse(self.v_cruise_helper.v_cruise_initialized)
self.enable(float(v_ego))
self.assertTrue(V_CRUISE_ENABLE_MIN <= self.v_cruise_helper.v_cruise_kph <= V_CRUISE_MAX)
self.assertTrue(self.v_cruise_helper.v_cruise_initialized)
if __name__ == "__main__":
unittest.main()

@ -4,6 +4,7 @@ import signal
import time
import unittest
from common.params import Params
import selfdrive.manager.manager as manager
from selfdrive.manager.process import DaemonProcess
from selfdrive.manager.process_config import managed_processes
@ -20,6 +21,10 @@ class TestManager(unittest.TestCase):
os.environ['PASSIVE'] = '0'
HARDWARE.set_power_save(False)
# ensure clean CarParams
params = Params()
params.clear_all()
def tearDown(self):
manager.manager_cleanup()
@ -40,6 +45,7 @@ class TestManager(unittest.TestCase):
Ensure all processes exit cleanly when stopped.
"""
HARDWARE.set_power_save(False)
manager.manager_init()
manager.manager_prepare()
for p in ALL_PROCESSES:
managed_processes[p].start()

@ -17,6 +17,7 @@ class Maneuver():
self.only_lead2 = kwargs.get("only_lead2", False)
self.only_radar = kwargs.get("only_radar", False)
self.ensure_start = kwargs.get("ensure_start", False)
self.enabled = kwargs.get("enabled", True)
self.duration = duration
self.title = title
@ -26,23 +27,24 @@ class Maneuver():
lead_relevancy=self.lead_relevancy,
speed=self.speed,
distance_lead=self.distance_lead,
enabled=self.enabled,
only_lead2=self.only_lead2,
only_radar=self.only_radar,
)
valid = True
logs = []
while plant.current_time() < self.duration:
speed_lead = np.interp(plant.current_time(), self.breakpoints, self.speed_lead_values)
prob = np.interp(plant.current_time(), self.breakpoints, self.prob_lead_values)
cruise = np.interp(plant.current_time(), self.breakpoints, self.cruise_values)
while plant.current_time < self.duration:
speed_lead = np.interp(plant.current_time, self.breakpoints, self.speed_lead_values)
prob = np.interp(plant.current_time, self.breakpoints, self.prob_lead_values)
cruise = np.interp(plant.current_time, self.breakpoints, self.cruise_values)
log = plant.step(speed_lead, prob, cruise)
d_rel = log['distance_lead'] - log['distance'] if self.lead_relevancy else 200.
v_rel = speed_lead - log['speed'] if self.lead_relevancy else 0.
log['d_rel'] = d_rel
log['v_rel'] = v_rel
logs.append(np.array([plant.current_time(),
logs.append(np.array([plant.current_time,
log['distance'],
log['distance_lead'],
log['speed'],

@ -10,11 +10,12 @@ from selfdrive.modeld.constants import T_IDXS
from selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner
from selfdrive.controls.lib.radar_helpers import _LEAD_ACCEL_TAU
class Plant():
class Plant:
messaging_initialized = False
def __init__(self, lead_relevancy=False, speed=0.0, distance_lead=2.0,
only_lead2=False, only_radar=False):
enabled=True, only_lead2=False, only_radar=False):
self.rate = 1. / DT_MDL
if not Plant.messaging_initialized:
@ -32,10 +33,11 @@ class Plant():
self.speeds = []
# lead car
self.distance_lead = distance_lead
self.lead_relevancy = lead_relevancy
self.only_lead2=only_lead2
self.only_radar=only_radar
self.distance_lead = distance_lead
self.enabled = enabled
self.only_lead2 = only_lead2
self.only_radar = only_radar
self.rk = Ratekeeper(self.rate, print_delay_threshold=100.0)
self.ts = 1. / self.rate
@ -47,6 +49,7 @@ class Plant():
self.planner = LongitudinalPlanner(CarInterface.get_params(CAR.CIVIC), init_v=self.speed)
@property
def current_time(self):
return float(self.rk.frame) / self.rate
@ -104,9 +107,7 @@ class Plant():
acceleration.x = [float(x) for x in np.zeros_like(T_IDXS)]
model.modelV2.acceleration = acceleration
control.controlsState.longControlState = LongCtrlState.pid
control.controlsState.longControlState = LongCtrlState.pid if self.enabled else LongCtrlState.off
control.controlsState.vCruise = float(v_cruise * 3.6)
car_state.carState.vEgo = float(self.speed)
car_state.carState.standstill = self.speed < 0.01
@ -141,7 +142,7 @@ class Plant():
# print at 5hz
if (self.rk.frame % (self.rate // 5)) == 0:
print("%2.2f sec %6.2f m %6.2f m/s %6.2f m/s2 lead_rel: %6.2f m %6.2f m/s"
% (self.current_time(), self.distance, self.speed, self.acceleration, d_rel, v_rel))
% (self.current_time, self.distance, self.speed, self.acceleration, d_rel, v_rel))
# ******** update prevs ********

@ -10,7 +10,7 @@ from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
# TODO: make new FCW tests
maneuvers = [
Maneuver(
'approach stopped car at 20m/s, initial distance: 120m',
'approach stopped car at 25m/s, initial distance: 120m',
duration=20.,
initial_speed=25.,
lead_relevancy=True,
@ -118,6 +118,13 @@ maneuvers = [
breakpoints=[1., 10., 15.],
ensure_start=True,
),
Maneuver(
'cruising at 25 m/s while disabled',
duration=20.,
initial_speed=25.,
lead_relevancy=False,
enabled=False,
),
]

@ -1 +1 @@
a36f7e2fd922fcadca6f8a3d777f4db787cba016
aa2d370836588fd80b648dbed8d156765ec804d5

@ -1004,11 +1004,11 @@ location set</source>
</message>
<message>
<source>Experimental Mode</source>
<translation> </translation>
<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>
<translation> openpilot은 &lt;b&gt; &lt;/b&gt; . &lt;b&gt; &lt;/b&gt; . &lt;br&gt; &lt;h4&gt;🌮 E2E 🌮&lt;/h4&gt; . openpilot . .</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>
@ -1016,7 +1016,7 @@ location set</source>
</message>
<message>
<source>WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation type="unfinished"></translation>
<translation>경고: openpilot (AEB) .</translation>
</message>
</context>
<context>

@ -1012,7 +1012,7 @@ trabalho definido</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>
<translation> openpilot por padrão funciona em &lt;b&gt;modo chill&lt;/b&gt;. modo Experimental ativa &lt;b&gt;recursos de nível-alfa&lt;/b&gt; que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo: &lt;br&gt; &lt;h4&gt;🌮 Controle Longitudinal de Ponta a Ponta 🌮&lt;/h4&gt; Deixe o modelo de condução controlar o acelerador e os freios. Uma vez que o modelo de condução decide qual velocidade dirigir, a velocidade definida só funcionará como um limite superior.</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>
@ -1020,7 +1020,7 @@ trabalho definido</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>
<translation>ATENÇÃO: o controle longitudinal do openpilot é experimental para este carro e desativará a Frenagem Automática de Emergência (AEB).</translation>
</message>
</context>
<context>

@ -60,11 +60,11 @@
</message>
<message>
<source>Cellular Metered</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<source>Prevent large data uploads when on a metered connection</source>
<translation type="unfinished"></translation>
<translation>使</translation>
</message>
</context>
<context>
@ -240,11 +240,11 @@
</message>
<message>
<source>Reset</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<source>Review</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
</context>
<context>
@ -864,7 +864,7 @@ location set</source>
</message>
<message>
<source>Uninstall</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
</context>
<context>
@ -1004,19 +1004,19 @@ location set</source>
</message>
<message>
<source>Experimental Mode</source>
<translation type="unfinished"></translation>
<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>
<translation> openpilot &lt;b&gt;&lt;/b&gt; &lt;b&gt;alpha &lt;/b&gt; &lt;br&gt; &lt;h4&gt;🌮🌮&lt;/h4&gt; openpilot </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>
<translation>openpilot 使 (ACC)使 openpilot 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>
<translation>openpilot (AEB) </translation>
</message>
</context>
<context>
@ -1074,7 +1074,7 @@ location set</source>
</message>
<message>
<source>Forget</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
</context>
</TS>

@ -1,9 +1,9 @@
[
{
"name": "boot",
"url": "https://commadist.azureedge.net/agnosupdate/boot-57626d7737ab2fa1318e8707a202b1295b5da79ad2fa0a36377cc9481ad0d136.img.xz",
"hash": "57626d7737ab2fa1318e8707a202b1295b5da79ad2fa0a36377cc9481ad0d136",
"hash_raw": "57626d7737ab2fa1318e8707a202b1295b5da79ad2fa0a36377cc9481ad0d136",
"url": "https://commadist.azureedge.net/agnosupdate/boot-72662ec5d586c7a22659a1c8b140932d5472914176020fe76ba4204edbbb214a.img.xz",
"hash": "72662ec5d586c7a22659a1c8b140932d5472914176020fe76ba4204edbbb214a",
"hash_raw": "72662ec5d586c7a22659a1c8b140932d5472914176020fe76ba4204edbbb214a",
"size": 14780416,
"sparse": false,
"full_check": true,
@ -41,9 +41,9 @@
},
{
"name": "system",
"url": "https://commadist.azureedge.net/agnosupdate/system-b40b08912576bb972907acba7c201c1399395cbc0cba06ce6e5e3f70ab565cb5.img.xz",
"hash": "6e8fbcc21a265f7f58062abce7675dc05540e2b60cee2df56992a151ba64936f",
"hash_raw": "b40b08912576bb972907acba7c201c1399395cbc0cba06ce6e5e3f70ab565cb5",
"url": "https://commadist.azureedge.net/agnosupdate/system-9efdc9368f05e06008a7a1dbbee21b564e89988dc94d6ddee3a3a88e42268f0e.img.xz",
"hash": "48209ce7e8cc2fff4ec024f0cd82fc2e3e097b5c0629be2b292acf64e6701449",
"hash_raw": "9efdc9368f05e06008a7a1dbbee21b564e89988dc94d6ddee3a3a88e42268f0e",
"size": 10737418240,
"sparse": true,
"full_check": false,

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

@ -19,7 +19,7 @@ prev_moc_path = cabana_env['QT_MOCHPREFIX']
cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_'
cabana_env.Execute('./generate_dbc_json.py --out car_fingerprint_to_dbc.json')
cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc',
'canmessages.cc', 'messageswidget.cc', 'settings.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
'canmessages.cc', 'commands.cc', 'messageswidget.cc', 'settings.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
cabana_env.Program('_cabana', ['cabana.cc', cabana_lib], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
if GetOption('test'):

@ -165,14 +165,14 @@ void BinaryViewModel::setMessage(const QString &message_id) {
if ((dbc_msg = dbc()->msg(msg_id))) {
row_count = dbc_msg->size;
items.resize(row_count * column_count);
for (int i = 0; i < dbc_msg->sigs.size(); ++i) {
const auto &sig = dbc_msg->sigs[i];
int i = 0;
for (auto &[name, sig] : dbc_msg->sigs) {
auto [start, end] = getSignalRange(&sig);
for (int j = start; j <= end; ++j) {
int bit_index = sig.is_little_endian ? bigEndianBitIndex(j) : j;
int idx = column_count * (bit_index / 8) + bit_index % 8;
if (idx >= items.size()) {
qWarning() << "signal " << sig.name.c_str() << "out of bounds.start_bit:" << sig.start_bit << "size:" << sig.size;
qWarning() << "signal " << name << "out of bounds.start_bit:" << sig.start_bit << "size:" << sig.size;
break;
}
if (j == start) sig.is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true;
@ -180,6 +180,7 @@ void BinaryViewModel::setMessage(const QString &message_id) {
items[idx].bg_color = getColor(i);
items[idx].sigs.push_back(&sig);
}
++i;
}
} else {
row_count = can->lastMessage(msg_id).dat.size();

@ -50,7 +50,7 @@ public:
private:
QString msg_id;
const Msg *dbc_msg;
const DBCMsg *dbc_msg;
int row_count = 0;
const int column_count = 9;
};

@ -44,7 +44,7 @@ QList<QPointF> CANMessages::findSignalValues(const QString &id, const Signal *si
for (auto &evt : *evts) {
if (evt->which != cereal::Event::Which::CAN) continue;
for (auto c : evt->event.getCan()) {
for (const auto &c : evt->event.getCan()) {
if (bus == c.getSrc() && address == c.getAddress()) {
double val = get_raw_value((uint8_t *)c.getDat().begin(), c.getDat().size(), *signal);
if ((flag == EQ && val == value) || (flag == LT && val < value) || (flag == GT && val > value)) {
@ -65,6 +65,7 @@ void CANMessages::process(QHash<QString, CanData> *messages) {
emit updated();
emit msgsReceived(messages);
delete messages;
processing = false;
}
bool CANMessages::eventFilter(const Event *event) {
@ -78,7 +79,7 @@ bool CANMessages::eventFilter(const Event *event) {
}
double current_sec = replay->currentSeconds();
if (counters_begin_sec == 0) {
if (counters_begin_sec == 0 || counters_begin_sec >= current_sec) {
counters.clear();
counters_begin_sec = current_sec;
}
@ -94,7 +95,6 @@ bool CANMessages::eventFilter(const Event *event) {
}
CanData &data = list.emplace_front();
data.ts = current_sec;
data.bus_time = c.getBusTime();
data.dat.append((char *)c.getDat().begin(), c.getDat().size());
data.count = ++counters[id];
@ -105,7 +105,9 @@ bool CANMessages::eventFilter(const Event *event) {
}
double ts = millis_since_boot();
if ((ts - prev_update_ts) > (1000.0 / settings.fps)) {
if ((ts - prev_update_ts) > (1000.0 / settings.fps) && !processing) {
// delay posting CAN message if UI thread is busy
processing = true;
prev_update_ts = ts;
// use pointer to avoid data copy in queued connection.
emit received(new_msgs.release());
@ -120,7 +122,7 @@ const std::deque<CanData> CANMessages::messages(const QString &id) {
}
void CANMessages::seekTo(double ts) {
replay->seekTo(ts, false);
replay->seekTo(std::max(double(0), ts), false);
counters_begin_sec = 0;
}

@ -16,7 +16,6 @@ struct CanData {
double ts = 0.;
uint32_t count = 0;
uint32_t freq = 0;
uint16_t bus_time = 0;
QByteArray dat;
};
@ -63,6 +62,7 @@ protected:
Replay *replay = nullptr;
std::mutex lock;
std::atomic<double> counters_begin_sec = 0;
std::atomic<bool> processing = false;
QHash<QString, uint32_t> counters;
QHash<QString, std::deque<CanData>> received_msgs;
};

@ -192,6 +192,8 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent)
chart->createDefaultAxes();
chart->legend()->hide();
chart->layout()->setContentsMargins(0, 0, 0, 0);
// top margin for title
chart->setMargins({0, 11, 0, 0});
line_marker = new QGraphicsLineItem(chart);
line_marker->setZValue(chart->zValue() + 10);
@ -205,7 +207,6 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent)
item_group->setZValue(chart->zValue() + 10);
// title
msg_title = new QGraphicsTextItem(chart);
QToolButton *remove_btn = new QToolButton();
remove_btn->setText("X");
remove_btn->setAutoRaise(true);
@ -234,13 +235,11 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent)
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));
chart()->setTitle(tr("<font color=\"gray\" text-align:left>%1 %2</font> <b>%3</b>").arg(dbc()->msg(id)->name).arg(id).arg(signal->name.c_str()));
}
void ChartView::updateFromSettings() {
@ -248,7 +247,6 @@ void ChartView::updateFromSettings() {
chart()->setTheme(settings.chart_theme == 0 ? QChart::ChartThemeLight : QChart::QChart::ChartThemeDark);
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) {
@ -265,6 +263,7 @@ void ChartView::adjustChartMargins() {
if (chart()->plotArea().left() != aligned_pos) {
const float left_margin = chart()->margins().left() + aligned_pos - chart()->plotArea().left();
chart()->setMargins(QMargins(left_margin, 11, 0, 0));
updateLineMarker(can->currentSec());
}
}
@ -290,7 +289,7 @@ void ChartView::updateSeries(const std::pair<double, double> range) {
double end_ns = (route_start_time + range.second) * 1e9;
for (auto it = begin; it != events->end() && (*it)->mono_time <= end_ns; ++it) {
if ((*it)->which == cereal::Event::Which::CAN) {
for (auto c : (*it)->event.getCan()) {
for (const auto &c : (*it)->event.getCan()) {
if (bus == c.getSrc() && address == c.getAddress()) {
auto dat = c.getDat();
double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *signal);

@ -44,7 +44,7 @@ private:
QGraphicsItemGroup *item_group;
QGraphicsLineItem *line_marker, *track_line;
QGraphicsEllipseItem *track_ellipse;
QGraphicsTextItem *value_text, *msg_title;
QGraphicsTextItem *value_text;
QGraphicsProxyWidget *close_btn_proxy;
QVector<QPointF> vals;
};

@ -0,0 +1,75 @@
#include "tools/cabana/commands.h"
// EditMsgCommand
EditMsgCommand::EditMsgCommand(const QString &id, const QString &title, int size, QUndoCommand *parent)
: id(id), new_title(title), new_size(size), QUndoCommand(parent) {
if (auto msg = dbc()->msg(id)) {
old_title = msg->name;
old_size = msg->size;
}
setText(QObject::tr("Edit message %1:%2").arg(DBCManager::parseId(id).second).arg(title));
}
void EditMsgCommand::undo() {
if (old_title.isEmpty())
dbc()->removeMsg(id);
else
dbc()->updateMsg(id, old_title, old_size);
}
void EditMsgCommand::redo() {
dbc()->updateMsg(id, new_title, new_size);
}
// RemoveMsgCommand
RemoveMsgCommand::RemoveMsgCommand(const QString &id, QUndoCommand *parent) : id(id), QUndoCommand(parent) {
if (auto msg = dbc()->msg(id)) {
message = *msg;
setText(QObject::tr("Remove message %1:%2").arg(DBCManager::parseId(id).second).arg(message.name));
}
}
void RemoveMsgCommand::undo() {
if (!message.name.isEmpty()) {
dbc()->updateMsg(id, message.name, message.size);
for (auto &[name, s] : message.sigs)
dbc()->addSignal(id, s);
}
}
void RemoveMsgCommand::redo() {
if (!message.name.isEmpty())
dbc()->removeMsg(id);
}
// AddSigCommand
AddSigCommand::AddSigCommand(const QString &id, const Signal &sig, QUndoCommand *parent)
: id(id), signal(sig), QUndoCommand(parent) {
setText(QObject::tr("Add signal %1 to %2").arg(sig.name.c_str()).arg(DBCManager::parseId(id).second));
}
void AddSigCommand::undo() { dbc()->removeSignal(id, signal.name.c_str()); }
void AddSigCommand::redo() { dbc()->addSignal(id, signal); }
// RemoveSigCommand
RemoveSigCommand::RemoveSigCommand(const QString &id, const Signal *sig, QUndoCommand *parent)
: id(id), signal(*sig), QUndoCommand(parent) {
setText(QObject::tr("Remove signal %1 from %2").arg(signal.name.c_str()).arg(DBCManager::parseId(id).second));
}
void RemoveSigCommand::undo() { dbc()->addSignal(id, signal); }
void RemoveSigCommand::redo() { dbc()->removeSignal(id, signal.name.c_str()); }
// EditSignalCommand
EditSignalCommand::EditSignalCommand(const QString &id, const Signal *sig, const Signal &new_sig, QUndoCommand *parent)
: id(id), old_signal(*sig), new_signal(new_sig), QUndoCommand(parent) {
setText(QObject::tr("Edit signal %1").arg(old_signal.name.c_str()));
}
void EditSignalCommand::undo() { dbc()->updateSignal(id, new_signal.name.c_str(), old_signal); }
void EditSignalCommand::redo() { dbc()->updateSignal(id, old_signal.name.c_str(), new_signal); }

@ -0,0 +1,63 @@
#pragma once
#include <QUndoCommand>
#include "tools/cabana/canmessages.h"
#include "tools/cabana/dbcmanager.h"
class EditMsgCommand : public QUndoCommand {
public:
EditMsgCommand(const QString &id, const QString &title, int size, QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
private:
const QString id;
QString old_title, new_title;
int old_size = 0, new_size = 0;
};
class RemoveMsgCommand : public QUndoCommand {
public:
RemoveMsgCommand(const QString &id, QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
private:
const QString id;
DBCMsg message;
};
class AddSigCommand : public QUndoCommand {
public:
AddSigCommand(const QString &id, const Signal &sig, QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
private:
const QString id;
Signal signal = {};
};
class RemoveSigCommand : public QUndoCommand {
public:
RemoveSigCommand(const QString &id, const Signal *sig, QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
private:
const QString id;
Signal signal = {};
};
class EditSignalCommand : public QUndoCommand {
public:
EditSignalCommand(const QString &id, const Signal *sig, const Signal &new_sig, QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
private:
const QString id;
Signal old_signal = {};
Signal new_signal = {};
};

@ -10,32 +10,36 @@ DBCManager::~DBCManager() {}
void DBCManager::open(const QString &dbc_file_name) {
dbc = const_cast<DBC *>(dbc_lookup(dbc_file_name.toStdString()));
updateMsgMap();
emit DBCFileChanged();
initMsgMap();
}
void DBCManager::open(const QString &name, const QString &content) {
std::istringstream stream(content.toStdString());
dbc = const_cast<DBC *>(dbc_parse_from_stream(name.toStdString(), stream));
updateMsgMap();
emit DBCFileChanged();
initMsgMap();
}
void DBCManager::updateMsgMap() {
msg_map.clear();
for (auto &msg : dbc->msgs)
msg_map[msg.address] = &msg;
void DBCManager::initMsgMap() {
msgs.clear();
for (auto &msg : dbc->msgs) {
auto &m = msgs[msg.address];
m.name = msg.name.c_str();
m.size = msg.size;
for (auto &s : msg.sigs)
m.sigs[QString::fromStdString(s.name)] = s;
}
emit DBCFileChanged();
}
QString DBCManager::generateDBC() {
if (!dbc) return {};
QString dbc_string;
for (auto &m : dbc->msgs) {
dbc_string += QString("BO_ %1 %2: %3 XXX\n").arg(m.address).arg(m.name.c_str()).arg(m.size);
for (auto &sig : m.sigs) {
for (auto &[address, m] : msgs) {
dbc_string += QString("BO_ %1 %2: %3 XXX\n").arg(address).arg(m.name).arg(m.size);
for (auto &[name, sig] : m.sigs) {
dbc_string += QString(" SG_ %1 : %2|%3@%4%5 (%6,%7) [0|0] \"\" XXX\n")
.arg(sig.name.c_str())
.arg(name)
.arg(sig.start_bit)
.arg(sig.size)
.arg(sig.is_little_endian ? '1' : '0')
@ -49,48 +53,45 @@ QString DBCManager::generateDBC() {
}
void DBCManager::updateMsg(const QString &id, const QString &name, uint32_t size) {
auto [bus, address] = parseId(id);
if (auto m = const_cast<Msg *>(msg(address))) {
m->name = name.toStdString();
m->size = size;
} else {
m = &dbc->msgs.emplace_back(Msg{.address = address, .name = name.toStdString(), .size = size});
msg_map[address] = m;
}
auto [_, address] = parseId(id);
auto &m = msgs[address];
m.name = name;
m.size = size;
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);
}
msgs.erase(address);
emit msgRemoved(address);
}
void DBCManager::addSignal(const QString &id, const Signal &sig) {
if (Msg *m = const_cast<Msg *>(msg(id))) {
emit signalAdded(&m->sigs.emplace_back(sig));
if (auto m = const_cast<DBCMsg *>(msg(id))) {
auto &s = m->sigs[sig.name.c_str()];
s = sig;
emit signalAdded(&s);
}
}
void DBCManager::updateSignal(const QString &id, const QString &sig_name, const Signal &sig) {
if (Msg *m = const_cast<Msg *>(msg(id))) {
auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [=](auto &sig) { return sig_name == sig.name.c_str(); });
if (it != m->sigs.end()) {
*it = sig;
emit signalUpdated(&(*it));
}
if (auto m = const_cast<DBCMsg *>(msg(id))) {
// change key name
QString new_name = QString::fromStdString(sig.name);
auto node = m->sigs.extract(sig_name);
node.key() = new_name;
auto it = m->sigs.insert(std::move(node));
auto &s = m->sigs[new_name];
s = sig;
emit signalUpdated(&s);
}
}
void DBCManager::removeSignal(const QString &id, const QString &sig_name) {
if (Msg *m = const_cast<Msg *>(msg(id))) {
auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [=](auto &sig) { return sig_name == sig.name.c_str(); });
if (auto m = const_cast<DBCMsg *>(msg(id))) {
auto it = m->sigs.find(sig_name);
if (it != m->sigs.end()) {
emit signalRemoved(&(*it));
emit signalRemoved(&(it->second));
m->sigs.erase(it);
}
}
@ -164,3 +165,11 @@ std::pair<int, int> getSignalRange(const Signal *s) {
int to = from + s->size - 1;
return {from, to};
}
bool operator==(const Signal &l, const Signal &r) {
return l.name == r.name && l.size == r.size &&
l.start_bit == r.start_bit &&
l.msb == r.msb && l.lsb == r.lsb &&
l.is_signed == r.is_signed && l.is_little_endian == r.is_little_endian &&
l.factor == r.factor && l.offset == r.offset;
}

@ -1,9 +1,16 @@
#pragma once
#include <map>
#include <QObject>
#include <QString>
#include "opendbc/can/common_dbc.h"
struct DBCMsg {
QString name;
uint32_t size;
std::map<QString, Signal> sigs;
};
class DBCManager : public QObject {
Q_OBJECT
@ -24,11 +31,11 @@ public:
void updateMsg(const QString &id, const QString &name, uint32_t size);
void removeMsg(const QString &id);
inline const DBC *getDBC() const { return dbc; }
inline const Msg *msg(const QString &id) const { return msg(parseId(id).second); }
inline const Msg *msg(uint32_t address) const {
auto it = msg_map.find(address);
return it != msg_map.end() ? it->second : nullptr;
inline const std::map<uint32_t, DBCMsg> &messages() const { return msgs; }
inline const DBCMsg *msg(const QString &id) const { return msg(parseId(id).second); }
inline const DBCMsg *msg(uint32_t address) const {
auto it = msgs.find(address);
return it != msgs.end() ? &it->second : nullptr;
}
signals:
@ -40,13 +47,15 @@ signals:
void DBCFileChanged();
private:
void updateMsgMap();
void initMsgMap();
DBC *dbc = nullptr;
std::unordered_map<uint32_t, const Msg *> msg_map;
std::map<uint32_t, DBCMsg> msgs;
};
// TODO: Add helper function in dbc.h
double get_raw_value(uint8_t *data, size_t data_size, const Signal &sig);
bool operator==(const Signal &l, const Signal &r);
inline bool operator!=(const Signal &l, const Signal &r) { return !(l == r); }
int bigEndianStartBitsIndex(int start_bit);
int bigEndianBitIndex(int index);
void updateSigSizeParamsFromRange(Signal &s, int start_bit, int size);
@ -54,5 +63,5 @@ std::pair<int, int> getSignalRange(const Signal *s);
DBCManager *dbc();
inline QString msgName(const QString &id, const char *def = "untitled") {
auto msg = dbc()->msg(id);
return msg ? msg->name.c_str() : def;
return msg ? msg->name : def;
}

@ -6,20 +6,22 @@
#include <QMessageBox>
#include <QScrollBar>
#include <QTimer>
#include <QVBoxLayout>
#include "selfdrive/ui/qt/util.h"
#include "tools/cabana/canmessages.h"
#include "tools/cabana/commands.h"
#include "tools/cabana/dbcmanager.h"
// DetailWidget
DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(charts), QWidget(parent) {
undo_stack = new QUndoStack(this);
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0);
main_layout->setSpacing(0);
// tabbar
// tabbar
tabbar = new QTabBar(this);
tabbar->setTabsClosable(true);
tabbar->setDrawBase(false);
@ -78,9 +80,8 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
container_layout->addWidget(binary_view);
// signals
signals_container = new QWidget(this);
signals_container->setLayout(new QVBoxLayout);
container_layout->addWidget(signals_container);
signals_layout = new QVBoxLayout();
container_layout->addLayout(signals_layout);
// history log
history_log = new HistoryLog(this);
@ -88,7 +89,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
QObject::connect(binary_view, &BinaryView::resizeSignal, this, &DetailWidget::resizeSignal);
QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal);
QObject::connect(can, &CANMessages::updated, this, &DetailWidget::updateState);
QObject::connect(can, &CANMessages::msgsReceived, this, &DetailWidget::updateState);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { dbcMsgChanged(); });
QObject::connect(tabbar, &QTabBar::customContextMenuRequested, this, &DetailWidget::showTabBarContextMenu);
QObject::connect(tabbar, &QTabBar::currentChanged, [this](int index) {
@ -99,6 +100,10 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
QObject::connect(tabbar, &QTabBar::tabCloseRequested, tabbar, &QTabBar::removeTab);
QObject::connect(charts, &ChartsWidget::chartOpened, [this](const QString &id, const Signal *sig) { updateChartState(id, sig, true); });
QObject::connect(charts, &ChartsWidget::chartClosed, [this](const QString &id, const Signal *sig) { updateChartState(id, sig, false); });
QObject::connect(undo_stack, &QUndoStack::indexChanged, [this]() {
if (undo_stack->count() > 0)
dbcMsgChanged();
});
}
void DetailWidget::showTabBarContextMenu(const QPoint &pt) {
@ -107,103 +112,82 @@ void DetailWidget::showTabBarContextMenu(const QPoint &pt) {
QMenu menu(this);
menu.addAction(tr("Close Other Tabs"));
if (menu.exec(tabbar->mapToGlobal(pt))) {
tabbar->setCurrentIndex(index);
// remove all tabs before the one to keep
for (int i = 0; i < index; ++i) {
tabbar->removeTab(0);
}
// remove all tabs after the one to keep
while (tabbar->count() > 1) {
tabbar->moveTab(index, 0);
tabbar->setCurrentIndex(0);
while (tabbar->count() > 1)
tabbar->removeTab(1);
}
}
}
}
void DetailWidget::setMessage(const QString &message_id) {
if (message_id.isEmpty()) return;
int index = -1;
for (int i = 0; i < tabbar->count(); ++i) {
if (tabbar->tabText(i) == message_id) {
index = i;
break;
}
}
msg_id = message_id;
int index = tabbar->count() - 1;
for (/**/; index >= 0 && tabbar->tabText(index) != msg_id; --index) { /**/ }
if (index == -1) {
index = tabbar->addTab(message_id);
tabbar->setTabToolTip(index, msgName(message_id));
}
tabbar->setCurrentIndex(index);
dbcMsgChanged();
scroll->verticalScrollBar()->setValue(0);
}
void DetailWidget::dbcMsgChanged(int show_form_idx) {
if (msg_id.isEmpty()) return;
setUpdatesEnabled(false);
QStringList warnings;
for (auto f : signal_list) f->hide();
const Msg *msg = dbc()->msg(msg_id);
binary_view->setMessage(msg_id);
history_log->setMessage(msg_id);
int i = 0;
QStringList warnings;
const DBCMsg *msg = dbc()->msg(msg_id);
if (msg) {
for (int i = 0; i < msg->sigs.size(); ++i) {
for (auto &[name, sig] : msg->sigs) {
SignalEdit *form = i < signal_list.size() ? signal_list[i] : nullptr;
if (!form) {
form = new SignalEdit(i);
QObject::connect(form, &SignalEdit::showFormClicked, this, &DetailWidget::showForm);
QObject::connect(form, &SignalEdit::remove, this, &DetailWidget::removeSignal);
QObject::connect(form, &SignalEdit::save, this, &DetailWidget::saveSignal);
QObject::connect(form, &SignalEdit::highlight, binary_view, &BinaryView::highlight);
QObject::connect(binary_view, &BinaryView::signalHovered, form, &SignalEdit::signalHovered);
QObject::connect(form, &SignalEdit::showChart, charts, &ChartsWidget::showChart);
signals_container->layout()->addWidget(form);
signals_layout->addWidget(form);
signal_list.push_back(form);
}
form->setSignal(msg_id, &(msg->sigs[i]), i == show_form_idx);
form->setChartOpened(charts->isChartOpened(msg_id, &(msg->sigs[i])));
form->show();
form->setSignal(msg_id, &sig);
form->setChartOpened(charts->isChartOpened(msg_id, &sig));
++i;
}
if (msg->size != can->lastMessage(msg_id).dat.size())
warnings.push_back(tr("Message size (%1) is incorrect.").arg(msg->size));
}
for (/**/; i < signal_list.size(); ++i)
signal_list[i]->hide();
toolbar->setVisible(!msg_id.isEmpty());
remove_msg_act->setEnabled(msg != nullptr);
name_label->setText(msgName(msg_id));
binary_view->setMessage(msg_id);
history_log->setMessage(msg_id);
// Check overlapping bits
if (auto overlapping = binary_view->getOverlappingSignals(); !overlapping.isEmpty()) {
for (auto s : overlapping)
warnings.push_back(tr("%1 has overlapping bits.").arg(s->name.c_str()));
}
for (auto s : binary_view->getOverlappingSignals())
warnings.push_back(tr("%1 has overlapping bits.").arg(s->name.c_str()));
warning_label->setText(warnings.join('\n'));
warning_widget->setVisible(!warnings.isEmpty());
setUpdatesEnabled(true);
scroll->verticalScrollBar()->setValue(0);
QTimer::singleShot(1, [this]() { setUpdatesEnabled(true); });
}
void DetailWidget::updateState() {
void DetailWidget::updateState(const QHash<QString, CanData> * msgs) {
time_label->setText(QString::number(can->currentSec(), 'f', 3));
if (msg_id.isEmpty()) return;
if (!msgs->contains(msg_id))
return;
binary_view->updateState();
history_log->updateState();
}
void DetailWidget::showForm() {
SignalEdit *sender = qobject_cast<SignalEdit *>(QObject::sender());
setUpdatesEnabled(false);
for (auto f : signal_list)
f->setFormVisible(f == sender && !f->isFormVisible());
QTimer::singleShot(1, [this]() { setUpdatesEnabled(true); });
}
void DetailWidget::updateChartState(const QString &id, const Signal *sig, bool opened) {
for (auto f : signal_list)
if (f->msg_id == id && f->sig == sig) f->setChartOpened(opened);
@ -215,45 +199,34 @@ void DetailWidget::editMsg() {
int size = msg ? msg->size : can->lastMessage(id).dat.size();
EditMessageDialog dlg(id, msgName(id), size, this);
if (dlg.exec()) {
dbc()->updateMsg(id, dlg.name_edit->text(), dlg.size_spin->value());
dbcMsgChanged();
undo_stack->push(new EditMsgCommand(msg_id, dlg.name_edit->text(), dlg.size_spin->value()));
}
}
void DetailWidget::removeMsg() {
QString id = msg_id;
if (auto msg = dbc()->msg(id)) {
QString text = tr("Are you sure you want to remove '%1'").arg(msg->name.c_str());
if (QMessageBox::Yes == QMessageBox::question(this, tr("Remove Message"), text)) {
dbc()->removeMsg(id);
dbcMsgChanged();
}
}
undo_stack->push(new RemoveMsgCommand(msg_id));
}
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());
QString name = QString("NEW_MSG_%1").arg(i);
auto it = std::find_if(dbc()->messages().begin(), dbc()->messages().end(), [&](auto &m) { return m.second.name == name; });
if (it == dbc()->messages().end()) {
undo_stack->push(new EditMsgCommand(msg_id, name, can->lastMessage(msg_id).dat.size()));
msg = dbc()->msg(msg_id);
break;
}
}
}
Signal sig = {};
Signal sig = {.is_little_endian = little_endian};
for (int i = 1; /**/; ++i) {
sig.name = "NEW_SIGNAL_" + std::to_string(i);
auto it = std::find_if(msg->sigs.begin(), msg->sigs.end(), [&](auto &s) { return sig.name == s.name; });
if (it == msg->sigs.end()) break;
if (msg->sigs.count(sig.name.c_str()) == 0) break;
}
sig.is_little_endian = little_endian;
updateSigSizeParamsFromRange(sig, start_bit, size);
dbc()->addSignal(msg_id, sig);
dbcMsgChanged(msg->sigs.size() - 1);
undo_stack->push(new AddSigCommand(msg_id, sig));
}
void DetailWidget::resizeSignal(const Signal *sig, int start_bit, int size) {
@ -265,14 +238,13 @@ void DetailWidget::resizeSignal(const Signal *sig, int start_bit, int size) {
void DetailWidget::saveSignal(const Signal *sig, const Signal &new_sig) {
auto msg = dbc()->msg(msg_id);
if (new_sig.name != sig->name) {
auto it = std::find_if(msg->sigs.begin(), msg->sigs.end(), [&](auto &s) { return s.name == new_sig.name; });
auto it = msg->sigs.find(new_sig.name.c_str());
if (it != msg->sigs.end()) {
QString warning_str = tr("There is already a signal with the same name '%1'").arg(new_sig.name.c_str());
QMessageBox::warning(this, tr("Failed to save signal"), warning_str);
return;
}
}
auto [start, end] = getSignalRange(&new_sig);
if (start < 0 || end >= msg->size * 8) {
QString warning_str = tr("Signal size [%1] exceed limit").arg(new_sig.size);
@ -280,16 +252,11 @@ void DetailWidget::saveSignal(const Signal *sig, const Signal &new_sig) {
return;
}
dbc()->updateSignal(msg_id, sig->name.c_str(), new_sig);
dbcMsgChanged();
undo_stack->push(new EditSignalCommand(msg_id, sig, new_sig));
}
void DetailWidget::removeSignal(const Signal *sig) {
QString text = tr("Are you sure you want to remove signal '%1'").arg(sig->name.c_str());
if (QMessageBox::Yes == QMessageBox::question(this, tr("Remove signal"), text)) {
dbc()->removeSignal(msg_id, sig->name.c_str());
dbcMsgChanged();
}
undo_stack->push(new RemoveSigCommand(msg_id, sig));
}
// EditMessageDialog
@ -300,6 +267,7 @@ EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title
form_layout->addRow("ID", new QLabel(msg_id));
name_edit = new QLineEdit(title, this);
name_edit->setValidator(new QRegExpValidator(QRegExp("^(\\w+)"), name_edit));
form_layout->addRow(tr("Name"), name_edit);
size_spin = new QSpinBox(this);

@ -3,6 +3,7 @@
#include <QScrollArea>
#include <QTabBar>
#include <QToolBar>
#include <QUndoStack>
#include "tools/cabana/binaryview.h"
#include "tools/cabana/chartswidget.h"
@ -10,8 +11,6 @@
#include "tools/cabana/signaledit.h"
class EditMessageDialog : public QDialog {
Q_OBJECT
public:
EditMessageDialog(const QString &msg_id, const QString &title, int size, QWidget *parent);
@ -26,6 +25,7 @@ public:
DetailWidget(ChartsWidget *charts, QWidget *parent);
void setMessage(const QString &message_id);
void dbcMsgChanged(int show_form_idx = -1);
QUndoStack *undo_stack = nullptr;
private:
void updateChartState(const QString &id, const Signal *sig, bool opened);
@ -36,13 +36,12 @@ private:
void removeSignal(const Signal *sig);
void editMsg();
void removeMsg();
void showForm();
void updateState();
void updateState(const QHash<QString, CanData> * msgs);
QString msg_id;
QLabel *name_label, *time_label, *warning_label;
QWidget *warning_widget;
QWidget *signals_container;
QVBoxLayout *signals_layout;
QTabBar *tabbar;
QToolBar *toolbar;
QAction *remove_msg_act;

@ -4,6 +4,10 @@
// HistoryLogModel
inline const Signal &get_signal(const DBCMsg *m, int index) {
return std::next(m->sigs.begin(), index)->second;
}
QVariant HistoryLogModel::data(const QModelIndex &index, int role) const {
bool has_signal = dbc_msg && !dbc_msg->sigs.empty();
if (role == Qt::DisplayRole) {
@ -11,7 +15,7 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const {
if (index.column() == 0) {
return QString::number(m.ts, 'f', 2);
}
return has_signal ? QString::number(get_raw_value((uint8_t *)m.dat.begin(), m.dat.size(), dbc_msg->sigs[index.column() - 1]))
return has_signal ? QString::number(get_raw_value((uint8_t *)m.dat.begin(), m.dat.size(), get_signal(dbc_msg, index.column() - 1)))
: toHex(m.dat);
} else if (role == Qt::FontRole && index.column() == 1 && !has_signal) {
return QFontDatabase::systemFont(QFontDatabase::FixedFont);
@ -37,7 +41,7 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i
if (section == 0) {
return "Time";
}
return has_signal ? QString::fromStdString(dbc_msg->sigs[section - 1].name).replace('_', ' ') : "Data";
return has_signal ? QString::fromStdString(get_signal(dbc_msg, section - 1).name).replace('_', ' ') : "Data";
} else if (role == Qt::BackgroundRole && section > 0 && has_signal) {
return QBrush(QColor(getColor(section - 1)));
}

@ -28,7 +28,7 @@ private:
QString msg_id;
int row_count = 0;
int column_count = 2;
const Msg *dbc_msg = nullptr;
const DBCMsg *dbc_msg = nullptr;
std::deque<CanData> messages;
};

@ -1,5 +1,6 @@
#include "tools/cabana/mainwin.h"
#include <iostream>
#include <QApplication>
#include <QClipboard>
#include <QCompleter>
@ -12,12 +13,15 @@
#include <QMessageBox>
#include <QScreen>
#include <QToolBar>
#include <QUndoView>
#include <QVBoxLayout>
#include <QWidgetAction>
#include "tools/replay/util.h"
static MainWindow *main_win = nullptr;
void qLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
if (type == QtDebugMsg) std::cout << msg.toStdString() << std::endl;
if (main_win) emit main_win->showMessage(msg, 0);
}
@ -41,9 +45,7 @@ MainWindow::MainWindow() : QMainWindow() {
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);
@ -102,9 +104,13 @@ MainWindow::MainWindow() : QMainWindow() {
QObject::connect(charts_widget, &ChartsWidget::rangeChanged, video_widget, &VideoWidget::rangeChanged);
QObject::connect(can, &CANMessages::streamStarted, this, &MainWindow::loadDBCFromFingerprint);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() {
detail_widget->undo_stack->clear();
dbc_combo->setCurrentText(QFileInfo(dbc()->name()).baseName());
setWindowTitle(tr("%1 - Cabana").arg(dbc()->name()));
});
QObject::connect(detail_widget->undo_stack, &QUndoStack::indexChanged, [this](int index) {
setWindowTitle(tr("%1%2 - Cabana").arg(index > 0 ? "* " : "").arg(dbc()->name()));
});
}
void MainWindow::createActions() {
@ -116,6 +122,23 @@ void MainWindow::createActions() {
file_menu->addAction(tr("Copy DBC To Clipboard"), this, &MainWindow::saveDBCToClipboard);
file_menu->addSeparator();
file_menu->addAction(tr("Settings..."), this, &MainWindow::setOption);
QMenu *edit_menu = menuBar()->addMenu(tr("&Edit"));
auto undo_act = detail_widget->undo_stack->createUndoAction(this, tr("&Undo"));
undo_act->setShortcuts(QKeySequence::Undo);
edit_menu->addAction(undo_act);
auto redo_act = detail_widget->undo_stack->createRedoAction(this, tr("&Rndo"));
redo_act->setShortcuts(QKeySequence::Redo);
edit_menu->addAction(redo_act);
edit_menu->addSeparator();
QMenu *commands_menu = edit_menu->addMenu(tr("Command &List"));
auto undo_view = new QUndoView(detail_widget->undo_stack);
undo_view->setWindowTitle(tr("Command List"));
QWidgetAction *commands_act = new QWidgetAction(this);
commands_act->setDefaultWidget(undo_view);
commands_menu->addAction(commands_act);
QMenu *help_menu = menuBar()->addMenu(tr("&Help"));
help_menu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
}
@ -135,8 +158,9 @@ void MainWindow::loadDBCFromName(const QString &name) {
}
void MainWindow::loadDBCFromFile() {
QString file_name = QFileDialog::getOpenFileName(this, tr("Open File"), QDir::homePath(), "DBC (*.dbc)");
QString file_name = QFileDialog::getOpenFileName(this, tr("Open File"), settings.last_dir, "DBC (*.dbc)");
if (!file_name.isEmpty()) {
settings.last_dir = QFileInfo(file_name).absolutePath();
QFile file(file_name);
if (file.open(QIODevice::ReadOnly)) {
auto dbc_name = QFileInfo(file_name).baseName();
@ -164,11 +188,13 @@ void MainWindow::loadDBCFromFingerprint() {
void MainWindow::saveDBCToFile() {
QString file_name = QFileDialog::getSaveFileName(this, tr("Save File"),
QDir::homePath() + "/untitled.dbc", tr("DBC (*.dbc)"));
QDir::cleanPath(settings.last_dir + "/untitled.dbc"), tr("DBC (*.dbc)"));
if (!file_name.isEmpty()) {
settings.last_dir = QFileInfo(file_name).absolutePath();
QFile file(file_name);
if (file.open(QIODevice::WriteOnly))
file.write(dbc()->generateDBC().toUtf8());
detail_widget->undo_stack->clear();
}
}
@ -204,9 +230,21 @@ void MainWindow::dockCharts(bool dock) {
}
void MainWindow::closeEvent(QCloseEvent *event) {
if (detail_widget->undo_stack->index() > 0) {
auto ret = QMessageBox::question(this, tr("Unsaved Changes"),
tr("Are you sure you want to exit without saving?\nAny unsaved changes will be lost."),
QMessageBox::Yes | QMessageBox::No);
if (ret == QMessageBox::No) {
event->ignore();
return;
}
}
main_win = nullptr;
if (floating_window)
floating_window->deleteLater();
settings.save();
QWidget::closeEvent(event);
}

@ -1,6 +1,7 @@
#include "tools/cabana/settings.h"
#include <QDialogButtonBox>
#include <QDir>
#include <QFormLayout>
#include <QSettings>
@ -19,7 +20,7 @@ void Settings::save() {
s.setValue("chart_height", chart_height);
s.setValue("chart_theme", chart_theme);
s.setValue("max_chart_x_range", max_chart_x_range);
emit changed();
s.setValue("last_dir", last_dir);
}
void Settings::load() {
@ -30,6 +31,7 @@ void Settings::load() {
chart_height = s.value("chart_height", 200).toInt();
chart_theme = s.value("chart_theme", 0).toInt();
max_chart_x_range = s.value("max_chart_x_range", 3 * 60).toInt();
last_dir = s.value("last_dir", QDir::homePath()).toString();
}
// SettingsDlg
@ -90,4 +92,5 @@ void SettingsDlg::save() {
settings.max_chart_x_range = max_chart_x_range->value() * 60;
settings.save();
accept();
emit settings.changed();
}

@ -18,6 +18,7 @@ public:
int chart_height = 200;
int chart_theme = 0;
int max_chart_x_range = 3 * 60; // 3 minutes
QString last_dir;
signals:
void changed();

@ -1,12 +1,11 @@
#include "tools/cabana/signaledit.h"
#include <QDialogButtonBox>
#include <QDoubleValidator>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QRadioButton>
#include <QScrollArea>
#include <QTimer>
#include <QToolBar>
#include <QVBoxLayout>
#include "selfdrive/ui/qt/util.h"
@ -15,9 +14,9 @@
SignalForm::SignalForm(QWidget *parent) : QWidget(parent) {
QFormLayout *form_layout = new QFormLayout(this);
form_layout->setContentsMargins(0, 0, 0, 0);
name = new QLineEdit();
name->setValidator(new QRegExpValidator(QRegExp("^(\\w+)"), name));
form_layout->addRow(tr("Name"), name);
size = new QSpinBox();
@ -58,6 +57,13 @@ SignalForm::SignalForm(QWidget *parent) : QWidget(parent) {
form_layout->addRow(tr("Maximum value"), max_val);
val_desc = new QLineEdit();
form_layout->addRow(tr("Value descriptions"), val_desc);
QObject::connect(name, &QLineEdit::textEdited, this, &SignalForm::changed);
QObject::connect(factor, &QLineEdit::textEdited, this, &SignalForm::changed);
QObject::connect(offset, &QLineEdit::textEdited, this, &SignalForm::changed);
QObject::connect(sign, SIGNAL(activated(int)), SIGNAL(changed()));
QObject::connect(endianness, SIGNAL(activated(int)), SIGNAL(changed()));
QObject::connect(size, SIGNAL(valueChanged(int)), SIGNAL(changed()));
}
// SignalEdit
@ -65,38 +71,45 @@ SignalForm::SignalForm(QWidget *parent) : QWidget(parent) {
SignalEdit::SignalEdit(int index, QWidget *parent) : form_idx(index), QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0);
main_layout->setSpacing(0);
// title bar
QHBoxLayout *title_layout = new QHBoxLayout();
icon = new QLabel();
auto title_bar = new QWidget(this);
title_bar->setFixedHeight(32);
QHBoxLayout *title_layout = new QHBoxLayout(title_bar);
title_layout->setContentsMargins(0, 0, 0, 0);
title_bar->setStyleSheet("QToolButton {width:15px;height:15px;font-size:15px}");
color_label = new QLabel(this);
color_label->setFixedWidth(25);
color_label->setContentsMargins(5, 0, 0, 0);
title_layout->addWidget(color_label);
icon = new QLabel(this);
title_layout->addWidget(icon);
title = new ElidedLabel(this);
title->setStyleSheet(QString("font-weight:bold; color:%1").arg(getColor(index)));
title_layout->addWidget(title, 1);
title->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
title_layout->addWidget(title);
QPushButton *seek_btn = new QPushButton("");
seek_btn->setStyleSheet("QPushButton{font-weight:bold;font-size:18px}");
plot_btn = new QToolButton(this);
plot_btn->setText("📈");
plot_btn->setCheckable(true);
plot_btn->setAutoRaise(true);
title_layout->addWidget(plot_btn);
auto seek_btn = new QToolButton(this);
seek_btn->setIcon(QIcon::fromTheme("edit-find"));
seek_btn->setAutoRaise(true);
seek_btn->setToolTip(tr("Find signal values"));
seek_btn->setFixedSize(25, 25);
title_layout->addWidget(seek_btn);
plot_btn = new QPushButton(this);
plot_btn->setStyleSheet("QPushButton {font-size:18px}");
plot_btn->setFixedSize(25, 25);
title_layout->addWidget(plot_btn);
main_layout->addLayout(title_layout);
auto remove_btn = new QToolButton(this);
remove_btn->setAutoRaise(true);
remove_btn->setText("x");
remove_btn->setToolTip(tr("Remove signal"));
title_layout->addWidget(remove_btn);
main_layout->addWidget(title_bar);
// signal form
form_container = new QWidget(this);
QVBoxLayout *v_layout = new QVBoxLayout(form_container);
QHBoxLayout *h = new QHBoxLayout();
QPushButton *remove_btn = new QPushButton(tr("Remove Signal"));
h->addWidget(remove_btn);
h->addStretch();
QPushButton *save_btn = new QPushButton(tr("Save"));
h->addWidget(save_btn);
v_layout->addLayout(h);
main_layout->addWidget(form_container);
form = new SignalForm(this);
form->setVisible(false);
main_layout->addWidget(form);
// bottom line
QFrame *hline = new QFrame();
@ -104,25 +117,27 @@ SignalEdit::SignalEdit(int index, QWidget *parent) : form_idx(index), QWidget(pa
hline->setFrameShadow(QFrame::Sunken);
main_layout->addWidget(hline);
QObject::connect(remove_btn, &QPushButton::clicked, [this]() { emit remove(this->sig); });
QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked);
QObject::connect(save_btn, &QPushButton::clicked, this, &SignalEdit::saveSignal);
QObject::connect(plot_btn, &QPushButton::clicked, [this]() { emit showChart(msg_id, sig, !chart_opened); });
QObject::connect(seek_btn, &QPushButton::clicked, [this]() {
SignalFindDlg dlg(msg_id, sig, this);
dlg.exec();
});
QObject::connect(plot_btn, &QToolButton::clicked, [this](bool checked) { emit showChart(msg_id, sig, checked); });
QObject::connect(seek_btn, &QToolButton::clicked, [this]() { SignalFindDlg(msg_id, sig, this).exec(); });
QObject::connect(remove_btn, &QToolButton::clicked, [this]() { emit remove(sig); });
QObject::connect(form, &SignalForm::changed, this, &SignalEdit::saveSignal);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
}
void SignalEdit::setSignal(const QString &message_id, const Signal *signal, bool show_form) {
msg_id = message_id;
void SignalEdit::setSignal(const QString &message_id, const Signal *signal) {
sig = signal;
title->setText(QString("%1. %2").arg(form_idx + 1).arg(sig->name.c_str()));
setFormVisible(show_form);
updateForm(msg_id == message_id && form->isVisible());
msg_id = message_id;
color_label->setText(QString::number(form_idx + 1));
color_label->setStyleSheet(QString("background-color:%1").arg(getColor(form_idx)));
title->setText(sig->name.c_str());
show();
}
void SignalEdit::saveSignal() {
if (!sig || !form->changed_by_user) return;
Signal s = *sig;
s.name = form->name->text().toStdString();
s.size = form->size->text().toInt();
@ -148,38 +163,50 @@ void SignalEdit::saveSignal() {
s.lsb = bigEndianStartBitsIndex(bigEndianBitIndex(s.start_bit) + s.size - 1);
s.msb = s.start_bit;
}
title->setText(QString("%1. %2").arg(form_idx + 1).arg(form->name->text()));
emit save(this->sig, s);
if (s != *sig)
emit save(this->sig, s);
}
void SignalEdit::setChartOpened(bool opened) {
plot_btn->setText(opened ? "" : "📈");
plot_btn->setToolTip(opened ? tr("Close Plot") :tr("Show Plot"));
chart_opened = opened;
plot_btn->setToolTip(opened ? tr("Close Plot") : tr("Show Plot"));
plot_btn->setChecked(opened);
}
void SignalEdit::setFormVisible(bool visible) {
if (visible) {
if (!form) {
form = new SignalForm(this);
((QVBoxLayout *)form_container->layout())->insertWidget(0, form);
}
void SignalEdit::updateForm(bool visible) {
if (visible && sig) {
form->changed_by_user = false;
form->name->setText(sig->name.c_str());
form->size->setValue(sig->size);
form->endianness->setCurrentIndex(sig->is_little_endian ? 0 : 1);
form->sign->setCurrentIndex(sig->is_signed ? 0 : 1);
form->factor->setText(QString::number(sig->factor));
form->offset->setText(QString::number(sig->offset));
form->msb->setText(QString::number(sig->msb));
form->lsb->setText(QString::number(sig->lsb));
form->size->setValue(sig->size);
form->changed_by_user = true;
}
form_container->setVisible(visible);
icon->setText(visible ? "" : ">");
form->setVisible(visible);
icon->setText(visible ? "" : "> ");
}
void SignalEdit::showFormClicked() {
parentWidget()->setUpdatesEnabled(false);
for (auto &edit : parentWidget()->findChildren<SignalEdit*>())
edit->updateForm(edit == this && !form->isVisible());
QTimer::singleShot(1, [this]() { parentWidget()->setUpdatesEnabled(true); });
}
void SignalEdit::signalHovered(const Signal *s) {
auto color = sig == s ? hoverColor(getColor(form_idx)) : QColor(getColor(form_idx));
title->setStyleSheet(QString("font-weight:bold; color:%1").arg(color.name()));
auto bg_color = sig == s ? hoverColor(getColor(form_idx)) : QColor(getColor(form_idx));
auto color = sig == s ? "white" : "black";
color_label->setStyleSheet(QString("color:%1; background-color:%2").arg(color).arg(bg_color.name()));
}
void SignalEdit::hideEvent(QHideEvent *event) {
msg_id = "";
sig = nullptr;
updateForm(false);
QWidget::hideEvent(event);
}
void SignalEdit::enterEvent(QEvent *event) {
@ -204,7 +231,7 @@ SignalFindDlg::SignalFindDlg(const QString &id, const Signal *signal, QWidget *p
comp_box->addItems({">", "=", "<"});
h->addWidget(comp_box);
QLineEdit *value_edit = new QLineEdit("0", this);
value_edit->setValidator( new QDoubleValidator(-500000, 500000, 6, this) );
value_edit->setValidator(new QDoubleValidator(-500000, 500000, 6, this));
h->addWidget(value_edit, 1);
QPushButton *search_btn = new QPushButton(tr("Find"), this);
h->addWidget(search_btn);

@ -4,8 +4,8 @@
#include <QDialog>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QSpinBox>
#include <QToolButton>
#include "selfdrive/ui/qt/widgets/controls.h"
@ -13,13 +13,17 @@
#include "tools/cabana/dbcmanager.h"
class SignalForm : public QWidget {
Q_OBJECT
public:
SignalForm(QWidget *parent);
QLineEdit *name, *unit, *comment, *val_desc, *offset, *factor, *min_val, *max_val;
QLabel *lsb, *msb;
QSpinBox *size;
QComboBox *sign, *endianness;
bool changed_by_user = false;
signals:
void changed();
};
class SignalEdit : public QWidget {
@ -27,38 +31,35 @@ class SignalEdit : public QWidget {
public:
SignalEdit(int index, QWidget *parent = nullptr);
void setSignal(const QString &msg_id, const Signal *sig, bool show_form);
void setSignal(const QString &msg_id, const Signal *sig);
void setChartOpened(bool opened);
void setFormVisible(bool show);
void signalHovered(const Signal *sig);
inline bool isFormVisible() const { return form_container->isVisible(); }
const Signal *sig = nullptr;
QString msg_id;
signals:
void highlight(const Signal *sig);
void showChart(const QString &name, const Signal *sig, bool show);
void showFormClicked();
void remove(const Signal *sig);
void save(const Signal *sig, const Signal &new_sig);
protected:
void hideEvent(QHideEvent *event) override;
void enterEvent(QEvent *event) override;
void leaveEvent(QEvent *event) override;
void saveSignal();
void updateForm(bool show);
void showFormClicked();
SignalForm *form = nullptr;
ElidedLabel *title;
QWidget *form_container;
QLabel *color_label;
QLabel *icon;
int form_idx = 0;
bool chart_opened = false;
QPushButton *plot_btn;
QToolButton *plot_btn;
};
class SignalFindDlg : public QDialog {
Q_OBJECT
public:
SignalFindDlg(const QString &id, const Signal *signal, QWidget *parent);
};

@ -1,35 +1,67 @@
#include "opendbc/can/common.h"
#undef INFO
#include "catch2/catch.hpp"
#include "tools/cabana/dbcmanager.h"
#include "tools/replay/logreader.h"
// demo route, first segment
const std::string TEST_RLOG_URL = "https://commadata2.blob.core.windows.net/commadata2/4cf7a6ad03080c90/2021-09-29--13-46-36/0/rlog.bz2";
TEST_CASE("DBCManager::generateDBC") {
DBCManager dbc_origin(nullptr);
dbc_origin.open("toyota_new_mc_pt_generated");
QString dbc_string = dbc_origin.generateDBC();
DBCManager dbc_from_generated(nullptr);
dbc_from_generated.open("", dbc_string);
dbc_from_generated.open("", dbc_origin.generateDBC());
auto &msgs = dbc_origin.messages();
auto &new_msgs = dbc_from_generated.messages();
REQUIRE(msgs.size() == new_msgs.size());
for (auto &[address, m] : msgs) {
auto new_m = new_msgs.at(address);
REQUIRE(m.name == new_m.name);
REQUIRE(m.size == new_m.size);
REQUIRE(m.sigs.size() == new_m.sigs.size());
for (auto &[name, sig] : m.sigs)
REQUIRE(sig == new_m.sigs[name]);
}
}
TEST_CASE("Parse can messages") {
DBCManager dbc(nullptr);
dbc.open("toyota_new_mc_pt_generated");
CANParser can_parser(0, "toyota_new_mc_pt_generated", {}, {});
LogReader log;
REQUIRE(log.load(TEST_RLOG_URL, nullptr, {}, true));
REQUIRE(log.events.size() > 0);
for (auto e : log.events) {
if (e->which == cereal::Event::Which::CAN) {
std::map<std::pair<uint32_t, std::string>, std::vector<double>> values_1;
for (const auto &c : e->event.getCan()) {
const auto msg = dbc.msg(c.getAddress());
if (c.getSrc() == 0 && msg) {
for (auto &[name, sig] : msg->sigs) {
double val = get_raw_value((uint8_t *)c.getDat().begin(), c.getDat().size(), sig);
values_1[{c.getAddress(), name.toStdString()}].push_back(val);
}
}
}
auto dbc = dbc_origin.getDBC();
auto new_dbc = dbc_from_generated.getDBC();
REQUIRE(dbc->msgs.size() == new_dbc->msgs.size());
for (int i = 0; i < dbc->msgs.size(); ++i) {
REQUIRE(dbc->msgs[i].name == new_dbc->msgs[i].name);
REQUIRE(dbc->msgs[i].address == new_dbc->msgs[i].address);
REQUIRE(dbc->msgs[i].size == new_dbc->msgs[i].size);
REQUIRE(dbc->msgs[i].sigs.size() == new_dbc->msgs[i].sigs.size());
auto &sig = dbc->msgs[i].sigs;
auto &new_sig = new_dbc->msgs[i].sigs;
for (int j = 0; j < sig.size(); ++j) {
REQUIRE(sig[j].name == new_sig[j].name);
REQUIRE(sig[j].start_bit == new_sig[j].start_bit);
REQUIRE(sig[j].msb == new_sig[j].msb);
REQUIRE(sig[j].lsb == new_sig[j].lsb);
REQUIRE(sig[j].size == new_sig[j].size);
REQUIRE(sig[j].is_signed == new_sig[j].is_signed);
REQUIRE(sig[j].factor == new_sig[j].factor);
REQUIRE(sig[j].offset == new_sig[j].offset);
REQUIRE(sig[j].is_little_endian == new_sig[j].is_little_endian);
can_parser.UpdateCans(e->mono_time, e->event.getCan());
auto values_2 = can_parser.query_latest();
for (auto &[key, v1] : values_1) {
bool found = false;
for (auto &v2 : values_2) {
if (v2.address == key.first && v2.name == key.second) {
REQUIRE(v2.all_values.size() == v1.size());
REQUIRE(v2.all_values == v1);
found = true;
break;
}
}
REQUIRE(found);
}
}
}
}

@ -37,7 +37,7 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) {
// btn controls
QHBoxLayout *control_layout = new QHBoxLayout();
play_btn = new QPushButton("");
play_btn->setStyleSheet("font-weight:bold");
play_btn->setStyleSheet("font-weight:bold; height:16px");
control_layout->addWidget(play_btn);
QButtonGroup *group = new QButtonGroup(this);

@ -91,9 +91,14 @@ def main(addr, cams, nvidia=False):
vipc_server.create_buffers(vst, 4, False, W, H)
vipc_server.start_listener()
procs = []
for k, v in cams.items():
multiprocessing.Process(target=decoder, args=(addr, k, vipc_server, v, nvidia)).start()
p = multiprocessing.Process(target=decoder, args=(addr, k, vipc_server, v, nvidia))
p.start()
procs.append(p)
for p in procs:
p.join()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Decode video streams and broadcast on VisionIPC")

Loading…
Cancel
Save