diff --git a/RELEASES.md b/RELEASES.md index 83b3ac46f4..18817bda4d 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,9 +1,17 @@ -Version 0.8.17 (2022-XX-XX) +Version 0.8.17 (2022-11-XX) ======================== * New driving model - * Internal feature space accuracy increased tenfold during training, this makes the model dramatically more accurate. + * Internal feature space information content increased tenfold during training (to ~700 bits), this makes the model dramatically more accurate + * Less reliance on previous frames makes model more reactive and snappy + * Trained in new reprojective simulator + * Model trained in openpilot was trained in 36hrs from scratch, compared to around 1 week of previous releases + * Model training now simulates lateral and longitudinal behavior, this allows openpilot to slow down for turns, stop at traffic lights, etc,... in experimental mode * New driver monitoring model * New end-to-end distracted trigger +* Experimental driving mode + * End-to-end longitudinal control + * Stops for red lights and stop signs + * openpilot defaults to chill mode, enable experimental in settings * Self-tuning torque lateral controller parameters * Parameters learned live for each car * Torque controller used on all Toyota, Lexus, Hyundai, Kia, and Genesis models @@ -12,9 +20,10 @@ Version 0.8.17 (2022-XX-XX) * Matched speeds shown on car's dash * Improved update experience * Border turns grey while overriding steering - * Added button to bookmark events while driving; view them later in comma connect + * Bookmark events while driving; view them in comma connect * AGNOS 6 * tools: new and improved cabana thanks to deanlee! +* Genesis GV70 2022-23 support thanks to zunichky and sunnyhaibin! * Hyundai Santa Cruz 2021-22 support thanks to sunnyhaibin! * Kia Sportage 2023 support thanks to sunnyhaibin! * Kia Sportage Hybrid 2023 support thanks to sunnyhaibin! diff --git a/cereal b/cereal index 1d25fc3f20..cdba1aafec 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 1d25fc3f202d5ddeee97848480323e9b14f9bdfa +Subproject commit cdba1aafec5e36505ef6ace675568e1f15003c47 diff --git a/common/params.cc b/common/params.cc index 155bc88487..e17d1f1b13 100644 --- a/common/params.cc +++ b/common/params.cc @@ -102,7 +102,7 @@ std::unordered_map keys = { {"DashcamOverride", PERSISTENT}, {"DisableLogging", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"DisablePowerDown", PERSISTENT}, - {"EndToEndLong", PERSISTENT}, + {"ExperimentalMode", PERSISTENT}, {"ExperimentalLongitudinalEnabled", PERSISTENT}, // WARNING: THIS MAY DISABLE AEB {"DisableUpdates", PERSISTENT}, {"DisengageOnAccelerator", PERSISTENT}, diff --git a/common/util.cc b/common/util.cc index b6a8322a27..010fe8a11a 100644 --- a/common/util.cc +++ b/common/util.cc @@ -1,5 +1,6 @@ #include "common/util.h" +#include #include #include @@ -149,6 +150,14 @@ int safe_fflush(FILE *stream) { return ret; } +int safe_ioctl(int fd, unsigned long request, void *argp) { + int ret; + do { + ret = ioctl(fd, request, argp); + } while ((ret == -1) && (errno == EINTR)); + return ret; +} + std::string readlink(const std::string &path) { char buff[4096]; ssize_t len = ::readlink(path.c_str(), buff, sizeof(buff)-1); diff --git a/common/util.h b/common/util.h index e13f4dc130..b46f7bde4a 100644 --- a/common/util.h +++ b/common/util.h @@ -86,6 +86,7 @@ int write_file(const char* path, const void* data, size_t size, int flags = O_WR FILE* safe_fopen(const char* filename, const char* mode); size_t safe_fwrite(const void * ptr, size_t size, size_t count, FILE * stream); int safe_fflush(FILE *stream); +int safe_ioctl(int fd, unsigned long request, void *argp); std::string readlink(const std::string& path); bool file_exists(const std::string& fn); diff --git a/docs/CARS.md b/docs/CARS.md index c36fb6f54a..8c2dc9bff4 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. -# 213 Supported Cars +# 214 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:| @@ -31,6 +31,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Genesis|G70 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| |Genesis|G80 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Genesis|G90 2017-18|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| +|Genesis|GV70 2022-23|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| |GMC|Acadia 2018[3](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| |GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| |Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| @@ -60,7 +61,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai J| |Hyundai|i30 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| |Hyundai|Ioniq 5 (with HDA II) 2022-23|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q| -|Hyundai|Ioniq 5 (without HDA II) 2022|Highway Driving Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| +|Hyundai|Ioniq 5 (without HDA II) 2022-23|Highway Driving Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Hyundai|Ioniq Electric 2020|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| @@ -166,7 +167,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Toyota|Camry Hybrid 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Corolla Cross (Non-US only) 2020-21|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| +|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| diff --git a/release/files_common b/release/files_common index 61d16a2088..26662f1ef1 100644 --- a/release/files_common +++ b/release/files_common @@ -90,6 +90,7 @@ selfdrive/boardd/boardd_api_impl.pyx selfdrive/boardd/can_list_to_can_capnp.cc selfdrive/boardd/panda.cc selfdrive/boardd/panda.h +selfdrive/boardd/spi.cc selfdrive/boardd/panda_comms.h selfdrive/boardd/panda_comms.cc selfdrive/boardd/set_time.py diff --git a/selfdrive/boardd/SConscript b/selfdrive/boardd/SConscript index 356b5de663..d99e67a9f0 100644 --- a/selfdrive/boardd/SConscript +++ b/selfdrive/boardd/SConscript @@ -1,9 +1,9 @@ Import('env', 'envCython', 'common', 'cereal', 'messaging') libs = ['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj'] -env.Program('boardd', ['main.cc', 'boardd.cc', 'panda.cc', 'panda_comms.cc'], LIBS=libs) +env.Program('boardd', ['main.cc', 'boardd.cc', 'panda.cc', 'panda_comms.cc', 'spi.cc'], LIBS=libs) env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc']) envCython.Program('boardd_api_impl.so', 'boardd_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"]) if GetOption('test'): - env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc', 'panda.cc', 'panda_comms.cc'], LIBS=libs) + env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc', 'panda.cc', 'panda_comms.cc', 'spi.cc'], LIBS=libs) diff --git a/selfdrive/boardd/panda.cc b/selfdrive/boardd/panda.cc index e68558632e..deccee3e76 100644 --- a/selfdrive/boardd/panda.cc +++ b/selfdrive/boardd/panda.cc @@ -12,7 +12,11 @@ Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) { // TODO: support SPI here one day... - handle = std::make_unique(serial); + if (serial.find("spi") != std::string::npos) { + handle = std::make_unique(serial); + } else { + handle = std::make_unique(serial); + } hw_type = get_hw_type(); diff --git a/selfdrive/boardd/panda_comms.h b/selfdrive/boardd/panda_comms.h index c5143b16b3..08d0c1a2af 100644 --- a/selfdrive/boardd/panda_comms.h +++ b/selfdrive/boardd/panda_comms.h @@ -8,6 +8,7 @@ #include #define TIMEOUT 0 +#define SPI_BUF_SIZE 1024 // comms base class @@ -49,3 +50,24 @@ private: std::vector recv_buf; void handle_usb_issue(int err, const char func[]); }; + +class PandaSpiHandle : public PandaCommsHandle { +public: + PandaSpiHandle(std::string serial); + ~PandaSpiHandle(); + int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT); + int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT); + int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); + int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); + void cleanup(); + + static std::vector list(); + +private: + int spi_fd = -1; + int spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len); + int wait_for_ack(); + + uint8_t tx_buf[SPI_BUF_SIZE]; + uint8_t rx_buf[SPI_BUF_SIZE]; +}; diff --git a/selfdrive/boardd/spi.cc b/selfdrive/boardd/spi.cc new file mode 100644 index 0000000000..1ec5e89c71 --- /dev/null +++ b/selfdrive/boardd/spi.cc @@ -0,0 +1,252 @@ +#include +#include + +#include +#include + +#include "common/util.h" +#include "common/swaglog.h" +#include "selfdrive/boardd/panda_comms.h" + + +#define SPI_SYNC 0x5AU +#define SPI_HACK 0x79U +#define SPI_DACK 0x85U +#define SPI_NACK 0x1FU +#define SPI_CHECKSUM_START 0xABU + +struct __attribute__((packed)) spi_header { + uint8_t sync; + uint8_t endpoint; + uint16_t tx_len; + uint16_t max_rx_len; +}; + +struct __attribute__((packed)) spi_control_packet { + uint16_t request; + uint16_t param1; + uint16_t param2; + uint16_t length; +}; + + +PandaSpiHandle::PandaSpiHandle(std::string serial) : PandaCommsHandle(serial) { + LOGD("opening SPI panda: %s", serial.c_str()); + + int err; + uint32_t spi_mode = SPI_MODE_0; + uint32_t spi_speed = 30000000; + uint8_t spi_bits_per_word = 8; + + spi_fd = open(serial.c_str(), O_RDWR); + if (spi_fd < 0) { + LOGE("failed setting SPI mode %d", err); + goto fail; + } + + // SPI settings + err = util::safe_ioctl(spi_fd, SPI_IOC_WR_MODE, &spi_mode); + if (err < 0) { + LOGE("failed setting SPI mode %d", err); + goto fail; + } + + err = util::safe_ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed); + if (err < 0) { + LOGE("failed setting SPI speed"); + goto fail; + } + + err = util::safe_ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits_per_word); + if (err < 0) { + LOGE("failed setting SPI bits per word"); + goto fail; + } + + return; + +fail: + cleanup(); + throw std::runtime_error("Error connecting to panda"); +} + +PandaSpiHandle::~PandaSpiHandle() { + std::lock_guard lk(hw_lock); + cleanup(); +} + +void PandaSpiHandle::cleanup() { + if (spi_fd != -1) { + close(spi_fd); + spi_fd = -1; + } +} + + + +int PandaSpiHandle::control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout) { + int err; + + std::lock_guard lk(hw_lock); + do { + spi_control_packet packet = { + .request = request, + .param1 = param1, + .param2 = param2, + .length = 0 + }; + + // TODO: handle error + err = spi_transfer(0, (uint8_t *) &packet, sizeof(packet), NULL, 0); + } while (err < 0 && connected); + + return err; +} + +int PandaSpiHandle::control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout) { + int err; + + std::lock_guard lk(hw_lock); + do { + spi_control_packet packet = { + .request = request, + .param1 = param1, + .param2 = param2, + .length = length + }; + + // TODO: handle error + err = spi_transfer(0, (uint8_t *) &packet, sizeof(packet), data, length); + } while (err < 0 && connected); + + return err; +} + +int PandaSpiHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { + return 0; +} + +int PandaSpiHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { + return 0; +} + +std::vector PandaSpiHandle::list() { + // TODO: list all pandas available over SPI + return {}; +} + + + +void add_checksum(uint8_t *data, int data_len) { + data[data_len] = SPI_CHECKSUM_START; + for (int i=0; i < data_len; i++) { + data[data_len] ^= data[i]; + } +} + + +int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len) { + int ret; + uint16_t rx_data_len; + + // needs to be less, since we need to have space for the checksum + assert(tx_len < SPI_BUF_SIZE); + assert(max_rx_len < SPI_BUF_SIZE); + + spi_header header = { + .sync = SPI_SYNC, + .endpoint = endpoint, + .tx_len = tx_len, + .max_rx_len = max_rx_len + }; + + spi_ioc_transfer transfer = { + .tx_buf = (uint64_t)tx_buf, + .rx_buf = (uint64_t)rx_buf + }; + + // Send header + memcpy(tx_buf, &header, sizeof(header)); + add_checksum(tx_buf, sizeof(header)); + transfer.len = sizeof(header) + 1; + ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); + if (ret < 0) { + LOGE("SPI: failed to send header"); + goto transfer_fail; + } + + // Wait for (N)ACK + tx_buf[0] = 0x12; + transfer.len = 1; + while (true) { + ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); + if (ret < 0) { + LOGE("SPI: failed to send ACK request"); + goto transfer_fail; + } + + if (rx_buf[0] == SPI_HACK) { + break; + } else if (rx_buf[0] == SPI_NACK) { + LOGW("SPI: got header NACK"); + goto transfer_fail; + } + } + + // Send data + if (tx_data != NULL) { + memcpy(tx_buf, tx_data, tx_len); + } + add_checksum(tx_buf, tx_len); + transfer.len = tx_len + 1; + ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); + if (ret < 0) { + LOGE("SPI: failed to send data"); + goto transfer_fail; + } + + // Wait for (N)ACK + tx_buf[0] = 0xab; + transfer.len = 1; + while (true) { + ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); + if (ret < 0) { + LOGE("SPI: failed to send ACK request"); + goto transfer_fail; + } + + if (rx_buf[0] == SPI_DACK) { + break; + } else if (rx_buf[0] == SPI_NACK) { + LOGE("SPI: got data NACK"); + goto transfer_fail; + } + } + + // Read data len + transfer.len = 2; + ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); + if (ret < 0) { + LOGE("SPI: failed to read rx data len"); + goto transfer_fail; + } + rx_data_len = *(uint16_t *)rx_buf; + assert(rx_data_len < SPI_BUF_SIZE); + + // Read data + transfer.len = rx_data_len + 1; + ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); + if (ret < 0) { + LOGE("SPI: failed to read rx data"); + goto transfer_fail; + } + // TODO: check checksum + + if (rx_data != NULL) { + memcpy(rx_data, rx_buf, rx_data_len); + } + ret = rx_data_len; + +transfer_fail: + return ret; +} diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index af69307a2c..f4b3f88e99 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -51,12 +51,15 @@ class CarState(CarStateBase): else: ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["ECMPRDNL2"]["PRNDL2"], None)) - # Some Volt 2016-17 have loose brake pedal push rod retainers which causes the ECM to believe - # that the brake is being intermittently pressed without user interaction. - # To avoid a cruise fault we need to match the ECM's brake pressed signal and threshold - # https://static.nhtsa.gov/odi/tsbs/2017/MC-10137629-9999.pdf ret.brake = pt_cp.vl["ECMAcceleratorPos"]["BrakePedalPos"] - ret.brakePressed = ret.brake >= 8 + if self.CP.networkLocation == NetworkLocation.fwdCamera: + ret.brakePressed = pt_cp.vl["ECMEngineStatus"]["BrakePressed"] != 0 + else: + # Some Volt 2016-17 have loose brake pedal push rod retainers which causes the ECM to believe + # that the brake is being intermittently pressed without user interaction. + # To avoid a cruise fault we need to use a conservative brake position threshold + # https://static.nhtsa.gov/odi/tsbs/2017/MC-10137629-9999.pdf + ret.brakePressed = ret.brake >= 8 # Regen braking is braking if self.CP.transmissionType == TransmissionType.direct: @@ -154,6 +157,7 @@ class CarState(CarStateBase): ("TractionControlOn", "ESPStatus"), ("ParkBrake", "VehicleIgnitionAlt"), ("CruiseMainOn", "ECMEngineStatus"), + ("BrakePressed", "ECMEngineStatus"), ] checks = [ diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 7cf1515fda..2c309fa0df 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -200,7 +200,7 @@ class CarState(CarStateBase): self.is_metric = cp.vl["CLUSTER_INFO"]["DISTANCE_UNIT"] != 1 if not self.CP.openpilotLongitudinalControl: speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS - cp_cruise_info = cp if self.CP.flags & HyundaiFlags.CANFD_HDA2 else cp_cam + cp_cruise_info = cp_cam if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp ret.cruiseState.speed = cp_cruise_info.vl["SCC_CONTROL"]["VSetDis"] * speed_factor ret.cruiseState.standstill = cp_cruise_info.vl["SCC_CONTROL"]["CRUISE_STANDSTILL"] == 1 ret.cruiseState.enabled = cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] in (1, 2) @@ -467,7 +467,7 @@ class CarState(CarStateBase): ("BLINDSPOTS_REAR_CORNERS", 20), ] - if CP.flags & HyundaiFlags.CANFD_HDA2 and not CP.openpilotLongitudinalControl: + if not (CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value) and not CP.openpilotLongitudinalControl: signals += [ ("ACCMode", "SCC_CONTROL"), ("VSetDis", "SCC_CONTROL"), @@ -504,11 +504,13 @@ class CarState(CarStateBase): @staticmethod def get_cam_can_parser_canfd(CP): + signals = [] + checks = [] if CP.flags & HyundaiFlags.CANFD_HDA2: - signals = [(f"BYTE{i}", "CAM_0x2a4") for i in range(3, 24)] - checks = [("CAM_0x2a4", 20)] - else: - signals = [ + signals += [(f"BYTE{i}", "CAM_0x2a4") for i in range(3, 24)] + checks += [("CAM_0x2a4", 20)] + elif CP.flags & HyundaiFlags.CANFD_CAMERA_SCC: + signals += [ ("COUNTER", "SCC_CONTROL"), ("NEW_SIGNAL_1", "SCC_CONTROL"), ("MainMode_ACC", "SCC_CONTROL"), @@ -521,7 +523,7 @@ class CarState(CarStateBase): ("VSetDis", "SCC_CONTROL"), ] - checks = [ + checks += [ ("SCC_CONTROL", 50), ] diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 6c8d0076f1..0306f7e104 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -2,7 +2,7 @@ from cereal import car from panda import Panda from common.conversions import Conversions as CV -from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons, CarControllerParams +from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, CANFD_RADAR_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons, CarControllerParams from selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR from selfdrive.car import STD_CARGO_KG, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase @@ -44,6 +44,8 @@ class CarInterface(CarInterfaceBase): # ICE cars do not have 0x130; GEARS message on 0x40 instead if 0x130 not in fingerprint[4]: ret.flags |= HyundaiFlags.CANFD_ALT_GEARS.value + if candidate not in CANFD_RADAR_SCC_CAR: + ret.flags |= HyundaiFlags.CANFD_CAMERA_SCC.value ret.steerActuatorDelay = 0.1 # Default delay ret.steerLimitTimer = 0.4 @@ -200,6 +202,10 @@ class CarInterface(CarInterfaceBase): ret.mass = 3673.0 * CV.LB_TO_KG + STD_CARGO_KG ret.wheelbase = 2.83 ret.steerRatio = 12.9 + elif candidate == CAR.GENESIS_GV70_1ST_GEN: + ret.mass = 1950. + STD_CARGO_KG + ret.wheelbase = 2.87 + ret.steerRatio = 14.6 elif candidate == CAR.GENESIS_G80: ret.mass = 2060. + STD_CARGO_KG ret.wheelbase = 3.01 @@ -244,6 +250,8 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2 if ret.flags & HyundaiFlags.CANFD_ALT_BUTTONS: ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_ALT_BUTTONS + if ret.flags & HyundaiFlags.CANFD_CAMERA_SCC: + ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC else: if candidate in LEGACY_SAFETY_MODE_CAR: # these cars require a special panda safety mode due to missing counters and checksums in the messages diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 536af7cf01..b7e28825c3 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -48,6 +48,7 @@ class HyundaiFlags(IntFlag): CANFD_HDA2 = 1 CANFD_ALT_BUTTONS = 2 CANFD_ALT_GEARS = 4 + CANFD_CAMERA_SCC = 8 class CAR: @@ -100,6 +101,7 @@ class CAR: # Genesis GENESIS_G70 = "GENESIS G70 2018" GENESIS_G70_2020 = "GENESIS G70 2020" + GENESIS_GV70_1ST_GEN = "GENESIS GV70 1ST GEN" GENESIS_G80 = "GENESIS G80 2017" GENESIS_G90 = "GENESIS G90 2017" @@ -145,7 +147,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.VELOSTER: HyundaiCarInfo("Hyundai Veloster 2019-20", min_enable_speed=5. * CV.MPH_TO_MS, harness=Harness.hyundai_e), CAR.SONATA_HYBRID: HyundaiCarInfo("Hyundai Sonata Hybrid 2020-22", "All", harness=Harness.hyundai_a), CAR.IONIQ_5: [ - HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022" , "Highway Driving Assist", harness=Harness.hyundai_k), + HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022-23" , "Highway Driving Assist", harness=Harness.hyundai_k), HyundaiCarInfo("Hyundai Ioniq 5 (with HDA II) 2022-23", "Highway Driving Assist II", harness=Harness.hyundai_q), ], CAR.TUCSON_HYBRID_4TH_GEN: HyundaiCarInfo("Hyundai Tucson Hybrid 2022", "All", harness=Harness.hyundai_n), @@ -188,6 +190,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { # Genesis CAR.GENESIS_G70: HyundaiCarInfo("Genesis G70 2018-19", "All", harness=Harness.hyundai_f), CAR.GENESIS_G70_2020: HyundaiCarInfo("Genesis G70 2020", "All", harness=Harness.hyundai_f), + CAR.GENESIS_GV70_1ST_GEN: HyundaiCarInfo("Genesis GV70 2022-23", "All", harness=Harness.hyundai_l), CAR.GENESIS_G80: HyundaiCarInfo("Genesis G80 2017-19", "All", harness=Harness.hyundai_h), CAR.GENESIS_G90: HyundaiCarInfo("Genesis G90 2017-18", "All", harness=Harness.hyundai_c), } @@ -465,6 +468,7 @@ FW_VERSIONS = { b'\xf1\x82DNBWN5TMDCXXXG2E', b'\xf1\x82DNCVN5GMCCXXXF0A', b'\xf1\x82DNCVN5GMCCXXXG2B', + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DNDWN5TMDCXXXJ1A', b'\xf1\x87391162M003', b'\xf1\x87391162M013', @@ -489,6 +493,7 @@ FW_VERSIONS = { b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101', b'\xf1\x8756310L0210\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC101', b'\xf1\x8757700-L0000\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100', + b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP101', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.02 99211-L1000 190422', @@ -509,6 +514,7 @@ FW_VERSIONS = { b'\xf1\x00HT6WA250BLHT6WA910A1SDN8G25NB1\x00\x00\x00\x00\x00\x00\x96\xa1\xf1\x92', b'\xf1\x00HT6WA280BLHT6WAD10A1SDN8G25NB2\x00\x00\x00\x00\x00\x00\x08\xc9O:', b'\xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5', + b'\xf1\x00T02601BL T02832A1 VDN8T25XXX832NS8G\x0e\xfeE', b'\xf1\x87954A02N060\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5', b'\xf1\x87SAKFBA2926554GJ2VefVww\x87xwwwww\x88\x87xww\x87wTo\xfb\xffvUo\xff\x8d\x16\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SAKFBA3030524GJ2UVugww\x97yx\x88\x87\x88vw\x87gww\x87wto\xf9\xfffUo\xff\xa2\x0c\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', @@ -1374,6 +1380,7 @@ FW_VERSIONS = { b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.05 99210-CV000 211027', b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.06 99210-CV000 220328', b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.05 99210-CV000 211027', + b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.06 99210-CV000 220328', ], }, CAR.IONIQ_5: { @@ -1422,6 +1429,14 @@ FW_VERSIONS = { b'\xf1\x00NQ5__ 1.00 1.03 99110-P1000 ', ], }, + CAR.GENESIS_GV70_1ST_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.04 99211-AR000 210204', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00JK1_ SCC FHCUP 1.00 1.02 99110-AR000 ', + ], + }, } CHECKSUM = { @@ -1439,7 +1454,10 @@ FEATURES = { "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022}, } -CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN} +CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN} + +# The radar does SCC on these cars when HDA I, rather than the camera +CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, } # The camera does SCC on these cars, rather than the radar CAMERA_SCC_CAR = {CAR.KONA_EV_2022, } @@ -1499,4 +1517,5 @@ DBC = { CAR.SANTA_CRUZ_1ST_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_SPORTAGE_5TH_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: dbc_dict('hyundai_canfd', None), + CAR.GENESIS_GV70_1ST_GEN: dbc_dict('hyundai_canfd', None), } diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 4647a04244..982ba40b17 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -23,7 +23,7 @@ TorqueFromLateralAccelCallbackType = Callable[[float, car.CarParams.LateralTorqu MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS ACCEL_MAX = 2.0 ACCEL_MIN = -3.5 -FRICTION_THRESHOLD = 0.2 +FRICTION_THRESHOLD = 0.3 TORQUE_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/params.yaml') TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/override.yaml') diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index da5ff1785a..7a1e9a8a3d 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -196,6 +196,7 @@ FW_VERSIONS = { b'\xaa!dt\a', b'\xc5!ar\a', b'\xbe!as\a', + b'\xc5!as\x07', b'\xc5!ds\a', b'\xc5!`s\a', b'\xaa!au\a', diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index bf949d3492..d0051454a6 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -81,6 +81,7 @@ routes = [ CarTestRoute("6fe86b4e410e4c37|2020-07-22--16-27-13", HYUNDAI.HYUNDAI_GENESIS), CarTestRoute("70c5bec28ec8e345|2020-08-08--12-22-23", HYUNDAI.GENESIS_G70), + CarTestRoute("ca4de5b12321bd98|2022-10-18--21-15-59", HYUNDAI.GENESIS_GV70_1ST_GEN), CarTestRoute("6b301bf83f10aa90|2020-11-22--16-45-07", HYUNDAI.GENESIS_G80), CarTestRoute("f0709d2bc6ca451f|2022-10-15--08-13-54", HYUNDAI.SANTA_CRUZ_1ST_GEN), CarTestRoute("4dbd55df87507948|2022-03-01--09-45-38", HYUNDAI.SANTA_FE), diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index aabf652c8c..8d22173671 100755 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -34,15 +34,18 @@ class TestCarInterfaces(unittest.TestCase): self.assertGreater(car_params.maxLateralAccel, 0) if car_params.steerControlType != car.CarParams.SteerControlType.angle: - tuning = car_params.lateralTuning.which() - if tuning == 'pid': - self.assertTrue(len(car_params.lateralTuning.pid.kpV)) - elif tuning == 'torque': - kf = car_params.lateralTuning.torque.kf - self.assertTrue(not math.isnan(kf) and kf > 0) - self.assertTrue(not math.isnan(car_params.lateralTuning.torque.friction)) - elif tuning == 'indi': - self.assertTrue(len(car_params.lateralTuning.indi.outerLoopGainV)) + tune = car_params.lateralTuning + if tune.which() == 'pid': + self.assertTrue(not math.isnan(tune.pid.kf) and tune.pid.kf > 0) + self.assertTrue(len(tune.pid.kpV) > 0 and len(tune.pid.kpV) == len(tune.pid.kpBP)) + self.assertTrue(len(tune.pid.kiV) > 0 and len(tune.pid.kiV) == len(tune.pid.kiBP)) + + elif tune.which() == 'torque': + self.assertTrue(not math.isnan(tune.torque.kf) and tune.torque.kf > 0) + self.assertTrue(not math.isnan(tune.torque.friction)) + + elif tune.which() == 'indi': + self.assertTrue(len(tune.indi.outerLoopGainV)) # Run car interface CC = car.CarControl.new_message() diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index c5a316aaaf..2ef5a1cd0f 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -34,6 +34,7 @@ HYUNDAI TUCSON HYBRID 4TH GEN: [2.5, 2.5, 0.0] HYUNDAI SANTA CRUZ 1ST GEN: [2.7, 2.7, 0.0] KIA SPORTAGE 5TH GEN: [2.7, 2.7, 0.0] KIA SPORTAGE HYBRID 5TH GEN: [2.5, 2.5, 0.0] +GENESIS GV70 1ST GEN: [2.42, 2.42, 0.01] # Dashcam or fallback configured as ideal car mock: [10.0, 10, 0.0] diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index 4758149916..8efb2c79e3 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -15,6 +15,8 @@ class CarState(CarStateBase): can_define = CANDefine(DBC[CP.carFingerprint]["pt"]) self.shifter_values = can_define.dv["GEAR_PACKET"]["GEAR"] self.eps_torque_scale = EPS_SCALE[CP.carFingerprint] / 100. + self.cluster_speed_hyst_gap = CV.KPH_TO_MS / 2. + self.cluster_min_speed = CV.KPH_TO_MS / 2. # On cars with cp.vl["STEER_TORQUE_SENSOR"]["STEER_ANGLE"] # the signal is zeroed to where the steering angle is at start. @@ -52,6 +54,7 @@ class CarState(CarStateBase): ) ret.vEgoRaw = mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr]) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) + ret.vEgoCluster = ret.vEgo * 1.015 # minimum of all the cars ret.standstill = ret.vEgoRaw == 0 diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 02e4caa9d6..3f4edb36d2 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -110,6 +110,21 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 14.3 tire_stiffness_factor = 0.7933 ret.mass = 3585. * CV.LB_TO_KG + STD_CARGO_KG # Average between ICE and Hybrid + ret.lateralTuning.init('pid') + ret.lateralTuning.pid.kiBP = [0.0] + ret.lateralTuning.pid.kpBP = [0.0] + ret.lateralTuning.pid.kpV = [0.6] + ret.lateralTuning.pid.kiV = [0.1] + ret.lateralTuning.pid.kf = 0.00007818594 + + # 2019+ RAV4 TSS2 uses two different steering racks and specific tuning seems to be necessary. + # See https://github.com/commaai/openpilot/pull/21429#issuecomment-873652891 + for fw in car_fw: + if fw.ecu == "eps" and (fw.fwVersion.startswith(b'\x02') or fw.fwVersion in [b'8965B42181\x00\x00\x00\x00\x00\x00']): + ret.lateralTuning.pid.kpV = [0.15] + ret.lateralTuning.pid.kiV = [0.05] + ret.lateralTuning.pid.kf = 0.00004 + break elif candidate in (CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2): stop_and_go = True diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 9e96118085..ba26c7e03b 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -119,7 +119,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { CAR.COROLLA: ToyotaCarInfo("Toyota Corolla 2017-19"), CAR.COROLLA_TSS2: [ ToyotaCarInfo("Toyota Corolla 2020-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"), - ToyotaCarInfo("Toyota Corolla Cross (Non-US only) 2020-21", min_enable_speed=7.5), + ToyotaCarInfo("Toyota Corolla Cross (Non-US only) 2020-23", min_enable_speed=7.5), ToyotaCarInfo("Toyota Corolla Hatchback 2019-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"), ], CAR.COROLLAH_TSS2: [ @@ -713,6 +713,7 @@ FW_VERSIONS = { }, CAR.COROLLA_TSS2: { (Ecu.engine, 0x700, None): [ + b'\x01896630A22000\x00\x00\x00\x00', b'\x01896630ZG2000\x00\x00\x00\x00', b'\x01896630ZG5000\x00\x00\x00\x00', b'\x01896630ZG5100\x00\x00\x00\x00', @@ -792,6 +793,7 @@ FW_VERSIONS = { b'F152602191\x00\x00\x00\x00\x00\x00', b'\x01F152612862\x00\x00\x00\x00\x00\x00', b'\x01F152612B91\x00\x00\x00\x00\x00\x00', + b'\x01F15260A070\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301100\x00\x00\x00\x00', @@ -809,6 +811,7 @@ FW_VERSIONS = { b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F1202200\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', b'\x028646F1601100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', ], }, CAR.COROLLAH_TSS2: { @@ -1389,6 +1392,7 @@ FW_VERSIONS = { b'\x02896634A14001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', b'\x02896634A23000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02896634A23001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', + b'\x02896634A23101\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', b'\x02896634A14001\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896634A14101\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', ], diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index d09420cf7a..def14ab019 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -12,6 +12,7 @@ class CarState(CarStateBase): super().__init__(CP) self.CCP = CarControllerParams(CP) self.button_states = {button.event_type: False for button in self.CCP.BUTTONS} + self.esp_hold_confirmation = False def create_button_events(self, pt_cp, buttons): button_events = [] diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 1adbba4171..0bdaadf6ef 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -147,7 +147,7 @@ class Controls: if not self.CP.experimentalLongitudinalAvailable: self.params.remove("ExperimentalLongitudinalEnabled") if not self.CP.openpilotLongitudinalControl: - self.params.remove("EndToEndLong") + self.params.remove("ExperimentalMode") self.CC = car.CarControl.new_message() self.CS_prev = car.CarState.new_message() diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index d41d6780e3..4790b8f0eb 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -5,7 +5,7 @@ from common.realtime import DT_MDL LaneChangeState = log.LateralPlan.LaneChangeState LaneChangeDirection = log.LateralPlan.LaneChangeDirection -LANE_CHANGE_SPEED_MIN = 15 * CV.MPH_TO_MS +LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS LANE_CHANGE_TIME_MAX = 10. DESIRES = { diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 51676086ba..d10d39d945 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -17,7 +17,8 @@ from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY # friction in the steering wheel that needs to be overcome to # move it at all, this is compensated for too. -LOW_SPEED_FACTOR = 200 +LOW_SPEED_X = [0, 10, 20, 30] +LOW_SPEED_Y = [15, 13, 10, 5] class LatControlTorque(LatControl): @@ -57,8 +58,9 @@ class LatControlTorque(LatControl): actual_lateral_accel = actual_curvature * CS.vEgo ** 2 lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 - setpoint = desired_lateral_accel + LOW_SPEED_FACTOR * desired_curvature - measurement = actual_lateral_accel + LOW_SPEED_FACTOR * actual_curvature + low_speed_factor = interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y)**2 + setpoint = desired_lateral_accel + low_speed_factor * desired_curvature + measurement = actual_lateral_accel + low_speed_factor * actual_curvature error = setpoint - measurement gravity_adjusted_lateral_accel = desired_lateral_accel - params.roll * ACCELERATION_DUE_TO_GRAVITY pid_log.error = self.torque_from_lateral_accel(error, self.torque_params, error, diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 49eb5988e2..080782ad0f 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -36,7 +36,7 @@ A_EGO_COST = 0. J_EGO_COST = 5.0 A_CHANGE_COST = 200. DANGER_ZONE_COST = 100. -CRASH_DISTANCE = .5 +CRASH_DISTANCE = .25 LEAD_DANGER_FACTOR = 0.75 LIMIT_COST = 1e6 ACADOS_SOLVER_TYPE = 'SQP_RTI' @@ -49,6 +49,7 @@ MAX_T = 10.0 T_IDXS_LST = [index_function(idx, max_val=MAX_T, max_idx=N) for idx in range(N+1)] T_IDXS = np.array(T_IDXS_LST) +FCW_IDXS = T_IDXS < 5.0 T_DIFFS = np.diff(T_IDXS, prepend=[0.]) MIN_ACCEL = -3.5 MAX_ACCEL = 2.0 @@ -369,7 +370,7 @@ class LongitudinalMpc: self.params[:,4] = T_FOLLOW self.run() - if (np.any(lead_xv_0[:,0] - self.x_sol[:,0] < CRASH_DISTANCE) and + if (np.any(lead_xv_0[FCW_IDXS,0] - self.x_sol[FCW_IDXS,0] < CRASH_DISTANCE) and radarstate.leadOne.modelProb > 0.9): self.crash_cnt += 1 else: diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 457065d3b5..2fa13bfb15 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -58,6 +58,7 @@ class LongitudinalPlanner: self.a_desired = init_a self.v_desired_filter = FirstOrderFilter(init_v, 2.0, DT_MDL) + self.v_model_error = 0.0 self.v_desired_trajectory = np.zeros(CONTROL_N) self.a_desired_trajectory = np.zeros(CONTROL_N) @@ -65,15 +66,15 @@ class LongitudinalPlanner: self.solverExecutionTime = 0.0 def read_param(self): - e2e = self.params.get_bool('EndToEndLong') and self.CP.openpilotLongitudinalControl + e2e = self.params.get_bool('ExperimentalMode') and self.CP.openpilotLongitudinalControl self.mpc.mode = 'blended' if e2e else 'acc' - def parse_model(self, model_msg): + def parse_model(self, model_msg, model_error): if (len(model_msg.position.x) == 33 and len(model_msg.velocity.x) == 33 and len(model_msg.acceleration.x) == 33): - x = np.interp(T_IDXS_MPC, T_IDXS, model_msg.position.x) - v = np.interp(T_IDXS_MPC, T_IDXS, model_msg.velocity.x) + x = np.interp(T_IDXS_MPC, T_IDXS, model_msg.position.x) - model_error * T_IDXS_MPC + v = np.interp(T_IDXS_MPC, T_IDXS, model_msg.velocity.x) - model_error a = np.interp(T_IDXS_MPC, T_IDXS, model_msg.acceleration.x) j = np.zeros(len(T_IDXS_MPC)) else: @@ -112,6 +113,9 @@ class LongitudinalPlanner: # Prevent divergence, smooth in current v_ego self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego)) + # Compute model v_ego error + if len(sm['modelV2'].temporalPose.trans): + self.v_model_error = sm['modelV2'].temporalPose.trans[0] - v_ego if force_slow_decel: # if required so, force a smooth deceleration @@ -124,7 +128,7 @@ class LongitudinalPlanner: self.mpc.set_weights(prev_accel_constraint) self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) - x, v, a, j = self.parse_model(sm['modelV2']) + x, v, a, j = self.parse_model(sm['modelV2'], self.v_model_error) self.mpc.update(sm['carState'], sm['radarState'], v_cruise, x, v, a, j) self.v_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.v_solution) @@ -132,8 +136,7 @@ class LongitudinalPlanner: self.j_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC[:-1], self.mpc.j_solution) # TODO counter is only needed because radar is glitchy, remove once radar is gone - # TODO write fcw in e2e_long mode - self.fcw = self.mpc.mode == 'acc' and self.mpc.crash_cnt > 5 and not sm['carState'].standstill + self.fcw = self.mpc.crash_cnt > 2 and not sm['carState'].standstill if self.fcw: cloudlog.info("FCW triggered") diff --git a/selfdrive/controls/tests/test_cruise_speed.py b/selfdrive/controls/tests/test_cruise_speed.py index a972bfb073..ca070f1c3f 100644 --- a/selfdrive/controls/tests/test_cruise_speed.py +++ b/selfdrive/controls/tests/test_cruise_speed.py @@ -26,7 +26,7 @@ class TestCruiseSpeed(unittest.TestCase): def test_cruise_speed(self): params = Params() for e2e in [False, True]: - params.put_bool("EndToEndLong", e2e) + params.put_bool("ExperimentalMode", e2e) for speed in np.arange(5, 40, 5): print(f'Testing {speed} m/s') cruise_speed = float(speed) diff --git a/selfdrive/controls/tests/test_following_distance.py b/selfdrive/controls/tests/test_following_distance.py index 3534f58235..0535caab84 100644 --- a/selfdrive/controls/tests/test_following_distance.py +++ b/selfdrive/controls/tests/test_following_distance.py @@ -27,7 +27,7 @@ class TestFollowingDistance(unittest.TestCase): def test_following_distance(self): params = Params() for e2e in [False, True]: - params.put_bool("EndToEndLong", e2e) + params.put_bool("ExperimentalMode", e2e) for speed in np.arange(0, 40, 5): print(f'Testing {speed} m/s') v_lead = float(speed) diff --git a/selfdrive/modeld/models/driving.cc b/selfdrive/modeld/models/driving.cc index cc4a83de62..4015731c42 100644 --- a/selfdrive/modeld/models/driving.cc +++ b/selfdrive/modeld/models/driving.cc @@ -337,6 +337,17 @@ void fill_model(cereal::ModelDataV2::Builder &framed, const ModelOutput &net_out for (int i=0; i min(self.ee1_offseter.filtered_stat.M, self.settings._EE_MAX_OFFSET1) * self.settings._EE_THRESH12 + ee1_dist = self.eev1 > max(min(self.ee1_offseter.filtered_stat.M, self.settings._EE_MAX_OFFSET1), self.settings._EE_MIN_OFFSET1) * self.settings._EE_THRESH12 else: ee1_dist = self.eev1 > self.settings._EE_THRESH11 # if self.ee2_calibrated: diff --git a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py index c7c2915878..7cc95b104a 100755 --- a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py +++ b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py @@ -143,11 +143,11 @@ def run_maneuver_worker(k): params = Params() man = maneuvers[k] - params.put_bool("EndToEndLong", True) + params.put_bool("ExperimentalMode", True) print(man.title, ' in e2e mode') valid, _ = man.evaluate() self.assertTrue(valid, msg=man.title) - params.put_bool("EndToEndLong", False) + params.put_bool("ExperimentalMode", False) print(man.title, ' in acc mode') valid, _ = man.evaluate() self.assertTrue(valid, msg=man.title) diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index d13ced3a53..f541b6a6d5 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -49ea844254883ac61caa2ac425f453799aeb28a6 +30efb4238327d723e17a3bda7e7c19c18f8a3b18 diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 8bc13f37d4..cac678ca1f 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -24a8d02b148b7f6d20f641d56a7bed71c244b6e3 \ No newline at end of file +a36f7e2fd922fcadca6f8a3d777f4db787cba016 diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 5c754d9312..c58909bf7f 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -18,7 +18,7 @@ from tools.lib.logreader import LogReader source_segments = [ ("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY ("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.SONATA - ("HYUNDAI2", "d545129f3ca90f28|2022-10-19--09-22-54--9"), # HYUNDAI.KIA_EV6 + ("HYUNDAI2", "d545129f3ca90f28|2022-11-07--20-43-08--3"), # HYUNDAI.KIA_EV6 ("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.PRIUS (INDI) ("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.RAV4 (LQR) ("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2 @@ -41,7 +41,7 @@ source_segments = [ segments = [ ("BODY", "regenFA002A80700|2022-09-27--15-37-02--0"), ("HYUNDAI", "regenBE53A59065B|2022-09-27--16-52-03--0"), - ("HYUNDAI2", "d545129f3ca90f28|2022-10-19--09-22-54--9"), + ("HYUNDAI2", "d545129f3ca90f28|2022-11-07--20-43-08--3"), ("TOYOTA", "regen929C5790007|2022-09-27--16-27-47--0"), ("TOYOTA2", "regenEA3950D7F22|2022-09-27--15-43-24--0"), ("TOYOTA3", "regen89026F6BD8D|2022-09-27--15-45-37--0"), diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 51b5ce6bd7..a03cf02010 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -36,6 +36,22 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { "../assets/offroad/icon_openpilot.png", false, }, + { + "ExperimentalMode", + tr("Experimental Mode"), + "", + "../assets/offroad/icon_road.png", + false, + }, + { + "ExperimentalLongitudinalEnabled", + tr("Experimental openpilot Longitudinal Control"), + QString("%1
%2") + .arg(tr("WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).")) + .arg(tr("openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control.")), + "../assets/offroad/icon_speed_limit.png", + true, + }, { "IsLdwEnabled", tr("Enable Lane Departure Warnings"), @@ -59,25 +75,11 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { }, { "DisengageOnAccelerator", - tr("Disengage On Accelerator Pedal"), + tr("Disengage on Accelerator Pedal"), tr("When enabled, pressing the accelerator pedal will disengage openpilot."), "../assets/offroad/icon_disengage_on_accelerator.svg", false, }, - { - "EndToEndLong", - tr("🌮 End-to-end longitudinal (extremely alpha) 🌮"), - "", - "../assets/offroad/icon_road.png", - false, - }, - { - "ExperimentalLongitudinalEnabled", - tr("Experimental openpilot longitudinal control"), - tr("WARNING: openpilot longitudinal control is experimental for this car and will disable AEB."), - "../assets/offroad/icon_speed_limit.png", - true, - }, #ifdef ENABLE_MAPS { "NavSettingTime24h", @@ -116,9 +118,15 @@ void TogglesPanel::showEvent(QShowEvent *event) { } void TogglesPanel::updateToggles() { - auto e2e_toggle = toggles["EndToEndLong"]; + auto e2e_toggle = toggles["ExperimentalMode"]; auto op_long_toggle = toggles["ExperimentalLongitudinalEnabled"]; - const QString e2e_description = tr("Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental."); + const QString e2e_description = tr("\ + openpilot defaults to driving in chill mode.\ + Experimental mode enables alpha-level features that aren't ready for chill mode. \ + Experimental features are listed below:\ +
\ +

