mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-08-05 04:45:41 +08:00
Merge remote-tracking branch 'upstream/commaai/openpilot/master' into dev-pub/master
# Conflicts: # cereal # opendbc
This commit is contained in:
@@ -219,6 +219,7 @@ jobs:
|
||||
$UNIT_TEST selfdrive/car && \
|
||||
$UNIT_TEST selfdrive/locationd && \
|
||||
selfdrive/locationd/test/_test_locationd_lib.py && \
|
||||
./selfdrive/locationd/test/test_glonass_runner && \
|
||||
$UNIT_TEST selfdrive/athena && \
|
||||
$UNIT_TEST selfdrive/thermald && \
|
||||
$UNIT_TEST system/hardware/tici && \
|
||||
|
||||
@@ -6,8 +6,10 @@ Version 0.9.1 (2022-12-XX)
|
||||
* Chevrolet Bolt EV 2022-23 support thanks to JasonJShuler!
|
||||
* Genesis GV60 2023 support thanks to sunnyhaibin!
|
||||
* Hyundai Tucson 2022-23 support
|
||||
* Kia K5 Hybrid 2020 support thanks to sunnyhaibin!
|
||||
* Kia Sorento 2022-23 support thanks to sunnyhaibin!
|
||||
* Kia Sorento Plug-in Hybrid 2022 support thanks to sunnyhaibin!
|
||||
* Volkswagen Crafter and MAN TGE 2017-23 support thanks to jyoung8607!
|
||||
|
||||
Version 0.9.0 (2022-11-21)
|
||||
========================
|
||||
|
||||
+1
-1
Submodule cereal updated: 29d571c841...79f76b153e
+3
-1
@@ -10,11 +10,13 @@ common_libs = [
|
||||
'statlog.cc',
|
||||
'swaglog.cc',
|
||||
'util.cc',
|
||||
'gpio.cc',
|
||||
'i2c.cc',
|
||||
'watchdog.cc',
|
||||
]
|
||||
|
||||
if arch != "Darwin":
|
||||
common_libs.append('gpio.cc')
|
||||
|
||||
_common = fxn('common', common_libs, LIBS="json11")
|
||||
|
||||
files = [
|
||||
|
||||
+2
-1
@@ -111,6 +111,7 @@ std::unordered_map<std::string, uint32_t> keys = {
|
||||
{"DoReboot", CLEAR_ON_MANAGER_START},
|
||||
{"DoShutdown", CLEAR_ON_MANAGER_START},
|
||||
{"DoUninstall", CLEAR_ON_MANAGER_START},
|
||||
{"FirmwareObdQueryDone", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON},
|
||||
{"ForcePowerDown", CLEAR_ON_MANAGER_START},
|
||||
{"GitBranch", PERSISTENT},
|
||||
{"GitCommit", PERSISTENT},
|
||||
@@ -170,7 +171,7 @@ std::unordered_map<std::string, uint32_t> keys = {
|
||||
{"Timezone", PERSISTENT},
|
||||
{"TrainingVersion", PERSISTENT},
|
||||
{"UbloxAvailable", PERSISTENT},
|
||||
{"UpdateAvailable", CLEAR_ON_MANAGER_START},
|
||||
{"UpdateAvailable", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON},
|
||||
{"UpdateFailedCount", CLEAR_ON_MANAGER_START},
|
||||
{"UpdaterState", CLEAR_ON_MANAGER_START},
|
||||
{"UpdaterFetchAvailable", CLEAR_ON_MANAGER_START},
|
||||
|
||||
+11
-2
@@ -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.
|
||||
|
||||
# 224 Supported Cars
|
||||
# 233 Supported Cars
|
||||
|
||||
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness|Video|
|
||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
@@ -30,7 +30,8 @@ A supported vehicle is one that just works when you install a comma three. All s
|
||||
|comma|body|All|openpilot|0 mph|0 mph|[](##)|[](##)|None||
|
||||
|Genesis|G70 2018-19|All|Stock|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Genesis&model=G70 2018-19">Hyundai F</a>||
|
||||
|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|G80 2017|All|Stock|19 mph|37 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Genesis&model=G80 2017">Hyundai J</a>||
|
||||
|Genesis|G80 2018-19|All|Stock|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Genesis&model=G80 2018-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 (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>||
|
||||
@@ -99,6 +100,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|
||||
|Kia|EV6 (without HDA II) 2022[<sup>5</sup>](#footnotes)|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Kia&model=EV6 (without HDA II) 2022">Hyundai L</a>||
|
||||
|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Kia&model=Forte 2019-21">Hyundai G</a>||
|
||||
|Kia|K5 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=Kia&model=K5 2021-22">Hyundai A</a>||
|
||||
|Kia|K5 Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Kia&model=K5 Hybrid 2020">Hyundai A</a>||
|
||||
|Kia|Niro EV 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Kia&model=Niro EV 2019">Hyundai H</a>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Kia|Niro EV 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Kia&model=Niro EV 2020">Hyundai F</a>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Kia|Niro EV 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Kia&model=Niro EV 2021">Hyundai C</a>|<a href="https://www.youtube.com/watch?v=lT7zcG6ZpGo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
@@ -106,6 +108,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|
||||
|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Kia&model=Niro Hybrid 2021">Hyundai F</a>||
|
||||
|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Kia&model=Niro Hybrid 2022">Hyundai H</a>||
|
||||
|Kia|Niro Plug-in Hybrid 2018-19|All|openpilot available[<sup>1</sup>](#footnotes)|10 mph|32 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Kia&model=Niro Plug-in Hybrid 2018-19">Hyundai C</a>||
|
||||
|Kia|Niro Plug-in Hybrid 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Kia&model=Niro Plug-in Hybrid 2020">Hyundai D</a>||
|
||||
|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Kia&model=Optima 2017">Hyundai B</a>||
|
||||
|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Kia&model=Optima 2019-20">Hyundai G</a>||
|
||||
|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Kia&model=Seltos 2021">Hyundai A</a>||
|
||||
@@ -135,6 +138,8 @@ A supported vehicle is one that just works when you install a comma three. All s
|
||||
|Lexus|RX Hybrid 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Lexus&model=RX Hybrid 2017-19">Toyota</a>||
|
||||
|Lexus|RX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Lexus&model=RX Hybrid 2020-21">Toyota</a>||
|
||||
|Lexus|UX Hybrid 2019-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Lexus&model=UX Hybrid 2019-22">Toyota</a>||
|
||||
|MAN|eTGE 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=MAN&model=eTGE 2020-23">J533</a>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|MAN|TGE 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=MAN&model=TGE 2017-23">J533</a>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Mazda|CX-5 2022-23|All|Stock|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Mazda&model=CX-5 2022-23">Mazda</a>||
|
||||
|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Mazda&model=CX-9 2021-23">Mazda</a>|<a href="https://youtu.be/dA3duO4a0O4" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Nissan|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Nissan&model=Altima 2019-20">Nissan B</a>||
|
||||
@@ -181,6 +186,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|
||||
|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Corolla Cross Hybrid (Non-US only) 2020-22">Toyota</a>||
|
||||
|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Corolla Hatchback 2019-22">Toyota</a>|<a href="https://www.youtube.com/watch?v=_66pXk0CBYA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Corolla Hybrid 2020-22">Toyota</a>||
|
||||
|Toyota|Corolla Hybrid (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Corolla Hybrid (Non-US only) 2020-23">Toyota</a>||
|
||||
|Toyota|Highlander 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Highlander 2017-19">Toyota</a>|<a href="https://www.youtube.com/watch?v=0wS0wXSLzoo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Highlander 2020-23">Toyota</a>||
|
||||
|Toyota|Highlander Hybrid 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Highlander Hybrid 2017-19">Toyota</a>||
|
||||
@@ -209,6 +215,8 @@ A supported vehicle is one that just works when you install a comma three. All s
|
||||
|Volkswagen|California 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Volkswagen&model=California 2021">J533</a>||
|
||||
|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Volkswagen&model=Caravelle 2020">J533</a>||
|
||||
|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Volkswagen&model=CC 2018-22">J533</a>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Volkswagen|Crafter 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Volkswagen&model=Crafter 2017-23">J533</a>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Volkswagen|e-Crafter 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Volkswagen&model=e-Crafter 2018-23">J533</a>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Volkswagen&model=e-Golf 2014-20">J533</a>||
|
||||
|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Volkswagen&model=Golf 2015-20">J533</a>||
|
||||
|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Volkswagen&model=Golf Alltrack 2015-19">J533</a>||
|
||||
@@ -217,6 +225,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|
||||
|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Volkswagen&model=Golf GTI 2015-21">J533</a>||
|
||||
|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Volkswagen&model=Golf R 2015-19">J533</a>||
|
||||
|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Volkswagen&model=Golf SportsVan 2015-20">J533</a>||
|
||||
|Volkswagen|Grand California 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|31 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Volkswagen&model=Grand California 2019-23">J533</a>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Volkswagen|Jetta 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Volkswagen&model=Jetta 2018-22">J533</a>||
|
||||
|Volkswagen|Jetta GLI 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Volkswagen&model=Jetta GLI 2021-22">J533</a>||
|
||||
|Volkswagen|Passat 2015-22[<sup>8</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Volkswagen&model=Passat 2015-22">J533</a>||
|
||||
|
||||
@@ -77,3 +77,4 @@ tools
|
||||
Simulator <tools/sim/README.md>
|
||||
tools/ssh/README.md
|
||||
Webcam <tools/webcam/README.md>
|
||||
tools/cabana/README.md
|
||||
|
||||
+1
-1
Submodule laika_repo updated: 2e5c7b8a85...b30afe44d2
+1
-1
Submodule opendbc updated: e8ce453227...77996759d9
@@ -225,10 +225,7 @@ selfdrive/locationd/SConscript
|
||||
selfdrive/locationd/ubloxd.cc
|
||||
selfdrive/locationd/ublox_msg.cc
|
||||
selfdrive/locationd/ublox_msg.h
|
||||
selfdrive/locationd/generated/ubx.cpp
|
||||
selfdrive/locationd/generated/ubx.h
|
||||
selfdrive/locationd/generated/gps.cpp
|
||||
selfdrive/locationd/generated/gps.h
|
||||
selfdrive/locationd/generated/*
|
||||
|
||||
selfdrive/locationd/laikad.py
|
||||
selfdrive/locationd/locationd.h
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <cassert>
|
||||
@@ -105,6 +106,8 @@ void sync_time(Panda *panda, SyncTimeDir dir) {
|
||||
bool safety_setter_thread(std::vector<Panda *> pandas) {
|
||||
LOGD("Starting safety setter thread");
|
||||
|
||||
Params p;
|
||||
|
||||
// there should be at least one panda connected
|
||||
if (pandas.size() == 0) {
|
||||
return false;
|
||||
@@ -116,25 +119,20 @@ bool safety_setter_thread(std::vector<Panda *> pandas) {
|
||||
pandas[i]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, safety_param);
|
||||
}
|
||||
|
||||
Params p = Params();
|
||||
|
||||
// wait for VIN to be read
|
||||
// wait for FW query at OBD port to finish
|
||||
while (true) {
|
||||
if (do_exit || !check_all_connected(pandas) || !ignition) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string value_vin = p.get("CarVin");
|
||||
if (value_vin.size() > 0) {
|
||||
// sanity check VIN format
|
||||
assert(value_vin.size() == 17);
|
||||
LOGW("got CarVin %s", value_vin.c_str());
|
||||
if (p.getBool("FirmwareObdQueryDone")) {
|
||||
LOGW("finished FW query at OBD port");
|
||||
break;
|
||||
}
|
||||
util::sleep_for(20);
|
||||
}
|
||||
|
||||
// set to ELM327 for ECU knockouts
|
||||
// set to ELM327 to finish fingerprinting and for potential ECU knockouts
|
||||
for (Panda *panda : pandas) {
|
||||
panda->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1U);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@ Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) {
|
||||
try {
|
||||
handle = std::make_unique<PandaUsbHandle>(serial);
|
||||
} catch (std::exception &e) {
|
||||
#ifndef __APPLE__
|
||||
handle = std::make_unique<PandaSpiHandle>(serial);
|
||||
#endif
|
||||
}
|
||||
|
||||
hw_type = get_hw_type();
|
||||
@@ -46,11 +48,13 @@ std::string Panda::hw_serial() {
|
||||
std::vector<std::string> Panda::list() {
|
||||
std::vector<std::string> serials = PandaUsbHandle::list();
|
||||
|
||||
#ifndef __APPLE__
|
||||
for (auto s : PandaSpiHandle::list()) {
|
||||
if (std::find(serials.begin(), serials.end(), s) == serials.end()) {
|
||||
serials.push_back(s);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return serials;
|
||||
}
|
||||
@@ -232,6 +236,9 @@ void Panda::can_send(capnp::List<cereal::CanData>::Reader can_data_list) {
|
||||
}
|
||||
|
||||
bool Panda::can_receive(std::vector<can_frame>& out_vec) {
|
||||
// Check if enough space left in buffer to store RECV_SIZE data
|
||||
assert(receive_buffer_size + RECV_SIZE <= sizeof(receive_buffer));
|
||||
|
||||
int recv = handle->bulk_read(0x81, &receive_buffer[receive_buffer_size], RECV_SIZE);
|
||||
if (!comms_healthy()) {
|
||||
return false;
|
||||
@@ -274,6 +281,7 @@ bool Panda::unpack_can_buffer(uint8_t *data, uint32_t &size, std::vector<can_fra
|
||||
|
||||
if (calculate_checksum(&data[pos], sizeof(can_header) + data_len) != 0) {
|
||||
LOGE("Panda CAN checksum failed");
|
||||
size = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#ifndef __APPLE__
|
||||
#include <linux/spi/spidev.h>
|
||||
#endif
|
||||
|
||||
#include <libusb-1.0/libusb.h>
|
||||
|
||||
@@ -52,6 +54,7 @@ private:
|
||||
void handle_usb_issue(int err, const char func[]);
|
||||
};
|
||||
|
||||
#ifndef __APPLE__
|
||||
class PandaSpiHandle : public PandaCommsHandle {
|
||||
public:
|
||||
PandaSpiHandle(std::string serial);
|
||||
@@ -75,3 +78,4 @@ private:
|
||||
int spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len);
|
||||
int spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len);
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#ifndef __APPLE__
|
||||
#include <sys/file.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/spi/spidev.h>
|
||||
@@ -339,3 +340,4 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx
|
||||
transfer_fail:
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -51,7 +51,7 @@ class TestBoardd(unittest.TestCase):
|
||||
cp.safetyConfigs = [safety_config]*num_pandas
|
||||
|
||||
params = Params()
|
||||
params.put("CarVin", b"0"*17)
|
||||
params.put_bool("FirmwareObdQueryDone", True)
|
||||
params.put_bool("ControlsReady", True)
|
||||
params.put("CarParams", cp.to_bytes())
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.wheelSpeedFactor = SPEED_FROM_RPM
|
||||
ret.centerToFront = ret.wheelbase * 0.44
|
||||
|
||||
ret.radarOffCan = True
|
||||
ret.radarUnavailable = True
|
||||
ret.openpilotLongitudinalControl = True
|
||||
ret.steerControlType = car.CarParams.SteerControlType.angle
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ def fingerprint(logcan, sendcan, num_pandas):
|
||||
else:
|
||||
cloudlog.warning("Getting VIN & FW versions")
|
||||
vin_rx_addr, vin = get_vin(logcan, sendcan, bus)
|
||||
ecu_rx_addrs = get_present_ecus(logcan, sendcan)
|
||||
ecu_rx_addrs = get_present_ecus(logcan, sendcan, num_pandas=num_pandas)
|
||||
car_fw = get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, num_pandas=num_pandas)
|
||||
cached = False
|
||||
|
||||
@@ -113,7 +113,10 @@ def fingerprint(logcan, sendcan, num_pandas):
|
||||
cloudlog.event("Malformed VIN", vin=vin, error=True)
|
||||
vin = VIN_UNKNOWN
|
||||
cloudlog.warning("VIN %s", vin)
|
||||
Params().put("CarVin", vin)
|
||||
|
||||
params = Params()
|
||||
params.put("CarVin", vin)
|
||||
params.put_bool("FirmwareObdQueryDone", True)
|
||||
|
||||
finger = gen_empty_fingerprint()
|
||||
candidate_cars = {i: all_legacy_fingerprint_cars() for i in [0, 1]} # attempt fingerprint on both bus 0 and 1
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
from cereal import car
|
||||
from panda import Panda
|
||||
from selfdrive.car import STD_CARGO_KG, get_safety_config
|
||||
from selfdrive.car.chrysler.values import CAR, DBC, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags
|
||||
from selfdrive.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags
|
||||
from selfdrive.car.interfaces import CarInterfaceBase
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.carName = "chrysler"
|
||||
ret.dashcamOnly = candidate in RAM_HD
|
||||
|
||||
ret.radarOffCan = DBC[candidate]['radar'] is None
|
||||
ret.radarUnavailable = True # DBC[candidate]['radar'] is None
|
||||
ret.steerActuatorDelay = 0.1
|
||||
ret.steerLimitTimer = 0.4
|
||||
|
||||
|
||||
@@ -45,12 +45,13 @@ def _address_to_track(address):
|
||||
class RadarInterface(RadarInterfaceBase):
|
||||
def __init__(self, CP):
|
||||
super().__init__(CP)
|
||||
self.CP = CP
|
||||
self.rcp = _create_radar_can_parser(CP.carFingerprint)
|
||||
self.updated_messages = set()
|
||||
self.trigger_msg = LAST_MSG
|
||||
|
||||
def update(self, can_strings):
|
||||
if self.rcp is None:
|
||||
if self.rcp is None or self.CP.radarUnavailable:
|
||||
return super().update(None)
|
||||
|
||||
vls = self.rcp.update_strings(can_strings)
|
||||
|
||||
@@ -87,5 +87,5 @@ if __name__ == "__main__":
|
||||
for addr, subaddr, bus in ecu_addrs:
|
||||
msg = f" 0x{hex(addr)}"
|
||||
if subaddr is not None:
|
||||
msg += f" (sub-address: 0x{hex(subaddr)})"
|
||||
msg += f" (sub-address: {hex(subaddr)})"
|
||||
print(msg)
|
||||
|
||||
@@ -2,8 +2,9 @@ from cereal import car
|
||||
from common.numpy_fast import clip
|
||||
from opendbc.can.packer import CANPacker
|
||||
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
|
||||
from selfdrive.car.ford.fordcan import create_acc_command, create_acc_ui_msg, create_button_msg, create_lat_ctl_msg, \
|
||||
create_lat_ctl2_msg, create_lka_msg, create_lkas_ui_msg
|
||||
from selfdrive.car.ford.values import CANBUS, CANFD_CARS, CarControllerParams
|
||||
|
||||
VisualAlert = car.CarControl.HUDControl.VisualAlert
|
||||
|
||||
@@ -53,7 +54,29 @@ class CarController:
|
||||
|
||||
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.))
|
||||
|
||||
if self.CP.carFingerprint in CANFD_CARS:
|
||||
# TODO: extended mode
|
||||
mode = 1 if CC.latActive else 0
|
||||
counter = self.frame // CarControllerParams.STEER_STEP
|
||||
can_sends.append(create_lat_ctl2_msg(self.packer, mode, 0., 0., -apply_curvature, 0., counter))
|
||||
else:
|
||||
can_sends.append(create_lat_ctl_msg(self.packer, CC.latActive, 0., 0., -apply_curvature, 0.))
|
||||
|
||||
### longitudinal control ###
|
||||
# send acc command at 50Hz
|
||||
if self.CP.openpilotLongitudinalControl and (self.frame % CarControllerParams.ACC_CONTROL_STEP) == 0:
|
||||
accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX)
|
||||
|
||||
precharge_brake = accel < -0.1
|
||||
if accel > -0.5:
|
||||
gas = accel
|
||||
decel = False
|
||||
else:
|
||||
gas = -5.0
|
||||
decel = True
|
||||
|
||||
can_sends.append(create_acc_command(self.packer, CC.longActive, gas, accel, precharge_brake, decel))
|
||||
|
||||
### ui ###
|
||||
send_ui = (self.main_on_last != main_on) or (self.lkas_enabled_last != CC.latActive) or (self.steer_alert_last != steer_alert)
|
||||
|
||||
@@ -14,7 +14,7 @@ class CarState(CarStateBase):
|
||||
super().__init__(CP)
|
||||
can_define = CANDefine(DBC[CP.carFingerprint]["pt"])
|
||||
if CP.transmissionType == TransmissionType.automatic:
|
||||
self.shifter_values = can_define.dv["Gear_Shift_by_Wire_FD1"]["TrnGear_D_RqDrv"]
|
||||
self.shifter_values = can_define.dv["Gear_Shift_by_Wire_FD1"]["TrnRng_D_RqGsm"]
|
||||
|
||||
def update(self, cp, cp_cam):
|
||||
ret = car.CarState.new_message()
|
||||
@@ -51,7 +51,7 @@ class CarState(CarStateBase):
|
||||
|
||||
# gear
|
||||
if self.CP.transmissionType == TransmissionType.automatic:
|
||||
gear = self.shifter_values.get(cp.vl["Gear_Shift_by_Wire_FD1"]["TrnGear_D_RqDrv"], None)
|
||||
gear = self.shifter_values.get(cp.vl["Gear_Shift_by_Wire_FD1"]["TrnRng_D_RqGsm"])
|
||||
ret.gearShifter = self.parse_gear_shifter(gear)
|
||||
elif self.CP.transmissionType == TransmissionType.manual:
|
||||
ret.clutchPressed = cp.vl["Engine_Clutch_Data"]["CluPdlPos_Pc_Meas"] > 0
|
||||
@@ -171,7 +171,7 @@ class CarState(CarStateBase):
|
||||
|
||||
if CP.transmissionType == TransmissionType.automatic:
|
||||
signals += [
|
||||
("TrnGear_D_RqDrv", "Gear_Shift_by_Wire_FD1"), # GWM transmission gear position
|
||||
("TrnRng_D_RqGsm", "Gear_Shift_by_Wire_FD1"), # GWM transmission gear position
|
||||
]
|
||||
checks += [
|
||||
("Gear_Shift_by_Wire_FD1", 10),
|
||||
|
||||
@@ -4,6 +4,15 @@ from selfdrive.car.ford.values import CANBUS
|
||||
HUDControl = car.CarControl.HUDControl
|
||||
|
||||
|
||||
def calculate_lat_ctl2_checksum(mode: int, counter: int, dat: bytearray):
|
||||
checksum = mode + counter
|
||||
checksum += dat[2] + ((dat[3] & 0xE0) >> 5) # curvature
|
||||
checksum += dat[6] + ((dat[7] & 0xE0) >> 5) # curvature rate
|
||||
checksum += (dat[3] & 0x1F) + ((dat[4] & 0xFC) >> 2) # path angle
|
||||
checksum += (dat[4] & 0x3) + dat[5] # path offset
|
||||
return 0xFF - (checksum & 0xFF)
|
||||
|
||||
|
||||
def create_lka_msg(packer):
|
||||
"""
|
||||
Creates an empty CAN message for the Ford LKA Command.
|
||||
@@ -16,7 +25,8 @@ def create_lka_msg(packer):
|
||||
return packer.make_can_msg("Lane_Assist_Data1", CANBUS.main, {})
|
||||
|
||||
|
||||
def create_lat_ctl_msg(packer, lat_active: bool, path_offset: float, path_angle: float, curvature: float, curvature_rate: 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.
|
||||
|
||||
@@ -55,6 +65,58 @@ def create_lat_ctl_msg(packer, lat_active: bool, path_offset: float, path_angle:
|
||||
return packer.make_can_msg("LateralMotionControl", CANBUS.main, values)
|
||||
|
||||
|
||||
def create_lat_ctl2_msg(packer, mode: int, path_offset: float, path_angle: float, curvature: float,
|
||||
curvature_rate: float, counter: int):
|
||||
"""
|
||||
Create a CAN message for the new Ford Lane Centering command.
|
||||
|
||||
This message is used on the CAN FD platform and replaces the old LateralMotionControl message. It is similar but has
|
||||
additional signals for a counter and checksum.
|
||||
|
||||
Frequency is 20Hz.
|
||||
"""
|
||||
|
||||
values = {
|
||||
"LatCtl_D2_Rq": mode, # Mode: 0=None, 1=PathFollowingLimitedMode, 2=PathFollowingExtendedMode,
|
||||
# 3=SafeRampOut, 4-7=NotUsed [0|7]
|
||||
"LatCtlRampType_D_Rq": 0, # 0=Slow, 1=Medium, 2=Fast, 3=Immediate [0|3]
|
||||
"LatCtlPrecision_D_Rq": 1, # 0=Comfortable, 1=Precise, 2/3=NotUsed [0|3]
|
||||
"LatCtlPathOffst_L_Actl": path_offset, # [-5.12|5.11] meter
|
||||
"LatCtlPath_An_Actl": path_angle, # [-0.5|0.5235] radians
|
||||
"LatCtlCurv_No_Actl": curvature, # [-0.02|0.02094] 1/meter
|
||||
"LatCtlCrv_NoRate2_Actl": curvature_rate, # [-0.001024|0.001023] 1/meter^2
|
||||
"HandsOffCnfm_B_Rq": 0, # 0=Inactive, 1=Active [0|1]
|
||||
"LatCtlPath_No_Cnt": counter, # [0|15]
|
||||
"LatCtlPath_No_Cs": 0, # [0|255]
|
||||
}
|
||||
|
||||
# calculate checksum
|
||||
dat = packer.make_can_msg("LateralMotionControl2", CANBUS.main, values)[2]
|
||||
values["LatCtlPath_No_Cs"] = calculate_lat_ctl2_checksum(mode, counter, dat)
|
||||
|
||||
return packer.make_can_msg("LateralMotionControl2", CANBUS.main, values)
|
||||
|
||||
|
||||
def create_acc_command(packer, long_active: bool, gas: float, accel: float, precharge_brake: bool, decel: bool):
|
||||
"""
|
||||
Creates a CAN message for the Ford ACC Command.
|
||||
|
||||
This command can be used to enable ACC, to set the ACC gas/brake/decel values
|
||||
and to disable ACC.
|
||||
|
||||
Frequency is 50Hz.
|
||||
"""
|
||||
|
||||
values = {
|
||||
"AccBrkTot_A_Rq": accel, # Brake total accel request: [-20|11.9449] m/s^2
|
||||
"Cmbb_B_Enbl": 1 if long_active else 0, # Enabled: 0=No, 1=Yes
|
||||
"AccPrpl_A_Rq": gas, # Acceleration request: [-5|5.23] m/s^2
|
||||
"AccBrkPrchg_B_Rq": 1 if precharge_brake else 0, # Pre-charge brake request: 0=No, 1=Yes
|
||||
"AccBrkDecel_B_Rq": 1 if decel else 0, # Deceleration request: 0=Inactive, 1=Active
|
||||
}
|
||||
return packer.make_can_msg("ACCDATA", CANBUS.main, values)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
@@ -18,7 +18,7 @@ class CarInterface(CarInterfaceBase):
|
||||
# These cars are dashcam only until the port is finished
|
||||
ret.dashcamOnly = True
|
||||
|
||||
ret.radarOffCan = True
|
||||
ret.radarUnavailable = True
|
||||
ret.steerControlType = car.CarParams.SteerControlType.angle
|
||||
ret.steerActuatorDelay = 0.2
|
||||
ret.steerLimitTimer = 1.0
|
||||
|
||||
@@ -47,7 +47,7 @@ class RadarInterface(RadarInterfaceBase):
|
||||
self.updated_messages = set()
|
||||
self.track_id = 0
|
||||
self.radar = DBC[CP.carFingerprint]['radar']
|
||||
if self.radar is None:
|
||||
if self.radar is None or CP.radarUnavailable:
|
||||
self.rcp = None
|
||||
elif self.radar == RADAR.DELPHI_ESR:
|
||||
self.rcp = _create_delphi_esr_radar_can_parser()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Dict, List, Union
|
||||
from typing import Dict, List, Set, Union
|
||||
|
||||
from cereal import car
|
||||
from selfdrive.car import AngleRateLimit, dbc_dict
|
||||
@@ -14,6 +14,8 @@ Ecu = car.CarParams.Ecu
|
||||
class CarControllerParams:
|
||||
# Messages: Lane_Assist_Data1, LateralMotionControl
|
||||
STEER_STEP = 5
|
||||
# Message: ACCDATA
|
||||
ACC_CONTROL_STEP = 2
|
||||
# Message: IPMA_Data
|
||||
LKAS_UI_STEP = 100
|
||||
# Message: ACCDATA_3
|
||||
@@ -29,6 +31,9 @@ class CarControllerParams:
|
||||
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])
|
||||
|
||||
ACCEL_MAX = 2.0 # m/s^s max acceleration
|
||||
ACCEL_MIN = -3.5 # m/s^s max deceleration
|
||||
|
||||
def __init__(self, CP):
|
||||
pass
|
||||
|
||||
@@ -47,12 +52,15 @@ class CAR:
|
||||
MAVERICK_MK1 = "FORD MAVERICK 1ST GEN"
|
||||
|
||||
|
||||
CANFD_CARS: Set[str] = set()
|
||||
|
||||
|
||||
class RADAR:
|
||||
DELPHI_ESR = 'ford_fusion_2018_adas'
|
||||
DELPHI_MRR = 'FORD_CADS'
|
||||
|
||||
|
||||
DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("ford_lincoln_base_pt", None))
|
||||
DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("ford_lincoln_base_pt", RADAR.DELPHI_MRR))
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -67,7 +75,10 @@ CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = {
|
||||
FordCarInfo("Ford Escape 2020-21"),
|
||||
FordCarInfo("Ford Kuga 2020-21", "Driver Assistance Pack"),
|
||||
],
|
||||
CAR.EXPLORER_MK6: FordCarInfo("Ford Explorer 2020-22"),
|
||||
CAR.EXPLORER_MK6: [
|
||||
FordCarInfo("Ford Explorer 2020-22"),
|
||||
FordCarInfo("Lincoln Aviator 2021", "Co-Pilot360 Plus"),
|
||||
],
|
||||
CAR.FOCUS_MK4: FordCarInfo("Ford Focus EU 2019", "Driver Assistance Pack"),
|
||||
CAR.MAVERICK_MK1: FordCarInfo("Ford Maverick 2022", "Co-Pilot360 Assist"),
|
||||
}
|
||||
@@ -157,13 +168,16 @@ FW_VERSIONS = {
|
||||
(Ecu.fwdCamera, 0x706, None): [
|
||||
b'LB5T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'LB5T-14F397-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'LC5T-14F397-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.engine, 0x7E0, None): [
|
||||
b'LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'MB5A-14C204-MD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'MB5A-14C204-RC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'NB5A-14C204-HB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.shiftByWire, 0x732, None): [
|
||||
b'L1MP-14C561-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'L1MP-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'L1MP-14G395-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'L1MP-14G395-JB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
|
||||
@@ -146,12 +146,16 @@ def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True):
|
||||
return True, set()
|
||||
|
||||
|
||||
def get_present_ecus(logcan, sendcan):
|
||||
def get_present_ecus(logcan, sendcan, num_pandas=1) -> Set[Tuple[int, Optional[int], int]]:
|
||||
queries = list()
|
||||
parallel_queries = list()
|
||||
responses = set()
|
||||
|
||||
for brand, r in REQUESTS:
|
||||
# Skip query if no panda available
|
||||
if r.bus > num_pandas * 4 - 1:
|
||||
continue
|
||||
|
||||
for brand_versions in VERSIONS[brand].values():
|
||||
for ecu_type, addr, sub_addr in brand_versions:
|
||||
# Only query ecus in whitelist if whitelist is not empty
|
||||
@@ -171,7 +175,7 @@ def get_present_ecus(logcan, sendcan):
|
||||
|
||||
queries.insert(0, parallel_queries)
|
||||
|
||||
ecu_responses: Set[Tuple[int, Optional[int], int]] = set()
|
||||
ecu_responses = set()
|
||||
for query in queries:
|
||||
ecu_responses.update(get_ecu_addrs(logcan, sendcan, set(query), responses, timeout=0.1))
|
||||
return ecu_responses
|
||||
|
||||
@@ -50,9 +50,14 @@ class CarController:
|
||||
|
||||
# Steering (Active: 50Hz, inactive: 10Hz)
|
||||
# Attempt to sync with camera on startup at 50Hz, first few msgs are blocked
|
||||
init_lka_counter = not self.sent_lka_steering_cmd and self.CP.networkLocation == NetworkLocation.fwdCamera
|
||||
init_lka_counter = not self.sent_lka_steering_cmd
|
||||
# Also send at 50Hz until we're in sync with camera so counters align when relay closes, preventing a fault
|
||||
# openpilot can subtly drift, so this is activated throughout a drive to stay synced
|
||||
out_of_sync = self.lka_steering_cmd_counter % 4 != (CS.camera_lka_steering_cmd_counter + 1) % 4
|
||||
sync_steer = (init_lka_counter or out_of_sync) and self.CP.networkLocation == NetworkLocation.fwdCamera
|
||||
|
||||
steer_step = self.params.INACTIVE_STEER_STEP
|
||||
if CC.latActive or init_lka_counter:
|
||||
if CC.latActive or sync_steer:
|
||||
steer_step = self.params.STEER_STEP
|
||||
|
||||
# Avoid GM EPS faults when transmitting messages too close together: skip this transmit if we just received the
|
||||
@@ -63,7 +68,7 @@ class CarController:
|
||||
elif (self.frame - self.last_steer_frame) >= steer_step:
|
||||
# Initialize ASCMLKASteeringCmd counter using the camera until we get a msg on the bus
|
||||
if init_lka_counter:
|
||||
self.lka_steering_cmd_counter = CS.camera_lka_steering_cmd_counter + 1
|
||||
self.lka_steering_cmd_counter = CS.pt_lka_steering_cmd_counter + 1
|
||||
|
||||
if CC.latActive:
|
||||
new_steer = int(round(actuators.steer * self.params.STEER_MAX))
|
||||
@@ -109,7 +114,7 @@ class CarController:
|
||||
|
||||
# Radar needs to know current speed and yaw rate (50hz),
|
||||
# and that ADAS is alive (10hz)
|
||||
if not self.CP.radarOffCan:
|
||||
if not self.CP.radarUnavailable:
|
||||
tt = self.frame * DT_CTRL
|
||||
time_and_headlights_step = 10
|
||||
if self.frame % time_and_headlights_step == 0:
|
||||
|
||||
@@ -18,6 +18,7 @@ class CarState(CarStateBase):
|
||||
can_define = CANDefine(DBC[CP.carFingerprint]["pt"])
|
||||
self.shifter_values = can_define.dv["ECMPRDNL2"]["PRNDL2"]
|
||||
self.loopback_lka_steering_cmd_updated = False
|
||||
self.pt_lka_steering_cmd_counter = 0
|
||||
self.camera_lka_steering_cmd_counter = 0
|
||||
self.buttons_counter = 0
|
||||
|
||||
@@ -33,6 +34,7 @@ class CarState(CarStateBase):
|
||||
# Variables used for avoiding LKAS faults
|
||||
self.loopback_lka_steering_cmd_updated = len(loopback_cp.vl_all["ASCMLKASteeringCmd"]["RollingCounter"]) > 0
|
||||
if self.CP.networkLocation == NetworkLocation.fwdCamera:
|
||||
self.pt_lka_steering_cmd_counter = pt_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"]
|
||||
self.camera_lka_steering_cmd_counter = cam_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"]
|
||||
|
||||
ret.wheelSpeeds = self.get_wheel_speeds(
|
||||
@@ -180,6 +182,15 @@ class CarState(CarStateBase):
|
||||
("ECMAcceleratorPos", 80),
|
||||
]
|
||||
|
||||
# Used to read back last counter sent to PT by camera
|
||||
if CP.networkLocation == NetworkLocation.fwdCamera:
|
||||
signals += [
|
||||
("RollingCounter", "ASCMLKASteeringCmd"),
|
||||
]
|
||||
checks += [
|
||||
("ASCMLKASteeringCmd", 0),
|
||||
]
|
||||
|
||||
if CP.transmissionType == TransmissionType.direct:
|
||||
signals.append(("RegenPaddle", "EBCMRegenPaddle"))
|
||||
checks.append(("EBCMRegenPaddle", 50))
|
||||
|
||||
@@ -63,7 +63,7 @@ class CarInterface(CarInterfaceBase):
|
||||
if candidate in CAMERA_ACC_CAR:
|
||||
ret.experimentalLongitudinalAvailable = True
|
||||
ret.networkLocation = NetworkLocation.fwdCamera
|
||||
ret.radarOffCan = True # no radar
|
||||
ret.radarUnavailable = True # no radar
|
||||
ret.pcmCruise = True
|
||||
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM
|
||||
ret.minEnableSpeed = 5 * CV.KPH_TO_MS
|
||||
@@ -85,7 +85,7 @@ class CarInterface(CarInterfaceBase):
|
||||
else: # ASCM, OBD-II harness
|
||||
ret.openpilotLongitudinalControl = True
|
||||
ret.networkLocation = NetworkLocation.gateway
|
||||
ret.radarOffCan = False
|
||||
ret.radarUnavailable = False
|
||||
ret.pcmCruise = False # stock non-adaptive cruise control is kept off
|
||||
# supports stop and go, but initial engage must (conservatively) be above 18mph
|
||||
ret.minEnableSpeed = 18 * CV.MPH_TO_MS
|
||||
|
||||
@@ -36,7 +36,7 @@ class RadarInterface(RadarInterfaceBase):
|
||||
def __init__(self, CP):
|
||||
super().__init__(CP)
|
||||
|
||||
self.rcp = None if CP.radarOffCan else create_radar_can_parser(CP.carFingerprint)
|
||||
self.rcp = None if CP.radarUnavailable else create_radar_can_parser(CP.carFingerprint)
|
||||
|
||||
self.trigger_msg = LAST_RADAR_MSG
|
||||
self.updated_messages = set()
|
||||
|
||||
@@ -34,7 +34,7 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
if candidate in HONDA_BOSCH:
|
||||
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hondaBosch)]
|
||||
ret.radarOffCan = True
|
||||
ret.radarUnavailable = True
|
||||
|
||||
if candidate not in HONDA_BOSCH_RADARLESS:
|
||||
# Disable the radar and let openpilot control longitudinal
|
||||
|
||||
@@ -21,7 +21,7 @@ class RadarInterface(RadarInterfaceBase):
|
||||
self.track_id = 0
|
||||
self.radar_fault = False
|
||||
self.radar_wrong_config = False
|
||||
self.radar_off_can = CP.radarOffCan
|
||||
self.radar_off_can = CP.radarUnavailable
|
||||
self.radar_ts = CP.radarTimeStep
|
||||
|
||||
self.delay = int(round(0.1 / CP.radarTimeStep)) # 0.1s delay of radar
|
||||
|
||||
@@ -1440,6 +1440,7 @@ FW_VERSIONS = {
|
||||
(Ecu.eps, 0x18DA30F1, None): [
|
||||
b'39990-T39-A130\x00\x00',
|
||||
b'39990-T43-J020\x00\x00',
|
||||
b'39990-T24-T120\x00\x00',
|
||||
],
|
||||
(Ecu.gateway, 0x18DAEFF1, None): [
|
||||
b'38897-T20-A020\x00\x00',
|
||||
@@ -1447,11 +1448,13 @@ FW_VERSIONS = {
|
||||
b'38897-T21-A010\x00\x00',
|
||||
b'38897-T20-A210\x00\x00',
|
||||
b'38897-T20-A310\x00\x00',
|
||||
b'38897-T24-Z120\x00\x00',
|
||||
],
|
||||
(Ecu.srs, 0x18DA53F1, None): [
|
||||
b'77959-T20-A970\x00\x00',
|
||||
b'77959-T47-A940\x00\x00',
|
||||
b'77959-T47-A950\x00\x00',
|
||||
b'77959-T20-M820\x00\x00',
|
||||
],
|
||||
(Ecu.combinationMeter, 0x18DA60F1, None): [
|
||||
b'78108-T21-A220\x00\x00',
|
||||
@@ -1459,22 +1462,26 @@ FW_VERSIONS = {
|
||||
b'78108-T23-A110\x00\x00',
|
||||
b'78108-T21-A230\x00\x00',
|
||||
b'78108-T22-A020\x00\x00',
|
||||
b'78108-T21-MB10\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',
|
||||
b'36161-T24-T070\x00\x00',
|
||||
],
|
||||
(Ecu.vsa, 0x18DA28F1, None): [
|
||||
b'57114-T20-AB40\x00\x00',
|
||||
b'57114-T43-JB30\x00\x00',
|
||||
b'57114-T24-TB30\x00\x00',
|
||||
],
|
||||
(Ecu.transmission, 0x18da1ef1, None): [
|
||||
b'28101-65D-A020\x00\x00',
|
||||
b'28101-65D-A120\x00\x00',
|
||||
b'28101-65H-A020\x00\x00',
|
||||
b'28101-65H-A120\x00\x00',
|
||||
b'28101-65J-N010\x00\x00',
|
||||
],
|
||||
(Ecu.programmedFuelInjection, 0x18da10f1, None): [
|
||||
b'37805-64L-A540\x00\x00',
|
||||
@@ -1482,6 +1489,7 @@ FW_VERSIONS = {
|
||||
b'37805-64S-A720\x00\x00',
|
||||
b'37805-64A-A540\x00\x00',
|
||||
b'37805-64A-A620\x00\x00',
|
||||
b'37805-64D-P510\x00\x00',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -181,7 +181,8 @@ class CarController:
|
||||
if self.frame % 5 == 0 and self.car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.IONIQ, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021,
|
||||
CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_CEED, CAR.KIA_SELTOS, CAR.KONA_EV, CAR.KONA_EV_2022,
|
||||
CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.SANTA_FE_2022,
|
||||
CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.GENESIS_G70_2020, CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022):
|
||||
CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.GENESIS_G70_2020, CAR.SANTA_FE_PHEV_2022,
|
||||
CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020):
|
||||
can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled))
|
||||
|
||||
# 5 Hz ACC options
|
||||
|
||||
@@ -21,7 +21,7 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req,
|
||||
CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.GENESIS_G70_2020,
|
||||
CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022,
|
||||
CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022,
|
||||
CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022):
|
||||
CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020):
|
||||
values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1)
|
||||
values["CF_Lkas_LdwsOpt_USM"] = 2
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class CarInterface(CarInterfaceBase):
|
||||
@staticmethod
|
||||
def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
|
||||
ret.carName = "hyundai"
|
||||
ret.radarOffCan = RADAR_START_ADDR not in fingerprint[1] or DBC[ret.carFingerprint]["radar"] is None
|
||||
ret.radarUnavailable = RADAR_START_ADDR not in fingerprint[1] or DBC[ret.carFingerprint]["radar"] is None
|
||||
|
||||
# These cars have been put into dashcam only due to both a lack of users and test coverage.
|
||||
# These cars likely still work fine. Once a user confirms each car works and a test route is
|
||||
@@ -166,7 +166,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.wheelbase = 2.65
|
||||
ret.steerRatio = 13.75
|
||||
tire_stiffness_factor = 0.5
|
||||
elif candidate == CAR.KIA_K5_2021:
|
||||
elif candidate in (CAR.KIA_K5_2021, CAR.KIA_K5_HEV_2020):
|
||||
ret.mass = 3228. * CV.LB_TO_KG
|
||||
ret.wheelbase = 2.85
|
||||
ret.steerRatio = 13.27 # 2021 Kia K5 Steering Ratio (all trims)
|
||||
|
||||
@@ -37,7 +37,7 @@ class RadarInterface(RadarInterfaceBase):
|
||||
self.trigger_msg = RADAR_START_ADDR + RADAR_MSG_COUNT - 1
|
||||
self.track_id = 0
|
||||
|
||||
self.radar_off_can = CP.radarOffCan
|
||||
self.radar_off_can = CP.radarUnavailable
|
||||
self.rcp = get_radar_can_parser(CP)
|
||||
|
||||
def update(self, can_strings):
|
||||
|
||||
@@ -35,7 +35,7 @@ class CarControllerParams:
|
||||
|
||||
# To determine the limit for your car, find the maximum value that the stock LKAS will request.
|
||||
# If the max stock LKAS request is <384, add your car to this list.
|
||||
elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.HYUNDAI_GENESIS, CAR.IONIQ,
|
||||
elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.IONIQ,
|
||||
CAR.IONIQ_EV_LTD, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_PHEV,
|
||||
CAR.KIA_OPTIMA_H, CAR.KIA_SORENTO):
|
||||
self.STEER_MAX = 255
|
||||
@@ -96,6 +96,7 @@ class CAR:
|
||||
# Kia
|
||||
KIA_FORTE = "KIA FORTE E 2018 & GT 2021"
|
||||
KIA_K5_2021 = "KIA K5 2021"
|
||||
KIA_K5_HEV_2020 = "KIA K5 HYBRID 2020"
|
||||
KIA_NIRO_EV = "KIA NIRO EV 2020"
|
||||
KIA_NIRO_PHEV = "KIA NIRO HYBRID 2019"
|
||||
KIA_NIRO_HEV_2021 = "KIA NIRO HYBRID 2021"
|
||||
@@ -147,7 +148,10 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
|
||||
],
|
||||
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.HYUNDAI_GENESIS: [
|
||||
HyundaiCarInfo("Hyundai Genesis 2015-16", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_j), # TODO: check 2015 packages
|
||||
HyundaiCarInfo("Genesis G80 2017", "All", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_j),
|
||||
],
|
||||
CAR.IONIQ: HyundaiCarInfo("Hyundai Ioniq Hybrid 2017-19", harness=Harness.hyundai_c),
|
||||
CAR.IONIQ_HEV_2022: HyundaiCarInfo("Hyundai Ioniq Hybrid 2020-22", harness=Harness.hyundai_h), # TODO: confirm 2020-21 harness
|
||||
CAR.IONIQ_EV_LTD: HyundaiCarInfo("Hyundai Ioniq Electric 2019", harness=Harness.hyundai_c),
|
||||
@@ -189,13 +193,17 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
|
||||
# Kia
|
||||
CAR.KIA_FORTE: HyundaiCarInfo("Kia Forte 2019-21", harness=Harness.hyundai_g),
|
||||
CAR.KIA_K5_2021: HyundaiCarInfo("Kia K5 2021-22", harness=Harness.hyundai_a),
|
||||
CAR.KIA_K5_HEV_2020: HyundaiCarInfo("Kia K5 Hybrid 2020", harness=Harness.hyundai_a),
|
||||
CAR.KIA_NIRO_EV: [
|
||||
HyundaiCarInfo("Kia Niro EV 2019", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h),
|
||||
HyundaiCarInfo("Kia Niro EV 2020", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_f),
|
||||
HyundaiCarInfo("Kia Niro EV 2021", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_c),
|
||||
HyundaiCarInfo("Kia Niro EV 2022", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h),
|
||||
],
|
||||
CAR.KIA_NIRO_PHEV: HyundaiCarInfo("Kia Niro Plug-in Hybrid 2018-19", "All", min_enable_speed=10. * CV.MPH_TO_MS, harness=Harness.hyundai_c),
|
||||
CAR.KIA_NIRO_PHEV: [
|
||||
HyundaiCarInfo("Kia Niro Plug-in Hybrid 2018-19", "All", min_enable_speed=10. * CV.MPH_TO_MS, harness=Harness.hyundai_c),
|
||||
HyundaiCarInfo("Kia Niro Plug-in Hybrid 2020", "All", harness=Harness.hyundai_d),
|
||||
],
|
||||
CAR.KIA_NIRO_HEV_2021: [
|
||||
HyundaiCarInfo("Kia Niro Hybrid 2021", harness=Harness.hyundai_f), # TODO: could be hyundai_d, verify
|
||||
HyundaiCarInfo("Kia Niro Hybrid 2022", harness=Harness.hyundai_h),
|
||||
@@ -232,7 +240,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
|
||||
CAR.GENESIS_G70: HyundaiCarInfo("Genesis G70 2018-19", "All", harness=Harness.hyundai_f),
|
||||
CAR.GENESIS_G70_2020: HyundaiCarInfo("Genesis G70 2020", "All", harness=Harness.hyundai_f),
|
||||
CAR.GENESIS_GV70_1ST_GEN: HyundaiCarInfo("Genesis GV70 2022-23", "All", harness=Harness.hyundai_l),
|
||||
CAR.GENESIS_G80: HyundaiCarInfo("Genesis G80 2017-19", "All", harness=Harness.hyundai_h),
|
||||
CAR.GENESIS_G80: HyundaiCarInfo("Genesis G80 2018-19", "All", harness=Harness.hyundai_h),
|
||||
CAR.GENESIS_G90: HyundaiCarInfo("Genesis G90 2017-18", "All", harness=Harness.hyundai_c),
|
||||
}
|
||||
|
||||
@@ -364,6 +372,13 @@ FW_QUERY_CONFIG = FwQueryConfig(
|
||||
)
|
||||
|
||||
FW_VERSIONS = {
|
||||
CAR.HYUNDAI_GENESIS: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00DH LKAS 1.1 -150210',
|
||||
b'\xf1\x00DH LKAS 1.4 -140110',
|
||||
b'\xf1\x00DH LKAS 1.5 -140425',
|
||||
],
|
||||
},
|
||||
CAR.IONIQ: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 ',
|
||||
@@ -401,17 +416,18 @@ FW_VERSIONS = {
|
||||
},
|
||||
CAR.IONIQ_PHEV: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\000AEhe SCC FHCUP 1.00 1.02 99110-G2100 ',
|
||||
b'\xf1\x00AEhe SCC FHCUP 1.00 1.02 99110-G2100 ',
|
||||
b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2200 ',
|
||||
b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2600 ',
|
||||
b'\xf1\x00AEhe SCC F-CUP 1.00 1.02 99110-G2100 ',
|
||||
],
|
||||
(Ecu.eps, 0x7d4, None): [
|
||||
b'\xf1\000AE MDPS C 1.00 1.01 56310/G2510 4APHC101',
|
||||
b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2510 4APHC101',
|
||||
b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2560 4APHC101',
|
||||
b'\xf1\x00AE MDPS C 1.00 1.01 56310G2510\x00 4APHC101',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\000AEP MFC AT USA LHD 1.00 1.01 95740-G2600 190819',
|
||||
b'\xf1\x00AEP MFC AT USA LHD 1.00 1.01 95740-G2600 190819',
|
||||
b'\xf1\x00AEP MFC AT EUR RHD 1.00 1.01 95740-G2600 190819',
|
||||
b'\xf1\x00AEP MFC AT USA LHD 1.00 1.00 95740-G2700 201027',
|
||||
],
|
||||
@@ -424,6 +440,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x816U3J8051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00',
|
||||
b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\xad\xeb\xabt',
|
||||
b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\x00\x00\x00\x00',
|
||||
b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL0\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.IONIQ_EV_2020: {
|
||||
@@ -669,7 +686,7 @@ FW_VERSIONS = {
|
||||
(Ecu.abs, 0x7d1, None): [
|
||||
b'\xf1\x00TM ESC \r 100\x18\x031 58910-S2650',
|
||||
b'\xf1\x00TM ESC \r 103\x18\x11\x08 58910-S2650',
|
||||
b'\xf1\x00TM ESC \r 104\x19\a\b 58910-S2650',
|
||||
b'\xf1\x00TM ESC \r 104\x19\x07\x08 58910-S2650',
|
||||
b'\xf1\x00TM ESC \x02 100\x18\x030 58910-S2600',
|
||||
b'\xf1\x00TM ESC \x02 102\x18\x07\x01 58910-S2600',
|
||||
b'\xf1\x00TM ESC \x02 103\x18\x11\x07 58910-S2600',
|
||||
@@ -691,6 +708,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00TM MFC AT USA LHD 1.00 1.00 99211-S2000 180409',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS5\x00\x00\x00\x00',
|
||||
b'\xf1\x87LBJSGA7082574HG0\x87www\x98\x88\x88\x88\x99\xaa\xb9\x9afw\x86gx\x99\xa7\x89co\xf8\xffvU_\xffR\xaf\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\xa6\xe0\x91',
|
||||
b'\xf1\x87LBKSGA0458404HG0vfvg\x87www\x89\x99\xa8\x99y\xaa\xa7\x9ax\x88\xa7\x88t_\xf9\xff\x86w\x8f\xff\x15x\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\x00\x00\x00',
|
||||
b'\xf1\x87LDJUEA6010814HG1\x87w\x87x\x86gvw\x88\x88\x98\x88gw\x86wx\x88\x97\x88\x85o\xf8\xff\x86f_\xff\xd37\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4T20NS0\xf8\x19\x92g',
|
||||
@@ -1028,6 +1046,25 @@ FW_VERSIONS = {
|
||||
b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.GENESIS_G80: {
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00DH__ SCC F-CUP 1.00 1.01 96400-B1120 ',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00DH LKAS AT USA LHD 1.01 1.03 95895-B1500 180713',
|
||||
b'\xf1\x00DH LKAS AT USA LHD 1.01 1.02 95895-B1500 170810',
|
||||
b'\xf1\x00DH LKAS AT USA LHD 1.01 1.01 95895-B1500 161014',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SDH0T33NH4\xd7O\x9e\xc9',
|
||||
b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00TDH0G38NH3:-\xa9n',
|
||||
b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0G38NH2j\x9dA\x1c',
|
||||
b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0T33NH3\x97\xe6\xbc\xb8',
|
||||
],
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.GENESIS_G90: {
|
||||
(Ecu.transmission, 0x7e1, None): [b'\xf1\x87VDGMD15866192DD3x\x88x\x89wuFvvfUf\x88vWwgwwwvfVgx\x87o\xff\xbc^\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcshcm49 E14\x00\x00\x00\x00\x00\x00\x00SHI0G50NB1tc5\xb7'],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x00HI__ SCC F-CUP 1.00 1.01 96400-D2100 '],
|
||||
@@ -1116,6 +1153,23 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18',
|
||||
],
|
||||
},
|
||||
CAR.KIA_K5_HEV_2020: {
|
||||
(Ecu.fwdRadar, 0x7D0, None): [
|
||||
b'\xf1\x00DLhe SCC FHCUP 1.00 1.02 99110-L7000 ',
|
||||
],
|
||||
(Ecu.eps, 0x7D4, None): [
|
||||
b'\xf1\x00DL3 MDPS C 1.00 1.02 56310-L7000 4DLHC102',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7C4, None): [
|
||||
b'\xf1\x00DL3HMFC AT KOR LHD 1.00 1.02 99210-L2000 200309',
|
||||
],
|
||||
(Ecu.engine, 0x7E0, None): [
|
||||
b'\xf1\x87391162JLA0',
|
||||
],
|
||||
(Ecu.transmission, 0x7E1, None): [
|
||||
b'\xf1\x00PSBG2323 E08\x00\x00\x00\x00\x00\x00\x00TDL2H20KA2\xe3\xc6cz',
|
||||
],
|
||||
},
|
||||
CAR.KONA_EV: {
|
||||
(Ecu.abs, 0x7D1, None): [
|
||||
b'\xf1\x00OS IEB \r 105\x18\t\x18 58520-K4000',
|
||||
@@ -1202,22 +1256,27 @@ FW_VERSIONS = {
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'\xf1\x816H6F4051\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'\xf1\x816H6D1051\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b"\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\xf4'\\\x91",
|
||||
b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\x00\x00\x00\x00',
|
||||
b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x00\x00\x00\x00',
|
||||
b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x13\xcd\x88\x92',
|
||||
b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PDE0G16NL2&[\xc3\x01',
|
||||
],
|
||||
(Ecu.eps, 0x7D4, None): [
|
||||
b'\xf1\x00DE MDPS C 1.00 1.09 56310G5301\x00 4DEHC109',
|
||||
b'\xf1\x00DE MDPS C 1.00 1.01 56310G5520\x00 4DEPC101',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x7C4, None): [
|
||||
b'\xf1\x00DEP MFC AT USA LHD 1.00 1.01 95740-G5010 170424',
|
||||
b'\xf1\x00DEP MFC AT USA LHD 1.00 1.00 95740-G5010 170117',
|
||||
b'\xf1\x00DEP MFC AT USA LHD 1.00 1.05 99211-G5000 190826',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7D0, None): [
|
||||
b'\xf1\x00DEhe SCC H-CUP 1.01 1.02 96400-G5100 ',
|
||||
b'\xf1\x00DEhe SCC F-CUP 1.00 1.02 99110-G5100 ',
|
||||
],
|
||||
},
|
||||
CAR.KIA_NIRO_HEV_2021: {
|
||||
@@ -1493,6 +1552,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.06 99211-GI000 210813',
|
||||
b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.05 99211-GI010 220614',
|
||||
b'\xf1\x00NE1 MFC AT EUR RHD 1.00 1.01 99211-GI010 211007',
|
||||
b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.01 99211-GI010 211007',
|
||||
],
|
||||
},
|
||||
CAR.TUCSON_4TH_GEN: {
|
||||
@@ -1570,7 +1630,7 @@ FW_VERSIONS = {
|
||||
}
|
||||
|
||||
CHECKSUM = {
|
||||
"crc8": [CAR.SANTA_FE, CAR.SONATA, CAR.PALISADE, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022],
|
||||
"crc8": [CAR.SANTA_FE, CAR.SONATA, CAR.PALISADE, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.KIA_K5_HEV_2020],
|
||||
"6B": [CAR.KIA_SORENTO, CAR.HYUNDAI_GENESIS],
|
||||
}
|
||||
|
||||
@@ -1578,10 +1638,10 @@ FEATURES = {
|
||||
# which message has the gear
|
||||
"use_cluster_gears": {CAR.ELANTRA, CAR.KONA},
|
||||
"use_tcu_gears": {CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON},
|
||||
"use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.KONA_EV_2022},
|
||||
"use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.KONA_EV_2022, CAR.KIA_K5_HEV_2020},
|
||||
|
||||
# these cars use the FCA11 message for the AEB and FCW signals, all others use SCC12
|
||||
"use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022, CAR.KIA_STINGER_2022},
|
||||
"use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020},
|
||||
}
|
||||
|
||||
CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN}
|
||||
@@ -1592,7 +1652,7 @@ CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, C
|
||||
# The camera does SCC on these cars, rather than the radar
|
||||
CAMERA_SCC_CAR = {CAR.KONA_EV_2022, }
|
||||
|
||||
HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN} # these cars use a different gas signal
|
||||
HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_K5_HEV_2020} # these cars use a different gas signal
|
||||
EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5, CAR.GENESIS_GV60_EV_1ST_GEN}
|
||||
|
||||
# these cars require a special panda safety mode due to missing counters and checksums in the messages
|
||||
@@ -1617,6 +1677,7 @@ DBC = {
|
||||
CAR.IONIQ_HEV_2022: dbc_dict('hyundai_kia_generic', None),
|
||||
CAR.KIA_FORTE: dbc_dict('hyundai_kia_generic', None),
|
||||
CAR.KIA_K5_2021: dbc_dict('hyundai_kia_generic', None),
|
||||
CAR.KIA_K5_HEV_2020: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
|
||||
CAR.KIA_NIRO_EV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
|
||||
CAR.KIA_NIRO_PHEV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
|
||||
CAR.KIA_NIRO_HEV_2021: dbc_dict('hyundai_kia_generic', None),
|
||||
|
||||
@@ -401,15 +401,15 @@ class CarStateBase(ABC):
|
||||
return GearShifter.unknown
|
||||
|
||||
d: Dict[str, car.CarState.GearShifter] = {
|
||||
'P': GearShifter.park, 'PARK': GearShifter.park,
|
||||
'R': GearShifter.reverse, 'REVERSE': GearShifter.reverse,
|
||||
'N': GearShifter.neutral, 'NEUTRAL': GearShifter.neutral,
|
||||
'E': GearShifter.eco, 'ECO': GearShifter.eco,
|
||||
'T': GearShifter.manumatic, 'MANUAL': GearShifter.manumatic,
|
||||
'D': GearShifter.drive, 'DRIVE': GearShifter.drive,
|
||||
'S': GearShifter.sport, 'SPORT': GearShifter.sport,
|
||||
'L': GearShifter.low, 'LOW': GearShifter.low,
|
||||
'B': GearShifter.brake, 'BRAKE': GearShifter.brake,
|
||||
'P': GearShifter.park, 'PARK': GearShifter.park,
|
||||
'R': GearShifter.reverse, 'REVERSE': GearShifter.reverse,
|
||||
'N': GearShifter.neutral, 'NEUTRAL': GearShifter.neutral,
|
||||
'E': GearShifter.eco, 'ECO': GearShifter.eco,
|
||||
'T': GearShifter.manumatic, 'MANUAL': GearShifter.manumatic,
|
||||
'D': GearShifter.drive, 'DRIVE': GearShifter.drive,
|
||||
'S': GearShifter.sport, 'SPORT': GearShifter.sport,
|
||||
'L': GearShifter.low, 'LOW': GearShifter.low,
|
||||
'B': GearShifter.brake, 'BRAKE': GearShifter.brake,
|
||||
}
|
||||
return d.get(gear.upper(), GearShifter.unknown)
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class CarInterface(CarInterfaceBase):
|
||||
def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
|
||||
ret.carName = "mazda"
|
||||
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.mazda)]
|
||||
ret.radarOffCan = True
|
||||
ret.radarUnavailable = True
|
||||
|
||||
ret.dashcamOnly = candidate not in (CAR.CX5_2022, CAR.CX9_2021)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.steerRatio = 17
|
||||
|
||||
ret.steerControlType = car.CarParams.SteerControlType.angle
|
||||
ret.radarOffCan = True
|
||||
ret.radarUnavailable = True
|
||||
|
||||
if candidate in (CAR.ROGUE, CAR.XTRAIL):
|
||||
ret.mass = 1610 + STD_CARGO_KG
|
||||
|
||||
@@ -11,7 +11,7 @@ class CarInterface(CarInterfaceBase):
|
||||
@staticmethod
|
||||
def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
|
||||
ret.carName = "subaru"
|
||||
ret.radarOffCan = True
|
||||
ret.radarUnavailable = True
|
||||
ret.dashcamOnly = candidate in PREGLOBAL_CARS
|
||||
ret.autoResumeSng = False
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ non_tested_cars = [
|
||||
HYUNDAI.GENESIS_G90,
|
||||
HYUNDAI.KIA_OPTIMA_H,
|
||||
HONDA.ODYSSEY_CHN,
|
||||
VOLKSWAGEN.CRAFTER_MK2, # need a route from an ACC-equipped Crafter
|
||||
]
|
||||
|
||||
CarTestRoute = namedtuple('CarTestRoute', ['route', 'car_model', 'segment'], defaults=(None,))
|
||||
@@ -122,6 +123,7 @@ routes = [
|
||||
CarTestRoute("d545129f3ca90f28|2022-10-19--09-22-54", HYUNDAI.KIA_EV6), # HDA2
|
||||
CarTestRoute("68d6a96e703c00c9|2022-09-10--16-09-39", HYUNDAI.KIA_EV6), # HDA1
|
||||
CarTestRoute("007d5e4ad9f86d13|2021-09-30--15-09-23", HYUNDAI.KIA_K5_2021),
|
||||
CarTestRoute("c58dfc9fc16590e0|2023-01-14--13-51-48", HYUNDAI.KIA_K5_HEV_2020),
|
||||
CarTestRoute("50c6c9b85fd1ff03|2020-10-26--17-56-06", HYUNDAI.KIA_NIRO_EV),
|
||||
CarTestRoute("173219cf50acdd7b|2021-07-05--10-27-41", HYUNDAI.KIA_NIRO_PHEV),
|
||||
CarTestRoute("34a875f29f69841a|2021-07-29--13-02-09", HYUNDAI.KIA_NIRO_HEV_2021),
|
||||
|
||||
@@ -76,7 +76,7 @@ class TestCarInterfaces(unittest.TestCase):
|
||||
|
||||
# Run radar interface once
|
||||
radar_interface.update([])
|
||||
if not car_params.radarOffCan and radar_interface.rcp is not None and \
|
||||
if not car_params.radarUnavailable and radar_interface.rcp is not None and \
|
||||
hasattr(radar_interface, '_update') and hasattr(radar_interface, 'trigger_msg'):
|
||||
radar_interface._update([radar_interface.trigger_msg])
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ HONDA ODYSSEY 2018: [1.8774809275211801, 0.8394431662987996, 0.2096978613792822]
|
||||
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]
|
||||
HYUNDAI GENESIS 2015-2016: [1.8466226943929824, 1.5552063647830634, 0.0984484465421171]
|
||||
HYUNDAI GENESIS 2015-2016: [2.7807965280270794, 2.325, 0.0984484465421171]
|
||||
HYUNDAI IONIQ 5 2022: [3.172929, 2.713050, 0.096019]
|
||||
HYUNDAI IONIQ ELECTRIC LIMITED 2019: [1.7662975472852054, 1.613755614526594, 0.17087579756306276]
|
||||
HYUNDAI IONIQ PHEV 2020: [3.2928700076638537, 2.1193482926455656, 0.12463700961468778]
|
||||
|
||||
@@ -37,6 +37,7 @@ HYUNDAI ELANTRA HYBRID 2021: HYUNDAI SONATA 2020
|
||||
HYUNDAI TUCSON 2019: HYUNDAI SANTA FE 2019
|
||||
HYUNDAI TUCSON 4TH GEN: HYUNDAI TUCSON HYBRID 4TH GEN
|
||||
HYUNDAI SANTA FE 2022: HYUNDAI SANTA FE HYBRID 2022
|
||||
KIA K5 HYBRID 2020: KIA K5 2021
|
||||
KIA STINGER 2022: KIA STINGER GT2 2018
|
||||
GENESIS G90 2017: GENESIS G70 2018
|
||||
GENESIS G80 2017: GENESIS G70 2018
|
||||
@@ -59,6 +60,7 @@ SKODA SCALA 1ST GEN: SKODA SUPERB 3RD GEN
|
||||
SKODA KODIAQ 1ST GEN: SKODA SUPERB 3RD GEN
|
||||
SKODA KAROQ 1ST GEN: SKODA SUPERB 3RD GEN
|
||||
SKODA KAMIQ 1ST GEN: SKODA SUPERB 3RD GEN
|
||||
VOLKSWAGEN CRAFTER 2ND GEN: VOLKSWAGEN TIGUAN 2ND GEN
|
||||
VOLKSWAGEN T-ROC 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN
|
||||
VOLKSWAGEN T-CROSS 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN
|
||||
VOLKSWAGEN TOURAN 2ND GEN: VOLKSWAGEN TIGUAN 2ND GEN
|
||||
|
||||
@@ -143,7 +143,7 @@ class CarController:
|
||||
# forcing the pcm to disengage causes a bad fault sound so play a good sound instead
|
||||
send_ui = True
|
||||
|
||||
if self.frame % 100 == 0 or send_ui:
|
||||
if self.frame % 20 == 0 or send_ui:
|
||||
can_sends.append(create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, hud_control.leftLaneVisible,
|
||||
hud_control.rightLaneVisible, hud_control.leftLaneDepart,
|
||||
hud_control.rightLaneDepart, CC.enabled, CS.lkas_hud))
|
||||
|
||||
@@ -125,6 +125,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = {
|
||||
],
|
||||
CAR.COROLLAH_TSS2: [
|
||||
ToyotaCarInfo("Toyota Corolla Hybrid 2020-22"),
|
||||
ToyotaCarInfo("Toyota Corolla Hybrid (Non-US only) 2020-23", min_enable_speed=7.5),
|
||||
ToyotaCarInfo("Toyota Corolla Cross Hybrid (Non-US only) 2020-22", min_enable_speed=7.5),
|
||||
ToyotaCarInfo("Lexus UX Hybrid 2019-22"),
|
||||
],
|
||||
@@ -841,11 +842,13 @@ FW_VERSIONS = {
|
||||
b'\x02896630A07000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
|
||||
b'\x02896630A21000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
|
||||
b'\x02896630ZJ5000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
|
||||
b'\x02896630ZK8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
|
||||
b'\x02896630ZN8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
|
||||
b'\x02896630ZQ3000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
|
||||
b'\x02896630ZR2000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
|
||||
b'\x02896630ZT8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
|
||||
b'\x02896630ZT9000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
|
||||
b'\x02896630ZZ0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
|
||||
b'\x028966312K6000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
|
||||
b'\x028966312L0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
|
||||
b'\x028966312Q3000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
|
||||
@@ -858,6 +861,7 @@ FW_VERSIONS = {
|
||||
b'8965B12361\x00\x00\x00\x00\x00\x00',
|
||||
b'8965B12451\x00\x00\x00\x00\x00\x00',
|
||||
b'8965B16011\x00\x00\x00\x00\x00\x00',
|
||||
b'8965B16101\x00\x00\x00\x00\x00\x00',
|
||||
b'8965B76012\x00\x00\x00\x00\x00\x00',
|
||||
b'8965B76050\x00\x00\x00\x00\x00\x00',
|
||||
b'\x018965B12350\x00\x00\x00\x00\x00\x00',
|
||||
@@ -905,6 +909,7 @@ FW_VERSIONS = {
|
||||
b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
|
||||
b'\x028646F1202200\x00\x00\x00\x008646G2601500\x00\x00\x00\x00',
|
||||
b'\x028646F1601100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
|
||||
b'\x028646F1601200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
|
||||
b"\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00",
|
||||
b'\x028646F4203400\x00\x00\x00\x008646G2601200\x00\x00\x00\x00',
|
||||
b'\x028646F76020C0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00',
|
||||
|
||||
@@ -23,7 +23,7 @@ class CarInterface(CarInterfaceBase):
|
||||
@staticmethod
|
||||
def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
|
||||
ret.carName = "volkswagen"
|
||||
ret.radarOffCan = True
|
||||
ret.radarUnavailable = True
|
||||
|
||||
use_off_car_defaults = len(fingerprint[0]) == 0 # Pick sensible carParams during offline doc generation/CI jobs
|
||||
|
||||
@@ -107,6 +107,11 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.mass = 2011 + STD_CARGO_KG
|
||||
ret.wheelbase = 2.98
|
||||
|
||||
elif candidate == CAR.CRAFTER_MK2:
|
||||
ret.mass = 2100 + STD_CARGO_KG
|
||||
ret.wheelbase = 3.64 # SWB, LWB is 4.49, TBD how to detect difference
|
||||
ret.minSteerSpeed = 50 * CV.KPH_TO_MS
|
||||
|
||||
elif candidate == CAR.GOLF_MK7:
|
||||
ret.mass = 1397 + STD_CARGO_KG
|
||||
ret.wheelbase = 2.62
|
||||
|
||||
@@ -111,6 +111,7 @@ class CANBUS:
|
||||
class CAR:
|
||||
ARTEON_MK1 = "VOLKSWAGEN ARTEON 1ST GEN" # Chassis AN, Mk1 VW Arteon and variants
|
||||
ATLAS_MK1 = "VOLKSWAGEN ATLAS 1ST GEN" # Chassis CA, Mk1 VW Atlas and Atlas Cross Sport
|
||||
CRAFTER_MK2 = "VOLKSWAGEN CRAFTER 2ND GEN" # Chassis SY/SZ, Mk2 VW Crafter, VW Grand California, MAN TGE
|
||||
GOLF_MK7 = "VOLKSWAGEN GOLF 7TH GEN" # Chassis 5G/AU/BA/BE, Mk7 VW Golf and variants
|
||||
JETTA_MK7 = "VOLKSWAGEN JETTA 7TH GEN" # Chassis BU, Mk7 VW Jetta
|
||||
PASSAT_MK8 = "VOLKSWAGEN PASSAT 8TH GEN" # Chassis 3G, Mk8 VW Passat and variants
|
||||
@@ -122,7 +123,7 @@ class CAR:
|
||||
TIGUAN_MK2 = "VOLKSWAGEN TIGUAN 2ND GEN" # Chassis AD/BW, Mk2 VW Tiguan and variants
|
||||
TOURAN_MK2 = "VOLKSWAGEN TOURAN 2ND GEN" # Chassis 1T, Mk2 VW Touran and variants
|
||||
TRANSPORTER_T61 = "VOLKSWAGEN TRANSPORTER T6.1" # Chassis 7H/7L, T6-facelift Transporter/Multivan/Caravelle/California
|
||||
TROC_MK1 = "VOLKSWAGEN T-ROC 1ST GEN" # Chassis A1, Mk1 VW VW T-Roc and variants
|
||||
TROC_MK1 = "VOLKSWAGEN T-ROC 1ST GEN" # Chassis A1, Mk1 VW T-Roc and variants
|
||||
AUDI_A3_MK3 = "AUDI A3 3RD GEN" # Chassis 8V/FF, Mk3 Audi A3 and variants
|
||||
AUDI_Q2_MK1 = "AUDI Q2 1ST GEN" # Chassis GA, Mk1 Audi Q2 (RoW) and Q2L (China only)
|
||||
AUDI_Q3_MK2 = "AUDI Q3 2ND GEN" # Chassis 8U/F3/FS, Mk2 Audi Q3 and variants
|
||||
@@ -184,6 +185,13 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = {
|
||||
VWCarInfo("Volkswagen Teramont Cross Sport 2021-22"),
|
||||
VWCarInfo("Volkswagen Teramont X 2021-22"),
|
||||
],
|
||||
CAR.CRAFTER_MK2: [
|
||||
VWCarInfo("Volkswagen Crafter 2017-23", video_link="https://youtu.be/4100gLeabmo"),
|
||||
VWCarInfo("Volkswagen e-Crafter 2018-23", video_link="https://youtu.be/4100gLeabmo"),
|
||||
VWCarInfo("Volkswagen Grand California 2019-23", video_link="https://youtu.be/4100gLeabmo"),
|
||||
VWCarInfo("MAN TGE 2017-23", video_link="https://youtu.be/4100gLeabmo"),
|
||||
VWCarInfo("MAN eTGE 2020-23", video_link="https://youtu.be/4100gLeabmo"),
|
||||
],
|
||||
CAR.GOLF_MK7: [
|
||||
VWCarInfo("Volkswagen e-Golf 2014-20"),
|
||||
VWCarInfo("Volkswagen Golf 2015-20"),
|
||||
@@ -352,6 +360,23 @@ FW_VERSIONS = {
|
||||
b'\xf1\x875Q0907572P \xf1\x890682',
|
||||
],
|
||||
},
|
||||
CAR.CRAFTER_MK2: {
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'\xf1\x8704L906056EK\xf1\x896391',
|
||||
],
|
||||
# Only current upstreamed vehicle has a manual transmission
|
||||
#(Ecu.transmission, 0x7e1, None): [
|
||||
#],
|
||||
(Ecu.srs, 0x715, None): [
|
||||
b'\xf1\x873Q0959655BG\xf1\x890703\xf1\x82\x0e16120016130012051G1313052900',
|
||||
],
|
||||
(Ecu.eps, 0x712, None): [
|
||||
b'\xf1\x872N0909143E \xf1\x897021\xf1\x82\x05163AZ306A2',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x757, None): [
|
||||
b'\xf1\x872Q0907572M \xf1\x890233',
|
||||
],
|
||||
},
|
||||
CAR.GOLF_MK7: {
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'\xf1\x8704E906016A \xf1\x897697',
|
||||
@@ -942,16 +967,19 @@ FW_VERSIONS = {
|
||||
b'\xf1\x8704L906021EL\xf1\x897542',
|
||||
b'\xf1\x8704L906026BP\xf1\x891198',
|
||||
b'\xf1\x8704L906026BP\xf1\x897608',
|
||||
b'\xf1\x8704L906056CR\xf1\x892797',
|
||||
b'\xf1\x8705E906018AS\xf1\x899596',
|
||||
b'\xf1\x878V0906264H \xf1\x890005',
|
||||
],
|
||||
(Ecu.transmission, 0x7e1, None): [
|
||||
b'\xf1\x870CW300041D \xf1\x891004',
|
||||
b'\xf1\x870CW300041G \xf1\x891003',
|
||||
b'\xf1\x870CW300050J \xf1\x891908',
|
||||
b'\xf1\x870D9300042M \xf1\x895016',
|
||||
],
|
||||
(Ecu.srs, 0x715, None): [
|
||||
b'\xf1\x873Q0959655AC\xf1\x890189\xf1\x82\r11110011110011021511110200',
|
||||
b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r11110011110011021511110200',
|
||||
b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r12110012120012021612110200',
|
||||
b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1312001313001305171311052900',
|
||||
b'\xf1\x873Q0959655CM\xf1\x890720\xf1\x82\0161312001313001305171311052900',
|
||||
@@ -965,6 +993,7 @@ FW_VERSIONS = {
|
||||
(Ecu.fwdRadar, 0x757, None): [
|
||||
b'\xf1\x875Q0907572B \xf1\x890200\xf1\x82\00101',
|
||||
b'\xf1\x875Q0907572H \xf1\x890620',
|
||||
b'\xf1\x875Q0907572K \xf1\x890402\xf1\x82\x0101',
|
||||
b'\xf1\x875Q0907572P \xf1\x890682',
|
||||
],
|
||||
},
|
||||
|
||||
@@ -173,6 +173,7 @@ class Controls:
|
||||
self.cruise_mismatch_counter = 0
|
||||
self.can_rcv_timeout_counter = 0
|
||||
self.last_blinker_frame = 0
|
||||
self.last_steering_pressed_frame = 0
|
||||
self.distance_traveled = 0
|
||||
self.last_functional_fan_frame = 0
|
||||
self.events_prev = []
|
||||
@@ -623,29 +624,34 @@ class Controls:
|
||||
lac_log.output = actuators.steer
|
||||
lac_log.saturated = abs(actuators.steer) >= 0.9
|
||||
|
||||
if CS.steeringPressed:
|
||||
self.last_steering_pressed_frame = self.sm.frame
|
||||
recent_steer_pressed = (self.sm.frame - self.last_steering_pressed_frame)*DT_CTRL < 2.0
|
||||
|
||||
# Send a "steering required alert" if saturation count has reached the limit
|
||||
if lac_log.active and not CS.steeringPressed and self.CP.lateralTuning.which() == 'torque' and not self.joystick_mode:
|
||||
undershooting = abs(lac_log.desiredLateralAccel) / abs(1e-3 + lac_log.actualLateralAccel) > 1.2
|
||||
turning = abs(lac_log.desiredLateralAccel) > 1.0
|
||||
good_speed = CS.vEgo > 5
|
||||
max_torque = abs(self.last_actuators.steer) > 0.99
|
||||
if undershooting and turning and good_speed and max_torque:
|
||||
self.events.add(EventName.steerSaturated)
|
||||
elif lac_log.active and lac_log.saturated:
|
||||
dpath_points = lat_plan.dPathPoints
|
||||
if len(dpath_points):
|
||||
# Check if we deviated from the path
|
||||
# TODO use desired vs actual curvature
|
||||
if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
|
||||
steering_value = actuators.steeringAngleDeg
|
||||
else:
|
||||
steering_value = actuators.steer
|
||||
if lac_log.active and not recent_steer_pressed:
|
||||
if self.CP.lateralTuning.which() == 'torque' and not self.joystick_mode:
|
||||
undershooting = abs(lac_log.desiredLateralAccel) / abs(1e-3 + lac_log.actualLateralAccel) > 1.2
|
||||
turning = abs(lac_log.desiredLateralAccel) > 1.0
|
||||
good_speed = CS.vEgo > 5
|
||||
max_torque = abs(self.last_actuators.steer) > 0.99
|
||||
if undershooting and turning and good_speed and max_torque:
|
||||
lac_log.active and self.events.add(EventName.steerSaturated)
|
||||
elif lac_log.saturated:
|
||||
dpath_points = lat_plan.dPathPoints
|
||||
if len(dpath_points):
|
||||
# Check if we deviated from the path
|
||||
# TODO use desired vs actual curvature
|
||||
if self.CP.steerControlType == car.CarParams.SteerControlType.angle:
|
||||
steering_value = actuators.steeringAngleDeg
|
||||
else:
|
||||
steering_value = actuators.steer
|
||||
|
||||
left_deviation = steering_value > 0 and dpath_points[0] < -0.20
|
||||
right_deviation = steering_value < 0 and dpath_points[0] > 0.20
|
||||
left_deviation = steering_value > 0 and dpath_points[0] < -0.20
|
||||
right_deviation = steering_value < 0 and dpath_points[0] > 0.20
|
||||
|
||||
if left_deviation or right_deviation:
|
||||
self.events.add(EventName.steerSaturated)
|
||||
if left_deviation or right_deviation:
|
||||
self.events.add(EventName.steerSaturated)
|
||||
|
||||
# Ensure no NaNs/Infs
|
||||
for p in ACTUATOR_FIELDS:
|
||||
|
||||
@@ -52,6 +52,10 @@ SUPPORTED_FW_VERSIONS = {
|
||||
b'IK__ SCC F-CUP 1.00 1.02 96400-G9100\x18\x07\x06\x17\x12 ': ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
# 2019 SANTA FE
|
||||
b"TM__ SCC F-CUP 1.00 1.00 99110-S1210\x19\x01%\x168 ": ConfigValues(
|
||||
default_config=b"\x00\x00\x00\x01\x00\x00",
|
||||
tracks_enabled=b"\x00\x00\x00\x01\x00\x01"),
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -3,3 +3,4 @@ ubloxd_test
|
||||
params_learner
|
||||
paramsd
|
||||
locationd
|
||||
test/test_glonass_runner
|
||||
|
||||
@@ -13,7 +13,8 @@ if GetOption('kaitai'):
|
||||
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)
|
||||
glonass_obj = env.Object('generated/glonass.cpp')
|
||||
env.Program("ubloxd", ["ubloxd.cc", "ublox_msg.cc", "generated/ubx.cpp", "generated/gps.cpp", glonass_obj], LIBS=loc_libs)
|
||||
|
||||
ekf_sym_cc = env.SharedObject("#rednose/helpers/ekf_sym.cc")
|
||||
locationd_sources = ["locationd.cc", "models/live_kf.cc", ekf_sym_cc]
|
||||
@@ -25,3 +26,6 @@ lenv.Depends(locationd, libkf)
|
||||
if File("liblocationd.cc").exists():
|
||||
liblocationd = lenv.SharedLibrary("liblocationd", ["liblocationd.cc"] + locationd_sources, LIBS=loc_libs + transformations)
|
||||
lenv.Depends(liblocationd, libkf)
|
||||
|
||||
if GetOption('test'):
|
||||
env.Program("test/test_glonass_runner", ['test/test_glonass_runner.cc', 'test/test_glonass_kaitai.cc', glonass_obj], LIBS=[loc_libs])
|
||||
@@ -31,6 +31,10 @@ void glonass_t::_read() {
|
||||
m_data = new string_3_t(m__io, this, m__root);
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
m_data = new string_5_t(m__io, this, m__root);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
m_data = new string_2_t(m__io, this, m__root);
|
||||
break;
|
||||
@@ -60,6 +64,8 @@ void glonass_t::_clean_up() {
|
||||
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;
|
||||
f_tau_n = false;
|
||||
f_delta_tau_n = false;
|
||||
|
||||
try {
|
||||
_read();
|
||||
@@ -70,8 +76,10 @@ glonass_t::string_4_t::string_4_t(kaitai::kstream* p__io, glonass_t* p__parent,
|
||||
}
|
||||
|
||||
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_tau_n_sign = m__io->read_bits_int_be(1);
|
||||
m_tau_n_value = m__io->read_bits_int_be(21);
|
||||
m_delta_tau_n_sign = m__io->read_bits_int_be(1);
|
||||
m_delta_tau_n_value = m__io->read_bits_int_be(4);
|
||||
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);
|
||||
@@ -89,6 +97,22 @@ glonass_t::string_4_t::~string_4_t() {
|
||||
void glonass_t::string_4_t::_clean_up() {
|
||||
}
|
||||
|
||||
int32_t glonass_t::string_4_t::tau_n() {
|
||||
if (f_tau_n)
|
||||
return m_tau_n;
|
||||
m_tau_n = ((tau_n_sign()) ? ((tau_n_value() * -1)) : (tau_n_value()));
|
||||
f_tau_n = true;
|
||||
return m_tau_n;
|
||||
}
|
||||
|
||||
int32_t glonass_t::string_4_t::delta_tau_n() {
|
||||
if (f_delta_tau_n)
|
||||
return m_delta_tau_n;
|
||||
m_delta_tau_n = ((delta_tau_n_sign()) ? ((delta_tau_n_value() * -1)) : (delta_tau_n_value()));
|
||||
f_delta_tau_n = true;
|
||||
return m_delta_tau_n;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -113,9 +137,40 @@ glonass_t::string_non_immediate_t::~string_non_immediate_t() {
|
||||
void glonass_t::string_non_immediate_t::_clean_up() {
|
||||
}
|
||||
|
||||
glonass_t::string_5_t::string_5_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_5_t::_read() {
|
||||
m_n_a = m__io->read_bits_int_be(11);
|
||||
m_tau_c = m__io->read_bits_int_be(32);
|
||||
m_not_used = m__io->read_bits_int_be(1);
|
||||
m_n_4 = m__io->read_bits_int_be(5);
|
||||
m_tau_gps = m__io->read_bits_int_be(22);
|
||||
m_l_n = m__io->read_bits_int_be(1);
|
||||
}
|
||||
|
||||
glonass_t::string_5_t::~string_5_t() {
|
||||
_clean_up();
|
||||
}
|
||||
|
||||
void glonass_t::string_5_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;
|
||||
f_x_vel = false;
|
||||
f_x_accel = false;
|
||||
f_x = false;
|
||||
|
||||
try {
|
||||
_read();
|
||||
@@ -129,9 +184,12 @@ 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);
|
||||
m_x_vel_sign = m__io->read_bits_int_be(1);
|
||||
m_x_vel_value = m__io->read_bits_int_be(23);
|
||||
m_x_accel_sign = m__io->read_bits_int_be(1);
|
||||
m_x_accel_value = m__io->read_bits_int_be(4);
|
||||
m_x_sign = m__io->read_bits_int_be(1);
|
||||
m_x_value = m__io->read_bits_int_be(26);
|
||||
}
|
||||
|
||||
glonass_t::string_1_t::~string_1_t() {
|
||||
@@ -141,9 +199,36 @@ glonass_t::string_1_t::~string_1_t() {
|
||||
void glonass_t::string_1_t::_clean_up() {
|
||||
}
|
||||
|
||||
int32_t glonass_t::string_1_t::x_vel() {
|
||||
if (f_x_vel)
|
||||
return m_x_vel;
|
||||
m_x_vel = ((x_vel_sign()) ? ((x_vel_value() * -1)) : (x_vel_value()));
|
||||
f_x_vel = true;
|
||||
return m_x_vel;
|
||||
}
|
||||
|
||||
int32_t glonass_t::string_1_t::x_accel() {
|
||||
if (f_x_accel)
|
||||
return m_x_accel;
|
||||
m_x_accel = ((x_accel_sign()) ? ((x_accel_value() * -1)) : (x_accel_value()));
|
||||
f_x_accel = true;
|
||||
return m_x_accel;
|
||||
}
|
||||
|
||||
int32_t glonass_t::string_1_t::x() {
|
||||
if (f_x)
|
||||
return m_x;
|
||||
m_x = ((x_sign()) ? ((x_value() * -1)) : (x_value()));
|
||||
f_x = true;
|
||||
return m_x;
|
||||
}
|
||||
|
||||
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;
|
||||
f_y_vel = false;
|
||||
f_y_accel = false;
|
||||
f_y = false;
|
||||
|
||||
try {
|
||||
_read();
|
||||
@@ -158,9 +243,12 @@ void glonass_t::string_2_t::_read() {
|
||||
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);
|
||||
m_y_vel_sign = m__io->read_bits_int_be(1);
|
||||
m_y_vel_value = m__io->read_bits_int_be(23);
|
||||
m_y_accel_sign = m__io->read_bits_int_be(1);
|
||||
m_y_accel_value = m__io->read_bits_int_be(4);
|
||||
m_y_sign = m__io->read_bits_int_be(1);
|
||||
m_y_value = m__io->read_bits_int_be(26);
|
||||
}
|
||||
|
||||
glonass_t::string_2_t::~string_2_t() {
|
||||
@@ -170,9 +258,37 @@ glonass_t::string_2_t::~string_2_t() {
|
||||
void glonass_t::string_2_t::_clean_up() {
|
||||
}
|
||||
|
||||
int32_t glonass_t::string_2_t::y_vel() {
|
||||
if (f_y_vel)
|
||||
return m_y_vel;
|
||||
m_y_vel = ((y_vel_sign()) ? ((y_vel_value() * -1)) : (y_vel_value()));
|
||||
f_y_vel = true;
|
||||
return m_y_vel;
|
||||
}
|
||||
|
||||
int32_t glonass_t::string_2_t::y_accel() {
|
||||
if (f_y_accel)
|
||||
return m_y_accel;
|
||||
m_y_accel = ((y_accel_sign()) ? ((y_accel_value() * -1)) : (y_accel_value()));
|
||||
f_y_accel = true;
|
||||
return m_y_accel;
|
||||
}
|
||||
|
||||
int32_t glonass_t::string_2_t::y() {
|
||||
if (f_y)
|
||||
return m_y;
|
||||
m_y = ((y_sign()) ? ((y_value() * -1)) : (y_value()));
|
||||
f_y = true;
|
||||
return m_y;
|
||||
}
|
||||
|
||||
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;
|
||||
f_gamma_n = false;
|
||||
f_z_vel = false;
|
||||
f_z_accel = false;
|
||||
f_z = false;
|
||||
|
||||
try {
|
||||
_read();
|
||||
@@ -184,13 +300,17 @@ glonass_t::string_3_t::string_3_t(kaitai::kstream* p__io, glonass_t* p__parent,
|
||||
|
||||
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_gamma_n_sign = m__io->read_bits_int_be(1);
|
||||
m_gamma_n_value = m__io->read_bits_int_be(10);
|
||||
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);
|
||||
m_z_vel_sign = m__io->read_bits_int_be(1);
|
||||
m_z_vel_value = m__io->read_bits_int_be(23);
|
||||
m_z_accel_sign = m__io->read_bits_int_be(1);
|
||||
m_z_accel_value = m__io->read_bits_int_be(4);
|
||||
m_z_sign = m__io->read_bits_int_be(1);
|
||||
m_z_value = m__io->read_bits_int_be(26);
|
||||
}
|
||||
|
||||
glonass_t::string_3_t::~string_3_t() {
|
||||
@@ -199,3 +319,35 @@ glonass_t::string_3_t::~string_3_t() {
|
||||
|
||||
void glonass_t::string_3_t::_clean_up() {
|
||||
}
|
||||
|
||||
int32_t glonass_t::string_3_t::gamma_n() {
|
||||
if (f_gamma_n)
|
||||
return m_gamma_n;
|
||||
m_gamma_n = ((gamma_n_sign()) ? ((gamma_n_value() * -1)) : (gamma_n_value()));
|
||||
f_gamma_n = true;
|
||||
return m_gamma_n;
|
||||
}
|
||||
|
||||
int32_t glonass_t::string_3_t::z_vel() {
|
||||
if (f_z_vel)
|
||||
return m_z_vel;
|
||||
m_z_vel = ((z_vel_sign()) ? ((z_vel_value() * -1)) : (z_vel_value()));
|
||||
f_z_vel = true;
|
||||
return m_z_vel;
|
||||
}
|
||||
|
||||
int32_t glonass_t::string_3_t::z_accel() {
|
||||
if (f_z_accel)
|
||||
return m_z_accel;
|
||||
m_z_accel = ((z_accel_sign()) ? ((z_accel_value() * -1)) : (z_accel_value()));
|
||||
f_z_accel = true;
|
||||
return m_z_accel;
|
||||
}
|
||||
|
||||
int32_t glonass_t::string_3_t::z() {
|
||||
if (f_z)
|
||||
return m_z;
|
||||
m_z = ((z_sign()) ? ((z_value() * -1)) : (z_value()));
|
||||
f_z = true;
|
||||
return m_z;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ class glonass_t : public kaitai::kstruct {
|
||||
public:
|
||||
class string_4_t;
|
||||
class string_non_immediate_t;
|
||||
class string_5_t;
|
||||
class string_1_t;
|
||||
class string_2_t;
|
||||
class string_3_t;
|
||||
@@ -42,8 +43,24 @@ public:
|
||||
~string_4_t();
|
||||
|
||||
private:
|
||||
uint64_t m_tau_n;
|
||||
uint64_t m_delta_tau_n;
|
||||
bool f_tau_n;
|
||||
int32_t m_tau_n;
|
||||
|
||||
public:
|
||||
int32_t tau_n();
|
||||
|
||||
private:
|
||||
bool f_delta_tau_n;
|
||||
int32_t m_delta_tau_n;
|
||||
|
||||
public:
|
||||
int32_t delta_tau_n();
|
||||
|
||||
private:
|
||||
bool m_tau_n_sign;
|
||||
uint64_t m_tau_n_value;
|
||||
bool m_delta_tau_n_sign;
|
||||
uint64_t m_delta_tau_n_value;
|
||||
uint64_t m_e_n;
|
||||
uint64_t m_not_used_1;
|
||||
bool m_p4;
|
||||
@@ -56,8 +73,10 @@ public:
|
||||
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; }
|
||||
bool tau_n_sign() const { return m_tau_n_sign; }
|
||||
uint64_t tau_n_value() const { return m_tau_n_value; }
|
||||
bool delta_tau_n_sign() const { return m_delta_tau_n_sign; }
|
||||
uint64_t delta_tau_n_value() const { return m_delta_tau_n_value; }
|
||||
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; }
|
||||
@@ -96,6 +115,40 @@ public:
|
||||
glonass_t* _parent() const { return m__parent; }
|
||||
};
|
||||
|
||||
class string_5_t : public kaitai::kstruct {
|
||||
|
||||
public:
|
||||
|
||||
string_5_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0);
|
||||
|
||||
private:
|
||||
void _read();
|
||||
void _clean_up();
|
||||
|
||||
public:
|
||||
~string_5_t();
|
||||
|
||||
private:
|
||||
uint64_t m_n_a;
|
||||
uint64_t m_tau_c;
|
||||
bool m_not_used;
|
||||
uint64_t m_n_4;
|
||||
uint64_t m_tau_gps;
|
||||
bool m_l_n;
|
||||
glonass_t* m__root;
|
||||
glonass_t* m__parent;
|
||||
|
||||
public:
|
||||
uint64_t n_a() const { return m_n_a; }
|
||||
uint64_t tau_c() const { return m_tau_c; }
|
||||
bool not_used() const { return m_not_used; }
|
||||
uint64_t n_4() const { return m_n_4; }
|
||||
uint64_t tau_gps() const { return m_tau_gps; }
|
||||
bool l_n() const { return m_l_n; }
|
||||
glonass_t* _root() const { return m__root; }
|
||||
glonass_t* _parent() const { return m__parent; }
|
||||
};
|
||||
|
||||
class string_1_t : public kaitai::kstruct {
|
||||
|
||||
public:
|
||||
@@ -109,13 +162,37 @@ public:
|
||||
public:
|
||||
~string_1_t();
|
||||
|
||||
private:
|
||||
bool f_x_vel;
|
||||
int32_t m_x_vel;
|
||||
|
||||
public:
|
||||
int32_t x_vel();
|
||||
|
||||
private:
|
||||
bool f_x_accel;
|
||||
int32_t m_x_accel;
|
||||
|
||||
public:
|
||||
int32_t x_accel();
|
||||
|
||||
private:
|
||||
bool f_x;
|
||||
int32_t m_x;
|
||||
|
||||
public:
|
||||
int32_t x();
|
||||
|
||||
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;
|
||||
bool m_x_vel_sign;
|
||||
uint64_t m_x_vel_value;
|
||||
bool m_x_accel_sign;
|
||||
uint64_t m_x_accel_value;
|
||||
bool m_x_sign;
|
||||
uint64_t m_x_value;
|
||||
glonass_t* m__root;
|
||||
glonass_t* m__parent;
|
||||
|
||||
@@ -123,9 +200,12 @@ 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; }
|
||||
bool x_vel_sign() const { return m_x_vel_sign; }
|
||||
uint64_t x_vel_value() const { return m_x_vel_value; }
|
||||
bool x_accel_sign() const { return m_x_accel_sign; }
|
||||
uint64_t x_accel_value() const { return m_x_accel_value; }
|
||||
bool x_sign() const { return m_x_sign; }
|
||||
uint64_t x_value() const { return m_x_value; }
|
||||
glonass_t* _root() const { return m__root; }
|
||||
glonass_t* _parent() const { return m__parent; }
|
||||
};
|
||||
@@ -143,14 +223,38 @@ public:
|
||||
public:
|
||||
~string_2_t();
|
||||
|
||||
private:
|
||||
bool f_y_vel;
|
||||
int32_t m_y_vel;
|
||||
|
||||
public:
|
||||
int32_t y_vel();
|
||||
|
||||
private:
|
||||
bool f_y_accel;
|
||||
int32_t m_y_accel;
|
||||
|
||||
public:
|
||||
int32_t y_accel();
|
||||
|
||||
private:
|
||||
bool f_y;
|
||||
int32_t m_y;
|
||||
|
||||
public:
|
||||
int32_t y();
|
||||
|
||||
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;
|
||||
bool m_y_vel_sign;
|
||||
uint64_t m_y_vel_value;
|
||||
bool m_y_accel_sign;
|
||||
uint64_t m_y_accel_value;
|
||||
bool m_y_sign;
|
||||
uint64_t m_y_value;
|
||||
glonass_t* m__root;
|
||||
glonass_t* m__parent;
|
||||
|
||||
@@ -159,9 +263,12 @@ public:
|
||||
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; }
|
||||
bool y_vel_sign() const { return m_y_vel_sign; }
|
||||
uint64_t y_vel_value() const { return m_y_vel_value; }
|
||||
bool y_accel_sign() const { return m_y_accel_sign; }
|
||||
uint64_t y_accel_value() const { return m_y_accel_value; }
|
||||
bool y_sign() const { return m_y_sign; }
|
||||
uint64_t y_value() const { return m_y_value; }
|
||||
glonass_t* _root() const { return m__root; }
|
||||
glonass_t* _parent() const { return m__parent; }
|
||||
};
|
||||
@@ -179,27 +286,63 @@ public:
|
||||
public:
|
||||
~string_3_t();
|
||||
|
||||
private:
|
||||
bool f_gamma_n;
|
||||
int32_t m_gamma_n;
|
||||
|
||||
public:
|
||||
int32_t gamma_n();
|
||||
|
||||
private:
|
||||
bool f_z_vel;
|
||||
int32_t m_z_vel;
|
||||
|
||||
public:
|
||||
int32_t z_vel();
|
||||
|
||||
private:
|
||||
bool f_z_accel;
|
||||
int32_t m_z_accel;
|
||||
|
||||
public:
|
||||
int32_t z_accel();
|
||||
|
||||
private:
|
||||
bool f_z;
|
||||
int32_t m_z;
|
||||
|
||||
public:
|
||||
int32_t z();
|
||||
|
||||
private:
|
||||
bool m_p3;
|
||||
uint64_t m_gamma_n;
|
||||
bool m_gamma_n_sign;
|
||||
uint64_t m_gamma_n_value;
|
||||
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;
|
||||
bool m_z_vel_sign;
|
||||
uint64_t m_z_vel_value;
|
||||
bool m_z_accel_sign;
|
||||
uint64_t m_z_accel_value;
|
||||
bool m_z_sign;
|
||||
uint64_t m_z_value;
|
||||
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 gamma_n_sign() const { return m_gamma_n_sign; }
|
||||
uint64_t gamma_n_value() const { return m_gamma_n_value; }
|
||||
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; }
|
||||
bool z_vel_sign() const { return m_z_vel_sign; }
|
||||
uint64_t z_vel_value() const { return m_z_vel_value; }
|
||||
bool z_accel_sign() const { return m_z_accel_sign; }
|
||||
uint64_t z_accel_value() const { return m_z_accel_value; }
|
||||
bool z_sign() const { return m_z_sign; }
|
||||
uint64_t z_value() const { return m_z_value; }
|
||||
glonass_t* _root() const { return m__root; }
|
||||
glonass_t* _parent() const { return m__parent; }
|
||||
};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# http://gauss.gge.unb.ca/GLONASS.ICD.pdf
|
||||
# some variables are misprinted but good in the old doc
|
||||
# https://www.unavco.org/help/glossary/docs/ICD_GLONASS_4.0_(1998)_en.pdf
|
||||
meta:
|
||||
id: glonass
|
||||
endian: be
|
||||
@@ -16,6 +18,7 @@ seq:
|
||||
2: string_2
|
||||
3: string_3
|
||||
4: string_4
|
||||
5: string_5
|
||||
_: string_non_immediate
|
||||
- id: hamming_code
|
||||
type: b8
|
||||
@@ -37,12 +40,25 @@ types:
|
||||
type: b2
|
||||
- id: t_k
|
||||
type: b12
|
||||
- id: x_vel
|
||||
type: b24
|
||||
- id: x_speedup
|
||||
type: b5
|
||||
- id: x
|
||||
type: b27
|
||||
- id: x_vel_sign
|
||||
type: b1
|
||||
- id: x_vel_value
|
||||
type: b23
|
||||
- id: x_accel_sign
|
||||
type: b1
|
||||
- id: x_accel_value
|
||||
type: b4
|
||||
- id: x_sign
|
||||
type: b1
|
||||
- id: x_value
|
||||
type: b26
|
||||
instances:
|
||||
x_vel:
|
||||
value: 'x_vel_sign ? (x_vel_value * (-1)) : x_vel_value'
|
||||
x_accel:
|
||||
value: 'x_accel_sign ? (x_accel_value * (-1)) : x_accel_value'
|
||||
x:
|
||||
value: 'x_sign ? (x_value * (-1)) : x_value'
|
||||
string_2:
|
||||
seq:
|
||||
- id: b_n
|
||||
@@ -53,36 +69,70 @@ types:
|
||||
type: b7
|
||||
- id: not_used
|
||||
type: b5
|
||||
- id: y_vel
|
||||
type: b24
|
||||
- id: y_speedup
|
||||
type: b5
|
||||
- id: y
|
||||
type: b27
|
||||
- id: y_vel_sign
|
||||
type: b1
|
||||
- id: y_vel_value
|
||||
type: b23
|
||||
- id: y_accel_sign
|
||||
type: b1
|
||||
- id: y_accel_value
|
||||
type: b4
|
||||
- id: y_sign
|
||||
type: b1
|
||||
- id: y_value
|
||||
type: b26
|
||||
instances:
|
||||
y_vel:
|
||||
value: 'y_vel_sign ? (y_vel_value * (-1)) : y_vel_value'
|
||||
y_accel:
|
||||
value: 'y_accel_sign ? (y_accel_value * (-1)) : y_accel_value'
|
||||
y:
|
||||
value: 'y_sign ? (y_value * (-1)) : y_value'
|
||||
string_3:
|
||||
seq:
|
||||
- id: p3
|
||||
type: b1
|
||||
- id: gamma_n
|
||||
type: b11
|
||||
- id: gamma_n_sign
|
||||
type: b1
|
||||
- id: gamma_n_value
|
||||
type: b10
|
||||
- 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
|
||||
- id: z_vel_sign
|
||||
type: b1
|
||||
- id: z_vel_value
|
||||
type: b23
|
||||
- id: z_accel_sign
|
||||
type: b1
|
||||
- id: z_accel_value
|
||||
type: b4
|
||||
- id: z_sign
|
||||
type: b1
|
||||
- id: z_value
|
||||
type: b26
|
||||
instances:
|
||||
gamma_n:
|
||||
value: 'gamma_n_sign ? (gamma_n_value * (-1)) : gamma_n_value'
|
||||
z_vel:
|
||||
value: 'z_vel_sign ? (z_vel_value * (-1)) : z_vel_value'
|
||||
z_accel:
|
||||
value: 'z_accel_sign ? (z_accel_value * (-1)) : z_accel_value'
|
||||
z:
|
||||
value: 'z_sign ? (z_value * (-1)) : z_value'
|
||||
string_4:
|
||||
seq:
|
||||
- id: tau_n
|
||||
type: b22
|
||||
- id: delta_tau_n
|
||||
type: b5
|
||||
- id: tau_n_sign
|
||||
type: b1
|
||||
- id: tau_n_value
|
||||
type: b21
|
||||
- id: delta_tau_n_sign
|
||||
type: b1
|
||||
- id: delta_tau_n_value
|
||||
type: b4
|
||||
- id: e_n
|
||||
type: b5
|
||||
- id: not_used_1
|
||||
@@ -99,9 +149,28 @@ types:
|
||||
type: b5
|
||||
- id: m
|
||||
type: b2
|
||||
instances:
|
||||
tau_n:
|
||||
value: 'tau_n_sign ? (tau_n_value * (-1)) : tau_n_value'
|
||||
delta_tau_n:
|
||||
value: 'delta_tau_n_sign ? (delta_tau_n_value * (-1)) : delta_tau_n_value'
|
||||
string_5:
|
||||
seq:
|
||||
- id: n_a
|
||||
type: b11
|
||||
- id: tau_c
|
||||
type: b32
|
||||
- id: not_used
|
||||
type: b1
|
||||
- id: n_4
|
||||
type: b5
|
||||
- id: tau_gps
|
||||
type: b22
|
||||
- id: l_n
|
||||
type: b1
|
||||
string_non_immediate:
|
||||
seq:
|
||||
- id: data_1
|
||||
type: b64
|
||||
- id: data_2
|
||||
type: b8
|
||||
type: b8
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/selfdrive/locationd/generated/glonass.cpp b/selfdrive/locationd/generated/glonass.cpp
|
||||
index 6a48fe62c..149134fbb 100644
|
||||
index 5b17bc327..b5c6aa610 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
|
||||
|
||||
@@ -17,7 +17,7 @@ from common.params import Params, put_nonblocking
|
||||
from laika import AstroDog
|
||||
from laika.constants import SECS_IN_HR, SECS_IN_MIN
|
||||
from laika.downloader import DownloadFailed
|
||||
from laika.ephemeris import Ephemeris, EphemerisType, convert_ublox_ephem, parse_qcom_ephem
|
||||
from laika.ephemeris import Ephemeris, EphemerisType, convert_ublox_gps_ephem, convert_ublox_glonass_ephem, parse_qcom_ephem
|
||||
from laika.gps_time import GPSTime
|
||||
from laika.helpers import ConstellationId
|
||||
from laika.raw_gnss import GNSSMeasurement, correct_measurements, process_measurements, read_raw_ublox, read_raw_qcom
|
||||
@@ -139,17 +139,22 @@ class Laikad:
|
||||
if self.use_qcom:
|
||||
return gnss_msg.which() == 'drSvPoly'
|
||||
else:
|
||||
return gnss_msg.which() == 'ephemeris'
|
||||
return gnss_msg.which() in ('ephemeris', 'glonassEphemeris')
|
||||
|
||||
def read_ephemeris(self, gnss_msg):
|
||||
# TODO this only works on GLONASS
|
||||
if self.use_qcom:
|
||||
# TODO this is not robust to gps week rollover
|
||||
if self.gps_week is None:
|
||||
return
|
||||
ephem = parse_qcom_ephem(gnss_msg.drSvPoly, self.gps_week)
|
||||
else:
|
||||
ephem = convert_ublox_ephem(gnss_msg.ephemeris)
|
||||
if gnss_msg.which() == 'ephemeris':
|
||||
ephem = convert_ublox_gps_ephem(gnss_msg.ephemeris)
|
||||
elif gnss_msg.which() == 'glonassEphemeris':
|
||||
ephem = convert_ublox_glonass_ephem(gnss_msg.glonassEphemeris)
|
||||
else:
|
||||
cloudlog.error(f"Unsupported ephemeris type: {gnss_msg.which()}")
|
||||
return
|
||||
self.astro_dog.add_navs({ephem.prn: [ephem]})
|
||||
self.cache_ephemeris(t=ephem.epoch)
|
||||
|
||||
@@ -410,8 +415,13 @@ def main(sm=None, pm=None, qc=None):
|
||||
if pm is None:
|
||||
pm = messaging.PubMaster(['gnssMeasurements'])
|
||||
|
||||
# disable until set as main gps source, to better analyze startup time
|
||||
use_internet = False #"LAIKAD_NO_INTERNET" not in os.environ
|
||||
|
||||
replay = "REPLAY" in os.environ
|
||||
use_internet = "LAIKAD_NO_INTERNET" not in os.environ
|
||||
if replay or "CI" in os.environ:
|
||||
use_internet = True
|
||||
|
||||
laikad = Laikad(save_ephemeris=not replay, auto_fetch_navs=use_internet, use_qcom=use_qcom)
|
||||
|
||||
while True:
|
||||
|
||||
@@ -14,8 +14,9 @@ from system.swaglog import cloudlog
|
||||
|
||||
|
||||
MAX_ANGLE_OFFSET_DELTA = 20 * DT_MDL # Max 20 deg/s
|
||||
ROLL_MAX_DELTA = np.radians(20.0) * DT_MDL # 20deg in 1 second is well within curvature limits
|
||||
ROLL_MAX_DELTA = math.radians(20.0) * DT_MDL # 20deg in 1 second is well within curvature limits
|
||||
ROLL_MIN, ROLL_MAX = math.radians(-10), math.radians(10)
|
||||
ROLL_STD_MAX = math.radians(1.5)
|
||||
LATERAL_ACC_SENSOR_THRESHOLD = 4.0
|
||||
|
||||
|
||||
@@ -37,8 +38,7 @@ class ParamsLearner:
|
||||
self.yaw_rate_std = 0.0
|
||||
self.roll = 0.0
|
||||
self.steering_angle = 0.0
|
||||
|
||||
self.valid = True
|
||||
self.roll_valid = False
|
||||
|
||||
def handle_log(self, t, which, msg):
|
||||
if which == 'liveLocationKalman':
|
||||
@@ -47,8 +47,8 @@ class ParamsLearner:
|
||||
|
||||
localizer_roll = msg.orientationNED.value[0]
|
||||
localizer_roll_std = np.radians(1) if np.isnan(msg.orientationNED.std[0]) else msg.orientationNED.std[0]
|
||||
roll_valid = msg.orientationNED.valid and ROLL_MIN < localizer_roll < ROLL_MAX
|
||||
if roll_valid:
|
||||
self.roll_valid = (localizer_roll_std < ROLL_STD_MAX) and (ROLL_MIN < localizer_roll < ROLL_MAX) and msg.sensorsOK
|
||||
if self.roll_valid:
|
||||
roll = localizer_roll
|
||||
# Experimentally found multiplier of 2 to be best trade-off between stability and accuracy or similar?
|
||||
roll_std = 2 * localizer_roll_std
|
||||
@@ -156,6 +156,7 @@ def main(sm=None, pm=None):
|
||||
learner = ParamsLearner(CP, params['steerRatio'], params['stiffnessFactor'], math.radians(params['angleOffsetAverageDeg']))
|
||||
angle_offset_average = params['angleOffsetAverageDeg']
|
||||
angle_offset = angle_offset_average
|
||||
roll = 0.0
|
||||
|
||||
while True:
|
||||
sm.update()
|
||||
@@ -175,6 +176,8 @@ def main(sm=None, pm=None):
|
||||
|
||||
angle_offset_average = clip(math.degrees(x[States.ANGLE_OFFSET]), angle_offset_average - MAX_ANGLE_OFFSET_DELTA, angle_offset_average + MAX_ANGLE_OFFSET_DELTA)
|
||||
angle_offset = clip(math.degrees(x[States.ANGLE_OFFSET] + x[States.ANGLE_OFFSET_FAST]), angle_offset - MAX_ANGLE_OFFSET_DELTA, angle_offset + MAX_ANGLE_OFFSET_DELTA)
|
||||
roll = clip(float(x[States.ROAD_ROLL]), roll - ROLL_MAX_DELTA, roll + ROLL_MAX_DELTA)
|
||||
roll_std = float(P[States.ROAD_ROLL])
|
||||
# Account for the opposite signs of the yaw rates
|
||||
sensors_valid = bool(abs(learner.speed * (x[States.YAW_RATE] + learner.yaw_rate)) < LATERAL_ACC_SENSOR_THRESHOLD)
|
||||
|
||||
@@ -185,12 +188,14 @@ def main(sm=None, pm=None):
|
||||
liveParameters.sensorValid = sensors_valid
|
||||
liveParameters.steerRatio = float(x[States.STEER_RATIO])
|
||||
liveParameters.stiffnessFactor = float(x[States.STIFFNESS])
|
||||
liveParameters.roll = float(x[States.ROAD_ROLL])
|
||||
liveParameters.roll = roll
|
||||
liveParameters.angleOffsetAverageDeg = angle_offset_average
|
||||
liveParameters.angleOffsetDeg = angle_offset
|
||||
liveParameters.valid = all((
|
||||
abs(liveParameters.angleOffsetAverageDeg) < 10.0,
|
||||
abs(liveParameters.angleOffsetDeg) < 10.0,
|
||||
abs(liveParameters.roll) < ROLL_MAX,
|
||||
roll_std < ROLL_STD_MAX,
|
||||
0.2 <= liveParameters.stiffnessFactor <= 5.0,
|
||||
min_sr <= liveParameters.steerRatio <= max_sr,
|
||||
))
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <bitset>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
|
||||
#include "catch2/catch.hpp"
|
||||
#include "selfdrive/locationd/generated/glonass.h"
|
||||
|
||||
typedef std::vector<std::pair<int, int64_t>> string_data;
|
||||
|
||||
#define IDLE_CHIP_IDX 0
|
||||
#define STRING_NUMBER_IDX 1
|
||||
// string data 1-5
|
||||
#define HC_IDX 0
|
||||
#define PAD1_IDX 1
|
||||
#define SUPERFRAME_IDX 2
|
||||
#define PAD2_IDX 3
|
||||
#define FRAME_IDX 4
|
||||
|
||||
// Indexes for string number 1
|
||||
#define ST1_NU_IDX 2
|
||||
#define ST1_P1_IDX 3
|
||||
#define ST1_T_K_IDX 4
|
||||
#define ST1_X_VEL_S_IDX 5
|
||||
#define ST1_X_VEL_V_IDX 6
|
||||
#define ST1_X_ACCEL_S_IDX 7
|
||||
#define ST1_X_ACCEL_V_IDX 8
|
||||
#define ST1_X_S_IDX 9
|
||||
#define ST1_X_V_IDX 10
|
||||
#define ST1_HC_OFF 11
|
||||
|
||||
// Indexes for string number 2
|
||||
#define ST2_BN_IDX 2
|
||||
#define ST2_P2_IDX 3
|
||||
#define ST2_TB_IDX 4
|
||||
#define ST2_NU_IDX 5
|
||||
#define ST2_Y_VEL_S_IDX 6
|
||||
#define ST2_Y_VEL_V_IDX 7
|
||||
#define ST2_Y_ACCEL_S_IDX 8
|
||||
#define ST2_Y_ACCEL_V_IDX 9
|
||||
#define ST2_Y_S_IDX 10
|
||||
#define ST2_Y_V_IDX 11
|
||||
#define ST2_HC_OFF 12
|
||||
|
||||
// Indexes for string number 3
|
||||
#define ST3_P3_IDX 2
|
||||
#define ST3_GAMMA_N_S_IDX 3
|
||||
#define ST3_GAMMA_N_V_IDX 4
|
||||
#define ST3_NU_1_IDX 5
|
||||
#define ST3_P_IDX 6
|
||||
#define ST3_L_N_IDX 7
|
||||
#define ST3_Z_VEL_S_IDX 8
|
||||
#define ST3_Z_VEL_V_IDX 9
|
||||
#define ST3_Z_ACCEL_S_IDX 10
|
||||
#define ST3_Z_ACCEL_V_IDX 11
|
||||
#define ST3_Z_S_IDX 12
|
||||
#define ST3_Z_V_IDX 13
|
||||
#define ST3_HC_OFF 14
|
||||
|
||||
// Indexes for string number 4
|
||||
#define ST4_TAU_N_S_IDX 2
|
||||
#define ST4_TAU_N_V_IDX 3
|
||||
#define ST4_DELTA_TAU_N_S_IDX 4
|
||||
#define ST4_DELTA_TAU_N_V_IDX 5
|
||||
#define ST4_E_N_IDX 6
|
||||
#define ST4_NU_1_IDX 7
|
||||
#define ST4_P4_IDX 8
|
||||
#define ST4_F_T_IDX 9
|
||||
#define ST4_NU_2_IDX 10
|
||||
#define ST4_N_T_IDX 11
|
||||
#define ST4_N_IDX 12
|
||||
#define ST4_M_IDX 13
|
||||
#define ST4_HC_OFF 14
|
||||
|
||||
// Indexes for string number 5
|
||||
#define ST5_N_A_IDX 2
|
||||
#define ST5_TAU_C_IDX 3
|
||||
#define ST5_NU_IDX 4
|
||||
#define ST5_N_4_IDX 5
|
||||
#define ST5_TAU_GPS_IDX 6
|
||||
#define ST5_L_N_IDX 7
|
||||
#define ST5_HC_OFF 8
|
||||
|
||||
// Indexes for non immediate
|
||||
#define ST6_DATA_1_IDX 2
|
||||
#define ST6_DATA_2_IDX 3
|
||||
#define ST6_HC_OFF 4
|
||||
|
||||
|
||||
std::string generate_inp_data(string_data& data) {
|
||||
std::string inp_data = "";
|
||||
for (auto& [b, v] : data) {
|
||||
std::string tmp = std::bitset<64>(v).to_string();
|
||||
inp_data += tmp.substr(64-b, b);
|
||||
}
|
||||
assert(inp_data.size() == 128);
|
||||
|
||||
std::string string_data;
|
||||
string_data.reserve(16);
|
||||
for (int i = 0; i < 128; i+=8) {
|
||||
std::string substr = inp_data.substr(i, 8);
|
||||
string_data.push_back( (uint8_t)std::stoi(substr.c_str(), 0, 2));
|
||||
}
|
||||
|
||||
return string_data;
|
||||
}
|
||||
|
||||
string_data generate_string_data(uint8_t string_number) {
|
||||
|
||||
srand((unsigned)time(0));
|
||||
string_data data; //<bit length, value>
|
||||
data.push_back({1, 0}); // idle chip
|
||||
data.push_back({4, string_number}); // string number
|
||||
|
||||
if (string_number == 1) {
|
||||
data.push_back({2, 3}); // not_used
|
||||
data.push_back({2, 1}); // p1
|
||||
data.push_back({12, 113}); // t_k
|
||||
data.push_back({1, rand() & 1}); // x_vel_sign
|
||||
data.push_back({23, 7122}); // x_vel_value
|
||||
data.push_back({1, rand() & 1}); // x_accel_sign
|
||||
data.push_back({4, 3}); // x_accel_value
|
||||
data.push_back({1, rand() & 1}); // x_sign
|
||||
data.push_back({26, 33554431}); // x_value
|
||||
} else if (string_number == 2) {
|
||||
data.push_back({3, 3}); // b_n
|
||||
data.push_back({1, 1}); // p2
|
||||
data.push_back({7, 123}); // t_b
|
||||
data.push_back({5, 31}); // not_used
|
||||
data.push_back({1, rand() & 1}); // y_vel_sign
|
||||
data.push_back({23, 7422}); // y_vel_value
|
||||
data.push_back({1, rand() & 1}); // y_accel_sign
|
||||
data.push_back({4, 3}); // y_accel_value
|
||||
data.push_back({1, rand() & 1}); // y_sign
|
||||
data.push_back({26, 67108863}); // y_value
|
||||
} else if (string_number == 3) {
|
||||
data.push_back({1, 0}); // p3
|
||||
data.push_back({1, 1}); // gamma_n_sign
|
||||
data.push_back({10, 123}); // gamma_n_value
|
||||
data.push_back({1, 0}); // not_used
|
||||
data.push_back({2, 2}); // p
|
||||
data.push_back({1, 1}); // l_n
|
||||
data.push_back({1, rand() & 1}); // z_vel_sign
|
||||
data.push_back({23, 1337}); // z_vel_value
|
||||
data.push_back({1, rand() & 1}); // z_accel_sign
|
||||
data.push_back({4, 9}); // z_accel_value
|
||||
data.push_back({1, rand() & 1}); // z_sign
|
||||
data.push_back({26, 100023}); // z_value
|
||||
} else if (string_number == 4) {
|
||||
data.push_back({1, rand() & 1}); // tau_n_sign
|
||||
data.push_back({21, 197152}); // tau_n_value
|
||||
data.push_back({1, rand() & 1}); // delta_tau_n_sign
|
||||
data.push_back({4, 4}); // delta_tau_n_value
|
||||
data.push_back({5, 0}); // e_n
|
||||
data.push_back({14, 2}); // not_used_1
|
||||
data.push_back({1, 1}); // p4
|
||||
data.push_back({4, 9}); // f_t
|
||||
data.push_back({3, 3}); // not_used_2
|
||||
data.push_back({11, 2047}); // n_t
|
||||
data.push_back({5, 2}); // n
|
||||
data.push_back({2, 1}); // m
|
||||
} else if (string_number == 5) {
|
||||
data.push_back({11, 2047}); // n_a
|
||||
data.push_back({32, 4294767295}); // tau_c
|
||||
data.push_back({1, 0}); // not_used_1
|
||||
data.push_back({5, 2}); // n_4
|
||||
data.push_back({22, 4114304}); // tau_gps
|
||||
data.push_back({1, 0}); // l_n
|
||||
} else { // non-immediate data is not parsed
|
||||
data.push_back({64, rand()}); // data_1
|
||||
data.push_back({8, 6}); // data_2
|
||||
}
|
||||
|
||||
data.push_back({8, rand() & 0xFF}); // hamming code
|
||||
data.push_back({11, rand() & 0x7FF}); // pad
|
||||
data.push_back({16, rand() & 0xFFFF}); // superframe
|
||||
data.push_back({8, rand() & 0xFF}); // pad
|
||||
data.push_back({8, rand() & 0xFF}); // frame
|
||||
return data;
|
||||
}
|
||||
|
||||
TEST_CASE("parse_string_number_1"){
|
||||
string_data data = generate_string_data(1);
|
||||
std::string inp_data = generate_inp_data(data);
|
||||
|
||||
kaitai::kstream stream(inp_data);
|
||||
glonass_t gl_string(&stream);
|
||||
|
||||
REQUIRE(gl_string.idle_chip() == data[IDLE_CHIP_IDX].second);
|
||||
REQUIRE(gl_string.string_number() == data[STRING_NUMBER_IDX].second);
|
||||
REQUIRE(gl_string.hamming_code() == data[ST1_HC_OFF + HC_IDX].second);
|
||||
REQUIRE(gl_string.pad_1() == data[ST1_HC_OFF + PAD1_IDX].second);
|
||||
REQUIRE(gl_string.superframe_number() == data[ST1_HC_OFF + SUPERFRAME_IDX].second);
|
||||
REQUIRE(gl_string.pad_2() == data[ST1_HC_OFF + PAD2_IDX].second);
|
||||
REQUIRE(gl_string.frame_number() == data[ST1_HC_OFF + FRAME_IDX].second);
|
||||
|
||||
kaitai::kstream str1(inp_data);
|
||||
glonass_t str1_data(&str1);
|
||||
glonass_t::string_1_t* s1 = static_cast<glonass_t::string_1_t*>(str1_data.data());
|
||||
|
||||
REQUIRE(s1->not_used() == data[ST1_NU_IDX].second);
|
||||
REQUIRE(s1->p1() == data[ST1_P1_IDX].second);
|
||||
REQUIRE(s1->t_k() == data[ST1_T_K_IDX].second);
|
||||
|
||||
int mul = s1->x_vel_sign() ? (-1) : 1;
|
||||
REQUIRE(s1->x_vel() == (data[ST1_X_VEL_V_IDX].second * mul));
|
||||
mul = s1->x_accel_sign() ? (-1) : 1;
|
||||
REQUIRE(s1->x_accel() == (data[ST1_X_ACCEL_V_IDX].second * mul));
|
||||
mul = s1->x_sign() ? (-1) : 1;
|
||||
REQUIRE(s1->x() == (data[ST1_X_V_IDX].second * mul));
|
||||
}
|
||||
|
||||
TEST_CASE("parse_string_number_2"){
|
||||
string_data data = generate_string_data(2);
|
||||
std::string inp_data = generate_inp_data(data);
|
||||
|
||||
kaitai::kstream stream(inp_data);
|
||||
glonass_t gl_string(&stream);
|
||||
|
||||
REQUIRE(gl_string.idle_chip() == data[IDLE_CHIP_IDX].second);
|
||||
REQUIRE(gl_string.string_number() == data[STRING_NUMBER_IDX].second);
|
||||
REQUIRE(gl_string.hamming_code() == data[ST2_HC_OFF + HC_IDX].second);
|
||||
REQUIRE(gl_string.pad_1() == data[ST2_HC_OFF + PAD1_IDX].second);
|
||||
REQUIRE(gl_string.superframe_number() == data[ST2_HC_OFF + SUPERFRAME_IDX].second);
|
||||
REQUIRE(gl_string.pad_2() == data[ST2_HC_OFF + PAD2_IDX].second);
|
||||
REQUIRE(gl_string.frame_number() == data[ST2_HC_OFF + FRAME_IDX].second);
|
||||
|
||||
kaitai::kstream str2(inp_data);
|
||||
glonass_t str2_data(&str2);
|
||||
glonass_t::string_2_t* s2 = static_cast<glonass_t::string_2_t*>(str2_data.data());
|
||||
|
||||
REQUIRE(s2->b_n() == data[ST2_BN_IDX].second);
|
||||
REQUIRE(s2->not_used() == data[ST2_NU_IDX].second);
|
||||
REQUIRE(s2->p2() == data[ST2_P2_IDX].second);
|
||||
REQUIRE(s2->t_b() == data[ST2_TB_IDX].second);
|
||||
int mul = s2->y_vel_sign() ? (-1) : 1;
|
||||
REQUIRE(s2->y_vel() == (data[ST2_Y_VEL_V_IDX].second * mul));
|
||||
mul = s2->y_accel_sign() ? (-1) : 1;
|
||||
REQUIRE(s2->y_accel() == (data[ST2_Y_ACCEL_V_IDX].second * mul));
|
||||
mul = s2->y_sign() ? (-1) : 1;
|
||||
REQUIRE(s2->y() == (data[ST2_Y_V_IDX].second * mul));
|
||||
}
|
||||
|
||||
TEST_CASE("parse_string_number_3"){
|
||||
string_data data = generate_string_data(3);
|
||||
std::string inp_data = generate_inp_data(data);
|
||||
|
||||
kaitai::kstream stream(inp_data);
|
||||
glonass_t gl_string(&stream);
|
||||
|
||||
REQUIRE(gl_string.idle_chip() == data[IDLE_CHIP_IDX].second);
|
||||
REQUIRE(gl_string.string_number() == data[STRING_NUMBER_IDX].second);
|
||||
REQUIRE(gl_string.hamming_code() == data[ST3_HC_OFF + HC_IDX].second);
|
||||
REQUIRE(gl_string.pad_1() == data[ST3_HC_OFF + PAD1_IDX].second);
|
||||
REQUIRE(gl_string.superframe_number() == data[ST3_HC_OFF + SUPERFRAME_IDX].second);
|
||||
REQUIRE(gl_string.pad_2() == data[ST3_HC_OFF + PAD2_IDX].second);
|
||||
REQUIRE(gl_string.frame_number() == data[ST3_HC_OFF + FRAME_IDX].second);
|
||||
|
||||
kaitai::kstream str3(inp_data);
|
||||
glonass_t str3_data(&str3);
|
||||
glonass_t::string_3_t* s3 = static_cast<glonass_t::string_3_t*>(str3_data.data());
|
||||
|
||||
REQUIRE(s3->p3() == data[ST3_P3_IDX].second);
|
||||
int mul = s3->gamma_n_sign() ? (-1) : 1;
|
||||
REQUIRE(s3->gamma_n() == (data[ST3_GAMMA_N_V_IDX].second * mul));
|
||||
REQUIRE(s3->not_used() == data[ST3_NU_1_IDX].second);
|
||||
REQUIRE(s3->p() == data[ST3_P_IDX].second);
|
||||
REQUIRE(s3->l_n() == data[ST3_L_N_IDX].second);
|
||||
mul = s3->z_vel_sign() ? (-1) : 1;
|
||||
REQUIRE(s3->z_vel() == (data[ST3_Z_VEL_V_IDX].second * mul));
|
||||
mul = s3->z_accel_sign() ? (-1) : 1;
|
||||
REQUIRE(s3->z_accel() == (data[ST3_Z_ACCEL_V_IDX].second * mul));
|
||||
mul = s3->z_sign() ? (-1) : 1;
|
||||
REQUIRE(s3->z() == (data[ST3_Z_V_IDX].second * mul));
|
||||
}
|
||||
|
||||
TEST_CASE("parse_string_number_4"){
|
||||
string_data data = generate_string_data(4);
|
||||
std::string inp_data = generate_inp_data(data);
|
||||
|
||||
kaitai::kstream stream(inp_data);
|
||||
glonass_t gl_string(&stream);
|
||||
|
||||
REQUIRE(gl_string.idle_chip() == data[IDLE_CHIP_IDX].second);
|
||||
REQUIRE(gl_string.string_number() == data[STRING_NUMBER_IDX].second);
|
||||
REQUIRE(gl_string.hamming_code() == data[ST4_HC_OFF + HC_IDX].second);
|
||||
REQUIRE(gl_string.pad_1() == data[ST4_HC_OFF + PAD1_IDX].second);
|
||||
REQUIRE(gl_string.superframe_number() == data[ST4_HC_OFF + SUPERFRAME_IDX].second);
|
||||
REQUIRE(gl_string.pad_2() == data[ST4_HC_OFF + PAD2_IDX].second);
|
||||
REQUIRE(gl_string.frame_number() == data[ST4_HC_OFF + FRAME_IDX].second);
|
||||
|
||||
kaitai::kstream str4(inp_data);
|
||||
glonass_t str4_data(&str4);
|
||||
glonass_t::string_4_t* s4 = static_cast<glonass_t::string_4_t*>(str4_data.data());
|
||||
|
||||
int mul = s4->tau_n_sign() ? (-1) : 1;
|
||||
REQUIRE(s4->tau_n() == (data[ST4_TAU_N_V_IDX].second * mul));
|
||||
mul = s4->delta_tau_n_sign() ? (-1) : 1;
|
||||
REQUIRE(s4->delta_tau_n() == (data[ST4_DELTA_TAU_N_V_IDX].second * mul));
|
||||
REQUIRE(s4->e_n() == data[ST4_E_N_IDX].second);
|
||||
REQUIRE(s4->not_used_1() == data[ST4_NU_1_IDX].second);
|
||||
REQUIRE(s4->p4() == data[ST4_P4_IDX].second);
|
||||
REQUIRE(s4->f_t() == data[ST4_F_T_IDX].second);
|
||||
REQUIRE(s4->not_used_2() == data[ST4_NU_2_IDX].second);
|
||||
REQUIRE(s4->n_t() == data[ST4_N_T_IDX].second);
|
||||
REQUIRE(s4->n() == data[ST4_N_IDX].second);
|
||||
REQUIRE(s4->m() == data[ST4_M_IDX].second);
|
||||
}
|
||||
|
||||
TEST_CASE("parse_string_number_5"){
|
||||
string_data data = generate_string_data(5);
|
||||
std::string inp_data = generate_inp_data(data);
|
||||
|
||||
kaitai::kstream stream(inp_data);
|
||||
glonass_t gl_string(&stream);
|
||||
|
||||
REQUIRE(gl_string.idle_chip() == data[IDLE_CHIP_IDX].second);
|
||||
REQUIRE(gl_string.string_number() == data[STRING_NUMBER_IDX].second);
|
||||
REQUIRE(gl_string.hamming_code() == data[ST5_HC_OFF + HC_IDX].second);
|
||||
REQUIRE(gl_string.pad_1() == data[ST5_HC_OFF + PAD1_IDX].second);
|
||||
REQUIRE(gl_string.superframe_number() == data[ST5_HC_OFF + SUPERFRAME_IDX].second);
|
||||
REQUIRE(gl_string.pad_2() == data[ST5_HC_OFF + PAD2_IDX].second);
|
||||
REQUIRE(gl_string.frame_number() == data[ST5_HC_OFF + FRAME_IDX].second);
|
||||
|
||||
kaitai::kstream str5(inp_data);
|
||||
glonass_t str5_data(&str5);
|
||||
glonass_t::string_5_t* s5 = static_cast<glonass_t::string_5_t*>(str5_data.data());
|
||||
|
||||
REQUIRE(s5->n_a() == data[ST5_N_A_IDX].second);
|
||||
REQUIRE(s5->tau_c() == data[ST5_TAU_C_IDX].second);
|
||||
REQUIRE(s5->not_used() == data[ST5_NU_IDX].second);
|
||||
REQUIRE(s5->n_4() == data[ST5_N_4_IDX].second);
|
||||
REQUIRE(s5->tau_gps() == data[ST5_TAU_GPS_IDX].second);
|
||||
REQUIRE(s5->l_n() == data[ST5_L_N_IDX].second);
|
||||
}
|
||||
|
||||
TEST_CASE("parse_string_number_NI"){
|
||||
string_data data = generate_string_data((rand() % 10) + 6);
|
||||
std::string inp_data = generate_inp_data(data);
|
||||
|
||||
kaitai::kstream stream(inp_data);
|
||||
glonass_t gl_string(&stream);
|
||||
|
||||
REQUIRE(gl_string.idle_chip() == data[IDLE_CHIP_IDX].second);
|
||||
REQUIRE(gl_string.string_number() == data[STRING_NUMBER_IDX].second);
|
||||
REQUIRE(gl_string.hamming_code() == data[ST6_HC_OFF + HC_IDX].second);
|
||||
REQUIRE(gl_string.pad_1() == data[ST6_HC_OFF + PAD1_IDX].second);
|
||||
REQUIRE(gl_string.superframe_number() == data[ST6_HC_OFF + SUPERFRAME_IDX].second);
|
||||
REQUIRE(gl_string.pad_2() == data[ST6_HC_OFF + PAD2_IDX].second);
|
||||
REQUIRE(gl_string.frame_number() == data[ST6_HC_OFF + FRAME_IDX].second);
|
||||
|
||||
kaitai::kstream strni(inp_data);
|
||||
glonass_t strni_data(&strni);
|
||||
glonass_t::string_non_immediate_t* sni = static_cast<glonass_t::string_non_immediate_t*>(strni_data.data());
|
||||
|
||||
REQUIRE(sni->data_1() == data[ST6_DATA_1_IDX].second);
|
||||
REQUIRE(sni->data_2() == data[ST6_DATA_2_IDX].second);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch2/catch.hpp"
|
||||
@@ -1,5 +1,5 @@
|
||||
import unittest
|
||||
|
||||
import time
|
||||
import numpy as np
|
||||
|
||||
from laika import AstroDog
|
||||
@@ -8,7 +8,8 @@ from laika.raw_gnss import correct_measurements, process_measurements, read_raw_
|
||||
from laika.opt import calc_pos_fix
|
||||
from selfdrive.test.openpilotci import get_url
|
||||
from tools.lib.logreader import LogReader
|
||||
|
||||
from selfdrive.test.helpers import with_processes
|
||||
import cereal.messaging as messaging
|
||||
|
||||
def get_gnss_measurements(log_reader):
|
||||
gnss_measurements = []
|
||||
@@ -21,6 +22,12 @@ def get_gnss_measurements(log_reader):
|
||||
gnss_measurements.append(read_raw_ublox(report))
|
||||
return gnss_measurements
|
||||
|
||||
def get_ublox_raw(log_reader):
|
||||
ublox_raw = []
|
||||
for msg in log_reader:
|
||||
if msg.which() == "ubloxRaw":
|
||||
ublox_raw.append(msg)
|
||||
return ublox_raw
|
||||
|
||||
class TestUbloxProcessing(unittest.TestCase):
|
||||
NUM_TEST_PROCESS_MEAS = 10
|
||||
@@ -30,6 +37,10 @@ class TestUbloxProcessing(unittest.TestCase):
|
||||
lr = LogReader(get_url("4cf7a6ad03080c90|2021-09-29--13-46-36", 0))
|
||||
cls.gnss_measurements = get_gnss_measurements(lr)
|
||||
|
||||
# test gps ephemeris continuity check (drive has ephemeris issues with cutover data)
|
||||
lr = LogReader(get_url("37b6542f3211019a|2023-01-15--23-45-10", 14))
|
||||
cls.ublox_raw = get_ublox_raw(lr)
|
||||
|
||||
def test_read_ublox_raw(self):
|
||||
count_gps = 0
|
||||
count_glonass = 0
|
||||
@@ -76,6 +87,29 @@ class TestUbloxProcessing(unittest.TestCase):
|
||||
self.assertEqual(count_processed_measurements, 69)
|
||||
self.assertEqual(count_corrected_measurements, 69)
|
||||
|
||||
@with_processes(['ubloxd'])
|
||||
def test_ublox_gps_cutover(self):
|
||||
time.sleep(2)
|
||||
ugs = messaging.sub_sock("ubloxGnss", timeout=0.1)
|
||||
ur_pm = messaging.PubMaster(['ubloxRaw'])
|
||||
|
||||
def replay_segment():
|
||||
rcv_msgs = []
|
||||
for msg in self.ublox_raw:
|
||||
ur_pm.send(msg.which(), msg.as_builder())
|
||||
time.sleep(0.01)
|
||||
rcv_msgs += messaging.drain_sock(ugs)
|
||||
|
||||
time.sleep(0.1)
|
||||
rcv_msgs += messaging.drain_sock(ugs)
|
||||
return rcv_msgs
|
||||
|
||||
# replay twice to enforce cutover data on rewind
|
||||
rcv_msgs = replay_segment()
|
||||
rcv_msgs += replay_segment()
|
||||
|
||||
ephems_cnt = sum(m.ubloxGnss.which() == 'ephemeris' for m in rcv_msgs)
|
||||
self.assertEqual(ephems_cnt, 15)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
+243
-105
@@ -65,6 +65,21 @@ inline bool UbloxMsgParser::valid_so_far() {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline uint16_t UbloxMsgParser::get_glonass_year(uint8_t N4, uint16_t Nt) {
|
||||
// convert time to year (conversion from A3.1.3)
|
||||
int J = 0;
|
||||
if (1 <= Nt && Nt <= 366) {
|
||||
J = 1;
|
||||
} else if (367 <= Nt && Nt <= 731) {
|
||||
J = 2;
|
||||
} else if (732 <= Nt && Nt <= 1096) {
|
||||
J = 3;
|
||||
} else if (1097 <= Nt && Nt <= 1461) {
|
||||
J = 4;
|
||||
}
|
||||
uint16_t year = 1996 + 4*(N4 -1) + (J - 1);
|
||||
return year;
|
||||
}
|
||||
|
||||
bool UbloxMsgParser::add_data(const uint8_t *incoming_data, uint32_t incoming_data_len, size_t &bytes_consumed) {
|
||||
int needed = needed_bytes();
|
||||
@@ -103,9 +118,9 @@ std::pair<std::string, kj::Array<capnp::word>> UbloxMsgParser::gen_msg() {
|
||||
switch (ubx_message.msg_type()) {
|
||||
case 0x0107:
|
||||
return {"gpsLocationExternal", gen_nav_pvt(static_cast<ubx_t::nav_pvt_t*>(body))};
|
||||
case 0x0213:
|
||||
case 0x0213: // UBX-RXM-SFRB (Broadcast Navigation Data Subframe)
|
||||
return {"ubloxGnss", gen_rxm_sfrbx(static_cast<ubx_t::rxm_sfrbx_t*>(body))};
|
||||
case 0x0215:
|
||||
case 0x0215: // UBX-RXM-RAW (Multi-GNSS Raw Measurement Data)
|
||||
return {"ubloxGnss", gen_rxm_rawx(static_cast<ubx_t::rxm_rawx_t*>(body))};
|
||||
case 0x0a09:
|
||||
return {"ubloxGnss", gen_mon_hw(static_cast<ubx_t::mon_hw_t*>(body))};
|
||||
@@ -147,125 +162,248 @@ kj::Array<capnp::word> UbloxMsgParser::gen_nav_pvt(ubx_t::nav_pvt_t *msg) {
|
||||
return capnp::messageToFlatArray(msg_builder);
|
||||
}
|
||||
|
||||
|
||||
kj::Array<capnp::word> UbloxMsgParser::gen_rxm_sfrbx(ubx_t::rxm_sfrbx_t *msg) {
|
||||
kj::Array<capnp::word> UbloxMsgParser::parse_gps_ephemeris(ubx_t::rxm_sfrbx_t *msg) {
|
||||
// GPS subframes are packed into 10x 4 bytes, each containing 3 actual bytes
|
||||
// We will first need to separate the data from the padding and parity
|
||||
auto body = *msg->body();
|
||||
assert(body.size() == 10);
|
||||
|
||||
if (msg->gnss_id() == ubx_t::gnss_type_t::GNSS_TYPE_GPS) {
|
||||
// GPS subframes are packed into 10x 4 bytes, each containing 3 actual bytes
|
||||
// We will first need to separate the data from the padding and parity
|
||||
assert(body.size() == 10);
|
||||
std::string subframe_data;
|
||||
subframe_data.reserve(30);
|
||||
for (uint32_t word : body) {
|
||||
word = word >> 6; // TODO: Verify parity
|
||||
subframe_data.push_back(word >> 16);
|
||||
subframe_data.push_back(word >> 8);
|
||||
subframe_data.push_back(word >> 0);
|
||||
}
|
||||
|
||||
std::string subframe_data;
|
||||
subframe_data.reserve(30);
|
||||
for (uint32_t word : body) {
|
||||
word = word >> 6; // TODO: Verify parity
|
||||
subframe_data.push_back(word >> 16);
|
||||
subframe_data.push_back(word >> 8);
|
||||
subframe_data.push_back(word >> 0);
|
||||
// Collect subframes in map and parse when we have all the parts
|
||||
{
|
||||
kaitai::kstream stream(subframe_data);
|
||||
gps_t subframe(&stream);
|
||||
|
||||
int subframe_id = subframe.how()->subframe_id();
|
||||
if (subframe_id > 3) {
|
||||
// dont parse almanac subframes
|
||||
return kj::Array<capnp::word>();
|
||||
}
|
||||
gps_subframes[msg->sv_id()][subframe_id] = subframe_data;
|
||||
}
|
||||
|
||||
// Collect subframes in map and parse when we have all the parts
|
||||
// publish if subframes 1-3 have been collected
|
||||
if (gps_subframes[msg->sv_id()].size() == 3) {
|
||||
MessageBuilder msg_builder;
|
||||
auto eph = msg_builder.initEvent().initUbloxGnss().initEphemeris();
|
||||
eph.setSvId(msg->sv_id());
|
||||
|
||||
int iode_s2 = 0;
|
||||
int iode_s3 = 0;
|
||||
int iodc_lsb = 0;
|
||||
|
||||
// Subframe 1
|
||||
{
|
||||
kaitai::kstream stream(subframe_data);
|
||||
kaitai::kstream stream(gps_subframes[msg->sv_id()][1]);
|
||||
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();
|
||||
gps_t::subframe_1_t* subframe_1 = static_cast<gps_t::subframe_1_t*>(subframe.body());
|
||||
|
||||
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;
|
||||
eph.setGpsWeek(subframe_1->week_no());
|
||||
eph.setTgd(subframe_1->t_gd() * pow(2, -31));
|
||||
eph.setToc(subframe_1->t_oc() * pow(2, 4));
|
||||
eph.setAf2(subframe_1->af_2() * pow(2, -55));
|
||||
eph.setAf1(subframe_1->af_1() * pow(2, -43));
|
||||
eph.setAf0(subframe_1->af_0() * pow(2, -31));
|
||||
eph.setSvHealth(subframe_1->sv_health());
|
||||
iodc_lsb = subframe_1->iodc_lsb();
|
||||
}
|
||||
|
||||
if (gps_subframes[msg->sv_id()].size() == 5) {
|
||||
MessageBuilder msg_builder;
|
||||
auto eph = msg_builder.initEvent().initUbloxGnss().initEphemeris();
|
||||
eph.setSvId(msg->sv_id());
|
||||
// Subframe 2
|
||||
{
|
||||
kaitai::kstream stream(gps_subframes[msg->sv_id()][2]);
|
||||
gps_t subframe(&stream);
|
||||
gps_t::subframe_2_t* subframe_2 = static_cast<gps_t::subframe_2_t*>(subframe.body());
|
||||
|
||||
// Subframe 1
|
||||
{
|
||||
kaitai::kstream stream(gps_subframes[msg->sv_id()][1]);
|
||||
gps_t subframe(&stream);
|
||||
gps_t::subframe_1_t* subframe_1 = static_cast<gps_t::subframe_1_t*>(subframe.body());
|
||||
|
||||
eph.setGpsWeek(subframe_1->week_no());
|
||||
eph.setTgd(subframe_1->t_gd() * pow(2, -31));
|
||||
eph.setToc(subframe_1->t_oc() * pow(2, 4));
|
||||
eph.setAf2(subframe_1->af_2() * pow(2, -55));
|
||||
eph.setAf1(subframe_1->af_1() * pow(2, -43));
|
||||
eph.setAf0(subframe_1->af_0() * pow(2, -31));
|
||||
eph.setSvHealth(subframe_1->sv_health());
|
||||
}
|
||||
|
||||
// Subframe 2
|
||||
{
|
||||
kaitai::kstream stream(gps_subframes[msg->sv_id()][2]);
|
||||
gps_t subframe(&stream);
|
||||
gps_t::subframe_2_t* subframe_2 = static_cast<gps_t::subframe_2_t*>(subframe.body());
|
||||
|
||||
eph.setCrs(subframe_2->c_rs() * pow(2, -5));
|
||||
eph.setDeltaN(subframe_2->delta_n() * pow(2, -43) * gpsPi);
|
||||
eph.setM0(subframe_2->m_0() * pow(2, -31) * gpsPi);
|
||||
eph.setCuc(subframe_2->c_uc() * pow(2, -29));
|
||||
eph.setEcc(subframe_2->e() * pow(2, -33));
|
||||
eph.setCus(subframe_2->c_us() * pow(2, -29));
|
||||
eph.setA(pow(subframe_2->sqrt_a() * pow(2, -19), 2.0));
|
||||
eph.setToe(subframe_2->t_oe() * pow(2, 4));
|
||||
}
|
||||
|
||||
// Subframe 3
|
||||
{
|
||||
kaitai::kstream stream(gps_subframes[msg->sv_id()][3]);
|
||||
gps_t subframe(&stream);
|
||||
gps_t::subframe_3_t* subframe_3 = static_cast<gps_t::subframe_3_t*>(subframe.body());
|
||||
|
||||
eph.setCic(subframe_3->c_ic() * pow(2, -29));
|
||||
eph.setOmega0(subframe_3->omega_0() * pow(2, -31) * gpsPi);
|
||||
eph.setCis(subframe_3->c_is() * pow(2, -29));
|
||||
eph.setI0(subframe_3->i_0() * pow(2, -31) * gpsPi);
|
||||
eph.setCrc(subframe_3->c_rc() * pow(2, -5));
|
||||
eph.setOmega(subframe_3->omega() * pow(2, -31) * gpsPi);
|
||||
eph.setOmegaDot(subframe_3->omega_dot() * pow(2, -43) * gpsPi);
|
||||
eph.setIode(subframe_3->iode());
|
||||
eph.setIDot(subframe_3->idot() * pow(2, -43) * gpsPi);
|
||||
}
|
||||
|
||||
// Subframe 4
|
||||
{
|
||||
kaitai::kstream stream(gps_subframes[msg->sv_id()][4]);
|
||||
gps_t subframe(&stream);
|
||||
gps_t::subframe_4_t* subframe_4 = static_cast<gps_t::subframe_4_t*>(subframe.body());
|
||||
|
||||
// This is page 18, why is the page id 56?
|
||||
if (subframe_4->data_id() == 1 && subframe_4->page_id() == 56) {
|
||||
auto iono = static_cast<gps_t::subframe_4_t::ionosphere_data_t*>(subframe_4->body());
|
||||
double a0 = iono->a0() * pow(2, -30);
|
||||
double a1 = iono->a1() * pow(2, -27);
|
||||
double a2 = iono->a2() * pow(2, -24);
|
||||
double a3 = iono->a3() * pow(2, -24);
|
||||
eph.setIonoAlpha({a0, a1, a2, a3});
|
||||
|
||||
double b0 = iono->b0() * pow(2, 11);
|
||||
double b1 = iono->b1() * pow(2, 14);
|
||||
double b2 = iono->b2() * pow(2, 16);
|
||||
double b3 = iono->b3() * pow(2, 16);
|
||||
eph.setIonoBeta({b0, b1, b2, b3});
|
||||
}
|
||||
}
|
||||
|
||||
return capnp::messageToFlatArray(msg_builder);
|
||||
eph.setCrs(subframe_2->c_rs() * pow(2, -5));
|
||||
eph.setDeltaN(subframe_2->delta_n() * pow(2, -43) * gpsPi);
|
||||
eph.setM0(subframe_2->m_0() * pow(2, -31) * gpsPi);
|
||||
eph.setCuc(subframe_2->c_uc() * pow(2, -29));
|
||||
eph.setEcc(subframe_2->e() * pow(2, -33));
|
||||
eph.setCus(subframe_2->c_us() * pow(2, -29));
|
||||
eph.setA(pow(subframe_2->sqrt_a() * pow(2, -19), 2.0));
|
||||
eph.setToe(subframe_2->t_oe() * pow(2, 4));
|
||||
iode_s2 = subframe_2->iode();
|
||||
}
|
||||
|
||||
// Subframe 3
|
||||
{
|
||||
kaitai::kstream stream(gps_subframes[msg->sv_id()][3]);
|
||||
gps_t subframe(&stream);
|
||||
gps_t::subframe_3_t* subframe_3 = static_cast<gps_t::subframe_3_t*>(subframe.body());
|
||||
|
||||
eph.setCic(subframe_3->c_ic() * pow(2, -29));
|
||||
eph.setOmega0(subframe_3->omega_0() * pow(2, -31) * gpsPi);
|
||||
eph.setCis(subframe_3->c_is() * pow(2, -29));
|
||||
eph.setI0(subframe_3->i_0() * pow(2, -31) * gpsPi);
|
||||
eph.setCrc(subframe_3->c_rc() * pow(2, -5));
|
||||
eph.setOmega(subframe_3->omega() * pow(2, -31) * gpsPi);
|
||||
eph.setOmegaDot(subframe_3->omega_dot() * pow(2, -43) * gpsPi);
|
||||
eph.setIode(subframe_3->iode());
|
||||
eph.setIDot(subframe_3->idot() * pow(2, -43) * gpsPi);
|
||||
iode_s3 = subframe_3->iode();
|
||||
}
|
||||
|
||||
gps_subframes[msg->sv_id()].clear();
|
||||
if (iodc_lsb != iode_s2 || iodc_lsb != iode_s3) {
|
||||
// data set cutover, reject ephemeris
|
||||
return kj::Array<capnp::word>();
|
||||
}
|
||||
return capnp::messageToFlatArray(msg_builder);
|
||||
}
|
||||
return kj::Array<capnp::word>();
|
||||
}
|
||||
|
||||
kj::Array<capnp::word> UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_t *msg) {
|
||||
if (msg->sv_id() == 255) {
|
||||
// data can be decoded before identifying the SV number, in this case 255
|
||||
// is returned, which means "unknown" (ublox p32)
|
||||
return kj::Array<capnp::word>();
|
||||
}
|
||||
|
||||
auto body = *msg->body();
|
||||
assert(body.size() == 4);
|
||||
{
|
||||
std::string string_data;
|
||||
string_data.reserve(16);
|
||||
for (uint32_t word : body) {
|
||||
for (int i = 3; i >= 0; i--)
|
||||
string_data.push_back(word >> 8*i);
|
||||
}
|
||||
|
||||
kaitai::kstream stream(string_data);
|
||||
glonass_t gl_string(&stream);
|
||||
|
||||
int string_number = gl_string.string_number();
|
||||
if (string_number > 5 || gl_string.idle_chip()) {
|
||||
// dont parse non immediate data, idle_chip == 0
|
||||
return kj::Array<capnp::word>();
|
||||
}
|
||||
|
||||
// immediate data is the same within one superframe
|
||||
if (glonass_superframes[msg->sv_id()] != gl_string.superframe_number()) {
|
||||
glonass_strings[msg->sv_id()].clear();
|
||||
glonass_superframes[msg->sv_id()] = gl_string.superframe_number();
|
||||
}
|
||||
glonass_strings[msg->sv_id()][string_number] = string_data;
|
||||
}
|
||||
|
||||
// publish if strings 1-5 have been collected
|
||||
if (glonass_strings[msg->sv_id()].size() != 5) {
|
||||
return kj::Array<capnp::word>();
|
||||
}
|
||||
|
||||
MessageBuilder msg_builder;
|
||||
auto eph = msg_builder.initEvent().initUbloxGnss().initGlonassEphemeris();
|
||||
eph.setSvId(msg->sv_id());
|
||||
uint16_t current_day = 0;
|
||||
|
||||
// string number 1
|
||||
{
|
||||
kaitai::kstream stream(glonass_strings[msg->sv_id()][1]);
|
||||
glonass_t gl_stream(&stream);
|
||||
glonass_t::string_1_t* data = static_cast<glonass_t::string_1_t*>(gl_stream.data());
|
||||
|
||||
eph.setP1(data->p1());
|
||||
eph.setTk(data->t_k());
|
||||
eph.setXVel(data->x_vel() * pow(2, -20));
|
||||
eph.setXAccel(data->x_accel() * pow(2, -30));
|
||||
eph.setX(data->x() * pow(2, -11));
|
||||
}
|
||||
|
||||
// string number 2
|
||||
{
|
||||
kaitai::kstream stream(glonass_strings[msg->sv_id()][2]);
|
||||
glonass_t gl_stream(&stream);
|
||||
glonass_t::string_2_t* data = static_cast<glonass_t::string_2_t*>(gl_stream.data());
|
||||
|
||||
eph.setSvHealth(data->b_n()>>2); // MSB indicates health
|
||||
eph.setP2(data->p2());
|
||||
eph.setTb(data->t_b());
|
||||
eph.setYVel(data->y_vel() * pow(2, -20));
|
||||
eph.setYAccel(data->y_accel() * pow(2, -30));
|
||||
eph.setY(data->y() * pow(2, -11));
|
||||
}
|
||||
|
||||
// string number 3
|
||||
{
|
||||
kaitai::kstream stream(glonass_strings[msg->sv_id()][3]);
|
||||
glonass_t gl_stream(&stream);
|
||||
glonass_t::string_3_t* data = static_cast<glonass_t::string_3_t*>(gl_stream.data());
|
||||
|
||||
eph.setP3(data->p3());
|
||||
eph.setGammaN(data->gamma_n() * pow(2, -40));
|
||||
eph.setSvHealth(eph.getSvHealth() | data->l_n());
|
||||
eph.setZVel(data->z_vel() * pow(2, -20));
|
||||
eph.setZAccel(data->z_accel() * pow(2, -30));
|
||||
eph.setZ(data->z() * pow(2, -11));
|
||||
}
|
||||
|
||||
// string number 4
|
||||
{
|
||||
kaitai::kstream stream(glonass_strings[msg->sv_id()][4]);
|
||||
glonass_t gl_stream(&stream);
|
||||
glonass_t::string_4_t* data = static_cast<glonass_t::string_4_t*>(gl_stream.data());
|
||||
|
||||
current_day = data->n_t();
|
||||
eph.setTauN(data->tau_n() * pow(2, -30));
|
||||
eph.setDeltaTauN(data->delta_tau_n() * pow(2, -30));
|
||||
eph.setAge(data->e_n());
|
||||
eph.setP4(data->p4());
|
||||
eph.setSvURA(glonass_URA_lookup.at(data->f_t()));
|
||||
if (msg->sv_id() != data->n()) {
|
||||
LOGE("SV_ID != SLOT_NUMBER: %d %d", msg->sv_id(), data->n())
|
||||
}
|
||||
eph.setSvType(data->m());
|
||||
}
|
||||
|
||||
// string number 5
|
||||
{
|
||||
kaitai::kstream stream(glonass_strings[msg->sv_id()][5]);
|
||||
glonass_t gl_stream(&stream);
|
||||
glonass_t::string_5_t* data = static_cast<glonass_t::string_5_t*>(gl_stream.data());
|
||||
|
||||
// string5 parsing is only needed to get the year, this can be removed and
|
||||
// the year can be fetched later in laika (note rollovers and leap year)
|
||||
uint8_t n_4 = data->n_4();
|
||||
uint16_t year = get_glonass_year(n_4, current_day);
|
||||
|
||||
uint16_t last_leap_year = 1996 + 4*(n_4-1);
|
||||
uint16_t days_till_this_year = (year - last_leap_year)*365;
|
||||
if (days_till_this_year != 0) {
|
||||
days_till_this_year++;
|
||||
}
|
||||
|
||||
eph.setYear(year);
|
||||
eph.setDayInYear(current_day - days_till_this_year);
|
||||
eph.setHour((eph.getTk()>>7) & 0x1F);
|
||||
eph.setMinute((eph.getTk()>>1) & 0x3F);
|
||||
eph.setSecond((eph.getTk() & 0x1) * 30);
|
||||
}
|
||||
|
||||
glonass_strings[msg->sv_id()].clear();
|
||||
return capnp::messageToFlatArray(msg_builder);
|
||||
}
|
||||
|
||||
|
||||
kj::Array<capnp::word> UbloxMsgParser::gen_rxm_sfrbx(ubx_t::rxm_sfrbx_t *msg) {
|
||||
switch (msg->gnss_id()) {
|
||||
case ubx_t::gnss_type_t::GNSS_TYPE_GPS:
|
||||
return parse_gps_ephemeris(msg);
|
||||
case ubx_t::gnss_type_t::GNSS_TYPE_GLONASS:
|
||||
return parse_glonass_ephemeris(msg);
|
||||
default:
|
||||
return kj::Array<capnp::word>();
|
||||
}
|
||||
}
|
||||
|
||||
kj::Array<capnp::word> UbloxMsgParser::gen_rxm_rawx(ubx_t::rxm_rawx_t *msg) {
|
||||
MessageBuilder msg_builder;
|
||||
auto mr = msg_builder.initEvent().initUbloxGnss().initMeasurementReport();
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "cereal/messaging/messaging.h"
|
||||
#include "common/util.h"
|
||||
#include "selfdrive/locationd/generated/gps.h"
|
||||
#include "selfdrive/locationd/generated/glonass.h"
|
||||
#include "selfdrive/locationd/generated/ubx.h"
|
||||
|
||||
using namespace std::string_literals;
|
||||
@@ -101,12 +102,22 @@ class UbloxMsgParser {
|
||||
inline bool valid_cheksum();
|
||||
inline bool valid();
|
||||
inline bool valid_so_far();
|
||||
inline uint16_t get_glonass_year(uint8_t N4, uint16_t Nt);
|
||||
|
||||
kj::Array<capnp::word> parse_gps_ephemeris(ubx_t::rxm_sfrbx_t *msg);
|
||||
kj::Array<capnp::word> parse_glonass_ephemeris(ubx_t::rxm_sfrbx_t *msg);
|
||||
|
||||
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];
|
||||
|
||||
};
|
||||
// user range accuracy in meters
|
||||
const std::unordered_map<uint8_t, float> glonass_URA_lookup =
|
||||
{{ 0, 1}, { 1, 2}, { 2, 2.5}, { 3, 4}, { 4, 5}, {5, 7},
|
||||
{ 6, 10}, { 7, 12}, { 8, 14}, { 9, 16}, {10, 32},
|
||||
{11, 64}, {12, 128}, {13, 256}, {14, 512}, {15, 1024}};
|
||||
|
||||
std::unordered_map<int, std::unordered_map<int, std::string>> glonass_strings;
|
||||
std::unordered_map<int, int> glonass_superframes;
|
||||
};
|
||||
|
||||
@@ -13,6 +13,8 @@ if arch == "Darwin":
|
||||
# fix OpenCL
|
||||
del libs[libs.index('OpenCL')]
|
||||
env['FRAMEWORKS'] = ['OpenCL']
|
||||
# exclude v4l
|
||||
del src[src.index('encoder/v4l_encoder.cc')]
|
||||
|
||||
logger_lib = env.Library('logger', src)
|
||||
libs.insert(0, logger_lib)
|
||||
|
||||
@@ -24,15 +24,25 @@ void logger_rotate(LoggerdState *s) {
|
||||
}
|
||||
|
||||
void rotate_if_needed(LoggerdState *s) {
|
||||
if (s->ready_to_rotate == s->max_waiting) {
|
||||
logger_rotate(s);
|
||||
// all encoders ready, trigger rotation
|
||||
bool all_ready = s->ready_to_rotate == s->max_waiting;
|
||||
|
||||
// fallback logic to prevent extremely long segments in the case of camera, encoder, etc. malfunctions
|
||||
bool timed_out = false;
|
||||
double tms = millis_since_boot();
|
||||
double seg_length_secs = (tms - s->last_rotate_tms) / 1000.;
|
||||
if ((seg_length_secs > SEGMENT_LENGTH) && !LOGGERD_TEST) {
|
||||
// TODO: might be nice to put these reasons in the sentinel
|
||||
if ((tms - s->last_camera_seen_tms) > NO_CAMERA_PATIENCE) {
|
||||
timed_out = true;
|
||||
LOGE("no camera packets seen. auto rotating");
|
||||
} else if (seg_length_secs > SEGMENT_LENGTH*1.2) {
|
||||
timed_out = true;
|
||||
LOGE("segment too long. auto rotating");
|
||||
}
|
||||
}
|
||||
|
||||
double tms = millis_since_boot();
|
||||
if ((tms - s->last_rotate_tms) > SEGMENT_LENGTH * 1000 &&
|
||||
(tms - s->last_camera_seen_tms) > NO_CAMERA_PATIENCE &&
|
||||
!LOGGERD_TEST) {
|
||||
LOGW("no camera packet seen. auto rotating");
|
||||
if (all_ready || timed_out) {
|
||||
logger_rotate(s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ from system.version import is_dirty
|
||||
MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9
|
||||
CACHE_DIR = Path("/data/scons_cache" if AGNOS else "/tmp/scons_cache")
|
||||
|
||||
TOTAL_SCONS_NODES = 2395
|
||||
TOTAL_SCONS_NODES = 2460
|
||||
MAX_BUILD_PROGRESS = 100
|
||||
PREBUILT = os.path.exists(os.path.join(BASEDIR, 'prebuilt'))
|
||||
|
||||
|
||||
@@ -226,7 +226,11 @@ class RouteEngine:
|
||||
remaining = 1.0 - along_geometry / max(step['distance'], 1)
|
||||
total_distance = step['distance'] * remaining
|
||||
total_time = step['duration'] * remaining
|
||||
total_time_typical = step['duration_typical'] * remaining
|
||||
|
||||
if step['duration_typical'] is None:
|
||||
total_time_typical = total_time
|
||||
else:
|
||||
total_time_typical = step['duration_typical'] * remaining
|
||||
|
||||
# Add up totals for future steps
|
||||
for i in range(self.step_idx + 1, len(self.route)):
|
||||
|
||||
@@ -123,7 +123,7 @@ class TTYPigeon():
|
||||
init_baudrate(self)
|
||||
|
||||
# clear configuration
|
||||
self.send_with_ack(b"\xb5\x62\x06\x09\x0d\x00\x00\x00\x1f\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x17\x71\x5b")
|
||||
self.send_with_ack(b"\xb5\x62\x06\x09\x0d\x00\x1f\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17\x71\xd7")
|
||||
|
||||
# clear flash memory (almanac backup)
|
||||
self.send_with_ack(b"\xB5\x62\x09\x14\x04\x00\x01\x00\x00\x00\x22\xf0")
|
||||
|
||||
@@ -204,7 +204,7 @@ def controlsd_rcv_callback(msg, CP, cfg, fsm):
|
||||
def radar_rcv_callback(msg, CP, cfg, fsm):
|
||||
if msg.which() != "can":
|
||||
return [], False
|
||||
elif CP.radarOffCan:
|
||||
elif CP.radarUnavailable:
|
||||
return ["radarState", "liveTracks"], True
|
||||
|
||||
radar_msgs = {"honda": [0x445], "toyota": [0x19f, 0x22f], "gm": [0x474],
|
||||
|
||||
@@ -1 +1 @@
|
||||
844c84eae269e74b246b039d2b33fd44e98a5b68
|
||||
7c2a98f4a94b6dded6a90d8913ab85a63069c186
|
||||
+59
-13
@@ -175,11 +175,62 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) {
|
||||
}
|
||||
|
||||
|
||||
ExperimentalButton::ExperimentalButton(QWidget *parent) : QPushButton(parent) {
|
||||
setVisible(false);
|
||||
setFixedSize(btn_size, btn_size);
|
||||
setCheckable(true);
|
||||
|
||||
params = Params();
|
||||
engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size});
|
||||
experimental_img = loadPixmap("../assets/img_experimental.svg", {img_size, img_size});
|
||||
|
||||
QObject::connect(this, &QPushButton::toggled, [=](bool checked) {
|
||||
params.putBool("ExperimentalMode", checked);
|
||||
});
|
||||
}
|
||||
|
||||
void ExperimentalButton::updateState(const UIState &s) {
|
||||
const SubMaster &sm = *(s.sm);
|
||||
|
||||
// button is "visible" if engageable or enabled
|
||||
const auto cs = sm["controlsState"].getControlsState();
|
||||
setVisible(cs.getEngageable() || cs.getEnabled());
|
||||
|
||||
// button is "checked" if experimental mode is enabled
|
||||
setChecked(sm["controlsState"].getControlsState().getExperimentalMode());
|
||||
|
||||
// disable button when experimental mode is not available, or has not been confirmed for the first time
|
||||
const auto cp = sm["carParams"].getCarParams();
|
||||
const bool experimental_mode_available = cp.getExperimentalLongitudinalAvailable() ? params.getBool("ExperimentalLongitudinalEnabled") : cp.getOpenpilotLongitudinalControl();
|
||||
setEnabled(params.getBool("ExperimentalModeConfirmed") && experimental_mode_available);
|
||||
}
|
||||
|
||||
void ExperimentalButton::paintEvent(QPaintEvent *event) {
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
QPoint center(btn_size / 2, btn_size / 2);
|
||||
QPixmap img = isChecked() ? experimental_img : engage_img;
|
||||
|
||||
p.setOpacity(1.0);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(QColor(0, 0, 0, 166));
|
||||
p.drawEllipse(center, btn_size / 2, btn_size / 2);
|
||||
p.setOpacity(isDown() ? 0.8 : 1.0);
|
||||
p.drawPixmap((btn_size - img_size) / 2, (btn_size - img_size) / 2, img);
|
||||
}
|
||||
|
||||
|
||||
AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) {
|
||||
pm = std::make_unique<PubMaster, const std::initializer_list<const char *>>({"uiDebug"});
|
||||
|
||||
engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size});
|
||||
experimental_img = loadPixmap("../assets/img_experimental.svg", {img_size - 5, img_size - 5});
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->setMargin(bdr_s);
|
||||
main_layout->setSpacing(0);
|
||||
|
||||
experimental_btn = new ExperimentalButton(this);
|
||||
main_layout->addWidget(experimental_btn, 0, Qt::AlignTop | Qt::AlignRight);
|
||||
|
||||
dm_img = loadPixmap("../assets/img_driver_face.png", {img_size, img_size});
|
||||
}
|
||||
|
||||
@@ -227,9 +278,11 @@ void AnnotatedCameraWidget::updateState(const UIState &s) {
|
||||
setProperty("hideDM", cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE);
|
||||
setProperty("status", s.status);
|
||||
|
||||
// update engageability and DM icons at 2Hz
|
||||
// update engageability/experimental mode button
|
||||
experimental_btn->updateState(s);
|
||||
|
||||
// update DM icons at 2Hz
|
||||
if (sm.frame % (UI_FREQ / 2) == 0) {
|
||||
setProperty("engageable", cs.getEngageable() || cs.getEnabled());
|
||||
setProperty("dmActive", sm["driverMonitoringState"].getDriverMonitoringState().getIsActiveMode());
|
||||
setProperty("rightHandDM", sm["driverMonitoringState"].getDriverMonitoringState().getIsRHD());
|
||||
}
|
||||
@@ -382,16 +435,9 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) {
|
||||
configFont(p, "Inter", 66, "Regular");
|
||||
drawText(p, rect().center().x(), 290, speedUnit, 200);
|
||||
|
||||
// engage-ability icon
|
||||
if (engageable) {
|
||||
SubMaster &sm = *(uiState()->sm);
|
||||
drawIcon(p, rect().right() - radius / 2 - bdr_s * 2, radius / 2 + int(bdr_s * 1.5),
|
||||
sm["controlsState"].getControlsState().getExperimentalMode() ? experimental_img : engage_img, blackColor(166), 1.0);
|
||||
}
|
||||
|
||||
// dm icon
|
||||
if (!hideDM) {
|
||||
int dm_icon_x = rightHandDM ? rect().right() - radius / 2 - (bdr_s * 2) : radius / 2 + (bdr_s * 2);
|
||||
int dm_icon_x = rightHandDM ? rect().right() - btn_size / 2 - (bdr_s * 2) : btn_size / 2 + (bdr_s * 2);
|
||||
drawIcon(p, dm_icon_x, rect().bottom() - footer_h / 2,
|
||||
dm_img, blackColor(70), dmActive ? 1.0 : 0.2);
|
||||
}
|
||||
@@ -414,7 +460,7 @@ void AnnotatedCameraWidget::drawIcon(QPainter &p, int x, int y, QPixmap &img, QB
|
||||
p.setOpacity(1.0); // bg dictates opacity of ellipse
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(bg);
|
||||
p.drawEllipse(x - radius / 2, y - radius / 2, radius, radius);
|
||||
p.drawEllipse(x - btn_size / 2, y - btn_size / 2, btn_size, btn_size);
|
||||
p.setOpacity(opacity);
|
||||
p.drawPixmap(x - img.size().width() / 2, y - img.size().height() / 2, img);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QStackedLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#include "common/util.h"
|
||||
#include "selfdrive/ui/qt/widgets/cameraview.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
#include "selfdrive/ui/qt/widgets/cameraview.h"
|
||||
|
||||
|
||||
const int btn_size = 192;
|
||||
const int img_size = (btn_size / 4) * 3;
|
||||
|
||||
|
||||
// ***** onroad widgets *****
|
||||
@@ -24,6 +29,21 @@ private:
|
||||
Alert alert = {};
|
||||
};
|
||||
|
||||
class ExperimentalButton : public QPushButton {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ExperimentalButton(QWidget *parent = 0);
|
||||
void updateState(const UIState &s);
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
Params params;
|
||||
QPixmap engage_img;
|
||||
QPixmap experimental_img;
|
||||
};
|
||||
|
||||
// container window for the NVG UI
|
||||
class AnnotatedCameraWidget : public CameraWidget {
|
||||
Q_OBJECT
|
||||
@@ -36,7 +56,6 @@ class AnnotatedCameraWidget : public CameraWidget {
|
||||
Q_PROPERTY(bool has_us_speed_limit MEMBER has_us_speed_limit);
|
||||
Q_PROPERTY(bool is_metric MEMBER is_metric);
|
||||
|
||||
Q_PROPERTY(bool engageable MEMBER engageable);
|
||||
Q_PROPERTY(bool dmActive MEMBER dmActive);
|
||||
Q_PROPERTY(bool hideDM MEMBER hideDM);
|
||||
Q_PROPERTY(bool rightHandDM MEMBER rightHandDM);
|
||||
@@ -50,18 +69,14 @@ private:
|
||||
void drawIcon(QPainter &p, int x, int y, QPixmap &img, QBrush bg, float opacity);
|
||||
void drawText(QPainter &p, int x, int y, const QString &text, int alpha = 255);
|
||||
|
||||
QPixmap engage_img;
|
||||
QPixmap experimental_img;
|
||||
ExperimentalButton *experimental_btn;
|
||||
QPixmap dm_img;
|
||||
const int radius = 192;
|
||||
const int img_size = (radius / 2) * 1.5;
|
||||
float speed;
|
||||
QString speedUnit;
|
||||
float setSpeed;
|
||||
float speedLimit;
|
||||
bool is_cruise_set = false;
|
||||
bool is_metric = false;
|
||||
bool engageable = false;
|
||||
bool dmActive = false;
|
||||
bool hideDM = false;
|
||||
bool rightHandDM = false;
|
||||
|
||||
@@ -238,6 +238,14 @@
|
||||
<source>Disengage to Power Off</source>
|
||||
<translation>ยกเลิกระบบช่วยขับเพื่อปิดเครื่อง</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reset</source>
|
||||
<translation>รีเซ็ต</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Review</source>
|
||||
<translation>ทบทวน</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DriveStats</name>
|
||||
@@ -273,6 +281,17 @@
|
||||
<translation>กำลังเปิดกล้อง</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ExperimentalModeButton</name>
|
||||
<message>
|
||||
<source>EXPERIMENTAL MODE ON</source>
|
||||
<translation>คุณกำลังใช้โหมดทดลอง</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>CHILL MODE ON</source>
|
||||
<translation>คุณกำลังใช้โหมดชิล</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>InputDialog</name>
|
||||
<message>
|
||||
@@ -463,6 +482,17 @@ location set</source>
|
||||
<translation>จดจำ connect.comma.ai โดยการเพิ่มไปยังหน้าจอโฮม เพื่อใช้งานเหมือนเป็นแอปพลิเคชัน</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ParamControl</name>
|
||||
<message>
|
||||
<source>Enable</source>
|
||||
<translation>เปิดใช้งาน</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel</source>
|
||||
<translation>ยกเลิก</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PrimeAdWidget</name>
|
||||
<message>
|
||||
@@ -585,13 +615,6 @@ location set</source>
|
||||
<translation>ไม่สามารถเมานต์พาร์ติชั่นข้อมูล กดยืนยันเพื่อรีเซ็ตอุปกรณ์ของคุณ</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RichTextDialog</name>
|
||||
<message>
|
||||
<source>Ok</source>
|
||||
<translation>ตกลง</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsWindow</name>
|
||||
<message>
|
||||
@@ -850,6 +873,10 @@ location set</source>
|
||||
<source>Select a branch</source>
|
||||
<translation>เลือก Branch</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Uninstall</source>
|
||||
<translation>ถอนการติดตั้ง</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SshControl</name>
|
||||
@@ -974,29 +1001,57 @@ location set</source>
|
||||
<source>Show map on left side when in split screen view.</source>
|
||||
<translation>แสดงแผนที่ด้านซ้ายของหน้าจอเมื่ออยู่ในโหมดแบ่งหน้าจอ</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>🌮 End-to-end longitudinal (extremely alpha) 🌮</source>
|
||||
<translation>🌮 ควบคุมเร่ง/เบรคแบบ End-to-end (อยู่ขั้นพัฒนา) 🌮</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Experimental openpilot Longitudinal Control</source>
|
||||
<translation>ทดลองใช้ระบบควบคุมการเร่ง/เบรคโดย openpilot</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b></source>
|
||||
<translation><b>คำเตือน: การควบคุมการเร่ง/เบรคโดย openpilot สำหรับรถคันนี้ยังอยู่ในขั้นทดลอง และระบบเบรคฉุกเฉินอัตโนมัติ (AEB) จะถูกปิด</b></translation>
|
||||
<source>Experimental Mode</source>
|
||||
<translation>โหมดทดลอง</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental.</source>
|
||||
<translation>ให้ openpilot ควบคุมการเร่ง/เบรคแบบ end-to-end โดย openpilot จะขับอย่างที่มนุษย์คิด ระบบยังอยู่ในขั้นทดลอง</translation>
|
||||
<source>WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).</source>
|
||||
<translation>คำเตือน: การควบคุมการเร่ง/เบรคโดย openpilot สำหรับรถคันนี้ยังอยู่ในขั้นพัฒนา และระบบเบรคฉุกเฉินอัตโนมัติ (AEB) จะถูกปิด</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>openpilot longitudinal control is not currently available for this car.</source>
|
||||
<translation>ขณะนี้ยังไม่มีระบบควบคุมการเร่ง/เบรคโดย openpilot สำหรับรถคันนี้</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>โดยปกติสำหรับรถคันนี้ openpilot จะควบคุมการเร่ง/เบรคด้วยระบบ ACC จากโรงงาน แทนการควยคุมโดย openpilot เปิดสวิตซ์นี้เพื่อให้ openpilot ควบคุมการเร่ง/เบรค แนะนำให้เปิดโหมดทดลองเมื่อต้องการให้ openpilot ควบคุมการเร่ง/เบรค ซึ่งอยู่ในขั้นพัฒนา</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable experimental longitudinal control to enable this.</source>
|
||||
<translation>เปิดใช้งานระบบควบคุมการเร่ง/เบรคขั้นทดลอง เพื่อเปิดใช้งานสิ่งนี้</translation>
|
||||
<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>
|
||||
</message>
|
||||
<message>
|
||||
<source>🌮 End-to-End Longitudinal Control 🌮</source>
|
||||
<translation>🌮 ควบคุมเร่ง/เบรคแบบ End-to-End 🌮</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected.</source>
|
||||
<translation>ให้ openpilot ควบคุมการเร่ง/เบรค โดย openpilot จะขับอย่างที่มนุษย์คิด รวมถึงการหยุดที่ไฟแดง และป้ายหยุดรถ เนื่องจาก openpilot จะกำหนดความเร็วในการขับด้วยตัวเอง การตั้งความเร็วจะเป็นเพียงการกำหนดความเร็วสูงสูดเท่านั้น ความสามารถนี้ยังอยู่ในขั้นพัฒนา อาจเกิดข้อผิดพลาดขึ้นได้</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>New Driving Visualization</source>
|
||||
<translation>การแสดงภาพการขับขี่แบบใหม่</translation>
|
||||
</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>
|
||||
</message>
|
||||
<message>
|
||||
<source>Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.</source>
|
||||
<translation>ขณะนี้โหมดทดลองไม่สามารถใช้งานได้ในรถคันนี้ เนื่องจากเปิดใช้ระบบควบคุมการเร่ง/เบรคของรถที่ติดตั้งจากโรงงานอยู่</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>openpilot longitudinal control may come in a future update.</source>
|
||||
<translation>ระบบควบคุมการเร่ง/เบรคโดย openpilot อาจมาในการอัปเดตในอนาคต</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches.</source>
|
||||
<translation>เวอร์ชันทดลองของระบบควบคุมการเร่ง/เบรคโดย openpilot สามารถทดสอบได้พร้อมกับโหมดการทดลอง บน branch ที่กำลังพัฒนา</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable experimental longitudinal control to allow Experimental mode.</source>
|
||||
<translation>เปิดระบบควบคุมการเร่ง/เบรคขั้นพัฒนาโดย openpilot เพื่อเปิดใช้งานโหมดทดลอง</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -1052,5 +1107,9 @@ location set</source>
|
||||
<source>Forget Wi-Fi Network "%1"?</source>
|
||||
<translation>เลิกใช้เครือข่าย Wi-Fi "%1"?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Forget</source>
|
||||
<translation>เลิกใช้</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
||||
@@ -423,6 +423,9 @@ def main() -> None:
|
||||
wait_helper = WaitTimeHelper()
|
||||
wait_helper.only_check_for_update = True
|
||||
|
||||
# invalidate old finalized update
|
||||
set_consistent_flag(False)
|
||||
|
||||
# Run the update loop
|
||||
while True:
|
||||
wait_helper.ready_event.clear()
|
||||
|
||||
+4
-5
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
import sounddevice as sd
|
||||
import numpy as np
|
||||
|
||||
from cereal import messaging
|
||||
@@ -84,11 +83,11 @@ class Mic:
|
||||
|
||||
self.measurements = self.measurements[FFT_SAMPLES:]
|
||||
|
||||
def micd_thread(self, device=None):
|
||||
if device is None:
|
||||
device = "sysdefault"
|
||||
def micd_thread(self):
|
||||
# sounddevice must be imported after forking processes
|
||||
import sounddevice as sd # pylint: disable=import-outside-toplevel
|
||||
|
||||
with sd.InputStream(device=device, channels=1, samplerate=SAMPLE_RATE, callback=self.callback) as stream:
|
||||
with sd.InputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback) as stream:
|
||||
cloudlog.info(f"micd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}")
|
||||
while True:
|
||||
self.update()
|
||||
|
||||
@@ -21,9 +21,12 @@ 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', '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)
|
||||
'commands.cc', 'messageswidget.cc', 'route.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 arch == "Darwin":
|
||||
cabana_env.Execute('install_name_tool -change opendbc/can/libdbc.dylib @loader_path/../../opendbc/can/libdbc.dylib ./_cabana')
|
||||
|
||||
if GetOption('test'):
|
||||
cabana_env.Program('tests/_test_cabana', ['tests/test_runner.cc', 'tests/test_cabana.cc', cabana_lib], LIBS=[cabana_libs])
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
#include <QHeaderView>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QScrollBar>
|
||||
#include <QToolTip>
|
||||
|
||||
#include "tools/cabana/commands.h"
|
||||
#include "tools/cabana/streams/abstractstream.h"
|
||||
|
||||
// BinaryView
|
||||
@@ -30,12 +32,15 @@ BinaryView::BinaryView(QWidget *parent) : QTableView(parent) {
|
||||
setFrameShape(QFrame::NoFrame);
|
||||
setShowGrid(false);
|
||||
setMouseTracking(true);
|
||||
setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
|
||||
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &BinaryView::refresh);
|
||||
QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &BinaryView::refresh);
|
||||
}
|
||||
|
||||
QSize BinaryView::minimumSizeHint() const {
|
||||
return {horizontalHeader()->minimumSectionSize() * 9 + VERTICAL_HEADER_WIDTH + 2, 0};
|
||||
return {(horizontalHeader()->minimumSectionSize() + 1) * 9 + VERTICAL_HEADER_WIDTH,
|
||||
CELL_HEIGHT * std::min(model->rowCount(), 10)};
|
||||
}
|
||||
|
||||
void BinaryView::highlight(const Signal *sig) {
|
||||
@@ -124,13 +129,20 @@ void BinaryView::leaveEvent(QEvent *event) {
|
||||
}
|
||||
|
||||
void BinaryView::setMessage(const QString &message_id) {
|
||||
model->setMessage(message_id);
|
||||
model->msg_id = message_id;
|
||||
verticalScrollBar()->setValue(0);
|
||||
refresh();
|
||||
}
|
||||
|
||||
void BinaryView::refresh() {
|
||||
if (model->msg_id.isEmpty()) return;
|
||||
|
||||
clearSelection();
|
||||
anchor_index = QModelIndex();
|
||||
resize_sig = nullptr;
|
||||
hovered_sig = nullptr;
|
||||
highlightPosition(QCursor::pos());
|
||||
updateState();
|
||||
model->refresh();
|
||||
}
|
||||
|
||||
QSet<const Signal *> BinaryView::getOverlappingSignals() const {
|
||||
@@ -155,9 +167,8 @@ std::tuple<int, int, bool> BinaryView::getSelection(QModelIndex index) {
|
||||
|
||||
// BinaryViewModel
|
||||
|
||||
void BinaryViewModel::setMessage(const QString &message_id) {
|
||||
void BinaryViewModel::refresh() {
|
||||
beginResetModel();
|
||||
msg_id = message_id;
|
||||
items.clear();
|
||||
if ((dbc_msg = dbc()->msg(msg_id))) {
|
||||
row_count = dbc_msg->size;
|
||||
@@ -184,6 +195,7 @@ void BinaryViewModel::setMessage(const QString &message_id) {
|
||||
items.resize(row_count * column_count);
|
||||
}
|
||||
endResetModel();
|
||||
updateState();
|
||||
}
|
||||
|
||||
void BinaryViewModel::updateState() {
|
||||
@@ -200,7 +212,7 @@ void BinaryViewModel::updateState() {
|
||||
}
|
||||
char hex[3] = {'\0'};
|
||||
for (int i = 0; i < binary.size(); ++i) {
|
||||
for (int j = 0; j < column_count - 1; ++j) {
|
||||
for (int j = 0; j < 8; ++j) {
|
||||
items[i * column_count + j].val = ((binary[i] >> (7 - j)) & 1) != 0 ? '1' : '0';
|
||||
}
|
||||
hex[0] = toHex(binary[i] >> 4);
|
||||
@@ -214,7 +226,7 @@ void BinaryViewModel::updateState() {
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < row_count * column_count; ++i) {
|
||||
for (int i = 0; i < items.size(); ++i) {
|
||||
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);
|
||||
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
class BinaryViewModel : public QAbstractTableModel {
|
||||
public:
|
||||
BinaryViewModel(QObject *parent) : QAbstractTableModel(parent) {}
|
||||
void setMessage(const QString &message_id);
|
||||
void refresh();
|
||||
void updateState();
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const { return {}; }
|
||||
@@ -39,12 +39,11 @@ public:
|
||||
QColor bg_color = QApplication::style()->standardPalette().color(QPalette::Base);
|
||||
bool is_msb = false;
|
||||
bool is_lsb = false;
|
||||
QString val = "0";
|
||||
QString val = "-";
|
||||
QList<const Signal *> sigs;
|
||||
};
|
||||
std::vector<Item> items;
|
||||
|
||||
private:
|
||||
QString msg_id;
|
||||
const DBCMsg *dbc_msg = nullptr;
|
||||
int row_count = 0;
|
||||
@@ -69,6 +68,7 @@ signals:
|
||||
void resizeSignal(const Signal *sig, int from, int size);
|
||||
|
||||
private:
|
||||
void refresh();
|
||||
std::tuple<int, int, bool> getSelection(QModelIndex index);
|
||||
void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
|
||||
+23
-7
@@ -4,6 +4,7 @@
|
||||
#include "common/prefix.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "tools/cabana/mainwin.h"
|
||||
#include "tools/cabana/route.h"
|
||||
#include "tools/cabana/streams/livestream.h"
|
||||
#include "tools/cabana/streams/replaystream.h"
|
||||
|
||||
@@ -24,11 +25,8 @@ int main(int argc, char *argv[]) {
|
||||
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.addOption({"dbc", "dbc file to open", "dbc"});
|
||||
cmd_parser.process(app);
|
||||
const QStringList args = cmd_parser.positionalArguments();
|
||||
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;
|
||||
@@ -40,7 +38,6 @@ int main(int argc, char *argv[]) {
|
||||
#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;
|
||||
@@ -49,14 +46,33 @@ int main(int argc, char *argv[]) {
|
||||
} else if (cmd_parser.isSet("no-vipc")) {
|
||||
replay_flags |= REPLAY_FLAG_NO_VIPC;
|
||||
}
|
||||
auto replay_stream = new ReplayStream(&app);
|
||||
|
||||
const QStringList args = cmd_parser.positionalArguments();
|
||||
QString route;
|
||||
if (args.size() > 0) {
|
||||
route = args.first();
|
||||
} else if (cmd_parser.isSet("demo")) {
|
||||
route = DEMO_ROUTE;
|
||||
}
|
||||
|
||||
auto replay_stream = new ReplayStream(replay_flags, &app);
|
||||
stream.reset(replay_stream);
|
||||
if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) {
|
||||
if (route.isEmpty()) {
|
||||
if (OpenRouteDialog dlg(nullptr); !dlg.exec()) {
|
||||
return 0;
|
||||
}
|
||||
} else if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"))) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
MainWindow w;
|
||||
|
||||
// Load DBC
|
||||
if (cmd_parser.isSet("dbc")) {
|
||||
w.loadFile(cmd_parser.value("dbc"));
|
||||
}
|
||||
|
||||
w.show();
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
+166
-56
@@ -7,14 +7,13 @@
|
||||
#include <QFutureSynchronizer>
|
||||
#include <QGraphicsLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QRubberBand>
|
||||
#include <QToolBar>
|
||||
#include <QToolButton>
|
||||
#include <QToolTip>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
|
||||
// ChartsWidget
|
||||
|
||||
ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
|
||||
@@ -24,14 +23,14 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
|
||||
QToolBar *toolbar = new QToolBar(tr("Charts"), this);
|
||||
toolbar->setIconSize({16, 16});
|
||||
|
||||
QAction *new_plot_btn = toolbar->addAction(bootstrapPixmap("file-plus"), "");
|
||||
QAction *new_plot_btn = toolbar->addAction(utils::icon("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);
|
||||
columns_lb_action = toolbar->addWidget(new QLabel(tr("Columns:")));
|
||||
columns_cb_action = toolbar->addWidget(columns_cb);
|
||||
|
||||
QLabel *stretch_label = new QLabel(this);
|
||||
stretch_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
@@ -46,19 +45,21 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
|
||||
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 = toolbar->addAction(utils::icon("zoom-out"), "");
|
||||
reset_zoom_btn->setToolTip(tr("Reset zoom (drag on chart to zoom X-Axis)"));
|
||||
remove_all_btn = toolbar->addAction(bootstrapPixmap("x"), "");
|
||||
remove_all_btn = toolbar->addAction(utils::icon("x"), "");
|
||||
remove_all_btn->setToolTip(tr("Remove all charts"));
|
||||
dock_btn = toolbar->addAction("");
|
||||
main_layout->addWidget(toolbar);
|
||||
|
||||
// charts
|
||||
charts_layout = new QGridLayout();
|
||||
charts_layout->setSpacing(10);
|
||||
|
||||
QWidget *charts_container = new QWidget(this);
|
||||
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->addLayout(charts_layout);
|
||||
charts_main_layout->addStretch(0);
|
||||
|
||||
QScrollArea *charts_scroll = new QScrollArea(this);
|
||||
@@ -136,7 +137,7 @@ void ChartsWidget::updateState() {
|
||||
if (!is_zoomed) {
|
||||
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();
|
||||
const double min_event_sec = can->events()->empty() ? 0 : (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);
|
||||
@@ -156,7 +157,7 @@ void ChartsWidget::updateState() {
|
||||
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();
|
||||
const double min_event_sec = can->events()->empty() ? 0 : (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)));
|
||||
@@ -167,9 +168,8 @@ void ChartsWidget::setMaxChartRange(int value) {
|
||||
|
||||
void ChartsWidget::updateToolBar() {
|
||||
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->setIcon(utils::icon(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);
|
||||
@@ -179,6 +179,7 @@ void ChartsWidget::settingChanged() {
|
||||
range_slider->setRange(1, settings.max_cached_minutes * 60);
|
||||
for (auto c : charts) {
|
||||
c->setFixedHeight(settings.chart_height);
|
||||
c->setSeriesType(settings.chart_series_type == 0 ? QAbstractSeries::SeriesTypeLine : QAbstractSeries::SeriesTypeScatter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,10 +229,16 @@ void ChartsWidget::setColumnCount(int n) {
|
||||
}
|
||||
|
||||
void ChartsWidget::updateLayout() {
|
||||
int n = column_count;
|
||||
int n = columns_cb->count();
|
||||
for (; n > 1; --n) {
|
||||
if ((n * (CHART_MIN_WIDTH + charts_layout->spacing())) < rect().width()) break;
|
||||
if ((n * CHART_MIN_WIDTH + (n - 1) * charts_layout->spacing()) < charts_layout->geometry().width()) break;
|
||||
}
|
||||
|
||||
bool show_column_cb = n > 1;
|
||||
columns_lb_action->setVisible(show_column_cb);
|
||||
columns_cb_action->setVisible(show_column_cb);
|
||||
|
||||
n = std::min(column_count, n);
|
||||
for (int i = 0; i < charts.size(); ++i) {
|
||||
charts_layout->addWidget(charts[charts.size() - i - 1], i / n, i % n);
|
||||
}
|
||||
@@ -292,18 +299,21 @@ bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) {
|
||||
// ChartView
|
||||
|
||||
ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
|
||||
series_type = settings.chart_series_type == 0 ? QAbstractSeries::SeriesTypeLine : QAbstractSeries::SeriesTypeScatter;
|
||||
|
||||
QChart *chart = new QChart();
|
||||
chart->setBackgroundRoundness(0);
|
||||
axis_x = new QValueAxis(this);
|
||||
axis_y = new QValueAxis(this);
|
||||
chart->addAxis(axis_x, Qt::AlignBottom);
|
||||
chart->addAxis(axis_y, Qt::AlignLeft);
|
||||
chart->legend()->layout()->setContentsMargins(0, 0, 40, 0);
|
||||
chart->legend()->setShowToolTips(true);
|
||||
chart->layout()->setContentsMargins(0, 0, 0, 0);
|
||||
chart->setMargins({20, 11, 11, 11});
|
||||
|
||||
QToolButton *remove_btn = new QToolButton();
|
||||
remove_btn->setIcon(bootstrapPixmap("x"));
|
||||
remove_btn->setIcon(utils::icon("x"));
|
||||
remove_btn->setAutoRaise(true);
|
||||
remove_btn->setToolTip(tr("Remove Chart"));
|
||||
close_btn_proxy = new QGraphicsProxyWidget(chart);
|
||||
@@ -311,9 +321,20 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
|
||||
close_btn_proxy->setZValue(chart->zValue() + 11);
|
||||
|
||||
QToolButton *manage_btn = new QToolButton();
|
||||
manage_btn->setIcon(bootstrapPixmap("gear"));
|
||||
manage_btn->setToolButtonStyle(Qt::ToolButtonIconOnly);
|
||||
manage_btn->setIcon(utils::icon("list"));
|
||||
manage_btn->setAutoRaise(true);
|
||||
manage_btn->setToolTip(tr("Manage series"));
|
||||
QMenu *menu = new QMenu(this);
|
||||
line_series_action = menu->addAction(tr("Line"), [this]() { setSeriesType(QAbstractSeries::SeriesTypeLine); });
|
||||
line_series_action->setCheckable(true);
|
||||
line_series_action->setChecked(series_type == QAbstractSeries::SeriesTypeLine);
|
||||
scatter_series_action = menu->addAction(tr("Scatter"), [this]() { setSeriesType(QAbstractSeries::SeriesTypeScatter); });
|
||||
scatter_series_action->setCheckable(true);
|
||||
scatter_series_action->setChecked(series_type == QAbstractSeries::SeriesTypeScatter);
|
||||
menu->addSeparator();
|
||||
menu->addAction(tr("Manage series"), this, &ChartView::manageSeries);
|
||||
manage_btn->setMenu(menu);
|
||||
manage_btn->setPopupMode(QToolButton::InstantPopup);
|
||||
manage_btn_proxy = new QGraphicsProxyWidget(chart);
|
||||
manage_btn_proxy->setWidget(manage_btn);
|
||||
manage_btn_proxy->setZValue(chart->zValue() + 11);
|
||||
@@ -328,7 +349,6 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
|
||||
QObject::connect(dbc(), &DBCManager::msgRemoved, this, &ChartView::msgRemoved);
|
||||
QObject::connect(dbc(), &DBCManager::msgUpdated, this, &ChartView::msgUpdated);
|
||||
QObject::connect(remove_btn, &QToolButton::clicked, this, &ChartView::remove);
|
||||
QObject::connect(manage_btn, &QToolButton::clicked, this, &ChartView::manageSeries);
|
||||
}
|
||||
|
||||
qreal ChartView::getYAsixLabelWidth() const {
|
||||
@@ -348,13 +368,7 @@ 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
|
||||
QXYSeries *series = createSeries(series_type);
|
||||
chart()->addSeries(series);
|
||||
series->attachAxis(axis_x);
|
||||
series->attachAxis(axis_y);
|
||||
@@ -362,13 +376,14 @@ 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);
|
||||
updateSeriesPoints();
|
||||
emit seriesAdded(msg_id, sig);
|
||||
}
|
||||
|
||||
void ChartView::removeSeries(const QString &msg_id, const Signal *sig) {
|
||||
auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s.msg_id == msg_id && s.sig == sig; });
|
||||
if (it != sigs.end()) {
|
||||
it = removeSeries(it);
|
||||
it = removeItem(it);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,7 +391,7 @@ bool ChartView::hasSeries(const QString &msg_id, const Signal *sig) const {
|
||||
return std::any_of(sigs.begin(), sigs.end(), [&](auto &s) { return s.msg_id == msg_id && s.sig == sig; });
|
||||
}
|
||||
|
||||
QList<ChartView::SigItem>::iterator ChartView::removeSeries(const QList<ChartView::SigItem>::iterator &it) {
|
||||
QList<ChartView::SigItem>::iterator ChartView::removeItem(const QList<ChartView::SigItem>::iterator &it) {
|
||||
chart()->removeSeries(it->series);
|
||||
it->series->deleteLater();
|
||||
QString msg_id = it->msg_id;
|
||||
@@ -401,7 +416,7 @@ void ChartView::signalUpdated(const Signal *sig) {
|
||||
|
||||
void ChartView::signalRemoved(const Signal *sig) {
|
||||
for (auto it = sigs.begin(); it != sigs.end(); /**/) {
|
||||
it = (it->sig == sig) ? removeSeries(it) : ++it;
|
||||
it = (it->sig == sig) ? removeItem(it) : ++it;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,7 +427,7 @@ void ChartView::msgUpdated(uint32_t address) {
|
||||
|
||||
void ChartView::msgRemoved(uint32_t address) {
|
||||
for (auto it = sigs.begin(); it != sigs.end(); /**/) {
|
||||
it = (it->address == address) ? removeSeries(it) : ++it;
|
||||
it = (it->address == address) ? removeItem(it) : ++it;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,7 +459,7 @@ void ChartView::manageSeries() {
|
||||
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();
|
||||
});
|
||||
it = exists ? ++it : removeSeries(it);
|
||||
it = exists ? ++it : removeItem(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -468,10 +483,44 @@ void ChartView::updatePlot(double cur, double min, double max) {
|
||||
if (min != axis_x->min() || max != axis_x->max()) {
|
||||
axis_x->setRange(min, max);
|
||||
updateAxisY();
|
||||
updateSeriesPoints();
|
||||
}
|
||||
|
||||
scene()->invalidate({}, QGraphicsScene::ForegroundLayer);
|
||||
}
|
||||
|
||||
void ChartView::updateSeriesPoints() {
|
||||
// Show points when zoomed in enough
|
||||
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; });
|
||||
auto end = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->max(), [](auto &p, double x) { return p.x() < x; });
|
||||
|
||||
int num_points = std::max<int>(end - begin, 1);
|
||||
int pixels_per_point = width() / num_points;
|
||||
|
||||
if (series_type == QAbstractSeries::SeriesTypeScatter) {
|
||||
((QScatterSeries *)s.series)->setMarkerSize(std::clamp(pixels_per_point / 3, 1, 8));
|
||||
} else {
|
||||
s.series->setPointsVisible(pixels_per_point > 20);
|
||||
|
||||
// TODO: On MacOS QChartWidget doesn't work with the OpenGL settings that CameraWidget needs.
|
||||
#ifndef __APPLE
|
||||
// OpenGL mode lacks certain features (such as showing points), only use when drawing many points
|
||||
bool use_opengl = pixels_per_point < 1;
|
||||
s.series->setUseOpenGL(use_opengl);
|
||||
|
||||
// Qt doesn't properly apply device pixel ratio in OpenGL mode
|
||||
QApplication *application = static_cast<QApplication *>(QApplication::instance());
|
||||
float scale = use_opengl ? application->devicePixelRatio() : 1.0;
|
||||
|
||||
QPen pen = s.series->pen();
|
||||
pen.setWidth(2.0 * scale);
|
||||
s.series->setPen(pen);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::updateSeries(const Signal *sig, const std::vector<Event *> *events, bool clear) {
|
||||
events = events ? events : can->events();
|
||||
for (auto &s : sigs) {
|
||||
@@ -521,8 +570,8 @@ void ChartView::updateAxisY() {
|
||||
|
||||
if (min_y == std::numeric_limits<double>::max()) min_y = 0;
|
||||
if (max_y == std::numeric_limits<double>::lowest()) max_y = 0;
|
||||
if (max_y == min_y) {
|
||||
axis_y->setRange(min_y - 1, max_y + 1);
|
||||
if (std::abs(max_y - min_y) < 1e-3) {
|
||||
applyNiceNumbers(min_y - 1, max_y + 1);
|
||||
} else {
|
||||
double range = max_y - min_y;
|
||||
applyNiceNumbers(min_y - range * 0.05, max_y + range * 0.05);
|
||||
@@ -539,6 +588,7 @@ void ChartView::applyNiceNumbers(qreal min, qreal max) {
|
||||
tick_count = int(max - min) + 1;
|
||||
axis_y->setRange(min * step, max * step);
|
||||
axis_y->setTickCount(tick_count);
|
||||
axis_y->setLabelFormat("%.1f");
|
||||
}
|
||||
|
||||
// nice numbers can be expressed as form of 1*10^n, 2* 10^n or 5*10^n
|
||||
@@ -560,14 +610,14 @@ qreal ChartView::niceNumber(qreal x, bool ceiling) {
|
||||
}
|
||||
|
||||
void ChartView::leaveEvent(QEvent *event) {
|
||||
track_pt = {0, 0};
|
||||
track_pts.clear();
|
||||
scene()->update();
|
||||
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()) {
|
||||
!manage_btn_proxy->geometry().contains(event->pos()) && !close_btn_proxy->geometry().contains(event->pos())) {
|
||||
QMimeData *mimeData = new QMimeData;
|
||||
mimeData->setData(mime_type, QByteArray::number((qulonglong)this));
|
||||
QDrag *drag = new QDrag(this);
|
||||
@@ -588,14 +638,21 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
|
||||
if (event->button() == Qt::LeftButton && rubber && rubber->isVisible()) {
|
||||
rubber->hide();
|
||||
QRectF rect = rubber->geometry().normalized();
|
||||
double min = std::floor(chart()->mapToValue(rect.topLeft()).x() * 10.0) / 10.0;
|
||||
double max = std::floor(chart()->mapToValue(rect.bottomRight()).x() * 10.0) / 10.0;
|
||||
double min = chart()->mapToValue(rect.topLeft()).x();
|
||||
double max = chart()->mapToValue(rect.bottomRight()).x();
|
||||
|
||||
// Prevent zooming/seeking past the end of the route
|
||||
min = std::clamp(min, can->routeStartTime(), can->routeStartTime() + can->totalSeconds());
|
||||
max = std::clamp(max, can->routeStartTime(), can->routeStartTime() + can->totalSeconds());
|
||||
|
||||
double min_rounded = std::floor(min * 10.0) / 10.0;
|
||||
double max_rounded = std::floor(max * 10.0) / 10.0;
|
||||
if (rubber->width() <= 0) {
|
||||
// no rubber dragged, seek to mouse position
|
||||
can->seekTo(min);
|
||||
} else if ((max - min) >= 0.5) {
|
||||
} else if ((max_rounded - min_rounded) >= 0.5) {
|
||||
// zoom in if selected range is greater than 0.5s
|
||||
emit zoomIn(min, max);
|
||||
emit zoomIn(min_rounded, max_rounded);
|
||||
}
|
||||
event->accept();
|
||||
} else if (!can->liveStreaming() && event->button() == Qt::RightButton) {
|
||||
@@ -610,32 +667,40 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
|
||||
auto rubber = findChild<QRubberBand *>();
|
||||
bool is_zooming = rubber && rubber->isVisible();
|
||||
const auto plot_area = chart()->plotArea();
|
||||
track_pt = {0, 0};
|
||||
track_pts.clear();
|
||||
if (!is_zooming && plot_area.contains(ev->pos())) {
|
||||
track_pts.resize(sigs.size());
|
||||
QStringList text_list;
|
||||
const double sec = chart()->mapToValue(ev->pos()).x();
|
||||
for (auto &s : sigs) {
|
||||
for (int i = 0; i < sigs.size(); ++i) {
|
||||
QString value = "--";
|
||||
// use reverse iterator to find last item <= sec.
|
||||
auto it = std::lower_bound(s.vals.rbegin(), s.vals.rend(), sec, [](auto &p, double x) { return p.x() > x; });
|
||||
if (it != s.vals.rend() && it->x() >= axis_x->min()) {
|
||||
auto it = std::lower_bound(sigs[i].vals.rbegin(), sigs[i].vals.rend(), sec, [](auto &p, double x) { return p.x() > x; });
|
||||
if (it != sigs[i].vals.rend() && it->x() >= axis_x->min()) {
|
||||
value = QString::number(it->y());
|
||||
auto value_pos = chart()->mapToPosition(*it);
|
||||
if (value_pos.x() > track_pt.x()) track_pt = value_pos;
|
||||
track_pts[i] = chart()->mapToPosition(*it);
|
||||
}
|
||||
text_list.push_back(QString(" %1 : %2 ").arg(sigs.size() > 1 ? s.sig->name.c_str() : "Value").arg(value));
|
||||
text_list.push_back(QString("<span style=\"color:%1;\">■ </span>%2: <b>%3</b>").arg(sigs[i].series->color().name(), sigs[i].sig->name.c_str(), value));
|
||||
}
|
||||
if (track_pt.x() == 0) track_pt = ev->pos();
|
||||
QString text = QString("<div style=\"background-color: darkGray;color: white;\"> Time: %1 <br />%2</div>")
|
||||
.arg(chart()->mapToValue(track_pt).x(), 0, 'f', 3)
|
||||
.arg(text_list.join("<br />"));
|
||||
QPoint pt((int)track_pt.x() + 20, plot_area.top() - 20);
|
||||
QToolTip::showText(mapToGlobal(pt), text, this, plot_area.toRect());
|
||||
auto max = std::max_element(track_pts.begin(), track_pts.end(), [](auto &a, auto &b) { return a.x() < b.x(); });
|
||||
auto pt = (max == track_pts.end()) ? ev->pos() : *max;
|
||||
text_list.push_front(QString::number(chart()->mapToValue(pt).x(), 'f', 3));
|
||||
QPointF tooltip_pt(pt.x() + 12, plot_area.top() - 20);
|
||||
QToolTip::showText(mapToGlobal(tooltip_pt.toPoint()), pt.isNull() ? "" : text_list.join("<br />"), this, plot_area.toRect());
|
||||
scene()->update();
|
||||
} else {
|
||||
QToolTip::hideText();
|
||||
}
|
||||
|
||||
QChartView::mouseMoveEvent(ev);
|
||||
if (is_zooming) {
|
||||
QRect rubber_rect = rubber->geometry();
|
||||
rubber_rect.setLeft(std::max(rubber_rect.left(), (int)plot_area.left()));
|
||||
rubber_rect.setRight(std::min(rubber_rect.right(), (int)plot_area.right()));
|
||||
if (rubber_rect != rubber->geometry()) {
|
||||
rubber->setGeometry(rubber_rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::dragMoveEvent(QDragMoveEvent *event) {
|
||||
@@ -674,11 +739,56 @@ void ChartView::drawForeground(QPainter *painter, const QRectF &rect) {
|
||||
qreal y2 = chart()->plotArea().bottom() + 2;
|
||||
painter->setPen(QPen(chart()->titleBrush().color(), 2));
|
||||
painter->drawLine(QPointF{x, y1}, QPointF{x, y2});
|
||||
if (!track_pt.isNull()) {
|
||||
|
||||
auto max = std::max_element(track_pts.begin(), track_pts.end(), [](auto &a, auto &b) { return a.x() < b.x(); });
|
||||
if (max != track_pts.end() && !max->isNull()) {
|
||||
painter->setPen(QPen(Qt::darkGray, 1, Qt::DashLine));
|
||||
painter->drawLine(QPointF{track_pt.x(), y1}, QPointF{track_pt.x(), y2});
|
||||
painter->setBrush(Qt::darkGray);
|
||||
painter->drawEllipse(track_pt, 5, 5);
|
||||
painter->drawLine(QPointF{max->x(), y1}, QPointF{max->x(), y2});
|
||||
painter->setPen(Qt::NoPen);
|
||||
for (int i = 0; i < track_pts.size(); ++i) {
|
||||
if (!track_pts[i].isNull() && i < sigs.size()) {
|
||||
painter->setBrush(sigs[i].series->color().darker(125));
|
||||
painter->drawEllipse(track_pts[i], 5.5, 5.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QXYSeries *ChartView::createSeries(QAbstractSeries::SeriesType type) {
|
||||
QXYSeries *series = nullptr;
|
||||
if (type == QAbstractSeries::SeriesTypeLine) {
|
||||
series = new QLineSeries(this);
|
||||
} else {
|
||||
series = new QScatterSeries(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
|
||||
return series;
|
||||
}
|
||||
|
||||
void ChartView::setSeriesType(QAbstractSeries::SeriesType type) {
|
||||
if (type != series_type) {
|
||||
series_type = type;
|
||||
line_series_action->setChecked(type == QAbstractSeries::SeriesTypeLine);
|
||||
scatter_series_action->setChecked(type == QAbstractSeries::SeriesTypeScatter);
|
||||
|
||||
for (auto &s : sigs) {
|
||||
chart()->removeSeries(s.series);
|
||||
s.series->deleteLater();
|
||||
}
|
||||
for (auto &s : sigs) {
|
||||
auto series = createSeries(series_type);
|
||||
chart()->addSeries(series);
|
||||
series->attachAxis(axis_x);
|
||||
series->attachAxis(axis_y);
|
||||
series->replace(s.vals);
|
||||
s.series = series;
|
||||
}
|
||||
updateSeriesPoints();
|
||||
updateTitle();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <QTimer>
|
||||
#include <QtCharts/QChartView>
|
||||
#include <QtCharts/QLineSeries>
|
||||
#include <QtCharts/QScatterSeries>
|
||||
#include <QtCharts/QValueAxis>
|
||||
|
||||
#include "tools/cabana/dbcmanager.h"
|
||||
@@ -30,13 +31,14 @@ public:
|
||||
void updatePlot(double cur, double min, double max);
|
||||
void setPlotAreaLeftPosition(int pos);
|
||||
qreal getYAsixLabelWidth() const;
|
||||
void setSeriesType(QAbstractSeries::SeriesType type);
|
||||
|
||||
struct SigItem {
|
||||
QString msg_id;
|
||||
uint8_t source = 0;
|
||||
uint32_t address = 0;
|
||||
const Signal *sig = nullptr;
|
||||
QLineSeries *series = nullptr;
|
||||
QXYSeries *series = nullptr;
|
||||
QVector<QPointF> vals;
|
||||
uint64_t last_value_mono_time = 0;
|
||||
};
|
||||
@@ -57,7 +59,7 @@ private slots:
|
||||
void manageSeries();
|
||||
|
||||
private:
|
||||
QList<ChartView::SigItem>::iterator removeSeries(const QList<ChartView::SigItem>::iterator &it);
|
||||
QList<ChartView::SigItem>::iterator removeItem(const QList<ChartView::SigItem>::iterator &it);
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *ev) override;
|
||||
@@ -70,15 +72,20 @@ private:
|
||||
void drawForeground(QPainter *painter, const QRectF &rect) override;
|
||||
void applyNiceNumbers(qreal min, qreal max);
|
||||
qreal niceNumber(qreal x, bool ceiling);
|
||||
QXYSeries *createSeries(QAbstractSeries::SeriesType type);
|
||||
void updateSeriesPoints();
|
||||
|
||||
QValueAxis *axis_x;
|
||||
QValueAxis *axis_y;
|
||||
QPointF track_pt;
|
||||
QVector<QPointF> track_pts;
|
||||
QGraphicsProxyWidget *close_btn_proxy;
|
||||
QGraphicsProxyWidget *manage_btn_proxy;
|
||||
QList<SigItem> sigs;
|
||||
double cur_sec = 0;
|
||||
const QString mime_type = "application/x-cabanachartview";
|
||||
QAbstractSeries::SeriesType series_type = QAbstractSeries::SeriesTypeLine;
|
||||
QAction *line_series_action;
|
||||
QAction *scatter_series_action;
|
||||
};
|
||||
|
||||
class ChartsWidget : public QWidget {
|
||||
@@ -91,6 +98,7 @@ public:
|
||||
|
||||
public slots:
|
||||
void setColumnCount(int n);
|
||||
void removeAll();
|
||||
|
||||
signals:
|
||||
void dock(bool floating);
|
||||
@@ -108,7 +116,6 @@ private:
|
||||
void zoomIn(double min, double max);
|
||||
void zoomReset();
|
||||
void updateToolBar();
|
||||
void removeAll();
|
||||
void setMaxChartRange(int value);
|
||||
void updateLayout();
|
||||
void settingChanged();
|
||||
@@ -116,7 +123,6 @@ private:
|
||||
ChartView *findChart(const QString &id, const Signal *sig);
|
||||
|
||||
QLabel *title_label;
|
||||
QLabel *zoom_range_lb;
|
||||
QLabel *range_lb;
|
||||
QSlider *range_slider;
|
||||
bool docking = true;
|
||||
@@ -131,6 +137,8 @@ private:
|
||||
std::pair<double, double> display_range;
|
||||
std::pair<double, double> zoomed_range;
|
||||
bool use_dark_theme = false;
|
||||
QAction *columns_lb_action;
|
||||
QAction *columns_cb_action;
|
||||
QComboBox *columns_cb;
|
||||
int column_count = 1;
|
||||
const int CHART_MIN_WIDTH = 300;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include <QApplication>
|
||||
|
||||
#include "tools/cabana/commands.h"
|
||||
|
||||
// EditMsgCommand
|
||||
@@ -73,3 +75,12 @@ EditSignalCommand::EditSignalCommand(const QString &id, const Signal *sig, const
|
||||
|
||||
void EditSignalCommand::undo() { dbc()->updateSignal(id, new_signal.name.c_str(), old_signal); }
|
||||
void EditSignalCommand::redo() { dbc()->updateSignal(id, old_signal.name.c_str(), new_signal); }
|
||||
|
||||
namespace UndoStack {
|
||||
|
||||
QUndoStack *instance() {
|
||||
static QUndoStack *undo_stack = new QUndoStack(qApp);
|
||||
return undo_stack;
|
||||
}
|
||||
|
||||
} // namespace UndoStack
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <QUndoCommand>
|
||||
#include <QUndoStack>
|
||||
|
||||
#include "tools/cabana/dbcmanager.h"
|
||||
|
||||
@@ -60,3 +61,8 @@ private:
|
||||
Signal old_signal = {};
|
||||
Signal new_signal = {};
|
||||
};
|
||||
|
||||
namespace UndoStack {
|
||||
QUndoStack *instance();
|
||||
inline void push(QUndoCommand *cmd) { instance()->push(cmd); }
|
||||
};
|
||||
|
||||
@@ -68,7 +68,7 @@ void DBCManager::addSignal(const QString &id, const Signal &sig) {
|
||||
if (auto m = const_cast<DBCMsg *>(msg(id))) {
|
||||
auto &s = m->sigs[sig.name.c_str()];
|
||||
s = sig;
|
||||
emit signalAdded(&s);
|
||||
emit signalAdded(parseId(id).second, &s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ DBCManager *dbc() {
|
||||
|
||||
std::vector<const Signal*> DBCMsg::getSignals() const {
|
||||
std::vector<const Signal*> ret;
|
||||
ret.reserve(sigs.size());
|
||||
for (auto &[_, sig] : sigs) ret.push_back(&sig);
|
||||
std::sort(ret.begin(), ret.end(), [](auto l, auto r) { return l->start_bit < r->start_bit; });
|
||||
return ret;
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
struct DBCMsg {
|
||||
QString name;
|
||||
uint32_t size;
|
||||
// signal must be saved as value in map to make undo stack work properly.
|
||||
std::map<QString, Signal> sigs;
|
||||
// return vector<signals>, sort by start_bits
|
||||
std::vector<const Signal*> getSignals() const;
|
||||
};
|
||||
|
||||
@@ -39,7 +41,7 @@ public:
|
||||
}
|
||||
|
||||
signals:
|
||||
void signalAdded(const Signal *sig);
|
||||
void signalAdded(uint32_t address, const Signal *sig);
|
||||
void signalRemoved(const Signal *sig);
|
||||
void signalUpdated(const Signal *sig);
|
||||
void msgUpdated(uint32_t address);
|
||||
|
||||
+50
-143
@@ -4,10 +4,8 @@
|
||||
#include <QFormLayout>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QScrollBar>
|
||||
#include <QTimer>
|
||||
#include <QToolButton>
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "tools/cabana/commands.h"
|
||||
#include "tools/cabana/dbcmanager.h"
|
||||
#include "tools/cabana/streams/abstractstream.h"
|
||||
@@ -15,7 +13,6 @@
|
||||
// DetailWidget
|
||||
|
||||
DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(charts), QWidget(parent) {
|
||||
undo_stack = new QUndoStack(this);
|
||||
QWidget *main_widget = new QWidget(this);
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(main_widget);
|
||||
main_layout->setContentsMargins(0, 0, 0, 0);
|
||||
@@ -29,7 +26,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
|
||||
main_layout->addWidget(tabbar);
|
||||
|
||||
// message title
|
||||
toolbar = new QToolBar(this);
|
||||
QToolBar *toolbar = new QToolBar(this);
|
||||
toolbar->setIconSize({16, 16});
|
||||
toolbar->addWidget(new QLabel("time:"));
|
||||
time_label = new QLabel(this);
|
||||
@@ -40,8 +37,8 @@ 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(bootstrapPixmap("pencil"), "", this, &DetailWidget::editMsg)->setToolTip(tr("Edit Message"));
|
||||
remove_msg_act = toolbar->addAction(bootstrapPixmap("x-lg"), "", this, &DetailWidget::removeMsg);
|
||||
toolbar->addAction(utils::icon("pencil"), "", this, &DetailWidget::editMsg)->setToolTip(tr("Edit Message"));
|
||||
remove_msg_act = toolbar->addAction(utils::icon("x-lg"), "", this, &DetailWidget::removeMsg);
|
||||
remove_msg_act->setToolTip(tr("Remove Message"));
|
||||
main_layout->addWidget(toolbar);
|
||||
|
||||
@@ -54,41 +51,35 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
|
||||
main_layout->addWidget(warning_widget);
|
||||
|
||||
// msg widget
|
||||
QWidget *msg_widget = new QWidget(this);
|
||||
QVBoxLayout *msg_layout = new QVBoxLayout(msg_widget);
|
||||
msg_layout->setContentsMargins(0, 0, 0, 0);
|
||||
// binary view
|
||||
binary_view = new BinaryView(this);
|
||||
msg_layout->addWidget(binary_view);
|
||||
// signals
|
||||
signals_layout = new QVBoxLayout();
|
||||
signals_layout->setSpacing(0);
|
||||
msg_layout->addLayout(signals_layout);
|
||||
msg_layout->addStretch(0);
|
||||
|
||||
scroll = new QScrollArea(this);
|
||||
scroll->setFrameShape(QFrame::NoFrame);
|
||||
scroll->setWidget(msg_widget);
|
||||
scroll->setWidgetResizable(true);
|
||||
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
splitter = new QSplitter(Qt::Vertical, this);
|
||||
splitter->setAutoFillBackground(true);
|
||||
splitter->addWidget(binary_view = new BinaryView(this));
|
||||
splitter->addWidget(signal_view = new SignalView(charts, this));
|
||||
binary_view->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
|
||||
signal_view->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||
splitter->setStretchFactor(0, 0);
|
||||
splitter->setStretchFactor(1, 1);
|
||||
|
||||
tab_widget = new QTabWidget(this);
|
||||
tab_widget->setTabPosition(QTabWidget::South);
|
||||
tab_widget->addTab(scroll, bootstrapPixmap("file-earmark-ruled"), "&Msg");
|
||||
history_log = new LogsWidget(this);
|
||||
tab_widget->addTab(history_log, bootstrapPixmap("stopwatch"), "&Logs");
|
||||
tab_widget->addTab(splitter, utils::icon("file-earmark-ruled"), "&Msg");
|
||||
tab_widget->addTab(history_log = new LogsWidget(this), utils::icon("stopwatch"), "&Logs");
|
||||
main_layout->addWidget(tab_widget);
|
||||
|
||||
stacked_layout = new QStackedLayout(this);
|
||||
stacked_layout->addWidget(new WelcomeWidget(this));
|
||||
stacked_layout->addWidget(main_widget);
|
||||
|
||||
QObject::connect(binary_view, &BinaryView::signalClicked, this, &DetailWidget::showForm);
|
||||
QObject::connect(binary_view, &BinaryView::resizeSignal, this, &DetailWidget::resizeSignal);
|
||||
QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal);
|
||||
QObject::connect(binary_view, &BinaryView::resizeSignal, signal_view->model, &SignalModel::resizeSignal);
|
||||
QObject::connect(binary_view, &BinaryView::addSignal, signal_view->model, &SignalModel::addSignal);
|
||||
QObject::connect(binary_view, &BinaryView::signalHovered, signal_view, &SignalView::signalHovered);
|
||||
QObject::connect(binary_view, &BinaryView::signalClicked, signal_view, &SignalView::expandSignal);
|
||||
QObject::connect(signal_view, &SignalView::showChart, charts, &ChartsWidget::showChart);
|
||||
QObject::connect(signal_view, &SignalView::highlight, binary_view, &BinaryView::highlight);
|
||||
QObject::connect(tab_widget, &QTabWidget::currentChanged, [this]() { updateState(); });
|
||||
QObject::connect(can, &AbstractStream::msgsReceived, this, &DetailWidget::updateState);
|
||||
QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { dbcMsgChanged(); });
|
||||
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &DetailWidget::refresh);
|
||||
QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &DetailWidget::refresh);
|
||||
QObject::connect(tabbar, &QTabBar::customContextMenuRequested, this, &DetailWidget::showTabBarContextMenu);
|
||||
QObject::connect(tabbar, &QTabBar::currentChanged, [this](int index) {
|
||||
if (index != -1 && tabbar->tabText(index) != msg_id) {
|
||||
@@ -96,14 +87,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart
|
||||
}
|
||||
});
|
||||
QObject::connect(tabbar, &QTabBar::tabCloseRequested, tabbar, &QTabBar::removeTab);
|
||||
QObject::connect(charts, &ChartsWidget::seriesChanged, this, &DetailWidget::updateChartState);
|
||||
QObject::connect(history_log, &LogsWidget::openChart, [this](const QString &id, const Signal *sig) {
|
||||
this->charts->showChart(id, sig, true, false);
|
||||
});
|
||||
QObject::connect(undo_stack, &QUndoStack::indexChanged, [this]() {
|
||||
if (undo_stack->count() > 0)
|
||||
dbcMsgChanged();
|
||||
});
|
||||
QObject::connect(charts, &ChartsWidget::seriesChanged, signal_view, &SignalView::updateChartState);
|
||||
}
|
||||
|
||||
void DetailWidget::showTabBarContextMenu(const QPoint &pt) {
|
||||
@@ -120,6 +104,16 @@ void DetailWidget::showTabBarContextMenu(const QPoint &pt) {
|
||||
}
|
||||
}
|
||||
|
||||
void DetailWidget::removeAll() {
|
||||
msg_id = "";
|
||||
tabbar->blockSignals(true);
|
||||
while (tabbar->count() > 0) {
|
||||
tabbar->removeTab(0);
|
||||
}
|
||||
tabbar->blockSignals(false);
|
||||
stacked_layout->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
void DetailWidget::setMessage(const QString &message_id) {
|
||||
msg_id = message_id;
|
||||
int index = tabbar->count() - 1;
|
||||
@@ -128,41 +122,27 @@ void DetailWidget::setMessage(const QString &message_id) {
|
||||
index = tabbar->addTab(message_id);
|
||||
tabbar->setTabToolTip(index, msgName(message_id));
|
||||
}
|
||||
tabbar->setCurrentIndex(index);
|
||||
dbcMsgChanged();
|
||||
scroll->verticalScrollBar()->setValue(0);
|
||||
stacked_layout->setCurrentIndex(1);
|
||||
}
|
||||
|
||||
void DetailWidget::dbcMsgChanged(int show_form_idx) {
|
||||
if (msg_id.isEmpty()) return;
|
||||
|
||||
setUpdatesEnabled(false);
|
||||
|
||||
signal_view->setMessage(msg_id);
|
||||
binary_view->setMessage(msg_id);
|
||||
history_log->setMessage(msg_id);
|
||||
|
||||
int i = 0;
|
||||
stacked_layout->setCurrentIndex(1);
|
||||
tabbar->setCurrentIndex(index);
|
||||
refresh();
|
||||
splitter->setSizes({1, 2});
|
||||
|
||||
setUpdatesEnabled(true);
|
||||
}
|
||||
|
||||
void DetailWidget::refresh() {
|
||||
if (msg_id.isEmpty()) return;
|
||||
|
||||
QStringList warnings;
|
||||
const DBCMsg *msg = dbc()->msg(msg_id);
|
||||
if (msg) {
|
||||
for (auto sig : msg->getSignals()) {
|
||||
SignalEdit *form = i < signal_list.size() ? signal_list[i] : nullptr;
|
||||
if (!form) {
|
||||
form = new SignalEdit(i, this);
|
||||
QObject::connect(form, &SignalEdit::remove, this, &DetailWidget::removeSignal);
|
||||
QObject::connect(form, &SignalEdit::save, this, &DetailWidget::saveSignal);
|
||||
QObject::connect(form, &SignalEdit::showFormClicked, this, &DetailWidget::showForm);
|
||||
QObject::connect(form, &SignalEdit::highlight, binary_view, &BinaryView::highlight);
|
||||
QObject::connect(binary_view, &BinaryView::signalHovered, form, &SignalEdit::signalHovered);
|
||||
QObject::connect(form, &SignalEdit::showChart, charts, &ChartsWidget::showChart);
|
||||
signals_layout->addWidget(form);
|
||||
signal_list.push_back(form);
|
||||
}
|
||||
form->setSignal(msg_id, sig);
|
||||
form->setChartOpened(charts->hasSignal(msg_id, sig));
|
||||
++i;
|
||||
}
|
||||
if (msg->size != can->lastMessage(msg_id).dat.size()) {
|
||||
warnings.push_back(tr("Message size (%1) is incorrect.").arg(msg->size));
|
||||
}
|
||||
@@ -172,22 +152,17 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) {
|
||||
} else {
|
||||
warnings.push_back(tr("Drag-Select in binary view to create new signal."));
|
||||
}
|
||||
for (/**/; i < signal_list.size(); ++i)
|
||||
signal_list[i]->hide();
|
||||
|
||||
toolbar->setVisible(!msg_id.isEmpty());
|
||||
remove_msg_act->setEnabled(msg != nullptr);
|
||||
name_label->setText(msgName(msg_id));
|
||||
|
||||
if (!warnings.isEmpty()) {
|
||||
warning_label->setText(warnings.join('\n'));
|
||||
warning_icon->setPixmap(bootstrapPixmap(msg ? "exclamation-triangle" : "info-circle"));
|
||||
warning_icon->setPixmap(utils::icon(msg ? "exclamation-triangle" : "info-circle"));
|
||||
}
|
||||
warning_widget->setVisible(!warnings.isEmpty());
|
||||
setUpdatesEnabled(true);
|
||||
}
|
||||
|
||||
void DetailWidget::updateState(const QHash<QString, CanData> * msgs) {
|
||||
void DetailWidget::updateState(const QHash<QString, CanData> *msgs) {
|
||||
time_label->setText(QString::number(can->currentSec(), 'f', 3));
|
||||
if (msg_id.isEmpty() || (msgs && !msgs->contains(msg_id)))
|
||||
return;
|
||||
@@ -198,86 +173,18 @@ void DetailWidget::updateState(const QHash<QString, CanData> * msgs) {
|
||||
history_log->updateState();
|
||||
}
|
||||
|
||||
void DetailWidget::showForm(const Signal *sig) {
|
||||
setUpdatesEnabled(false);
|
||||
for (auto f : signal_list) {
|
||||
f->updateForm(f->sig == sig && !f->form->isVisible());
|
||||
if (f->sig == sig && f->form->isVisible()) {
|
||||
QTimer::singleShot(0, [=]() { scroll->ensureWidgetVisible(f); });
|
||||
}
|
||||
}
|
||||
setUpdatesEnabled(true);
|
||||
}
|
||||
|
||||
void DetailWidget::updateChartState() {
|
||||
for (auto f : signal_list)
|
||||
f->setChartOpened(charts->hasSignal(f->msg_id, f->sig));
|
||||
}
|
||||
|
||||
void DetailWidget::editMsg() {
|
||||
QString id = msg_id;
|
||||
auto msg = dbc()->msg(id);
|
||||
int size = msg ? msg->size : can->lastMessage(id).dat.size();
|
||||
EditMessageDialog dlg(id, msgName(id), size, this);
|
||||
if (dlg.exec()) {
|
||||
undo_stack->push(new EditMsgCommand(msg_id, dlg.name_edit->text(), dlg.size_spin->value()));
|
||||
UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text(), dlg.size_spin->value()));
|
||||
}
|
||||
}
|
||||
|
||||
void DetailWidget::removeMsg() {
|
||||
undo_stack->push(new RemoveMsgCommand(msg_id));
|
||||
}
|
||||
|
||||
void DetailWidget::addSignal(int start_bit, int size, bool little_endian) {
|
||||
auto msg = dbc()->msg(msg_id);
|
||||
if (!msg) {
|
||||
for (int i = 1; /**/; ++i) {
|
||||
QString name = QString("NEW_MSG_%1").arg(i);
|
||||
auto it = std::find_if(dbc()->messages().begin(), dbc()->messages().end(), [&](auto &m) { return m.second.name == name; });
|
||||
if (it == dbc()->messages().end()) {
|
||||
undo_stack->push(new EditMsgCommand(msg_id, name, can->lastMessage(msg_id).dat.size()));
|
||||
msg = dbc()->msg(msg_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Signal sig = {.is_little_endian = little_endian, .factor = 1};
|
||||
for (int i = 1; /**/; ++i) {
|
||||
sig.name = "NEW_SIGNAL_" + std::to_string(i);
|
||||
if (msg->sigs.count(sig.name.c_str()) == 0) break;
|
||||
}
|
||||
updateSigSizeParamsFromRange(sig, start_bit, size);
|
||||
undo_stack->push(new AddSigCommand(msg_id, sig));
|
||||
}
|
||||
|
||||
void DetailWidget::resizeSignal(const Signal *sig, int start_bit, int size) {
|
||||
Signal s = *sig;
|
||||
updateSigSizeParamsFromRange(s, start_bit, size);
|
||||
saveSignal(sig, s);
|
||||
}
|
||||
|
||||
void DetailWidget::saveSignal(const Signal *sig, const Signal &new_sig) {
|
||||
auto msg = dbc()->msg(msg_id);
|
||||
if (new_sig.name != sig->name) {
|
||||
auto it = msg->sigs.find(new_sig.name.c_str());
|
||||
if (it != msg->sigs.end()) {
|
||||
QString warning_str = tr("There is already a signal with the same name '%1'").arg(new_sig.name.c_str());
|
||||
QMessageBox::warning(this, tr("Failed to save signal"), warning_str);
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto [start, end] = getSignalRange(&new_sig);
|
||||
if (start < 0 || end >= msg->size * 8) {
|
||||
QString warning_str = tr("Signal size [%1] exceed limit").arg(new_sig.size);
|
||||
QMessageBox::warning(this, tr("Failed to save signal"), warning_str);
|
||||
return;
|
||||
}
|
||||
|
||||
undo_stack->push(new EditSignalCommand(msg_id, sig, new_sig));
|
||||
}
|
||||
|
||||
void DetailWidget::removeSignal(const Signal *sig) {
|
||||
undo_stack->push(new RemoveSigCommand(msg_id, sig));
|
||||
UndoStack::push(new RemoveMsgCommand(msg_id));
|
||||
}
|
||||
|
||||
// EditMessageDialog
|
||||
@@ -288,7 +195,7 @@ EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title
|
||||
form_layout->addRow("ID", new QLabel(msg_id));
|
||||
|
||||
name_edit = new QLineEdit(title, this);
|
||||
name_edit->setValidator(new QRegExpValidator(QRegExp("^(\\w+)"), name_edit));
|
||||
name_edit->setValidator(new NameValidator(name_edit));
|
||||
form_layout->addRow(tr("Name"), name_edit);
|
||||
|
||||
size_spin = new QSpinBox(this);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <QSplitter>
|
||||
#include <QStackedLayout>
|
||||
#include <QScrollArea>
|
||||
#include <QTabWidget>
|
||||
#include <QToolBar>
|
||||
#include <QUndoStack>
|
||||
|
||||
#include "tools/cabana/binaryview.h"
|
||||
#include "tools/cabana/chartswidget.h"
|
||||
@@ -30,18 +29,12 @@ class DetailWidget : public QWidget {
|
||||
public:
|
||||
DetailWidget(ChartsWidget *charts, QWidget *parent);
|
||||
void setMessage(const QString &message_id);
|
||||
void dbcMsgChanged(int show_form_idx = -1);
|
||||
void refresh();
|
||||
void removeAll();
|
||||
QSize minimumSizeHint() const override { return binary_view->minimumSizeHint(); }
|
||||
QUndoStack *undo_stack = nullptr;
|
||||
|
||||
private:
|
||||
void showForm(const Signal *sig);
|
||||
void updateChartState();
|
||||
void showTabBarContextMenu(const QPoint &pt);
|
||||
void addSignal(int start_bit, int size, bool little_endian);
|
||||
void resizeSignal(const Signal *sig, int from, int to);
|
||||
void saveSignal(const Signal *sig, const Signal &new_sig);
|
||||
void removeSignal(const Signal *sig);
|
||||
void editMsg();
|
||||
void removeMsg();
|
||||
void updateState(const QHash<QString, CanData> * msgs = nullptr);
|
||||
@@ -49,15 +42,13 @@ private:
|
||||
QString msg_id;
|
||||
QLabel *name_label, *time_label, *warning_icon, *warning_label;
|
||||
QWidget *warning_widget;
|
||||
QVBoxLayout *signals_layout;
|
||||
QTabBar *tabbar;
|
||||
QTabWidget *tab_widget;
|
||||
QToolBar *toolbar;
|
||||
QAction *remove_msg_act;
|
||||
LogsWidget *history_log;
|
||||
BinaryView *binary_view;
|
||||
QScrollArea *scroll;
|
||||
SignalView *signal_view;
|
||||
ChartsWidget *charts;
|
||||
QSplitter *splitter;
|
||||
QStackedLayout *stacked_layout;
|
||||
QList<SignalEdit *> signal_list;
|
||||
};
|
||||
|
||||
+42
-37
@@ -4,6 +4,8 @@
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "tools/cabana/commands.h"
|
||||
|
||||
// HistoryLogModel
|
||||
|
||||
QVariant HistoryLogModel::data(const QModelIndex &index, int role) const {
|
||||
@@ -15,24 +17,23 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const {
|
||||
}
|
||||
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 ChangeTracker::toVariantList(m.colors);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void HistoryLogModel::setMessage(const QString &message_id) {
|
||||
msg_id = message_id;
|
||||
sigs.clear();
|
||||
if (auto dbc_msg = dbc()->msg(msg_id)) {
|
||||
sigs = dbc_msg->getSignals();
|
||||
}
|
||||
filter_cmp = nullptr;
|
||||
refresh();
|
||||
}
|
||||
|
||||
void HistoryLogModel::refresh() {
|
||||
beginResetModel();
|
||||
sigs.clear();
|
||||
if (auto dbc_msg = dbc()->msg(msg_id)) {
|
||||
sigs = dbc_msg->getSignals();
|
||||
}
|
||||
last_fetch_time = 0;
|
||||
has_more_data = true;
|
||||
messages.clear();
|
||||
hex_colors.clear();
|
||||
updateState();
|
||||
@@ -74,17 +75,15 @@ void HistoryLogModel::setFilter(int sig_idx, const QString &value, std::function
|
||||
filter_sig_idx = sig_idx;
|
||||
filter_value = value.toDouble();
|
||||
filter_cmp = value.isEmpty() ? nullptr : cmp;
|
||||
refresh();
|
||||
}
|
||||
|
||||
void HistoryLogModel::updateState() {
|
||||
if (!msg_id.isEmpty()) {
|
||||
uint64_t current_time = (can->currentSec() + can->routeStartTime()) * 1e9;
|
||||
uint64_t current_time = (can->lastMessage(msg_id).ts + can->routeStartTime()) * 1e9 + 1;
|
||||
auto new_msgs = dynamic_mode ? fetchData(current_time, last_fetch_time) : fetchData(0);
|
||||
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;
|
||||
@@ -97,29 +96,11 @@ 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;
|
||||
@@ -128,7 +109,7 @@ std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(InputIt first, I
|
||||
for (auto it = first; it != last && (*it)->mono_time > min_time; ++it) {
|
||||
if ((*it)->which == cereal::Event::Which::CAN) {
|
||||
for (const auto &c : (*it)->event.getCan()) {
|
||||
if (src == c.getSrc() && address == c.getAddress()) {
|
||||
if (address == c.getAddress() && src == c.getSrc()) {
|
||||
const auto dat = c.getDat();
|
||||
for (int i = 0; i < sigs.size(); ++i) {
|
||||
values[i] = get_raw_value((uint8_t *)dat.begin(), dat.size(), *(sigs[i]));
|
||||
@@ -153,17 +134,30 @@ template std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData<>(std::
|
||||
|
||||
std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(uint64_t from_time, uint64_t min_time) {
|
||||
auto events = can->events();
|
||||
const auto freq = can->lastMessage(msg_id).freq;
|
||||
const bool update_colors = !display_signals_mode || sigs.empty();
|
||||
|
||||
if (dynamic_mode) {
|
||||
auto it = std::upper_bound(events->rbegin(), events->rend(), from_time, [=](uint64_t ts, auto &e) {
|
||||
return e->mono_time < ts;
|
||||
});
|
||||
return fetchData(it, events->rend(), min_time);
|
||||
auto first = std::upper_bound(events->rbegin(), events->rend(), from_time, [=](uint64_t ts, auto &e) { return e->mono_time < ts; });
|
||||
auto msgs = fetchData(first, events->rend(), min_time);
|
||||
if (update_colors && (min_time > 0 || messages.empty())) {
|
||||
for (auto it = msgs.rbegin(); it != msgs.rend(); ++it) {
|
||||
hex_colors.compute(it->data, it->mono_time / (double)1e9, freq);
|
||||
it->colors = hex_colors.colors;
|
||||
}
|
||||
}
|
||||
return msgs;
|
||||
} else {
|
||||
assert(min_time == 0);
|
||||
auto it = std::upper_bound(events->begin(), events->end(), from_time, [=](uint64_t ts, auto &e) {
|
||||
return ts < e->mono_time;
|
||||
});
|
||||
return fetchData(it, events->end(), 0);
|
||||
auto first = std::upper_bound(events->begin(), events->end(), from_time, [=](uint64_t ts, auto &e) { return ts < e->mono_time; });
|
||||
auto msgs = fetchData(first, events->end(), 0);
|
||||
if (update_colors) {
|
||||
for (auto it = msgs.rbegin(); it != msgs.rend(); ++it) {
|
||||
hex_colors.compute(it->data, it->mono_time / (double)1e9, freq);
|
||||
it->colors = hex_colors.colors;
|
||||
}
|
||||
}
|
||||
return msgs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,11 +234,21 @@ LogsWidget::LogsWidget(QWidget *parent) : QWidget(parent) {
|
||||
QObject::connect(comp_box, SIGNAL(activated(int)), this, SLOT(setFilter()));
|
||||
QObject::connect(value_edit, &QLineEdit::textChanged, this, &LogsWidget::setFilter);
|
||||
QObject::connect(can, &AbstractStream::seekedTo, model, &HistoryLogModel::refresh);
|
||||
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &LogsWidget::refresh);
|
||||
QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &LogsWidget::refresh);
|
||||
QObject::connect(can, &AbstractStream::eventsMerged, model, &HistoryLogModel::segmentsMerged);
|
||||
}
|
||||
|
||||
void LogsWidget::setMessage(const QString &message_id) {
|
||||
model->setMessage(message_id);
|
||||
refresh();
|
||||
}
|
||||
|
||||
void LogsWidget::refresh() {
|
||||
if (model->msg_id.isEmpty()) return;
|
||||
|
||||
model->setFilter(0, "", nullptr);
|
||||
model->refresh();
|
||||
bool has_signal = model->sigs.size();
|
||||
if (has_signal) {
|
||||
signals_cb->clear();
|
||||
@@ -268,4 +272,5 @@ void LogsWidget::setFilter() {
|
||||
case 3: cmp = std::less<double>{}; break;
|
||||
}
|
||||
model->setFilter(signals_cb->currentIndex(), value_edit->text(), cmp);
|
||||
model->refresh();
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ public:
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override {
|
||||
return display_signals_mode && !sigs.empty() ? sigs.size() + 1 : 2;
|
||||
}
|
||||
void updateColors();
|
||||
void refresh();
|
||||
|
||||
public slots:
|
||||
@@ -54,7 +53,7 @@ public:
|
||||
std::deque<Message> fetchData(uint64_t from_time, uint64_t min_time = 0);
|
||||
|
||||
QString msg_id;
|
||||
HexColors hex_colors;
|
||||
ChangeTracker hex_colors;
|
||||
bool has_more_data = true;
|
||||
const int batch_size = 50;
|
||||
int filter_sig_idx = -1;
|
||||
@@ -76,13 +75,12 @@ public:
|
||||
void updateState() {if (dynamic_mode->isChecked()) model->updateState(); }
|
||||
void showEvent(QShowEvent *event) override { if (dynamic_mode->isChecked()) model->refresh(); }
|
||||
|
||||
signals:
|
||||
void openChart(const QString &msg_id, const Signal *sig);
|
||||
|
||||
private slots:
|
||||
void setFilter();
|
||||
|
||||
private:
|
||||
void refresh();
|
||||
|
||||
QTableView *logs;
|
||||
HistoryLogModel *model;
|
||||
QCheckBox *dynamic_mode;
|
||||
|
||||
+58
-39
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <QClipboard>
|
||||
#include <QCompleter>
|
||||
#include <QDesktopWidget>
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
@@ -15,6 +14,9 @@
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidgetAction>
|
||||
|
||||
#include "tools/cabana/commands.h"
|
||||
#include "tools/cabana/route.h"
|
||||
|
||||
static MainWindow *main_win = nullptr;
|
||||
void qLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
|
||||
if (type == QtDebugMsg) std::cout << msg.toStdString() << std::endl;
|
||||
@@ -54,21 +56,25 @@ MainWindow::MainWindow() : QMainWindow() {
|
||||
fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll());
|
||||
}
|
||||
|
||||
QObject::connect(dbc_combo, SIGNAL(activated(const QString &)), SLOT(loadDBCFromName(const QString &)));
|
||||
QObject::connect(this, &MainWindow::showMessage, statusBar(), &QStatusBar::showMessage);
|
||||
QObject::connect(this, &MainWindow::updateProgressBar, this, &MainWindow::updateDownloadProgress);
|
||||
QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, detail_widget, &DetailWidget::setMessage);
|
||||
QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts);
|
||||
QObject::connect(can, &AbstractStream::streamStarted, this, &MainWindow::loadDBCFromFingerprint);
|
||||
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MainWindow::DBCFileChanged);
|
||||
QObject::connect(detail_widget->undo_stack, &QUndoStack::cleanChanged, [this](bool clean) { setWindowModified(!clean); });
|
||||
QObject::connect(UndoStack::instance(), &QUndoStack::cleanChanged, this, &MainWindow::undoStackCleanChanged);
|
||||
}
|
||||
|
||||
void MainWindow::createActions() {
|
||||
QMenu *file_menu = menuBar()->addMenu(tr("&File"));
|
||||
if (!can->liveStreaming()) {
|
||||
file_menu->addAction(tr("Open Route..."), this, &MainWindow::openRoute);
|
||||
file_menu->addSeparator();
|
||||
}
|
||||
|
||||
file_menu->addAction(tr("New DBC File"), this, &MainWindow::newFile)->setShortcuts(QKeySequence::New);
|
||||
file_menu->addAction(tr("Open DBC File..."), this, &MainWindow::openFile)->setShortcuts(QKeySequence::Open);
|
||||
file_menu->addAction(tr("Load DBC From Clipboard"), this, &MainWindow::loadDBCFromClipboard);
|
||||
|
||||
open_recent_menu = file_menu->addMenu(tr("Open &Recent"));
|
||||
for (int i = 0; i < MAX_RECENT_FILES; ++i) {
|
||||
recent_files_acts[i] = new QAction(this);
|
||||
@@ -78,6 +84,17 @@ void MainWindow::createActions() {
|
||||
}
|
||||
updateRecentFileActions();
|
||||
|
||||
file_menu->addSeparator();
|
||||
QMenu *load_opendbc_menu = file_menu->addMenu(tr("Load DBC from commaai/opendbc"));
|
||||
// load_opendbc_menu->setStyleSheet("QMenu { menu-scrollable: true; }");
|
||||
auto dbc_names = dbc()->allDBCNames();
|
||||
std::sort(dbc_names.begin(), dbc_names.end());
|
||||
for (const auto &name : dbc_names) {
|
||||
load_opendbc_menu->addAction(QString::fromStdString(name), this, &MainWindow::openOpendbcFile);
|
||||
}
|
||||
|
||||
file_menu->addAction(tr("Load DBC From Clipboard"), this, &MainWindow::loadDBCFromClipboard);
|
||||
|
||||
file_menu->addSeparator();
|
||||
file_menu->addAction(tr("Save DBC..."), this, &MainWindow::save)->setShortcuts(QKeySequence::Save);
|
||||
file_menu->addAction(tr("Save DBC As..."), this, &MainWindow::saveAs)->setShortcuts(QKeySequence::SaveAs);
|
||||
@@ -89,16 +106,16 @@ void MainWindow::createActions() {
|
||||
file_menu->addAction(tr("E&xit"), qApp, &QApplication::closeAllWindows)->setShortcuts(QKeySequence::Quit);
|
||||
|
||||
QMenu *edit_menu = menuBar()->addMenu(tr("&Edit"));
|
||||
auto undo_act = detail_widget->undo_stack->createUndoAction(this, tr("&Undo"));
|
||||
auto undo_act = UndoStack::instance()->createUndoAction(this, tr("&Undo"));
|
||||
undo_act->setShortcuts(QKeySequence::Undo);
|
||||
edit_menu->addAction(undo_act);
|
||||
auto redo_act = detail_widget->undo_stack->createRedoAction(this, tr("&Rndo"));
|
||||
auto redo_act = UndoStack::instance()->createRedoAction(this, tr("&Rndo"));
|
||||
redo_act->setShortcuts(QKeySequence::Redo);
|
||||
edit_menu->addAction(redo_act);
|
||||
edit_menu->addSeparator();
|
||||
|
||||
QMenu *commands_menu = edit_menu->addMenu(tr("Command &List"));
|
||||
auto undo_view = new QUndoView(detail_widget->undo_stack);
|
||||
auto undo_view = new QUndoView(UndoStack::instance());
|
||||
undo_view->setWindowTitle(tr("Command List"));
|
||||
QWidgetAction *commands_act = new QWidgetAction(this);
|
||||
commands_act->setDefaultWidget(undo_view);
|
||||
@@ -113,18 +130,12 @@ void MainWindow::createActions() {
|
||||
|
||||
void MainWindow::createDockWindows() {
|
||||
// left panel
|
||||
QWidget *messages_container = new QWidget(this);
|
||||
QVBoxLayout *messages_layout = new QVBoxLayout(messages_container);
|
||||
dbc_combo = createDBCSelector();
|
||||
messages_layout->addWidget(dbc_combo);
|
||||
messages_widget = new MessagesWidget(this);
|
||||
messages_layout->addWidget(messages_widget);
|
||||
|
||||
QDockWidget *dock = new QDockWidget(tr("MESSAGES"), this);
|
||||
dock->setObjectName("MessagesPanel");
|
||||
dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea);
|
||||
dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
|
||||
dock->setWidget(messages_container);
|
||||
dock->setWidget(messages_widget);
|
||||
addDockWidget(Qt::LeftDockWidgetArea, dock);
|
||||
|
||||
// right panel
|
||||
@@ -156,23 +167,6 @@ void MainWindow::createDockWindows() {
|
||||
addDockWidget(Qt::RightDockWidgetArea, video_dock);
|
||||
}
|
||||
|
||||
QComboBox *MainWindow::createDBCSelector() {
|
||||
QComboBox *c = new QComboBox(this);
|
||||
c->setEditable(true);
|
||||
c->lineEdit()->setPlaceholderText(tr("Select from an existing DBC file"));
|
||||
c->setInsertPolicy(QComboBox::NoInsert);
|
||||
c->completer()->setCompletionMode(QCompleter::PopupCompletion);
|
||||
c->completer()->setFilterMode(Qt::MatchContains);
|
||||
|
||||
auto dbc_names = dbc()->allDBCNames();
|
||||
std::sort(dbc_names.begin(), dbc_names.end());
|
||||
for (const auto &name : dbc_names) {
|
||||
c->addItem(QString::fromStdString(name));
|
||||
}
|
||||
c->setCurrentIndex(-1);
|
||||
return c;
|
||||
}
|
||||
|
||||
void MainWindow::createStatusBar() {
|
||||
progress_bar = new QProgressBar();
|
||||
progress_bar->setRange(0, 100);
|
||||
@@ -188,13 +182,26 @@ void MainWindow::createShortcuts() {
|
||||
// TODO: add more shortcuts here.
|
||||
}
|
||||
|
||||
void MainWindow::undoStackCleanChanged(bool clean) {
|
||||
setWindowModified(!clean);
|
||||
}
|
||||
|
||||
void MainWindow::DBCFileChanged() {
|
||||
detail_widget->undo_stack->clear();
|
||||
int index = dbc_combo->findText(QFileInfo(dbc()->name()).baseName());
|
||||
dbc_combo->setCurrentIndex(index);
|
||||
UndoStack::instance()->clear();
|
||||
setWindowFilePath(QString("%1").arg(dbc()->name()));
|
||||
}
|
||||
|
||||
void MainWindow::openRoute() {
|
||||
OpenRouteDialog dlg(this);
|
||||
if (dlg.exec()) {
|
||||
detail_widget->removeAll();
|
||||
charts_widget->removeAll();
|
||||
statusBar()->showMessage(tr("Route %1 loaded").arg(can->routeName()), 2000);
|
||||
} else if (dlg.failedToLoad()) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::newFile() {
|
||||
remindSaveChanges();
|
||||
dbc()->open("untitled.dbc", "");
|
||||
@@ -220,6 +227,13 @@ void MainWindow::loadFile(const QString &fn) {
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::openOpendbcFile() {
|
||||
if (auto action = qobject_cast<QAction *>(sender())) {
|
||||
remindSaveChanges();
|
||||
loadDBCFromOpendbc(action->text());
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::openRecentFile() {
|
||||
if (auto action = qobject_cast<QAction *>(sender())) {
|
||||
remindSaveChanges();
|
||||
@@ -227,7 +241,7 @@ void MainWindow::openRecentFile() {
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::loadDBCFromName(const QString &name) {
|
||||
void MainWindow::loadDBCFromOpendbc(const QString &name) {
|
||||
if (name != dbc()->name()) {
|
||||
remindSaveChanges();
|
||||
dbc()->open(name);
|
||||
@@ -246,13 +260,18 @@ void MainWindow::loadDBCFromClipboard() {
|
||||
}
|
||||
|
||||
void MainWindow::loadDBCFromFingerprint() {
|
||||
// Don't overwrite already loaded DBC
|
||||
if (!dbc()->name().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
remindSaveChanges();
|
||||
auto fingerprint = can->carFingerprint();
|
||||
video_dock->setWindowTitle(tr("ROUTE: %1 FINGERPINT: %2").arg(can->routeName()).arg(fingerprint.isEmpty() ? tr("Unknown Car") : fingerprint));
|
||||
if (!fingerprint.isEmpty()) {
|
||||
auto dbc_name = fingerprint_to_dbc[fingerprint];
|
||||
if (dbc_name != QJsonValue::Undefined) {
|
||||
loadDBCFromName(dbc_name.toString());
|
||||
loadDBCFromOpendbc(dbc_name.toString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -271,7 +290,7 @@ void MainWindow::saveFile(const QString &fn) {
|
||||
QFile file(fn);
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
file.write(dbc()->generateDBC().toUtf8());
|
||||
detail_widget->undo_stack->setClean();
|
||||
UndoStack::instance()->setClean();
|
||||
setCurrentFile(fn);
|
||||
statusBar()->showMessage(tr("File saved"), 2000);
|
||||
}
|
||||
@@ -318,7 +337,7 @@ void MainWindow::updateRecentFileActions() {
|
||||
|
||||
void MainWindow::remindSaveChanges() {
|
||||
bool discard_changes = false;
|
||||
while (!detail_widget->undo_stack->isClean() && !discard_changes) {
|
||||
while (!UndoStack::instance()->isClean() && !discard_changes) {
|
||||
int ret = (QMessageBox::question(this, tr("Unsaved Changes"),
|
||||
tr("You have unsaved changes. Press ok to save them, cancel to discard."),
|
||||
QMessageBox::Ok | QMessageBox::Cancel));
|
||||
@@ -328,7 +347,7 @@ void MainWindow::remindSaveChanges() {
|
||||
discard_changes = true;
|
||||
}
|
||||
}
|
||||
detail_widget->undo_stack->clear();
|
||||
UndoStack::instance()->clear();
|
||||
current_file = "";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDockWidget>
|
||||
#include <QJsonDocument>
|
||||
#include <QMainWindow>
|
||||
@@ -21,12 +20,15 @@ public:
|
||||
MainWindow();
|
||||
void dockCharts(bool dock);
|
||||
void showStatusMessage(const QString &msg, int timeout = 0) { statusBar()->showMessage(msg, timeout); }
|
||||
void loadFile(const QString &fn);
|
||||
|
||||
public slots:
|
||||
void openRoute();
|
||||
void newFile();
|
||||
void openFile();
|
||||
void openRecentFile();
|
||||
void loadDBCFromName(const QString &name);
|
||||
void openOpendbcFile();
|
||||
void loadDBCFromOpendbc(const QString &name);
|
||||
void loadDBCFromFingerprint();
|
||||
void loadDBCFromClipboard();
|
||||
void save();
|
||||
@@ -40,12 +42,10 @@ signals:
|
||||
protected:
|
||||
void remindSaveChanges();
|
||||
void saveFile(const QString &fn);
|
||||
void loadFile(const QString &fn);
|
||||
void setCurrentFile(const QString &fn);
|
||||
void updateRecentFileActions();
|
||||
void createActions();
|
||||
void createDockWindows();
|
||||
QComboBox *createDBCSelector();
|
||||
void createStatusBar();
|
||||
void createShortcuts();
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
@@ -53,6 +53,7 @@ protected:
|
||||
void updateDownloadProgress(uint64_t cur, uint64_t total, bool success);
|
||||
void setOption();
|
||||
void findSimilarBits();
|
||||
void undoStackCleanChanged(bool clean);
|
||||
|
||||
VideoWidget *video_widget = nullptr;
|
||||
QDockWidget *video_dock;
|
||||
@@ -63,7 +64,6 @@ protected:
|
||||
QVBoxLayout *charts_layout;
|
||||
QProgressBar *progress_bar;
|
||||
QJsonDocument fingerprint_to_dbc;
|
||||
QComboBox *dbc_combo;
|
||||
QSplitter *video_splitter;;
|
||||
QString current_file = "";
|
||||
enum { MAX_RECENT_FILES = 15 };
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
#include "tools/cabana/messageswidget.h"
|
||||
|
||||
#include <QFontDatabase>
|
||||
#include <QLineEdit>
|
||||
#include <QVBoxLayout>
|
||||
#include <QPainter>
|
||||
#include <QApplication>
|
||||
#include <QFontDatabase>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPainter>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "tools/cabana/dbcmanager.h"
|
||||
|
||||
MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
// message filter
|
||||
QLineEdit *filter = new QLineEdit(this);
|
||||
filter = new QLineEdit(this);
|
||||
filter->setClearButtonEnabled(true);
|
||||
filter->setPlaceholderText(tr("filter messages"));
|
||||
main_layout->addWidget(filter);
|
||||
@@ -27,17 +27,22 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
|
||||
table_widget->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
table_widget->setSortingEnabled(true);
|
||||
table_widget->sortByColumn(0, Qt::AscendingOrder);
|
||||
table_widget->setColumnWidth(0, 250);
|
||||
table_widget->setColumnWidth(1, 80);
|
||||
table_widget->setColumnWidth(2, 80);
|
||||
table_widget->horizontalHeader()->setStretchLastSection(true);
|
||||
table_widget->verticalHeader()->hide();
|
||||
|
||||
main_layout->addWidget(table_widget);
|
||||
|
||||
// suppress
|
||||
QHBoxLayout *suppress_layout = new QHBoxLayout();
|
||||
suppress_add = new QPushButton("Suppress Highlighted");
|
||||
suppress_clear = new QPushButton();
|
||||
suppress_layout->addWidget(suppress_add);
|
||||
suppress_layout->addWidget(suppress_clear);
|
||||
main_layout->addLayout(suppress_layout);
|
||||
|
||||
// signals/slots
|
||||
QObject::connect(filter, &QLineEdit::textChanged, model, &MessageListModel::setFilterString);
|
||||
QObject::connect(filter, &QLineEdit::textEdited, model, &MessageListModel::setFilterString);
|
||||
QObject::connect(can, &AbstractStream::msgsReceived, model, &MessageListModel::msgsReceived);
|
||||
QObject::connect(can, &AbstractStream::streamStarted, this, &MessagesWidget::reset);
|
||||
QObject::connect(dbc(), &DBCManager::DBCFileChanged, model, &MessageListModel::sortMessages);
|
||||
QObject::connect(dbc(), &DBCManager::msgUpdated, model, &MessageListModel::sortMessages);
|
||||
QObject::connect(dbc(), &DBCManager::msgRemoved, model, &MessageListModel::sortMessages);
|
||||
@@ -50,6 +55,16 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
|
||||
}
|
||||
}
|
||||
});
|
||||
QObject::connect(suppress_add, &QPushButton::clicked, [=]() {
|
||||
model->suppress();
|
||||
updateSuppressedButtons();
|
||||
});
|
||||
QObject::connect(suppress_clear, &QPushButton::clicked, [=]() {
|
||||
model->clearSuppress();
|
||||
updateSuppressedButtons();
|
||||
});
|
||||
|
||||
updateSuppressedButtons();
|
||||
}
|
||||
|
||||
void MessagesWidget::selectMessage(const QString &msg_id) {
|
||||
@@ -58,6 +73,24 @@ void MessagesWidget::selectMessage(const QString &msg_id) {
|
||||
}
|
||||
}
|
||||
|
||||
void MessagesWidget::updateSuppressedButtons() {
|
||||
if (model->suppressed_bytes.empty()) {
|
||||
suppress_clear->setEnabled(false);
|
||||
suppress_clear->setText("Clear Suppressed");
|
||||
} else {
|
||||
suppress_clear->setEnabled(true);
|
||||
suppress_clear->setText(QString("Clear Suppressed (%1)").arg(model->suppressed_bytes.size()));
|
||||
}
|
||||
}
|
||||
|
||||
void MessagesWidget::reset() {
|
||||
model->reset();
|
||||
filter->clear();
|
||||
current_msg_id = "";
|
||||
updateSuppressedButtons();
|
||||
}
|
||||
|
||||
|
||||
// MessageListModel
|
||||
|
||||
QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||
@@ -79,33 +112,38 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const {
|
||||
case 4: return toHex(can_data.dat);
|
||||
}
|
||||
} else if (role == Qt::UserRole && index.column() == 4) {
|
||||
return HexColors::toVariantList(can_data.colors);
|
||||
QList<QVariant> colors;
|
||||
colors.reserve(can_data.dat.size());
|
||||
for (int i = 0; i < can_data.dat.size(); i++){
|
||||
if (suppressed_bytes.contains({id, i})) {
|
||||
colors.append(QColor(255, 255, 255, 0));
|
||||
} else {
|
||||
colors.append(i < can_data.colors.size() ? can_data.colors[i] : QColor(255, 255, 255, 0));
|
||||
}
|
||||
}
|
||||
return colors;
|
||||
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void MessageListModel::setFilterString(const QString &string) {
|
||||
auto contains = [](const QString &id, const QString &txt) {
|
||||
auto cs = Qt::CaseInsensitive;
|
||||
if (id.contains(txt, cs) || msgName(id).contains(txt, cs)) return true;
|
||||
// Search by signal name
|
||||
if (const auto msg = dbc()->msg(id)) {
|
||||
for (auto &signal : msg->getSignals()) {
|
||||
if (QString::fromStdString(signal->name).contains(txt, cs)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
filter_str = string;
|
||||
msgs.clear();
|
||||
for (auto it = can->can_msgs.begin(); it != can->can_msgs.end(); ++it) {
|
||||
bool found = false;
|
||||
|
||||
// Search by message id or name
|
||||
if (it.key().contains(filter_str, Qt::CaseInsensitive) || msgName(it.key()).contains(filter_str, Qt::CaseInsensitive)) {
|
||||
found = true;
|
||||
}
|
||||
|
||||
// Search by signal name
|
||||
const DBCMsg *msg = dbc()->msg(it.key());
|
||||
if (msg != nullptr) {
|
||||
for (auto &signal: msg->getSignals()) {
|
||||
if (QString::fromStdString(signal->name).contains(filter_str, Qt::CaseInsensitive)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
if (filter_str.isEmpty() || contains(it.key(), filter_str)) {
|
||||
msgs.push_back(it.key());
|
||||
}
|
||||
}
|
||||
@@ -122,8 +160,8 @@ void MessageListModel::sortMessages() {
|
||||
});
|
||||
} else if (sort_column == 1) {
|
||||
std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) {
|
||||
auto ll = std::tuple{can->lastMessage(l).src, can->lastMessage(l).address, l};
|
||||
auto rr = std::tuple{can->lastMessage(r).src, can->lastMessage(r).address, r};
|
||||
auto ll = DBCManager::parseId(l);
|
||||
auto rr = DBCManager::parseId(r);
|
||||
return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr;
|
||||
});
|
||||
} else if (sort_column == 2) {
|
||||
@@ -144,10 +182,11 @@ void MessageListModel::sortMessages() {
|
||||
|
||||
void MessageListModel::msgsReceived(const QHash<QString, CanData> *new_msgs) {
|
||||
int prev_row_count = msgs.size();
|
||||
if (filter_str.isEmpty() && msgs.size() != can->can_msgs.size()) {
|
||||
bool update_all = new_msgs->size() == can->can_msgs.size();
|
||||
if (update_all || (filter_str.isEmpty() && msgs.size() != can->can_msgs.size())) {
|
||||
msgs = can->can_msgs.keys();
|
||||
}
|
||||
if (msgs.size() != prev_row_count) {
|
||||
if (update_all || msgs.size() != prev_row_count) {
|
||||
sortMessages();
|
||||
return;
|
||||
}
|
||||
@@ -166,3 +205,29 @@ void MessageListModel::sort(int column, Qt::SortOrder order) {
|
||||
sortMessages();
|
||||
}
|
||||
}
|
||||
|
||||
void MessageListModel::suppress() {
|
||||
const double cur_ts = can->currentSec();
|
||||
|
||||
for (auto &id : msgs) {
|
||||
auto &can_data = can->lastMessage(id);
|
||||
for (int i = 0; i < can_data.dat.size(); i++) {
|
||||
const double dt = cur_ts - can_data.last_change_t[i];
|
||||
if (dt < 2.0) {
|
||||
suppressed_bytes.insert({id, i});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageListModel::clearSuppress() {
|
||||
suppressed_bytes.clear();
|
||||
}
|
||||
|
||||
void MessageListModel::reset() {
|
||||
beginResetModel();
|
||||
filter_str = "";
|
||||
msgs.clear();
|
||||
clearSuppress();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QHeaderView>
|
||||
#include <QTableView>
|
||||
#include <QLineEdit>
|
||||
#include <QSet>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QTableView>
|
||||
|
||||
#include "tools/cabana/streams/abstractstream.h"
|
||||
|
||||
@@ -20,7 +22,11 @@ public:
|
||||
void setFilterString(const QString &string);
|
||||
void msgsReceived(const QHash<QString, CanData> *new_msgs = nullptr);
|
||||
void sortMessages();
|
||||
void suppress();
|
||||
void clearSuppress();
|
||||
void reset();
|
||||
QStringList msgs;
|
||||
QSet<std::pair<QString, int>> suppressed_bytes;
|
||||
|
||||
private:
|
||||
QString filter_str;
|
||||
@@ -36,6 +42,8 @@ public:
|
||||
void selectMessage(const QString &message_id);
|
||||
QByteArray saveHeaderState() const { return table_widget->horizontalHeader()->saveState(); }
|
||||
bool restoreHeaderState(const QByteArray &state) const { return table_widget->horizontalHeader()->restoreState(state); }
|
||||
void updateSuppressedButtons();
|
||||
void reset();
|
||||
|
||||
signals:
|
||||
void msgSelectionChanged(const QString &message_id);
|
||||
@@ -43,5 +51,9 @@ signals:
|
||||
protected:
|
||||
QTableView *table_widget;
|
||||
QString current_msg_id;
|
||||
QLineEdit *filter;
|
||||
MessageListModel *model;
|
||||
QPushButton *suppress_add;
|
||||
QPushButton *suppress_clear;
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "tools/cabana/route.h"
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QFileDialog>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "tools/cabana/streams/replaystream.h"
|
||||
|
||||
OpenRouteDialog::OpenRouteDialog(QWidget *parent) : QDialog(parent) {
|
||||
// TODO: get route list from api.comma.ai
|
||||
QHBoxLayout *edit_layout = new QHBoxLayout;
|
||||
edit_layout->addWidget(new QLabel(tr("Route:")));
|
||||
edit_layout->addWidget(route_edit = new QLineEdit(this));
|
||||
route_edit->setPlaceholderText(tr("Enter remote route name or click browse to select a local route"));
|
||||
auto file_btn = new QPushButton(tr("Browse..."), this);
|
||||
edit_layout->addWidget(file_btn);
|
||||
|
||||
btn_box = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel);
|
||||
btn_box->button(QDialogButtonBox::Open)->setEnabled(false);
|
||||
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->addStretch(0);
|
||||
main_layout->addLayout(edit_layout);
|
||||
main_layout->addStretch(0);
|
||||
main_layout->addWidget(btn_box);
|
||||
setMinimumSize({550, 120});
|
||||
|
||||
QObject::connect(btn_box, &QDialogButtonBox::accepted, this, &OpenRouteDialog::loadRoute);
|
||||
QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
QObject::connect(route_edit, &QLineEdit::textChanged, [this]() {
|
||||
btn_box->button(QDialogButtonBox::Open)->setEnabled(!route_edit->text().isEmpty());
|
||||
});
|
||||
QObject::connect(file_btn, &QPushButton::clicked, [=]() {
|
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Open Local Route"), settings.last_route_dir);
|
||||
if (!dir.isEmpty()) {
|
||||
route_edit->setText(dir);
|
||||
settings.last_route_dir = QFileInfo(dir).absolutePath();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OpenRouteDialog::loadRoute() {
|
||||
btn_box->setEnabled(false);
|
||||
|
||||
QString route = route_edit->text();
|
||||
QString data_dir;
|
||||
if (int idx = route.lastIndexOf('/'); idx != -1) {
|
||||
data_dir = route.mid(0, idx + 1);
|
||||
route = route.mid(idx + 1);
|
||||
}
|
||||
|
||||
bool is_valid_format = Route::parseRoute(route).str.size() > 0;
|
||||
if (!is_valid_format) {
|
||||
QMessageBox::warning(nullptr, tr("Warning"), tr("Invalid route format: '%1'").arg(route));
|
||||
} else {
|
||||
failed_to_load = !dynamic_cast<ReplayStream *>(can)->loadRoute(route, data_dir);
|
||||
if (failed_to_load) {
|
||||
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to load route: '%1'").arg(route));
|
||||
} else {
|
||||
accept();
|
||||
}
|
||||
}
|
||||
|
||||
btn_box->setEnabled(true);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLineEdit>
|
||||
#include <QDialog>
|
||||
|
||||
class OpenRouteDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OpenRouteDialog(QWidget *parent);
|
||||
void loadRoute();
|
||||
inline bool failedToLoad() const { return failed_to_load; }
|
||||
|
||||
private:
|
||||
QLineEdit *route_edit;
|
||||
QDialogButtonBox *btn_box;
|
||||
bool failed_to_load = false;
|
||||
};
|
||||
@@ -20,11 +20,13 @@ void Settings::save() {
|
||||
s.setValue("chart_range", chart_range);
|
||||
s.setValue("chart_column_count", chart_column_count);
|
||||
s.setValue("last_dir", last_dir);
|
||||
s.setValue("last_route_dir", last_route_dir);
|
||||
s.setValue("window_state", window_state);
|
||||
s.setValue("geometry", geometry);
|
||||
s.setValue("video_splitter_state", video_splitter_state);
|
||||
s.setValue("recent_files", recent_files);
|
||||
s.setValue("message_header_state", message_header_state);
|
||||
s.setValue("chart_series_type", chart_series_type);
|
||||
}
|
||||
|
||||
void Settings::load() {
|
||||
@@ -35,11 +37,13 @@ void Settings::load() {
|
||||
chart_range = s.value("chart_range", 3 * 60).toInt();
|
||||
chart_column_count = s.value("chart_column_count", 1).toInt();
|
||||
last_dir = s.value("last_dir", QDir::homePath()).toString();
|
||||
last_route_dir = s.value("last_route_dir", QDir::homePath()).toString();
|
||||
window_state = s.value("window_state").toByteArray();
|
||||
geometry = s.value("geometry").toByteArray();
|
||||
video_splitter_state = s.value("video_splitter_state").toByteArray();
|
||||
recent_files = s.value("recent_files").toStringList();
|
||||
message_header_state = s.value("message_header_state").toByteArray();
|
||||
chart_series_type = s.value("chart_series_type", 0).toInt();
|
||||
}
|
||||
|
||||
// SettingsDlg
|
||||
@@ -60,6 +64,11 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
|
||||
cached_minutes->setValue(settings.max_cached_minutes);
|
||||
form_layout->addRow(tr("Max Cached Minutes"), cached_minutes);
|
||||
|
||||
chart_series_type = new QComboBox(this);
|
||||
chart_series_type->addItems({tr("Line"), tr("Scatter")});
|
||||
chart_series_type->setCurrentIndex(settings.chart_series_type);
|
||||
form_layout->addRow(tr("Chart Default Series Type"), chart_series_type);
|
||||
|
||||
chart_height = new QSpinBox(this);
|
||||
chart_height->setRange(100, 500);
|
||||
chart_height->setSingleStep(10);
|
||||
@@ -77,6 +86,7 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
|
||||
void SettingsDlg::save() {
|
||||
settings.fps = fps->value();
|
||||
settings.max_cached_minutes = cached_minutes->value();
|
||||
settings.chart_series_type = chart_series_type->currentIndex();
|
||||
settings.chart_height = chart_height->value();
|
||||
settings.save();
|
||||
accept();
|
||||
|
||||
@@ -18,7 +18,9 @@ public:
|
||||
int chart_height = 200;
|
||||
int chart_column_count = 1;
|
||||
int chart_range = 3 * 60; // e minutes
|
||||
int chart_series_type = 0;
|
||||
QString last_dir;
|
||||
QString last_route_dir;
|
||||
QByteArray geometry;
|
||||
QByteArray video_splitter_state;
|
||||
QByteArray window_state;
|
||||
@@ -38,6 +40,7 @@ public:
|
||||
QSpinBox *fps;
|
||||
QSpinBox *cached_minutes;
|
||||
QSpinBox *chart_height;
|
||||
QComboBox *chart_series_type;
|
||||
};
|
||||
|
||||
extern Settings settings;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user