mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-30 19:13:43 +08:00
Merge branch 'dev-pub/master' into dev-priv/master
# Conflicts: # panda
This commit is contained in:
@@ -61,7 +61,7 @@ jobs:
|
||||
cp .pylintrc $STRIPPED_DIR
|
||||
cp mypy.ini $STRIPPED_DIR
|
||||
cd $STRIPPED_DIR
|
||||
${{ env.RUN }} "SKIP=test_translations pre-commit run --all"
|
||||
${{ env.RUN }} "pre-commit run --all"
|
||||
|
||||
build_all:
|
||||
name: build all
|
||||
|
||||
Vendored
+3
-1
@@ -81,6 +81,7 @@ pipeline {
|
||||
|
||||
parallel {
|
||||
|
||||
/*
|
||||
stage('simulator') {
|
||||
agent {
|
||||
dockerfile {
|
||||
@@ -108,6 +109,7 @@ pipeline {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
stage('build') {
|
||||
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
|
||||
@@ -152,7 +154,7 @@ pipeline {
|
||||
stage('camerad-ar') {
|
||||
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
|
||||
steps {
|
||||
phone_steps("tici-ar0321", [
|
||||
phone_steps("tici-ar0231", [
|
||||
["build", "cd selfdrive/manager && ./build.py"],
|
||||
["test camerad", "python system/camerad/test/test_camerad.py"],
|
||||
["test exposure", "python system/camerad/test/test_exposure.py"],
|
||||
|
||||
+3
-2
@@ -282,7 +282,7 @@ Export('envCython')
|
||||
|
||||
# Qt build environment
|
||||
qt_env = env.Clone()
|
||||
qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "Multimedia", "Quick", "Qml", "QuickWidgets", "Location", "Positioning", "DBus"]
|
||||
qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "Multimedia", "Quick", "Qml", "QuickWidgets", "Location", "Positioning", "DBus", "Xml"]
|
||||
|
||||
qt_libs = []
|
||||
if arch == "Darwin":
|
||||
@@ -311,6 +311,7 @@ else:
|
||||
qt_libs = [f"Qt5{m}" for m in qt_modules]
|
||||
if arch == "larch64":
|
||||
qt_libs += ["GLESv2", "wayland-client"]
|
||||
qt_env.PrependENVPath('PATH', Dir("#third_party/qt5/larch64/bin/").abspath)
|
||||
elif arch != "Darwin":
|
||||
qt_libs += ["GL"]
|
||||
|
||||
@@ -387,10 +388,10 @@ rednose_config = {
|
||||
if arch != "larch64":
|
||||
rednose_config['to_build'].update({
|
||||
'loc_4': ('#selfdrive/locationd/models/loc_kf.py', True, [], rednose_deps),
|
||||
'lane': ('#selfdrive/locationd/models/lane_kf.py', True, [], rednose_deps),
|
||||
'pos_computer_4': ('#rednose/helpers/lst_sq_computer.py', False, [], []),
|
||||
'pos_computer_5': ('#rednose/helpers/lst_sq_computer.py', False, [], []),
|
||||
'feature_handler_5': ('#rednose/helpers/feature_handler.py', False, [], []),
|
||||
'lane': ('#xx/pipeline/lib/ekf/lane_kf.py', True, [], rednose_deps),
|
||||
})
|
||||
|
||||
Export('rednose_config')
|
||||
|
||||
+1
-1
Submodule body updated: dc780f858c...e1805f65ee
+1
-1
Submodule cereal updated: f0471b7165...0565db5918
@@ -1,5 +1,20 @@
|
||||
#include "common/gpio.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
int gpio_init(int pin_nr, bool output) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gpio_set(int pin_nr, bool high) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gpiochip_get_ro_value_fd(const char* consumer_label, int gpiochiop_id, int pin_nr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -63,3 +78,5 @@ int gpiochip_get_ro_value_fd(const char* consumer_label, int gpiochiop_id, int p
|
||||
close(fd);
|
||||
return rq.fd;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+2
-1
@@ -104,7 +104,7 @@ std::unordered_map<std::string, uint32_t> keys = {
|
||||
{"DisablePowerDown", PERSISTENT},
|
||||
{"ExperimentalMode", PERSISTENT},
|
||||
{"ExperimentalModeConfirmed", PERSISTENT},
|
||||
{"ExperimentalLongitudinalEnabled", PERSISTENT}, // WARNING: THIS MAY DISABLE AEB
|
||||
{"ExperimentalLongitudinalEnabled", PERSISTENT},
|
||||
{"DisableUpdates", PERSISTENT},
|
||||
{"DisengageOnAccelerator", PERSISTENT},
|
||||
{"DongleId", PERSISTENT},
|
||||
@@ -134,6 +134,7 @@ std::unordered_map<std::string, uint32_t> keys = {
|
||||
{"IsRhdDetected", PERSISTENT},
|
||||
{"IsTakingSnapshot", CLEAR_ON_MANAGER_START},
|
||||
{"IsTestedBranch", CLEAR_ON_MANAGER_START},
|
||||
{"IsReleaseBranch", CLEAR_ON_MANAGER_START},
|
||||
{"IsUpdateAvailable", CLEAR_ON_MANAGER_START},
|
||||
{"JoystickDebugMode", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF},
|
||||
{"LaikadEphemeris", PERSISTENT | DONT_LOG},
|
||||
|
||||
+4
-3
@@ -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.
|
||||
|
||||
# 223 Supported Cars
|
||||
# 224 Supported Cars
|
||||
|
||||
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness|Video|
|
||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
@@ -32,7 +32,8 @@ A supported vehicle is one that just works when you install a comma three. All s
|
||||
|Genesis|G70 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Genesis&model=G70 2020">Hyundai F</a>||
|
||||
|Genesis|G80 2017-19|All|Stock|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Genesis&model=G80 2017-19">Hyundai H</a>||
|
||||
|Genesis|G90 2017-18|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Genesis&model=G90 2017-18">Hyundai C</a>||
|
||||
|Genesis|GV60 2023[<sup>5</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Genesis&model=GV60 2023">Hyundai K</a>||
|
||||
|Genesis|GV60 (Advanced Trim) 2023[<sup>5</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Genesis&model=GV60 (Advanced Trim) 2023">Hyundai A</a>||
|
||||
|Genesis|GV60 (Performance Trim) 2023[<sup>5</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Genesis&model=GV60 (Performance Trim) 2023">Hyundai K</a>||
|
||||
|Genesis|GV70 2022-23[<sup>5</sup>](#footnotes)|All|Stock|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Genesis&model=GV70 2022-23">Hyundai L</a>||
|
||||
|GMC|Acadia 2018[<sup>3</sup>](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=GMC&model=Acadia 2018">OBD-II</a>|<a href="https://www.youtube.com/watch?v=0ZN6DdsBUZo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=GMC&model=Sierra 1500 2020-21">GM</a>|<a href="https://youtu.be/5HbNoBLzRwE" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
@@ -57,7 +58,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|
||||
|Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Honda&model=Pilot 2016-22">Honda Nidec</a>||
|
||||
|Honda|Ridgeline 2017-22|Honda Sensing|openpilot|25 mph|12 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Honda&model=Ridgeline 2017-22">Honda Nidec</a>||
|
||||
|Hyundai|Elantra 2017-19|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Hyundai&model=Elantra 2017-19">Hyundai B</a>||
|
||||
|Hyundai|Elantra 2021-22|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Hyundai&model=Elantra 2021-22">Hyundai K</a>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Hyundai&model=Elantra 2021-23">Hyundai K</a>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Hyundai&model=Elantra GT 2017-19">Hyundai E</a>||
|
||||
|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Hyundai&model=Elantra Hybrid 2021-23">Hyundai K</a>|<a href="https://youtu.be/_EdYQtV52-c" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Hyundai&model=Genesis 2015-16">Hyundai J</a>||
|
||||
|
||||
+1
-1
Submodule laika_repo updated: 73bf110ae0...2e5c7b8a85
+1
-1
Submodule panda updated: ac3ea96e27...9cf6c5a158
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
while read hash submodule ref; do
|
||||
git -C $submodule fetch --depth 100 origin master
|
||||
git -C $submodule fetch --depth 200 origin master
|
||||
git -C $submodule branch -r --contains $hash | grep "origin/master"
|
||||
if [ "$?" -eq 0 ]; then
|
||||
echo "$submodule ok"
|
||||
|
||||
+12
-9
@@ -320,6 +320,12 @@ selfdrive/ui/qt/widgets/*.cc
|
||||
selfdrive/ui/qt/widgets/*.h
|
||||
selfdrive/ui/qt/maps/*.cc
|
||||
selfdrive/ui/qt/maps/*.h
|
||||
selfdrive/ui/qt/setup/*.cc
|
||||
selfdrive/ui/qt/setup/*.h
|
||||
|
||||
selfdrive/ui/installer/*.cc
|
||||
selfdrive/ui/installer/*.h
|
||||
selfdrive/ui/installer/*.cc
|
||||
|
||||
system/camerad/SConscript
|
||||
system/camerad/main.cc
|
||||
@@ -436,6 +442,7 @@ third_party/acados/larch64/**
|
||||
third_party/acados/include/**
|
||||
third_party/acados/acados_template/**
|
||||
|
||||
third_party/bootstrap/**
|
||||
third_party/qt5/larch64/bin/**
|
||||
|
||||
scripts/update_now.sh
|
||||
@@ -573,17 +580,13 @@ opendbc/tesla_can.dbc
|
||||
opendbc/tesla_radar.dbc
|
||||
opendbc/tesla_powertrain.dbc
|
||||
|
||||
tinygrad_repo/openpilot/compile.py
|
||||
tinygrad_repo/accel/opencl/*
|
||||
tinygrad_repo/tinygrad/llops/ops_opencl.py
|
||||
tinygrad_repo/openpilot/compile.py
|
||||
tinygrad_repo/extra/onnx.py
|
||||
tinygrad_repo/extra/thneed.py
|
||||
tinygrad_repo/extra/utils.py
|
||||
tinygrad_repo/tinygrad/llops/ops_gpu.py
|
||||
tinygrad_repo/tinygrad/llops/ops_opencl.py
|
||||
tinygrad_repo/tinygrad/helpers.py
|
||||
tinygrad_repo/tinygrad/mlops.py
|
||||
tinygrad_repo/tinygrad/ops.py
|
||||
tinygrad_repo/tinygrad/shapetracker.py
|
||||
tinygrad_repo/tinygrad/tensor.py
|
||||
tinygrad_repo/tinygrad/nn/__init__.py
|
||||
tinygrad_repo/tinygrad/nn/optim.py
|
||||
tinygrad_repo/tinygrad/shape/*
|
||||
tinygrad_repo/tinygrad/nn/*
|
||||
tinygrad_repo/tinygrad/*.py
|
||||
|
||||
+2
-4
@@ -1,5 +1,3 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
nmcli connection modify --temporary lte ipv4.route-metric 1
|
||||
nmcli connection modify --temporary lte ipv6.route-metric 1
|
||||
nmcli con up lte
|
||||
nmcli connection modify --temporary lte ipv4.route-metric 1 ipv6.route-metric 1
|
||||
nmcli con up --wait --ask lte
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE RCC><RCC version="1.0">
|
||||
<qresource>
|
||||
<file alias="bootstrap-icons.svg">../../third_party/bootstrap/bootstrap-icons.svg</file>
|
||||
<file>img_continue_triangle.svg</file>
|
||||
<file>img_circled_check.svg</file>
|
||||
<file>img_circled_slash.svg</file>
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
|
||||
#define MAX_IR_POWER 0.5f
|
||||
#define MIN_IR_POWER 0.0f
|
||||
#define CUTOFF_IL 200
|
||||
#define SATURATE_IL 1600
|
||||
#define CUTOFF_IL 400
|
||||
#define SATURATE_IL 1000
|
||||
#define NIBBLE_TO_HEX(n) ((n) < 10 ? (n) + '0' : ((n) - 10) + 'a')
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@@ -225,9 +225,9 @@ void can_send_thread(std::vector<Panda *> pandas, bool fake_send) {
|
||||
//Dont send if older than 1 second
|
||||
if ((nanos_since_boot() - event.getLogMonoTime() < 1e9) && !fake_send) {
|
||||
for (const auto& panda : pandas) {
|
||||
LOGT("sending sendcan to panda: %s", (panda->hw_serial).c_str());
|
||||
LOGT("sending sendcan to panda: %s", (panda->hw_serial()).c_str());
|
||||
panda->can_send(event.getSendcan());
|
||||
LOGT("sendcan sent to panda: %s", (panda->hw_serial).c_str());
|
||||
LOGT("sendcan sent to panda: %s", (panda->hw_serial()).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -540,9 +540,8 @@ void peripheral_control_thread(Panda *panda, bool no_fan_control) {
|
||||
if (sm.updated("driverCameraState")) {
|
||||
auto event = sm["driverCameraState"];
|
||||
int cur_integ_lines = event.getDriverCameraState().getIntegLines();
|
||||
float cur_gain = event.getDriverCameraState().getGain();
|
||||
|
||||
cur_integ_lines = integ_lines_filter.update(cur_integ_lines * cur_gain);
|
||||
cur_integ_lines = integ_lines_filter.update(cur_integ_lines);
|
||||
last_front_frame_t = event.getLogMonoTime();
|
||||
|
||||
if (cur_integ_lines <= CUTOFF_IL) {
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
#include "common/util.h"
|
||||
|
||||
Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) {
|
||||
// TODO: support SPI here one day...
|
||||
if (serial.find("spi") != std::string::npos) {
|
||||
handle = std::make_unique<PandaSpiHandle>(serial);
|
||||
} else {
|
||||
// try USB first, then SPI
|
||||
try {
|
||||
handle = std::make_unique<PandaUsbHandle>(serial);
|
||||
} catch (std::exception &e) {
|
||||
handle = std::make_unique<PandaSpiHandle>(serial);
|
||||
}
|
||||
|
||||
hw_type = get_hw_type();
|
||||
@@ -39,8 +39,20 @@ bool Panda::comms_healthy() {
|
||||
return handle->comms_healthy;
|
||||
}
|
||||
|
||||
std::string Panda::hw_serial() {
|
||||
return handle->hw_serial;
|
||||
}
|
||||
|
||||
std::vector<std::string> Panda::list() {
|
||||
return PandaUsbHandle::list();
|
||||
std::vector<std::string> serials = PandaUsbHandle::list();
|
||||
|
||||
for (auto s : PandaSpiHandle::list()) {
|
||||
if (std::find(serials.begin(), serials.end(), s) == serials.end()) {
|
||||
serials.push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
return serials;
|
||||
}
|
||||
|
||||
void Panda::set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param) {
|
||||
|
||||
@@ -48,13 +48,13 @@ private:
|
||||
public:
|
||||
Panda(std::string serial="", uint32_t bus_offset=0);
|
||||
|
||||
std::string hw_serial;
|
||||
cereal::PandaState::PandaType hw_type = cereal::PandaState::PandaType::UNKNOWN;
|
||||
bool has_rtc = false;
|
||||
const uint32_t bus_offset;
|
||||
|
||||
bool connected();
|
||||
bool comms_healthy();
|
||||
std::string hw_serial();
|
||||
|
||||
// Static functions
|
||||
static std::vector<std::string> list();
|
||||
|
||||
@@ -44,7 +44,7 @@ PandaUsbHandle::PandaUsbHandle(std::string serial) : PandaCommsHandle(serial) {
|
||||
ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, desc_serial, std::size(desc_serial));
|
||||
if (ret < 0) { goto fail; }
|
||||
|
||||
auto hw_serial = std::string((char *)desc_serial, ret);
|
||||
hw_serial = std::string((char *)desc_serial, ret);
|
||||
if (serial.empty() || serial == hw_serial) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ public:
|
||||
virtual ~PandaCommsHandle() {};
|
||||
virtual void cleanup() = 0;
|
||||
|
||||
std::string hw_serial;
|
||||
std::atomic<bool> connected = true;
|
||||
std::atomic<bool> comms_healthy = true;
|
||||
static std::vector<std::string> list();
|
||||
@@ -30,9 +31,6 @@ public:
|
||||
virtual int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT) = 0;
|
||||
virtual int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0;
|
||||
virtual int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0;
|
||||
|
||||
protected:
|
||||
std::recursive_mutex hw_lock;
|
||||
};
|
||||
|
||||
class PandaUsbHandle : public PandaCommsHandle {
|
||||
@@ -50,6 +48,7 @@ public:
|
||||
private:
|
||||
libusb_context *ctx = NULL;
|
||||
libusb_device_handle *dev_handle = NULL;
|
||||
std::recursive_mutex hw_lock;
|
||||
void handle_usb_issue(int err, const char func[]);
|
||||
};
|
||||
|
||||
@@ -69,6 +68,7 @@ private:
|
||||
int spi_fd = -1;
|
||||
uint8_t tx_buf[SPI_BUF_SIZE];
|
||||
uint8_t rx_buf[SPI_BUF_SIZE];
|
||||
inline static std::recursive_mutex hw_lock;
|
||||
|
||||
int wait_for_ack(spi_ioc_transfer &transfer, uint8_t ack);
|
||||
int bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len);
|
||||
|
||||
@@ -7,7 +7,7 @@ import subprocess
|
||||
from typing import List, NoReturn
|
||||
from functools import cmp_to_key
|
||||
|
||||
from panda import DEFAULT_FW_FN, DEFAULT_H7_FW_FN, MCU_TYPE_H7, Panda, PandaDFU
|
||||
from panda import Panda, PandaDFU
|
||||
from common.basedir import BASEDIR
|
||||
from common.params import Params
|
||||
from system.hardware import HARDWARE
|
||||
@@ -15,10 +15,8 @@ from system.swaglog import cloudlog
|
||||
|
||||
|
||||
def get_expected_signature(panda: Panda) -> bytes:
|
||||
fn = DEFAULT_H7_FW_FN if (panda.get_mcu_type() == MCU_TYPE_H7) else DEFAULT_FW_FN
|
||||
|
||||
try:
|
||||
return Panda.get_signature_from_firmware(fn)
|
||||
return Panda.get_signature_from_firmware(panda.get_mcu_type().config.app_path)
|
||||
except Exception:
|
||||
cloudlog.exception("Error computing expected signature")
|
||||
return b""
|
||||
|
||||
+61
-16
@@ -1,9 +1,12 @@
|
||||
#include <sys/file.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/spi/spidev.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include "common/util.h"
|
||||
#include "common/timing.h"
|
||||
@@ -27,41 +30,77 @@ struct __attribute__((packed)) spi_header {
|
||||
|
||||
const int SPI_MAX_RETRIES = 5;
|
||||
const int SPI_ACK_TIMEOUT = 50; // milliseconds
|
||||
const std::string SPI_DEVICE = "/dev/spidev0.0";
|
||||
|
||||
class LockEx {
|
||||
public:
|
||||
LockEx(int fd, std::recursive_mutex &m) : fd(fd), m(m) {
|
||||
m.lock();
|
||||
flock(fd, LOCK_EX);
|
||||
};
|
||||
|
||||
~LockEx() {
|
||||
m.unlock();
|
||||
flock(fd, LOCK_UN);
|
||||
}
|
||||
|
||||
private:
|
||||
int fd;
|
||||
std::recursive_mutex &m;
|
||||
};
|
||||
|
||||
|
||||
PandaSpiHandle::PandaSpiHandle(std::string serial) : PandaCommsHandle(serial) {
|
||||
LOGD("opening SPI panda: %s", serial.c_str());
|
||||
int ret;
|
||||
const int uid_len = 12;
|
||||
uint8_t uid[uid_len] = {0};
|
||||
|
||||
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);
|
||||
spi_fd = open(SPI_DEVICE.c_str(), O_RDWR);
|
||||
if (spi_fd < 0) {
|
||||
LOGE("failed opening SPI device %d", err);
|
||||
LOGE("failed opening SPI device %d", spi_fd);
|
||||
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);
|
||||
ret = util::safe_ioctl(spi_fd, SPI_IOC_WR_MODE, &spi_mode);
|
||||
if (ret < 0) {
|
||||
LOGE("failed setting SPI mode %d", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
err = util::safe_ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed);
|
||||
if (err < 0) {
|
||||
ret = util::safe_ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed);
|
||||
if (ret < 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) {
|
||||
ret = util::safe_ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits_per_word);
|
||||
if (ret < 0) {
|
||||
LOGE("failed setting SPI bits per word");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// get hw UID/serial
|
||||
ret = control_read(0xc3, 0, 0, uid, uid_len);
|
||||
if (ret == uid_len) {
|
||||
std::stringstream stream;
|
||||
for (int i = 0; i < uid_len; i++) {
|
||||
stream << std::hex << std::setw(2) << std::setfill('0') << int(uid[i]);
|
||||
}
|
||||
hw_serial = stream.str();
|
||||
} else {
|
||||
LOGD("failed to get serial %d", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!serial.empty() && (serial != hw_serial)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
fail:
|
||||
@@ -84,6 +123,7 @@ void PandaSpiHandle::cleanup() {
|
||||
|
||||
|
||||
int PandaSpiHandle::control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout) {
|
||||
LockEx lock(spi_fd, hw_lock);
|
||||
ControlPacket_t packet = {
|
||||
.request = request,
|
||||
.param1 = param1,
|
||||
@@ -94,6 +134,7 @@ int PandaSpiHandle::control_write(uint8_t request, uint16_t param1, uint16_t par
|
||||
}
|
||||
|
||||
int PandaSpiHandle::control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout) {
|
||||
LockEx lock(spi_fd, hw_lock);
|
||||
ControlPacket_t packet = {
|
||||
.request = request,
|
||||
.param1 = param1,
|
||||
@@ -104,15 +145,15 @@ int PandaSpiHandle::control_read(uint8_t request, uint16_t param1, uint16_t para
|
||||
}
|
||||
|
||||
int PandaSpiHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
|
||||
LockEx lock(spi_fd, hw_lock);
|
||||
return bulk_transfer(endpoint, data, length, NULL, 0);
|
||||
}
|
||||
int PandaSpiHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
|
||||
LockEx lock(spi_fd, hw_lock);
|
||||
return bulk_transfer(endpoint, NULL, 0, data, length);
|
||||
}
|
||||
|
||||
int PandaSpiHandle::bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len) {
|
||||
std::lock_guard lk(hw_lock);
|
||||
|
||||
const int xfer_size = 0x40 * 15;
|
||||
|
||||
int ret = 0;
|
||||
@@ -143,7 +184,12 @@ int PandaSpiHandle::bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t t
|
||||
}
|
||||
|
||||
std::vector<std::string> PandaSpiHandle::list() {
|
||||
// TODO: list all pandas available over SPI
|
||||
try {
|
||||
PandaSpiHandle sh("");
|
||||
return {sh.hw_serial};
|
||||
} catch (std::exception &e) {
|
||||
// no panda on SPI
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -167,7 +213,6 @@ int PandaSpiHandle::spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint1
|
||||
int ret;
|
||||
|
||||
int count = SPI_MAX_RETRIES;
|
||||
std::lock_guard lk(hw_lock);
|
||||
do {
|
||||
// TODO: handle error
|
||||
ret = spi_transfer(endpoint, tx_data, tx_len, rx_data, max_rx_len);
|
||||
@@ -195,7 +240,7 @@ int PandaSpiHandle::wait_for_ack(spi_ioc_transfer &transfer, uint8_t ack) {
|
||||
|
||||
// handle timeout
|
||||
if (millis_since_boot() - start_millis > SPI_ACK_TIMEOUT) {
|
||||
LOGE("SPI: timed out waiting for ACK");
|
||||
LOGD("SPI: timed out waiting for ACK");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,17 +173,15 @@ def fingerprint(logcan, sendcan, num_pandas):
|
||||
return car_fingerprint, finger, vin, car_fw, source, exact_match
|
||||
|
||||
|
||||
def get_car(logcan, sendcan, num_pandas=1):
|
||||
def get_car(logcan, sendcan, experimental_long_allowed, num_pandas=1):
|
||||
candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(logcan, sendcan, num_pandas)
|
||||
|
||||
if candidate is None:
|
||||
cloudlog.warning("car doesn't match any fingerprints: %r", fingerprints)
|
||||
candidate = "mock"
|
||||
|
||||
experimental_long = Params().get_bool("ExperimentalLongitudinalEnabled")
|
||||
|
||||
CarInterface, CarController, CarState = interfaces[candidate]
|
||||
CP = CarInterface.get_params(candidate, fingerprints, car_fw, experimental_long)
|
||||
CP = CarInterface.get_params(candidate, fingerprints, car_fw, experimental_long_allowed)
|
||||
CP.carVin = vin
|
||||
CP.carFw = car_fw
|
||||
CP.fingerprintSource = source
|
||||
|
||||
@@ -58,9 +58,9 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.mass = 2493. + STD_CARGO_KG
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
ret.minSteerSpeed = 14.5
|
||||
for fw in car_fw:
|
||||
if fw.ecu == 'eps' and fw.fwVersion.startswith((b"68312176", b"68273275")):
|
||||
ret.minSteerSpeed = 0.
|
||||
# Older EPS FW allow steer to zero
|
||||
if any(fw.ecu == 'eps' and fw.fwVersion[:4] <= b"6831" for fw in car_fw):
|
||||
ret.minSteerSpeed = 0.
|
||||
|
||||
elif candidate == CAR.RAM_HD:
|
||||
ret.steerActuatorDelay = 0.2
|
||||
|
||||
@@ -1,28 +1,13 @@
|
||||
import math
|
||||
from cereal import car
|
||||
from common.numpy_fast import clip, interp
|
||||
from common.numpy_fast import clip
|
||||
from opendbc.can.packer import CANPacker
|
||||
from selfdrive.car.ford import fordcan
|
||||
from selfdrive.car import apply_std_steer_angle_limits
|
||||
from selfdrive.car.ford.fordcan import create_acc_ui_msg, create_button_msg, create_lat_ctl_msg, create_lka_msg, create_lkas_ui_msg
|
||||
from selfdrive.car.ford.values import CANBUS, CarControllerParams
|
||||
|
||||
VisualAlert = car.CarControl.HUDControl.VisualAlert
|
||||
|
||||
|
||||
def apply_ford_steer_angle_limits(apply_angle, apply_angle_last, vEgo):
|
||||
# rate limit
|
||||
steer_up = apply_angle_last * apply_angle > 0. and abs(apply_angle) > abs(apply_angle_last)
|
||||
rate_limit = CarControllerParams.RATE_LIMIT_UP if steer_up else CarControllerParams.RATE_LIMIT_DOWN
|
||||
max_angle_diff = interp(vEgo, rate_limit.speed_points, rate_limit.max_angle_diff_points)
|
||||
apply_angle = clip(apply_angle, (apply_angle_last - max_angle_diff), (apply_angle_last + max_angle_diff))
|
||||
|
||||
# absolute limit (LatCtlPath_An_Actl)
|
||||
apply_path_angle = math.radians(apply_angle) / CarControllerParams.LKAS_STEER_RATIO
|
||||
apply_path_angle = clip(apply_path_angle, -0.5, 0.5235)
|
||||
apply_angle = math.degrees(apply_path_angle) * CarControllerParams.LKAS_STEER_RATIO
|
||||
|
||||
return apply_angle
|
||||
|
||||
|
||||
class CarController:
|
||||
def __init__(self, dbc_name, CP, VM):
|
||||
self.CP = CP
|
||||
@@ -30,7 +15,7 @@ class CarController:
|
||||
self.packer = CANPacker(dbc_name)
|
||||
self.frame = 0
|
||||
|
||||
self.apply_angle_last = 0
|
||||
self.apply_curvature_last = 0
|
||||
self.main_on_last = False
|
||||
self.lkas_enabled_last = False
|
||||
self.steer_alert_last = False
|
||||
@@ -46,66 +31,47 @@ class CarController:
|
||||
|
||||
### acc buttons ###
|
||||
if CC.cruiseControl.cancel:
|
||||
can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, cancel=True))
|
||||
can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, cancel=True, bus=CANBUS.main))
|
||||
can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, cancel=True))
|
||||
can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, cancel=True, bus=CANBUS.main))
|
||||
elif CC.cruiseControl.resume and (self.frame % CarControllerParams.BUTTONS_STEP) == 0:
|
||||
can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, resume=True))
|
||||
can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, resume=True, bus=CANBUS.main))
|
||||
can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, resume=True))
|
||||
can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, resume=True, bus=CANBUS.main))
|
||||
# if stock lane centering isn't off, send a button press to toggle it off
|
||||
# the stock system checks for steering pressed, and eventually disengages cruise control
|
||||
elif CS.acc_tja_status_stock_values["Tja_D_Stat"] != 0 and (self.frame % CarControllerParams.ACC_UI_STEP) == 0:
|
||||
can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, tja_toggle=True))
|
||||
|
||||
can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, tja_toggle=True))
|
||||
|
||||
### lateral control ###
|
||||
if CC.latActive:
|
||||
lca_rq = 1
|
||||
apply_angle = apply_ford_steer_angle_limits(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgo)
|
||||
else:
|
||||
lca_rq = 0
|
||||
apply_angle = 0.
|
||||
|
||||
# send steering commands at 20Hz
|
||||
if (self.frame % CarControllerParams.STEER_STEP) == 0:
|
||||
# use LatCtlPath_An_Actl to actuate steering
|
||||
path_angle = math.radians(apply_angle) / CarControllerParams.LKAS_STEER_RATIO
|
||||
|
||||
# set slower ramp type when small steering angle change
|
||||
# 0=Slow, 1=Medium, 2=Fast, 3=Immediately
|
||||
steer_change = abs(CS.out.steeringAngleDeg - actuators.steeringAngleDeg)
|
||||
if steer_change < 2.0:
|
||||
ramp_type = 0
|
||||
elif steer_change < 4.0:
|
||||
ramp_type = 1
|
||||
elif steer_change < 6.0:
|
||||
ramp_type = 2
|
||||
if CC.latActive:
|
||||
# apply limits to curvature and clip to signal range
|
||||
apply_curvature = apply_std_steer_angle_limits(actuators.curvature, self.apply_curvature_last, CS.out.vEgo, CarControllerParams)
|
||||
apply_curvature = clip(apply_curvature, -CarControllerParams.CURVATURE_MAX, CarControllerParams.CURVATURE_MAX)
|
||||
else:
|
||||
ramp_type = 3
|
||||
precision = 1 # 0=Comfortable, 1=Precise (the stock system always uses comfortable)
|
||||
|
||||
self.apply_angle_last = apply_angle
|
||||
can_sends.append(fordcan.create_lka_command(self.packer, 0, 0))
|
||||
can_sends.append(fordcan.create_tja_command(self.packer, lca_rq, ramp_type, precision,
|
||||
0, path_angle, 0, 0))
|
||||
apply_curvature = 0.
|
||||
|
||||
self.apply_curvature_last = apply_curvature
|
||||
can_sends.append(create_lka_msg(self.packer))
|
||||
can_sends.append(create_lat_ctl_msg(self.packer, CC.latActive, 0., 0., -apply_curvature, 0.))
|
||||
|
||||
### ui ###
|
||||
send_ui = (self.main_on_last != main_on) or (self.lkas_enabled_last != CC.latActive) or (self.steer_alert_last != steer_alert)
|
||||
|
||||
# send lkas ui command at 1Hz or if ui state changes
|
||||
if (self.frame % CarControllerParams.LKAS_UI_STEP) == 0 or send_ui:
|
||||
can_sends.append(fordcan.create_lkas_ui_command(self.packer, main_on, CC.latActive, steer_alert, hud_control, CS.lkas_status_stock_values))
|
||||
can_sends.append(create_lkas_ui_msg(self.packer, main_on, CC.latActive, steer_alert, hud_control, CS.lkas_status_stock_values))
|
||||
|
||||
# send acc ui command at 20Hz or if ui state changes
|
||||
if (self.frame % CarControllerParams.ACC_UI_STEP) == 0 or send_ui:
|
||||
can_sends.append(fordcan.create_acc_ui_command(self.packer, main_on, CC.latActive, hud_control, CS.acc_tja_status_stock_values))
|
||||
can_sends.append(create_acc_ui_msg(self.packer, main_on, CC.latActive, hud_control, CS.acc_tja_status_stock_values))
|
||||
|
||||
self.main_on_last = main_on
|
||||
self.lkas_enabled_last = CC.latActive
|
||||
self.steer_alert_last = steer_alert
|
||||
|
||||
new_actuators = actuators.copy()
|
||||
new_actuators.steeringAngleDeg = self.apply_angle_last
|
||||
new_actuators.curvature = self.apply_curvature_last
|
||||
|
||||
self.frame += 1
|
||||
return new_actuators, can_sends
|
||||
|
||||
@@ -118,7 +118,7 @@ class CarState(CarStateBase):
|
||||
("DrStatRl_B_Actl", "BodyInfo_3_FD1"), # BCM Door open, rear left
|
||||
("DrStatRr_B_Actl", "BodyInfo_3_FD1"), # BCM Door open, rear right
|
||||
("FirstRowBuckleDriver", "RCMStatusMessage2_FD1"), # RCM Seatbelt status, driver
|
||||
("HeadLghtHiFlash_D_Stat", "Steering_Data_FD1"), # SCCM Passthru the remaining buttons
|
||||
("HeadLghtHiFlash_D_Stat", "Steering_Data_FD1"), # SCCM Passthrough the remaining buttons
|
||||
("WiprFront_D_Stat", "Steering_Data_FD1"),
|
||||
("LghtAmb_D_Sns", "Steering_Data_FD1"),
|
||||
("AccButtnGapDecPress", "Steering_Data_FD1"),
|
||||
|
||||
@@ -4,64 +4,58 @@ from selfdrive.car.ford.values import CANBUS
|
||||
HUDControl = car.CarControl.HUDControl
|
||||
|
||||
|
||||
def create_lka_command(packer, angle_deg: float, curvature: float):
|
||||
def create_lka_msg(packer):
|
||||
"""
|
||||
Creates a CAN message for the Ford LKAS Command.
|
||||
Creates an empty CAN message for the Ford LKA Command.
|
||||
|
||||
This command can apply "Lane Keeping Aid" manoeuvres, which are subject to the PSCM lockout.
|
||||
|
||||
Frequency is 20Hz.
|
||||
"""
|
||||
|
||||
values = {
|
||||
"LkaDrvOvrrd_D_Rq": 0, # driver override level? [0|3]
|
||||
"LkaActvStats_D2_Req": 0, # action [0|7]
|
||||
"LaRefAng_No_Req": angle_deg, # angle [-102.4|102.3] degrees
|
||||
"LaRampType_B_Req": 0, # Ramp speed: 0=Smooth, 1=Quick
|
||||
"LaCurvature_No_Calc": curvature, # curvature [-0.01024|0.01023] 1/meter
|
||||
"LdwActvStats_D_Req": 0, # LDW status [0|7]
|
||||
"LdwActvIntns_D_Req": 0, # LDW intensity [0|3], shake alert strength
|
||||
}
|
||||
return packer.make_can_msg("Lane_Assist_Data1", CANBUS.main, values)
|
||||
return packer.make_can_msg("Lane_Assist_Data1", CANBUS.main, {})
|
||||
|
||||
|
||||
def create_tja_command(packer, lca_rq: int, ramp_type: int, precision: int, path_offset: float, path_angle: float, curvature_rate: float, curvature: float):
|
||||
def create_lat_ctl_msg(packer, lat_active: bool, path_offset: float, path_angle: float, curvature: float, curvature_rate: float):
|
||||
"""
|
||||
Creates a CAN message for the Ford TJA/LCA Command.
|
||||
|
||||
This command can apply "Lane Centering" manoeuvres: continuous lane centering for traffic jam
|
||||
assist and highway driving. It is not subject to the PSCM lockout.
|
||||
This command can apply "Lane Centering" manoeuvres: continuous lane centering for traffic jam assist and highway
|
||||
driving. It is not subject to the PSCM lockout.
|
||||
|
||||
Ford lane centering command uses a third order polynomial to describe the road centerline. The
|
||||
polynomial is defined by the following coefficients:
|
||||
c0: lateral offset between the vehicle and the centerline
|
||||
c1: heading angle between the vehicle and the centerline
|
||||
c2: curvature of the centerline
|
||||
Ford lane centering command uses a third order polynomial to describe the road centerline. The polynomial is defined
|
||||
by the following coefficients:
|
||||
c0: lateral offset between the vehicle and the centerline (positive is right)
|
||||
c1: heading angle between the vehicle and the centerline (positive is right)
|
||||
c2: curvature of the centerline (positive is left)
|
||||
c3: rate of change of curvature of the centerline
|
||||
As the PSCM combines this information with other sensor data, such as the vehicle's yaw rate and
|
||||
speed, the steering angle cannot be easily controlled.
|
||||
As the PSCM combines this information with other sensor data, such as the vehicle's yaw rate and speed, the steering
|
||||
angle cannot be easily controlled.
|
||||
|
||||
The PSCM should be configured to accept TJA/LCA commands before these commands will be processed.
|
||||
This can be done using tools such as Forscan.
|
||||
The PSCM should be configured to accept TJA/LCA commands before these commands will be processed. This can be done
|
||||
using tools such as Forscan.
|
||||
|
||||
Frequency is 20Hz.
|
||||
"""
|
||||
|
||||
values = {
|
||||
"LatCtlRng_L_Max": 0, # Unknown [0|126] meter
|
||||
"HandsOffCnfm_B_Rq": 0, # Unknown: 0=Inactive, 1=Active [0|1]
|
||||
"LatCtl_D_Rq": lca_rq, # Mode: 0=None, 1=ContinuousPathFollowing, 2=InterventionLeft, 3=InterventionRight, 4-7=NotUsed [0|7]
|
||||
"LatCtlRampType_D_Rq": ramp_type, # Ramp speed: 0=Slow, 1=Medium, 2=Fast, 3=Immediate [0|3]
|
||||
"LatCtlPrecision_D_Rq": precision, # Precision: 0=Comfortable, 1=Precise, 2/3=NotUsed [0|3]
|
||||
"LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter
|
||||
"LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians
|
||||
"LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2
|
||||
"LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter
|
||||
"LatCtlRng_L_Max": 0, # Unknown [0|126] meter
|
||||
"HandsOffCnfm_B_Rq": 0, # Unknown: 0=Inactive, 1=Active [0|1]
|
||||
"LatCtl_D_Rq": 1 if lat_active else 0, # Mode: 0=None, 1=ContinuousPathFollowing, 2=InterventionLeft,
|
||||
# 3=InterventionRight, 4-7=NotUsed [0|7]
|
||||
"LatCtlRampType_D_Rq": 0, # Ramp speed: 0=Slow, 1=Medium, 2=Fast, 3=Immediate [0|3]
|
||||
# Makes no difference with curvature control
|
||||
"LatCtlPrecision_D_Rq": 1, # Precision: 0=Comfortable, 1=Precise, 2/3=NotUsed [0|3]
|
||||
# The stock system always uses comfortable
|
||||
"LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter
|
||||
"LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians
|
||||
"LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2
|
||||
"LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter
|
||||
}
|
||||
return packer.make_can_msg("LateralMotionControl", CANBUS.main, values)
|
||||
|
||||
|
||||
def create_lkas_ui_command(packer, main_on: bool, enabled: bool, steer_alert: bool, hud_control, stock_values: dict):
|
||||
def create_lkas_ui_msg(packer, main_on: bool, enabled: bool, steer_alert: bool, hud_control, stock_values: dict):
|
||||
"""
|
||||
Creates a CAN message for the Ford IPC IPMA/LKAS status.
|
||||
|
||||
@@ -107,16 +101,15 @@ def create_lkas_ui_command(packer, main_on: bool, enabled: bool, steer_alert: bo
|
||||
|
||||
values = {
|
||||
**stock_values,
|
||||
"LaActvStats_D_Dsply": lines, # LKAS status (lines) [0|31]
|
||||
"LaHandsOff_D_Dsply": hands_on_wheel_dsply, # 0=HandsOn, 1=Level1 (w/o chime), 2=Level2 (w/ chime), 3=Suppressed
|
||||
"LaActvStats_D_Dsply": lines, # LKAS status (lines) [0|31]
|
||||
"LaHandsOff_D_Dsply": hands_on_wheel_dsply, # 0=HandsOn, 1=Level1 (w/o chime), 2=Level2 (w/ chime), 3=Suppressed
|
||||
}
|
||||
return packer.make_can_msg("IPMA_Data", CANBUS.main, values)
|
||||
|
||||
|
||||
def create_acc_ui_command(packer, main_on: bool, enabled: bool, hud_control, stock_values: dict):
|
||||
def create_acc_ui_msg(packer, main_on: bool, enabled: bool, hud_control, stock_values: dict):
|
||||
"""
|
||||
Creates a CAN message for the Ford IPC adaptive cruise, forward collision warning and traffic jam
|
||||
assist status.
|
||||
Creates a CAN message for the Ford IPC adaptive cruise, forward collision warning and traffic jam assist status.
|
||||
|
||||
Stock functionality is maintained by passing through unmodified signals.
|
||||
|
||||
@@ -148,7 +141,8 @@ def create_acc_ui_command(packer, main_on: bool, enabled: bool, hud_control, sto
|
||||
return packer.make_can_msg("ACCDATA_3", CANBUS.main, values)
|
||||
|
||||
|
||||
def create_button_command(packer, stock_values: dict, cancel = False, resume = False, tja_toggle = False, bus: int = CANBUS.camera):
|
||||
def create_button_msg(packer, stock_values: dict, cancel=False, resume=False, tja_toggle=False,
|
||||
bus: int = CANBUS.camera):
|
||||
"""
|
||||
Creates a CAN message for the Ford SCCM buttons/switches.
|
||||
|
||||
|
||||
@@ -2,39 +2,52 @@
|
||||
from cereal import car
|
||||
from common.conversions import Conversions as CV
|
||||
from selfdrive.car import STD_CARGO_KG, get_safety_config
|
||||
from selfdrive.car.ford.values import CAR, Ecu, TransmissionType, GearShifter
|
||||
from selfdrive.car.ford.values import CAR, Ecu
|
||||
from selfdrive.car.interfaces import CarInterfaceBase
|
||||
|
||||
CarParams = car.CarParams
|
||||
TransmissionType = car.CarParams.TransmissionType
|
||||
GearShifter = car.CarState.GearShifter
|
||||
|
||||
|
||||
class CarInterface(CarInterfaceBase):
|
||||
@staticmethod
|
||||
def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
|
||||
ret.carName = "ford"
|
||||
ret.dashcamOnly = True
|
||||
ret.safetyConfigs = [get_safety_config(CarParams.SafetyModel.ford)]
|
||||
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.ford)]
|
||||
|
||||
# Angle-based steering
|
||||
ret.steerControlType = CarParams.SteerControlType.angle
|
||||
ret.steerActuatorDelay = 0.4
|
||||
# These cars are dashcam only until the port is finished
|
||||
ret.dashcamOnly = True
|
||||
|
||||
ret.radarOffCan = True
|
||||
ret.steerControlType = car.CarParams.SteerControlType.angle
|
||||
ret.steerActuatorDelay = 0.2
|
||||
ret.steerLimitTimer = 1.0
|
||||
|
||||
if candidate == CAR.ESCAPE_MK4:
|
||||
if candidate == CAR.BRONCO_SPORT_MK1:
|
||||
ret.wheelbase = 2.67
|
||||
ret.steerRatio = 17.7
|
||||
ret.mass = 1625 + STD_CARGO_KG
|
||||
|
||||
elif candidate == CAR.ESCAPE_MK4:
|
||||
ret.wheelbase = 2.71
|
||||
ret.steerRatio = 14.3 # Copied from Focus
|
||||
ret.steerRatio = 16.7
|
||||
ret.mass = 1750 + STD_CARGO_KG
|
||||
|
||||
elif candidate == CAR.EXPLORER_MK6:
|
||||
ret.wheelbase = 3.025
|
||||
ret.steerRatio = 16.8 # learned
|
||||
ret.steerRatio = 16.8
|
||||
ret.mass = 2050 + STD_CARGO_KG
|
||||
|
||||
elif candidate == CAR.FOCUS_MK4:
|
||||
ret.wheelbase = 2.7
|
||||
ret.steerRatio = 13.8 # learned
|
||||
ret.steerRatio = 15.0
|
||||
ret.mass = 1350 + STD_CARGO_KG
|
||||
|
||||
elif candidate == CAR.MAVERICK_MK1:
|
||||
ret.wheelbase = 3.076
|
||||
ret.steerRatio = 17.0
|
||||
ret.mass = 1650 + STD_CARGO_KG
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unsupported car: {candidate}")
|
||||
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
from collections import defaultdict, namedtuple
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Dict, List, Union
|
||||
|
||||
from cereal import car
|
||||
from selfdrive.car import dbc_dict
|
||||
from selfdrive.car import AngleRateLimit, dbc_dict
|
||||
from selfdrive.car.docs_definitions import CarInfo, Harness
|
||||
from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
TransmissionType = car.CarParams.TransmissionType
|
||||
GearShifter = car.CarState.GearShifter
|
||||
|
||||
AngleRateLimit = namedtuple('AngleRateLimit', ['speed_points', 'max_angle_diff_points'])
|
||||
|
||||
|
||||
class CarControllerParams:
|
||||
@@ -25,12 +21,13 @@ class CarControllerParams:
|
||||
# Message: Steering_Data_FD1, but send twice as fast
|
||||
BUTTONS_STEP = 10 / 2
|
||||
|
||||
LKAS_STEER_RATIO = 2.75 # Approximate ratio between LatCtlPath_An_Actl and steering angle in radians
|
||||
# TODO: remove this once we understand how the EPS calculates the steering angle better
|
||||
STEER_DRIVER_ALLOWANCE = 0.8 # Driver intervention threshold in Nm
|
||||
CURVATURE_MAX = 0.02 # Max curvature for steering command, m^-1
|
||||
STEER_DRIVER_ALLOWANCE = 0.8 # Driver intervention threshold, Nm
|
||||
|
||||
RATE_LIMIT_UP = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., .8, .15])
|
||||
RATE_LIMIT_DOWN = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., 3.5, 0.4])
|
||||
# Curvature rate limits
|
||||
# TODO: unify field names used by curvature and angle control cars
|
||||
ANGLE_RATE_LIMIT_UP = AngleRateLimit(speed_bp=[5, 15, 25], angle_v=[0.005, 0.00056, 0.0002])
|
||||
ANGLE_RATE_LIMIT_DOWN = AngleRateLimit(speed_bp=[5, 15, 25], angle_v=[0.008, 0.00089, 0.00032])
|
||||
|
||||
def __init__(self, CP):
|
||||
pass
|
||||
@@ -43,9 +40,11 @@ class CANBUS:
|
||||
|
||||
|
||||
class CAR:
|
||||
BRONCO_SPORT_MK1 = "FORD BRONCO SPORT 1ST GEN"
|
||||
ESCAPE_MK4 = "FORD ESCAPE 4TH GEN"
|
||||
EXPLORER_MK6 = "FORD EXPLORER 6TH GEN"
|
||||
FOCUS_MK4 = "FORD FOCUS 4TH GEN"
|
||||
MAVERICK_MK1 = "FORD MAVERICK 1ST GEN"
|
||||
|
||||
|
||||
class RADAR:
|
||||
@@ -53,7 +52,7 @@ class RADAR:
|
||||
DELPHI_MRR = 'FORD_CADS'
|
||||
|
||||
|
||||
DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("ford_lincoln_base_pt", RADAR.DELPHI_MRR))
|
||||
DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("ford_lincoln_base_pt", None))
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -63,12 +62,14 @@ class FordCarInfo(CarInfo):
|
||||
|
||||
|
||||
CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = {
|
||||
CAR.BRONCO_SPORT_MK1: FordCarInfo("Ford Bronco Sport 2021-22"),
|
||||
CAR.ESCAPE_MK4: [
|
||||
FordCarInfo("Ford Escape 2020-21"),
|
||||
FordCarInfo("Ford Kuga 2020-21", "Driver Assistance Pack"),
|
||||
],
|
||||
CAR.EXPLORER_MK6: FordCarInfo("Ford Explorer 2020-22"),
|
||||
CAR.FOCUS_MK4: FordCarInfo("Ford Focus EU 2019", "Driver Assistance Pack"),
|
||||
CAR.MAVERICK_MK1: FordCarInfo("Ford Maverick 2022", "Co-Pilot360 Assist"),
|
||||
}
|
||||
|
||||
FW_QUERY_CONFIG = FwQueryConfig(
|
||||
@@ -88,6 +89,30 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
)
|
||||
|
||||
FW_VERSIONS = {
|
||||
CAR.BRONCO_SPORT_MK1: {
|
||||
(Ecu.eps, 0x730, None): [
|
||||
b'LX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'LX6C-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.abs, 0x760, None): [
|
||||
b'LX6C-2D053-RD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'LX6C-2D053-RE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x764, None): [
|
||||
b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x706, None): [
|
||||
b'M1PT-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.engine, 0x7E0, None): [
|
||||
b'M1PA-14C204-GF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'N1PA-14C204-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.shiftByWire, 0x732, None): [
|
||||
b'LX6P-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'PZ1P-14G395-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.ESCAPE_MK4: {
|
||||
(Ecu.eps, 0x730, None): [
|
||||
b'LX6C-14D003-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
@@ -163,4 +188,26 @@ FW_VERSIONS = {
|
||||
(Ecu.shiftByWire, 0x732, None): [
|
||||
],
|
||||
},
|
||||
CAR.MAVERICK_MK1: {
|
||||
(Ecu.eps, 0x730, None): [
|
||||
b'NZ6C-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.abs, 0x760, None): [
|
||||
b'NZ6C-2D053-AG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x764, None): [
|
||||
b'NZ6T-14D049-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x706, None): [
|
||||
b'NZ6T-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.engine, 0x7E0, None): [
|
||||
b'NZ6A-14C204-AAA\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'NZ6A-14C204-PA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'NZ6A-14C204-ZA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.shiftByWire, 0x732, None): [
|
||||
b'NZ6P-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -274,7 +274,7 @@ class CarState(CarStateBase):
|
||||
ret.cruiseState.available = bool(cp.vl[self.main_on_sig_msg]["MAIN_ON"])
|
||||
|
||||
# Gets rid of Pedal Grinding noise when brake is pressed at slow speeds for some models
|
||||
if self.CP.carFingerprint in (CAR.PILOT, CAR.PASSPORT, CAR.RIDGELINE):
|
||||
if self.CP.carFingerprint in (CAR.PILOT, CAR.RIDGELINE):
|
||||
if ret.brake > 0.1:
|
||||
ret.brakePressed = True
|
||||
|
||||
|
||||
@@ -231,11 +231,11 @@ class CarInterface(CarInterfaceBase):
|
||||
else:
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
|
||||
elif candidate in (CAR.PILOT, CAR.PASSPORT):
|
||||
ret.mass = 4204. * CV.LB_TO_KG + STD_CARGO_KG # average weight
|
||||
ret.wheelbase = 2.82
|
||||
elif candidate == CAR.PILOT:
|
||||
ret.mass = 4278. * CV.LB_TO_KG + STD_CARGO_KG # average weight
|
||||
ret.wheelbase = 2.86
|
||||
ret.centerToFront = ret.wheelbase * 0.428
|
||||
ret.steerRatio = 17.25 # as spec
|
||||
ret.steerRatio = 16.0 # as spec
|
||||
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
|
||||
tire_stiffness_factor = 0.444
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.38], [0.11]]
|
||||
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
import re
|
||||
import unittest
|
||||
|
||||
from selfdrive.car.honda.values import FW_VERSIONS
|
||||
|
||||
HONDA_FW_VERSION_RE = br"\d{5}-[A-Z0-9]{3}(-|,)[A-Z0-9]{4}(\x00){2}$"
|
||||
|
||||
|
||||
class TestHondaFingerprint(unittest.TestCase):
|
||||
def test_fw_version_format(self):
|
||||
# Asserts all FW versions follow an expected format
|
||||
for fw_by_ecu in FW_VERSIONS.values():
|
||||
for fws in fw_by_ecu.values():
|
||||
for fw in fws:
|
||||
self.assertTrue(re.match(HONDA_FW_VERSION_RE, fw) is not None, fw)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -4,9 +4,10 @@ from typing import Dict, List, Optional, Union
|
||||
|
||||
from cereal import car
|
||||
from common.conversions import Conversions as CV
|
||||
from panda.python import uds
|
||||
from selfdrive.car import dbc_dict
|
||||
from selfdrive.car.docs_definitions import CarFootnote, CarInfo, Column, Harness
|
||||
from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
|
||||
from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
VisualAlert = car.CarControl.HUDControl.VisualAlert
|
||||
@@ -91,7 +92,6 @@ class CAR:
|
||||
ACURA_RDX = "ACURA RDX 2018"
|
||||
ACURA_RDX_3G = "ACURA RDX 2020"
|
||||
PILOT = "HONDA PILOT 2017"
|
||||
PASSPORT = "HONDA PASSPORT 2021"
|
||||
RIDGELINE = "HONDA RIDGELINE 2017"
|
||||
INSIGHT = "HONDA INSIGHT 2019"
|
||||
HONDA_E = "HONDA E 2020"
|
||||
@@ -142,19 +142,46 @@ CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = {
|
||||
CAR.ODYSSEY_CHN: None, # Chinese version of Odyssey
|
||||
CAR.ACURA_RDX: HondaCarInfo("Acura RDX 2016-18", "AcuraWatch Plus", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
CAR.ACURA_RDX_3G: HondaCarInfo("Acura RDX 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS),
|
||||
CAR.PILOT: HondaCarInfo("Honda Pilot 2016-22", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
CAR.PASSPORT: HondaCarInfo("Honda Passport 2019-21", "All", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
CAR.PILOT: [
|
||||
HondaCarInfo("Honda Pilot 2016-22", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
HondaCarInfo("Honda Passport 2019-21", "All", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
],
|
||||
CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-22", min_steer_speed=12. * CV.MPH_TO_MS),
|
||||
CAR.INSIGHT: HondaCarInfo("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS),
|
||||
CAR.HONDA_E: HondaCarInfo("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS),
|
||||
}
|
||||
|
||||
HONDA_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
|
||||
p16(0xF112)
|
||||
HONDA_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
|
||||
p16(0xF112)
|
||||
|
||||
FW_QUERY_CONFIG = FwQueryConfig(
|
||||
requests=[
|
||||
# Currently used to fingerprint
|
||||
Request(
|
||||
[StdQueries.UDS_VERSION_REQUEST],
|
||||
[StdQueries.UDS_VERSION_RESPONSE],
|
||||
bus=1,
|
||||
),
|
||||
|
||||
# Data collection requests:
|
||||
# Log extra identifiers for current ECUs
|
||||
Request(
|
||||
[HONDA_VERSION_REQUEST],
|
||||
[HONDA_VERSION_RESPONSE],
|
||||
bus=1,
|
||||
),
|
||||
# Query Nidec PT bus from camera for data collection
|
||||
Request(
|
||||
[StdQueries.UDS_VERSION_REQUEST],
|
||||
[StdQueries.UDS_VERSION_RESPONSE],
|
||||
bus=0,
|
||||
),
|
||||
],
|
||||
extra_ecus=[
|
||||
# The only other ECU on PT bus accessible by camera on radarless Civic
|
||||
(Ecu.unknown, 0x18DAB3F1, None),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1067,6 +1094,8 @@ FW_VERSIONS = {
|
||||
b'28101-5EZ-A060\x00\x00',
|
||||
b'28101-5EZ-A100\x00\x00',
|
||||
b'28101-5EZ-A210\x00\x00',
|
||||
b'28101-5EZ-A600\x00\x00',
|
||||
b'28101-5EZ-A430\x00\x00',
|
||||
],
|
||||
(Ecu.programmedFuelInjection, 0x18da10f1, None): [
|
||||
b'37805-RLV-4060\x00\x00',
|
||||
@@ -1078,6 +1107,9 @@ FW_VERSIONS = {
|
||||
b'37805-RLV-C520\x00\x00',
|
||||
b'37805-RLV-C530\x00\x00',
|
||||
b'37805-RLV-C910\x00\x00',
|
||||
b'37805-RLV-B220\x00\x00',
|
||||
b'37805-RLV-B210\x00\x00',
|
||||
b'37805-RLV-L160\x00\x00',
|
||||
],
|
||||
(Ecu.gateway, 0x18daeff1, None): [
|
||||
b'38897-TG7-A030\x00\x00',
|
||||
@@ -1110,6 +1142,7 @@ FW_VERSIONS = {
|
||||
b'36161-TGS-A130\x00\x00',
|
||||
b'36161-TGT-A030\x00\x00',
|
||||
b'36161-TGT-A130\x00\x00',
|
||||
b'36161-TGS-A030\x00\x00',
|
||||
],
|
||||
(Ecu.srs, 0x18da53f1, None): [
|
||||
b'77959-TG7-A020\x00\x00',
|
||||
@@ -1147,6 +1180,9 @@ FW_VERSIONS = {
|
||||
b'78109-TGS-AP20\x00\x00',
|
||||
b'78109-TGT-AJ20\x00\x00',
|
||||
b'78109-TGT-AK30\x00\x00',
|
||||
b'78109-TGS-AT20\x00\x00',
|
||||
b'78109-TGS-AX20\x00\x00',
|
||||
b'78109-TGS-AJ20\x00\x00',
|
||||
],
|
||||
(Ecu.vsa, 0x18da28f1, None): [
|
||||
b'57114-TG7-A130\x00\x00',
|
||||
@@ -1163,42 +1199,6 @@ FW_VERSIONS = {
|
||||
b'57114-TGT-A530\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.PASSPORT: {
|
||||
(Ecu.programmedFuelInjection, 0x18da10f1, None): [
|
||||
b'37805-RLV-B220\x00\x00',
|
||||
b'37805-RLV-B210\x00\x00',
|
||||
b'37805-RLV-L160\x00\x00',
|
||||
],
|
||||
(Ecu.eps, 0x18da30f1, None): [
|
||||
b'39990-TGS-A230\x00\x00',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x18dab0f1, None): [
|
||||
b'36161-TGS-A030\x00\x00',
|
||||
b'36161-TGS-A130\x00\x00',
|
||||
],
|
||||
(Ecu.gateway, 0x18daeff1, None): [
|
||||
b'38897-TG7-A040\x00\x00',
|
||||
b'38897-TG7-A030\x00\x00',
|
||||
],
|
||||
(Ecu.srs, 0x18da53f1, None): [
|
||||
b'77959-TGS-A010\x00\x00',
|
||||
],
|
||||
(Ecu.shiftByWire, 0x18da0bf1, None): [
|
||||
b'54008-TG7-A530\x00\x00',
|
||||
],
|
||||
(Ecu.transmission, 0x18da1ef1, None): [
|
||||
b'28101-5EZ-A600\x00\x00',
|
||||
b'28101-5EZ-A430\x00\x00',
|
||||
],
|
||||
(Ecu.combinationMeter, 0x18da60f1, None): [
|
||||
b'78109-TGS-AT20\x00\x00',
|
||||
b'78109-TGS-AX20\x00\x00',
|
||||
b'78109-TGS-AJ20\x00\x00',
|
||||
],
|
||||
(Ecu.vsa, 0x18da28f1, None): [
|
||||
b'57114-TGS-A530\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.ACURA_RDX: {
|
||||
(Ecu.vsa, 0x18da28f1, None): [
|
||||
b'57114-TX5-A220\x00\x00',
|
||||
@@ -1460,6 +1460,12 @@ FW_VERSIONS = {
|
||||
b'78108-T21-A230\x00\x00',
|
||||
b'78108-T22-A020\x00\x00',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x18dab0f1, None): [
|
||||
b'36161-T20-A070\x00\x00',
|
||||
b'36161-T20-A080\x00\x00',
|
||||
b'36161-T20-A060\x00\x00',
|
||||
b'36161-T47-A070\x00\x00',
|
||||
],
|
||||
(Ecu.vsa, 0x18DA28F1, None): [
|
||||
b'57114-T20-AB40\x00\x00',
|
||||
b'57114-T43-JB30\x00\x00',
|
||||
@@ -1499,7 +1505,6 @@ DBC = {
|
||||
CAR.ODYSSEY: dbc_dict('honda_odyssey_exl_2018_generated', 'acura_ilx_2016_nidec'),
|
||||
CAR.ODYSSEY_CHN: dbc_dict('honda_odyssey_extreme_edition_2018_china_can_generated', 'acura_ilx_2016_nidec'),
|
||||
CAR.PILOT: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'),
|
||||
CAR.PASSPORT: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'),
|
||||
CAR.RIDGELINE: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'),
|
||||
CAR.INSIGHT: dbc_dict('honda_insight_ex_2019_can_generated', None),
|
||||
CAR.HONDA_E: dbc_dict('acura_rdx_2020_can_generated', None),
|
||||
@@ -1514,7 +1519,7 @@ STEER_THRESHOLD = {
|
||||
|
||||
HONDA_NIDEC_ALT_PCM_ACCEL = {CAR.ODYSSEY}
|
||||
HONDA_NIDEC_ALT_SCM_MESSAGES = {CAR.ACURA_ILX, CAR.ACURA_RDX, CAR.CRV, CAR.CRV_EU, CAR.FIT, CAR.FREED, CAR.HRV, CAR.ODYSSEY_CHN,
|
||||
CAR.PILOT, CAR.PASSPORT, CAR.RIDGELINE}
|
||||
CAR.PILOT, CAR.RIDGELINE}
|
||||
HONDA_BOSCH = {CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_5G,
|
||||
CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.CIVIC_2022}
|
||||
HONDA_BOSCH_ALT_BRAKE_SIGNAL = {CAR.ACCORD, CAR.CRV_5G, CAR.ACURA_RDX_3G}
|
||||
|
||||
@@ -2,24 +2,19 @@
|
||||
import unittest
|
||||
|
||||
from cereal import car
|
||||
from selfdrive.car.car_helpers import get_interface_attr
|
||||
from selfdrive.car.fw_versions import FW_QUERY_CONFIGS
|
||||
from selfdrive.car.hyundai.values import CANFD_CAR
|
||||
from selfdrive.car.hyundai.values import CANFD_CAR, FW_QUERY_CONFIG, FW_VERSIONS
|
||||
|
||||
Ecu = car.CarParams.Ecu
|
||||
|
||||
ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
|
||||
VERSIONS = get_interface_attr("FW_VERSIONS", ignore_none=True)
|
||||
|
||||
|
||||
class TestHyundaiFingerprint(unittest.TestCase):
|
||||
def test_auxiliary_request_ecu_whitelist(self):
|
||||
# Asserts only auxiliary Ecus can exist in database for CAN-FD cars
|
||||
config = FW_QUERY_CONFIGS['hyundai']
|
||||
whitelisted_ecus = {ecu for r in config.requests for ecu in r.whitelist_ecus if r.bus > 3}
|
||||
whitelisted_ecus = {ecu for r in FW_QUERY_CONFIG.requests for ecu in r.whitelist_ecus if r.bus > 3}
|
||||
|
||||
for car_model in CANFD_CAR:
|
||||
ecus = {fw[0] for fw in VERSIONS['hyundai'][car_model].keys()}
|
||||
ecus = {fw[0] for fw in FW_VERSIONS[car_model].keys()}
|
||||
ecus_not_in_whitelist = ecus - whitelisted_ecus
|
||||
ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_not_in_whitelist])
|
||||
self.assertEqual(len(ecus_not_in_whitelist), 0, f'{car_model}: Car model has ECUs not in auxiliary request whitelists: {ecu_strings}')
|
||||
|
||||
@@ -145,7 +145,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
|
||||
HyundaiCarInfo("Hyundai Elantra GT 2017-19", harness=Harness.hyundai_e),
|
||||
HyundaiCarInfo("Hyundai i30 2017-19", harness=Harness.hyundai_e),
|
||||
],
|
||||
CAR.ELANTRA_2021: HyundaiCarInfo("Hyundai Elantra 2021-22", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k),
|
||||
CAR.ELANTRA_2021: HyundaiCarInfo("Hyundai Elantra 2021-23", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k),
|
||||
CAR.ELANTRA_HEV_2021: HyundaiCarInfo("Hyundai Elantra Hybrid 2021-23", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k),
|
||||
CAR.HYUNDAI_GENESIS: HyundaiCarInfo("Hyundai Genesis 2015-16", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_j), # TODO: check 2015 packages
|
||||
CAR.IONIQ: HyundaiCarInfo("Hyundai Ioniq Hybrid 2017-19", harness=Harness.hyundai_c),
|
||||
@@ -225,7 +225,10 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
|
||||
],
|
||||
|
||||
# Genesis
|
||||
CAR.GENESIS_GV60_EV_1ST_GEN: HyundaiCarInfo("Genesis GV60 2023", "All", harness=Harness.hyundai_k),
|
||||
CAR.GENESIS_GV60_EV_1ST_GEN: [
|
||||
HyundaiCarInfo("Genesis GV60 (Advanced Trim) 2023", "All", harness=Harness.hyundai_a),
|
||||
HyundaiCarInfo("Genesis GV60 (Performance Trim) 2023", "All", harness=Harness.hyundai_k),
|
||||
],
|
||||
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),
|
||||
@@ -448,11 +451,13 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00AE MDPS C 1.00 1.04 56310/G7501 4AEEC104',
|
||||
b'\xf1\x00AE MDPS C 1.00 1.03 56310/G7300 4AEEC103',
|
||||
b'\xf1\x00AE MDPS C 1.00 1.03 56310G7300\x00 4AEEC103',
|
||||
b'\xf1\x00AE MDPS C 1.00 1.04 56310/G7301 4AEEC104',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G7200 160418',
|
||||
b'\xf1\x00AEE MFC AT USA LHD 1.00 1.00 95740-G2400 180222',
|
||||
b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2300 170703',
|
||||
b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2400 180222',
|
||||
],
|
||||
},
|
||||
CAR.IONIQ_HEV_2022: {
|
||||
@@ -764,19 +769,23 @@ FW_VERSIONS = {
|
||||
},
|
||||
CAR.SANTA_FE_HEV_2022: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x8799110CL500\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ',
|
||||
b'\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ',
|
||||
],
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLAC0 4TSHC102',
|
||||
b'\xf1\x00TM MDPS R 1.00 1.05 57700-CL000 4TSHP105',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00TMH MFC AT EUR LHD 1.00 1.06 99211-S1500 220727',
|
||||
b'\xf1\x00TMH MFC AT USA LHD 1.00 1.03 99211-S1500 210224',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2H16UA3I\x94\xac\x8f',
|
||||
b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TTM2H16SA2\x80\xd7l\xb2',
|
||||
],
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'\xf1\x87391312MTC1',
|
||||
b'\xf1\x87391312MTE0',
|
||||
],
|
||||
},
|
||||
CAR.SANTA_FE_PHEV_2022: {
|
||||
@@ -1320,25 +1329,26 @@ FW_VERSIONS = {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ',
|
||||
b'\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ',
|
||||
b'\xf1\x00CN7_ SCC FNCUP 1.00 1.01 99110-AA000 ',
|
||||
b'\xf1\x8799110AA000\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ',
|
||||
b'\xf1\x8799110AA000\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ',
|
||||
],
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\x87\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x00CN7 MDPS C 1.00 1.06 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 4CNDC106',
|
||||
b'\xf1\x8756310/AA070\xf1\x00CN7 MDPS C 1.00 1.06 56310/AA070 4CNDC106',
|
||||
b'\xf1\x8756310AA050\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106',
|
||||
b'\xf1\x8756310AA050\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106\xf1\xa01.06',
|
||||
b'\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.00 99210-AB000 200819',
|
||||
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AA000 200819',
|
||||
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.01 99210-AB000 210205',
|
||||
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.06 99210-AA000 220111',
|
||||
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AB000 220426',
|
||||
],
|
||||
(Ecu.abs, 0x7d1, None): [
|
||||
b'\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800',
|
||||
b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 104 \x08\x03 58910-AA800',
|
||||
b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800',
|
||||
b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 105 \x10\x03 58910-AA800',
|
||||
b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800\xf1\xa01.01',
|
||||
],
|
||||
@@ -1356,6 +1366,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x81HM6M2_0a0_FF0',
|
||||
b'\xf1\x82CNCVD0AMFCXCSFFB',
|
||||
b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M2_0a0_G80',
|
||||
b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M2_0a0_HC0',
|
||||
],
|
||||
},
|
||||
CAR.ELANTRA_HEV_2021: {
|
||||
@@ -1542,6 +1553,7 @@ FW_VERSIONS = {
|
||||
CAR.GENESIS_GV60_EV_1ST_GEN: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.02 99211-CU100 211215',
|
||||
b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.02 99211-CU000 211215',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00JW1_ RDR ----- 1.00 1.00 99110-CU000 ',
|
||||
|
||||
@@ -18,6 +18,7 @@ from selfdrive.car.body.values import CAR as COMMA
|
||||
non_tested_cars = [
|
||||
FORD.ESCAPE_MK4,
|
||||
FORD.FOCUS_MK4,
|
||||
FORD.MAVERICK_MK1,
|
||||
GM.CADILLAC_ATS,
|
||||
GM.HOLDEN_ASTRA,
|
||||
GM.MALIBU,
|
||||
@@ -42,6 +43,7 @@ routes = [
|
||||
CarTestRoute("221c253375af4ee9|2022-06-15--18-38-24", CHRYSLER.RAM_1500),
|
||||
CarTestRoute("8fb5eabf914632ae|2022-08-04--17-28-53", CHRYSLER.RAM_HD, segment=6),
|
||||
|
||||
CarTestRoute("54827bf84c38b14f|2023-01-25--14-14-11", FORD.BRONCO_SPORT_MK1),
|
||||
CarTestRoute("62241b0c7fea4589|2022-09-01--15-32-49", FORD.EXPLORER_MK6),
|
||||
#TestRoute("f1b4c567731f4a1b|2018-04-30--10-15-35", FORD.FUSION),
|
||||
|
||||
@@ -72,7 +74,7 @@ routes = [
|
||||
CarTestRoute("d83f36766f8012a5|2020-02-05--18-42-21", HONDA.CIVIC_BOSCH_DIESEL),
|
||||
CarTestRoute("f0890d16a07a236b|2021-05-25--17-27-22", HONDA.INSIGHT),
|
||||
CarTestRoute("07d37d27996096b6|2020-03-04--21-57-27", HONDA.PILOT),
|
||||
CarTestRoute("684e8f96bd491a0e|2021-11-03--11-08-42", HONDA.PASSPORT),
|
||||
CarTestRoute("684e8f96bd491a0e|2021-11-03--11-08-42", HONDA.PILOT), # Passport
|
||||
CarTestRoute("0a78dfbacc8504ef|2020-03-04--13-29-55", HONDA.CIVIC_BOSCH),
|
||||
CarTestRoute("f34a60d68d83b1e5|2020-10-06--14-35-55", HONDA.ACURA_RDX),
|
||||
CarTestRoute("54fd8451b3974762|2021-04-01--14-50-10", HONDA.RIDGELINE),
|
||||
|
||||
@@ -245,7 +245,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
# TODO: remove this exception once this mismatch is resolved
|
||||
brake_pressed = CS.brakePressed
|
||||
if CS.brakePressed and not self.safety.get_brake_pressed_prev():
|
||||
if self.CP.carFingerprint in (HONDA.PILOT, HONDA.PASSPORT, HONDA.RIDGELINE) and CS.brake > 0.05:
|
||||
if self.CP.carFingerprint in (HONDA.PILOT, HONDA.RIDGELINE) and CS.brake > 0.05:
|
||||
brake_pressed = False
|
||||
checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev()
|
||||
checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev()
|
||||
|
||||
@@ -12,9 +12,11 @@ TESLA AP1 MODEL S: [.nan, 2.5, .nan]
|
||||
TESLA AP2 MODEL S: [.nan, 2.5, .nan]
|
||||
|
||||
# Guess
|
||||
FORD BRONCO SPORT 1ST GEN: [.nan, 1.5, .nan]
|
||||
FORD ESCAPE 4TH GEN: [.nan, 1.5, .nan]
|
||||
FORD EXPLORER 6TH GEN: [.nan, 1.5, .nan]
|
||||
FORD FOCUS 4TH GEN: [.nan, 1.5, .nan]
|
||||
FORD MAVERICK 1ST GEN: [.nan, 1.5, .nan]
|
||||
###
|
||||
|
||||
# No steering wheel
|
||||
|
||||
@@ -22,7 +22,6 @@ HONDA FIT 2018: [1.5719981427109775, 0.5712761407108976, 0.110773383324281]
|
||||
HONDA HRV 2019: [2.0661212805710205, 0.7521343418694775, 0.17760375789242094]
|
||||
HONDA INSIGHT 2019: [1.5201671214069354, 0.5660229120683284, 0.25808042580281876]
|
||||
HONDA ODYSSEY 2018: [1.8774809275211801, 0.8394431662987996, 0.2096978613792822]
|
||||
HONDA PASSPORT 2021: [1.5305538930036766, 0.7956063674638759, 0.19599407381531284]
|
||||
HONDA PILOT 2017: [1.7262026201812795, 0.9470005614967523, 0.21351430733218763]
|
||||
HONDA RIDGELINE 2017: [1.4146525028237624, 0.7356572861629564, 0.23307177552211328]
|
||||
HYUNDAI ELANTRA 2021: [3.169, 2.1259108157250735, 0.0819]
|
||||
|
||||
@@ -568,6 +568,7 @@ FW_VERSIONS = {
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'\xf1\x8703N906026E \xf1\x892114',
|
||||
b'\xf1\x8704E906023AH\xf1\x893379',
|
||||
b'\xf1\x8704L906026DP\xf1\x891538',
|
||||
b'\xf1\x8704L906026ET\xf1\x891990',
|
||||
b'\xf1\x8704L906026FP\xf1\x892012',
|
||||
b'\xf1\x8704L906026GA\xf1\x892013',
|
||||
@@ -586,6 +587,7 @@ FW_VERSIONS = {
|
||||
],
|
||||
(Ecu.srs, 0x715, None): [
|
||||
b'\xf1\x873Q0959655AE\xf1\x890195\xf1\x82\r56140056130012416612124111',
|
||||
b'\xf1\x873Q0959655AF\xf1\x890195\xf1\x82\r56140056130012026612120211',
|
||||
b'\xf1\x873Q0959655AN\xf1\x890306\xf1\x82\r58160058140013036914110311',
|
||||
b'\xf1\x873Q0959655BA\xf1\x890195\xf1\x82\r56140056130012516612125111',
|
||||
b'\xf1\x873Q0959655BB\xf1\x890195\xf1\x82\r56140056130012026612120211',
|
||||
@@ -596,6 +598,7 @@ FW_VERSIONS = {
|
||||
(Ecu.eps, 0x712, None): [
|
||||
b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566B00611A1',
|
||||
b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566B00711A1',
|
||||
b'\xf1\x875Q0909143K \xf1\x892033\xf1\x820514B0060703',
|
||||
b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0060803',
|
||||
b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0080803',
|
||||
b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521B00606A1',
|
||||
@@ -604,6 +607,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567B0020600',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x757, None): [
|
||||
b'\xf1\x873Q0907572A \xf1\x890126',
|
||||
b'\xf1\x873Q0907572A \xf1\x890130',
|
||||
b'\xf1\x873Q0907572B \xf1\x890192',
|
||||
b'\xf1\x873Q0907572C \xf1\x890195',
|
||||
@@ -706,10 +710,12 @@ FW_VERSIONS = {
|
||||
b'\xf1\x875N0906259 \xf1\x890002',
|
||||
b'\xf1\x875NA907115E \xf1\x890005',
|
||||
b'\xf1\x8783A907115B \xf1\x890005',
|
||||
b'\xf1\x8783A907115F \xf1\x890002',
|
||||
b'\xf1\x8783A907115G \xf1\x890001',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x8709G927158DT\xf1\x893698',
|
||||
b'\xf1\x8709G927158FM\xf1\x893757',
|
||||
b'\xf1\x8709G927158GC\xf1\x893821',
|
||||
b'\xf1\x8709G927158GD\xf1\x893820',
|
||||
b'\xf1\x870D9300043 \xf1\x895202',
|
||||
@@ -1079,6 +1085,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x870CW300050 \xf1\x891709',
|
||||
],
|
||||
(Ecu.srs, 0x715, None): [
|
||||
b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\x1211110411110411--04040404131111112H14',
|
||||
b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\022111104111104112104040404111111112H14',
|
||||
],
|
||||
(Ecu.eps, 0x712, None): [
|
||||
|
||||
@@ -99,7 +99,8 @@ class Controls:
|
||||
get_one_can(self.can_sock)
|
||||
|
||||
num_pandas = len(messaging.recv_one_retry(self.sm.sock['pandaStates']).pandaStates)
|
||||
self.CI, self.CP = get_car(self.can_sock, self.pm.sock['sendcan'], num_pandas)
|
||||
experimental_long_allowed = self.params.get_bool("ExperimentalLongitudinalEnabled") and not is_release_branch()
|
||||
self.CI, self.CP = get_car(self.can_sock, self.pm.sock['sendcan'], experimental_long_allowed, num_pandas)
|
||||
else:
|
||||
self.CI, self.CP = CI, CI.CP
|
||||
|
||||
@@ -132,9 +133,6 @@ class Controls:
|
||||
safety_config.safetyModel = car.CarParams.SafetyModel.noOutput
|
||||
self.CP.safetyConfigs = [safety_config]
|
||||
|
||||
if is_release_branch():
|
||||
self.CP.experimentalLongitudinalAvailable = False
|
||||
|
||||
# Write CarParams for radard
|
||||
cp_bytes = self.CP.to_bytes()
|
||||
self.params.put("CarParams", cp_bytes)
|
||||
@@ -142,7 +140,7 @@ class Controls:
|
||||
put_nonblocking("CarParamsPersistent", cp_bytes)
|
||||
|
||||
# cleanup old params
|
||||
if not self.CP.experimentalLongitudinalAvailable:
|
||||
if not self.CP.experimentalLongitudinalAvailable or is_release_branch():
|
||||
self.params.remove("ExperimentalLongitudinalEnabled")
|
||||
if not self.CP.openpilotLongitudinalControl:
|
||||
self.params.remove("ExperimentalMode")
|
||||
@@ -608,6 +606,7 @@ class Controls:
|
||||
actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp,
|
||||
self.last_actuators, self.steer_limited, self.desired_curvature,
|
||||
self.desired_curvature_rate, self.sm['liveLocationKalman'])
|
||||
actuators.curvature = self.desired_curvature
|
||||
else:
|
||||
lac_log = log.ControlsState.LateralDebugState.new_message()
|
||||
if self.sm.rcv_frame['testJoystick'] > 0:
|
||||
@@ -757,7 +756,6 @@ class Controls:
|
||||
controlsState.alertType = current_alert.alert_type
|
||||
controlsState.alertSound = current_alert.audible_alert
|
||||
|
||||
controlsState.canMonoTimes = list(CS.canMonoTimes)
|
||||
controlsState.longitudinalPlanMonoTime = self.sm.logMonoTime['longitudinalPlan']
|
||||
controlsState.lateralPlanMonoTime = self.sm.logMonoTime['lateralPlan']
|
||||
controlsState.enabled = self.enabled
|
||||
|
||||
@@ -35,7 +35,7 @@ class KalmanParams():
|
||||
self.K = [[interp(dt, dts, K0)], [interp(dt, dts, K1)]]
|
||||
|
||||
|
||||
def laplacian_cdf(x, mu, b):
|
||||
def laplacian_pdf(x, mu, b):
|
||||
b = max(b, 1e-4)
|
||||
return math.exp(-abs(x-mu)/b)
|
||||
|
||||
@@ -45,9 +45,9 @@ def match_vision_to_cluster(v_ego, lead, clusters):
|
||||
offset_vision_dist = lead.x[0] - RADAR_TO_CAMERA
|
||||
|
||||
def prob(c):
|
||||
prob_d = laplacian_cdf(c.dRel, offset_vision_dist, lead.xStd[0])
|
||||
prob_y = laplacian_cdf(c.yRel, -lead.y[0], lead.yStd[0])
|
||||
prob_v = laplacian_cdf(c.vRel + v_ego, lead.v[0], lead.vStd[0])
|
||||
prob_d = laplacian_pdf(c.dRel, offset_vision_dist, lead.xStd[0])
|
||||
prob_y = laplacian_pdf(c.yRel, -lead.y[0], lead.yStd[0])
|
||||
prob_v = laplacian_pdf(c.vRel + v_ego, lead.v[0], lead.vStd[0])
|
||||
|
||||
# This is isn't exactly right, but good heuristic
|
||||
return prob_d * prob_y * prob_v
|
||||
@@ -165,7 +165,6 @@ class RadarD():
|
||||
dat.valid = sm.all_checks() and len(rr.errors) == 0
|
||||
radarState = dat.radarState
|
||||
radarState.mdMonoTime = sm.logMonoTime['modelV2']
|
||||
radarState.canMonoTimes = list(rr.canMonoTimes)
|
||||
radarState.radarErrors = list(rr.errors)
|
||||
radarState.carStateMonoTime = sm.logMonoTime['carState']
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ if __name__ == "__main__":
|
||||
for candidate, fws in FWS.items():
|
||||
fw_dict = {}
|
||||
for (tp, addr, subaddr), fw_list in fws.items():
|
||||
fw_dict[(addr, subaddr)] = random.choice(fw_list)
|
||||
fw_dict[(addr, subaddr)] = [random.choice(fw_list)]
|
||||
|
||||
matches = match_fw_to_car_fuzzy(fw_dict, log=False, exclude=candidate)
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@ if __name__ == "__main__":
|
||||
uds_client.security_access(ACCESS_TYPE_LEVEL_1.SEND_KEY, struct.pack("!I", key)) # type: ignore
|
||||
except (NegativeResponseError, MessageTimeoutError):
|
||||
print("Security access failed!")
|
||||
print("Open the hood and retry (disables the \"diagnostic firewall\" on newer vehicles)")
|
||||
quit()
|
||||
|
||||
try:
|
||||
|
||||
@@ -7,6 +7,11 @@ if GetOption('kaitai'):
|
||||
cmd = f"kaitai-struct-compiler --target cpp_stl --outdir {generated} $SOURCES"
|
||||
env.Command(['generated/ubx.cpp', 'generated/ubx.h'], 'ubx.ksy', cmd)
|
||||
env.Command(['generated/gps.cpp', 'generated/gps.h'], 'gps.ksy', cmd)
|
||||
glonass = env.Command(['generated/glonass.cpp', 'generated/glonass.h'], 'glonass.ksy', cmd)
|
||||
|
||||
# kaitai issue: https://github.com/kaitai-io/kaitai_struct/issues/910
|
||||
patch = env.Command(None, 'glonass_fix.patch', 'git apply $SOURCES')
|
||||
env.Depends(patch, glonass)
|
||||
|
||||
env.Program("ubloxd", ["ubloxd.cc", "ublox_msg.cc", "generated/ubx.cpp", "generated/gps.cpp"], LIBS=loc_libs)
|
||||
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
|
||||
|
||||
#include "glonass.h"
|
||||
|
||||
glonass_t::glonass_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) {
|
||||
m__parent = p__parent;
|
||||
m__root = this;
|
||||
|
||||
try {
|
||||
_read();
|
||||
} catch(...) {
|
||||
_clean_up();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void glonass_t::_read() {
|
||||
m_idle_chip = m__io->read_bits_int_be(1);
|
||||
m_string_number = m__io->read_bits_int_be(4);
|
||||
//m__io->align_to_byte();
|
||||
switch (string_number()) {
|
||||
case 4: {
|
||||
m_data = new string_4_t(m__io, this, m__root);
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
m_data = new string_1_t(m__io, this, m__root);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
m_data = new string_3_t(m__io, this, m__root);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
m_data = new string_2_t(m__io, this, m__root);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
m_data = new string_non_immediate_t(m__io, this, m__root);
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_hamming_code = m__io->read_bits_int_be(8);
|
||||
m_pad_1 = m__io->read_bits_int_be(11);
|
||||
m_superframe_number = m__io->read_bits_int_be(16);
|
||||
m_pad_2 = m__io->read_bits_int_be(8);
|
||||
m_frame_number = m__io->read_bits_int_be(8);
|
||||
}
|
||||
|
||||
glonass_t::~glonass_t() {
|
||||
_clean_up();
|
||||
}
|
||||
|
||||
void glonass_t::_clean_up() {
|
||||
if (m_data) {
|
||||
delete m_data; m_data = 0;
|
||||
}
|
||||
}
|
||||
|
||||
glonass_t::string_4_t::string_4_t(kaitai::kstream* p__io, glonass_t* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) {
|
||||
m__parent = p__parent;
|
||||
m__root = p__root;
|
||||
|
||||
try {
|
||||
_read();
|
||||
} catch(...) {
|
||||
_clean_up();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void glonass_t::string_4_t::_read() {
|
||||
m_tau_n = m__io->read_bits_int_be(22);
|
||||
m_delta_tau_n = m__io->read_bits_int_be(5);
|
||||
m_e_n = m__io->read_bits_int_be(5);
|
||||
m_not_used_1 = m__io->read_bits_int_be(14);
|
||||
m_p4 = m__io->read_bits_int_be(1);
|
||||
m_f_t = m__io->read_bits_int_be(4);
|
||||
m_not_used_2 = m__io->read_bits_int_be(3);
|
||||
m_n_t = m__io->read_bits_int_be(11);
|
||||
m_n = m__io->read_bits_int_be(5);
|
||||
m_m = m__io->read_bits_int_be(2);
|
||||
}
|
||||
|
||||
glonass_t::string_4_t::~string_4_t() {
|
||||
_clean_up();
|
||||
}
|
||||
|
||||
void glonass_t::string_4_t::_clean_up() {
|
||||
}
|
||||
|
||||
glonass_t::string_non_immediate_t::string_non_immediate_t(kaitai::kstream* p__io, glonass_t* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) {
|
||||
m__parent = p__parent;
|
||||
m__root = p__root;
|
||||
|
||||
try {
|
||||
_read();
|
||||
} catch(...) {
|
||||
_clean_up();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void glonass_t::string_non_immediate_t::_read() {
|
||||
m_data_1 = m__io->read_bits_int_be(64);
|
||||
m_data_2 = m__io->read_bits_int_be(8);
|
||||
}
|
||||
|
||||
glonass_t::string_non_immediate_t::~string_non_immediate_t() {
|
||||
_clean_up();
|
||||
}
|
||||
|
||||
void glonass_t::string_non_immediate_t::_clean_up() {
|
||||
}
|
||||
|
||||
glonass_t::string_1_t::string_1_t(kaitai::kstream* p__io, glonass_t* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) {
|
||||
m__parent = p__parent;
|
||||
m__root = p__root;
|
||||
|
||||
try {
|
||||
_read();
|
||||
} catch(...) {
|
||||
_clean_up();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void glonass_t::string_1_t::_read() {
|
||||
m_not_used = m__io->read_bits_int_be(2);
|
||||
m_p1 = m__io->read_bits_int_be(2);
|
||||
m_t_k = m__io->read_bits_int_be(12);
|
||||
m_x_vel = m__io->read_bits_int_be(24);
|
||||
m_x_speedup = m__io->read_bits_int_be(5);
|
||||
m_x = m__io->read_bits_int_be(27);
|
||||
}
|
||||
|
||||
glonass_t::string_1_t::~string_1_t() {
|
||||
_clean_up();
|
||||
}
|
||||
|
||||
void glonass_t::string_1_t::_clean_up() {
|
||||
}
|
||||
|
||||
glonass_t::string_2_t::string_2_t(kaitai::kstream* p__io, glonass_t* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) {
|
||||
m__parent = p__parent;
|
||||
m__root = p__root;
|
||||
|
||||
try {
|
||||
_read();
|
||||
} catch(...) {
|
||||
_clean_up();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void glonass_t::string_2_t::_read() {
|
||||
m_b_n = m__io->read_bits_int_be(3);
|
||||
m_p2 = m__io->read_bits_int_be(1);
|
||||
m_t_b = m__io->read_bits_int_be(7);
|
||||
m_not_used = m__io->read_bits_int_be(5);
|
||||
m_y_vel = m__io->read_bits_int_be(24);
|
||||
m_y_speedup = m__io->read_bits_int_be(5);
|
||||
m_y = m__io->read_bits_int_be(27);
|
||||
}
|
||||
|
||||
glonass_t::string_2_t::~string_2_t() {
|
||||
_clean_up();
|
||||
}
|
||||
|
||||
void glonass_t::string_2_t::_clean_up() {
|
||||
}
|
||||
|
||||
glonass_t::string_3_t::string_3_t(kaitai::kstream* p__io, glonass_t* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) {
|
||||
m__parent = p__parent;
|
||||
m__root = p__root;
|
||||
|
||||
try {
|
||||
_read();
|
||||
} catch(...) {
|
||||
_clean_up();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void glonass_t::string_3_t::_read() {
|
||||
m_p3 = m__io->read_bits_int_be(1);
|
||||
m_gamma_n = m__io->read_bits_int_be(11);
|
||||
m_not_used = m__io->read_bits_int_be(1);
|
||||
m_p = m__io->read_bits_int_be(2);
|
||||
m_l_n = m__io->read_bits_int_be(1);
|
||||
m_z_vel = m__io->read_bits_int_be(24);
|
||||
m_z_speedup = m__io->read_bits_int_be(5);
|
||||
m_z = m__io->read_bits_int_be(27);
|
||||
}
|
||||
|
||||
glonass_t::string_3_t::~string_3_t() {
|
||||
_clean_up();
|
||||
}
|
||||
|
||||
void glonass_t::string_3_t::_clean_up() {
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
#ifndef GLONASS_H_
|
||||
#define GLONASS_H_
|
||||
|
||||
// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
|
||||
|
||||
#include "kaitai/kaitaistruct.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#if KAITAI_STRUCT_VERSION < 9000L
|
||||
#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required"
|
||||
#endif
|
||||
|
||||
class glonass_t : public kaitai::kstruct {
|
||||
|
||||
public:
|
||||
class string_4_t;
|
||||
class string_non_immediate_t;
|
||||
class string_1_t;
|
||||
class string_2_t;
|
||||
class string_3_t;
|
||||
|
||||
glonass_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, glonass_t* p__root = 0);
|
||||
|
||||
private:
|
||||
void _read();
|
||||
void _clean_up();
|
||||
|
||||
public:
|
||||
~glonass_t();
|
||||
|
||||
class string_4_t : public kaitai::kstruct {
|
||||
|
||||
public:
|
||||
|
||||
string_4_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0);
|
||||
|
||||
private:
|
||||
void _read();
|
||||
void _clean_up();
|
||||
|
||||
public:
|
||||
~string_4_t();
|
||||
|
||||
private:
|
||||
uint64_t m_tau_n;
|
||||
uint64_t m_delta_tau_n;
|
||||
uint64_t m_e_n;
|
||||
uint64_t m_not_used_1;
|
||||
bool m_p4;
|
||||
uint64_t m_f_t;
|
||||
uint64_t m_not_used_2;
|
||||
uint64_t m_n_t;
|
||||
uint64_t m_n;
|
||||
uint64_t m_m;
|
||||
glonass_t* m__root;
|
||||
glonass_t* m__parent;
|
||||
|
||||
public:
|
||||
uint64_t tau_n() const { return m_tau_n; }
|
||||
uint64_t delta_tau_n() const { return m_delta_tau_n; }
|
||||
uint64_t e_n() const { return m_e_n; }
|
||||
uint64_t not_used_1() const { return m_not_used_1; }
|
||||
bool p4() const { return m_p4; }
|
||||
uint64_t f_t() const { return m_f_t; }
|
||||
uint64_t not_used_2() const { return m_not_used_2; }
|
||||
uint64_t n_t() const { return m_n_t; }
|
||||
uint64_t n() const { return m_n; }
|
||||
uint64_t m() const { return m_m; }
|
||||
glonass_t* _root() const { return m__root; }
|
||||
glonass_t* _parent() const { return m__parent; }
|
||||
};
|
||||
|
||||
class string_non_immediate_t : public kaitai::kstruct {
|
||||
|
||||
public:
|
||||
|
||||
string_non_immediate_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0);
|
||||
|
||||
private:
|
||||
void _read();
|
||||
void _clean_up();
|
||||
|
||||
public:
|
||||
~string_non_immediate_t();
|
||||
|
||||
private:
|
||||
uint64_t m_data_1;
|
||||
uint64_t m_data_2;
|
||||
glonass_t* m__root;
|
||||
glonass_t* m__parent;
|
||||
|
||||
public:
|
||||
uint64_t data_1() const { return m_data_1; }
|
||||
uint64_t data_2() const { return m_data_2; }
|
||||
glonass_t* _root() const { return m__root; }
|
||||
glonass_t* _parent() const { return m__parent; }
|
||||
};
|
||||
|
||||
class string_1_t : public kaitai::kstruct {
|
||||
|
||||
public:
|
||||
|
||||
string_1_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0);
|
||||
|
||||
private:
|
||||
void _read();
|
||||
void _clean_up();
|
||||
|
||||
public:
|
||||
~string_1_t();
|
||||
|
||||
private:
|
||||
uint64_t m_not_used;
|
||||
uint64_t m_p1;
|
||||
uint64_t m_t_k;
|
||||
uint64_t m_x_vel;
|
||||
uint64_t m_x_speedup;
|
||||
uint64_t m_x;
|
||||
glonass_t* m__root;
|
||||
glonass_t* m__parent;
|
||||
|
||||
public:
|
||||
uint64_t not_used() const { return m_not_used; }
|
||||
uint64_t p1() const { return m_p1; }
|
||||
uint64_t t_k() const { return m_t_k; }
|
||||
uint64_t x_vel() const { return m_x_vel; }
|
||||
uint64_t x_speedup() const { return m_x_speedup; }
|
||||
uint64_t x() const { return m_x; }
|
||||
glonass_t* _root() const { return m__root; }
|
||||
glonass_t* _parent() const { return m__parent; }
|
||||
};
|
||||
|
||||
class string_2_t : public kaitai::kstruct {
|
||||
|
||||
public:
|
||||
|
||||
string_2_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0);
|
||||
|
||||
private:
|
||||
void _read();
|
||||
void _clean_up();
|
||||
|
||||
public:
|
||||
~string_2_t();
|
||||
|
||||
private:
|
||||
uint64_t m_b_n;
|
||||
bool m_p2;
|
||||
uint64_t m_t_b;
|
||||
uint64_t m_not_used;
|
||||
uint64_t m_y_vel;
|
||||
uint64_t m_y_speedup;
|
||||
uint64_t m_y;
|
||||
glonass_t* m__root;
|
||||
glonass_t* m__parent;
|
||||
|
||||
public:
|
||||
uint64_t b_n() const { return m_b_n; }
|
||||
bool p2() const { return m_p2; }
|
||||
uint64_t t_b() const { return m_t_b; }
|
||||
uint64_t not_used() const { return m_not_used; }
|
||||
uint64_t y_vel() const { return m_y_vel; }
|
||||
uint64_t y_speedup() const { return m_y_speedup; }
|
||||
uint64_t y() const { return m_y; }
|
||||
glonass_t* _root() const { return m__root; }
|
||||
glonass_t* _parent() const { return m__parent; }
|
||||
};
|
||||
|
||||
class string_3_t : public kaitai::kstruct {
|
||||
|
||||
public:
|
||||
|
||||
string_3_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0);
|
||||
|
||||
private:
|
||||
void _read();
|
||||
void _clean_up();
|
||||
|
||||
public:
|
||||
~string_3_t();
|
||||
|
||||
private:
|
||||
bool m_p3;
|
||||
uint64_t m_gamma_n;
|
||||
bool m_not_used;
|
||||
uint64_t m_p;
|
||||
bool m_l_n;
|
||||
uint64_t m_z_vel;
|
||||
uint64_t m_z_speedup;
|
||||
uint64_t m_z;
|
||||
glonass_t* m__root;
|
||||
glonass_t* m__parent;
|
||||
|
||||
public:
|
||||
bool p3() const { return m_p3; }
|
||||
uint64_t gamma_n() const { return m_gamma_n; }
|
||||
bool not_used() const { return m_not_used; }
|
||||
uint64_t p() const { return m_p; }
|
||||
bool l_n() const { return m_l_n; }
|
||||
uint64_t z_vel() const { return m_z_vel; }
|
||||
uint64_t z_speedup() const { return m_z_speedup; }
|
||||
uint64_t z() const { return m_z; }
|
||||
glonass_t* _root() const { return m__root; }
|
||||
glonass_t* _parent() const { return m__parent; }
|
||||
};
|
||||
|
||||
private:
|
||||
bool m_idle_chip;
|
||||
uint64_t m_string_number;
|
||||
kaitai::kstruct* m_data;
|
||||
uint64_t m_hamming_code;
|
||||
uint64_t m_pad_1;
|
||||
uint64_t m_superframe_number;
|
||||
uint64_t m_pad_2;
|
||||
uint64_t m_frame_number;
|
||||
glonass_t* m__root;
|
||||
kaitai::kstruct* m__parent;
|
||||
|
||||
public:
|
||||
bool idle_chip() const { return m_idle_chip; }
|
||||
uint64_t string_number() const { return m_string_number; }
|
||||
kaitai::kstruct* data() const { return m_data; }
|
||||
uint64_t hamming_code() const { return m_hamming_code; }
|
||||
uint64_t pad_1() const { return m_pad_1; }
|
||||
uint64_t superframe_number() const { return m_superframe_number; }
|
||||
uint64_t pad_2() const { return m_pad_2; }
|
||||
uint64_t frame_number() const { return m_frame_number; }
|
||||
glonass_t* _root() const { return m__root; }
|
||||
kaitai::kstruct* _parent() const { return m__parent; }
|
||||
};
|
||||
|
||||
#endif // GLONASS_H_
|
||||
@@ -274,9 +274,9 @@ gps_t::tlm_t::tlm_t(kaitai::kstream* p__io, gps_t* p__parent, gps_t* p__root) :
|
||||
}
|
||||
|
||||
void gps_t::tlm_t::_read() {
|
||||
m_magic = m__io->read_bytes(1);
|
||||
if (!(magic() == std::string("\x8B", 1))) {
|
||||
throw kaitai::validation_not_equal_error<std::string>(std::string("\x8B", 1), magic(), _io(), std::string("/types/tlm/seq/0"));
|
||||
m_preamble = m__io->read_bytes(1);
|
||||
if (!(preamble() == std::string("\x8B", 1))) {
|
||||
throw kaitai::validation_not_equal_error<std::string>(std::string("\x8B", 1), preamble(), _io(), std::string("/types/tlm/seq/0"));
|
||||
}
|
||||
m_tlm = m__io->read_bits_int_be(14);
|
||||
m_integrity_status = m__io->read_bits_int_be(1);
|
||||
|
||||
@@ -273,7 +273,7 @@ public:
|
||||
~tlm_t();
|
||||
|
||||
private:
|
||||
std::string m_magic;
|
||||
std::string m_preamble;
|
||||
uint64_t m_tlm;
|
||||
bool m_integrity_status;
|
||||
bool m_reserved;
|
||||
@@ -281,7 +281,7 @@ public:
|
||||
gps_t* m__parent;
|
||||
|
||||
public:
|
||||
std::string magic() const { return m_magic; }
|
||||
std::string preamble() const { return m_preamble; }
|
||||
uint64_t tlm() const { return m_tlm; }
|
||||
bool integrity_status() const { return m_integrity_status; }
|
||||
bool reserved() const { return m_reserved; }
|
||||
|
||||
@@ -89,13 +89,10 @@ void ubx_t::rxm_rawx_t::_read() {
|
||||
m_num_meas = m__io->read_u1();
|
||||
m_rec_stat = m__io->read_u1();
|
||||
m_reserved1 = m__io->read_bytes(3);
|
||||
int l_measurements = num_meas();
|
||||
m__raw_measurements = new std::vector<std::string>();
|
||||
m__raw_measurements->reserve(l_measurements);
|
||||
m__io__raw_measurements = new std::vector<kaitai::kstream*>();
|
||||
m__io__raw_measurements->reserve(l_measurements);
|
||||
m_measurements = new std::vector<meas_t*>();
|
||||
m_measurements->reserve(l_measurements);
|
||||
const int l_measurements = num_meas();
|
||||
for (int i = 0; i < l_measurements; i++) {
|
||||
m__raw_measurements->push_back(m__io->read_bytes(32));
|
||||
kaitai::kstream* io__raw_measurements = new kaitai::kstream(m__raw_measurements->at(m__raw_measurements->size() - 1));
|
||||
@@ -184,9 +181,8 @@ void ubx_t::rxm_sfrbx_t::_read() {
|
||||
m_reserved2 = m__io->read_bytes(1);
|
||||
m_version = m__io->read_u1();
|
||||
m_reserved3 = m__io->read_bytes(1);
|
||||
int l_body = num_words();
|
||||
m_body = new std::vector<uint32_t>();
|
||||
m_body->reserve(l_body);
|
||||
const int l_body = num_words();
|
||||
for (int i = 0; i < l_body; i++) {
|
||||
m_body->push_back(m__io->read_u4le());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
# http://gauss.gge.unb.ca/GLONASS.ICD.pdf
|
||||
meta:
|
||||
id: glonass
|
||||
endian: be
|
||||
bit-endian: be
|
||||
seq:
|
||||
- id: idle_chip
|
||||
type: b1
|
||||
- id: string_number
|
||||
type: b4
|
||||
- id: data
|
||||
type:
|
||||
switch-on: string_number
|
||||
cases:
|
||||
1: string_1
|
||||
2: string_2
|
||||
3: string_3
|
||||
4: string_4
|
||||
_: string_non_immediate
|
||||
- id: hamming_code
|
||||
type: b8
|
||||
- id: pad_1
|
||||
type: b11
|
||||
- id: superframe_number
|
||||
type: b16
|
||||
- id: pad_2
|
||||
type: b8
|
||||
- id: frame_number
|
||||
type: b8
|
||||
|
||||
types:
|
||||
string_1:
|
||||
seq:
|
||||
- id: not_used
|
||||
type: b2
|
||||
- id: p1
|
||||
type: b2
|
||||
- id: t_k
|
||||
type: b12
|
||||
- id: x_vel
|
||||
type: b24
|
||||
- id: x_speedup
|
||||
type: b5
|
||||
- id: x
|
||||
type: b27
|
||||
string_2:
|
||||
seq:
|
||||
- id: b_n
|
||||
type: b3
|
||||
- id: p2
|
||||
type: b1
|
||||
- id: t_b
|
||||
type: b7
|
||||
- id: not_used
|
||||
type: b5
|
||||
- id: y_vel
|
||||
type: b24
|
||||
- id: y_speedup
|
||||
type: b5
|
||||
- id: y
|
||||
type: b27
|
||||
string_3:
|
||||
seq:
|
||||
- id: p3
|
||||
type: b1
|
||||
- id: gamma_n
|
||||
type: b11
|
||||
- id: not_used
|
||||
type: b1
|
||||
- id: p
|
||||
type: b2
|
||||
- id: l_n
|
||||
type: b1
|
||||
- id: z_vel
|
||||
type: b24
|
||||
- id: z_speedup
|
||||
type: b5
|
||||
- id: z
|
||||
type: b27
|
||||
string_4:
|
||||
seq:
|
||||
- id: tau_n
|
||||
type: b22
|
||||
- id: delta_tau_n
|
||||
type: b5
|
||||
- id: e_n
|
||||
type: b5
|
||||
- id: not_used_1
|
||||
type: b14
|
||||
- id: p4
|
||||
type: b1
|
||||
- id: f_t
|
||||
type: b4
|
||||
- id: not_used_2
|
||||
type: b3
|
||||
- id: n_t
|
||||
type: b11
|
||||
- id: n
|
||||
type: b5
|
||||
- id: m
|
||||
type: b2
|
||||
string_non_immediate:
|
||||
seq:
|
||||
- id: data_1
|
||||
type: b64
|
||||
- id: data_2
|
||||
type: b8
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/selfdrive/locationd/generated/glonass.cpp b/selfdrive/locationd/generated/glonass.cpp
|
||||
index 6a48fe62c..149134fbb 100644
|
||||
--- a/selfdrive/locationd/generated/glonass.cpp
|
||||
+++ b/selfdrive/locationd/generated/glonass.cpp
|
||||
@@ -17,7 +17,7 @@ glonass_t::glonass_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, glonass
|
||||
void glonass_t::_read() {
|
||||
m_idle_chip = m__io->read_bits_int_be(1);
|
||||
m_string_number = m__io->read_bits_int_be(4);
|
||||
- m__io->align_to_byte();
|
||||
+ //m__io->align_to_byte();
|
||||
switch (string_number()) {
|
||||
case 4: {
|
||||
m_data = new string_4_t(m__io, this, m__root);
|
||||
@@ -19,7 +19,7 @@ seq:
|
||||
types:
|
||||
tlm:
|
||||
seq:
|
||||
- id: magic
|
||||
- id: preamble
|
||||
contents: [0x8b]
|
||||
- id: tlm
|
||||
type: b14
|
||||
|
||||
Executable
+105
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import numpy as np
|
||||
import sympy as sp
|
||||
|
||||
from selfdrive.locationd.models.constants import ObservationKind
|
||||
from rednose.helpers.ekf_sym import gen_code, EKF_sym
|
||||
|
||||
|
||||
class LaneKalman():
|
||||
name = 'lane'
|
||||
|
||||
@staticmethod
|
||||
def generate_code(generated_dir):
|
||||
# make functions and jacobians with sympy
|
||||
# state variables
|
||||
dim = 6
|
||||
state = sp.MatrixSymbol('state', dim, 1)
|
||||
|
||||
dd = sp.Symbol('dd') # WARNING: NOT TIME
|
||||
|
||||
# Time derivative of the state as a function of state
|
||||
state_dot = sp.Matrix(np.zeros((dim, 1)))
|
||||
state_dot[:3,0] = sp.Matrix(state[3:6,0])
|
||||
|
||||
# Basic descretization, 1st order intergrator
|
||||
# Can be pretty bad if dt is big
|
||||
f_sym = sp.Matrix(state) + dd*state_dot
|
||||
|
||||
#
|
||||
# Observation functions
|
||||
#
|
||||
h_lane_sym = sp.Matrix(state[:3,0])
|
||||
obs_eqs = [[h_lane_sym, ObservationKind.LANE_PT, None]]
|
||||
gen_code(generated_dir, LaneKalman.name, f_sym, dd, state, obs_eqs, dim, dim)
|
||||
|
||||
def __init__(self, generated_dir, pt_std=5):
|
||||
# state
|
||||
# left and right lane centers in ecef
|
||||
# WARNING: this is not a temporal model
|
||||
# the 'time' in this kalman filter is
|
||||
# the distance traveled by the vehicle,
|
||||
# which should approximately be the
|
||||
# distance along the lane path
|
||||
# a more logical parametrization
|
||||
# states 0-2 are ecef coordinates distance d
|
||||
# states 3-5 is the 3d "velocity" of the
|
||||
# lane in ecef (m/m).
|
||||
x_initial = np.array([0,0,0,
|
||||
0,0,0])
|
||||
|
||||
# state covariance
|
||||
P_initial = np.diag([1e16, 1e16, 1e16,
|
||||
1**2, 1**2, 1**2])
|
||||
|
||||
# process noise
|
||||
Q = np.diag([0.1**2, 0.1**2, 0.1**2,
|
||||
0.1**2, 0.1**2, 0.1*2])
|
||||
|
||||
self.dim_state = len(x_initial)
|
||||
|
||||
# init filter
|
||||
self.filter = EKF_sym(generated_dir, self.name, Q, x_initial, P_initial, x_initial.shape[0], P_initial.shape[0])
|
||||
self.obs_noise = {ObservationKind.LANE_PT: np.diag([pt_std**2]*3)}
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
return self.filter.state()
|
||||
|
||||
@property
|
||||
def P(self):
|
||||
return self.filter.covs()
|
||||
|
||||
def predict(self, t):
|
||||
return self.filter.predict(t)
|
||||
|
||||
def rts_smooth(self, estimates):
|
||||
return self.filter.rts_smooth(estimates, norm_quats=False)
|
||||
|
||||
|
||||
def init_state(self, state, covs_diag=None, covs=None, filter_time=None):
|
||||
if covs_diag is not None:
|
||||
P = np.diag(covs_diag)
|
||||
elif covs is not None:
|
||||
P = covs
|
||||
else:
|
||||
P = self.filter.covs()
|
||||
self.filter.init_state(state, P, filter_time)
|
||||
|
||||
def predict_and_observe(self, t, kind, data):
|
||||
data = np.atleast_2d(data)
|
||||
return self.filter.predict_and_update_batch(t, kind, data, self.get_R(kind, len(data)))
|
||||
|
||||
def get_R(self, kind, n):
|
||||
obs_noise = self.obs_noise[kind]
|
||||
dim = obs_noise.shape[0]
|
||||
R = np.zeros((n, dim, dim))
|
||||
for i in range(n):
|
||||
R[i,:,:] = obs_noise
|
||||
return R
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
generated_dir = sys.argv[2]
|
||||
LaneKalman.generate_code(generated_dir)
|
||||
@@ -22,7 +22,24 @@ def get_log(segs=range(0)):
|
||||
logs = []
|
||||
for i in segs:
|
||||
logs.extend(LogReader(get_url("4cf7a6ad03080c90|2021-09-29--13-46-36", i)))
|
||||
return [m for m in logs if m.which() == 'ubloxGnss']
|
||||
|
||||
all_logs = [m for m in logs if m.which() == 'ubloxGnss']
|
||||
low_gnss = []
|
||||
for m in all_logs:
|
||||
if m.ubloxGnss.which() != 'measurementReport':
|
||||
continue
|
||||
|
||||
MAX_MEAS = 7
|
||||
if m.ubloxGnss.measurementReport.numMeas > MAX_MEAS:
|
||||
mb = m.as_builder()
|
||||
mb.ubloxGnss.measurementReport.numMeas = MAX_MEAS
|
||||
mb.ubloxGnss.measurementReport.measurements = list(m.ubloxGnss.measurementReport.measurements)[:MAX_MEAS]
|
||||
mb.ubloxGnss.measurementReport.measurements[0].pseudorange += 1000
|
||||
low_gnss.append(mb.as_reader())
|
||||
else:
|
||||
low_gnss.append(m)
|
||||
|
||||
return all_logs, low_gnss
|
||||
|
||||
|
||||
def verify_messages(lr, laikad, return_one_success=False):
|
||||
@@ -59,8 +76,9 @@ class TestLaikad(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
logs = get_log(range(1))
|
||||
logs, low_gnss = get_log(range(1))
|
||||
cls.logs = logs
|
||||
cls.low_gnss = low_gnss
|
||||
first_gps_time = get_first_gps_time(logs)
|
||||
cls.first_gps_time = first_gps_time
|
||||
|
||||
@@ -254,6 +272,18 @@ class TestLaikad(unittest.TestCase):
|
||||
# Verify orbit data is not downloaded
|
||||
mock_method.assert_not_called()
|
||||
|
||||
def test_low_gnss_meas(self):
|
||||
cnt = 0
|
||||
laikad = Laikad()
|
||||
for m in self.low_gnss:
|
||||
msg = laikad.process_gnss_msg(m.ubloxGnss, m.logMonoTime, block=True)
|
||||
if msg is None:
|
||||
continue
|
||||
gm = msg.gnssMeasurements
|
||||
if len(gm.correctedMeasurements) != 0 and gm.positionECEF.valid:
|
||||
cnt += 1
|
||||
self.assertEqual(cnt, 554)
|
||||
|
||||
def dict_has_values(self, dct):
|
||||
self.assertGreater(len(dct), 0)
|
||||
self.assertGreater(min([len(v) for v in dct.values()]), 0)
|
||||
|
||||
@@ -22,15 +22,15 @@ FIT_POINTS_TOTAL_QLOG = 600
|
||||
MIN_VEL = 15 # m/s
|
||||
FRICTION_FACTOR = 1.5 # ~85% of data coverage
|
||||
FACTOR_SANITY = 0.3
|
||||
FACTOR_SANITY_QLOG = 0.5
|
||||
FRICTION_SANITY = 0.5
|
||||
FRICTION_SANITY_QLOG = 0.8
|
||||
STEER_MIN_THRESHOLD = 0.02
|
||||
MIN_FILTER_DECAY = 50
|
||||
MAX_FILTER_DECAY = 250
|
||||
LAT_ACC_THRESHOLD = 1
|
||||
STEER_BUCKET_BOUNDS = [(-0.5, -0.3), (-0.3, -0.2), (-0.2, -0.1), (-0.1, 0), (0, 0.1), (0.1, 0.2), (0.2, 0.3), (0.3, 0.5)]
|
||||
MIN_BUCKET_POINTS = np.array([100, 300, 500, 500, 500, 500, 300, 100])
|
||||
MAX_RESETS = 5.0
|
||||
MAX_INVALID_THRESHOLD = 10
|
||||
MIN_ENGAGE_BUFFER = 2 # secs
|
||||
|
||||
VERSION = 1 # bump this to invalidate old parameter caches
|
||||
@@ -100,10 +100,15 @@ class TorqueEstimator:
|
||||
self.min_bucket_points = MIN_BUCKET_POINTS / 10
|
||||
self.min_points_total = MIN_POINTS_TOTAL_QLOG
|
||||
self.fit_points = FIT_POINTS_TOTAL_QLOG
|
||||
self.factor_sanity = FACTOR_SANITY_QLOG
|
||||
self.friction_sanity = FRICTION_SANITY_QLOG
|
||||
|
||||
else:
|
||||
self.min_bucket_points = MIN_BUCKET_POINTS
|
||||
self.min_points_total = MIN_POINTS_TOTAL
|
||||
self.fit_points = FIT_POINTS_TOTAL
|
||||
self.factor_sanity = FACTOR_SANITY
|
||||
self.friction_sanity = FRICTION_SANITY
|
||||
|
||||
self.offline_friction = 0.0
|
||||
self.offline_latAccelFactor = 0.0
|
||||
@@ -123,10 +128,10 @@ class TorqueEstimator:
|
||||
'points': []
|
||||
}
|
||||
self.decay = MIN_FILTER_DECAY
|
||||
self.min_lataccel_factor = (1.0 - FACTOR_SANITY) * self.offline_latAccelFactor
|
||||
self.max_lataccel_factor = (1.0 + FACTOR_SANITY) * self.offline_latAccelFactor
|
||||
self.min_friction = (1.0 - FRICTION_SANITY) * self.offline_friction
|
||||
self.max_friction = (1.0 + FRICTION_SANITY) * self.offline_friction
|
||||
self.min_lataccel_factor = (1.0 - self.factor_sanity) * self.offline_latAccelFactor
|
||||
self.max_lataccel_factor = (1.0 + self.factor_sanity) * self.offline_latAccelFactor
|
||||
self.min_friction = (1.0 - self.friction_sanity) * self.offline_friction
|
||||
self.max_friction = (1.0 + self.friction_sanity) * self.offline_friction
|
||||
|
||||
# try to restore cached params
|
||||
params = Params()
|
||||
@@ -165,7 +170,6 @@ class TorqueEstimator:
|
||||
|
||||
def reset(self):
|
||||
self.resets += 1.0
|
||||
self.invalid_values_tracker = 0.0
|
||||
self.decay = MIN_FILTER_DECAY
|
||||
self.raw_points = defaultdict(lambda: deque(maxlen=self.hist_len))
|
||||
self.filtered_points = PointBuckets(x_bounds=STEER_BUCKET_BOUNDS, min_points=self.min_bucket_points, min_points_total=self.min_points_total)
|
||||
@@ -190,12 +194,6 @@ class TorqueEstimator:
|
||||
self.filtered_params[param].update(value)
|
||||
self.filtered_params[param].update_alpha(self.decay)
|
||||
|
||||
def is_sane(self, latAccelFactor, latAccelOffset, friction):
|
||||
if any([val is None or np.isnan(val) for val in [latAccelFactor, latAccelOffset, friction]]):
|
||||
return False
|
||||
return (self.max_friction >= friction >= self.min_friction) and\
|
||||
(self.max_lataccel_factor >= latAccelFactor >= self.min_lataccel_factor)
|
||||
|
||||
def handle_log(self, t, which, msg):
|
||||
if which == "carControl":
|
||||
self.raw_points["carControl_t"].append(t + self.lag)
|
||||
@@ -225,23 +223,20 @@ class TorqueEstimator:
|
||||
liveTorqueParameters.useParams = self.use_params
|
||||
|
||||
if self.filtered_points.is_valid():
|
||||
latAccelFactor, latAccelOffset, friction_coeff = self.estimate_params()
|
||||
latAccelFactor, latAccelOffset, frictionCoeff = self.estimate_params()
|
||||
liveTorqueParameters.latAccelFactorRaw = float(latAccelFactor)
|
||||
liveTorqueParameters.latAccelOffsetRaw = float(latAccelOffset)
|
||||
liveTorqueParameters.frictionCoefficientRaw = float(friction_coeff)
|
||||
liveTorqueParameters.frictionCoefficientRaw = float(frictionCoeff)
|
||||
|
||||
if self.is_sane(latAccelFactor, latAccelOffset, friction_coeff):
|
||||
liveTorqueParameters.liveValid = True
|
||||
self.update_params({'latAccelFactor': latAccelFactor, 'latAccelOffset': latAccelOffset, 'frictionCoefficient': friction_coeff})
|
||||
self.invalid_values_tracker = max(0.0, self.invalid_values_tracker - 0.5)
|
||||
else:
|
||||
cloudlog.exception("Live torque parameters are outside acceptable bounds.")
|
||||
if any([val is None or np.isnan(val) for val in [latAccelFactor, latAccelOffset, frictionCoeff]]):
|
||||
cloudlog.exception("Live torque parameters are invalid.")
|
||||
liveTorqueParameters.liveValid = False
|
||||
self.invalid_values_tracker += 1.0
|
||||
# Reset when ~10 invalid over 5 secs
|
||||
if self.invalid_values_tracker > MAX_INVALID_THRESHOLD:
|
||||
# Do not reset the filter as it may cause a drastic jump, just reset points
|
||||
self.reset()
|
||||
self.reset()
|
||||
else:
|
||||
liveTorqueParameters.liveValid = True
|
||||
latAccelFactor = np.clip(latAccelFactor, self.min_lataccel_factor, self.max_lataccel_factor)
|
||||
frictionCoeff = np.clip(frictionCoeff, self.min_friction, self.max_friction)
|
||||
self.update_params({'latAccelFactor': latAccelFactor, 'latAccelOffset': latAccelOffset, 'frictionCoefficient': frictionCoeff})
|
||||
else:
|
||||
liveTorqueParameters.liveValid = False
|
||||
|
||||
|
||||
@@ -170,9 +170,18 @@ kj::Array<capnp::word> UbloxMsgParser::gen_rxm_sfrbx(ubx_t::rxm_sfrbx_t *msg) {
|
||||
kaitai::kstream stream(subframe_data);
|
||||
gps_t subframe(&stream);
|
||||
int subframe_id = subframe.how()->subframe_id();
|
||||
int sv_id = msg->sv_id();
|
||||
uint64_t tow_counter = subframe.how()->tow_count();
|
||||
|
||||
if (subframe_id == 1) gps_subframes[msg->sv_id()].clear();
|
||||
gps_subframes[msg->sv_id()][subframe_id] = subframe_data;
|
||||
bool clear_buffer = subframe_id == 1;
|
||||
if (gps_sat_tow_count.count(sv_id) != 0) {
|
||||
int64_t counter_diff = tow_counter - gps_sat_tow_count[sv_id];
|
||||
clear_buffer |= counter_diff != 1 && counter_diff != -100798;
|
||||
}
|
||||
if (clear_buffer) gps_subframes[sv_id].clear();
|
||||
|
||||
gps_subframes[sv_id][subframe_id] = subframe_data;
|
||||
gps_sat_tow_count[sv_id] = tow_counter;
|
||||
}
|
||||
|
||||
if (gps_subframes[msg->sv_id()].size() == 5) {
|
||||
|
||||
@@ -103,6 +103,7 @@ class UbloxMsgParser {
|
||||
inline bool valid_so_far();
|
||||
|
||||
std::unordered_map<int, std::unordered_map<int, std::string>> gps_subframes;
|
||||
std::unordered_map<int, uint64_t> gps_sat_tow_count;
|
||||
|
||||
size_t bytes_in_parse_buf = 0;
|
||||
uint8_t msg_parse_buf[ublox::UBLOX_HEADER_SIZE + ublox::UBLOX_MAX_MSG_SIZE];
|
||||
|
||||
@@ -20,7 +20,7 @@ from selfdrive.manager.process_config import managed_processes
|
||||
from selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID
|
||||
from system.swaglog import cloudlog, add_file_handler
|
||||
from system.version import is_dirty, get_commit, get_version, get_origin, get_short_branch, \
|
||||
terms_version, training_version, is_tested_branch
|
||||
terms_version, training_version, is_tested_branch, is_release_branch
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ def manager_init() -> None:
|
||||
params.put("GitBranch", get_short_branch(default=""))
|
||||
params.put("GitRemote", get_origin(default=""))
|
||||
params.put_bool("IsTestedBranch", is_tested_branch())
|
||||
params.put_bool("IsReleaseBranch", is_release_branch())
|
||||
|
||||
# set dongle id
|
||||
reg_res = register(show_spinner=True)
|
||||
|
||||
@@ -290,10 +290,11 @@ class DaemonProcess(ManagerProcess):
|
||||
|
||||
|
||||
def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params=None, CP: car.CarParams=None,
|
||||
not_run: Optional[List[str]]=None) -> None:
|
||||
not_run: Optional[List[str]]=None) -> List[ManagerProcess]:
|
||||
if not_run is None:
|
||||
not_run = []
|
||||
|
||||
running = []
|
||||
for p in procs:
|
||||
# Conditions that make a process run
|
||||
run = any((
|
||||
@@ -311,7 +312,10 @@ def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params=None
|
||||
|
||||
if run:
|
||||
p.start()
|
||||
running.append(p)
|
||||
else:
|
||||
p.stop(block=False)
|
||||
|
||||
p.check_watchdog(started)
|
||||
|
||||
return running
|
||||
|
||||
@@ -4,17 +4,17 @@ import signal
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from cereal import car
|
||||
from common.params import Params
|
||||
import selfdrive.manager.manager as manager
|
||||
from selfdrive.manager.process import DaemonProcess
|
||||
from selfdrive.manager.process import ensure_running
|
||||
from selfdrive.manager.process_config import managed_processes
|
||||
from system.hardware import HARDWARE
|
||||
|
||||
os.environ['FAKEUPLOAD'] = "1"
|
||||
|
||||
MAX_STARTUP_TIME = 3
|
||||
ALL_PROCESSES = [p.name for p in managed_processes.values() if (type(p) is not DaemonProcess) and p.enabled and (p.name not in ['pandad', ])]
|
||||
|
||||
BLACKLIST_PROCS = ['manage_athenad', 'pandad', 'pigeond']
|
||||
|
||||
class TestManager(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@@ -47,24 +47,25 @@ class TestManager(unittest.TestCase):
|
||||
HARDWARE.set_power_save(False)
|
||||
manager.manager_init()
|
||||
manager.manager_prepare()
|
||||
for p in ALL_PROCESSES:
|
||||
managed_processes[p].start()
|
||||
|
||||
CP = car.CarParams.new_message()
|
||||
procs = ensure_running(managed_processes.values(), True, Params(), CP, not_run=BLACKLIST_PROCS)
|
||||
|
||||
time.sleep(10)
|
||||
|
||||
for p in reversed(ALL_PROCESSES):
|
||||
with self.subTest(proc=p):
|
||||
state = managed_processes[p].get_process_state_msg()
|
||||
self.assertTrue(state.running, f"{p} not running")
|
||||
exit_code = managed_processes[p].stop(retry=False)
|
||||
for p in procs:
|
||||
with self.subTest(proc=p.name):
|
||||
state = p.get_process_state_msg()
|
||||
self.assertTrue(state.running, f"{p.name} not running")
|
||||
exit_code = p.stop(retry=False)
|
||||
|
||||
self.assertTrue(exit_code is not None, f"{p} failed to exit")
|
||||
self.assertTrue(exit_code is not None, f"{p.name} failed to exit")
|
||||
|
||||
# TODO: interrupted blocking read exits with 1 in cereal. use a more unique return code
|
||||
exit_codes = [0, 1]
|
||||
if managed_processes[p].sigkill:
|
||||
if p.sigkill:
|
||||
exit_codes = [-signal.SIGKILL]
|
||||
self.assertIn(exit_code, exit_codes, f"{p} died with {exit_code}")
|
||||
self.assertIn(exit_code, exit_codes, f"{p.name} died with {exit_code}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -70,30 +70,14 @@ lenv.Program('_dmonitoringmodeld', [
|
||||
if use_thneed and arch == "larch64" or GetOption('pc_thneed'):
|
||||
fn = File("models/supercombo").abspath
|
||||
|
||||
if GetOption('pc_thneed'):
|
||||
cmd = f"cd {Dir('#').abspath}/tinygrad_repo && GPU=1 NATIVE_EXPLOG=1 OPTWG=1 UNSAFE_FLOAT4=1 DEBUGCL=1 python3 openpilot/compile.py {fn}.onnx {fn}.thneed"
|
||||
else:
|
||||
cmd = f"cd {Dir('#').abspath}/tinygrad_repo && FLOAT16=1 MATMUL=1 PYOPENCL_NO_CACHE=1 NATIVE_EXPLOG=1 OPTWG=1 UNSAFE_FLOAT4=1 DEBUGCL=1 python3 openpilot/compile.py {fn}.onnx {fn}.thneed"
|
||||
tinygrad_opts = ["NATIVE_EXPLOG=1", "VALIDHACKS=1", "OPTWG=1", "IMAGE=2", "GPU=1", "CLCACHE=0"]
|
||||
if not GetOption('pc_thneed'):
|
||||
# use FLOAT16 on device for speed + don't cache the CL kernels for space
|
||||
tinygrad_opts += ["FLOAT16=1", "PYOPENCL_NO_CACHE=1"]
|
||||
cmd = f"cd {Dir('#').abspath}/tinygrad_repo && " + ' '.join(tinygrad_opts) + f" python3 openpilot/compile.py {fn}.onnx {fn}.thneed"
|
||||
|
||||
# is there a better way then listing all of tinygrad?
|
||||
lenv.Command(fn + ".thneed", [fn + ".onnx",
|
||||
"#tinygrad_repo/openpilot/compile.py",
|
||||
"#tinygrad_repo/accel/opencl/conv.cl",
|
||||
"#tinygrad_repo/accel/opencl/matmul.cl",
|
||||
"#tinygrad_repo/accel/opencl/ops_opencl.py",
|
||||
"#tinygrad_repo/accel/opencl/preprocessing.py",
|
||||
"#tinygrad_repo/extra/onnx.py",
|
||||
"#tinygrad_repo/extra/thneed.py",
|
||||
"#tinygrad_repo/extra/utils.py",
|
||||
"#tinygrad_repo/tinygrad/llops/ops_gpu.py",
|
||||
"#tinygrad_repo/tinygrad/llops/ops_opencl.py",
|
||||
"#tinygrad_repo/tinygrad/helpers.py",
|
||||
"#tinygrad_repo/tinygrad/mlops.py",
|
||||
"#tinygrad_repo/tinygrad/ops.py",
|
||||
"#tinygrad_repo/tinygrad/shapetracker.py",
|
||||
"#tinygrad_repo/tinygrad/tensor.py",
|
||||
"#tinygrad_repo/tinygrad/nn/__init__.py"
|
||||
], cmd)
|
||||
tinygrad_files = sum([lenv.Glob("#"+x) for x in open(File("#release/files_common").abspath).read().split("\n") if x.startswith("tinygrad_repo/")], [])
|
||||
lenv.Command(fn + ".thneed", [fn + ".onnx"] + tinygrad_files, cmd)
|
||||
|
||||
llenv = lenv.Clone()
|
||||
if GetOption('pc_thneed'):
|
||||
@@ -116,4 +100,4 @@ llenv.Program('_modeld', [
|
||||
lenv.Program('_navmodeld', [
|
||||
"navmodeld.cc",
|
||||
"models/nav.cc",
|
||||
]+common_model, LIBS=libs + transformations)
|
||||
]+common_model, LIBS=libs + transformations)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:db746e3753de84367595fedd089c2bd41b06bd401ea28e085663533d0e63d74b
|
||||
size 45962473
|
||||
oid sha256:3d8f2f9d53908e7a5496412956548dab1aa08ed0b5fb9d1c685f275369f07a57
|
||||
size 45962515
|
||||
|
||||
@@ -11,7 +11,9 @@ from struct import unpack_from, calcsize, pack
|
||||
|
||||
from cereal import log
|
||||
import cereal.messaging as messaging
|
||||
from common.gpio import gpio_init, gpio_set
|
||||
from laika.gps_time import GPSTime
|
||||
from system.hardware.tici.pins import GPIO
|
||||
from system.swaglog import cloudlog
|
||||
from selfdrive.sensord.rawgps.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv
|
||||
from selfdrive.sensord.rawgps.structs import (dict_unpacker, position_report, relist,
|
||||
@@ -182,6 +184,7 @@ def main() -> NoReturn:
|
||||
|
||||
def cleanup(sig, frame):
|
||||
cloudlog.warning(f"caught sig {sig}, disabling quectel gps")
|
||||
gpio_set(GPIO.UBLOX_PWR_EN, False)
|
||||
teardown_quectel(diag)
|
||||
cloudlog.warning("quectel cleanup done")
|
||||
sys.exit(0)
|
||||
@@ -190,6 +193,8 @@ def main() -> NoReturn:
|
||||
|
||||
setup_quectel(diag)
|
||||
cloudlog.warning("quectel setup done")
|
||||
gpio_init(GPIO.UBLOX_PWR_EN, True)
|
||||
gpio_set(GPIO.UBLOX_PWR_EN, True)
|
||||
|
||||
pm = messaging.PubMaster(['qcomGnss', 'gpsLocation'])
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
db587bfef2317c5a3471632ac47381457e1be853
|
||||
2a255ecea3665a3d295210195fd8ef0791ab49a0
|
||||
|
||||
@@ -188,7 +188,7 @@ def get_car_params(msgs, fsm, can_sock, fingerprint):
|
||||
canmsgs = [msg for msg in msgs if msg.which() == 'can']
|
||||
for m in canmsgs[:300]:
|
||||
can.send(m.as_builder().to_bytes())
|
||||
_, CP = get_car(can, sendcan)
|
||||
_, CP = get_car(can, sendcan, Params().get_bool("ExperimentalLongitudinalEnabled"))
|
||||
Params().put("CarParams", CP.to_bytes())
|
||||
|
||||
|
||||
@@ -448,6 +448,9 @@ def setup_env(simulation=False, CP=None, cfg=None, controlsState=None):
|
||||
if CP.openpilotLongitudinalControl:
|
||||
params.put_bool("ExperimentalLongitudinalEnabled", True)
|
||||
|
||||
# controlsd process configuration assume all routes are out of dashcam
|
||||
params.put_bool("DashcamOverride", True)
|
||||
|
||||
|
||||
def python_replay_process(cfg, lr, fingerprint=None):
|
||||
sub_sockets = [s for _, sub in cfg.pub_sub.items() for s in sub]
|
||||
@@ -554,7 +557,7 @@ def cpp_replay_process(cfg, lr, fingerprint=None):
|
||||
managed_processes[cfg.proc_name].start()
|
||||
|
||||
try:
|
||||
with Timeout(TIMEOUT):
|
||||
with Timeout(TIMEOUT, error_msg=f"timed out testing process {repr(cfg.proc_name)}"):
|
||||
while not all(pm.all_readers_updated(s) for s in cfg.pub_sub.keys()):
|
||||
time.sleep(0)
|
||||
|
||||
@@ -568,7 +571,7 @@ def cpp_replay_process(cfg, lr, fingerprint=None):
|
||||
|
||||
resp_sockets = cfg.pub_sub[msg.which()] if cfg.should_recv_callback is None else cfg.should_recv_callback(msg)
|
||||
for s in resp_sockets:
|
||||
response = messaging.recv_one(sockets[s])
|
||||
response = messaging.recv_one_retry(sockets[s])
|
||||
|
||||
if response is None:
|
||||
print(f"Warning, no response received {i}")
|
||||
|
||||
@@ -1 +1 @@
|
||||
18a70665bdb6b6aee4a224e826417049415e8290
|
||||
844c84eae269e74b246b039d2b33fd44e98a5b68
|
||||
@@ -29,6 +29,7 @@ source_segments = [
|
||||
("SUBARU", "341dccd5359e3c97|2022-09-12--10-35-33--3"), # SUBARU.OUTBACK
|
||||
("GM", "0c58b6a25109da2b|2021-02-23--16-35-50--11"), # GM.VOLT
|
||||
("GM2", "376bf99325883932|2022-10-27--13-41-22--1"), # GM.BOLT_EUV
|
||||
("FORD", "54827bf84c38b14f|2023-01-26--21-59-07--4"), # FORD.BRONCO_SPORT_MK1
|
||||
("NISSAN", "35336926920f3571|2021-02-12--18-38-48--46"), # NISSAN.XTRAIL
|
||||
("VOLKSWAGEN", "de9592456ad7d144|2021-06-29--11-00-15--6"), # VOLKSWAGEN.GOLF
|
||||
("MAZDA", "bd6a637565e91581|2021-10-30--15-14-53--4"), # MAZDA.CX9_2021
|
||||
@@ -52,6 +53,7 @@ segments = [
|
||||
("SUBARU", "regen1E72BBDCED5|2022-09-27--15-55-31--0"),
|
||||
("GM", "regen45B05A80EF6|2022-09-27--15-57-22--0"),
|
||||
("GM2", "376bf99325883932|2022-10-27--13-41-22--1"),
|
||||
("FORD", "54827bf84c38b14f|2023-01-26--21-59-07--4"),
|
||||
("NISSAN", "regenC19D899B46D|2022-09-27--15-59-13--0"),
|
||||
("VOLKSWAGEN", "regenD8F7AC4BD0D|2022-09-27--16-41-45--0"),
|
||||
("MAZDA", "regenFC3F9ECBB64|2022-09-27--16-03-09--0"),
|
||||
|
||||
+12
-3
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import json
|
||||
Import('qt_env', 'arch', 'common', 'messaging', 'visionipc',
|
||||
'cereal', 'transformations')
|
||||
|
||||
@@ -41,6 +42,7 @@ assets_src = "#selfdrive/assets/assets.qrc"
|
||||
qt_env.Command(assets, assets_src, f"rcc $SOURCES -o $TARGET")
|
||||
qt_env.Depends(assets, Glob('#selfdrive/assets/*', exclude=[assets, assets_src, "#selfdrive/assets/assets.o"]))
|
||||
asset_obj = qt_env.Object("assets", assets)
|
||||
Export('asset_obj')
|
||||
|
||||
# build soundd
|
||||
qt_env.Program("soundd/_soundd", ["soundd/main.cc", "soundd/sound.cc"], LIBS=qt_libs)
|
||||
@@ -66,11 +68,18 @@ if GetOption('test'):
|
||||
|
||||
|
||||
# build translation files
|
||||
translation_sources = Glob("#selfdrive/ui/translations/*.ts", strings=True)
|
||||
with open(File("translations/languages.json").abspath) as f:
|
||||
languages = json.loads(f.read())
|
||||
translation_sources = [f"#selfdrive/ui/translations/{l}.ts" for l in languages.values()]
|
||||
translation_targets = [src.replace(".ts", ".qm") for src in translation_sources]
|
||||
lrelease = 'third_party/qt5/larch64/bin/lrelease' if arch == 'larch64' else 'lrelease'
|
||||
qt_env.Command(translation_targets, translation_sources, f"{lrelease} $SOURCES")
|
||||
lrelease_bin = 'third_party/qt5/larch64/bin/lrelease' if arch == 'larch64' else 'lrelease'
|
||||
|
||||
lupdate = qt_env.Command(translation_sources, qt_src, "selfdrive/ui/update_translations.py")
|
||||
lrelease = qt_env.Command(translation_targets, translation_sources, f"{lrelease_bin} $SOURCES")
|
||||
qt_env.Depends(lrelease, lupdate)
|
||||
qt_env.NoClean(translation_sources)
|
||||
qt_env.Precious(translation_sources)
|
||||
qt_env.NoCache(lupdate)
|
||||
|
||||
# setup and factory resetter
|
||||
if GetOption('extras'):
|
||||
|
||||
@@ -132,16 +132,17 @@ void TogglesPanel::updateToggles() {
|
||||
.arg(tr("New Driving Visualization"))
|
||||
.arg(tr("The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner."));
|
||||
|
||||
const bool is_release = params.getBool("IsReleaseBranch");
|
||||
auto cp_bytes = params.get("CarParamsPersistent");
|
||||
if (!cp_bytes.empty()) {
|
||||
AlignedBuffer aligned_buf;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||
|
||||
if (!CP.getExperimentalLongitudinalAvailable()) {
|
||||
if (!CP.getExperimentalLongitudinalAvailable() || is_release) {
|
||||
params.remove("ExperimentalLongitudinalEnabled");
|
||||
}
|
||||
op_long_toggle->setVisible(CP.getExperimentalLongitudinalAvailable());
|
||||
op_long_toggle->setVisible(CP.getExperimentalLongitudinalAvailable() && !is_release);
|
||||
|
||||
const bool op_long = CP.getOpenpilotLongitudinalControl() && !CP.getExperimentalLongitudinalAvailable();
|
||||
const bool exp_long_enabled = CP.getExperimentalLongitudinalAvailable() && params.getBool("ExperimentalLongitudinalEnabled");
|
||||
@@ -154,9 +155,18 @@ void TogglesPanel::updateToggles() {
|
||||
e2e_toggle->setEnabled(false);
|
||||
params.remove("ExperimentalMode");
|
||||
|
||||
const QString no_long = tr("Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control.");
|
||||
const QString exp_long = tr("Enable experimental longitudinal control to allow experimental mode.");
|
||||
e2e_toggle->setDescription("<b>" + (CP.getExperimentalLongitudinalAvailable() ? exp_long : no_long) + "</b><br><br>" + e2e_description);
|
||||
const QString unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.");
|
||||
|
||||
QString long_desc = unavailable + " " + \
|
||||
tr("openpilot longitudinal control may come in a future update.");
|
||||
if (CP.getExperimentalLongitudinalAvailable()) {
|
||||
if (is_release) {
|
||||
long_desc = unavailable + " " + tr("An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches.");
|
||||
} else {
|
||||
long_desc = tr("Enable experimental longitudinal control to allow Experimental mode.");
|
||||
}
|
||||
}
|
||||
e2e_toggle->setDescription("<b>" + long_desc + "</b><br><br>" + e2e_description);
|
||||
}
|
||||
|
||||
e2e_toggle->refresh();
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFile>
|
||||
#include <QHash>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QLayoutItem>
|
||||
#include <QStyleOption>
|
||||
#include <QPainterPath>
|
||||
#include <QTextStream>
|
||||
#include <QtXml/QDomDocument>
|
||||
|
||||
#include "common/params.h"
|
||||
#include "common/swaglog.h"
|
||||
@@ -218,3 +221,37 @@ QColor interpColor(float xv, std::vector<float> xp, std::vector<QColor> fp) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static QHash<QString, QByteArray> load_bootstrap_icons() {
|
||||
QHash<QString, QByteArray> icons;
|
||||
|
||||
QFile f(":/bootstrap-icons.svg");
|
||||
if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QDomDocument xml;
|
||||
xml.setContent(&f);
|
||||
QDomNode n = xml.documentElement().firstChild();
|
||||
while (!n.isNull()) {
|
||||
QDomElement e = n.toElement();
|
||||
if (!e.isNull() && e.hasAttribute("id")) {
|
||||
QString svg_str;
|
||||
QTextStream stream(&svg_str);
|
||||
n.save(stream, 0);
|
||||
svg_str.replace("<symbol", "<svg");
|
||||
svg_str.replace("</symbol>", "</svg>");
|
||||
icons[e.attribute("id")] = svg_str.toUtf8();
|
||||
}
|
||||
n = n.nextSibling();
|
||||
}
|
||||
}
|
||||
return icons;
|
||||
}
|
||||
|
||||
QPixmap bootstrapPixmap(const QString &id) {
|
||||
static QHash<QString, QByteArray> icons = load_bootstrap_icons();
|
||||
|
||||
QPixmap pixmap;
|
||||
if (auto it = icons.find(id); it != icons.end()) {
|
||||
pixmap.loadFromData(it.value(), "svg");
|
||||
}
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ void swagLogMessageHandler(QtMsgType type, const QMessageLogContext &context, co
|
||||
void initApp(int argc, char *argv[]);
|
||||
QWidget* topWidget (QWidget* widget);
|
||||
QPixmap loadPixmap(const QString &fileName, const QSize &size = {}, Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio);
|
||||
QPixmap bootstrapPixmap(const QString &id);
|
||||
|
||||
QRect getTextRect(QPainter &p, int flags, const QString &text);
|
||||
void drawRoundedRect(QPainter &painter, const QRectF &rect, qreal xRadiusTop, qreal yRadiusTop, qreal xRadiusBottom, qreal yRadiusBottom);
|
||||
|
||||
@@ -358,7 +358,7 @@ void CameraWidget::vipcThread() {
|
||||
while (!QThread::currentThread()->isInterruptionRequested()) {
|
||||
if (!vipc_client || cur_stream != requested_stream_type) {
|
||||
clearFrames();
|
||||
qDebug() << "connecting to stream " << requested_stream_type << ", was connected to " << cur_stream;
|
||||
qDebug().nospace() << "connecting to stream " << requested_stream_type << ", was connected to " << cur_stream;
|
||||
cur_stream = requested_stream_type;
|
||||
vipc_client.reset(new VisionIpcClient(stream_name, cur_stream, false));
|
||||
}
|
||||
|
||||
@@ -1044,11 +1044,19 @@ location set</source>
|
||||
<translation>Die Fahrvisualisierung wechselt bei niedrigen Geschwindigkeiten zur Straßengewandten Weitwinkelkamera, um manche Kurven besser zu zeigen. Außerdem wird das Experimenteller Modus logo oben rechts angezeigt.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control.</source>
|
||||
<translation>Der experimentelle Modus ist momentan für dieses Auto nicht verfügbar, da es den eingebauten adaptiven Tempomaten des Autos benutzt.</translation>
|
||||
<source>Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.</source>
|
||||
<translation>Der experimentelle Modus ist momentan für dieses Auto nicht verfügbar da es den eingebauten adaptiven Tempomaten des Autos benutzt.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable experimental longitudinal control to allow experimental mode.</source>
|
||||
<source>openpilot longitudinal control may come in a future update.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable experimental longitudinal control to allow Experimental mode.</source>
|
||||
<translation>Aktiviere den experimentellen Openpilot Tempomaten für experimentelle Funktionen.</translation>
|
||||
</message>
|
||||
</context>
|
||||
|
||||
@@ -1017,14 +1017,6 @@ location set</source>
|
||||
<source>On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control.</source>
|
||||
<translation>openpilotはこの車の場合、車に内蔵されているACCを標準で利用します。この機能を有効にすることで実験段階のopenpilotによるアクセル制御を利用できます。実験モードと合わせて利用することをお勧めします。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control.</source>
|
||||
<translation>この車のACCがアクセル制御を行うため、実験モードを利用することができません。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable experimental longitudinal control to allow experimental mode.</source>
|
||||
<translation>実験段階のopenpilotによるアクセル制御を有効にしてください。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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:</source>
|
||||
<translation>openpilotは標準ではゆっくりとくつろげる運転を提供します。この実験モードを有効にすると、以下のくつろげる段階ではない開発中の機能を利用する事ができます。</translation>
|
||||
@@ -1045,6 +1037,22 @@ location set</source>
|
||||
<source>The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.</source>
|
||||
<translation>新しい運転画面では、低速時に広角カメラの映像を表示することで、曲がる際の道路の視覚を向上します。実験段階を表すマークが右上に表示されます。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.</source>
|
||||
<translation>この車のACCがアクセル制御を行うため実験モードを利用することができません。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>openpilot longitudinal control may come in a future update.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable experimental longitudinal control to allow Experimental mode.</source>
|
||||
<translation>実験段階のopenpilotによるアクセル制御を有効にしてください。</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Updater</name>
|
||||
|
||||
@@ -501,7 +501,7 @@ location set</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Become a comma prime member at connect.comma.ai</source>
|
||||
<translation>connect.comma.ai에서 comma prime에 가입합니다</translation>
|
||||
<translation>connect.comma.ai 접속 comma prime 가입</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PRIME FEATURES:</source>
|
||||
@@ -1017,17 +1017,9 @@ location set</source>
|
||||
<source>On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control.</source>
|
||||
<translation>이 차량은 openpilot 롱컨트롤 대신 차량의 내장 ACC로 기본 설정됩니다. openpilot 롱컨트롤을 사용하려면 이 옵션을 활성화하세요. 실험적 openpilot 롱컨트롤을 사용하는 경우 실험적 모드를 활성화 하세요.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control.</source>
|
||||
<translation>차량의 기본 ACC가 롱컨트롤에 사용되기 때문에 현재 이 차량에서는 실험적 모드를 사용할수 없습니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable experimental longitudinal control to allow experimental mode.</source>
|
||||
<translation>실험적 롱컨트롤을 사용하려면 실험적 모드를 활성화 하세요.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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:</source>
|
||||
<translation>openpilot은 기본적으로 <b>안정적 모드</b>로 주행합니다. 실험적 모드는 안정적 모드에 준비되지 않은 <b>알파 수준 기능</b>을 활성화 합니다. 실험 모드의 특징은 아래에 나열되어 있습니다</translation>
|
||||
<translation>openpilot은 기본적으로 <b>안정적 모드</b>로 주행합니다. 실험적 모드는 안정적 모드에 준비되지 않은 <b>알파 수준 기능</b>을 활성화 합니다. 실험적 모드의 특징은 아래에 나열되어 있습니다</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>🌮 End-to-End Longitudinal Control 🌮</source>
|
||||
@@ -1043,7 +1035,23 @@ location set</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.</source>
|
||||
<translation>주행 시각화는 저속에서 도로를 향하는 광각 카메라로 전환되어 일부 회전을 더 잘 보여줍니다. 실험적 모드 로고도 우측상단에 표시됩니다.</translation>
|
||||
<translation>주행 시각화는 저속에서 도로를 향하는 광각 카메라로 전환되어 일부 회전을 더 잘 보여줍니다. 실험적 모드의 로고도 우측상단에 표시됩니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.</source>
|
||||
<translation>차량에 장착된 ACC가 롱컨트롤에 사용되기 때문에 현재 이 차량은 실험적 모드를 사용할 수 없습니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>openpilot longitudinal control may come in a future update.</source>
|
||||
<translation>오픈파일럿 롱컨트롤은 향후 업데이트에서 제공될 수 있습니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches.</source>
|
||||
<translation>오픈파일럿 롱컨트롤의 실험 버전은 실험적 모드와 함께 릴리즈 되지 않은 브랜치에서 테스트할 수 있습니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable experimental longitudinal control to allow Experimental mode.</source>
|
||||
<translation>실험적 롱컨트롤을 사용하려면 실험적 모드를 활성화 하세요.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
||||
@@ -132,19 +132,19 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Driver Camera</source>
|
||||
<translation>Câmera voltada para o Motorista</translation>
|
||||
<translation>Câmera do Motorista</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PREVIEW</source>
|
||||
<translation>PREVISUAL</translation>
|
||||
<translation>VER</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)</source>
|
||||
<translation>Pré-visualizar a câmera voltada para o motorista para garantir que monitor tem uma boa visibilidade (veículo precisa estar desligado)</translation>
|
||||
<translation>Pré-visualizar a câmera voltada para o motorista para garantir que o monitoramento do sistema tenha uma boa visibilidade (veículo precisa estar desligado)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reset Calibration</source>
|
||||
<translation>Resetar Calibragem</translation>
|
||||
<translation>Reinicializar Calibragem</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>RESET</source>
|
||||
@@ -200,7 +200,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required.</source>
|
||||
<translation>o openpilot requer que o dispositivo seja montado dentro de 4° esquerda ou direita e dentro de 5° para cima ou 8° para baixo. o openpilot está continuamente calibrando, resetar raramente é necessário.</translation>
|
||||
<translation>O openpilot requer que o dispositivo seja montado dentro de 4° esquerda ou direita e dentro de 5° para cima ou 8° para baixo. O openpilot está continuamente calibrando, resetar raramente é necessário.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source> Your device is pointed %1° %2 and %3° %4.</source>
|
||||
@@ -388,8 +388,8 @@
|
||||
<message>
|
||||
<source>Get turn-by-turn directions displayed and more with a comma
|
||||
prime subscription. Sign up now: https://connect.comma.ai</source>
|
||||
<translation>Obtenha instruções passo a passo exibidas e muito mais com
|
||||
uma assinatura prime Inscreva-se agora: https://connect.comma.ai</translation>
|
||||
<translation>Obtenha instruções passo a passo exibidas e muito mais com
|
||||
uma assinatura prime. Inscreva-se agora: https://connect.comma.ai</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No home
|
||||
@@ -502,7 +502,7 @@ trabalho definido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Become a comma prime member at connect.comma.ai</source>
|
||||
<translation>Torne-se um membro comma prime em connect.comma.ai</translation>
|
||||
<translation>Seja um membro comma prime em connect.comma.ai</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PRIME FEATURES:</source>
|
||||
@@ -514,11 +514,11 @@ trabalho definido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>1 year of storage</source>
|
||||
<translation>1 ano de armazenamento</translation>
|
||||
<translation>1 ano na nuvem</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Developer perks</source>
|
||||
<translation>Benefícios para desenvolvedor</translation>
|
||||
<translation>Benefícios para devs</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -835,7 +835,7 @@ trabalho definido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Current Version</source>
|
||||
<translation>Versao Atual</translation>
|
||||
<translation>Versão Atual</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Download</source>
|
||||
@@ -863,11 +863,11 @@ trabalho definido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>UNINSTALL</source>
|
||||
<translation>DESINSTAL</translation>
|
||||
<translation>REMOVER</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Uninstall %1</source>
|
||||
<translation>Desintalar o %1</translation>
|
||||
<translation>Desinstalar o %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Are you sure you want to uninstall?</source>
|
||||
@@ -987,7 +987,7 @@ trabalho definido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Disengage on Accelerator Pedal</source>
|
||||
<translation>Desacionar Com Pedal Do Acelerador</translation>
|
||||
<translation>Desacionar com Pedal do Acelerador</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>When enabled, pressing the accelerator pedal will disengage openpilot.</source>
|
||||
@@ -995,7 +995,7 @@ trabalho definido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show ETA in 24h Format</source>
|
||||
<translation>Mostrar ETA em formato 24h</translation>
|
||||
<translation>Mostrar ETA em Formato 24h</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use 24h format instead of am/pm</source>
|
||||
@@ -1021,14 +1021,6 @@ trabalho definido</translation>
|
||||
<source>On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control.</source>
|
||||
<translation>Neste carro o penpilot por padrão utiliza o ACC nativo do veículo ao invés de controlar longitudinalmente. Ative isto para mudar para o controle longitudinal do openpilot. Ativar o Modo Experimental é recomendado quando em uso do controle longitudinal experimental do openpilot.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control.</source>
|
||||
<translation>O Modo Experimental está atualmente indisponível para este carro, já que o ACC original do carro é usado para controle longitudinal.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable experimental longitudinal control to allow experimental mode.</source>
|
||||
<translation>Ative o controle longitudinal experimental para permitir o modo experimental.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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:</source>
|
||||
<translation>openpilot por padrão funciona em <b>modo chill</b>. modo Experimental ativa <b>recursos de nível-alfa</b> que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo:</translation>
|
||||
@@ -1049,6 +1041,22 @@ trabalho definido</translation>
|
||||
<source>The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.</source>
|
||||
<translation>A visualização da direção fará a transição para a câmera grande angular voltada para a estrada em baixas velocidades para mostrar melhor algumas curvas. O logotipo do modo Experimental também será exibido no canto superior direito.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.</source>
|
||||
<translation>O modo Experimental está atualmente indisponível para este carro já que o ACC original do carro é usado para controle longitudinal.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>openpilot longitudinal control may come in a future update.</source>
|
||||
<translation>O controle longitudinal openpilot pode vir em uma atualização futura.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches.</source>
|
||||
<translation>Uma versão experimental do controle longitudinal openpilot pode ser testada, juntamente com o modo Experimental, em branches de desenvolvimento.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable experimental longitudinal control to allow Experimental mode.</source>
|
||||
<translation>Ative o controle longitudinal experimental para permitir o modo Experimental.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Updater</name>
|
||||
|
||||
@@ -1015,14 +1015,6 @@ location set</source>
|
||||
<source>On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control.</source>
|
||||
<translation>针对此车辆,openpilot默认使用车辆自带的ACC,而非openpilot的纵向控制。启用此选项将切换到openpilot纵向控制。当使用试验性的openpilot纵向控制时,建议同时启用试验模式。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control.</source>
|
||||
<translation>由于此车辆使用自带的ACC纵向控制,当前无法使用试验模式。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable experimental longitudinal control to allow experimental mode.</source>
|
||||
<translation>启用试验性的纵向控制,以便允许使用试验模式。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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:</source>
|
||||
<translation>openpilot 默认 <b>轻松模式</b>驾驶车辆。试验模式启用一些轻松模式之外的 <b>试验性功能</b>。试验性功能包括:</translation>
|
||||
@@ -1043,6 +1035,22 @@ location set</source>
|
||||
<source>The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.</source>
|
||||
<translation>当低速行驶时,驾驶视角将切换到前向广角摄像头,便于更完整地显示转向路径。右上角将显示试验模式图标。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.</source>
|
||||
<translation>由于此车辆使用自带的ACC纵向控制,当前无法使用试验模式。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>openpilot longitudinal control may come in a future update.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable experimental longitudinal control to allow Experimental mode.</source>
|
||||
<translation>启用试验性的纵向控制,以便允许使用试验模式。</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Updater</name>
|
||||
|
||||
@@ -1017,14 +1017,6 @@ location set</source>
|
||||
<source>On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control.</source>
|
||||
<translation>在本車輛中,openpilot預設將使用原車內建的ACC系統,而非openpilot縱向控制。開啟此開關來啟用openpilot縱向控制,使用此選項時建議一併啟用實驗模式。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control.</source>
|
||||
<translation>因車輛使用內建ACC系統,無法在本車輛上啟動實驗模式。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable experimental longitudinal control to allow experimental mode.</source>
|
||||
<translation>啟用實驗性縱向控制以使用實驗模式。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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:</source>
|
||||
<translation>openpilot 預設以 <b>輕鬆模式</b> 駕駛。 實驗模式啟用了尚未準備好進入輕鬆模式的 <b>alpha 級功能</b>。實驗功能如下:</translation>
|
||||
@@ -1045,6 +1037,22 @@ location set</source>
|
||||
<source>The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.</source>
|
||||
<translation>低速行駛時,將會切換成路側廣角鏡頭,以完整顯示轉彎路徑,右上角將出現實驗模式圖案。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.</source>
|
||||
<translation>因車輛使用內建ACC系統,無法在本車輛上啟動實驗模式。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>openpilot longitudinal control may come in a future update.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable experimental longitudinal control to allow Experimental mode.</source>
|
||||
<translation>啟用實驗性縱向控制以使用實驗模式。</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Updater</name>
|
||||
|
||||
+1
-1
@@ -163,7 +163,7 @@ static void update_state(UIState *s) {
|
||||
scene.longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl();
|
||||
}
|
||||
if (sm.updated("wideRoadCameraState")) {
|
||||
float scale = (sm["wideRoadCameraState"].getWideRoadCameraState().getSensor() == cereal::FrameData::ImageSensor::AR0321) ? 6.0f : 1.0f;
|
||||
float scale = (sm["wideRoadCameraState"].getWideRoadCameraState().getSensor() == cereal::FrameData::ImageSensor::AR0231) ? 6.0f : 1.0f;
|
||||
scene.light_sensor = std::max(100.0f - scale * sm["wideRoadCameraState"].getWideRoadCameraState().getExposureValPercent(), 0.0f);
|
||||
}
|
||||
scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition;
|
||||
|
||||
@@ -167,7 +167,7 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr
|
||||
framed.setExposureValPercent(perc);
|
||||
|
||||
if (c->camera_id == CAMERA_ID_AR0231) {
|
||||
framed.setSensor(cereal::FrameData::ImageSensor::AR0321);
|
||||
framed.setSensor(cereal::FrameData::ImageSensor::AR0231);
|
||||
} else if (c->camera_id == CAMERA_ID_OX03C10) {
|
||||
framed.setSensor(cereal::FrameData::ImageSensor::OX03C10);
|
||||
}
|
||||
|
||||
@@ -60,15 +60,15 @@ CameraInfo cameras_supported[CAMERA_ID_MAX] = {
|
||||
const float DC_GAIN_AR0231 = 2.5;
|
||||
const float DC_GAIN_OX03C10 = 7.32;
|
||||
|
||||
const float DC_GAIN_ON_GREY_AR0231= 0.2;
|
||||
const float DC_GAIN_ON_GREY_AR0231 = 0.2;
|
||||
const float DC_GAIN_OFF_GREY_AR0231 = 0.3;
|
||||
const float DC_GAIN_ON_GREY_OX03C10= 0.25;
|
||||
const float DC_GAIN_OFF_GREY_OX03C10 = 0.35;
|
||||
const float DC_GAIN_ON_GREY_OX03C10 = 0.9;
|
||||
const float DC_GAIN_OFF_GREY_OX03C10 = 1.0;
|
||||
|
||||
const int DC_GAIN_MIN_WEIGHT_AR0231 = 0;
|
||||
const int DC_GAIN_MAX_WEIGHT_AR0231 = 1;
|
||||
const int DC_GAIN_MIN_WEIGHT_OX03C10 = 16;
|
||||
const int DC_GAIN_MAX_WEIGHT_OX03C10 = 32;
|
||||
const int DC_GAIN_MIN_WEIGHT_OX03C10 = 1; // always on is fine
|
||||
const int DC_GAIN_MAX_WEIGHT_OX03C10 = 1;
|
||||
|
||||
const float TARGET_GREY_FACTOR_AR0231 = 1.0;
|
||||
const float TARGET_GREY_FACTOR_OX03C10 = 0.02;
|
||||
@@ -1172,15 +1172,16 @@ void CameraState::set_camera_exposure(float grey_frac) {
|
||||
};
|
||||
sensors_i2c(exp_reg_array, sizeof(exp_reg_array)/sizeof(struct i2c_random_wr_payload), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, true);
|
||||
} else if (camera_id == CAMERA_ID_OX03C10) {
|
||||
// t_HCG + t_LCG + t_VS on LPD, t_SPD on SPD
|
||||
uint32_t hcg_time = std::max((dc_gain_weight * exposure_time / dc_gain_max_weight), 0);
|
||||
uint32_t lcg_time = std::max(((dc_gain_max_weight - dc_gain_weight) * exposure_time / dc_gain_max_weight), 0);
|
||||
// uint32_t spd_time = std::max(hcg_time / 16, (uint32_t)exposure_time_min);
|
||||
uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 128, VS_TIME_MIN_OX03C10), VS_TIME_MAX_OX03C10);
|
||||
uint32_t spd_time = vs_time;
|
||||
// t_HCG&t_LCG + t_VS on LPD, t_SPD on SPD
|
||||
uint32_t hcg_time = exposure_time;
|
||||
uint32_t lcg_time = hcg_time;
|
||||
uint32_t spd_time = exposure_time_max + VS_TIME_MAX_OX03C10;
|
||||
uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 40, VS_TIME_MIN_OX03C10), VS_TIME_MAX_OX03C10);
|
||||
|
||||
uint32_t real_gain = ox03c10_analog_gains_reg[new_exp_g];
|
||||
uint32_t min_gain = ox03c10_analog_gains_reg[0];
|
||||
uint32_t spd_gain = 0xF00;
|
||||
|
||||
struct i2c_random_wr_payload exp_reg_array[] = {
|
||||
{0x3501, hcg_time>>8}, {0x3502, hcg_time&0xFF},
|
||||
{0x3581, lcg_time>>8}, {0x3582, lcg_time&0xFF},
|
||||
@@ -1189,7 +1190,7 @@ void CameraState::set_camera_exposure(float grey_frac) {
|
||||
|
||||
{0x3508, real_gain>>8}, {0x3509, real_gain&0xFF},
|
||||
{0x3588, min_gain>>8}, {0x3589, min_gain&0xFF},
|
||||
{0x3548, min_gain>>8}, {0x3549, min_gain&0xFF},
|
||||
{0x3548, spd_gain>>8}, {0x3549, spd_gain&0xFF},
|
||||
{0x35c8, min_gain>>8}, {0x35c9, min_gain&0xFF},
|
||||
};
|
||||
sensors_i2c(exp_reg_array, sizeof(exp_reg_array)/sizeof(struct i2c_random_wr_payload), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, false);
|
||||
|
||||
@@ -126,7 +126,7 @@ struct i2c_random_wr_payload init_array_ox03c10[] = {
|
||||
{0x3219, 0x08},
|
||||
|
||||
{0x3506, 0x20}, {0x3507, 0x00}, // hcg fine exposure
|
||||
{0x350a, 0x04}, {0x350b, 0x00}, {0x350c, 0x00}, // hcg digital gain
|
||||
{0x350a, 0x01}, {0x350b, 0x00}, {0x350c, 0x00}, // hcg digital gain
|
||||
|
||||
{0x3586, 0x40}, {0x3587, 0x00}, // lcg fine exposure
|
||||
{0x358a, 0x01}, {0x358b, 0x00}, {0x358c, 0x00}, // lcg digital gain
|
||||
@@ -711,11 +711,11 @@ struct i2c_random_wr_payload init_array_ox03c10[] = {
|
||||
{0x4221, 0x03}, // this is changed from 1 -> 3
|
||||
|
||||
// DCG exposure coarse
|
||||
{0x3501, 0x01}, {0x3502, 0xc8},
|
||||
// {0x3501, 0x01}, {0x3502, 0xc8},
|
||||
// SPD exposure coarse
|
||||
{0x3541, 0x01}, {0x3542, 0xc8},
|
||||
// {0x3541, 0x01}, {0x3542, 0xc8},
|
||||
// VS exposure coarse
|
||||
{0x35c1, 0x00}, {0x35c2, 0x01},
|
||||
// {0x35c1, 0x00}, {0x35c2, 0x01},
|
||||
|
||||
// crc reference
|
||||
{0x420e, 0x66}, {0x420f, 0x5d}, {0x4210, 0xa8}, {0x4211, 0x55},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
import unittest
|
||||
import numpy as np
|
||||
from collections import defaultdict
|
||||
|
||||
import cereal.messaging as messaging
|
||||
@@ -10,8 +11,10 @@ from selfdrive.manager.process_config import managed_processes
|
||||
from system.hardware import TICI
|
||||
|
||||
TEST_TIMESPAN = 30
|
||||
LAG_FRAME_TOLERANCE = {log.FrameData.ImageSensor.ar0321: 0.5, # ARs use synced pulses for frame starts
|
||||
LAG_FRAME_TOLERANCE = {log.FrameData.ImageSensor.ar0231: 0.5, # ARs use synced pulses for frame starts
|
||||
log.FrameData.ImageSensor.ox03c10: 1.0} # OXs react to out-of-sync at next frame
|
||||
FRAME_DELTA_TOLERANCE = {log.FrameData.ImageSensor.ar0231: 1.0,
|
||||
log.FrameData.ImageSensor.ox03c10: 1.0}
|
||||
|
||||
CAMERAS = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState')
|
||||
|
||||
@@ -36,10 +39,16 @@ class TestCamerad(unittest.TestCase):
|
||||
managed_processes['camerad'].stop()
|
||||
|
||||
cls.log_by_frame_id = defaultdict(list)
|
||||
cls.sensor_type = None
|
||||
for cam, msgs in cls.logs.items():
|
||||
if cls.sensor_type is None:
|
||||
cls.sensor_type = getattr(msgs[0], msgs[0].which()).sensor.raw
|
||||
expected_frames = service_list[cam].frequency * TEST_TIMESPAN
|
||||
assert expected_frames*0.95 < len(msgs) < expected_frames*1.05, f"unexpected frame count {cam}: {expected_frames=}, got {len(msgs)}"
|
||||
|
||||
dts = np.abs(np.diff([getattr(m, m.which()).timestampSof/1e6 for m in msgs]) - 1000/service_list[cam].frequency)
|
||||
assert (dts < FRAME_DELTA_TOLERANCE[cls.sensor_type]).all(), f"{cam} dts(ms) out of spec: max diff {dts.max()}, 99 percentile {np.percentile(dts, 99)}"
|
||||
|
||||
for m in msgs:
|
||||
cls.log_by_frame_id[getattr(m, m.which()).frameId].append(m)
|
||||
|
||||
@@ -64,17 +73,16 @@ class TestCamerad(unittest.TestCase):
|
||||
assert len(skips) == 0, f"Found frame skips, missing cameras for the following frames: {skips}"
|
||||
|
||||
def test_frame_sync(self):
|
||||
sensor_type = [getattr(msgs[0], msgs[0].which()).sensor for frame_id, msgs in self.log_by_frame_id.items()][0].raw
|
||||
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 (diff, cam_times)
|
||||
laggy_frames = {k: get_desc(k, v) for k, v in diffs.items() if v > LAG_FRAME_TOLERANCE[sensor_type]}
|
||||
laggy_frames = {k: get_desc(k, v) for k, v in diffs.items() if v > LAG_FRAME_TOLERANCE[self.sensor_type]}
|
||||
|
||||
def in_tol(diff):
|
||||
return 50 - LAG_FRAME_TOLERANCE[sensor_type] < diff and diff < 50 + LAG_FRAME_TOLERANCE[sensor_type]
|
||||
return 50 - LAG_FRAME_TOLERANCE[self.sensor_type] < diff and diff < 50 + LAG_FRAME_TOLERANCE[self.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:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/icons/
|
||||
+1
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 1.0 MiB |
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
cd $DIR
|
||||
|
||||
if [ ! -d icons/ ]; then
|
||||
git clone https://github.com/twbs/icons/
|
||||
fi
|
||||
|
||||
cd icons
|
||||
git fetch --all
|
||||
git checkout d5aa187483a1b0b186f87adcfa8576350d970d98
|
||||
cp bootstrap-icons.svg ../
|
||||
+1
-1
Submodule tinygrad_repo updated: 8e22d5ee67...2e1d47b166
@@ -1,6 +1,6 @@
|
||||
import os
|
||||
Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', 'replay_lib',
|
||||
'cereal', 'transformations', 'widgets', 'opendbc')
|
||||
'cereal', 'transformations', 'widgets', 'opendbc', 'asset_obj')
|
||||
|
||||
base_frameworks = qt_env['FRAMEWORKS']
|
||||
base_libs = [common, messaging, cereal, visionipc, transformations, 'zmq',
|
||||
@@ -8,18 +8,21 @@ base_libs = [common, messaging, cereal, visionipc, transformations, 'zmq',
|
||||
|
||||
if arch == "Darwin":
|
||||
base_frameworks.append('OpenCL')
|
||||
base_frameworks.append('QtCharts')
|
||||
else:
|
||||
base_libs.append('OpenCL')
|
||||
base_libs.append('Qt5Charts')
|
||||
|
||||
qt_libs = ['qt_util'] + base_libs
|
||||
|
||||
qt_libs = ['qt_util', 'Qt5Charts'] + base_libs
|
||||
cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, opendbc,'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs
|
||||
cabana_env = qt_env.Clone()
|
||||
|
||||
prev_moc_path = cabana_env['QT_MOCHPREFIX']
|
||||
cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_'
|
||||
cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc',
|
||||
'canmessages.cc', 'commands.cc', 'messageswidget.cc', 'settings.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
|
||||
cabana_env.Program('_cabana', ['cabana.cc', cabana_lib], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
|
||||
cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc',
|
||||
'commands.cc', 'messageswidget.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
|
||||
cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, asset_obj], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
|
||||
|
||||
if GetOption('test'):
|
||||
cabana_env.Program('tests/_test_cabana', ['tests/test_runner.cc', 'tests/test_cabana.cc', cabana_lib], LIBS=[cabana_libs])
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
#include <QPainter>
|
||||
#include <QToolTip>
|
||||
|
||||
#include "tools/cabana/canmessages.h"
|
||||
#include "tools/cabana/streams/abstractstream.h"
|
||||
|
||||
// BinaryView
|
||||
|
||||
const int CELL_HEIGHT = 36;
|
||||
const int VERTICAL_HEADER_WIDTH = 30;
|
||||
|
||||
inline int get_bit_index(const QModelIndex &index, bool little_endian) {
|
||||
return index.row() * 8 + (little_endian ? 7 - index.column() : index.column());
|
||||
@@ -33,10 +34,20 @@ BinaryView::BinaryView(QWidget *parent) : QTableView(parent) {
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
}
|
||||
|
||||
QSize BinaryView::minimumSizeHint() const {
|
||||
return {horizontalHeader()->minimumSectionSize() * 9 + VERTICAL_HEADER_WIDTH + 2, 0};
|
||||
}
|
||||
|
||||
void BinaryView::highlight(const Signal *sig) {
|
||||
if (sig != hovered_sig) {
|
||||
for (int i = 0; i < model->items.size(); ++i) {
|
||||
auto &item_sigs = model->items[i].sigs;
|
||||
if ((sig && item_sigs.contains(sig)) || (hovered_sig && item_sigs.contains(hovered_sig))) {
|
||||
auto index = model->index(i / model->columnCount(), i % model->columnCount());
|
||||
emit model->dataChanged(index, index, {Qt::DisplayRole});
|
||||
}
|
||||
}
|
||||
hovered_sig = sig;
|
||||
model->dataChanged(model->index(0, 0), model->index(model->rowCount() - 1, model->columnCount() - 1));
|
||||
emit signalHovered(hovered_sig);
|
||||
}
|
||||
}
|
||||
@@ -177,7 +188,9 @@ void BinaryViewModel::setMessage(const QString &message_id) {
|
||||
|
||||
void BinaryViewModel::updateState() {
|
||||
auto prev_items = items;
|
||||
const auto &binary = can->lastMessage(msg_id).dat;
|
||||
const auto &last_msg = can->lastMessage(msg_id);
|
||||
const auto &binary = last_msg.dat;
|
||||
|
||||
// data size may changed.
|
||||
if (binary.size() > row_count) {
|
||||
beginInsertRows({}, row_count, binary.size() - 1);
|
||||
@@ -193,6 +206,7 @@ void BinaryViewModel::updateState() {
|
||||
hex[0] = toHex(binary[i] >> 4);
|
||||
hex[1] = toHex(binary[i] & 0xf);
|
||||
items[i * column_count + 8].val = hex;
|
||||
items[i * column_count + 8].bg_color = last_msg.colors[i];
|
||||
}
|
||||
for (int i = binary.size(); i < row_count; ++i) {
|
||||
for (int j = 0; j < column_count; ++j) {
|
||||
@@ -201,7 +215,7 @@ void BinaryViewModel::updateState() {
|
||||
}
|
||||
|
||||
for (int i = 0; i < row_count * column_count; ++i) {
|
||||
if (i >= prev_items.size() || prev_items[i].val != items[i].val) {
|
||||
if (i >= prev_items.size() || prev_items[i].val != items[i].val || prev_items[i].bg_color != items[i].bg_color) {
|
||||
auto idx = index(i / column_count, i % column_count);
|
||||
emit dataChanged(idx, idx);
|
||||
}
|
||||
@@ -212,7 +226,7 @@ QVariant BinaryViewModel::headerData(int section, Qt::Orientation orientation, i
|
||||
if (orientation == Qt::Vertical) {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole: return section;
|
||||
case Qt::SizeHintRole: return QSize(30, 0);
|
||||
case Qt::SizeHintRole: return QSize(VERTICAL_HEADER_WIDTH, 0);
|
||||
case Qt::TextAlignmentRole: return Qt::AlignCenter;
|
||||
}
|
||||
}
|
||||
@@ -234,12 +248,14 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
|
||||
|
||||
if (index.column() == 8) {
|
||||
painter->setFont(hex_font);
|
||||
painter->fillRect(option.rect, item->bg_color);
|
||||
} else if (option.state & QStyle::State_Selected) {
|
||||
painter->fillRect(option.rect, selection_color);
|
||||
painter->setPen(option.palette.color(QPalette::BrightText));
|
||||
} else if (!item->sigs.isEmpty() && (!bin_view->selectionModel()->hasSelection() || !item->sigs.contains(bin_view->resize_sig))) {
|
||||
painter->fillRect(option.rect, item->bg_color);
|
||||
painter->setPen(item->sigs.contains(bin_view->hovered_sig) ? option.palette.color(QPalette::BrightText) : Qt::black);
|
||||
bool sig_hovered = item->sigs.contains(bin_view->hovered_sig);
|
||||
painter->fillRect(option.rect, sig_hovered ? item->bg_color.darker(125) : item->bg_color); // 4/5x brightness
|
||||
painter->setPen(sig_hovered ? option.palette.color(QPalette::BrightText) : Qt::black);
|
||||
}
|
||||
|
||||
painter->drawText(option.rect, Qt::AlignCenter, item->val);
|
||||
|
||||
@@ -60,6 +60,7 @@ public:
|
||||
void highlight(const Signal *sig);
|
||||
QSet<const Signal*> getOverlappingSignals() const;
|
||||
inline void updateState() { model->updateState(); }
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
signals:
|
||||
void signalClicked(const Signal *sig);
|
||||
|
||||
+33
-16
@@ -4,12 +4,15 @@
|
||||
#include "common/prefix.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "tools/cabana/mainwin.h"
|
||||
#include "tools/cabana/streams/livestream.h"
|
||||
#include "tools/cabana/streams/replaystream.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
QCoreApplication::setApplicationName("Cabana");
|
||||
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
||||
initApp(argc, argv);
|
||||
QApplication app(argc, argv);
|
||||
app.setApplicationDisplayName("Cabana");
|
||||
|
||||
QCommandLineParser cmd_parser;
|
||||
cmd_parser.addHelpOption();
|
||||
@@ -17,29 +20,43 @@ int main(int argc, char *argv[]) {
|
||||
cmd_parser.addOption({"demo", "use a demo route instead of providing your own"});
|
||||
cmd_parser.addOption({"qcam", "load qcamera"});
|
||||
cmd_parser.addOption({"ecam", "load wide road camera"});
|
||||
cmd_parser.addOption({"stream", "read can messages from live streaming"});
|
||||
cmd_parser.addOption({"zmq", "the ip address on which to receive zmq messages", "zmq"});
|
||||
cmd_parser.addOption({"data_dir", "local directory with routes", "data_dir"});
|
||||
cmd_parser.addOption({"no-vipc", "do not output video"});
|
||||
cmd_parser.process(app);
|
||||
const QStringList args = cmd_parser.positionalArguments();
|
||||
if (args.empty() && !cmd_parser.isSet("demo")) {
|
||||
if (args.empty() && !cmd_parser.isSet("demo") && !cmd_parser.isSet("stream")) {
|
||||
cmd_parser.showHelp();
|
||||
}
|
||||
|
||||
std::unique_ptr<OpenpilotPrefix> op_prefix;
|
||||
std::unique_ptr<AbstractStream> stream;
|
||||
|
||||
const QString route = args.empty() ? DEMO_ROUTE : args.first();
|
||||
uint32_t replay_flags = REPLAY_FLAG_NONE;
|
||||
if (cmd_parser.isSet("ecam")) {
|
||||
replay_flags |= REPLAY_FLAG_ECAM;
|
||||
} else if (cmd_parser.isSet("qcam")) {
|
||||
replay_flags |= REPLAY_FLAG_QCAMERA;
|
||||
if (cmd_parser.isSet("stream")) {
|
||||
stream.reset(new LiveStream(&app, cmd_parser.value("zmq")));
|
||||
} else {
|
||||
// TODO: Remove when OpenpilotPrefix supports ZMQ
|
||||
#ifndef __APPLE__
|
||||
op_prefix.reset(new OpenpilotPrefix());
|
||||
#endif
|
||||
const QString route = args.empty() ? DEMO_ROUTE : args.first();
|
||||
uint32_t replay_flags = REPLAY_FLAG_NONE;
|
||||
if (cmd_parser.isSet("ecam")) {
|
||||
replay_flags |= REPLAY_FLAG_ECAM;
|
||||
} else if (cmd_parser.isSet("qcam")) {
|
||||
replay_flags |= REPLAY_FLAG_QCAMERA;
|
||||
} else if (cmd_parser.isSet("no-vipc")) {
|
||||
replay_flags |= REPLAY_FLAG_NO_VIPC;
|
||||
}
|
||||
auto replay_stream = new ReplayStream(&app);
|
||||
stream.reset(replay_stream);
|
||||
if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
OpenpilotPrefix op_prefix;
|
||||
CANMessages p(&app);
|
||||
int ret = 0;
|
||||
if (p.loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) {
|
||||
MainWindow w;
|
||||
w.show();
|
||||
ret = app.exec();
|
||||
}
|
||||
return ret;
|
||||
MainWindow w;
|
||||
w.show();
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
#include "tools/cabana/canmessages.h"
|
||||
#include "tools/cabana/dbcmanager.h"
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
CANMessages::~CANMessages() {
|
||||
replay->stop();
|
||||
}
|
||||
|
||||
static bool event_filter(const Event *e, void *opaque) {
|
||||
CANMessages *c = (CANMessages *)opaque;
|
||||
return c->eventFilter(e);
|
||||
}
|
||||
|
||||
bool CANMessages::loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags) {
|
||||
replay = new Replay(route, {"can", "roadEncodeIdx", "wideRoadEncodeIdx", "carParams"}, {}, nullptr, replay_flags, data_dir, this);
|
||||
replay->setSegmentCacheLimit(settings.cached_segment_limit);
|
||||
replay->installEventFilter(event_filter, this);
|
||||
QObject::connect(replay, &Replay::seekedTo, this, &CANMessages::seekedTo);
|
||||
QObject::connect(replay, &Replay::segmentsMerged, this, &CANMessages::eventsMerged);
|
||||
QObject::connect(replay, &Replay::streamStarted, this, &CANMessages::streamStarted);
|
||||
if (replay->load()) {
|
||||
const auto &segments = replay->route()->segments();
|
||||
if (std::none_of(segments.begin(), segments.end(), [](auto &s) { return s.second.rlog.length() > 0; })) {
|
||||
qWarning() << "no rlogs in route" << route;
|
||||
return false;
|
||||
}
|
||||
replay->start();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CANMessages::process(QHash<QString, CanData> *messages) {
|
||||
for (auto it = messages->begin(); it != messages->end(); ++it) {
|
||||
can_msgs[it.key()] = it.value();
|
||||
}
|
||||
emit updated();
|
||||
emit msgsReceived(messages);
|
||||
delete messages;
|
||||
processing = false;
|
||||
}
|
||||
|
||||
bool CANMessages::eventFilter(const Event *event) {
|
||||
static std::unique_ptr new_msgs = std::make_unique<QHash<QString, CanData>>();
|
||||
static double prev_update_ts = 0;
|
||||
|
||||
if (event->which == cereal::Event::Which::CAN) {
|
||||
double current_sec = replay->currentSeconds();
|
||||
if (counters_begin_sec == 0 || counters_begin_sec >= current_sec) {
|
||||
new_msgs->clear();
|
||||
counters.clear();
|
||||
counters_begin_sec = current_sec;
|
||||
}
|
||||
|
||||
auto can_events = event->event.getCan();
|
||||
for (const auto &c : can_events) {
|
||||
QString id = QString("%1:%2").arg(c.getSrc()).arg(c.getAddress(), 1, 16);
|
||||
CanData &data = (*new_msgs)[id];
|
||||
data.ts = current_sec;
|
||||
data.dat = QByteArray((char *)c.getDat().begin(), c.getDat().size());
|
||||
data.count = ++counters[id];
|
||||
if (double delta = (current_sec - counters_begin_sec); delta > 0) {
|
||||
data.freq = data.count / delta;
|
||||
}
|
||||
}
|
||||
|
||||
double ts = millis_since_boot();
|
||||
if ((ts - prev_update_ts) > (1000.0 / settings.fps) && !processing && !new_msgs->isEmpty()) {
|
||||
// delay posting CAN message if UI thread is busy
|
||||
processing = true;
|
||||
prev_update_ts = ts;
|
||||
// use pointer to avoid data copy in queued connection.
|
||||
emit received(new_msgs.release());
|
||||
new_msgs.reset(new QHash<QString, CanData>);
|
||||
new_msgs->reserve(100);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CANMessages::seekTo(double ts) {
|
||||
replay->seekTo(std::max(double(0), ts), false);
|
||||
counters_begin_sec = 0;
|
||||
emit updated();
|
||||
}
|
||||
|
||||
void CANMessages::pause(bool pause) {
|
||||
replay->pause(pause);
|
||||
emit (pause ? paused() : resume());
|
||||
}
|
||||
|
||||
void CANMessages::settingChanged() {
|
||||
replay->setSegmentCacheLimit(settings.cached_segment_limit);
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <QColor>
|
||||
#include <QHash>
|
||||
|
||||
#include "opendbc/can/common_dbc.h"
|
||||
#include "tools/cabana/settings.h"
|
||||
#include "tools/replay/replay.h"
|
||||
|
||||
struct CanData {
|
||||
double ts = 0.;
|
||||
uint32_t count = 0;
|
||||
uint32_t freq = 0;
|
||||
QByteArray dat;
|
||||
};
|
||||
|
||||
class CANMessages : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CANMessages(QObject *parent);
|
||||
~CANMessages();
|
||||
bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE);
|
||||
void seekTo(double ts);
|
||||
bool eventFilter(const Event *event);
|
||||
|
||||
inline QString routeName() const { return replay->route()->name(); }
|
||||
inline QString carFingerprint() const { return replay->carFingerprint().c_str(); }
|
||||
inline VisionStreamType visionStreamType() const { return replay->hasFlag(REPLAY_FLAG_ECAM) ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD; }
|
||||
inline double totalSeconds() const { return replay->totalSeconds(); }
|
||||
inline double routeStartTime() const { return replay->routeStartTime() / (double)1e9; }
|
||||
inline double currentSec() const { return replay->currentSeconds(); }
|
||||
inline const CanData &lastMessage(const QString &id) { return can_msgs[id]; }
|
||||
|
||||
inline const Route* route() const { return replay->route(); }
|
||||
inline const std::vector<Event *> *events() const { return replay->events(); }
|
||||
inline void setSpeed(float speed) { replay->setSpeed(speed); }
|
||||
inline bool isPaused() const { return replay->isPaused(); }
|
||||
void pause(bool pause);
|
||||
inline const std::vector<std::tuple<int, int, TimelineType>> getTimeline() { return replay->getTimeline(); }
|
||||
|
||||
signals:
|
||||
void paused();
|
||||
void resume();
|
||||
void seekedTo(double sec);
|
||||
void streamStarted();
|
||||
void eventsMerged();
|
||||
void updated();
|
||||
void msgsReceived(const QHash<QString, CanData> *);
|
||||
void received(QHash<QString, CanData> *);
|
||||
|
||||
public:
|
||||
QMap<QString, CanData> can_msgs;
|
||||
|
||||
protected:
|
||||
void process(QHash<QString, CanData> *);
|
||||
void settingChanged();
|
||||
|
||||
Replay *replay = nullptr;
|
||||
std::atomic<double> counters_begin_sec = 0;
|
||||
std::atomic<bool> processing = false;
|
||||
QHash<QString, uint32_t> counters;
|
||||
};
|
||||
|
||||
inline QString toHex(const QByteArray &dat) {
|
||||
return dat.toHex(' ').toUpper();
|
||||
}
|
||||
inline char toHex(uint value) {
|
||||
return "0123456789ABCDEF"[value & 0xF];
|
||||
}
|
||||
|
||||
inline const QString &getColor(int i) {
|
||||
// TODO: add more colors
|
||||
static const QString SIGNAL_COLORS[] = {"#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF", "#FF7F50", "#FFBF00"};
|
||||
return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)];
|
||||
}
|
||||
|
||||
// A global pointer referring to the unique CANMessages object
|
||||
extern CANMessages *can;
|
||||
+249
-131
@@ -2,39 +2,64 @@
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCompleter>
|
||||
#include <QLineEdit>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDrag>
|
||||
#include <QFutureSynchronizer>
|
||||
#include <QGraphicsLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QRubberBand>
|
||||
#include <QToolBar>
|
||||
#include <QToolButton>
|
||||
#include <QToolTip>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
|
||||
// ChartsWidget
|
||||
|
||||
ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
// toolbar
|
||||
QToolBar *toolbar = new QToolBar(tr("Charts"), this);
|
||||
title_label = new QLabel();
|
||||
title_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
toolbar->addWidget(title_label);
|
||||
show_all_values_btn = toolbar->addAction("");
|
||||
toolbar->addWidget(range_label = new QLabel());
|
||||
reset_zoom_btn = toolbar->addAction("⟲");
|
||||
toolbar->setIconSize({16, 16});
|
||||
|
||||
QAction *new_plot_btn = toolbar->addAction(bootstrapPixmap("file-plus"), "");
|
||||
new_plot_btn->setToolTip(tr("New Plot"));
|
||||
toolbar->addWidget(title_label = new QLabel());
|
||||
title_label->setContentsMargins(0, 0, 12, 0);
|
||||
columns_cb = new QComboBox(this);
|
||||
columns_cb->addItems({"1", "2", "3", "4"});
|
||||
toolbar->addWidget(new QLabel(tr("Columns:")));
|
||||
toolbar->addWidget(columns_cb);
|
||||
|
||||
QLabel *stretch_label = new QLabel(this);
|
||||
stretch_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
toolbar->addWidget(stretch_label);
|
||||
|
||||
toolbar->addWidget(new QLabel(tr("Range:")));
|
||||
toolbar->addWidget(range_lb = new QLabel(this));
|
||||
range_slider = new QSlider(Qt::Horizontal, this);
|
||||
range_slider->setToolTip(tr("Set the chart range"));
|
||||
range_slider->setRange(1, settings.max_cached_minutes * 60);
|
||||
range_slider->setSingleStep(1);
|
||||
range_slider->setPageStep(60); // 1 min
|
||||
toolbar->addWidget(range_slider);
|
||||
|
||||
toolbar->addWidget(zoom_range_lb = new QLabel());
|
||||
reset_zoom_btn = toolbar->addAction(bootstrapPixmap("arrow-counterclockwise"), "");
|
||||
reset_zoom_btn->setToolTip(tr("Reset zoom (drag on chart to zoom X-Axis)"));
|
||||
remove_all_btn = toolbar->addAction("✖");
|
||||
remove_all_btn = toolbar->addAction(bootstrapPixmap("x"), "");
|
||||
remove_all_btn->setToolTip(tr("Remove all charts"));
|
||||
dock_btn = toolbar->addAction("");
|
||||
main_layout->addWidget(toolbar);
|
||||
|
||||
// charts
|
||||
QWidget *charts_container = new QWidget(this);
|
||||
charts_layout = new QVBoxLayout(charts_container);
|
||||
charts_layout->addStretch();
|
||||
QVBoxLayout *charts_main_layout = new QVBoxLayout(charts_container);
|
||||
charts_main_layout->setContentsMargins(0, 0, 0, 0);
|
||||
charts_main_layout->addLayout(charts_layout = new QGridLayout);
|
||||
charts_main_layout->addStretch(0);
|
||||
|
||||
QScrollArea *charts_scroll = new QScrollArea(this);
|
||||
charts_scroll->setWidgetResizable(true);
|
||||
@@ -42,21 +67,29 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
|
||||
charts_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
main_layout->addWidget(charts_scroll);
|
||||
|
||||
max_chart_range = settings.max_chart_x_range;
|
||||
use_dark_theme = QApplication::style()->standardPalette().color(QPalette::WindowText).value() >
|
||||
QApplication::style()->standardPalette().color(QPalette::Background).value();
|
||||
updateToolBar();
|
||||
|
||||
align_charts_timer = new QTimer(this);
|
||||
align_charts_timer->setSingleShot(true);
|
||||
align_charts_timer->callOnTimeout(this, &ChartsWidget::alignCharts);
|
||||
|
||||
// init settings
|
||||
use_dark_theme = QApplication::style()->standardPalette().color(QPalette::WindowText).value() >
|
||||
QApplication::style()->standardPalette().color(QPalette::Background).value();
|
||||
column_count = std::clamp(settings.chart_column_count, 1, columns_cb->count());
|
||||
max_chart_range = std::clamp(settings.chart_range, 1, settings.max_cached_minutes * 60);
|
||||
display_range = {0, max_chart_range};
|
||||
columns_cb->setCurrentIndex(column_count - 1);
|
||||
range_slider->setValue(max_chart_range);
|
||||
updateToolBar();
|
||||
|
||||
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll);
|
||||
QObject::connect(can, &CANMessages::eventsMerged, this, &ChartsWidget::eventsMerged);
|
||||
QObject::connect(can, &CANMessages::updated, this, &ChartsWidget::updateState);
|
||||
QObject::connect(show_all_values_btn, &QAction::triggered, this, &ChartsWidget::showAllData);
|
||||
QObject::connect(can, &AbstractStream::eventsMerged, this, &ChartsWidget::eventsMerged);
|
||||
QObject::connect(can, &AbstractStream::updated, this, &ChartsWidget::updateState);
|
||||
QObject::connect(range_slider, &QSlider::valueChanged, this, &ChartsWidget::setMaxChartRange);
|
||||
QObject::connect(new_plot_btn, &QAction::triggered, this, &ChartsWidget::newChart);
|
||||
QObject::connect(remove_all_btn, &QAction::triggered, this, &ChartsWidget::removeAll);
|
||||
QObject::connect(reset_zoom_btn, &QAction::triggered, this, &ChartsWidget::zoomReset);
|
||||
QObject::connect(columns_cb, SIGNAL(activated(int)), SLOT(setColumnCount(int)));
|
||||
QObject::connect(&settings, &Settings::changed, this, &ChartsWidget::settingChanged);
|
||||
QObject::connect(dock_btn, &QAction::triggered, [this]() {
|
||||
emit dock(!docking);
|
||||
docking = !docking;
|
||||
@@ -65,27 +98,15 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
|
||||
}
|
||||
|
||||
void ChartsWidget::eventsMerged() {
|
||||
if (auto events = can->events(); events && !events->empty()) {
|
||||
event_range.first = (events->front()->mono_time / (double)1e9) - can->routeStartTime();
|
||||
event_range.second = (events->back()->mono_time / (double)1e9) - can->routeStartTime();
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
|
||||
void ChartsWidget::updateDisplayRange() {
|
||||
auto prev_range = display_range;
|
||||
double current_sec = can->currentSec();
|
||||
if (current_sec < display_range.first || current_sec >= (display_range.second - 5)) {
|
||||
// reached the end, or seeked to a timestamp out of range.
|
||||
display_range.first = current_sec - 5;
|
||||
}
|
||||
display_range.first = std::floor(std::max(display_range.first, event_range.first) * 10.0) / 10.0;
|
||||
display_range.second = std::floor(std::min(display_range.first + max_chart_range, event_range.second) * 10.0) / 10.0;
|
||||
if (prev_range != display_range) {
|
||||
{
|
||||
assert(!can->liveStreaming());
|
||||
QFutureSynchronizer<void> future_synchronizer;
|
||||
for (auto c : charts)
|
||||
future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::setEventsRange, display_range));
|
||||
const auto events = can->events();
|
||||
for (auto c : charts) {
|
||||
future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, nullptr, events, true));
|
||||
}
|
||||
}
|
||||
updateState();
|
||||
}
|
||||
|
||||
void ChartsWidget::zoomIn(double min, double max) {
|
||||
@@ -103,42 +124,62 @@ void ChartsWidget::zoomReset() {
|
||||
void ChartsWidget::updateState() {
|
||||
if (charts.isEmpty()) return;
|
||||
|
||||
if (can->liveStreaming()) {
|
||||
// appends incoming events to the end of series
|
||||
const auto events = can->events();
|
||||
for (auto c : charts) {
|
||||
c->updateSeries(nullptr, events, false);
|
||||
}
|
||||
}
|
||||
|
||||
const double cur_sec = can->currentSec();
|
||||
if (!is_zoomed) {
|
||||
updateDisplayRange();
|
||||
} else if (can->currentSec() < zoomed_range.first || can->currentSec() >= zoomed_range.second) {
|
||||
double pos = (cur_sec - display_range.first) / max_chart_range;
|
||||
if (pos < 0 || pos > 0.8) {
|
||||
const double min_event_sec = (can->events()->front()->mono_time / (double)1e9) - can->routeStartTime();
|
||||
display_range.first = std::floor(std::max(min_event_sec, cur_sec - max_chart_range * 0.2));
|
||||
}
|
||||
display_range.second = std::floor(display_range.first + max_chart_range);
|
||||
} else if (cur_sec < zoomed_range.first || cur_sec >= zoomed_range.second) {
|
||||
// loop in zoommed range
|
||||
can->seekTo(zoomed_range.first);
|
||||
}
|
||||
|
||||
const auto &range = is_zoomed ? zoomed_range : display_range;
|
||||
setUpdatesEnabled(false);
|
||||
const auto &range = is_zoomed ? zoomed_range : display_range;
|
||||
for (auto c : charts) {
|
||||
c->setDisplayRange(range.first, range.second);
|
||||
c->scene()->invalidate({}, QGraphicsScene::ForegroundLayer);
|
||||
c->updatePlot(cur_sec, range.first, range.second);
|
||||
}
|
||||
setUpdatesEnabled(true);
|
||||
}
|
||||
|
||||
void ChartsWidget::showAllData() {
|
||||
bool switch_to_show_all = max_chart_range == settings.max_chart_x_range;
|
||||
max_chart_range = switch_to_show_all ? settings.cached_segment_limit * 60
|
||||
: settings.max_chart_x_range;
|
||||
max_chart_range = std::min(max_chart_range, (uint32_t)can->totalSeconds());
|
||||
void ChartsWidget::setMaxChartRange(int value) {
|
||||
max_chart_range = settings.chart_range = value;
|
||||
double current_sec = can->currentSec();
|
||||
const double min_event_sec = (can->events()->front()->mono_time / (double)1e9) - can->routeStartTime();
|
||||
// keep current_sec's pos
|
||||
double pos = (current_sec - display_range.first) / (display_range.second - display_range.first);
|
||||
display_range.first = std::floor(std::max(min_event_sec, current_sec - max_chart_range * (1.0 - pos)));
|
||||
display_range.second = std::floor(display_range.first + max_chart_range);
|
||||
updateToolBar();
|
||||
updateState();
|
||||
}
|
||||
|
||||
void ChartsWidget::updateToolBar() {
|
||||
int min_range = std::min(settings.max_chart_x_range, (int)can->totalSeconds());
|
||||
bool displaying_all = max_chart_range != min_range;
|
||||
show_all_values_btn->setText(tr("%1 minutes").arg(max_chart_range / 60));
|
||||
show_all_values_btn->setToolTip(tr("Click to display %1 data").arg(displaying_all ? tr("%1 minutes").arg(min_range / 60) : tr("ALL cached")));
|
||||
show_all_values_btn->setVisible(!is_zoomed);
|
||||
range_lb->setText(QString(" %1:%2 ").arg(max_chart_range / 60, 2, 10, QLatin1Char('0')).arg(max_chart_range % 60, 2, 10, QLatin1Char('0')));
|
||||
zoom_range_lb->setText(is_zoomed ? tr("Zooming: %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()));
|
||||
dock_btn->setIcon(bootstrapPixmap(docking ? "arrow-up-right" : "arrow-down-left"));
|
||||
dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts"));
|
||||
remove_all_btn->setEnabled(!charts.isEmpty());
|
||||
reset_zoom_btn->setEnabled(is_zoomed);
|
||||
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"));
|
||||
}
|
||||
|
||||
void ChartsWidget::settingChanged() {
|
||||
range_slider->setRange(1, settings.max_cached_minutes * 60);
|
||||
for (auto c : charts) {
|
||||
c->setFixedHeight(settings.chart_height);
|
||||
}
|
||||
}
|
||||
|
||||
ChartView *ChartsWidget::findChart(const QString &id, const Signal *sig) {
|
||||
@@ -147,27 +188,30 @@ ChartView *ChartsWidget::findChart(const QString &id, const Signal *sig) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ChartView *ChartsWidget::createChart() {
|
||||
auto chart = new ChartView(this);
|
||||
chart->setFixedHeight(settings.chart_height);
|
||||
chart->setMinimumWidth(CHART_MIN_WIDTH);
|
||||
chart->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
chart->chart()->setTheme(use_dark_theme ? QChart::QChart::ChartThemeDark : QChart::ChartThemeLight);
|
||||
QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); });
|
||||
QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn);
|
||||
QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset);
|
||||
QObject::connect(chart, &ChartView::seriesRemoved, this, &ChartsWidget::seriesChanged);
|
||||
QObject::connect(chart, &ChartView::seriesAdded, this, &ChartsWidget::seriesChanged);
|
||||
QObject::connect(chart, &ChartView::axisYUpdated, [this]() { align_charts_timer->start(100); });
|
||||
charts.push_back(chart);
|
||||
updateLayout();
|
||||
return chart;
|
||||
}
|
||||
|
||||
void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show, bool merge) {
|
||||
setUpdatesEnabled(false);
|
||||
ChartView *chart = findChart(id, sig);
|
||||
if (show && !chart) {
|
||||
chart = merge && charts.size() > 0 ? charts.back() : nullptr;
|
||||
if (!chart) {
|
||||
chart = new ChartView(this);
|
||||
chart->chart()->setTheme(use_dark_theme ? QChart::QChart::ChartThemeDark : QChart::ChartThemeLight);
|
||||
chart->setEventsRange(display_range);
|
||||
auto range = is_zoomed ? zoomed_range : display_range;
|
||||
chart->setDisplayRange(range.first, range.second);
|
||||
QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); });
|
||||
QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn);
|
||||
QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset);
|
||||
QObject::connect(chart, &ChartView::seriesRemoved, this, &ChartsWidget::seriesChanged);
|
||||
QObject::connect(chart, &ChartView::seriesAdded, this, &ChartsWidget::seriesChanged);
|
||||
QObject::connect(chart, &ChartView::axisYUpdated, [this]() { align_charts_timer->start(100); });
|
||||
charts_layout->insertWidget(0, chart);
|
||||
charts.push_back(chart);
|
||||
}
|
||||
chart = merge && charts.size() > 0 ? charts.back() : createChart();
|
||||
chart->addSeries(id, sig);
|
||||
updateState();
|
||||
} else if (!show && chart) {
|
||||
chart->removeSeries(id, sig);
|
||||
}
|
||||
@@ -175,11 +219,46 @@ void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show, bo
|
||||
setUpdatesEnabled(true);
|
||||
}
|
||||
|
||||
void ChartsWidget::setColumnCount(int n) {
|
||||
n = std::clamp(n + 1, 1, columns_cb->count());
|
||||
if (column_count != n) {
|
||||
column_count = settings.chart_column_count = n;
|
||||
updateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
void ChartsWidget::updateLayout() {
|
||||
int n = column_count;
|
||||
for (; n > 1; --n) {
|
||||
if ((n * (CHART_MIN_WIDTH + charts_layout->spacing())) < rect().width()) break;
|
||||
}
|
||||
for (int i = 0; i < charts.size(); ++i) {
|
||||
charts_layout->addWidget(charts[charts.size() - i - 1], i / n, i % n);
|
||||
}
|
||||
}
|
||||
|
||||
void ChartsWidget::resizeEvent(QResizeEvent *event) {
|
||||
QWidget::resizeEvent(event);
|
||||
updateLayout();
|
||||
}
|
||||
|
||||
void ChartsWidget::newChart() {
|
||||
SeriesSelector dlg(this);
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
QList<QStringList> series_list = dlg.series();
|
||||
if (!series_list.isEmpty()) {
|
||||
auto c = createChart();
|
||||
c->addSeries(series_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChartsWidget::removeChart(ChartView *chart) {
|
||||
charts.removeOne(chart);
|
||||
chart->deleteLater();
|
||||
updateToolBar();
|
||||
alignCharts();
|
||||
updateLayout();
|
||||
emit seriesChanged();
|
||||
}
|
||||
|
||||
@@ -221,9 +300,10 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
|
||||
chart->addAxis(axis_y, Qt::AlignLeft);
|
||||
chart->legend()->setShowToolTips(true);
|
||||
chart->layout()->setContentsMargins(0, 0, 0, 0);
|
||||
chart->setMargins({20, 11, 11, 11});
|
||||
|
||||
QToolButton *remove_btn = new QToolButton();
|
||||
remove_btn->setText("X");
|
||||
remove_btn->setIcon(bootstrapPixmap("x"));
|
||||
remove_btn->setAutoRaise(true);
|
||||
remove_btn->setToolTip(tr("Remove Chart"));
|
||||
close_btn_proxy = new QGraphicsProxyWidget(chart);
|
||||
@@ -231,7 +311,7 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
|
||||
close_btn_proxy->setZValue(chart->zValue() + 11);
|
||||
|
||||
QToolButton *manage_btn = new QToolButton();
|
||||
manage_btn->setText("🔧");
|
||||
manage_btn->setIcon(bootstrapPixmap("gear"));
|
||||
manage_btn->setAutoRaise(true);
|
||||
manage_btn->setToolTip(tr("Manage series"));
|
||||
manage_btn_proxy = new QGraphicsProxyWidget(chart);
|
||||
@@ -240,19 +320,21 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
|
||||
|
||||
setChart(chart);
|
||||
setRenderHint(QPainter::Antialiasing);
|
||||
setRubberBand(QChartView::HorizontalRubberBand);
|
||||
updateFromSettings();
|
||||
// TODO: enable zoomIn/seekTo in live streaming mode.
|
||||
setRubberBand(can->liveStreaming() ? QChartView::NoRubberBand : QChartView::HorizontalRubberBand);
|
||||
|
||||
QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartView::signalRemoved);
|
||||
QObject::connect(dbc(), &DBCManager::signalUpdated, this, &ChartView::signalUpdated);
|
||||
QObject::connect(dbc(), &DBCManager::msgRemoved, this, &ChartView::msgRemoved);
|
||||
QObject::connect(dbc(), &DBCManager::msgUpdated, this, &ChartView::msgUpdated);
|
||||
QObject::connect(&settings, &Settings::changed, this, &ChartView::updateFromSettings);
|
||||
QObject::connect(remove_btn, &QToolButton::clicked, this, &ChartView::remove);
|
||||
QObject::connect(manage_btn, &QToolButton::clicked, this, &ChartView::manageSeries);
|
||||
}
|
||||
|
||||
qreal ChartView::getYAsixLabelWidth() const {
|
||||
if (axis_y->max() <= axis_y->min() || axis_y->tickCount() <= 1) {
|
||||
return 0;
|
||||
}
|
||||
QFontMetrics fm(axis_y->labelsFont());
|
||||
int n = qMax(int(-qFloor(std::log10((axis_y->max() - axis_y->min()) / (axis_y->tickCount() - 1)))), 0) + 1;
|
||||
return qMax(fm.width(QString::number(axis_y->min(), 'f', n)), fm.width(QString::number(axis_y->max(), 'f', n))) + 20;
|
||||
@@ -267,7 +349,12 @@ void ChartView::setPlotAreaLeftPosition(int pos) {
|
||||
|
||||
void ChartView::addSeries(const QString &msg_id, const Signal *sig) {
|
||||
QLineSeries *series = new QLineSeries(this);
|
||||
|
||||
// TODO: Due to a bug in CameraWidget the camera frames
|
||||
// are drawn instead of the graphs on MacOS. Re-enable OpenGL when fixed
|
||||
#ifndef __APPLE__
|
||||
series->setUseOpenGL(true);
|
||||
#endif
|
||||
chart()->addSeries(series);
|
||||
series->attachAxis(axis_x);
|
||||
series->attachAxis(axis_y);
|
||||
@@ -275,7 +362,6 @@ void ChartView::addSeries(const QString &msg_id, const Signal *sig) {
|
||||
sigs.push_back({.msg_id = msg_id, .address = address, .source = source, .sig = sig, .series = series});
|
||||
updateTitle();
|
||||
updateSeries(sig);
|
||||
updateAxisY();
|
||||
emit seriesAdded(msg_id, sig);
|
||||
}
|
||||
|
||||
@@ -310,7 +396,6 @@ void ChartView::signalUpdated(const Signal *sig) {
|
||||
updateTitle();
|
||||
// TODO: don't update series if only name changed.
|
||||
updateSeries(sig);
|
||||
updateAxisY();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,6 +416,17 @@ void ChartView::msgRemoved(uint32_t address) {
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::addSeries(const QList<QStringList> &series_list) {
|
||||
for (auto &s : series_list) {
|
||||
if (auto m = dbc()->msg(s[0])) {
|
||||
auto it = m->sigs.find(s[2]);
|
||||
if (it != m->sigs.end() && !hasSeries(s[0], &(it->second))) {
|
||||
addSeries(s[0], &(it->second));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::manageSeries() {
|
||||
SeriesSelector dlg(this);
|
||||
for (auto &s : sigs) {
|
||||
@@ -343,14 +439,7 @@ void ChartView::manageSeries() {
|
||||
if (series_list.isEmpty()) {
|
||||
emit remove();
|
||||
} else {
|
||||
for (auto &s : series_list) {
|
||||
if (auto m = dbc()->msg(s[0])) {
|
||||
auto it = m->sigs.find(s[2]);
|
||||
if (it != m->sigs.end() && !hasSeries(s[0], &(it->second))) {
|
||||
addSeries(s[0], &(it->second));
|
||||
}
|
||||
}
|
||||
}
|
||||
addSeries(series_list);
|
||||
for (auto it = sigs.begin(); it != sigs.end(); /**/) {
|
||||
bool exists = std::any_of(series_list.cbegin(), series_list.cend(), [&](auto &s) {
|
||||
return s[0] == it->msg_id && s[2] == it->sig->name.c_str();
|
||||
@@ -374,41 +463,28 @@ void ChartView::updateTitle() {
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::updateFromSettings() {
|
||||
setFixedHeight(settings.chart_height);
|
||||
}
|
||||
|
||||
void ChartView::setEventsRange(const std::pair<double, double> &range) {
|
||||
if (range != events_range) {
|
||||
events_range = range;
|
||||
updateSeries();
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::setDisplayRange(double min, double max) {
|
||||
void ChartView::updatePlot(double cur, double min, double max) {
|
||||
cur_sec = cur;
|
||||
if (min != axis_x->min() || max != axis_x->max()) {
|
||||
axis_x->setRange(min, max);
|
||||
updateAxisY();
|
||||
}
|
||||
scene()->invalidate({}, QGraphicsScene::ForegroundLayer);
|
||||
}
|
||||
|
||||
void ChartView::updateSeries(const Signal *sig) {
|
||||
auto events = can->events();
|
||||
if (!events || sigs.isEmpty()) return;
|
||||
|
||||
void ChartView::updateSeries(const Signal *sig, const std::vector<Event *> *events, bool clear) {
|
||||
events = events ? events : can->events();
|
||||
for (auto &s : sigs) {
|
||||
if (!sig || s.sig == sig) {
|
||||
s.vals.clear();
|
||||
s.vals.reserve((events_range.second - events_range.first) * 1000); // [n]seconds * 1000hz
|
||||
s.min_y = std::numeric_limits<double>::max();
|
||||
s.max_y = std::numeric_limits<double>::lowest();
|
||||
|
||||
if (clear) {
|
||||
s.vals.clear();
|
||||
s.vals.reserve(settings.max_cached_minutes * 60 * 100); // [n]seconds * 100hz
|
||||
s.last_value_mono_time = 0;
|
||||
}
|
||||
double route_start_time = can->routeStartTime();
|
||||
Event begin_event(cereal::Event::Which::INIT_DATA, (route_start_time + events_range.first) * 1e9);
|
||||
auto begin = std::lower_bound(events->begin(), events->end(), &begin_event, Event::lessThan());
|
||||
double end_ns = (route_start_time + events_range.second) * 1e9;
|
||||
|
||||
for (auto it = begin; it != events->end() && (*it)->mono_time <= end_ns; ++it) {
|
||||
Event begin_event(cereal::Event::Which::INIT_DATA, s.last_value_mono_time);
|
||||
auto begin = std::upper_bound(events->begin(), events->end(), &begin_event, Event::lessThan());
|
||||
for (auto it = begin; it != events->end(); ++it) {
|
||||
if ((*it)->which == cereal::Event::Which::CAN) {
|
||||
for (const auto &c : (*it)->event.getCan()) {
|
||||
if (s.source == c.getSrc() && s.address == c.getAddress()) {
|
||||
@@ -416,14 +492,15 @@ void ChartView::updateSeries(const Signal *sig) {
|
||||
double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *s.sig);
|
||||
double ts = ((*it)->mono_time / (double)1e9) - route_start_time; // seconds
|
||||
s.vals.push_back({ts, value});
|
||||
|
||||
if (value < s.min_y) s.min_y = value;
|
||||
if (value > s.max_y) s.max_y = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (events->size()) {
|
||||
s.last_value_mono_time = events->back()->mono_time;
|
||||
}
|
||||
s.series->replace(s.vals);
|
||||
updateAxisY();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -434,18 +511,11 @@ void ChartView::updateAxisY() {
|
||||
|
||||
double min_y = std::numeric_limits<double>::max();
|
||||
double max_y = std::numeric_limits<double>::lowest();
|
||||
if (events_range == std::pair{axis_x->min(), axis_x->max()}) {
|
||||
for (auto &s : sigs) {
|
||||
if (s.min_y < min_y) min_y = s.min_y;
|
||||
if (s.max_y > max_y) max_y = s.max_y;
|
||||
}
|
||||
} else {
|
||||
for (auto &s : sigs) {
|
||||
auto begin = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; });
|
||||
for (auto it = begin; it != s.vals.end() && it->x() <= axis_x->max(); ++it) {
|
||||
if (it->y() < min_y) min_y = it->y();
|
||||
if (it->y() > max_y) max_y = it->y();
|
||||
}
|
||||
for (auto &s : sigs) {
|
||||
auto begin = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; });
|
||||
for (auto it = begin; it != s.vals.end() && it->x() <= axis_x->max(); ++it) {
|
||||
if (it->y() < min_y) min_y = it->y();
|
||||
if (it->y() > max_y) max_y = it->y();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -495,6 +565,24 @@ void ChartView::leaveEvent(QEvent *event) {
|
||||
QChartView::leaveEvent(event);
|
||||
}
|
||||
|
||||
void ChartView::mousePressEvent(QMouseEvent *event) {
|
||||
if (event->button() == Qt::LeftButton && !chart()->plotArea().contains(event->pos()) &&
|
||||
!manage_btn_proxy->widget()->underMouse() && !close_btn_proxy->widget()->underMouse()) {
|
||||
QMimeData *mimeData = new QMimeData;
|
||||
mimeData->setData(mime_type, QByteArray::number((qulonglong)this));
|
||||
QDrag *drag = new QDrag(this);
|
||||
drag->setMimeData(mimeData);
|
||||
drag->setPixmap(grab());
|
||||
drag->setHotSpot(event->pos());
|
||||
Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::MoveAction);
|
||||
if (dropAction == Qt::MoveAction) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
QChartView::mousePressEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::mouseReleaseEvent(QMouseEvent *event) {
|
||||
auto rubber = findChild<QRubberBand *>();
|
||||
if (event->button() == Qt::LeftButton && rubber && rubber->isVisible()) {
|
||||
@@ -510,7 +598,7 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
|
||||
emit zoomIn(min, max);
|
||||
}
|
||||
event->accept();
|
||||
} else if (event->button() == Qt::RightButton) {
|
||||
} else if (!can->liveStreaming() && event->button() == Qt::RightButton) {
|
||||
emit zoomReset();
|
||||
event->accept();
|
||||
} else {
|
||||
@@ -550,8 +638,38 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
|
||||
QChartView::mouseMoveEvent(ev);
|
||||
}
|
||||
|
||||
void ChartView::dragMoveEvent(QDragMoveEvent *event) {
|
||||
if (event->mimeData()->hasFormat(mime_type)) {
|
||||
event->setDropAction(event->source() == this ? Qt::MoveAction : Qt::CopyAction);
|
||||
event->accept();
|
||||
} else {
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::dropEvent(QDropEvent *event) {
|
||||
if (event->mimeData()->hasFormat(mime_type)) {
|
||||
if (event->source() == this) {
|
||||
event->setDropAction(Qt::MoveAction);
|
||||
event->accept();
|
||||
} else {
|
||||
ChartView *source_chart = (ChartView *)event->source();
|
||||
QList<QStringList> series;
|
||||
for (auto &s : source_chart->sigs) {
|
||||
series.push_back({s.msg_id, msgName(s.msg_id), QString::fromStdString(s.sig->name)});
|
||||
}
|
||||
addSeries(series);
|
||||
emit source_chart->remove();
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
} else {
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::drawForeground(QPainter *painter, const QRectF &rect) {
|
||||
qreal x = chart()->mapToPosition(QPointF{can->currentSec(), 0}).x();
|
||||
qreal x = chart()->mapToPosition(QPointF{cur_sec, 0}).x();
|
||||
x = std::clamp(x, chart()->plotArea().left(), chart()->plotArea().right());
|
||||
qreal y1 = chart()->plotArea().top() - 2;
|
||||
qreal y2 = chart()->plotArea().bottom() + 2;
|
||||
painter->setPen(QPen(chart()->titleBrush().color(), 2));
|
||||
|
||||
+29
-16
@@ -1,18 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDragEnterEvent>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QGraphicsProxyWidget>
|
||||
#include <QSlider>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
#include <QtCharts/QChartView>
|
||||
#include <QtCharts/QLineSeries>
|
||||
#include <QtCharts/QValueAxis>
|
||||
|
||||
#include "tools/cabana/canmessages.h"
|
||||
#include "tools/cabana/dbcmanager.h"
|
||||
#include "tools/cabana/streams/abstractstream.h"
|
||||
|
||||
using namespace QtCharts;
|
||||
|
||||
@@ -22,11 +23,11 @@ class ChartView : public QChartView {
|
||||
public:
|
||||
ChartView(QWidget *parent = nullptr);
|
||||
void addSeries(const QString &msg_id, const Signal *sig);
|
||||
void addSeries(const QList<QStringList> &series_list);
|
||||
void removeSeries(const QString &msg_id, const Signal *sig);
|
||||
bool hasSeries(const QString &msg_id, const Signal *sig) const;
|
||||
void updateSeries(const Signal *sig = nullptr);
|
||||
void setEventsRange(const std::pair<double, double> &range);
|
||||
void setDisplayRange(double min, double max);
|
||||
void updateSeries(const Signal *sig = nullptr, const std::vector<Event*> *events = nullptr, bool clear = true);
|
||||
void updatePlot(double cur, double min, double max);
|
||||
void setPlotAreaLeftPosition(int pos);
|
||||
qreal getYAsixLabelWidth() const;
|
||||
|
||||
@@ -36,9 +37,8 @@ public:
|
||||
uint32_t address = 0;
|
||||
const Signal *sig = nullptr;
|
||||
QLineSeries *series = nullptr;
|
||||
double min_y = 0;
|
||||
double max_y = 0;
|
||||
QVector<QPointF> vals;
|
||||
uint64_t last_value_mono_time = 0;
|
||||
};
|
||||
|
||||
signals:
|
||||
@@ -58,13 +58,15 @@ private slots:
|
||||
|
||||
private:
|
||||
QList<ChartView::SigItem>::iterator removeSeries(const QList<ChartView::SigItem>::iterator &it);
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *ev) override;
|
||||
void dragMoveEvent(QDragMoveEvent *event) override;
|
||||
void dropEvent(QDropEvent *event) override;
|
||||
void leaveEvent(QEvent *event) override;
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void updateAxisY();
|
||||
void updateTitle();
|
||||
void updateFromSettings();
|
||||
void drawForeground(QPainter *painter, const QRectF &rect) override;
|
||||
void applyNiceNumbers(qreal min, qreal max);
|
||||
qreal niceNumber(qreal x, bool ceiling);
|
||||
@@ -74,8 +76,9 @@ private:
|
||||
QPointF track_pt;
|
||||
QGraphicsProxyWidget *close_btn_proxy;
|
||||
QGraphicsProxyWidget *manage_btn_proxy;
|
||||
std::pair<double, double> events_range = {0, 0};
|
||||
QList<SigItem> sigs;
|
||||
double cur_sec = 0;
|
||||
const QString mime_type = "application/x-cabanachartview";
|
||||
};
|
||||
|
||||
class ChartsWidget : public QWidget {
|
||||
@@ -86,41 +89,51 @@ public:
|
||||
void showChart(const QString &id, const Signal *sig, bool show, bool merge);
|
||||
inline bool hasSignal(const QString &id, const Signal *sig) { return findChart(id, sig) != nullptr; }
|
||||
|
||||
public slots:
|
||||
void setColumnCount(int n);
|
||||
|
||||
signals:
|
||||
void dock(bool floating);
|
||||
void rangeChanged(double min, double max, bool is_zommed);
|
||||
void seriesChanged();
|
||||
|
||||
private:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void alignCharts();
|
||||
void newChart();
|
||||
ChartView * createChart();
|
||||
void removeChart(ChartView *chart);
|
||||
void eventsMerged();
|
||||
void updateState();
|
||||
void updateDisplayRange();
|
||||
void zoomIn(double min, double max);
|
||||
void zoomReset();
|
||||
void updateToolBar();
|
||||
void removeAll();
|
||||
void showAllData();
|
||||
void setMaxChartRange(int value);
|
||||
void updateLayout();
|
||||
void settingChanged();
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
ChartView *findChart(const QString &id, const Signal *sig);
|
||||
|
||||
QLabel *title_label;
|
||||
QLabel *range_label;
|
||||
QLabel *zoom_range_lb;
|
||||
QLabel *range_lb;
|
||||
QSlider *range_slider;
|
||||
bool docking = true;
|
||||
QAction *show_all_values_btn;
|
||||
QAction *dock_btn;
|
||||
QAction *reset_zoom_btn;
|
||||
QAction *remove_all_btn;
|
||||
QTimer *align_charts_timer;
|
||||
QVBoxLayout *charts_layout;
|
||||
QGridLayout *charts_layout;
|
||||
QList<ChartView *> charts;
|
||||
uint32_t max_chart_range = 0;
|
||||
bool is_zoomed = false;
|
||||
std::pair<double, double> event_range;
|
||||
std::pair<double, double> display_range;
|
||||
std::pair<double, double> zoomed_range;
|
||||
bool use_dark_theme = false;
|
||||
QComboBox *columns_cb;
|
||||
int column_count = 1;
|
||||
const int CHART_MIN_WIDTH = 300;
|
||||
};
|
||||
|
||||
class SeriesSelector : public QDialog {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <QUndoCommand>
|
||||
|
||||
#include "tools/cabana/canmessages.h"
|
||||
#include "tools/cabana/dbcmanager.h"
|
||||
|
||||
class EditMsgCommand : public QUndoCommand {
|
||||
|
||||
@@ -28,9 +28,7 @@ public:
|
||||
|
||||
static std::pair<uint8_t, uint32_t> parseId(const QString &id);
|
||||
inline static std::vector<std::string> allDBCNames() { return get_dbc_names(); }
|
||||
inline std::map<uint32_t, DBCMsg> &allMsgs() { return msgs; }
|
||||
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 std::map<uint32_t, DBCMsg> &messages() const { return msgs; }
|
||||
|
||||
@@ -8,19 +8,17 @@
|
||||
#include <QTimer>
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "tools/cabana/canmessages.h"
|
||||
#include "tools/cabana/commands.h"
|
||||
#include "tools/cabana/dbcmanager.h"
|
||||
#include "tools/cabana/streams/abstractstream.h"
|
||||
|
||||
// DetailWidget
|
||||
|
||||
DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(charts), QWidget(parent) {
|
||||
undo_stack = new QUndoStack(this);
|
||||
setMinimumWidth(500);
|
||||
QWidget *main_widget = new QWidget(this);
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(main_widget);
|
||||
main_layout->setContentsMargins(0, 0, 0, 0);
|
||||
main_layout->setSpacing(0);
|
||||
|
||||
// tabbar
|
||||
tabbar = new QTabBar(this);
|
||||
@@ -30,12 +28,9 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
|
||||
tabbar->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
main_layout->addWidget(tabbar);
|
||||
|
||||
QFrame *title_frame = new QFrame(this);
|
||||
QVBoxLayout *frame_layout = new QVBoxLayout(title_frame);
|
||||
title_frame->setFrameShape(QFrame::StyledPanel);
|
||||
|
||||
// message title
|
||||
toolbar = new QToolBar(this);
|
||||
toolbar->setIconSize({16, 16});
|
||||
toolbar->addWidget(new QLabel("time:"));
|
||||
time_label = new QLabel(this);
|
||||
time_label->setStyleSheet("font-weight:bold");
|
||||
@@ -45,24 +40,18 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
|
||||
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);
|
||||
toolbar->addAction(bootstrapPixmap("pencil"), "", this, &DetailWidget::editMsg)->setToolTip(tr("Edit Message"));
|
||||
remove_msg_act = toolbar->addAction(bootstrapPixmap("x-lg"), "", this, &DetailWidget::removeMsg);
|
||||
remove_msg_act->setToolTip(tr("Remove Message"));
|
||||
toolbar->setVisible(false);
|
||||
frame_layout->addWidget(toolbar);
|
||||
main_layout->addWidget(toolbar);
|
||||
|
||||
// warning
|
||||
warning_widget = new QWidget(this);
|
||||
QHBoxLayout *warning_hlayout = new QHBoxLayout(warning_widget);
|
||||
warning_hlayout->setContentsMargins(0, 0, 0, 0);
|
||||
QLabel *warning_icon = new QLabel(this);
|
||||
warning_icon->setPixmap(style()->standardPixmap(QStyle::SP_MessageBoxWarning).scaledToWidth(24, Qt::SmoothTransformation));
|
||||
warning_hlayout->addWidget(warning_icon, 0, Qt::AlignTop);
|
||||
warning_label = new QLabel(this);
|
||||
warning_hlayout->addWidget(warning_label, 1, Qt::AlignLeft);
|
||||
warning_hlayout->addWidget(warning_icon = new QLabel(this), 0, Qt::AlignTop);
|
||||
warning_hlayout->addWidget(warning_label = new QLabel(this), 1, Qt::AlignLeft);
|
||||
warning_widget->hide();
|
||||
frame_layout->addWidget(warning_widget);
|
||||
main_layout->addWidget(title_frame);
|
||||
main_layout->addWidget(warning_widget);
|
||||
|
||||
// msg widget
|
||||
QWidget *msg_widget = new QWidget(this);
|
||||
@@ -85,9 +74,9 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
|
||||
|
||||
tab_widget = new QTabWidget(this);
|
||||
tab_widget->setTabPosition(QTabWidget::South);
|
||||
tab_widget->addTab(scroll, "&Msg");
|
||||
tab_widget->addTab(scroll, bootstrapPixmap("file-earmark-ruled"), "&Msg");
|
||||
history_log = new LogsWidget(this);
|
||||
tab_widget->addTab(history_log, "&Logs");
|
||||
tab_widget->addTab(history_log, bootstrapPixmap("stopwatch"), "&Logs");
|
||||
main_layout->addWidget(tab_widget);
|
||||
|
||||
stacked_layout = new QStackedLayout(this);
|
||||
@@ -98,7 +87,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
|
||||
QObject::connect(binary_view, &BinaryView::resizeSignal, this, &DetailWidget::resizeSignal);
|
||||
QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal);
|
||||
QObject::connect(tab_widget, &QTabWidget::currentChanged, [this]() { updateState(); });
|
||||
QObject::connect(can, &CANMessages::msgsReceived, this, &DetailWidget::updateState);
|
||||
QObject::connect(can, &AbstractStream::msgsReceived, this, &DetailWidget::updateState);
|
||||
QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { dbcMsgChanged(); });
|
||||
QObject::connect(tabbar, &QTabBar::customContextMenuRequested, this, &DetailWidget::showTabBarContextMenu);
|
||||
QObject::connect(tabbar, &QTabBar::currentChanged, [this](int index) {
|
||||
@@ -174,8 +163,14 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) {
|
||||
form->setChartOpened(charts->hasSignal(msg_id, sig));
|
||||
++i;
|
||||
}
|
||||
if (msg->size != can->lastMessage(msg_id).dat.size())
|
||||
if (msg->size != can->lastMessage(msg_id).dat.size()) {
|
||||
warnings.push_back(tr("Message size (%1) is incorrect.").arg(msg->size));
|
||||
}
|
||||
for (auto s : binary_view->getOverlappingSignals()) {
|
||||
warnings.push_back(tr("%1 has overlapping bits.").arg(s->name.c_str()));
|
||||
}
|
||||
} else {
|
||||
warnings.push_back(tr("Drag-Select in binary view to create new signal."));
|
||||
}
|
||||
for (/**/; i < signal_list.size(); ++i)
|
||||
signal_list[i]->hide();
|
||||
@@ -184,10 +179,10 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) {
|
||||
remove_msg_act->setEnabled(msg != nullptr);
|
||||
name_label->setText(msgName(msg_id));
|
||||
|
||||
for (auto s : binary_view->getOverlappingSignals())
|
||||
warnings.push_back(tr("%1 has overlapping bits.").arg(s->name.c_str()));
|
||||
|
||||
warning_label->setText(warnings.join('\n'));
|
||||
if (!warnings.isEmpty()) {
|
||||
warning_label->setText(warnings.join('\n'));
|
||||
warning_icon->setPixmap(bootstrapPixmap(msg ? "exclamation-triangle" : "info-circle"));
|
||||
}
|
||||
warning_widget->setVisible(!warnings.isEmpty());
|
||||
setUpdatesEnabled(true);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ public:
|
||||
DetailWidget(ChartsWidget *charts, QWidget *parent);
|
||||
void setMessage(const QString &message_id);
|
||||
void dbcMsgChanged(int show_form_idx = -1);
|
||||
QSize minimumSizeHint() const override { return binary_view->minimumSizeHint(); }
|
||||
QUndoStack *undo_stack = nullptr;
|
||||
|
||||
private:
|
||||
@@ -46,7 +47,7 @@ private:
|
||||
void updateState(const QHash<QString, CanData> * msgs = nullptr);
|
||||
|
||||
QString msg_id;
|
||||
QLabel *name_label, *time_label, *warning_label;
|
||||
QLabel *name_label, *time_label, *warning_icon, *warning_label;
|
||||
QWidget *warning_widget;
|
||||
QVBoxLayout *signals_layout;
|
||||
QTabBar *tabbar;
|
||||
|
||||
+93
-89
@@ -1,6 +1,5 @@
|
||||
#include "tools/cabana/historylog.h"
|
||||
|
||||
#include <QFontDatabase>
|
||||
#include <QPainter>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
@@ -8,16 +7,15 @@
|
||||
// HistoryLogModel
|
||||
|
||||
QVariant HistoryLogModel::data(const QModelIndex &index, int role) const {
|
||||
const bool show_signals = display_signals_mode && sigs.size() > 0;
|
||||
const auto &m = messages[index.row()];
|
||||
if (role == Qt::DisplayRole) {
|
||||
const auto &m = messages[index.row()];
|
||||
if (index.column() == 0) {
|
||||
return QString::number((m.mono_time / (double)1e9) - can->routeStartTime(), 'f', 2);
|
||||
}
|
||||
return !sigs.empty() ? QString::number(m.sig_values[index.column() - 1]) : m.data;
|
||||
} else if (role == Qt::FontRole && index.column() == 1 && sigs.empty()) {
|
||||
return QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
||||
} else if (role == Qt::ToolTipRole && index.column() > 0 && !sigs.empty()) {
|
||||
return tr("double click to open the chart");
|
||||
return show_signals ? QString::number(m.sig_values[index.column() - 1]) : toHex(m.data);
|
||||
} else if (role == Qt::UserRole && index.column() == 1 && !show_signals) {
|
||||
return HexColors::toVariantList(m.colors);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -36,21 +34,21 @@ void HistoryLogModel::refresh() {
|
||||
beginResetModel();
|
||||
last_fetch_time = 0;
|
||||
messages.clear();
|
||||
hex_colors.clear();
|
||||
updateState();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||
if (orientation == Qt::Horizontal) {
|
||||
const bool show_signals = display_signals_mode && !sigs.empty();
|
||||
if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
|
||||
if (section == 0) {
|
||||
return "Time";
|
||||
}
|
||||
return !sigs.empty() ? QString::fromStdString(sigs[section - 1]->name).replace('_', ' ') : "Data";
|
||||
} else if (role == Qt::BackgroundRole && section > 0 && !sigs.empty()) {
|
||||
return show_signals ? QString::fromStdString(sigs[section - 1]->name).replace('_', ' ') : "Data";
|
||||
} else if (role == Qt::BackgroundRole && section > 0 && show_signals) {
|
||||
return QBrush(QColor(getColor(section - 1)));
|
||||
} else if (role == Qt::ForegroundRole && section > 0 && !sigs.empty()) {
|
||||
return QBrush(Qt::black);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
@@ -61,6 +59,11 @@ void HistoryLogModel::setDynamicMode(int state) {
|
||||
refresh();
|
||||
}
|
||||
|
||||
void HistoryLogModel::setDisplayType(int type) {
|
||||
display_signals_mode = type == 0;
|
||||
refresh();
|
||||
}
|
||||
|
||||
void HistoryLogModel::segmentsMerged() {
|
||||
if (!dynamic_mode) {
|
||||
has_more_data = true;
|
||||
@@ -81,6 +84,7 @@ void HistoryLogModel::updateState() {
|
||||
if ((has_more_data = !new_msgs.empty())) {
|
||||
beginInsertRows({}, 0, new_msgs.size() - 1);
|
||||
messages.insert(messages.begin(), std::move_iterator(new_msgs.begin()), std::move_iterator(new_msgs.end()));
|
||||
updateColors();
|
||||
endInsertRows();
|
||||
}
|
||||
last_fetch_time = current_time;
|
||||
@@ -93,11 +97,29 @@ void HistoryLogModel::fetchMore(const QModelIndex &parent) {
|
||||
if ((has_more_data = !new_msgs.empty())) {
|
||||
beginInsertRows({}, messages.size(), messages.size() + new_msgs.size() - 1);
|
||||
messages.insert(messages.end(), std::move_iterator(new_msgs.begin()), std::move_iterator(new_msgs.end()));
|
||||
if (!dynamic_mode) {
|
||||
updateColors();
|
||||
}
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryLogModel::updateColors() {
|
||||
if (!display_signals_mode || sigs.empty()) {
|
||||
const auto freq = can->lastMessage(msg_id).freq;
|
||||
if (dynamic_mode) {
|
||||
for (auto it = messages.rbegin(); it != messages.rend(); ++it) {
|
||||
it->colors = hex_colors.compute(it->data, it->mono_time / (double)1e9, freq);
|
||||
}
|
||||
} else {
|
||||
for (auto it = messages.begin(); it != messages.end(); ++it) {
|
||||
it->colors = hex_colors.compute(it->data, it->mono_time / (double)1e9, freq);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class InputIt>
|
||||
std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(InputIt first, InputIt last, uint64_t min_time) {
|
||||
std::deque<HistoryLogModel::Message> msgs;
|
||||
@@ -114,7 +136,7 @@ std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(InputIt first, I
|
||||
if (!filter_cmp || filter_cmp(values[filter_sig_idx], filter_value)) {
|
||||
auto &m = msgs.emplace_back();
|
||||
m.mono_time = (*it)->mono_time;
|
||||
m.data = toHex(QByteArray((char *)dat.begin(), dat.size()));
|
||||
m.data = QByteArray((char *)dat.begin(), dat.size());
|
||||
m.sig_values = values;
|
||||
if (msgs.size() >= batch_size && min_time == 0)
|
||||
return msgs;
|
||||
@@ -125,16 +147,16 @@ std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(InputIt first, I
|
||||
}
|
||||
return msgs;
|
||||
}
|
||||
|
||||
template std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData<>(std::vector<const Event*>::iterator first, std::vector<const Event*>::iterator last, uint64_t min_time);
|
||||
template std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData<>(std::vector<const Event*>::reverse_iterator first, std::vector<const Event*>::reverse_iterator last, uint64_t min_time);
|
||||
|
||||
std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(uint64_t from_time, uint64_t min_time) {
|
||||
auto events = can->events();
|
||||
if (dynamic_mode) {
|
||||
auto it = std::lower_bound(events->rbegin(), events->rend(), from_time, [=](auto &e, uint64_t ts) {
|
||||
return e->mono_time > ts;
|
||||
auto it = std::upper_bound(events->rbegin(), events->rend(), from_time, [=](uint64_t ts, auto &e) {
|
||||
return e->mono_time < ts;
|
||||
});
|
||||
if (it != events->rend()) ++it;
|
||||
return fetchData(it, events->rend(), min_time);
|
||||
} else {
|
||||
assert(min_time == 0);
|
||||
@@ -148,120 +170,102 @@ std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(uint64_t from_ti
|
||||
// HeaderView
|
||||
|
||||
QSize HeaderView::sectionSizeFromContents(int logicalIndex) const {
|
||||
int default_size = qMax(100, rect().width() / model()->columnCount());
|
||||
const QString text = model()->headerData(logicalIndex, this->orientation(), Qt::DisplayRole).toString();
|
||||
const QRect rect = fontMetrics().boundingRect({0, 0, default_size, 2000}, defaultAlignment(), text);
|
||||
QSize size = rect.size() + QSize{10, 6};
|
||||
return {qMax(size.width(), default_size), size.height()};
|
||||
static QSize time_col_size = fontMetrics().boundingRect({0, 0, 200, 200}, defaultAlignment(), "000000.000").size() + QSize(10, 6);
|
||||
if (logicalIndex == 0) {
|
||||
return time_col_size;
|
||||
} else {
|
||||
int default_size = qMax(100, (rect().width() - time_col_size.width()) / (model()->columnCount() - 1));
|
||||
const QString text = model()->headerData(logicalIndex, this->orientation(), Qt::DisplayRole).toString();
|
||||
const QRect rect = fontMetrics().boundingRect({0, 0, default_size, 2000}, defaultAlignment(), text);
|
||||
QSize size = rect.size() + QSize{10, 6};
|
||||
return QSize{qMax(size.width(), default_size), size.height()};
|
||||
}
|
||||
}
|
||||
|
||||
void HeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const {
|
||||
auto bg_role = model()->headerData(logicalIndex, Qt::Horizontal, Qt::BackgroundRole);
|
||||
if (bg_role.isValid()) {
|
||||
QPen pen(model()->headerData(logicalIndex, Qt::Horizontal, Qt::ForegroundRole).value<QBrush>(), 1);
|
||||
painter->setPen(pen);
|
||||
painter->fillRect(rect, bg_role.value<QBrush>());
|
||||
}
|
||||
QString text = model()->headerData(logicalIndex, Qt::Horizontal, Qt::DisplayRole).toString();
|
||||
painter->drawText(rect.adjusted(5, 3, -5, -3), defaultAlignment(), text);
|
||||
}
|
||||
|
||||
// HistoryLog
|
||||
|
||||
HistoryLog::HistoryLog(QWidget *parent) : QTableView(parent) {
|
||||
setHorizontalHeader(new HeaderView(Qt::Horizontal, this));
|
||||
horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | (Qt::Alignment)Qt::TextWordWrap);
|
||||
horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
verticalHeader()->setVisible(false);
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||
}
|
||||
|
||||
// LogsWidget
|
||||
|
||||
LogsWidget::LogsWidget(QWidget *parent) : QWidget(parent) {
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->setContentsMargins(0, 0, 0, 0);
|
||||
main_layout->setSpacing(0);
|
||||
|
||||
QHBoxLayout *h = new QHBoxLayout();
|
||||
signals_cb = new QComboBox(this);
|
||||
signals_cb->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
|
||||
h->addWidget(signals_cb);
|
||||
comp_box = new QComboBox();
|
||||
QWidget *toolbar = new QWidget(this);
|
||||
toolbar->setAutoFillBackground(true);
|
||||
QHBoxLayout *h = new QHBoxLayout(toolbar);
|
||||
|
||||
filters_widget = new QWidget(this);
|
||||
QHBoxLayout *filter_layout = new QHBoxLayout(filters_widget);
|
||||
filter_layout->setContentsMargins(0, 0, 0, 0);
|
||||
filter_layout->addWidget(display_type_cb = new QComboBox(this));
|
||||
filter_layout->addWidget(signals_cb = new QComboBox(this));
|
||||
filter_layout->addWidget(comp_box = new QComboBox(this));
|
||||
filter_layout->addWidget(value_edit = new QLineEdit(this));
|
||||
h->addWidget(filters_widget);
|
||||
h->addStretch(0);
|
||||
h->addWidget(dynamic_mode = new QCheckBox(tr("Dynamic")), 0, Qt::AlignRight);
|
||||
|
||||
display_type_cb->addItems({"Signal Value", "Hex Value"});
|
||||
comp_box->addItems({">", "=", "!=", "<"});
|
||||
h->addWidget(comp_box);
|
||||
value_edit = new QLineEdit(this);
|
||||
value_edit->setClearButtonEnabled(true);
|
||||
value_edit->setValidator(new QDoubleValidator(-500000, 500000, 6, this));
|
||||
h->addWidget(value_edit);
|
||||
dynamic_mode = new QCheckBox(tr("Dynamic"));
|
||||
h->addWidget(dynamic_mode, 0, Qt::AlignRight);
|
||||
main_layout->addLayout(h);
|
||||
dynamic_mode->setChecked(true);
|
||||
dynamic_mode->setEnabled(!can->liveStreaming());
|
||||
|
||||
model = new HistoryLogModel(this);
|
||||
logs = new HistoryLog(this);
|
||||
logs->setModel(model);
|
||||
main_layout->addWidget(logs);
|
||||
main_layout->addWidget(toolbar);
|
||||
QFrame *line = new QFrame(this);
|
||||
line->setFrameStyle(QFrame::HLine | QFrame::Sunken);
|
||||
main_layout->addWidget(line);;
|
||||
|
||||
QObject::connect(logs, &QTableView::doubleClicked, this, &LogsWidget::doubleClicked);
|
||||
main_layout->addWidget(logs = new QTableView(this));
|
||||
logs->setModel(model = new HistoryLogModel(this));
|
||||
logs->setItemDelegateForColumn(1, new MessageBytesDelegate(this));
|
||||
logs->setHorizontalHeader(new HeaderView(Qt::Horizontal, this));
|
||||
logs->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | (Qt::Alignment)Qt::TextWordWrap);
|
||||
logs->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
logs->verticalHeader()->setVisible(false);
|
||||
logs->setFrameShape(QFrame::NoFrame);
|
||||
|
||||
QObject::connect(display_type_cb, SIGNAL(activated(int)), model, SLOT(setDisplayType(int)));
|
||||
QObject::connect(dynamic_mode, &QCheckBox::stateChanged, model, &HistoryLogModel::setDynamicMode);
|
||||
QObject::connect(signals_cb, SIGNAL(activated(int)), this, SLOT(setFilter()));
|
||||
QObject::connect(comp_box, SIGNAL(activated(int)), this, SLOT(setFilter()));
|
||||
QObject::connect(value_edit, &QLineEdit::textChanged, this, &LogsWidget::setFilter);
|
||||
QObject::connect(dynamic_mode, &QCheckBox::stateChanged, model, &HistoryLogModel::setDynamicMode);
|
||||
QObject::connect(can, &CANMessages::seekedTo, model, &HistoryLogModel::refresh);
|
||||
QObject::connect(can, &CANMessages::eventsMerged, model, &HistoryLogModel::segmentsMerged);
|
||||
QObject::connect(can, &AbstractStream::seekedTo, model, &HistoryLogModel::refresh);
|
||||
QObject::connect(can, &AbstractStream::eventsMerged, model, &HistoryLogModel::segmentsMerged);
|
||||
}
|
||||
|
||||
void LogsWidget::setMessage(const QString &message_id) {
|
||||
model->setMessage(message_id);
|
||||
cur_filter_text = "";
|
||||
value_edit->setText("");
|
||||
signals_cb->clear();
|
||||
comp_box->setCurrentIndex(0);
|
||||
bool has_signals = model->sigs.size() > 0;
|
||||
if (has_signals) {
|
||||
bool has_signal = model->sigs.size();
|
||||
if (has_signal) {
|
||||
signals_cb->clear();
|
||||
for (auto s : model->sigs) {
|
||||
signals_cb->addItem(s->name.c_str());
|
||||
}
|
||||
}
|
||||
comp_box->setVisible(has_signals);
|
||||
value_edit->setVisible(has_signals);
|
||||
signals_cb->setVisible(has_signals);
|
||||
value_edit->clear();
|
||||
comp_box->setCurrentIndex(0);
|
||||
filters_widget->setVisible(has_signal);
|
||||
}
|
||||
|
||||
static bool not_equal(double l, double r) { return l != r; }
|
||||
|
||||
void LogsWidget::setFilter() {
|
||||
if (cur_filter_text.isEmpty() && value_edit->text().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (value_edit->text().isEmpty() && !value_edit->isModified()) return;
|
||||
|
||||
std::function<bool(double, double)> cmp;
|
||||
std::function<bool(double, double)> cmp = nullptr;
|
||||
switch (comp_box->currentIndex()) {
|
||||
case 0: cmp = std::greater<double>{}; break;
|
||||
case 1: cmp = std::equal_to<double>{}; break;
|
||||
case 2: cmp = not_equal; break;
|
||||
case 2: cmp = [](double l, double r) { return l != r; }; break; // not equal
|
||||
case 3: cmp = std::less<double>{}; break;
|
||||
}
|
||||
model->setFilter(signals_cb->currentIndex(), value_edit->text(), cmp);
|
||||
cur_filter_text = value_edit->text();
|
||||
}
|
||||
|
||||
void LogsWidget::showEvent(QShowEvent *event) {
|
||||
if (dynamic_mode->isChecked()) {
|
||||
model->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void LogsWidget::updateState() {
|
||||
if (dynamic_mode->isChecked()) {
|
||||
model->updateState();
|
||||
}
|
||||
}
|
||||
|
||||
void LogsWidget::doubleClicked(const QModelIndex &index) {
|
||||
if (index.isValid()) {
|
||||
if (model->sigs.size() > 0 && index.column() > 0) {
|
||||
emit openChart(model->msg_id, model->sigs[index.column()-1]);
|
||||
}
|
||||
can->seekTo(model->messages[index.row()].mono_time / (double)1e9 - can->routeStartTime());
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user