🌮 End-to-End Longitudinal Control 🌮

\ + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound."); auto cp_bytes = params.get("CarParamsPersistent"); if (!cp_bytes.empty()) { @@ -140,7 +148,7 @@ void TogglesPanel::updateToggles() { } else { // no long for now e2e_toggle->setEnabled(false); - params.remove("EndToEndLong"); + params.remove("ExperimentalMode"); const QString no_long = tr("openpilot longitudinal control is not currently available for this car."); const QString exp_long = tr("Enable experimental longitudinal control to enable this."); diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index c4e4beb76a..23986726c8 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -170,7 +170,7 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) { } -AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), accel_filter(UI_FREQ, .5, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) { +AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) { pm = std::make_unique>({"uiDebug"}); engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size}); @@ -404,6 +404,7 @@ void AnnotatedCameraWidget::drawText(QPainter &p, int x, int y, const QString &t } void AnnotatedCameraWidget::drawIcon(QPainter &p, int x, int y, QPixmap &img, QBrush bg, float opacity) { + p.setOpacity(1.0); // bg dictates opacity of ellipse p.setPen(Qt::NoPen); p.setBrush(bg); p.drawEllipse(x - radius / 2, y - radius / 2, radius, radius); @@ -460,37 +461,26 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { // paint path QLinearGradient bg(0, height(), 0, height() / 4); float start_hue, end_hue; - if (scene.end_to_end_long) { + if (scene.experimental_mode) { const auto &acceleration = (*s->sm)["modelV2"].getModelV2().getAcceleration(); float acceleration_future = 0; - if (acceleration.getZ().size() > 10 && (*s->sm)["carControl"].getCarControl().getLongActive()) { - acceleration_future = acceleration.getX()[10]; // 1.0 second + if (acceleration.getZ().size() > 16) { + acceleration_future = acceleration.getX()[16]; // 2.5 seconds } - // speed up: 148, slow down: 0 - start_hue = fmax(fmin(60 + accel_filter.update(acceleration_future) * 80, 148), 0); - - // FIXME: painter.drawPolygon can be slow if hue is not rounded - start_hue = int(start_hue * 100 + 0.5) / 100; - - bg.setColorAt(0.0, QColor::fromHslF(start_hue / 360., 0.97, 0.56, 0.4)); - bg.setColorAt(0.75, QColor::fromHslF(63 / 360., 1.0, 0.68, 0.35)); - bg.setColorAt(1.0, QColor::fromHslF(63 / 360., 1.0, 0.68, 0.0)); - } else { - const auto &orientation = (*s->sm)["modelV2"].getModelV2().getOrientation(); - float orientation_future = 0; - if (orientation.getZ().size() > 16) { - orientation_future = std::abs(orientation.getZ()[16]); // 2.5 seconds - } - start_hue = 148; - // straight: 112, in turns: 70 - end_hue = fmax(70, 112 - (orientation_future * 420)); + start_hue = 60; + // speed up: 120, slow down: 0 + end_hue = fmax(fmin(start_hue + acceleration_future * 45, 148), 0); // FIXME: painter.drawPolygon can be slow if hue is not rounded end_hue = int(end_hue * 100 + 0.5) / 100; - bg.setColorAt(0.0, QColor::fromHslF(start_hue / 360., 0.94, 0.51, 0.4)); + bg.setColorAt(0.0, QColor::fromHslF(start_hue / 360., 0.97, 0.56, 0.4)); bg.setColorAt(0.5, QColor::fromHslF(end_hue / 360., 1.0, 0.68, 0.35)); bg.setColorAt(1.0, QColor::fromHslF(end_hue / 360., 1.0, 0.68, 0.0)); + } else { + bg.setColorAt(0.0, QColor::fromHslF(148 / 360., 0.94, 0.51, 0.4)); + bg.setColorAt(0.5, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.35)); + bg.setColorAt(1.0, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.0)); } painter.setBrush(bg); @@ -565,6 +555,7 @@ void AnnotatedCameraWidget::paintGL() { } else if (v_ego > 15) { wide_cam_requested = false; } + wide_cam_requested = wide_cam_requested && s->scene.experimental_mode; // TODO: also detect when ecam vision stream isn't available // for replay of old routes, never go to widecam wide_cam_requested = wide_cam_requested && s->scene.calibration_wide_valid; diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index 1f6a49bf8c..7edca6b3d5 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -87,7 +87,6 @@ protected: double prev_draw_t = 0; FirstOrderFilter fps_filter; - FirstOrderFilter accel_filter; }; // container for all onroad widgets diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 243c078f85..b67224b33d 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -142,9 +142,9 @@ public: ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, const bool confirm, QWidget *parent = nullptr) : ToggleControl(title, desc, icon, false, parent) { key = param.toStdString(); QObject::connect(this, &ParamControl::toggleFlipped, [=](bool state) { - QString content("

" + title + "



" - "

" + getDescription() + "

"); - ConfirmationDialog dialog(content, tr("Ok"), tr("Cancel"), true, this); + QString content("

" + title + "


" + "

" + getDescription() + "

"); + ConfirmationDialog dialog(content, tr("Enable"), tr("Cancel"), true, this); if (!confirm || !state || dialog.exec()) { params.putBool(key, state); } else { diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py index 11ecd30ae0..26d6c39349 100755 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -9,6 +9,7 @@ import xml.etree.ElementTree as ET from selfdrive.ui.update_translations import TRANSLATIONS_DIR, LANGUAGES_FILE, update_translations TMP_TRANSLATIONS_DIR = os.path.join(TRANSLATIONS_DIR, "tmp") +UNFINISHED_TRANSLATION_TAG = "" not in cur_translations, + self.assertTrue(UNFINISHED_TRANSLATION_TAG not in cur_translations, f"{file} ({name}) translation file has unfinished translations. Finish translations or mark them as completed in Qt Linguist") def test_vanished_translations(self): diff --git a/selfdrive/ui/translations/create_badges.py b/selfdrive/ui/translations/create_badges.py index 32904f242a..575584dd50 100755 --- a/selfdrive/ui/translations/create_badges.py +++ b/selfdrive/ui/translations/create_badges.py @@ -2,19 +2,22 @@ import json import os import requests +import xml.etree.ElementTree as ET from common.basedir import BASEDIR +from selfdrive.ui.tests.test_translations import UNFINISHED_TRANSLATION_TAG from selfdrive.ui.update_translations import LANGUAGES_FILE, TRANSLATIONS_DIR TRANSLATION_TAG = "'] + badge_svg = [] + max_badge_width = 0 # keep track of max width to set parent element for idx, (name, file) in enumerate(translation_files.items()): with open(os.path.join(TRANSLATIONS_DIR, f"{file}.ts"), "r") as tr_f: tr_file = tr_f.read() @@ -28,19 +31,30 @@ if __name__ == "__main__": unfinished_translations += 1 percent_finished = int(100 - (unfinished_translations / total_translations * 100.)) - color = "green" if percent_finished == 100 else "orange" if percent_finished >= 70 else "red" + color = "green" if percent_finished == 100 else "orange" if percent_finished > 90 else "red" - r = requests.get(f"https://img.shields.io/badge/LANGUAGE {name}-{percent_finished}%25 complete-{color}", timeout=10) + # Download badge + badge_label = f"LANGUAGE {name}" + badge_message = f"{percent_finished}% complete" + if unfinished_translations != 0: + badge_message += f" ({unfinished_translations} unfinished)" + + r = requests.get(f"{SHIELDS_URL}/{badge_label}-{badge_message}-{color}", timeout=10) assert r.status_code == 200, "Error downloading badge" content_svg = r.content.decode("utf-8") - # make tag ids in each badge unique + xml = ET.fromstring(content_svg) + assert "width" in xml.attrib + max_badge_width = max(max_badge_width, int(xml.attrib["width"])) + + # Make tag ids in each badge unique to combine them into one svg for tag in ("r", "s"): content_svg = content_svg.replace(f'id="{tag}"', f'id="{tag}{idx}"') content_svg = content_svg.replace(f'"url(#{tag})"', f'"url(#{tag}{idx})"') badge_svg.extend([f'', content_svg, ""]) + badge_svg.insert(0, f'') badge_svg.append("") with open(os.path.join(BASEDIR, "translation_badge.svg"), "w") as badge_f: diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index 3c47e6e8e8..07a84fca08 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -1023,7 +1023,7 @@ location set قم بتحميل البيانات من الكاميرا المواجهة للسائق وساعد في تحسين خوارزمية مراقبة السائق. - Disengage On Accelerator Pedal + Disengage on Accelerator Pedal فك الارتباط على دواسة التسريع @@ -1059,7 +1059,7 @@ location set - Experimental openpilot longitudinal control + Experimental openpilot Longitudinal Control diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 5ee2d86756..87c60516fc 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -474,12 +474,12 @@ location set ParamControl - Ok - OK + Cancel + キャンセル - Cancel - キャンセル + Enable + を有効化 @@ -967,21 +967,9 @@ location set 車内カメラの映像をアップロードし、ドライバー監視システムのアルゴリズムの向上に役立てます。 - 🌮 End-to-end longitudinal (extremely alpha) 🌮 - 🌮 エンドツーエンドのアクセル制御 (超α版) 🌮 - - - Experimental openpilot longitudinal control + Experimental openpilot Longitudinal Control 実験段階のopenpilotによるアクセル制御 - - <b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b> - <b>警告: openpilotによるアクセル制御は実験段階であり、AEBを無効化します。</b> - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental. - アクセルとブレーキの制御をopenpilotに任せます。openpilotが人間と同じように運転します。最初期の実験段階です。 - openpilot longitudinal control is not currently available for this car. openpilotによるアクセル制御は、この車では現在利用できません。 @@ -991,7 +979,7 @@ location set ここ機能を使う為には、「実験段階のopenpilotによるアクセル制御」を先に有効化してください。 - Disengage On Accelerator Pedal + Disengage on Accelerator Pedal アクセルを踏むと openpilot を中断 @@ -1014,6 +1002,22 @@ location set Show map on left side when in split screen view. 分割画面表示の場合、ディスプレイの左側にマップを表示します。 + + Experimental Mode + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. + + + + openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. + + + + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + + Updater diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 397b43f545..0bbae22517 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -240,11 +240,11 @@ Reset - + 리셋 Review - + 다시보기 @@ -474,12 +474,12 @@ location set ParamControl - Ok - 확인 + Cancel + 취소 - Cancel - 취소 + Enable + 사용 @@ -864,7 +864,7 @@ location set Uninstall - + 삭제 @@ -967,21 +967,9 @@ location set 운전자 카메라에서 데이터를 업로드하고 운전자 모니터링 알고리즘을 개선합니다. - 🌮 End-to-end longitudinal (extremely alpha) 🌮 - 🌮 e2e 롱컨트롤 사용 (매우 실험적) 🌮 - - - Experimental openpilot longitudinal control + Experimental openpilot Longitudinal Control openpilot 롱컨트롤 (실험적) - - <b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b> - <b>경고: openpilot 롱컨트롤은 실험적인 기능으로 차량의 AEB를 비활성화합니다.</b> - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental. - 주행모델이 가속과 감속을 제어하도록 하면 openpilot은 운전자가 생각하는것처럼 운전합니다. (매우 실험적) - openpilot longitudinal control is not currently available for this car. 현재 이 차량에는 openpilot 롱컨트롤을 사용할 수 없습니다. @@ -991,7 +979,7 @@ location set openpilot 롱컨트롤을 활성화합니다. (실험적) - Disengage On Accelerator Pedal + Disengage on Accelerator Pedal 가속페달 조작시 해제 @@ -1014,6 +1002,22 @@ location set Show map on left side when in split screen view. 분할 화면 보기에서 지도를 왼쪽에 표시합니다. + + Experimental Mode + 실험 모드 + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. + + + + openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. + openpilot은 차량의 내장 ACC로 기본 설정됩니다. 롱컨트롤으로 전환하려면 이 옵션을 활성화하세요. + + + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + + Updater @@ -1070,7 +1074,7 @@ location set Forget - + 저장안함 diff --git a/selfdrive/ui/translations/main_nl.ts b/selfdrive/ui/translations/main_nl.ts index 24d2031f31..10651a4160 100644 --- a/selfdrive/ui/translations/main_nl.ts +++ b/selfdrive/ui/translations/main_nl.ts @@ -1007,7 +1007,7 @@ ingesteld Upload gegevens van de bestuurders camera en help het algoritme voor het monitoren van de bestuurder te verbeteren. - Disengage On Accelerator Pedal + Disengage on Accelerator Pedal Deactiveren Met Gaspedaal @@ -1043,7 +1043,7 @@ ingesteld - Experimental openpilot longitudinal control + Experimental openpilot Longitudinal Control diff --git a/selfdrive/ui/translations/main_pl.ts b/selfdrive/ui/translations/main_pl.ts index bdce181375..4f8b03ef50 100644 --- a/selfdrive/ui/translations/main_pl.ts +++ b/selfdrive/ui/translations/main_pl.ts @@ -1011,7 +1011,7 @@ nie zostało ustawione Prześlij dane z kamery skierowanej na kierowcę i pomóż poprawiać algorytm monitorowania kierowcy. - Disengage On Accelerator Pedal + Disengage on Accelerator Pedal Odłącz poprzez naciśnięcie gazu @@ -1047,7 +1047,7 @@ nie zostało ustawione - Experimental openpilot longitudinal control + Experimental openpilot Longitudinal Control diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 6774555b73..702702a8be 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -240,11 +240,11 @@ Reset - + Resetar Review - + Revisar @@ -475,12 +475,12 @@ trabalho definido ParamControl - Ok - OK + Cancel + Cancelar - Cancel - Cancelar + Enable + Ativar @@ -868,7 +868,7 @@ trabalho definido Uninstall - + Desinstalar @@ -971,21 +971,9 @@ trabalho definido Upload dados da câmera voltada para o motorista e ajude a melhorar o algoritmo de monitoramentor. - 🌮 End-to-end longitudinal (extremely alpha) 🌮 - 🌮 End-to-end longitudinal (experimental) 🌮 - - - Experimental openpilot longitudinal control + Experimental openpilot Longitudinal Control Controle longitudinal experimental openpilot - - <b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b> - <b>AVISO: o controle longitudinal openpilot é experimental para este carro e irá desabilitar AEB.</b> - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental. - Deixe o modelo controlar o acelerador e os freios. openpilot irá conduzir como pensa que um humano faria. Super experimental. - openpilot longitudinal control is not currently available for this car. controle longitudinal openpilot não está disponível para este carro. @@ -995,7 +983,7 @@ trabalho definido Habilite o controle longitudinal experimental para habilitar isso. - Disengage On Accelerator Pedal + Disengage on Accelerator Pedal Desacionar Com Pedal Do Acelerador @@ -1018,6 +1006,22 @@ trabalho definido Show map on left side when in split screen view. Exibir mapa do lado esquerdo quando a tela for dividida. + + Experimental Mode + Modo Experimental + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. + + + + openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. + O padrão do openpilot é o ACC integrado do carro em vez do controle longitudinal do openpilot neste carro. Habilite isto para alternar para controle longitudinal do openpilot. + + + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + + Updater @@ -1074,7 +1078,7 @@ trabalho definido Forget - + Esquecer diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index 95880e69c9..0de0ba5f9a 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -951,7 +951,7 @@ location set อัปโหลดข้อมูลจากกล้องที่หันหน้าไปทางคนขับ และช่วยปรับปรุงอัลกอริธึมการตรวจสอบผู้ขับขี่ - Disengage On Accelerator Pedal + Disengage on Accelerator Pedal ยกเลิกระบบช่วยขับเมื่อเหยียบคันเร่ง @@ -979,7 +979,7 @@ location set 🌮 ควบคุมเร่ง/เบรคแบบ End-to-end (อยู่ขั้นพัฒนา) 🌮 - Experimental openpilot longitudinal control + Experimental openpilot Longitudinal Control ทดลองใช้ระบบควบคุมการเร่ง/เบรคโดย openpilot diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 6189a235e8..558a6cec4f 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -376,7 +376,7 @@ Get turn-by-turn directions displayed and more with a comma prime subscription. Sign up now: https://connect.comma.ai - 订阅comma prime以获取导航。 + 订阅comma prime以获取导航。 立即注册:https://connect.comma.ai @@ -472,12 +472,12 @@ location set ParamControl - Ok - 好的 + Cancel + 取消 - Cancel - 取消 + Enable + 启用 @@ -965,21 +965,9 @@ location set 上传驾驶员摄像头的数据,帮助改进驾驶员监控算法。 - 🌮 End-to-end longitudinal (extremely alpha) 🌮 - 🌮 端对端纵向控制(实验性功能) 🌮 - - - Experimental openpilot longitudinal control + Experimental openpilot Longitudinal Control - - <b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b> - - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental. - 让驾驶模型直接控制油门和刹车,openpilot将会模仿人类司机的驾驶方式。该功能仍非常实验性。 - openpilot longitudinal control is not currently available for this car. @@ -989,7 +977,7 @@ location set - Disengage On Accelerator Pedal + Disengage on Accelerator Pedal 踩油门时取消控制 @@ -1012,6 +1000,22 @@ location set Show map on left side when in split screen view. 在分屏模式中,将地图置于屏幕左侧。 + + Experimental Mode + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. + + + + openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. + + + + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + + Updater diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 0b0dbe4ae5..0f6bc981cf 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -474,12 +474,12 @@ location set ParamControl - Ok - 確定 + Cancel + 取消 - Cancel - 取消 + Enable + 啟用 @@ -967,21 +967,9 @@ location set 上傳駕駛監控的錄像來協助我們提升駕駛監控的準確率。 - 🌮 End-to-end longitudinal (extremely alpha) 🌮 - 🌮 端對端縱向控制(實驗性功能) 🌮 - - - Experimental openpilot longitudinal control + Experimental openpilot Longitudinal Control 使用 openpilot 縱向控制(實驗) - - <b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b> - <b>注意:這台車的 openpilot 縱向控制仍然是實驗中的功能,開啟這功能將會關閉自動緊急煞車 (AEB)。</b> - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental. - 讓駕駛模型直接控製油門和剎車,openpilot將會模仿人類司機的駕駛方式。該功能仍非常實驗性。 - openpilot longitudinal control is not currently available for this car. openpilot 縱向控制目前不適用於這輛車。 @@ -991,7 +979,7 @@ location set 打開縱向控制(實驗)以啟用此功能。 - Disengage On Accelerator Pedal + Disengage on Accelerator Pedal 油門取消控車 @@ -1014,6 +1002,22 @@ location set Show map on left side when in split screen view. 進入分割畫面後,地圖將會顯示在畫面的左側。 + + Experimental Mode + + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides which speed to drive, the set speed will only act as an upper bound. + + + + openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control on this car. Enable this to switch to openpilot longitudinal control. + + + + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + + Updater diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 0198405769..970448359e 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -174,7 +174,7 @@ void ui_update_params(UIState *s) { auto params = Params(); s->scene.is_metric = params.getBool("IsMetric"); s->scene.map_on_left = params.getBool("NavSettingLeftSide"); - s->scene.end_to_end_long = params.getBool("EndToEndLong"); + s->scene.experimental_mode = params.getBool("ExperimentalMode"); } void UIState::updateStatus() { @@ -214,7 +214,7 @@ void UIState::updateStatus() { UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ - "modelV2", "carControl", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState", + "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState", "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "gnssMeasurements", }); diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index e550afd5f2..38e2ffe3ce 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -104,7 +104,7 @@ typedef struct UIScene { QPointF lead_vertices[2]; float light_sensor; - bool started, ignition, is_metric, map_on_left, longitudinal_control, end_to_end_long; + bool started, ignition, is_metric, map_on_left, longitudinal_control, experimental_mode; uint64_t started_frame; } UIScene; diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 78848af5be..2ee06e372a 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -80,30 +80,26 @@ const float sensor_analog_gains_AR0231[] = { 7.0/2.0, 8.0/2.0, 8.0/1.0}; // 12, 13, 14, 15 = bypass const float sensor_analog_gains_OX03C10[] = { - 1.0, 1.125, 1.25, 1.3125, 1.5625, - 1.6875, 2.0, 2.25, 2.625, 3.125, - 3.625, 4.0, 4.5, 5.0, 5.5, - 6.0, 6.5, 7.0, 7.5, 8.0, - 8.5, 9.0, 9.5, 10.0, 10.5, - 11.0, 11.5, 12.0, 12.5, 13.0, - 13.5, 14.0, 14.5, 15.0, 15.5}; + 1.0, 1.0625, 1.125, 1.1875, 1.25, 1.3125, 1.375, 1.4375, 1.5, 1.5625, 1.6875, + 1.8125, 1.9375, 2.0, 2.125, 2.25, 2.375, 2.5, 2.625, 2.75, 2.875, 3.0, + 3.125, 3.375, 3.625, 3.875, 4.0, 4.25, 4.5, 4.75, 5.0, 5.25, 5.5, + 5.75, 6.0, 6.25, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0, + 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5, 15.0, 15.5}; const uint32_t ox03c10_analog_gains_reg[] = { - 0x100, 0x120, 0x140, 0x150, 0x190, - 0x1B0, 0x200, 0x240, 0x2A0, 0x320, - 0x3A0, 0x400, 0x480, 0x500, 0x580, - 0x600, 0x680, 0x700, 0x780, 0x800, - 0x880, 0x900, 0x980, 0xA00, 0xA80, - 0xB00, 0xB80, 0xC00, 0xC80, 0xD00, - 0xD80, 0xE00, 0xE80, 0xF00, 0xF80}; + 0x100, 0x110, 0x120, 0x130, 0x140, 0x150, 0x160, 0x170, 0x180, 0x190, 0x1B0, + 0x1D0, 0x1F0, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, 0x2E0, 0x300, + 0x320, 0x360, 0x3A0, 0x3E0, 0x400, 0x440, 0x480, 0x4C0, 0x500, 0x540, 0x580, + 0x5C0, 0x600, 0x640, 0x680, 0x700, 0x780, 0x800, 0x880, 0x900, 0x980, 0xA00, + 0xA80, 0xB00, 0xB80, 0xC00, 0xC80, 0xD00, 0xD80, 0xE00, 0xE80, 0xF00, 0xF80}; const int ANALOG_GAIN_MIN_IDX_AR0231 = 0x1; // 0.25x const int ANALOG_GAIN_REC_IDX_AR0231 = 0x6; // 0.8x const int ANALOG_GAIN_MAX_IDX_AR0231 = 0xD; // 4.0x const int ANALOG_GAIN_MIN_IDX_OX03C10 = 0x0; -const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x6; // 2x -const int ANALOG_GAIN_MAX_IDX_OX03C10 = 0x22; +const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x11; // 2.5x +const int ANALOG_GAIN_MAX_IDX_OX03C10 = 0x36; const int EXPOSURE_TIME_MIN_AR0231 = 2; // with HDR, fastest ss const int EXPOSURE_TIME_MAX_AR0231 = 0x0855; // with HDR, slowest ss, 40ms diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index 5023c82458..1e25e605c5 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -12,6 +12,7 @@ #include "common/util.h" #define FRAME_BUF_COUNT 4 +#define ANALOG_GAIN_MAX_CNT 55 class CameraState { public: @@ -36,7 +37,7 @@ public: float dc_gain_on_grey; float dc_gain_off_grey; - float sensor_analog_gains[35]; + float sensor_analog_gains[ANALOG_GAIN_MAX_CNT]; int analog_gain_min_idx; int analog_gain_max_idx; int analog_gain_rec_idx; diff --git a/system/camerad/test/test_camerad.py b/system/camerad/test/test_camerad.py index 3c6d466a69..6c2ef1c7bc 100755 --- a/system/camerad/test/test_camerad.py +++ b/system/camerad/test/test_camerad.py @@ -11,7 +11,7 @@ from system.hardware import TICI TEST_TIMESPAN = 30 LAG_FRAME_TOLERANCE = {log.FrameData.ImageSensor.ar0321: 0.5, # ARs use synced pulses for frame starts - log.FrameData.ImageSensor.ox03c10: 1.0} # OXs react to out-of-sync at next frame + log.FrameData.ImageSensor.ox03c10: 1.0} # OXs react to out-of-sync at next frame CAMERAS = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState') @@ -68,12 +68,17 @@ class TestCamerad(unittest.TestCase): frame_times = {frame_id: [getattr(m, m.which()).timestampSof for m in msgs] for frame_id, msgs in self.log_by_frame_id.items()} diffs = {frame_id: (max(ts) - min(ts))/1e6 for frame_id, ts in frame_times.items()} - def get_desc(fid, diff): cam_times = [(m.which(), getattr(m, m.which()).timestampSof/1e6) for m in self.log_by_frame_id[fid]] - return f"{diff=} {cam_times=}" + return (diff, cam_times) laggy_frames = {k: get_desc(k, v) for k, v in diffs.items() if v > LAG_FRAME_TOLERANCE[sensor_type]} - assert len(laggy_frames) == 0, f"Frames not synced properly: {laggy_frames=}" + + def in_tol(diff): + return 50 - LAG_FRAME_TOLERANCE[sensor_type] < diff and diff < 50 + LAG_FRAME_TOLERANCE[sensor_type] + if len(laggy_frames) != 0 and all( in_tol(laggy_frames[lf][0]) for lf in laggy_frames): + print("TODO: handle camera out of sync") + else: + assert len(laggy_frames) == 0, f"Frames not synced properly: {laggy_frames=}" if __name__ == "__main__": unittest.main() diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index e2fd20c1be..c5b931ddae 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -431,6 +431,9 @@ class Tici(HardwareBase): def initialize_hardware(self): self.amplifier.initialize_configuration() + # TODO: this should go in AGNOS + os.system("sudo chmod 666 /dev/spidev0.0") + # Allow thermald to write engagement status to kmsg os.system("sudo chmod a+w /dev/kmsg") diff --git a/tools/cabana/README.md b/tools/cabana/README.md index 99d0d4c9ce..dd131880a6 100644 --- a/tools/cabana/README.md +++ b/tools/cabana/README.md @@ -8,7 +8,7 @@ Cabana is a tool developed to view raw CAN data. One use for this is creating an ```bash $ ./cabana -h -Usage: ./_cabana [options] route +Usage: ./cabana [options] route Options: -h, --help Displays this help. diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index ba50b101fc..678fe5f876 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -13,6 +13,10 @@ const int CELL_HEIGHT = 26; +inline int get_bit_index(const QModelIndex &index, bool little_endian) { + return index.row() * 8 + (little_endian ? 7 - index.column() : index.column()); +} + BinaryView::BinaryView(QWidget *parent) : QTableView(parent) { model = new BinaryViewModel(this); setModel(model); @@ -37,31 +41,49 @@ void BinaryView::highlight(const Signal *sig) { } void BinaryView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) { - QModelIndex tl = indexAt({qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom())}); - QModelIndex br = indexAt({qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom())}); - if (!tl.isValid() || !br.isValid()) + auto index = indexAt(viewport()->mapFromGlobal(QCursor::pos())); + if (!anchor_index.isValid() || !index.isValid()) return; - if (tl < anchor_index) { - br = anchor_index; - } else if (anchor_index < br) { - tl = anchor_index; - } QItemSelection selection; - for (int row = tl.row(); row <= br.row(); ++row) { - int left_col = (row == tl.row()) ? tl.column() : 0; - int right_col = (row == br.row()) ? br.column() : 7; - selection.merge({model->index(row, left_col), model->index(row, right_col)}, flags); + auto [tl, br] = std::minmax(anchor_index, index); + if ((resize_sig && resize_sig->is_little_endian) || (!resize_sig && index < anchor_index)) { + // little_endian selection + if (tl.row() == br.row()) { + selection.merge({model->index(tl.row(), tl.column()), model->index(tl.row(), br.column())}, flags); + } else { + for (int row = tl.row(); row <= br.row(); ++row) { + int left_col = (row == br.row()) ? br.column() : 0; + int right_col = (row == tl.row()) ? tl.column() : 7; + selection.merge({model->index(row, left_col), model->index(row, right_col)}, flags); + } + } + } else { + // big endian selection + for (int row = tl.row(); row <= br.row(); ++row) { + int left_col = (row == tl.row()) ? tl.column() : 0; + int right_col = (row == br.row()) ? br.column() : 7; + selection.merge({model->index(row, left_col), model->index(row, right_col)}, flags); + } } selectionModel()->select(selection, flags); } void BinaryView::mousePressEvent(QMouseEvent *event) { delegate->setSelectionColor(style()->standardPalette().color(QPalette::Active, QPalette::Highlight)); - anchor_index = indexAt(event->pos()); - if (getResizingSignal() != nullptr) { + if (auto index = indexAt(event->pos()); index.isValid() && index.column() != 8) { + anchor_index = index; auto item = (const BinaryViewModel::Item *)anchor_index.internalPointer(); - delegate->setSelectionColor(item->bg_color); + if (item && item->sigs.size() > 0) { + int bit_idx = get_bit_index(anchor_index, true); + for (auto s : item->sigs) { + if (bit_idx == s->lsb || bit_idx == s->msb) { + resize_sig = s; + delegate->setSelectionColor(item->bg_color); + break; + } + } + } } QTableView::mousePressEvent(event); } @@ -80,23 +102,35 @@ void BinaryView::mouseMoveEvent(QMouseEvent *event) { void BinaryView::mouseReleaseEvent(QMouseEvent *event) { QTableView::mouseReleaseEvent(event); - if (auto indexes = selectedIndexes(); !indexes.isEmpty()) { - int from = indexes.first().row() * 8 + indexes.first().column(); - int to = indexes.back().row() * 8 + indexes.back().column(); - if (auto sig = getResizingSignal()) { - auto [sig_from, sig_to] = getSignalRange(sig); - if (from >= sig_from && to <= sig_to) { // reduce size - emit(from == sig_from ? resizeSignal(sig, std::min(to + 1, sig_to), sig_to) - : resizeSignal(sig, sig_from, std::max(from - 1, sig_from))); - } else { // increase size - emit resizeSignal(sig, std::min(from, sig_from), std::max(to, sig_to)); + auto release_index = indexAt(event->pos()); + if (release_index.isValid() && anchor_index.isValid()) { + if (release_index.column() == 8) { + release_index = model->index(release_index.row(), 7); + } + bool little_endian = (resize_sig && resize_sig->is_little_endian) || (!resize_sig && release_index < anchor_index); + int release_bit_idx = get_bit_index(release_index, little_endian); + int archor_bit_idx = get_bit_index(anchor_index, little_endian); + if (resize_sig) { + auto [sig_from, sig_to] = getSignalRange(resize_sig); + int start_bit, end_bit; + if (archor_bit_idx == sig_from) { + std::tie(start_bit, end_bit) = std::minmax(release_bit_idx, sig_to); + if (start_bit >= sig_from && start_bit <= sig_to) + start_bit = std::min(start_bit + 1, sig_to); + } else { + std::tie(start_bit, end_bit) = std::minmax(release_bit_idx, sig_from); + if (end_bit >= sig_from && end_bit <= sig_to) + end_bit = std::max(end_bit - 1, sig_from); } + emit resizeSignal(resize_sig, start_bit, end_bit - start_bit + 1); } else { - emit addSignal(from, to); + auto [sart_bit, end_bit] = std::minmax(archor_bit_idx, release_bit_idx); + emit addSignal(sart_bit, end_bit - sart_bit + 1, little_endian); } - clearSelection(); } + clearSelection(); anchor_index = QModelIndex(); + resize_sig = nullptr; } void BinaryView::leaveEvent(QEvent *event) { @@ -107,34 +141,17 @@ void BinaryView::leaveEvent(QEvent *event) { void BinaryView::setMessage(const QString &message_id) { model->setMessage(message_id); clearSelection(); + anchor_index = QModelIndex(); + resize_sig = nullptr; + hovered_sig = nullptr; updateState(); } -const Signal *BinaryView::getResizingSignal() const { - if (anchor_index.isValid()) { - auto item = (const BinaryViewModel::Item *)anchor_index.internalPointer(); - if (item && item->sigs.size() > 0) { - int archor_pos = anchor_index.row() * 8 + anchor_index.column(); - for (auto s : item->sigs) { - auto [sig_from, sig_to] = getSignalRange(s); - if (archor_pos == sig_from || archor_pos == sig_to) - return s; - } - } - } - return nullptr; -} - QSet BinaryView::getOverlappingSignals() const { QSet overlapping; - for (int i = 0; i < model->rowCount(); ++i) { - for (int j = 0; j < model->columnCount() - 1; ++j) { - auto item = (const BinaryViewModel::Item *)model->index(i, j).internalPointer(); - if (item && item->sigs.size() > 1) { - for (auto s : item->sigs) - overlapping.insert(s); - } - } + for (auto &item : model->items) { + if (item.sigs.size() > 1) + for (auto s : item.sigs) overlapping += s; } return overlapping; } @@ -142,53 +159,37 @@ QSet BinaryView::getOverlappingSignals() const { // BinaryViewModel void BinaryViewModel::setMessage(const QString &message_id) { - msg_id = message_id; - beginResetModel(); + msg_id = message_id; items.clear(); - row_count = 0; - - dbc_msg = dbc()->msg(msg_id); - if (dbc_msg) { + if ((dbc_msg = dbc()->msg(msg_id))) { row_count = dbc_msg->size; items.resize(row_count * column_count); for (int i = 0; i < dbc_msg->sigs.size(); ++i) { const auto &sig = dbc_msg->sigs[i]; auto [start, end] = getSignalRange(&sig); for (int j = start; j <= end; ++j) { - int idx = column_count * (j / 8) + j % 8; + int bit_index = sig.is_little_endian ? bigEndianBitIndex(j) : j; + int idx = column_count * (bit_index / 8) + bit_index % 8; if (idx >= items.size()) { qWarning() << "signal " << sig.name.c_str() << "out of bounds.start_bit:" << sig.start_bit << "size:" << sig.size; break; } - if (j == start) { - sig.is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true; - } else if (j == end) { - sig.is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true; - } + if (j == start) sig.is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true; + if (j == end) sig.is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true; items[idx].bg_color = getColor(i); - items[idx].sigs.push_back(&dbc_msg->sigs[i]); + items[idx].sigs.push_back(&sig); } } } else { row_count = can->lastMessage(msg_id).dat.size(); items.resize(row_count * column_count); } - endResetModel(); } -QModelIndex BinaryViewModel::index(int row, int column, const QModelIndex &parent) const { - return createIndex(row, column, (void *)&items[row * column_count + column]); -} - -Qt::ItemFlags BinaryViewModel::flags(const QModelIndex &index) const { - return (index.column() == column_count - 1) ? Qt::ItemIsEnabled : Qt::ItemIsEnabled | Qt::ItemIsSelectable; -} - void BinaryViewModel::updateState() { auto prev_items = items; - const auto &binary = can->lastMessage(msg_id).dat; // data size may changed. if (!dbc_msg && binary.size() != row_count) { @@ -198,7 +199,6 @@ void BinaryViewModel::updateState() { items.resize(row_count * column_count); endResetModel(); } - char hex[3] = {'\0'}; for (int i = 0; i < std::min(binary.size(), row_count); ++i) { for (int j = 0; j < column_count - 1; ++j) { @@ -248,9 +248,8 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op BinaryView *bin_view = (BinaryView *)parent(); painter->save(); - bool hover = std::find_if(item->sigs.begin(), item->sigs.end(), [=](auto s) { return s == bin_view->hoveredSignal(); }) != - item->sigs.end(); // background + bool hover = item->sigs.contains(bin_view->hoveredSignal()); QColor bg_color = hover ? hoverColor(item->bg_color) : item->bg_color; if (option.state & QStyle::State_Selected) { bg_color = selection_color; @@ -258,7 +257,7 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op painter->fillRect(option.rect, bg_color); // text - if (index.column() == 8) { // hex column + if (index.column() == 8) { // hex column painter->setFont(hex_font); } else if (hover) { painter->setPen(Qt::white); diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index a907a673bf..05bfe7e79f 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -28,12 +28,16 @@ public: BinaryViewModel(QObject *parent) : QAbstractTableModel(parent) {} void setMessage(const QString &message_id); void updateState(); - Qt::ItemFlags flags(const QModelIndex &index) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const { return {}; } int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; } int columnCount(const QModelIndex &parent = QModelIndex()) const override { return column_count; } + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { + return createIndex(row, column, (void *)&items[row * column_count + column]); + } + Qt::ItemFlags flags(const QModelIndex &index) const override { + return (index.column() == column_count - 1) ? Qt::ItemIsEnabled : Qt::ItemIsEnabled | Qt::ItemIsSelectable; + } struct Item { QColor bg_color = QColor(Qt::white); @@ -42,13 +46,13 @@ public: QString val = "0"; QList sigs; }; + std::vector items; private: QString msg_id; const Msg *dbc_msg; int row_count = 0; const int column_count = 9; - std::vector items; }; class BinaryView : public QTableView { @@ -64,7 +68,7 @@ public: signals: void signalHovered(const Signal *sig); - void addSignal(int from, int size); + void addSignal(int start_bit, int size, bool little_endian); void resizeSignal(const Signal *sig, int from, int size); private: @@ -73,10 +77,10 @@ private: void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void leaveEvent(QEvent *event) override; - const Signal *getResizingSignal() const; QModelIndex anchor_index; BinaryViewModel *model; BinaryItemDelegate *delegate; + const Signal *resize_sig = nullptr; const Signal *hovered_sig = nullptr; }; diff --git a/tools/cabana/canmessages.cc b/tools/cabana/canmessages.cc index 3ffc29916f..e670ee8c94 100644 --- a/tools/cabana/canmessages.cc +++ b/tools/cabana/canmessages.cc @@ -1,6 +1,5 @@ #include "tools/cabana/canmessages.h" -#include #include #include "tools/cabana/dbcmanager.h" @@ -9,7 +8,6 @@ CANMessages *can = nullptr; CANMessages::CANMessages(QObject *parent) : QObject(parent) { can = this; - QObject::connect(this, &CANMessages::received, this, &CANMessages::process, Qt::QueuedConnection); QObject::connect(&settings, &Settings::changed, this, &CANMessages::settingChanged); } @@ -24,11 +22,11 @@ static bool event_filter(const Event *e, void *opaque) { } bool CANMessages::loadRoute(const QString &route, const QString &data_dir, bool use_qcam) { - routeName = route; replay = new Replay(route, {"can", "roadEncodeIdx", "carParams"}, {}, nullptr, use_qcam ? REPLAY_FLAG_QCAMERA : 0, data_dir, this); replay->setSegmentCacheLimit(settings.cached_segment_limit); replay->installEventFilter(event_filter, this); QObject::connect(replay, &Replay::segmentsMerged, this, &CANMessages::eventsMerged); + QObject::connect(replay, &Replay::streamStarted, this, &CANMessages::streamStarted); if (replay->load()) { replay->start(); return true; @@ -40,12 +38,9 @@ QList CANMessages::findSignalValues(const QString &id, const Signal *si auto evts = events(); if (!evts) return {}; - auto l = id.split(':'); - int bus = l[0].toInt(); - uint32_t address = l[1].toUInt(nullptr, 16); - QList ret; ret.reserve(max_count); + auto [bus, address] = DBCManager::parseId(id); for (auto &evt : *evts) { if (evt->which != cereal::Event::Which::CAN) continue; @@ -67,8 +62,9 @@ void CANMessages::process(QHash *messages) { for (auto it = messages->begin(); it != messages->end(); ++it) { can_msgs[it.key()] = it.value(); } - delete messages; emit updated(); + emit msgsReceived(messages); + delete messages; } bool CANMessages::eventFilter(const Event *event) { @@ -101,10 +97,9 @@ bool CANMessages::eventFilter(const Event *event) { data.bus_time = c.getBusTime(); data.dat.append((char *)c.getDat().begin(), c.getDat().size()); - auto &count = counters[id]; - data.count = ++count; + data.count = ++counters[id]; if (double delta = (current_sec - counters_begin_sec); delta > 0) { - data.freq = count / delta; + data.freq = data.count / delta; } (*new_msgs)[id] = data; } diff --git a/tools/cabana/canmessages.h b/tools/cabana/canmessages.h index 5fbccdbe12..5ee33bce0d 100644 --- a/tools/cabana/canmessages.h +++ b/tools/cabana/canmessages.h @@ -32,7 +32,7 @@ public: QList findSignalValues(const QString&id, const Signal* signal, double value, FindFlags flag, int max_count); bool eventFilter(const Event *event); - inline QString route() const { return routeName; } + inline QString route() const { return replay->route()->name(); } inline QString carFingerprint() const { return replay->carFingerprint().c_str(); } inline double totalSeconds() const { return replay->totalSeconds(); } inline double routeStartTime() const { return replay->routeStartTime() / (double)1e9; } @@ -47,8 +47,10 @@ public: inline const std::vector> getTimeline() { return replay->getTimeline(); } signals: + void streamStarted(); void eventsMerged(); void updated(); + void msgsReceived(const QHash *); void received(QHash *); public: @@ -58,9 +60,7 @@ protected: void process(QHash *); void settingChanged(); - QString routeName; Replay *replay = nullptr; - std::mutex lock; std::atomic counters_begin_sec = 0; QHash counters; diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 7540abbcb2..3a170bccdc 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -2,12 +2,13 @@ #include #include -#include #include #include +#include #include #include #include +#include // ChartsWidget @@ -15,41 +16,24 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); - // title bar - title_bar = new QWidget(this); - title_bar->setVisible(false); - QHBoxLayout *title_layout = new QHBoxLayout(title_bar); - title_layout->setContentsMargins(0, 0, 0, 0); - title_label = new QLabel(tr("Charts")); - - title_layout->addWidget(title_label); - title_layout->addStretch(); - - range_label = new QLabel(); - title_layout->addWidget(range_label); - - reset_zoom_btn = new QPushButton("⟲", this); - reset_zoom_btn->setFixedSize(30, 30); + // toolbar + QToolBar *toolbar = new QToolBar(tr("Charts"), this); + title_label = new QLabel(); + title_label->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred); + toolbar->addWidget(title_label); + toolbar->addWidget(range_label = new QLabel()); + reset_zoom_btn = toolbar->addAction("⟲"); reset_zoom_btn->setToolTip(tr("Reset zoom (drag on chart to zoom X-Axis)")); - title_layout->addWidget(reset_zoom_btn); - - remove_all_btn = new QPushButton("✖", this); + remove_all_btn = toolbar->addAction("✖"); remove_all_btn->setToolTip(tr("Remove all charts")); - remove_all_btn->setFixedSize(30, 30); - title_layout->addWidget(remove_all_btn); - - dock_btn = new QPushButton(); - dock_btn->setFixedSize(30, 30); - title_layout->addWidget(dock_btn); - - main_layout->addWidget(title_bar, 0, Qt::AlignTop); + dock_btn = toolbar->addAction(""); + main_layout->addWidget(toolbar); + updateToolBar(); // charts QWidget *charts_container = new QWidget(this); - QVBoxLayout *charts_main = new QVBoxLayout(charts_container); - charts_layout = new QVBoxLayout(); - charts_main->addLayout(charts_layout); - charts_main->addStretch(); + charts_layout = new QVBoxLayout(charts_container); + charts_layout->addStretch(); QScrollArea *charts_scroll = new QScrollArea(this); charts_scroll->setWidgetResizable(true); @@ -61,19 +45,23 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { removeAll(nullptr); }); QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartsWidget::removeAll); QObject::connect(dbc(), &DBCManager::signalUpdated, this, &ChartsWidget::signalUpdated); - QObject::connect(dbc(), &DBCManager::msgUpdated, [this](const QString &msg_id) { + QObject::connect(dbc(), &DBCManager::msgRemoved, [this](uint32_t address) { + for (auto c : charts.toVector()) + if (DBCManager::parseId(c->id).second == address) removeChart(c); + }); + QObject::connect(dbc(), &DBCManager::msgUpdated, [this](uint32_t address) { for (auto c : charts) { - if (c->id == msg_id) c->updateTitle(); + if (DBCManager::parseId(c->id).second == address) c->updateTitle(); } }); QObject::connect(can, &CANMessages::eventsMerged, this, &ChartsWidget::eventsMerged); QObject::connect(can, &CANMessages::updated, this, &ChartsWidget::updateState); - QObject::connect(remove_all_btn, &QPushButton::clicked, [this]() { removeAll(); }); - QObject::connect(reset_zoom_btn, &QPushButton::clicked, this, &ChartsWidget::zoomReset); - QObject::connect(dock_btn, &QPushButton::clicked, [this]() { + QObject::connect(remove_all_btn, &QAction::triggered, [this]() { removeAll(); }); + QObject::connect(reset_zoom_btn, &QAction::triggered, this, &ChartsWidget::zoomReset); + QObject::connect(dock_btn, &QAction::triggered, [this]() { emit dock(!docking); docking = !docking; - updateTitleBar(); + updateToolBar(); }); } @@ -92,7 +80,7 @@ void ChartsWidget::eventsMerged() { void ChartsWidget::zoomIn(double min, double max) { zoomed_range = {min, max}; is_zoomed = zoomed_range != display_range; - updateTitleBar(); + updateToolBar(); emit rangeChanged(min, max, is_zoomed); updateState(); } @@ -120,27 +108,22 @@ void ChartsWidget::updateState() { if (prev_range != display_range) { QFutureSynchronizer future_synchronizer; for (auto c : charts) - future_synchronizer.addFuture(QtConcurrent::run(c->chart_view, &ChartView::updateSeries, display_range)); + future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, display_range)); } } const auto &range = is_zoomed ? zoomed_range : display_range; for (auto c : charts) { - c->chart_view->setRange(range.first, range.second); - c->chart_view->updateLineMarker(current_sec); + c->setRange(range.first, range.second); + c->updateLineMarker(current_sec); } } -void ChartsWidget::updateTitleBar() { - title_bar->setVisible(!charts.isEmpty()); - if (charts.isEmpty()) return; - - range_label->setVisible(is_zoomed); +void ChartsWidget::updateToolBar() { + remove_all_btn->setEnabled(!charts.isEmpty()); reset_zoom_btn->setEnabled(is_zoomed); - if (is_zoomed) { - range_label->setText(tr("%1 - %2").arg(zoomed_range.first, 0, 'f', 2).arg(zoomed_range.second, 0, 'f', 2)); - } - title_label->setText(tr("Charts (%1)").arg(charts.size())); + range_label->setText(is_zoomed ? tr("%1 - %2").arg(zoomed_range.first, 0, 'f', 2).arg(zoomed_range.second, 0, 'f', 2) : ""); + title_label->setText(charts.size() > 0 ? tr("Charts (%1)").arg(charts.size()) : tr("Charts")); dock_btn->setText(docking ? "⬈" : "⬋"); dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); } @@ -150,17 +133,17 @@ void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show) { if (it != charts.end()) { if (!show) removeChart((*it)); } else if (show) { - auto chart = new ChartWidget(id, sig, this); - chart->chart_view->updateSeries(display_range); - QObject::connect(chart, &ChartWidget::remove, [=]() { removeChart(chart); }); - QObject::connect(chart->chart_view, &ChartView::zoomIn, this, &ChartsWidget::zoomIn); - QObject::connect(chart->chart_view, &ChartView::zoomReset, this, &ChartsWidget::zoomReset); + auto chart = new ChartView(id, sig, this); + chart->updateSeries(display_range); + QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); }); + QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn); + QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset); charts_layout->insertWidget(0, chart); charts.push_back(chart); emit chartOpened(chart->id, chart->signal); updateState(); } - updateTitleBar(); + updateToolBar(); } bool ChartsWidget::isChartOpened(const QString &id, const Signal *sig) { @@ -168,92 +151,36 @@ bool ChartsWidget::isChartOpened(const QString &id, const Signal *sig) { return it != charts.end(); } -void ChartsWidget::removeChart(ChartWidget *chart) { +void ChartsWidget::removeChart(ChartView *chart) { charts.removeOne(chart); chart->deleteLater(); - updateTitleBar(); + updateToolBar(); emit chartClosed(chart->id, chart->signal); } void ChartsWidget::removeAll(const Signal *sig) { - QMutableListIterator it(charts); - while (it.hasNext()) { - auto c = it.next(); - if (sig == nullptr || c->signal == sig) { - c->deleteLater(); - emit chartClosed(c->id, c->signal); - it.remove(); - } - } - updateTitleBar(); + for (auto c : charts.toVector()) + if (!sig || c->signal == sig) removeChart(c); } void ChartsWidget::signalUpdated(const Signal *sig) { for (auto c : charts) { if (c->signal == sig) { c->updateTitle(); - c->chart_view->updateSeries(display_range); - c->chart_view->setRange(display_range.first, display_range.second, true); + c->updateSeries(display_range); + c->setRange(display_range.first, display_range.second, true); } } } bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) { if (obj != this && event->type() == QEvent::Close) { - emit dock_btn->clicked(); + emit dock_btn->triggered(); return true; } return false; } -// ChartWidget - -ChartWidget::ChartWidget(const QString &id, const Signal *sig, QWidget *parent) : id(id), signal(sig), QWidget(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setSpacing(0); - main_layout->setContentsMargins(0, 0, 0, 0); - - header = new QWidget(this); - QGridLayout *header_layout = new QGridLayout(header); - header_layout->setContentsMargins(11, 11, 11, 0); - msg_name_label = new QLabel(this); - msg_name_label->setTextFormat(Qt::RichText); - header_layout->addWidget(msg_name_label, 0, 0, Qt::AlignLeft); - sig_name_label = new QLabel(this); - header_layout->addWidget(sig_name_label, 0, 1, Qt::AlignCenter); //, 0, Qt::AlignCenter); - - remove_btn = new QPushButton("✖", this); - remove_btn->setFixedSize(20, 20); - remove_btn->setToolTip(tr("Remove chart")); - header_layout->addWidget(remove_btn, 0, 2, Qt::AlignRight); - main_layout->addWidget(header); - - chart_view = new ChartView(id, sig, this); - main_layout->addWidget(chart_view); - main_layout->addStretch(); - - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - updateTitle(); - updateFromSettings(); - - QObject::connect(remove_btn, &QPushButton::clicked, [=]() { emit remove(id, sig); }); - QObject::connect(&settings, &Settings::changed, this, &ChartWidget::updateFromSettings); -} - -void ChartWidget::updateTitle() { - msg_name_label->setText(tr("%1 %2").arg(dbc()->msg(id)->name.c_str()).arg(id)); - sig_name_label->setText(signal->name.c_str()); -} - -void ChartWidget::updateFromSettings() { - header->setStyleSheet(settings.chart_theme == 0 ? "background-color:white" : "background-color:#23242c"); - QString color_style = settings.chart_theme == 0 ? "color:black" : "color:white"; - sig_name_label->setStyleSheet("font-weight:bold;" + color_style); - msg_name_label->setStyleSheet(color_style); - remove_btn->setStyleSheet(color_style); - chart_view->updateFromSettings(); -} - // ChartView ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent) @@ -264,45 +191,64 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent) chart->addSeries(series); chart->createDefaultAxes(); chart->legend()->hide(); - chart->setMargins({0, 0, 0, 0}); chart->layout()->setContentsMargins(0, 0, 0, 0); + line_marker = new QGraphicsLineItem(chart); + line_marker->setZValue(chart->zValue() + 10); + track_line = new QGraphicsLineItem(chart); - track_line->setZValue(chart->zValue() + 10); track_line->setPen(QPen(Qt::darkGray, 1, Qt::DashLine)); track_ellipse = new QGraphicsEllipseItem(chart); - track_ellipse->setZValue(chart->zValue() + 10); track_ellipse->setBrush(Qt::darkGray); value_text = new QGraphicsTextItem(chart); - value_text->setZValue(chart->zValue() + 10); - line_marker = new QGraphicsLineItem(chart); - line_marker->setZValue(chart->zValue() + 10); + item_group = scene()->createItemGroup({track_line, track_ellipse, value_text}); + item_group->setZValue(chart->zValue() + 10); + + // title + msg_title = new QGraphicsTextItem(chart); + QToolButton *remove_btn = new QToolButton(); + remove_btn->setText("X"); + remove_btn->setAutoRaise(true); + remove_btn->setToolTip(tr("Remove Chart")); + close_btn_proxy = new QGraphicsProxyWidget(chart); + close_btn_proxy->setWidget(remove_btn); setChart(chart); - setRenderHint(QPainter::Antialiasing); setRubberBand(QChartView::HorizontalRubberBand); - if (auto rubber = findChild()) { - QPalette pal; - pal.setBrush(QPalette::Base, QColor(0, 0, 0, 80)); - rubber->setPalette(pal); - } + updateFromSettings(); + updateTitle(); QTimer *timer = new QTimer(this); timer->setInterval(100); timer->setSingleShot(true); timer->callOnTimeout(this, &ChartView::adjustChartMargins); + QObject::connect(&settings, &Settings::changed, this, &ChartView::updateFromSettings); + QObject::connect(remove_btn, &QToolButton::clicked, [=]() { emit remove(id, sig); }); QObject::connect(chart, &QChart::plotAreaChanged, [=](const QRectF &plotArea) { // use a singleshot timer to avoid recursion call. timer->start(); }); } +void ChartView::resizeEvent(QResizeEvent *event) { + QChartView::resizeEvent(event); + msg_title->setPos(11, 6); + close_btn_proxy->setPos(event->size().width() - close_btn_proxy->size().width() - 11, 8); +} + +void ChartView::updateTitle() { + chart()->setTitle(signal->name.c_str()); + msg_title->setHtml(tr("%1 %2").arg(dbc()->msg(id)->name.c_str()).arg(id)); +} + void ChartView::updateFromSettings() { setFixedHeight(settings.chart_height); chart()->setTheme(settings.chart_theme == 0 ? QChart::ChartThemeLight : QChart::QChart::ChartThemeDark); - line_marker->setPen(QPen(settings.chart_theme == 0 ? Qt::black : Qt::white, 2)); + auto color = chart()->titleBrush().color(); + line_marker->setPen(QPen(color, 2)); + msg_title->setDefaultTextColor(color); } void ChartView::setRange(double min, double max, bool force_update) { @@ -318,7 +264,7 @@ void ChartView::adjustChartMargins() { const int aligned_pos = 60; if (chart()->plotArea().left() != aligned_pos) { const float left_margin = chart()->margins().left() + aligned_pos - chart()->plotArea().left(); - chart()->setMargins(QMargins(left_margin, 0, 0, 0)); + chart()->setMargins(QMargins(left_margin, 11, 0, 0)); } } @@ -327,7 +273,7 @@ void ChartView::updateLineMarker(double current_sec) { int x = chart()->plotArea().left() + chart()->plotArea().width() * (current_sec - axis_x->min()) / (axis_x->max() - axis_x->min()); if (int(line_marker->line().x1()) != x) { - line_marker->setLine(x, 0, x, height()); + line_marker->setLine(x, chart()->plotArea().top() - chart()->margins().top() + 3, x, height()); } } @@ -335,12 +281,9 @@ void ChartView::updateSeries(const std::pair range) { auto events = can->events(); if (!events) return; - auto l = id.split(':'); - int bus = l[0].toInt(); - uint32_t address = l[1].toUInt(nullptr, 16); - vals.clear(); vals.reserve((range.second - range.first) * 1000); // [n]seconds * 1000hz + auto [bus, address] = DBCManager::parseId(id); double route_start_time = can->routeStartTime(); Event begin_event(cereal::Event::Which::INIT_DATA, (route_start_time + range.first) * 1e9); auto begin = std::lower_bound(events->begin(), events->end(), &begin_event, Event::lessThan()); @@ -380,17 +323,8 @@ void ChartView::updateAxisY() { } } -void ChartView::enterEvent(QEvent *event) { - track_line->setVisible(true); - value_text->setVisible(true); - track_ellipse->setVisible(true); - QChartView::enterEvent(event); -} - void ChartView::leaveEvent(QEvent *event) { - track_line->setVisible(false); - value_text->setVisible(false); - track_ellipse->setVisible(false); + item_group->setVisible(false); QChartView::leaveEvent(event); } @@ -427,7 +361,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) { if (!is_zooming) { const auto plot_area = chart()->plotArea(); auto axis_x = dynamic_cast(chart()->axisX()); - double x = std::clamp((double)ev->pos().x(), plot_area.left(), plot_area.right()-1); + double x = std::clamp((double)ev->pos().x(), plot_area.left(), plot_area.right() - 1); double sec = axis_x->min() + (axis_x->max() - axis_x->min()) * (x - plot_area.left()) / plot_area.width(); auto value = std::upper_bound(vals.begin(), vals.end(), sec, [](double x, auto &p) { return x < p.x(); }); if (value != vals.end()) { @@ -442,9 +376,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) { } value_text->setPos(text_x, pos.y() - 10); } - track_line->setVisible(value != vals.end()); - value_text->setVisible(value != vals.end()); - track_ellipse->setVisible(value != vals.end()); + item_group->setVisible(value != vals.end()); } else { setViewportUpdateMode(QGraphicsView::FullViewportUpdate); } diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index ff56008e7d..e32a6697ce 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -1,14 +1,12 @@ #pragma once -#include - #include #include #include +#include #include #include #include -#include #include #include "tools/cabana/canmessages.h" @@ -25,48 +23,31 @@ public: void setRange(double min, double max, bool force_update = false); void updateLineMarker(double current_sec); void updateFromSettings(); + void updateTitle(); + + QString id; + const Signal *signal; signals: void zoomIn(double min, double max); void zoomReset(); + void remove(const QString &msg_id, const Signal *sig); private: void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *ev) override; - void enterEvent(QEvent *event) override; void leaveEvent(QEvent *event) override; + void resizeEvent(QResizeEvent *event) override; void adjustChartMargins(); void updateAxisY(); - QGraphicsLineItem *track_line; + QGraphicsItemGroup *item_group; + QGraphicsLineItem *line_marker, *track_line; QGraphicsEllipseItem *track_ellipse; - QGraphicsTextItem *value_text; - QGraphicsLineItem *line_marker; + QGraphicsTextItem *value_text, *msg_title; + QGraphicsProxyWidget *close_btn_proxy; QVector vals; - QString id; - const Signal *signal; -}; - -class ChartWidget : public QWidget { -Q_OBJECT - -public: - ChartWidget(const QString &id, const Signal *sig, QWidget *parent); - void updateTitle(); - void updateFromSettings(); - -signals: - void remove(const QString &msg_id, const Signal *sig); - -public: - QString id; - const Signal *signal; - QWidget *header; - QLabel *msg_name_label; - QLabel *sig_name_label; - QPushButton *remove_btn; - ChartView *chart_view = nullptr; -}; + }; class ChartsWidget : public QWidget { Q_OBJECT @@ -74,7 +55,7 @@ class ChartsWidget : public QWidget { public: ChartsWidget(QWidget *parent = nullptr); void showChart(const QString &id, const Signal *sig, bool show); - void removeChart(ChartWidget *chart); + void removeChart(ChartView *chart); bool isChartOpened(const QString &id, const Signal *sig); signals: @@ -89,20 +70,18 @@ private: void zoomIn(double min, double max); void zoomReset(); void signalUpdated(const Signal *sig); - void updateTitleBar(); + void updateToolBar(); void removeAll(const Signal *sig = nullptr); bool eventFilter(QObject *obj, QEvent *event) override; - QWidget *title_bar; QLabel *title_label; QLabel *range_label; bool docking = true; - QPushButton *dock_btn; - QPushButton *reset_zoom_btn; - QPushButton *remove_all_btn; + QAction *dock_btn; + QAction *reset_zoom_btn; + QAction *remove_all_btn; QVBoxLayout *charts_layout; - QList charts; - + QList charts; bool is_zoomed = false; std::pair event_range; std::pair display_range; diff --git a/tools/cabana/dbcmanager.cc b/tools/cabana/dbcmanager.cc index 0ab67dd305..abdd9a08df 100644 --- a/tools/cabana/dbcmanager.cc +++ b/tools/cabana/dbcmanager.cc @@ -9,24 +9,22 @@ DBCManager::DBCManager(QObject *parent) : QObject(parent) {} DBCManager::~DBCManager() {} void DBCManager::open(const QString &dbc_file_name) { - dbc_name = dbc_file_name; - dbc = const_cast(dbc_lookup(dbc_name.toStdString())); - msg_map.clear(); - for (auto &msg : dbc->msgs) { - msg_map[msg.address] = &msg; - } + dbc = const_cast(dbc_lookup(dbc_file_name.toStdString())); + updateMsgMap(); emit DBCFileChanged(); } void DBCManager::open(const QString &name, const QString &content) { - this->dbc_name = name; std::istringstream stream(content.toStdString()); dbc = const_cast(dbc_parse_from_stream(name.toStdString(), stream)); + updateMsgMap(); + emit DBCFileChanged(); +} + +void DBCManager::updateMsgMap() { msg_map.clear(); - for (auto &msg : dbc->msgs) { + for (auto &msg : dbc->msgs) msg_map[msg.address] = &msg; - } - emit DBCFileChanged(); } QString DBCManager::generateDBC() { @@ -51,22 +49,30 @@ QString DBCManager::generateDBC() { } void DBCManager::updateMsg(const QString &id, const QString &name, uint32_t size) { - auto m = const_cast(msg(id)); - if (m) { + auto [bus, address] = parseId(id); + if (auto m = const_cast(msg(address))) { m->name = name.toStdString(); m->size = size; } else { - uint32_t address = addressFromId(id); - dbc->msgs.push_back({.address = address, .name = name.toStdString(), .size = size}); - msg_map[address] = &dbc->msgs.back(); + m = &dbc->msgs.emplace_back(Msg{.address = address, .name = name.toStdString(), .size = size}); + msg_map[address] = m; + } + emit msgUpdated(address); +} + +void DBCManager::removeMsg(const QString &id) { + uint32_t address = parseId(id).second; + auto it = std::find_if(dbc->msgs.begin(), dbc->msgs.end(), [address](auto &m) { return m.address == address; }); + if (it != dbc->msgs.end()) { + dbc->msgs.erase(it); + updateMsgMap(); + emit msgRemoved(address); } - emit msgUpdated(id); } void DBCManager::addSignal(const QString &id, const Signal &sig) { if (Msg *m = const_cast(msg(id))) { - m->sigs.push_back(sig); - emit signalAdded(&m->sigs.back()); + emit signalAdded(&m->sigs.emplace_back(sig)); } } @@ -90,8 +96,9 @@ void DBCManager::removeSignal(const QString &id, const QString &sig_name) { } } -uint32_t DBCManager::addressFromId(const QString &id) { - return id.mid(id.indexOf(':') + 1).toUInt(nullptr, 16); +std::pair DBCManager::parseId(const QString &id) { + const auto list = id.split(':'); + return {list[0].toInt(), list[1].toUInt(nullptr, 16)}; } DBCManager *dbc() { @@ -140,9 +147,9 @@ double get_raw_value(uint8_t *data, size_t data_size, const Signal &sig) { return value; } -void updateSigSizeParamsFromRange(Signal &s, int from, int to) { - s.start_bit = s.is_little_endian ? from : bigEndianBitIndex(from); - s.size = to - from + 1; +void updateSigSizeParamsFromRange(Signal &s, int start_bit, int size) { + s.start_bit = s.is_little_endian ? start_bit : bigEndianBitIndex(start_bit); + s.size = size; if (s.is_little_endian) { s.lsb = s.start_bit; s.msb = s.start_bit + s.size - 1; diff --git a/tools/cabana/dbcmanager.h b/tools/cabana/dbcmanager.h index 913445d44e..81c723a20d 100644 --- a/tools/cabana/dbcmanager.h +++ b/tools/cabana/dbcmanager.h @@ -18,13 +18,14 @@ public: void updateSignal(const QString &id, const QString &sig_name, const Signal &sig); void removeSignal(const QString &id, const QString &sig_name); - static uint32_t addressFromId(const QString &id); + static std::pair parseId(const QString &id); inline static std::vector allDBCNames() { return get_dbc_names(); } - inline QString name() const { return dbc_name; } + inline QString name() const { return dbc ? dbc->name.c_str() : ""; } void updateMsg(const QString &id, const QString &name, uint32_t size); + void removeMsg(const QString &id); inline const DBC *getDBC() const { return dbc; } - inline const Msg *msg(const QString &id) const { return msg(addressFromId(id)); } + inline const Msg *msg(const QString &id) const { return msg(parseId(id).second); } inline const Msg *msg(uint32_t address) const { auto it = msg_map.find(address); return it != msg_map.end() ? it->second : nullptr; @@ -34,11 +35,12 @@ signals: void signalAdded(const Signal *sig); void signalRemoved(const Signal *sig); void signalUpdated(const Signal *sig); - void msgUpdated(const QString &id); + void msgUpdated(uint32_t address); + void msgRemoved(uint32_t address); void DBCFileChanged(); private: - QString dbc_name; + void updateMsgMap(); DBC *dbc = nullptr; std::unordered_map msg_map; }; @@ -47,7 +49,10 @@ private: double get_raw_value(uint8_t *data, size_t data_size, const Signal &sig); int bigEndianStartBitsIndex(int start_bit); int bigEndianBitIndex(int index); -void updateSigSizeParamsFromRange(Signal &s, int from, int to); +void updateSigSizeParamsFromRange(Signal &s, int start_bit, int size); std::pair 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; +} diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index f3e3438229..f96b60847b 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -33,20 +33,21 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart title_frame->setFrameShape(QFrame::StyledPanel); // message title - QHBoxLayout *title_layout = new QHBoxLayout(); - title_layout->addWidget(new QLabel("time:")); + toolbar = new QToolBar(this); + toolbar->addWidget(new QLabel("time:")); time_label = new QLabel(this); time_label->setStyleSheet("font-weight:bold"); - title_layout->addWidget(time_label); - title_layout->addStretch(); + toolbar->addWidget(time_label); name_label = new QLabel(this); name_label->setStyleSheet("font-weight:bold;"); - title_layout->addWidget(name_label); - title_layout->addStretch(); - edit_btn = new QPushButton(tr("Edit"), this); - edit_btn->setVisible(false); - title_layout->addWidget(edit_btn); - frame_layout->addLayout(title_layout); + name_label->setAlignment(Qt::AlignCenter); + name_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + toolbar->addWidget(name_label); + toolbar->addAction("🖍", this, &DetailWidget::editMsg)->setToolTip(tr("Edit Message")); + remove_msg_act = toolbar->addAction("X", this, &DetailWidget::removeMsg); + remove_msg_act->setToolTip(tr("Remove Message")); + toolbar->setVisible(false); + frame_layout->addWidget(toolbar); // warning warning_widget = new QWidget(this); @@ -85,7 +86,6 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart history_log = new HistoryLog(this); container_layout->addWidget(history_log); - QObject::connect(edit_btn, &QPushButton::clicked, this, &DetailWidget::editMsg); QObject::connect(binary_view, &BinaryView::resizeSignal, this, &DetailWidget::resizeSignal); QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal); QObject::connect(can, &CANMessages::updated, this, &DetailWidget::updateState); @@ -130,16 +130,13 @@ void DetailWidget::setMessage(const QString &message_id) { break; } } + msg_id = message_id; if (index == -1) { index = tabbar->addTab(message_id); - auto msg = dbc()->msg(message_id); - tabbar->setTabToolTip(index, msg ? msg->name.c_str() : "untitled"); + tabbar->setTabToolTip(index, msgName(message_id)); } tabbar->setCurrentIndex(index); - msg_id = message_id; dbcMsgChanged(); - - scroll->verticalScrollBar()->setValue(0); } void DetailWidget::dbcMsgChanged(int show_form_idx) { @@ -172,8 +169,9 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) { warnings.push_back(tr("Message size (%1) is incorrect.").arg(msg->size)); } - edit_btn->setVisible(true); - name_label->setText(msg ? msg->name.c_str() : "untitled"); + toolbar->setVisible(!msg_id.isEmpty()); + remove_msg_act->setEnabled(msg != nullptr); + name_label->setText(msgName(msg_id)); binary_view->setMessage(msg_id); history_log->setMessage(msg_id); @@ -187,6 +185,7 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) { warning_label->setText(warnings.join('\n')); warning_widget->setVisible(!warnings.isEmpty()); setUpdatesEnabled(true); + scroll->verticalScrollBar()->setValue(0); } void DetailWidget::updateState() { @@ -211,34 +210,55 @@ void DetailWidget::updateChartState(const QString &id, const Signal *sig, bool o } void DetailWidget::editMsg() { - auto msg = dbc()->msg(msg_id); - QString name = msg ? msg->name.c_str() : "untitled"; - int size = msg ? msg->size : can->lastMessage(msg_id).dat.size(); - EditMessageDialog dlg(msg_id, name, size, this); + QString id = msg_id; + auto msg = dbc()->msg(id); + int size = msg ? msg->size : can->lastMessage(id).dat.size(); + EditMessageDialog dlg(id, msgName(id), size, this); if (dlg.exec()) { - dbc()->updateMsg(msg_id, dlg.name_edit->text(), dlg.size_spin->value()); + dbc()->updateMsg(id, dlg.name_edit->text(), dlg.size_spin->value()); dbcMsgChanged(); } } -void DetailWidget::addSignal(int from, int to) { - if (auto msg = dbc()->msg(msg_id)) { - Signal sig = {}; +void DetailWidget::removeMsg() { + QString id = msg_id; + if (auto msg = dbc()->msg(id)) { + QString text = tr("Are you sure you want to remove '%1'").arg(msg->name.c_str()); + if (QMessageBox::Yes == QMessageBox::question(this, tr("Remove Message"), text)) { + dbc()->removeMsg(id); + dbcMsgChanged(); + } + } +} + +void DetailWidget::addSignal(int start_bit, int size, bool little_endian) { + auto msg = dbc()->msg(msg_id); + if (!msg) { for (int i = 1; /**/; ++i) { - sig.name = "NEW_SIGNAL_" + std::to_string(i); - auto it = std::find_if(msg->sigs.begin(), msg->sigs.end(), [&](auto &s) { return sig.name == s.name; }); - if (it == msg->sigs.end()) break; + std::string name = "NEW_MSG_" + std::to_string(i); + auto it = std::find_if(dbc()->getDBC()->msgs.begin(), dbc()->getDBC()->msgs.end(), [&](auto &m) { return m.name == name; }); + if (it == dbc()->getDBC()->msgs.end()) { + dbc()->updateMsg(msg_id, name.c_str(), can->lastMessage(msg_id).dat.size()); + msg = dbc()->msg(msg_id); + break; + } } - sig.is_little_endian = false, - updateSigSizeParamsFromRange(sig, from, to); - dbc()->addSignal(msg_id, sig); - dbcMsgChanged(msg->sigs.size() - 1); } + Signal sig = {}; + for (int i = 1; /**/; ++i) { + sig.name = "NEW_SIGNAL_" + std::to_string(i); + auto it = std::find_if(msg->sigs.begin(), msg->sigs.end(), [&](auto &s) { return sig.name == s.name; }); + if (it == msg->sigs.end()) break; + } + sig.is_little_endian = little_endian; + updateSigSizeParamsFromRange(sig, start_bit, size); + dbc()->addSignal(msg_id, sig); + dbcMsgChanged(msg->sigs.size() - 1); } -void DetailWidget::resizeSignal(const Signal *sig, int from, int to) { +void DetailWidget::resizeSignal(const Signal *sig, int start_bit, int size) { Signal s = *sig; - updateSigSizeParamsFromRange(s, from, to); + updateSigSizeParamsFromRange(s, start_bit, size); saveSignal(sig, s); } @@ -261,8 +281,7 @@ void DetailWidget::saveSignal(const Signal *sig, const Signal &new_sig) { } dbc()->updateSignal(msg_id, sig->name.c_str(), new_sig); - // update binary view and history log - updateState(); + dbcMsgChanged(); } void DetailWidget::removeSignal(const Signal *sig) { @@ -277,9 +296,7 @@ void DetailWidget::removeSignal(const Signal *sig) { EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title, int size, QWidget *parent) : QDialog(parent) { setWindowTitle(tr("Edit message")); - QVBoxLayout *main_layout = new QVBoxLayout(this); - - QFormLayout *form_layout = new QFormLayout(); + QFormLayout *form_layout = new QFormLayout(this); form_layout->addRow("ID", new QLabel(msg_id)); name_edit = new QLineEdit(title, this); @@ -291,10 +308,8 @@ EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title size_spin->setValue(size); form_layout->addRow(tr("Size"), size_spin); - main_layout->addLayout(form_layout); - auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - main_layout->addWidget(buttonBox); + form_layout->addRow(buttonBox); setFixedWidth(parent->width() * 0.9); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index ce3468e472..915e0bde60 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -2,6 +2,7 @@ #include #include +#include #include "tools/cabana/binaryview.h" #include "tools/cabana/chartswidget.h" @@ -26,26 +27,25 @@ public: void setMessage(const QString &message_id); void dbcMsgChanged(int show_form_idx = -1); -signals: - void binaryViewMoved(bool in); - private: void updateChartState(const QString &id, const Signal *sig, bool opened); void showTabBarContextMenu(const QPoint &pt); - void addSignal(int start_bit, int to); + void addSignal(int start_bit, int size, bool little_endian); void resizeSignal(const Signal *sig, int from, int to); void saveSignal(const Signal *sig, const Signal &new_sig); void removeSignal(const Signal *sig); void editMsg(); + void removeMsg(); void showForm(); void updateState(); QString msg_id; QLabel *name_label, *time_label, *warning_label; QWidget *warning_widget; - QPushButton *edit_btn; QWidget *signals_container; QTabBar *tabbar; + QToolBar *toolbar; + QAction *remove_msg_act; HistoryLog *history_log; BinaryView *binary_view; QScrollArea *scroll; diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 7bb2f37699..4b1818cf68 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -1,7 +1,6 @@ #include "tools/cabana/historylog.h" #include -#include // HistoryLogModel @@ -89,3 +88,8 @@ HistoryLog::HistoryLog(QWidget *parent) : QTableView(parent) { setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); setStyleSheet("QTableView::item { border:0px; padding-left:5px; padding-right:5px; }"); } + +int HistoryLog::sizeHintForColumn(int column) const { + // sizeHintForColumn is only called for column 0 (ResizeToContents) + return itemDelegate()->sizeHint(viewOptions(), model->index(0, 0)).width() + 1; // +1 for grid +} diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h index e1c1319166..e8b0f5a35b 100644 --- a/tools/cabana/historylog.h +++ b/tools/cabana/historylog.h @@ -9,7 +9,7 @@ class HeaderView : public QHeaderView { public: HeaderView(Qt::Orientation orientation, QWidget *parent = nullptr) : QHeaderView(orientation, parent) {} - QSize sectionSizeFromContents(int logicalIndex) const; + QSize sectionSizeFromContents(int logicalIndex) const override; }; class HistoryLogModel : public QAbstractTableModel { @@ -40,5 +40,6 @@ public: void setMessage(const QString &message_id) { model->setMessage(message_id); } void updateState() { model->updateState(); } private: + int sizeHintForColumn(int column) const override; HistoryLogModel *model; }; diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index c2baca4d22..9b85ba7e8d 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -1,8 +1,17 @@ #include "tools/cabana/mainwin.h" #include +#include +#include +#include +#include +#include #include +#include +#include +#include #include +#include #include #include "tools/replay/util.h" @@ -12,25 +21,39 @@ void qLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const if (main_win) emit main_win->showMessage(msg, 0); } -MainWindow::MainWindow() : QWidget() { - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setContentsMargins(11, 11, 11, 5); +MainWindow::MainWindow() : QMainWindow() { + setWindowTitle("Cabana"); + QWidget *central_widget = new QWidget(this); + QHBoxLayout *main_layout = new QHBoxLayout(central_widget); + main_layout->setContentsMargins(11, 11, 11, 0); main_layout->setSpacing(0); - QHBoxLayout *h_layout = new QHBoxLayout(); - h_layout->setContentsMargins(0, 0, 0, 0); - main_layout->addLayout(h_layout); - splitter = new QSplitter(Qt::Horizontal, this); splitter->setHandleWidth(11); + + // DBC file selector + QWidget *messages_container = new QWidget(this); + QVBoxLayout *messages_layout = new QVBoxLayout(messages_container); + messages_layout->setContentsMargins(0, 0, 0, 0); + dbc_combo = new QComboBox(this); + auto dbc_names = dbc()->allDBCNames(); + for (const auto &name : dbc_names) { + dbc_combo->addItem(QString::fromStdString(name)); + } + dbc_combo->model()->sort(0); + dbc_combo->setEditable(true); + dbc_combo->setInsertPolicy(QComboBox::NoInsert); + dbc_combo->completer()->setCompletionMode(QCompleter::PopupCompletion); + messages_layout->addWidget(dbc_combo); + messages_widget = new MessagesWidget(this); - splitter->addWidget(messages_widget); + messages_layout->addWidget(messages_widget); + splitter->addWidget(messages_container); charts_widget = new ChartsWidget(this); detail_widget = new DetailWidget(charts_widget, this); splitter->addWidget(detail_widget); - - h_layout->addWidget(splitter); + main_layout->addWidget(splitter); // right widgets QWidget *right_container = new QWidget(this); @@ -38,35 +61,21 @@ MainWindow::MainWindow() : QWidget() { r_layout = new QVBoxLayout(right_container); r_layout->setContentsMargins(11, 0, 0, 0); QHBoxLayout *right_hlayout = new QHBoxLayout(); - QLabel *fingerprint_label = new QLabel(this); - right_hlayout->addWidget(fingerprint_label); + fingerprint_label = new QLabel(this); + right_hlayout->addWidget(fingerprint_label, 0, Qt::AlignLeft); // TODO: click to select another route. - right_hlayout->addWidget(new QLabel(can->route())); - QPushButton *settings_btn = new QPushButton("Settings"); - right_hlayout->addWidget(settings_btn, 0, Qt::AlignRight); - + right_hlayout->addWidget(new QLabel(can->route()), 0, Qt::AlignRight); r_layout->addLayout(right_hlayout); video_widget = new VideoWidget(this); r_layout->addWidget(video_widget, 0, Qt::AlignTop); - r_layout->addWidget(charts_widget); + main_layout->addWidget(right_container); - h_layout->addWidget(right_container); - - // status bar - status_bar = new QStatusBar(this); - status_bar->setFixedHeight(20); - status_bar->setContentsMargins(0, 0, 0, 0); - status_bar->setSizeGripEnabled(true); - progress_bar = new QProgressBar(); - progress_bar->setRange(0, 100); - progress_bar->setTextVisible(true); - progress_bar->setFixedSize({230, 16}); - progress_bar->setVisible(false); - status_bar->addPermanentWidget(progress_bar); - main_layout->addWidget(status_bar); + setCentralWidget(central_widget); + createActions(); + createStatusBar(); qRegisterMetaType("uint64_t"); qRegisterMetaType("ReplyMsgType"); @@ -78,21 +87,98 @@ MainWindow::MainWindow() : QWidget() { emit updateProgressBar(cur, total, success); }); - QObject::connect(this, &MainWindow::showMessage, status_bar, &QStatusBar::showMessage); + main_win = this; + qInstallMessageHandler(qLogMessageHandler); + QFile json_file("./car_fingerprint_to_dbc.json"); + if (json_file.open(QIODevice::ReadOnly)) { + fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll()); + } + + QObject::connect(dbc_combo, SIGNAL(activated(const QString &)), SLOT(loadDBCFromName(const QString &))); + QObject::connect(this, &MainWindow::showMessage, statusBar(), &QStatusBar::showMessage); QObject::connect(this, &MainWindow::updateProgressBar, this, &MainWindow::updateDownloadProgress); QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, detail_widget, &DetailWidget::setMessage); - QObject::connect(detail_widget, &DetailWidget::binaryViewMoved, [this](bool in) { splitter->setSizes({in ? 100 : 0, 500}); }); QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts); QObject::connect(charts_widget, &ChartsWidget::rangeChanged, video_widget, &VideoWidget::rangeChanged); - QObject::connect(settings_btn, &QPushButton::clicked, this, &MainWindow::setOption); - QObject::connect(can, &CANMessages::eventsMerged, [=]() { fingerprint_label->setText(can->carFingerprint() ); }); + QObject::connect(can, &CANMessages::streamStarted, this, &MainWindow::loadDBCFromFingerprint); + QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { + dbc_combo->setCurrentText(QFileInfo(dbc()->name()).baseName()); + setWindowTitle(tr("%1 - Cabana").arg(dbc()->name())); + }); +} - main_win = this; - qInstallMessageHandler(qLogMessageHandler); +void MainWindow::createActions() { + QMenu *file_menu = menuBar()->addMenu(tr("&File")); + file_menu->addAction(tr("Open DBC File..."), this, &MainWindow::loadDBCFromFile); + file_menu->addAction(tr("Load DBC From Clipboard"), this, &MainWindow::loadDBCFromClipboard); + file_menu->addSeparator(); + file_menu->addAction(tr("Save DBC As..."), this, &MainWindow::saveDBCToFile); + file_menu->addAction(tr("Copy DBC To Clipboard"), this, &MainWindow::saveDBCToClipboard); + file_menu->addSeparator(); + file_menu->addAction(tr("Settings..."), this, &MainWindow::setOption); + QMenu *help_menu = menuBar()->addMenu(tr("&Help")); + help_menu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt); +} + +void MainWindow::createStatusBar() { + progress_bar = new QProgressBar(); + progress_bar->setRange(0, 100); + progress_bar->setTextVisible(true); + progress_bar->setFixedSize({230, 16}); + progress_bar->setVisible(false); + statusBar()->addPermanentWidget(progress_bar); +} + +void MainWindow::loadDBCFromName(const QString &name) { + if (name != dbc()->name()) + dbc()->open(name); +} + +void MainWindow::loadDBCFromFile() { + QString file_name = QFileDialog::getOpenFileName(this, tr("Open File"), QDir::homePath(), "DBC (*.dbc)"); + if (!file_name.isEmpty()) { + QFile file(file_name); + if (file.open(QIODevice::ReadOnly)) { + auto dbc_name = QFileInfo(file_name).baseName(); + dbc()->open(dbc_name, file.readAll()); + } + } +} + +void MainWindow::loadDBCFromClipboard() { + QString dbc_str = QGuiApplication::clipboard()->text(); + dbc()->open("From Clipboard", dbc_str); + QMessageBox::information(this, tr("Load From Clipboard"), tr("DBC Successfully Loaded!")); +} + +void MainWindow::loadDBCFromFingerprint() { + auto fingerprint = can->carFingerprint(); + fingerprint_label->setText(fingerprint); + if (!fingerprint.isEmpty() && dbc()->name().isEmpty()) { + auto dbc_name = fingerprint_to_dbc[fingerprint]; + if (dbc_name != QJsonValue::Undefined) { + loadDBCFromName(dbc_name.toString()); + } + } +} + +void MainWindow::saveDBCToFile() { + QString file_name = QFileDialog::getSaveFileName(this, tr("Save File"), + QDir::homePath() + "/untitled.dbc", tr("DBC (*.dbc)")); + if (!file_name.isEmpty()) { + QFile file(file_name); + if (file.open(QIODevice::WriteOnly)) + file.write(dbc()->generateDBC().toUtf8()); + } +} + +void MainWindow::saveDBCToClipboard() { + QGuiApplication::clipboard()->setText(dbc()->generateDBC()); + QMessageBox::information(this, tr("Copy To Clipboard"), tr("DBC Successfully copied!")); } void MainWindow::updateDownloadProgress(uint64_t cur, uint64_t total, bool success) { - if (success && cur < total) { + if (success && cur < total) { progress_bar->setValue((cur / (double)total) * 100); progress_bar->setFormat(tr("Downloading %p% (%1)").arg(formattedDataSize(total).c_str())); progress_bar->show(); diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index b77744ba9c..bb9280c3ea 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -1,5 +1,8 @@ #pragma once +#include +#include +#include #include #include #include @@ -9,19 +12,29 @@ #include "tools/cabana/messageswidget.h" #include "tools/cabana/videowidget.h" -class MainWindow : public QWidget { +class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(); void dockCharts(bool dock); - void showStatusMessage(const QString &msg, int timeout = 0) { status_bar->showMessage(msg, timeout); } + void showStatusMessage(const QString &msg, int timeout = 0) { statusBar()->showMessage(msg, timeout); } + +public slots: + void loadDBCFromName(const QString &name); + void loadDBCFromFingerprint(); + void loadDBCFromFile(); + void loadDBCFromClipboard(); + void saveDBCToFile(); + void saveDBCToClipboard(); signals: void showMessage(const QString &msg, int timeout); void updateProgressBar(uint64_t cur, uint64_t total, bool success); protected: + void createActions(); + void createStatusBar(); void closeEvent(QCloseEvent *event) override; void updateDownloadProgress(uint64_t cur, uint64_t total, bool success); void setOption(); @@ -34,5 +47,7 @@ protected: QWidget *floating_window = nullptr; QVBoxLayout *r_layout; QProgressBar *progress_bar; - QStatusBar *status_bar; + QLabel *fingerprint_label; + QJsonDocument fingerprint_to_dbc; + QComboBox *dbc_combo; }; diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index e80a66bce9..dbf87a3da2 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -1,15 +1,8 @@ #include "tools/cabana/messageswidget.h" -#include -#include -#include -#include -#include #include #include #include -#include -#include #include #include "tools/cabana/dbcmanager.h" @@ -18,31 +11,6 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); - // DBC file selector - QHBoxLayout *dbc_file_layout = new QHBoxLayout(); - dbc_combo = new QComboBox(this); - auto dbc_names = dbc()->allDBCNames(); - for (const auto &name : dbc_names) { - dbc_combo->addItem(QString::fromStdString(name)); - } - dbc_combo->model()->sort(0); - dbc_combo->setEditable(true); - dbc_combo->setCurrentText(QString()); - dbc_combo->setInsertPolicy(QComboBox::NoInsert); - dbc_combo->completer()->setCompletionMode(QCompleter::PopupCompletion); - QFont font; - font.setBold(true); - dbc_combo->lineEdit()->setFont(font); - dbc_file_layout->addWidget(dbc_combo); - - QPushButton *load_from_paste = new QPushButton(tr("Load from paste"), this); - dbc_file_layout->addWidget(load_from_paste); - - dbc_file_layout->addStretch(); - QPushButton *save_btn = new QPushButton(tr("Save DBC"), this); - dbc_file_layout->addWidget(save_btn); - main_layout->addLayout(dbc_file_layout); - // message filter QLineEdit *filter = new QLineEdit(this); filter->setClearButtonEnabled(true); @@ -67,55 +35,22 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { // signals/slots QObject::connect(filter, &QLineEdit::textChanged, model, &MessageListModel::setFilterString); - QObject::connect(can, &CANMessages::eventsMerged, this, &MessagesWidget::loadDBCFromFingerprint); - QObject::connect(can, &CANMessages::updated, [this]() { model->updateState(); }); - QObject::connect(dbc_combo, SIGNAL(activated(const QString &)), SLOT(loadDBCFromName(const QString &))); - QObject::connect(load_from_paste, &QPushButton::clicked, this, &MessagesWidget::loadDBCFromPaste); - QObject::connect(save_btn, &QPushButton::clicked, this, &MessagesWidget::saveDBC); + QObject::connect(can, &CANMessages::msgsReceived, model, &MessageListModel::msgsReceived); + QObject::connect(dbc(), &DBCManager::DBCFileChanged, model, &MessageListModel::sortMessages); + QObject::connect(dbc(), &DBCManager::msgUpdated, model, &MessageListModel::sortMessages); + QObject::connect(dbc(), &DBCManager::msgRemoved, model, &MessageListModel::sortMessages); QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex ¤t, const QModelIndex &previous) { - if (current.isValid()) { - emit msgSelectionChanged(current.data(Qt::UserRole).toString()); + if (current.isValid() && current.row() < model->msgs.size()) { + if (model->msgs[current.row()] != current_msg_id) { + current_msg_id = model->msgs[current.row()]; + emit msgSelectionChanged(current_msg_id); + } } }); - - QFile json_file("./car_fingerprint_to_dbc.json"); - if (json_file.open(QIODevice::ReadOnly)) { - fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll()); - } -} - -void MessagesWidget::loadDBCFromName(const QString &name) { - if (name != dbc()->name()) { - dbc()->open(name); - dbc_combo->setCurrentText(name); - // re-sort model to refresh column 'Name' - model->updateState(true); - } -} - -void MessagesWidget::loadDBCFromPaste() { - LoadDBCDialog dlg(this); - if (dlg.exec()) { - dbc()->open("from paste", dlg.dbc_edit->toPlainText()); - dbc_combo->setCurrentText("loaded from paste"); - model->updateState(true); - } -} - -void MessagesWidget::loadDBCFromFingerprint() { - auto fingerprint = can->carFingerprint(); - if (!fingerprint.isEmpty() && dbc()->name().isEmpty()) { - auto dbc_name = fingerprint_to_dbc[fingerprint]; - if (dbc_name != QJsonValue::Undefined) { - loadDBCFromName(dbc_name.toString()); - } - } -} - -void MessagesWidget::saveDBC() { - SaveDBCDialog dlg(this); - dlg.dbc_edit->setText(dbc()->generateDBC()); - dlg.exec(); + QObject::connect(model, &MessageListModel::modelReset, [this]() { + if (int row = model->msgs.indexOf(current_msg_id); row != -1) + table_widget->selectionModel()->setCurrentIndex(model->index(row, 0), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); + }); } // MessageListModel @@ -128,103 +63,71 @@ QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, QVariant MessageListModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { - const auto &m = msgs[index.row()]; - auto &can_data = can->lastMessage(m->id); + const auto &id = msgs[index.row()]; + auto &can_data = can->lastMessage(id); switch (index.column()) { - case 0: return m->name; - case 1: return m->id; + case 0: return msgName(id); + case 1: return id; case 2: return can_data.freq; case 3: return can_data.count; case 4: return toHex(can_data.dat); } - } else if (role == Qt::UserRole) { - return msgs[index.row()]->id; - } else if (role == Qt::FontRole) { - if (index.column() == columnCount() - 1) { - return QFontDatabase::systemFont(QFontDatabase::FixedFont); - } + } else if (role == Qt::FontRole && index.column() == columnCount() - 1) { + return QFontDatabase::systemFont(QFontDatabase::FixedFont); } return {}; } -void MessageListModel::setFilterString(const QString &string) { +void MessageListModel::setFilterString(const QString &string) { filter_str = string; - updateState(true); -} - -bool MessageListModel::updateMessages(bool sort) { - if (msgs.size() == can->can_msgs.size() && filter_str.isEmpty() && !sort) - return false; - - // update message list - int i = 0; bool search_id = filter_str.contains(':'); + msgs.clear(); for (auto it = can->can_msgs.begin(); it != can->can_msgs.end(); ++it) { - const Msg *msg = dbc()->msg(it.key()); - QString msg_name = msg ? msg->name.c_str() : "untitled"; - if (!filter_str.isEmpty() && !(search_id ? it.key() : msg_name).contains(filter_str, Qt::CaseInsensitive)) - continue; - auto &m = i < msgs.size() ? msgs[i] : msgs.emplace_back(new Message); - m->id = it.key(); - m->name = msg_name; - ++i; + if ((search_id ? it.key() : msgName(it.key())).contains(filter_str, Qt::CaseInsensitive)) + msgs.push_back(it.key()); } - msgs.resize(i); + sortMessages(); +} +void MessageListModel::sortMessages() { + beginResetModel(); if (sort_column == 0) { std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { - bool ret = l->name < r->name || (l->name == r->name && l->id < r->id); + bool ret = std::pair{msgName(l), l} < std::pair{msgName(r), r}; return sort_order == Qt::AscendingOrder ? ret : !ret; }); } else if (sort_column == 1) { std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { - return sort_order == Qt::AscendingOrder ? l->id < r->id : l->id > r->id; + return sort_order == Qt::AscendingOrder ? l < r : l > r; }); } else if (sort_column == 2) { - // sort by frequency std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { - uint32_t lfreq = can->lastMessage(l->id).freq; - uint32_t rfreq = can->lastMessage(r->id).freq; - bool ret = lfreq < rfreq || (lfreq == rfreq && l->id < r->id); + bool ret = std::pair{can->lastMessage(l).freq, l} < std::pair{can->lastMessage(r).freq, r}; return sort_order == Qt::AscendingOrder ? ret : !ret; }); } else if (sort_column == 3) { - // sort by count std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { - uint32_t lcount = can->lastMessage(l->id).count; - uint32_t rcount = can->lastMessage(r->id).count; - bool ret = lcount < rcount || (lcount == rcount && l->id < r->id); + bool ret = std::pair{can->lastMessage(l).count, l} < std::pair{can->lastMessage(r).count, r}; return sort_order == Qt::AscendingOrder ? ret : !ret; }); } - return true; + endResetModel(); } -void MessageListModel::updateState(bool sort) { +void MessageListModel::msgsReceived(const QHash *new_msgs) { int prev_row_count = msgs.size(); - auto prev_idx = persistentIndexList(); - QString selected_msg_id = prev_idx.empty() ? "" : prev_idx[0].data(Qt::UserRole).toString(); - - bool msg_updated = updateMessages(sort); - int delta = msgs.size() - prev_row_count; - if (delta > 0) { - beginInsertRows({}, prev_row_count, msgs.size() - 1); - endInsertRows(); - } else if (delta < 0) { - beginRemoveRows({}, msgs.size(), prev_row_count - 1); - endRemoveRows(); + if (filter_str.isEmpty() && msgs.size() != can->can_msgs.size()) { + msgs = can->can_msgs.keys(); } - - if (!msgs.empty()) { - if (msg_updated && !prev_idx.isEmpty()) { - // keep selection - auto it = std::find_if(msgs.begin(), msgs.end(), [&](auto &m) { return m->id == selected_msg_id; }); - if (it != msgs.end()) { - for (auto &idx : prev_idx) - changePersistentIndex(idx, index(std::distance(msgs.begin(), it), idx.column())); - } + if (msgs.size() != prev_row_count) { + sortMessages(); + return; + } + for (int i = 0; i < msgs.size(); ++i) { + if (new_msgs->contains(msgs[i])) { + for (int col = 2; col < columnCount(); ++col) + emit dataChanged(index(i, col), index(i, col), {Qt::DisplayRole}); } - emit dataChanged(index(0, 0), index(msgs.size() - 1, 3), {Qt::DisplayRole}); } } @@ -232,63 +135,6 @@ void MessageListModel::sort(int column, Qt::SortOrder order) { if (column != columnCount() - 1) { sort_column = column; sort_order = order; - updateState(true); - } -} - -// LoadDBCDialog - -LoadDBCDialog::LoadDBCDialog(QWidget *parent) : QDialog(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - dbc_edit = new QTextEdit(this); - dbc_edit->setAcceptRichText(false); - dbc_edit->setPlaceholderText(tr("paste DBC file here")); - main_layout->addWidget(dbc_edit); - auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - main_layout->addWidget(buttonBox); - - setMinimumSize({640, 480}); - QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); -} - -// SaveDBCDialog - -SaveDBCDialog::SaveDBCDialog(QWidget *parent) : QDialog(parent) { - setWindowTitle(tr("Save DBC")); - QVBoxLayout *main_layout = new QVBoxLayout(this); - dbc_edit = new QTextEdit(this); - dbc_edit->setAcceptRichText(false); - main_layout->addWidget(dbc_edit); - - QPushButton *copy_to_clipboard = new QPushButton(tr("Copy To Clipboard"), this); - QPushButton *save_as = new QPushButton(tr("Save As"), this); - - QHBoxLayout *btn_layout = new QHBoxLayout(); - btn_layout->addStretch(); - btn_layout->addWidget(copy_to_clipboard); - btn_layout->addWidget(save_as); - main_layout->addLayout(btn_layout); - setMinimumSize({640, 480}); - - QObject::connect(copy_to_clipboard, &QPushButton::clicked, this, &SaveDBCDialog::copytoClipboard); - QObject::connect(save_as, &QPushButton::clicked, this, &SaveDBCDialog::saveAs); -} - -void SaveDBCDialog::copytoClipboard() { - dbc_edit->selectAll(); - dbc_edit->copy(); - QDialog::accept(); -} - -void SaveDBCDialog::saveAs() { - QString file_name = QFileDialog::getSaveFileName(this, tr("Save File"), - QDir::homePath() + "/untitled.dbc", tr("DBC (*.dbc)")); - if (!file_name.isEmpty()) { - QFile file(file_name); - if (file.open(QIODevice::WriteOnly)) { - file.write(dbc_edit->toPlainText().toUtf8()); - } - QDialog::accept(); + sortMessages(); } } diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index 255dce7dc8..3a42bed4be 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -1,32 +1,10 @@ #pragma once #include -#include -#include -#include #include -#include #include "tools/cabana/canmessages.h" -class LoadDBCDialog : public QDialog { - Q_OBJECT - -public: - LoadDBCDialog(QWidget *parent); - QTextEdit *dbc_edit; -}; - -class SaveDBCDialog : public QDialog { - Q_OBJECT - -public: - SaveDBCDialog(QWidget *parent); - void copytoClipboard(); - void saveAs(); - QTextEdit *dbc_edit; -}; - class MessageListModel : public QAbstractTableModel { Q_OBJECT @@ -37,16 +15,12 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; int rowCount(const QModelIndex &parent = QModelIndex()) const override { return msgs.size(); } void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; - void updateState(bool sort = false); void setFilterString(const QString &string); + void msgsReceived(const QHash *new_msgs = nullptr); + void sortMessages(); + QStringList msgs; private: - bool updateMessages(bool sort); - - struct Message { - QString id, name; - }; - std::vector> msgs; QString filter_str; int sort_column = 0; Qt::SortOrder sort_order = Qt::AscendingOrder; @@ -57,19 +31,11 @@ class MessagesWidget : public QWidget { public: MessagesWidget(QWidget *parent); - -public slots: - void loadDBCFromName(const QString &name); - void loadDBCFromFingerprint(); - void loadDBCFromPaste(); - void saveDBC(); - signals: void msgSelectionChanged(const QString &message_id); protected: QTableView *table_widget; - QComboBox *dbc_combo; + QString current_msg_id; MessageListModel *model; - QJsonDocument fingerprint_to_dbc; }; diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc index bba59c0d74..b173b41df3 100644 --- a/tools/cabana/settings.cc +++ b/tools/cabana/settings.cc @@ -36,8 +36,7 @@ void Settings::load() { SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) { setWindowTitle(tr("Settings")); - QVBoxLayout *main_layout = new QVBoxLayout(this); - QFormLayout *form_layout = new QFormLayout(); + QFormLayout *form_layout = new QFormLayout(this); fps = new QSpinBox(this); fps->setRange(10, 100); @@ -74,10 +73,8 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) { chart_theme->setCurrentIndex(settings.chart_theme == 1 ? 1 : 0); form_layout->addRow(tr("Chart theme"), chart_theme); - main_layout->addLayout(form_layout); - auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - main_layout->addWidget(buttonBox); + form_layout->addRow(buttonBox); setFixedWidth(360); connect(buttonBox, &QDialogButtonBox::accepted, this, &SettingsDlg::save); diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index ee91887d0a..99365294b8 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -28,7 +28,6 @@ SignalForm::SignalForm(QWidget *parent) : QWidget(parent) { endianness->addItems({"Little", "Big"}); form_layout->addRow(tr("Endianness"), endianness); - ; form_layout->addRow(tr("lsb"), lsb = new QLabel()); form_layout->addRow(tr("msb"), msb = new QLabel()); @@ -130,7 +129,18 @@ void SignalEdit::saveSignal() { s.offset = form->offset->text().toDouble(); s.factor = form->factor->text().toDouble(); s.is_signed = form->sign->currentIndex() == 0; - s.is_little_endian = form->endianness->currentIndex() == 0; + bool little_endian = form->endianness->currentIndex() == 0; + if (little_endian != s.is_little_endian) { + int start = std::floor(s.start_bit / 8); + if (little_endian) { + int end = std::floor((s.start_bit - s.size + 1) / 8); + s.start_bit = start == end ? s.start_bit - s.size + 1 : bigEndianStartBitsIndex(s.start_bit); + } else { + int end = std::floor((s.start_bit + s.size - 1) / 8); + s.start_bit = start == end ? s.start_bit + s.size - 1 : bigEndianBitIndex(s.start_bit); + } + s.is_little_endian = little_endian; + } if (s.is_little_endian) { s.lsb = s.start_bit; s.msb = s.start_bit + s.size - 1; diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 9a28f71bb9..d5e640b5f7 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -28,11 +28,9 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { slider = new Slider(this); slider->setSingleStep(0); - slider->setMinimum(0); - slider->setMaximum(can->totalSeconds() * 1000); slider_layout->addWidget(slider); - end_time_label = new QLabel(formatTime(can->totalSeconds())); + end_time_label = new QLabel(this); slider_layout->addWidget(end_time_label); main_layout->addLayout(slider_layout); @@ -61,6 +59,10 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(formatTime(value / 1000)); }); QObject::connect(cam_widget, &CameraWidget::clicked, [this]() { pause(!can->isPaused()); }); QObject::connect(play_btn, &QPushButton::clicked, [=]() { pause(!can->isPaused()); }); + QObject::connect(can, &CANMessages::streamStarted, [this]() { + end_time_label->setText(formatTime(can->totalSeconds())); + slider->setRange(0, can->totalSeconds() * 1000); + }); } void VideoWidget::pause(bool pause) { @@ -74,8 +76,7 @@ void VideoWidget::rangeChanged(double min, double max, bool is_zoomed) { max = can->totalSeconds(); } end_time_label->setText(formatTime(max)); - slider->setMinimum(min * 1000); - slider->setMaximum(max * 1000); + slider->setRange(min * 1000, max * 1000); } void VideoWidget::updateState() { @@ -91,7 +92,7 @@ Slider::Slider(QWidget *parent) : QSlider(Qt::Horizontal, parent) { timeline = can->getTimeline(); update(); }); - timer->start(); + QObject::connect(can, SIGNAL(streamStarted()), timer, SLOT(start())); } void Slider::sliderChange(QAbstractSlider::SliderChange change) { diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index 51dae4c76f..16f60b0b03 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -3,7 +3,6 @@ #include #include #include -#include #include "selfdrive/ui/qt/widgets/cameraview.h" #include "tools/cabana/canmessages.h" diff --git a/tools/gpstest/run_unittest.sh b/tools/gpstest/run_unittest.sh index 9f93fdfc9a..e0ca017a6d 100755 --- a/tools/gpstest/run_unittest.sh +++ b/tools/gpstest/run_unittest.sh @@ -4,7 +4,7 @@ # run limeGPS with random static location timeout 300 ./simulate_gps_signal.py 32.7518 -117.1962 & -gps_PID=$(ps -aux | grep -m 1 "timeout 300" | cut -d ' ' -f 7) +gps_PID=$(ps -aux | grep -m 1 "timeout 300" | awk '{print $2}') echo "starting limeGPS..." sleep 10 diff --git a/tools/gpstest/test_gps_qcom.py b/tools/gpstest/test_gps_qcom.py index 0909316c5e..b3ce93fc81 100644 --- a/tools/gpstest/test_gps_qcom.py +++ b/tools/gpstest/test_gps_qcom.py @@ -40,14 +40,10 @@ class TestGPS(unittest.TestCase): if ublox_available: raise unittest.SkipTest - @unittest.skip("Skip cold start test due to time") - def test_quectel_cold_start(self): + def test_a_quectel_cold_start(self): # delete assistance data to enforce cold start for GNSS # testing shows that this takes up to 20min - # invalidate supl setting, cannot be reset - _, err = exec_mmcli("--location-set-supl-server=unittest:1") - _, err = exec_mmcli("--command='AT+QGPSDEL=0'") assert len(err) == 0, f"GPSDEL failed: {err}" @@ -55,27 +51,6 @@ class TestGPS(unittest.TestCase): start_time = time.monotonic() glo = messaging.sub_sock("gpsLocation", timeout=0.1) - timeout = 10*60*25 # 25 minute - timedout = wait_for_location(glo, timeout) - managed_processes['rawgpsd'].stop() - - assert timedout is False, "Waiting for location timed out (25min)!" - - duration = time.monotonic() - start_time - assert duration < 50, f"Received GPS location {duration}!" - - - def test_a_quectel_cold_start_AGPS(self): - _, err = exec_mmcli("--command='AT+QGPSDEL=0'") - assert len(err) == 0, f"GPSDEL failed: {err}" - - # setup AGPS - exec_mmcli("--location-set-supl-server=supl.google.com:7276") - - managed_processes['rawgpsd'].start() - start_time = time.monotonic() - glo = messaging.sub_sock("gpsLocation", timeout=0.1) - timeout = 10*60*3 # 3 minute timedout = wait_for_location(glo, timeout) managed_processes['rawgpsd'].stop() @@ -87,15 +62,11 @@ class TestGPS(unittest.TestCase): def test_b_quectel_startup(self): - - # setup AGPS - exec_mmcli("--location-set-supl-server=supl.google.com:7276") - managed_processes['rawgpsd'].start() start_time = time.monotonic() glo = messaging.sub_sock("gpsLocation", timeout=0.1) - timeout = 10*60*3 # 3 minute + timeout = 10*60 # 1 minute timedout = wait_for_location(glo, timeout) managed_processes['rawgpsd'].stop()