mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-08-03 08:41:53 +08:00
Merge remote-tracking branch 'upstream/commaai/openpilot/master'
# Conflicts: # cereal # opendbc # panda
This commit is contained in:
@@ -16,7 +16,6 @@ jobs:
|
||||
build_prebuilt:
|
||||
name: build prebuilt
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 60
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
env:
|
||||
IMAGE_NAME: openpilot-prebuilt
|
||||
|
||||
@@ -8,7 +8,6 @@ jobs:
|
||||
build_masterci:
|
||||
name: build master-ci
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 60
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
steps:
|
||||
- name: Wait for green check mark
|
||||
|
||||
@@ -32,7 +32,6 @@ jobs:
|
||||
build_release:
|
||||
name: build release
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
STRIPPED_DIR: /tmp/releasepilot
|
||||
steps:
|
||||
@@ -66,7 +65,6 @@ jobs:
|
||||
build_all:
|
||||
name: build all
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -84,7 +82,6 @@ jobs:
|
||||
#build_mac:
|
||||
# name: build macos
|
||||
# runs-on: macos-latest
|
||||
# timeout-minutes: 60
|
||||
# steps:
|
||||
# - uses: actions/checkout@v3
|
||||
# with:
|
||||
@@ -141,7 +138,6 @@ jobs:
|
||||
docker_push:
|
||||
name: docker push
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 22
|
||||
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
|
||||
needs: static_analysis # hack to ensure slow tests run first since this and static_analysis are fast
|
||||
steps:
|
||||
@@ -150,14 +146,12 @@ jobs:
|
||||
submodules: true
|
||||
- name: Build Docker image
|
||||
run: eval "$BUILD"
|
||||
timeout-minutes: 13
|
||||
- name: Push to container registry
|
||||
run: |
|
||||
$DOCKER_LOGIN
|
||||
docker push $DOCKER_REGISTRY/$BASE_IMAGE:latest
|
||||
- name: Build CL Docker image
|
||||
run: eval "$BUILD_CL"
|
||||
timeout-minutes: 4
|
||||
- name: Push to container registry
|
||||
run: |
|
||||
$DOCKER_LOGIN
|
||||
@@ -166,7 +160,6 @@ jobs:
|
||||
static_analysis:
|
||||
name: static analysis
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -174,13 +167,11 @@ jobs:
|
||||
- name: Build Docker image
|
||||
run: eval "$BUILD"
|
||||
- name: pre-commit
|
||||
timeout-minutes: 5
|
||||
run: ${{ env.RUN }} "pre-commit run --all"
|
||||
|
||||
valgrind:
|
||||
name: valgrind
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -198,7 +189,6 @@ jobs:
|
||||
unit_tests:
|
||||
name: unit tests
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -207,7 +197,6 @@ jobs:
|
||||
- name: Build openpilot
|
||||
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Run unit tests
|
||||
timeout-minutes: 15
|
||||
run: |
|
||||
${{ env.RUN }} "export SKIP_LONG_TESTS=1 && \
|
||||
$UNIT_TEST common && \
|
||||
@@ -242,7 +231,6 @@ jobs:
|
||||
process_replay:
|
||||
name: process replay
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -280,7 +268,6 @@ jobs:
|
||||
test_modeld:
|
||||
name: model tests
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -293,12 +280,10 @@ jobs:
|
||||
run: |
|
||||
${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Run model replay with ONNX
|
||||
timeout-minutes: 2
|
||||
run: |
|
||||
${{ env.RUN_CL }} "ONNXCPU=1 CI=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \
|
||||
coverage xml"
|
||||
- name: Run unit tests
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
${{ env.RUN_CL }} "$UNIT_TEST selfdrive/modeld && \
|
||||
coverage xml"
|
||||
@@ -308,7 +293,6 @@ jobs:
|
||||
test_longitudinal:
|
||||
name: longitudinal
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -323,7 +307,6 @@ jobs:
|
||||
cd selfdrive/test/longitudinal_maneuvers && \
|
||||
coverage run ./test_longitudinal.py && \
|
||||
coverage xml"
|
||||
timeout-minutes: 2
|
||||
- name: "Upload coverage to Codecov"
|
||||
uses: codecov/codecov-action@v2
|
||||
- uses: actions/upload-artifact@v2
|
||||
@@ -336,7 +319,6 @@ jobs:
|
||||
test_cars:
|
||||
name: cars
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -355,7 +337,6 @@ jobs:
|
||||
- name: Build openpilot
|
||||
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Test car models
|
||||
timeout-minutes: 12
|
||||
run: |
|
||||
${{ env.RUN }} "coverage run -m pytest selfdrive/car/tests/test_models.py && \
|
||||
coverage xml && \
|
||||
@@ -369,7 +350,6 @@ jobs:
|
||||
car_docs_diff:
|
||||
name: PR comments
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 20
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -30,7 +30,6 @@ jobs:
|
||||
plotjuggler:
|
||||
name: plotjuggler
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -38,7 +37,6 @@ jobs:
|
||||
- name: Build Docker image
|
||||
run: eval "$BUILD"
|
||||
- name: Unit test
|
||||
timeout-minutes: 2
|
||||
run: |
|
||||
${{ env.RUN }} "scons -j$(nproc) --directory=/tmp/openpilot/cereal && \
|
||||
apt-get update && \
|
||||
@@ -49,7 +47,6 @@ jobs:
|
||||
simulator:
|
||||
name: simulator
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
IMAGE_NAME: openpilot-sim
|
||||
if: github.repository == 'commaai/openpilot'
|
||||
@@ -74,7 +71,6 @@ jobs:
|
||||
docs:
|
||||
name: build docs
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
||||
+5
-2
@@ -1,12 +1,15 @@
|
||||
Version 0.9.1 (2022-12-XX)
|
||||
Version 0.9.1 (2023-2-20)
|
||||
========================
|
||||
* Adjust alert volume using ambient noise level
|
||||
* Removed driver monitoring timer resetting on interaction if face detected and distracted
|
||||
* New German translation thanks to Vrabetz and CzokNorris!
|
||||
* UI updates
|
||||
* Driver monitoring icon shows driver's head pose
|
||||
* German translation thanks to Vrabetz and CzokNorris!
|
||||
* 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 Niro Hybrid 2023 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!
|
||||
|
||||
+1
-1
Submodule cereal updated: 8214437efc...168f460269
+4
-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.
|
||||
|
||||
# 233 Supported Cars
|
||||
# 235 Supported Cars
|
||||
|
||||
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness|Video|
|
||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
@@ -107,6 +107,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|
||||
|Kia|Niro EV 2022|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 2022">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 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 Hybrid 2023[<sup>5</sup>](#footnotes)|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 2023">Hyundai A</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>||
|
||||
@@ -174,7 +175,8 @@ A supported vehicle is one that just works when you install a comma three. All s
|
||||
|Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Avalon 2022">Toyota</a>||
|
||||
|Toyota|Avalon Hybrid 2019-21|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Avalon Hybrid 2019-21">Toyota</a>||
|
||||
|Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Avalon Hybrid 2022">Toyota</a>||
|
||||
|Toyota|C-HR 2017-21|All|Stock|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=C-HR 2017-21">Toyota</a>||
|
||||
|Toyota|C-HR 2017-20|All|Stock|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=C-HR 2017-20">Toyota</a>||
|
||||
|Toyota|C-HR 2021|All|Stock|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=C-HR 2021">Toyota</a>||
|
||||
|Toyota|C-HR Hybrid 2017-19|All|Stock|0 mph|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=C-HR Hybrid 2017-19">Toyota</a>||
|
||||
|Toyota|Camry 2018-20|All|Stock|0 mph[<sup>6</sup>](#footnotes)|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Camry 2018-20">Toyota</a>|<a href="https://www.youtube.com/watch?v=fkcjviZY9CM" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|Toyota|Camry 2021-22|All|openpilot|0 mph[<sup>6</sup>](#footnotes)|0 mph|[](##)|[](##)|<a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Camry 2021-22">Toyota</a>||
|
||||
|
||||
+1
-1
Submodule laika_repo updated: b30afe44d2...278b44ba8c
+1
-1
Submodule opendbc updated: 77996759d9...7f0e42faba
+1
-1
Submodule panda updated: 76e766b038...25ff80351f
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 19 KiB |
@@ -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, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags
|
||||
from selfdrive.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, CHRYSLER_OLD_TUNING_BLACKLIST, ChryslerFlags
|
||||
from selfdrive.car.interfaces import CarInterfaceBase
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_CHRYSLER_RAM_DT
|
||||
|
||||
ret.minSteerSpeed = 3.8 # m/s
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
if candidate not in RAM_CARS:
|
||||
# Newer FW versions standard on the following platforms, or flashed by a dealer onto older platforms have a higher minimum steering speed.
|
||||
new_eps_platform = candidate in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE_2019)
|
||||
@@ -36,9 +37,12 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.mass = 2242. + STD_CARGO_KG
|
||||
ret.wheelbase = 3.089
|
||||
ret.steerRatio = 16.2 # Pacifica Hybrid 2017
|
||||
ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]]
|
||||
ret.lateralTuning.pid.kf = 0.00006
|
||||
|
||||
if candidate in CHRYSLER_OLD_TUNING_BLACKLIST:
|
||||
ret.lateralTuning.init('pid')
|
||||
ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]]
|
||||
ret.lateralTuning.pid.kf = 0.00006
|
||||
|
||||
# Jeep
|
||||
elif candidate in (CAR.JEEP_CHEROKEE, CAR.JEEP_CHEROKEE_2019):
|
||||
@@ -46,9 +50,12 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.wheelbase = 2.71
|
||||
ret.steerRatio = 16.7
|
||||
ret.steerActuatorDelay = 0.2
|
||||
ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]]
|
||||
ret.lateralTuning.pid.kf = 0.00006
|
||||
|
||||
if candidate in CHRYSLER_OLD_TUNING_BLACKLIST:
|
||||
ret.lateralTuning.init('pid')
|
||||
ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]]
|
||||
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]]
|
||||
ret.lateralTuning.pid.kf = 0.00006
|
||||
|
||||
# Ram
|
||||
elif candidate == CAR.RAM_1500:
|
||||
@@ -56,7 +63,6 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.wheelbase = 3.88
|
||||
ret.steerRatio = 16.3
|
||||
ret.mass = 2493. + STD_CARGO_KG
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
|
||||
ret.minSteerSpeed = 14.5
|
||||
# Older EPS FW allow steer to zero
|
||||
if any(fw.ecu == 'eps' and fw.fwVersion[:4] <= b"6831" for fw in car_fw):
|
||||
|
||||
@@ -46,8 +46,12 @@ class CarControllerParams:
|
||||
self.STEER_DELTA_DOWN = 6
|
||||
self.STEER_MAX = 261 # EPS allows more, up to 350?
|
||||
else:
|
||||
self.STEER_DELTA_UP = 3
|
||||
self.STEER_DELTA_DOWN = 3
|
||||
if CP.carFingerprint in CHRYSLER_OLD_TUNING_BLACKLIST:
|
||||
self.STEER_DELTA_UP = 3
|
||||
self.STEER_DELTA_DOWN = 3
|
||||
else:
|
||||
self.STEER_DELTA_UP = 6
|
||||
self.STEER_DELTA_DOWN = 6
|
||||
self.STEER_MAX = 261 # higher than this faults the EPS
|
||||
|
||||
STEER_THRESHOLD = 120
|
||||
@@ -56,6 +60,11 @@ RAM_DT = {CAR.RAM_1500, }
|
||||
RAM_HD = {CAR.RAM_HD, }
|
||||
RAM_CARS = RAM_DT | RAM_HD
|
||||
|
||||
# the increased steer rate hasn't been verified on these cars.
|
||||
# remove from this list once it's been tested and confirmed to not fault
|
||||
CHRYSLER_OLD_TUNING_BLACKLIST = {CAR.PACIFICA_2017_HYBRID, CAR.PACIFICA_2018, CAR.PACIFICA_2018_HYBRID,
|
||||
CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE}
|
||||
|
||||
@dataclass
|
||||
class ChryslerCarInfo(CarInfo):
|
||||
package: str = "Adaptive Cruise Control (ACC)"
|
||||
|
||||
@@ -49,25 +49,26 @@ class CarController:
|
||||
can_sends = []
|
||||
|
||||
# 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
|
||||
# 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.STEER_STEP if CC.latActive else self.params.INACTIVE_STEER_STEP
|
||||
|
||||
steer_step = self.params.INACTIVE_STEER_STEP
|
||||
if CC.latActive or sync_steer:
|
||||
steer_step = self.params.STEER_STEP
|
||||
if self.CP.networkLocation == NetworkLocation.fwdCamera:
|
||||
# Also send at 50Hz:
|
||||
# - on startup, first few msgs are blocked
|
||||
# - 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.cam_lka_steering_cmd_counter + 1) % 4
|
||||
if not self.sent_lka_steering_cmd or out_of_sync:
|
||||
steer_step = self.params.STEER_STEP
|
||||
|
||||
# Avoid GM EPS faults when transmitting messages too close together: skip this transmit if we just received the
|
||||
# next Panda loopback confirmation in the current CS frame.
|
||||
if CS.loopback_lka_steering_cmd_updated:
|
||||
self.lka_steering_cmd_counter += 1
|
||||
self.sent_lka_steering_cmd = True
|
||||
elif (self.frame - self.last_steer_frame) >= steer_step:
|
||||
|
||||
# Avoid GM EPS faults when transmitting messages too close together: skip this transmit if we just
|
||||
# received the ASCMLKASteeringCmd loopback confirmation in the current CS frame
|
||||
if (self.frame - self.last_steer_frame) >= steer_step and not CS.loopback_lka_steering_cmd_updated:
|
||||
# Initialize ASCMLKASteeringCmd counter using the camera until we get a msg on the bus
|
||||
if init_lka_counter:
|
||||
if not self.sent_lka_steering_cmd:
|
||||
self.lka_steering_cmd_counter = CS.pt_lka_steering_cmd_counter + 1
|
||||
|
||||
if CC.latActive:
|
||||
|
||||
@@ -19,7 +19,7 @@ class CarState(CarStateBase):
|
||||
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.cam_lka_steering_cmd_counter = 0
|
||||
self.buttons_counter = 0
|
||||
|
||||
def update(self, pt_cp, cam_cp, loopback_cp):
|
||||
@@ -35,7 +35,7 @@ class CarState(CarStateBase):
|
||||
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"]
|
||||
self.cam_lka_steering_cmd_counter = cam_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"]
|
||||
|
||||
ret.wheelSpeeds = self.get_wheel_speeds(
|
||||
pt_cp.vl["EBCMWheelSpdFront"]["FLWheelSpd"],
|
||||
|
||||
@@ -11,10 +11,10 @@ Ecu = car.CarParams.Ecu
|
||||
|
||||
class CarControllerParams:
|
||||
STEER_MAX = 300 # GM limit is 3Nm. Used by carcontroller to generate LKA output
|
||||
STEER_STEP = 2 # Active control frames per command (50hz)
|
||||
STEER_STEP = 3 # Active control frames per command (~33hz)
|
||||
INACTIVE_STEER_STEP = 10 # Inactive control frames per command (10hz)
|
||||
STEER_DELTA_UP = 7 # Delta rates require review due to observed EPS weakness
|
||||
STEER_DELTA_DOWN = 17
|
||||
STEER_DELTA_UP = 10 # Delta rates require review due to observed EPS weakness
|
||||
STEER_DELTA_DOWN = 25
|
||||
STEER_DRIVER_ALLOWANCE = 50
|
||||
STEER_DRIVER_MULTIPLIER = 4
|
||||
STEER_DRIVER_FACTOR = 100
|
||||
|
||||
@@ -6,6 +6,7 @@ from cereal import car
|
||||
from common.conversions import Conversions as CV
|
||||
from opendbc.can.parser import CANParser
|
||||
from opendbc.can.can_define import CANDefine
|
||||
from selfdrive.car.hyundai.hyundaicanfd import get_e_can_bus
|
||||
from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, FEATURES, CAMERA_SCC_CAR, CANFD_CAR, EV_CAR, HYBRID_CAR, Buttons, CarControllerParams
|
||||
from selfdrive.car.interfaces import CarStateBase
|
||||
|
||||
@@ -21,7 +22,9 @@ class CarState(CarStateBase):
|
||||
self.cruise_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES)
|
||||
self.main_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES)
|
||||
|
||||
self.gear_msg_canfd = "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else "GEAR_SHIFTER"
|
||||
self.gear_msg_canfd = "GEAR_ALT_2" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS_2 else \
|
||||
"GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else \
|
||||
"GEAR_SHIFTER"
|
||||
if CP.carFingerprint in CANFD_CAR:
|
||||
self.shifter_values = can_define.dv[self.gear_msg_canfd]["GEAR"]
|
||||
elif self.CP.carFingerprint in FEATURES["use_cluster_gears"]:
|
||||
@@ -427,7 +430,9 @@ class CarState(CarStateBase):
|
||||
def get_can_parser_canfd(CP):
|
||||
|
||||
cruise_btn_msg = "CRUISE_BUTTONS_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS"
|
||||
gear_msg = "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else "GEAR_SHIFTER"
|
||||
gear_msg = "GEAR_ALT_2" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS_2 else \
|
||||
"GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else \
|
||||
"GEAR_SHIFTER"
|
||||
signals = [
|
||||
("WHEEL_SPEED_1", "WHEEL_SPEEDS"),
|
||||
("WHEEL_SPEED_2", "WHEEL_SPEEDS"),
|
||||
@@ -513,8 +518,7 @@ class CarState(CarStateBase):
|
||||
("ACCELERATOR_BRAKE_ALT", 100),
|
||||
]
|
||||
|
||||
bus = 5 if CP.flags & HyundaiFlags.CANFD_HDA2 else 4
|
||||
return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, bus)
|
||||
return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, get_e_can_bus(CP))
|
||||
|
||||
@staticmethod
|
||||
def get_cam_can_parser_canfd(CP):
|
||||
|
||||
@@ -35,9 +35,12 @@ class CarInterface(CarInterfaceBase):
|
||||
# non-HDA2
|
||||
if 0x1cf not in fingerprint[4]:
|
||||
ret.flags |= HyundaiFlags.CANFD_ALT_BUTTONS.value
|
||||
# ICE cars do not have 0x130; GEARS message on 0x40 instead
|
||||
# ICE cars do not have 0x130; GEARS message on 0x40 or 0x70 instead
|
||||
if 0x130 not in fingerprint[4]:
|
||||
ret.flags |= HyundaiFlags.CANFD_ALT_GEARS.value
|
||||
if 0x40 not in fingerprint[4]:
|
||||
ret.flags |= HyundaiFlags.CANFD_ALT_GEARS_2.value
|
||||
else:
|
||||
ret.flags |= HyundaiFlags.CANFD_ALT_GEARS.value
|
||||
if candidate not in CANFD_RADAR_SCC_CAR:
|
||||
ret.flags |= HyundaiFlags.CANFD_CAMERA_SCC.value
|
||||
|
||||
@@ -129,8 +132,8 @@ class CarInterface(CarInterfaceBase):
|
||||
ret.mass = 1985. + STD_CARGO_KG
|
||||
ret.wheelbase = 2.78
|
||||
ret.steerRatio = 14.4 * 1.1 # 10% higher at the center seems reasonable
|
||||
elif candidate in (CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021):
|
||||
ret.mass = 1737. + STD_CARGO_KG
|
||||
elif candidate in (CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_NIRO_HEV_2ND_GEN):
|
||||
ret.mass = 3452. * CV.LB_TO_KG + STD_CARGO_KG # average of all the cars
|
||||
ret.wheelbase = 2.7
|
||||
ret.steerRatio = 13.9 if CAR.KIA_NIRO_HEV_2021 else 13.73 # Spec
|
||||
tire_stiffness_factor = 0.385
|
||||
|
||||
@@ -61,6 +61,8 @@ class HyundaiFlags(IntFlag):
|
||||
|
||||
ENABLE_BLINKERS = 32
|
||||
|
||||
CANFD_ALT_GEARS_2 = 64
|
||||
|
||||
|
||||
class CAR:
|
||||
# Hyundai
|
||||
@@ -100,6 +102,7 @@ class CAR:
|
||||
KIA_NIRO_EV = "KIA NIRO EV 2020"
|
||||
KIA_NIRO_PHEV = "KIA NIRO HYBRID 2019"
|
||||
KIA_NIRO_HEV_2021 = "KIA NIRO HYBRID 2021"
|
||||
KIA_NIRO_HEV_2ND_GEN = "KIA NIRO HYBRID 2ND GEN"
|
||||
KIA_OPTIMA_G4 = "KIA OPTIMA 4TH GEN"
|
||||
KIA_OPTIMA_G4_FL = "KIA OPTIMA 4TH GEN FACELIFT"
|
||||
KIA_OPTIMA_H = "KIA OPTIMA HYBRID 2017 & SPORTS 2019"
|
||||
@@ -208,6 +211,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
|
||||
HyundaiCarInfo("Kia Niro Hybrid 2021", harness=Harness.hyundai_f), # TODO: could be hyundai_d, verify
|
||||
HyundaiCarInfo("Kia Niro Hybrid 2022", harness=Harness.hyundai_h),
|
||||
],
|
||||
CAR.KIA_NIRO_HEV_2ND_GEN: HyundaiCarInfo("Kia Niro Hybrid 2023", harness=Harness.hyundai_a),
|
||||
CAR.KIA_OPTIMA_G4: HyundaiCarInfo("Kia Optima 2017", "Advanced Smart Cruise Control", harness=Harness.hyundai_b), # TODO: may support 2016, 2018
|
||||
CAR.KIA_OPTIMA_G4_FL: HyundaiCarInfo("Kia Optima 2019-20", harness=Harness.hyundai_g),
|
||||
CAR.KIA_OPTIMA_H: [
|
||||
@@ -1627,6 +1631,14 @@ FW_VERSIONS = {
|
||||
b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.06 99110-P2000 ',
|
||||
],
|
||||
},
|
||||
CAR.KIA_NIRO_HEV_2ND_GEN: {
|
||||
(Ecu.fwdCamera, 0x7c4, None): [
|
||||
b'\xf1\x00SG2HMFC AT USA LHD 1.01 1.08 99211-AT000 220531',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x7d0, None): [
|
||||
b'\xf1\x00SG2_ RDR ----- 1.00 1.01 99110-AT000 ',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
CHECKSUM = {
|
||||
@@ -1644,7 +1656,7 @@ FEATURES = {
|
||||
"use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022, 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}
|
||||
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, CAR.KIA_NIRO_HEV_2ND_GEN}
|
||||
|
||||
# The radar does SCC on these cars when HDA I, rather than the camera
|
||||
CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_SORENTO_4TH_GEN}
|
||||
@@ -1652,7 +1664,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, CAR.KIA_K5_HEV_2020} # 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, CAR.KIA_NIRO_HEV_2ND_GEN} # 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
|
||||
@@ -1714,4 +1726,5 @@ DBC = {
|
||||
CAR.KIA_SORENTO_PHEV_4TH_GEN: dbc_dict('hyundai_canfd', None),
|
||||
CAR.GENESIS_GV60_EV_1ST_GEN: dbc_dict('hyundai_canfd', None),
|
||||
CAR.KIA_SORENTO_4TH_GEN: dbc_dict('hyundai_canfd', None),
|
||||
CAR.KIA_NIRO_HEV_2ND_GEN: dbc_dict('hyundai_canfd', None),
|
||||
}
|
||||
|
||||
@@ -127,6 +127,7 @@ routes = [
|
||||
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),
|
||||
CarTestRoute("db04d2c63990e3ba|2023-02-08--16-52-39", HYUNDAI.KIA_NIRO_HEV_2ND_GEN),
|
||||
CarTestRoute("50a2212c41f65c7b|2021-05-24--16-22-06", HYUNDAI.KIA_FORTE),
|
||||
CarTestRoute("192283cdbb7a58c2|2022-10-15--01-43-18", HYUNDAI.KIA_SPORTAGE_5TH_GEN),
|
||||
CarTestRoute("c5ac319aa9583f83|2021-06-01--18-18-31", HYUNDAI.ELANTRA),
|
||||
@@ -179,6 +180,7 @@ routes = [
|
||||
CarTestRoute("0a0de17a1e6a2d15|2020-09-21--21-24-41", TOYOTA.PRIUS_TSS2),
|
||||
CarTestRoute("9b36accae406390e|2021-03-30--10-41-38", TOYOTA.MIRAI),
|
||||
CarTestRoute("cd9cff4b0b26c435|2021-05-13--15-12-39", TOYOTA.CHR),
|
||||
CarTestRoute("ea8fbe72b96a185c|2023-02-08--15-11-46", TOYOTA.CHR_TSS2),
|
||||
CarTestRoute("57858ede0369a261|2021-05-18--20-34-20", TOYOTA.CHRH),
|
||||
CarTestRoute("14623aae37e549f3|2021-10-24--01-20-49", TOYOTA.PRIUS_V),
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ COMMA BODY: [.nan, 1000, .nan]
|
||||
RAM 1500 5TH GEN: [2.0, 2.0, 0.0]
|
||||
RAM HD 5TH GEN: [1.4, 1.4, 0.0]
|
||||
SUBARU OUTBACK 6TH GEN: [2.3, 2.3, 0.11]
|
||||
CHEVROLET BOLT EV 2022: [2.0, 2.0, 0.05]
|
||||
CHEVROLET BOLT EUV 2022: [2.0, 2.0, 0.05]
|
||||
CHEVROLET SILVERADO 1500 2020: [1.9, 1.9, 0.112]
|
||||
CHEVROLET EQUINOX 2019: [2.0, 2.0, 0.05]
|
||||
@@ -39,6 +38,7 @@ GENESIS GV70 1ST GEN: [2.42, 2.42, 0.1]
|
||||
KIA SORENTO PLUG-IN HYBRID 4TH GEN: [2.5, 2.5, 0.1]
|
||||
GENESIS GV60 ELECTRIC 1ST GEN: [2.5, 2.5, 0.1]
|
||||
KIA SORENTO 4TH GEN: [2.5, 2.5, 0.1]
|
||||
KIA NIRO HYBRID 2ND GEN: [2.42, 2.5, 0.12]
|
||||
|
||||
# Dashcam or fallback configured as ideal car
|
||||
mock: [10.0, 10, 0.0]
|
||||
|
||||
@@ -4,11 +4,11 @@ ACURA RDX 2020: [1.4314459806646749, 0.33874701282109954, 0.18048847083897598]
|
||||
AUDI A3 3RD GEN: [1.5122414863077502, 1.7443517531719404, 0.15194151892450905]
|
||||
AUDI Q3 2ND GEN: [1.4439223359448605, 1.2254955789112076, 0.1413798895978097]
|
||||
CHEVROLET VOLT PREMIER 2017: [1.5961527626411784, 1.8422651988094612, 0.1572393918005158]
|
||||
CHRYSLER PACIFICA 2018: [1.593387270257916, 1.3366521181047952, 0.13776367250652022]
|
||||
CHRYSLER PACIFICA 2020: [1.4323553627965695, 1.509076559398423, 0.14328246159386085]
|
||||
CHRYSLER PACIFICA HYBRID 2017: [1.3032470208409048, 1.06831764583744, 0.13287170990024627]
|
||||
CHRYSLER PACIFICA HYBRID 2018: [1.6068280248761635, 1.2943025830995154, 0.1358557824293823]
|
||||
CHRYSLER PACIFICA HYBRID 2019: [1.4624643614072217, 1.1958788168371808, 0.15748488008472716]
|
||||
CHRYSLER PACIFICA 2018: [2.07140, 1.3366521181047952, 0.13776367250652022]
|
||||
CHRYSLER PACIFICA 2020: [1.86206, 1.509076559398423, 0.14328246159386085]
|
||||
CHRYSLER PACIFICA HYBRID 2017: [1.79422, 1.06831764583744, 0.116237]
|
||||
CHRYSLER PACIFICA HYBRID 2018: [2.08887, 1.2943025830995154, 0.114818]
|
||||
CHRYSLER PACIFICA HYBRID 2019: [1.90120, 1.1958788168371808, 0.131520]
|
||||
GENESIS G70 2018: [3.8520195946707947, 2.354697063349854, 0.06830285485626221]
|
||||
GMC ACADIA DENALI 2018: [1.3181430320331884, 1.1853735340610179, 0.3450592280031644]
|
||||
HONDA ACCORD 2018: [1.7135052593468778, 0.3461280068322071, 0.21579936052863807]
|
||||
@@ -39,8 +39,8 @@ HYUNDAI SONATA 2019: [2.2200457811703953, 1.2967330275895228, 0.1403992098658639
|
||||
HYUNDAI SONATA 2020: [2.9638737459977467, 2.1259108157250735, 0.07813665616927593]
|
||||
HYUNDAI SONATA HYBRID 2021: [2.8990264092395734, 2.061410192222139, 0.0899805488717382]
|
||||
HYUNDAI TUCSON HYBRID 4TH GEN: [2.035545, 2.035545, 0.110272]
|
||||
JEEP GRAND CHEROKEE 2019: [1.7321233388827006, 1.289689569171081, 0.15046331002097185]
|
||||
JEEP GRAND CHEROKEE V6 2018: [1.8776598027756923, 1.4057367824262523, 0.11725947414922003]
|
||||
JEEP GRAND CHEROKEE 2019: [2.30972, 1.289689569171081, 0.117048]
|
||||
JEEP GRAND CHEROKEE V6 2018: [2.27116, 1.4057367824262523, 0.11725947414922003]
|
||||
KIA EV6 2022: [3.2, 2.093457, 0.05]
|
||||
KIA K5 2021: [2.405339728085138, 1.460032270828705, 0.11650989850813716]
|
||||
KIA NIRO EV 2020: [2.9215954981365337, 2.1500583840260044, 0.09236802474810267]
|
||||
|
||||
@@ -72,7 +72,7 @@ class CarInterface(CarInterfaceBase):
|
||||
tire_stiffness_factor = 0.5533
|
||||
ret.mass = 4481. * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max
|
||||
|
||||
elif candidate in (CAR.CHR, CAR.CHRH):
|
||||
elif candidate in (CAR.CHR, CAR.CHRH, CAR.CHR_TSS2):
|
||||
stop_and_go = True
|
||||
ret.wheelbase = 2.63906
|
||||
ret.steerRatio = 13.6
|
||||
|
||||
@@ -49,6 +49,7 @@ class CAR:
|
||||
CAMRY_TSS2 = "TOYOTA CAMRY 2021" # TSS 2.5
|
||||
CAMRYH_TSS2 = "TOYOTA CAMRY HYBRID 2021"
|
||||
CHR = "TOYOTA C-HR 2018"
|
||||
CHR_TSS2 = "TOYOTA C-HR 2021"
|
||||
CHRH = "TOYOTA C-HR HYBRID 2018"
|
||||
COROLLA = "TOYOTA COROLLA 2017"
|
||||
COROLLA_TSS2 = "TOYOTA COROLLA TSS2 2019"
|
||||
@@ -115,7 +116,8 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = {
|
||||
CAR.CAMRYH: ToyotaCarInfo("Toyota Camry Hybrid 2018-20", video_link="https://www.youtube.com/watch?v=Q2DYY0AWKgk"),
|
||||
CAR.CAMRY_TSS2: ToyotaCarInfo("Toyota Camry 2021-22", footnotes=[Footnote.CAMRY]),
|
||||
CAR.CAMRYH_TSS2: ToyotaCarInfo("Toyota Camry Hybrid 2021-23"),
|
||||
CAR.CHR: ToyotaCarInfo("Toyota C-HR 2017-21"),
|
||||
CAR.CHR: ToyotaCarInfo("Toyota C-HR 2017-20"),
|
||||
CAR.CHR_TSS2: ToyotaCarInfo("Toyota C-HR 2021"),
|
||||
CAR.CHRH: ToyotaCarInfo("Toyota C-HR Hybrid 2017-19"),
|
||||
CAR.COROLLA: ToyotaCarInfo("Toyota Corolla 2017-19"),
|
||||
CAR.COROLLA_TSS2: [
|
||||
@@ -633,6 +635,23 @@ FW_VERSIONS = {
|
||||
b'8646FF407000 ',
|
||||
],
|
||||
},
|
||||
CAR.CHR_TSS2: {
|
||||
(Ecu.abs, 0x7b0, None): [
|
||||
b'F1526F4270\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.eps, 0x7a1, None): [
|
||||
b'8965B10091\x00\x00\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.engine, 0x700, None): [
|
||||
b'\x0189663F459000\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdRadar, 0x750, 0xf): [
|
||||
b'\x018821FF410300\x00\x00\x00\x00',
|
||||
],
|
||||
(Ecu.fwdCamera, 0x750, 0x6d): [
|
||||
b'\x028646FF411100\x00\x00\x00\x008646GF409000\x00\x00\x00\x00',
|
||||
],
|
||||
},
|
||||
CAR.CHRH: {
|
||||
(Ecu.engine, 0x700, None): [
|
||||
b'\x0289663F405100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00',
|
||||
@@ -2030,6 +2049,7 @@ DBC = {
|
||||
CAR.LEXUS_RX_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
|
||||
CAR.LEXUS_RXH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'),
|
||||
CAR.CHR: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'),
|
||||
CAR.CHR_TSS2: dbc_dict('toyota_nodsu_pt_generated', None),
|
||||
CAR.CHRH: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'),
|
||||
CAR.CAMRY: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'),
|
||||
CAR.CAMRYH: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'),
|
||||
@@ -2072,7 +2092,7 @@ EPS_SCALE = defaultdict(lambda: 73, {CAR.PRIUS: 66, CAR.COROLLA: 88, CAR.LEXUS_I
|
||||
# Toyota/Lexus Safety Sense 2.0 and 2.5
|
||||
TSS2_CAR = {CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022,
|
||||
CAR.LEXUS_RX_TSS2, CAR.LEXUS_RXH_TSS2, CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2, CAR.PRIUS_TSS2, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2,
|
||||
CAR.MIRAI, CAR.LEXUS_NX_TSS2, CAR.LEXUS_NXH_TSS2, CAR.ALPHARD_TSS2, CAR.AVALON_TSS2, CAR.AVALONH_TSS2, CAR.ALPHARDH_TSS2}
|
||||
CAR.MIRAI, CAR.LEXUS_NX_TSS2, CAR.LEXUS_NXH_TSS2, CAR.ALPHARD_TSS2, CAR.AVALON_TSS2, CAR.AVALONH_TSS2, CAR.ALPHARDH_TSS2, CAR.CHR_TSS2}
|
||||
|
||||
NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CHRH, CAR.CAMRY, CAR.CAMRYH}
|
||||
|
||||
@@ -2080,7 +2100,7 @@ NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CHRH, CAR.CAMRY, CAR.CAMRYH}
|
||||
UNSUPPORTED_DSU_CAR = {CAR.LEXUS_IS, CAR.LEXUS_RC}
|
||||
|
||||
# these cars have a radar which sends ACC messages instead of the camera
|
||||
RADAR_ACC_CAR = {CAR.RAV4H_TSS2_2022, CAR.RAV4_TSS2_2022}
|
||||
RADAR_ACC_CAR = {CAR.RAV4H_TSS2_2022, CAR.RAV4_TSS2_2022, CAR.CHR_TSS2}
|
||||
|
||||
EV_HYBRID_CAR = {CAR.AVALONH_2019, CAR.AVALONH_TSS2, CAR.CAMRYH, CAR.CAMRYH_TSS2, CAR.CHRH, CAR.COROLLAH_TSS2, CAR.HIGHLANDERH, CAR.HIGHLANDERH_TSS2, CAR.PRIUS,
|
||||
CAR.PRIUS_V, CAR.RAV4H, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022, CAR.LEXUS_CTH, CAR.MIRAI, CAR.LEXUS_ESH, CAR.LEXUS_ESH_TSS2, CAR.LEXUS_NXH, CAR.LEXUS_RXH,
|
||||
|
||||
@@ -729,6 +729,7 @@ FW_VERSIONS = {
|
||||
},
|
||||
CAR.TIGUAN_MK2: {
|
||||
(Ecu.engine, 0x7e0, None): [
|
||||
b'\xf1\x8703N906026D \xf1\x893680',
|
||||
b'\xf1\x8704E906027NB\xf1\x899504',
|
||||
b'\xf1\x8704L906026EJ\xf1\x893661',
|
||||
b'\xf1\x8704L906027G \xf1\x899893',
|
||||
@@ -746,12 +747,14 @@ FW_VERSIONS = {
|
||||
b'\xf1\x870D9300043 \xf1\x895202',
|
||||
b'\xf1\x870DL300011N \xf1\x892001',
|
||||
b'\xf1\x870DL300011N \xf1\x892012',
|
||||
b'\xf1\x870DL300012P \xf1\x892103',
|
||||
b'\xf1\x870DL300013A \xf1\x893005',
|
||||
b'\xf1\x870DL300013G \xf1\x892119',
|
||||
b'\xf1\x870DL300013G \xf1\x892120',
|
||||
],
|
||||
(Ecu.srs, 0x715, None): [
|
||||
b'\xf1\x875Q0959655AR\xf1\x890317\xf1\x82\02331310031333334313132573732379333313100',
|
||||
b'\xf1\x875Q0959655BJ\xf1\x890336\xf1\x82\x1312110031333300314232583732379333423100',
|
||||
b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02316143231313500314641011750179333423100',
|
||||
b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02312110031333300314240583752379333423100',
|
||||
b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02331310031333336313140013950399333423100',
|
||||
@@ -763,6 +766,7 @@ FW_VERSIONS = {
|
||||
b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820529A6060603',
|
||||
b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A60604A1',
|
||||
b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6000600',
|
||||
b'\xf1\x875QF909144A \xf1\x895581\xf1\x82\x0571A60834A1',
|
||||
b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\00571A60634A1',
|
||||
b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60604A1',
|
||||
b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60604A1',
|
||||
@@ -771,6 +775,7 @@ FW_VERSIONS = {
|
||||
(Ecu.fwdRadar, 0x757, None): [
|
||||
b'\xf1\x872Q0907572AA\xf1\x890396',
|
||||
b'\xf1\x872Q0907572J \xf1\x890156',
|
||||
b'\xf1\x872Q0907572M \xf1\x890233',
|
||||
b'\xf1\x872Q0907572Q \xf1\x890342',
|
||||
b'\xf1\x872Q0907572R \xf1\x890372',
|
||||
b'\xf1\x872Q0907572T \xf1\x890383',
|
||||
|
||||
@@ -213,6 +213,7 @@ kj::Array<capnp::word> UbloxMsgParser::parse_gps_ephemeris(ubx_t::rxm_sfrbx_t *m
|
||||
eph.setAf1(subframe_1->af_1() * pow(2, -43));
|
||||
eph.setAf0(subframe_1->af_0() * pow(2, -31));
|
||||
eph.setSvHealth(subframe_1->sv_health());
|
||||
eph.setTowCount(subframe.how()->tow_count());
|
||||
iodc_lsb = subframe_1->iodc_lsb();
|
||||
}
|
||||
|
||||
@@ -374,6 +375,13 @@ kj::Array<capnp::word> UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_
|
||||
// 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);
|
||||
if (current_day > 1461) {
|
||||
// impossible day within last 4 year, reject ephemeris
|
||||
// TODO: check if this can be detected via hamming code
|
||||
LOGE("INVALID DATA: current day out of range: %d, %d", current_day, n_4);
|
||||
glonass_strings[msg->sv_id()].clear();
|
||||
return kj::Array<capnp::word>();
|
||||
}
|
||||
|
||||
uint16_t last_leap_year = 1996 + 4*(n_4-1);
|
||||
uint16_t days_till_this_year = (year - last_leap_year)*365;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3d8f2f9d53908e7a5496412956548dab1aa08ed0b5fb9d1c685f275369f07a57
|
||||
oid sha256:736ddc08497d7596bae4d9515a8efb996676be80e67a6d34d632bb8af2ed3fa9
|
||||
size 45962515
|
||||
|
||||
@@ -1 +1 @@
|
||||
2a255ecea3665a3d295210195fd8ef0791ab49a0
|
||||
ba947edbb131a2a36ced7c490dfcf3280ad5b167
|
||||
|
||||
@@ -1 +1 @@
|
||||
7c2a98f4a94b6dded6a90d8913ab85a63069c186
|
||||
a6b17ab903022b42677abe2968f3c6e50c331980
|
||||
|
||||
@@ -141,9 +141,9 @@ void Installer::cachedFetch(const QString &cache) {
|
||||
void Installer::readProgress() {
|
||||
const QVector<QPair<QString, int>> stages = {
|
||||
// prefix, weight in percentage
|
||||
{tr("Receiving objects: "), 91},
|
||||
{tr("Resolving deltas: "), 2},
|
||||
{tr("Updating files: "), 7},
|
||||
{"Receiving objects: ", 91},
|
||||
{"Resolving deltas: ", 2},
|
||||
{"Updating files: ", 7},
|
||||
};
|
||||
|
||||
auto line = QString(proc.readAllStandardError());
|
||||
|
||||
@@ -54,7 +54,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
|
||||
connect(targetBranchBtn, &ButtonControl::clicked, [=]() {
|
||||
auto current = params.get("GitBranch");
|
||||
QStringList branches = QString::fromStdString(params.get("UpdaterAvailableBranches")).split(",");
|
||||
for (QString b : {current.c_str(), "devel-staging", "devel", "master-ci", "master"}) {
|
||||
for (QString b : {current.c_str(), "devel-staging", "devel", "nightly", "master-ci", "master"}) {
|
||||
auto i = branches.indexOf(b);
|
||||
if (i >= 0) {
|
||||
branches.removeAt(i);
|
||||
|
||||
@@ -231,7 +231,7 @@ AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* par
|
||||
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});
|
||||
dm_img = loadPixmap("../assets/img_driver_face.png", {img_size + 5, img_size + 5});
|
||||
}
|
||||
|
||||
void AnnotatedCameraWidget::updateState(const UIState &s) {
|
||||
@@ -275,7 +275,7 @@ void AnnotatedCameraWidget::updateState(const UIState &s) {
|
||||
setProperty("speed", cur_speed);
|
||||
setProperty("setSpeed", set_speed);
|
||||
setProperty("speedUnit", s.scene.is_metric ? tr("km/h") : tr("mph"));
|
||||
setProperty("hideDM", cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE);
|
||||
setProperty("hideDM", (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE));
|
||||
setProperty("status", s.status);
|
||||
|
||||
// update engageability/experimental mode button
|
||||
@@ -286,6 +286,9 @@ void AnnotatedCameraWidget::updateState(const UIState &s) {
|
||||
setProperty("dmActive", sm["driverMonitoringState"].getDriverMonitoringState().getIsActiveMode());
|
||||
setProperty("rightHandDM", sm["driverMonitoringState"].getDriverMonitoringState().getIsRHD());
|
||||
}
|
||||
|
||||
// DM icon transition
|
||||
dm_fade_state = fmax(0.0, fmin(1.0, dm_fade_state+0.2*(0.5-(float)(dmActive))));
|
||||
}
|
||||
|
||||
void AnnotatedCameraWidget::drawHud(QPainter &p) {
|
||||
@@ -435,12 +438,6 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) {
|
||||
configFont(p, "Inter", 66, "Regular");
|
||||
drawText(p, rect().center().x(), 290, speedUnit, 200);
|
||||
|
||||
// dm icon
|
||||
if (!hideDM) {
|
||||
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);
|
||||
}
|
||||
p.restore();
|
||||
}
|
||||
|
||||
@@ -544,6 +541,49 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) {
|
||||
painter.restore();
|
||||
}
|
||||
|
||||
void AnnotatedCameraWidget::drawDriverState(QPainter &painter, const UIState *s) {
|
||||
const UIScene &scene = s->scene;
|
||||
|
||||
painter.save();
|
||||
|
||||
// base icon
|
||||
int x = rightHandDM ? rect().right() - (btn_size - 24) / 2 - (bdr_s * 2) : (btn_size - 24) / 2 + (bdr_s * 2);
|
||||
int y = rect().bottom() - footer_h / 2;
|
||||
float opacity = dmActive ? 0.65 : 0.2;
|
||||
drawIcon(painter, x, y, dm_img, blackColor(0), opacity);
|
||||
|
||||
// circle background
|
||||
painter.setOpacity(1.0);
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(blackColor(70));
|
||||
painter.drawEllipse(x - btn_size / 2, y - btn_size / 2, btn_size, btn_size);
|
||||
|
||||
// face
|
||||
QPointF face_kpts_draw[std::size(default_face_kpts_3d)];
|
||||
float kp;
|
||||
for (int i = 0; i < std::size(default_face_kpts_3d); ++i) {
|
||||
kp = (scene.face_kpts_draw[i].v[2] - 8) / 120 + 1.0;
|
||||
face_kpts_draw[i] = QPointF(scene.face_kpts_draw[i].v[0] * kp + x, scene.face_kpts_draw[i].v[1] * kp + y);
|
||||
}
|
||||
|
||||
painter.setPen(QPen(QColor::fromRgbF(1.0, 1.0, 1.0, opacity), 5.2, Qt::SolidLine, Qt::RoundCap));
|
||||
painter.drawPolyline(face_kpts_draw, std::size(default_face_kpts_3d));
|
||||
|
||||
// tracking arcs
|
||||
const int arc_l = 133;
|
||||
const float arc_t_default = 6.7;
|
||||
const float arc_t_extend = 12.0;
|
||||
QColor arc_color = QColor::fromRgbF(0.09, 0.945, 0.26, 0.4*(1.0-dm_fade_state)*(s->engaged()));
|
||||
float delta_x = -scene.driver_pose_sins[1] * arc_l / 2;
|
||||
float delta_y = -scene.driver_pose_sins[0] * arc_l / 2;
|
||||
painter.setPen(QPen(arc_color, arc_t_default+arc_t_extend*fmin(1.0, scene.driver_pose_diff[1] * 5.0), Qt::SolidLine, Qt::RoundCap));
|
||||
painter.drawArc(QRectF(std::fmin(x + delta_x, x), y - arc_l / 2, fabs(delta_x), arc_l), (scene.driver_pose_sins[1]>0 ? 90 : -90) * 16, 180 * 16);
|
||||
painter.setPen(QPen(arc_color, arc_t_default+arc_t_extend*fmin(1.0, scene.driver_pose_diff[0] * 5.0), Qt::SolidLine, Qt::RoundCap));
|
||||
painter.drawArc(QRectF(x - arc_l / 2, std::fmin(y + delta_y, y), arc_l, fabs(delta_y)), (scene.driver_pose_sins[0]>0 ? 0 : 180) * 16, 180 * 16);
|
||||
|
||||
painter.restore();
|
||||
}
|
||||
|
||||
void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd) {
|
||||
painter.save();
|
||||
|
||||
@@ -654,6 +694,12 @@ void AnnotatedCameraWidget::paintGL() {
|
||||
}
|
||||
}
|
||||
|
||||
// DMoji
|
||||
if (!hideDM && (sm.rcv_frame("driverStateV2") > s->scene.started_frame)) {
|
||||
update_dmonitoring(s, sm["driverStateV2"].getDriverStateV2(), dm_fade_state, rightHandDM);
|
||||
drawDriverState(painter, s);
|
||||
}
|
||||
|
||||
drawHud(painter);
|
||||
|
||||
double cur_draw_t = millis_since_boot();
|
||||
|
||||
@@ -80,6 +80,7 @@ private:
|
||||
bool dmActive = false;
|
||||
bool hideDM = false;
|
||||
bool rightHandDM = false;
|
||||
float dm_fade_state = 1.0;
|
||||
bool has_us_speed_limit = false;
|
||||
bool has_eu_speed_limit = false;
|
||||
bool v_ego_cluster_seen = false;
|
||||
@@ -97,6 +98,7 @@ protected:
|
||||
void drawLaneLines(QPainter &painter, const UIState *s);
|
||||
void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd);
|
||||
void drawHud(QPainter &p);
|
||||
void drawDriverState(QPainter &painter, const UIState *s);
|
||||
inline QColor redColor(int alpha = 255) { return QColor(201, 34, 49, alpha); }
|
||||
inline QColor whiteColor(int alpha = 255) { return QColor(255, 255, 255, alpha); }
|
||||
inline QColor blackColor(int alpha = 255) { return QColor(0, 0, 0, alpha); }
|
||||
|
||||
+33
-106
@@ -20,6 +20,17 @@
|
||||
const std::string USER_AGENT = "AGNOSSetup-";
|
||||
const QString DASHCAM_URL = "https://dashcam.comma.ai";
|
||||
|
||||
bool is_elf(char *fname) {
|
||||
FILE *fp = fopen(fname, "rb");
|
||||
if (fp == NULL) {
|
||||
return false;
|
||||
}
|
||||
char buf[4];
|
||||
size_t n = fread(buf, 1, 4, fp);
|
||||
fclose(fp);
|
||||
return n == 4 && buf[0] == 0x7f && buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'F';
|
||||
}
|
||||
|
||||
void Setup::download(QString url) {
|
||||
CURL *curl = curl_easy_init();
|
||||
if (!curl) {
|
||||
@@ -29,6 +40,9 @@ void Setup::download(QString url) {
|
||||
|
||||
auto version = util::read_file("/VERSION");
|
||||
|
||||
struct curl_slist *list = NULL;
|
||||
list = curl_slist_append(list, ("X-openpilot-serial: " + Hardware::get_serial()).c_str());
|
||||
|
||||
char tmpfile[] = "/tmp/installer_XXXXXX";
|
||||
FILE *fp = fdopen(mkstemp(tmpfile), "w");
|
||||
|
||||
@@ -38,18 +52,19 @@ void Setup::download(QString url) {
|
||||
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, (USER_AGENT + version).c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
|
||||
|
||||
int ret = curl_easy_perform(curl);
|
||||
|
||||
long res_status = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &res_status);
|
||||
if (ret == CURLE_OK && res_status == 200) {
|
||||
if (ret == CURLE_OK && res_status == 200 && is_elf(tmpfile)) {
|
||||
rename(tmpfile, "/tmp/installer");
|
||||
emit finished(true);
|
||||
} else {
|
||||
emit finished(false);
|
||||
}
|
||||
|
||||
curl_slist_free_all(list);
|
||||
curl_easy_cleanup(curl);
|
||||
fclose(fp);
|
||||
}
|
||||
@@ -170,7 +185,20 @@ QWidget * Setup::network_setup() {
|
||||
QPushButton *cont = new QPushButton();
|
||||
cont->setObjectName("navBtn");
|
||||
cont->setProperty("primary", true);
|
||||
QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage);
|
||||
QObject::connect(cont, &QPushButton::clicked, [=]() {
|
||||
auto w = currentWidget();
|
||||
QTimer::singleShot(0, [=]() {
|
||||
setCurrentWidget(downloading_widget);
|
||||
});
|
||||
QString url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software"));
|
||||
if (!url.isEmpty()) {
|
||||
QTimer::singleShot(1000, this, [=]() {
|
||||
download(url);
|
||||
});
|
||||
} else {
|
||||
setCurrentWidget(w);
|
||||
}
|
||||
});
|
||||
blayout->addWidget(cont);
|
||||
|
||||
// setup timer for testing internet connection
|
||||
@@ -178,8 +206,8 @@ QWidget * Setup::network_setup() {
|
||||
QObject::connect(request, &HttpRequest::requestDone, [=](const QString &, bool success) {
|
||||
cont->setEnabled(success);
|
||||
if (success) {
|
||||
const bool cell = networking->wifi->currentNetworkType() == NetworkType::CELL;
|
||||
cont->setText(cell ? tr("Continue without Wi-Fi") : tr("Continue"));
|
||||
const bool wifi = networking->wifi->currentNetworkType() == NetworkType::WIFI;
|
||||
cont->setText(wifi ? tr("Continue") : tr("Continue without Wi-Fi"));
|
||||
} else {
|
||||
cont->setText(tr("Waiting for internet"));
|
||||
}
|
||||
@@ -197,106 +225,6 @@ QWidget * Setup::network_setup() {
|
||||
return widget;
|
||||
}
|
||||
|
||||
QWidget * radio_button(QString title, QButtonGroup *group) {
|
||||
QPushButton *btn = new QPushButton(title);
|
||||
btn->setCheckable(true);
|
||||
group->addButton(btn);
|
||||
btn->setStyleSheet(R"(
|
||||
QPushButton {
|
||||
height: 230;
|
||||
padding-left: 100px;
|
||||
padding-right: 100px;
|
||||
text-align: left;
|
||||
font-size: 80px;
|
||||
font-weight: 400;
|
||||
border-radius: 10px;
|
||||
background-color: #4F4F4F;
|
||||
}
|
||||
QPushButton:checked {
|
||||
background-color: #465BEA;
|
||||
}
|
||||
)");
|
||||
|
||||
// checkmark icon
|
||||
QPixmap pix(":/img_circled_check.svg");
|
||||
btn->setIcon(pix);
|
||||
btn->setIconSize(QSize(0, 0));
|
||||
btn->setLayoutDirection(Qt::RightToLeft);
|
||||
QObject::connect(btn, &QPushButton::toggled, [=](bool checked) {
|
||||
btn->setIconSize(checked ? QSize(104, 104) : QSize(0, 0));
|
||||
});
|
||||
return btn;
|
||||
}
|
||||
|
||||
QWidget * Setup::software_selection() {
|
||||
QWidget *widget = new QWidget();
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(widget);
|
||||
main_layout->setContentsMargins(55, 50, 55, 50);
|
||||
main_layout->setSpacing(0);
|
||||
|
||||
// title
|
||||
QLabel *title = new QLabel(tr("Choose Software to Install"));
|
||||
title->setStyleSheet("font-size: 90px; font-weight: 500;");
|
||||
main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
|
||||
|
||||
main_layout->addSpacing(50);
|
||||
|
||||
// dashcam + custom radio buttons
|
||||
QButtonGroup *group = new QButtonGroup(widget);
|
||||
group->setExclusive(true);
|
||||
|
||||
QWidget *dashcam = radio_button(tr("Dashcam"), group);
|
||||
main_layout->addWidget(dashcam);
|
||||
|
||||
main_layout->addSpacing(30);
|
||||
|
||||
QWidget *custom = radio_button(tr("Custom Software"), group);
|
||||
main_layout->addWidget(custom);
|
||||
|
||||
main_layout->addStretch();
|
||||
|
||||
// back + continue buttons
|
||||
QHBoxLayout *blayout = new QHBoxLayout;
|
||||
main_layout->addLayout(blayout);
|
||||
blayout->setSpacing(50);
|
||||
|
||||
QPushButton *back = new QPushButton(tr("Back"));
|
||||
back->setObjectName("navBtn");
|
||||
QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
|
||||
blayout->addWidget(back);
|
||||
|
||||
QPushButton *cont = new QPushButton(tr("Continue"));
|
||||
cont->setObjectName("navBtn");
|
||||
cont->setEnabled(false);
|
||||
cont->setProperty("primary", true);
|
||||
blayout->addWidget(cont);
|
||||
|
||||
QObject::connect(cont, &QPushButton::clicked, [=]() {
|
||||
auto w = currentWidget();
|
||||
QTimer::singleShot(0, [=]() {
|
||||
setCurrentWidget(downloading_widget);
|
||||
});
|
||||
QString url = DASHCAM_URL;
|
||||
if (group->checkedButton() != dashcam) {
|
||||
url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software"));
|
||||
}
|
||||
if (!url.isEmpty()) {
|
||||
QTimer::singleShot(1000, this, [=]() {
|
||||
download(url);
|
||||
});
|
||||
} else {
|
||||
setCurrentWidget(w);
|
||||
}
|
||||
});
|
||||
|
||||
connect(group, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked), [=](QAbstractButton *btn) {
|
||||
btn->setChecked(true);
|
||||
cont->setEnabled(true);
|
||||
});
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
QWidget * Setup::downloading() {
|
||||
QWidget *widget = new QWidget();
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(widget);
|
||||
@@ -372,7 +300,6 @@ Setup::Setup(QWidget *parent) : QStackedWidget(parent) {
|
||||
|
||||
addWidget(getting_started());
|
||||
addWidget(network_setup());
|
||||
addWidget(software_selection());
|
||||
|
||||
downloading_widget = downloading();
|
||||
addWidget(downloading_widget);
|
||||
|
||||
@@ -14,7 +14,6 @@ private:
|
||||
QWidget *low_voltage();
|
||||
QWidget *getting_started();
|
||||
QWidget *network_setup();
|
||||
QWidget *software_selection();
|
||||
QWidget *downloading();
|
||||
QWidget *download_failed();
|
||||
|
||||
|
||||
@@ -312,18 +312,6 @@
|
||||
<source>Installing...</source>
|
||||
<translation>Installiere...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Receiving objects: </source>
|
||||
<translation>Empfange Objekte: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Resolving deltas: </source>
|
||||
<translation>Unterschiede verarbeiten: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Updating files: </source>
|
||||
<translation>Dateien aktualisieren: </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MapETA</name>
|
||||
@@ -686,18 +674,6 @@ location set</source>
|
||||
<source>Waiting for internet</source>
|
||||
<translation>Auf Internet warten</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Choose Software to Install</source>
|
||||
<translation>Software zum installieren auswählen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Dashcam</source>
|
||||
<translation>Dashcam</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Custom Software</source>
|
||||
<translation>Spezifische Software</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enter URL</source>
|
||||
<translation>URL eingeben</translation>
|
||||
|
||||
@@ -311,18 +311,6 @@
|
||||
<source>Installing...</source>
|
||||
<translation>インストールしています...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Receiving objects: </source>
|
||||
<translation>オブジェクトをダウンロードしています: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Resolving deltas: </source>
|
||||
<translation>デルタを解決しています: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Updating files: </source>
|
||||
<translation>ファイルを更新しています: </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MapETA</name>
|
||||
@@ -684,18 +672,6 @@ location set</source>
|
||||
<source>Waiting for internet</source>
|
||||
<translation>インターネット接続を待機中</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Choose Software to Install</source>
|
||||
<translation>インストールするソフトウェアを選択してください</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Dashcam</source>
|
||||
<translation>ドライブレコーダー</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Custom Software</source>
|
||||
<translation>カスタムソフトウェア</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enter URL</source>
|
||||
<translation>URL を入力</translation>
|
||||
|
||||
@@ -311,18 +311,6 @@
|
||||
<source>Installing...</source>
|
||||
<translation>설치중...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Receiving objects: </source>
|
||||
<translation>수신중: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Resolving deltas: </source>
|
||||
<translation>델타병합: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Updating files: </source>
|
||||
<translation>파일갱신: </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MapETA</name>
|
||||
@@ -684,18 +672,6 @@ location set</source>
|
||||
<source>Waiting for internet</source>
|
||||
<translation>네트워크 접속을 기다립니다</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Choose Software to Install</source>
|
||||
<translation>설치할 소프트웨어를 선택하세요</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Dashcam</source>
|
||||
<translation>Dashcam</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Custom Software</source>
|
||||
<translation>Custom Software</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enter URL</source>
|
||||
<translation>URL 입력</translation>
|
||||
|
||||
@@ -312,18 +312,6 @@
|
||||
<source>Installing...</source>
|
||||
<translation>Instalando...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Receiving objects: </source>
|
||||
<translation>Recebendo objetos: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Resolving deltas: </source>
|
||||
<translation>Resolvendo deltas: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Updating files: </source>
|
||||
<translation>Atualizando arquivos: </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MapETA</name>
|
||||
@@ -688,18 +676,6 @@ trabalho definido</translation>
|
||||
<source>Waiting for internet</source>
|
||||
<translation>Esperando pela internet</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Choose Software to Install</source>
|
||||
<translation>Escolher Software para Instalar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Dashcam</source>
|
||||
<translation>Dashcam</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Custom Software</source>
|
||||
<translation>Sofware Customizado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enter URL</source>
|
||||
<translation>Preencher URL</translation>
|
||||
|
||||
@@ -311,18 +311,6 @@
|
||||
<source>Installing...</source>
|
||||
<translation>正在安装……</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Receiving objects: </source>
|
||||
<translation>正在接收: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Resolving deltas: </source>
|
||||
<translation>正在处理: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Updating files: </source>
|
||||
<translation>正在更新文件: </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MapETA</name>
|
||||
@@ -682,18 +670,6 @@ location set</source>
|
||||
<source>Waiting for internet</source>
|
||||
<translation>等待网络连接</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Choose Software to Install</source>
|
||||
<translation>选择要安装的软件</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Dashcam</source>
|
||||
<translation>Dashcam(行车记录仪)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Custom Software</source>
|
||||
<translation>自定义软件</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enter URL</source>
|
||||
<translation>输入网址</translation>
|
||||
|
||||
@@ -311,18 +311,6 @@
|
||||
<source>Installing...</source>
|
||||
<translation>安裝中…</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Receiving objects: </source>
|
||||
<translation>接收對象: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Resolving deltas: </source>
|
||||
<translation>分析差異: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Updating files: </source>
|
||||
<translation>更新檔案: </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MapETA</name>
|
||||
@@ -684,18 +672,6 @@ location set</source>
|
||||
<source>Waiting for internet</source>
|
||||
<translation>連接至網路中</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Choose Software to Install</source>
|
||||
<translation>選擇要安裝的軟體</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Dashcam</source>
|
||||
<translation>行車記錄器</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Custom Software</source>
|
||||
<translation>定制的軟體</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enter URL</source>
|
||||
<translation>輸入網址</translation>
|
||||
|
||||
+34
-1
@@ -112,6 +112,39 @@ void update_model(UIState *s, const cereal::ModelDataV2::Reader &model) {
|
||||
update_line_data(s, model_position, 0.9, 1.22, &scene.track_vertices, max_idx, false);
|
||||
}
|
||||
|
||||
void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd) {
|
||||
UIScene &scene = s->scene;
|
||||
const auto driver_orient = is_rhd ? driverstate.getRightDriverData().getFaceOrientation() : driverstate.getLeftDriverData().getFaceOrientation();
|
||||
for (int i = 0; i < std::size(scene.driver_pose_vals); i++) {
|
||||
float v_this = (i == 0 ? (driver_orient[i] < 0 ? 0.7 : 0.9) : 0.4) * driver_orient[i];
|
||||
scene.driver_pose_diff[i] = fabs(scene.driver_pose_vals[i] - v_this);
|
||||
scene.driver_pose_vals[i] = 0.8 * v_this + (1 - 0.8) * scene.driver_pose_vals[i];
|
||||
scene.driver_pose_sins[i] = sinf(scene.driver_pose_vals[i]*(1.0-dm_fade_state));
|
||||
scene.driver_pose_coss[i] = cosf(scene.driver_pose_vals[i]*(1.0-dm_fade_state));
|
||||
}
|
||||
|
||||
const mat3 r_xyz = (mat3){{
|
||||
scene.driver_pose_coss[1]*scene.driver_pose_coss[2],
|
||||
scene.driver_pose_coss[1]*scene.driver_pose_sins[2],
|
||||
-scene.driver_pose_sins[1],
|
||||
|
||||
-scene.driver_pose_sins[0]*scene.driver_pose_sins[1]*scene.driver_pose_coss[2] - scene.driver_pose_coss[0]*scene.driver_pose_sins[2],
|
||||
-scene.driver_pose_sins[0]*scene.driver_pose_sins[1]*scene.driver_pose_sins[2] + scene.driver_pose_coss[0]*scene.driver_pose_coss[2],
|
||||
-scene.driver_pose_sins[0]*scene.driver_pose_coss[1],
|
||||
|
||||
scene.driver_pose_coss[0]*scene.driver_pose_sins[1]*scene.driver_pose_coss[2] - scene.driver_pose_sins[0]*scene.driver_pose_sins[2],
|
||||
scene.driver_pose_coss[0]*scene.driver_pose_sins[1]*scene.driver_pose_sins[2] + scene.driver_pose_sins[0]*scene.driver_pose_coss[2],
|
||||
scene.driver_pose_coss[0]*scene.driver_pose_coss[1],
|
||||
}};
|
||||
|
||||
// transform vertices
|
||||
for (int kpi = 0; kpi < std::size(default_face_kpts_3d); kpi++) {
|
||||
vec3 kpt_this = default_face_kpts_3d[kpi];
|
||||
kpt_this = matvecmul3(r_xyz, kpt_this);
|
||||
scene.face_kpts_draw[kpi] = (vec3){{(float)kpt_this.v[0], (float)kpt_this.v[1], (float)(kpt_this.v[2] * (1.0-dm_fade_state) + 8 * dm_fade_state)}};
|
||||
}
|
||||
}
|
||||
|
||||
static void update_sockets(UIState *s) {
|
||||
s->sm->update(0);
|
||||
}
|
||||
@@ -213,7 +246,7 @@ void UIState::updateStatus() {
|
||||
UIState::UIState(QObject *parent) : QObject(parent) {
|
||||
sm = std::make_unique<SubMaster, const std::initializer_list<const char *>>({
|
||||
"modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState",
|
||||
"pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman",
|
||||
"pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2",
|
||||
"wideRoadCameraState", "managerState", "navInstruction", "navRoute", "gnssMeasurements",
|
||||
});
|
||||
|
||||
|
||||
@@ -25,6 +25,16 @@ typedef cereal::CarControl::HUDControl::AudibleAlert AudibleAlert;
|
||||
|
||||
const mat3 DEFAULT_CALIBRATION = {{ 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0 }};
|
||||
|
||||
const vec3 default_face_kpts_3d[] = {
|
||||
{-5.98, -51.20, 8.00}, {-17.64, -49.14, 8.00}, {-23.81, -46.40, 8.00}, {-29.98, -40.91, 8.00}, {-32.04, -37.49, 8.00},
|
||||
{-34.10, -32.00, 8.00}, {-36.16, -21.03, 8.00}, {-36.16, 6.40, 8.00}, {-35.47, 10.51, 8.00}, {-32.73, 19.43, 8.00},
|
||||
{-29.30, 26.29, 8.00}, {-24.50, 33.83, 8.00}, {-19.01, 41.37, 8.00}, {-14.21, 46.17, 8.00}, {-12.16, 47.54, 8.00},
|
||||
{-4.61, 49.60, 8.00}, {4.99, 49.60, 8.00}, {12.53, 47.54, 8.00}, {14.59, 46.17, 8.00}, {19.39, 41.37, 8.00},
|
||||
{24.87, 33.83, 8.00}, {29.67, 26.29, 8.00}, {33.10, 19.43, 8.00}, {35.84, 10.51, 8.00}, {36.53, 6.40, 8.00},
|
||||
{36.53, -21.03, 8.00}, {34.47, -32.00, 8.00}, {32.42, -37.49, 8.00}, {30.36, -40.91, 8.00}, {24.19, -46.40, 8.00},
|
||||
{18.02, -49.14, 8.00}, {6.36, -51.20, 8.00}, {-5.98, -51.20, 8.00},
|
||||
};
|
||||
|
||||
struct Alert {
|
||||
QString text1;
|
||||
QString text2;
|
||||
@@ -103,6 +113,13 @@ typedef struct UIScene {
|
||||
// lead
|
||||
QPointF lead_vertices[2];
|
||||
|
||||
// DMoji state
|
||||
float driver_pose_vals[3];
|
||||
float driver_pose_diff[3];
|
||||
float driver_pose_sins[3];
|
||||
float driver_pose_coss[3];
|
||||
vec3 face_kpts_draw[std::size(default_face_kpts_3d)];
|
||||
|
||||
float light_sensor;
|
||||
bool started, ignition, is_metric, map_on_left, longitudinal_control;
|
||||
uint64_t started_frame;
|
||||
@@ -183,6 +200,7 @@ public slots:
|
||||
void ui_update_params(UIState *s);
|
||||
int get_path_length_idx(const cereal::ModelDataV2::XYZTData::Reader &line, const float path_height);
|
||||
void update_model(UIState *s, const cereal::ModelDataV2::Reader &model);
|
||||
void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd);
|
||||
void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::ModelDataV2::XYZTData::Reader &line);
|
||||
void update_line_data(const UIState *s, const cereal::ModelDataV2::XYZTData::Reader &line,
|
||||
float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert);
|
||||
|
||||
@@ -16,6 +16,8 @@ public:
|
||||
static int get_voltage() { return 0; };
|
||||
static int get_current() { return 0; };
|
||||
|
||||
static std::string get_serial() { return "cccccc"; }
|
||||
|
||||
static void reboot() {}
|
||||
static void poweroff() {}
|
||||
static void set_brightness(int percent) {}
|
||||
|
||||
@@ -21,6 +21,23 @@ public:
|
||||
static int get_voltage() { return std::atoi(util::read_file("/sys/class/hwmon/hwmon1/in1_input").c_str()); };
|
||||
static int get_current() { return std::atoi(util::read_file("/sys/class/hwmon/hwmon1/curr1_input").c_str()); };
|
||||
|
||||
static std::string get_serial() {
|
||||
static std::string serial("");
|
||||
if (serial.empty()) {
|
||||
std::ifstream stream("/proc/cmdline");
|
||||
std::string cmdline;
|
||||
std::getline(stream, cmdline);
|
||||
|
||||
auto start = cmdline.find("serialno=");
|
||||
if (start == std::string::npos) {
|
||||
serial = "cccccc";
|
||||
} else {
|
||||
auto end = cmdline.find(" ", start + 9);
|
||||
serial = cmdline.substr(start + 9, end - start - 9);
|
||||
}
|
||||
}
|
||||
return serial;
|
||||
}
|
||||
|
||||
static void reboot() { std::system("sudo reboot"); };
|
||||
static void poweroff() { std::system("sudo poweroff"); };
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <QScrollBar>
|
||||
#include <QToolTip>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "tools/cabana/commands.h"
|
||||
#include "tools/cabana/streams/abstractstream.h"
|
||||
|
||||
@@ -173,7 +175,6 @@ void BinaryViewModel::refresh() {
|
||||
if ((dbc_msg = dbc()->msg(msg_id))) {
|
||||
row_count = dbc_msg->size;
|
||||
items.resize(row_count * column_count);
|
||||
int i = 0;
|
||||
for (auto sig : dbc_msg->getSignals()) {
|
||||
auto [start, end] = getSignalRange(sig);
|
||||
for (int j = start; j <= end; ++j) {
|
||||
@@ -185,10 +186,9 @@ void BinaryViewModel::refresh() {
|
||||
}
|
||||
if (j == start) sig->is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true;
|
||||
if (j == end) sig->is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true;
|
||||
items[idx].bg_color = getColor(i);
|
||||
items[idx].bg_color = getColor(sig);
|
||||
items[idx].sigs.push_back(sig);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
} else {
|
||||
row_count = can->lastMessage(msg_id).dat.size();
|
||||
@@ -214,6 +214,19 @@ void BinaryViewModel::updateState() {
|
||||
for (int i = 0; i < binary.size(); ++i) {
|
||||
for (int j = 0; j < 8; ++j) {
|
||||
items[i * column_count + j].val = ((binary[i] >> (7 - j)) & 1) != 0 ? '1' : '0';
|
||||
|
||||
// Bit update frequency based highlighting
|
||||
bool has_signal = items[i * column_count + j].sigs.size() > 0;
|
||||
double offset = has_signal ? 50 : 0;
|
||||
|
||||
double min_f = last_msg.bit_change_counts[i][7 - j] == 0 ? offset : offset + 25;
|
||||
double max_f = 255.0;
|
||||
|
||||
double factor = 0.25;
|
||||
double scaler = max_f / log2(1.0 + factor);
|
||||
|
||||
double alpha = std::clamp(offset + log2(1.0 + factor * (double)last_msg.bit_change_counts[i][7 - j] / (double)last_msg.count) * scaler, min_f, max_f);
|
||||
items[i * column_count + j].bg_color.setAlpha(alpha);
|
||||
}
|
||||
hex[0] = toHex(binary[i] >> 4);
|
||||
hex[1] = toHex(binary[i] & 0xf);
|
||||
@@ -266,10 +279,18 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
|
||||
painter->setPen(option.palette.color(QPalette::BrightText));
|
||||
} else if (!item->sigs.isEmpty() && (!bin_view->selectionModel()->hasSelection() || !item->sigs.contains(bin_view->resize_sig))) {
|
||||
bool sig_hovered = item->sigs.contains(bin_view->hovered_sig);
|
||||
painter->fillRect(option.rect, sig_hovered ? item->bg_color.darker(125) : item->bg_color); // 4/5x brightness
|
||||
int min_alpha = item->sigs.contains(bin_view->hovered_sig) ? 255 : 50;
|
||||
QColor bg = item->bg_color;
|
||||
if (bg.alpha() < min_alpha) {
|
||||
bg.setAlpha(min_alpha);
|
||||
}
|
||||
painter->fillRect(option.rect, sig_hovered ? bg.darker(125) : bg); // 4/5x brightness
|
||||
painter->setPen(sig_hovered ? option.palette.color(QPalette::BrightText) : Qt::black);
|
||||
} else {
|
||||
painter->fillRect(option.rect, item->bg_color);
|
||||
}
|
||||
|
||||
|
||||
painter->drawText(option.rect, Qt::AlignCenter, item->val);
|
||||
if (item->is_msb || item->is_lsb) {
|
||||
painter->setFont(small_font);
|
||||
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
}
|
||||
|
||||
struct Item {
|
||||
QColor bg_color = QApplication::style()->standardPalette().color(QPalette::Base);
|
||||
QColor bg_color = QColor(102, 86, 169, 0);
|
||||
bool is_msb = false;
|
||||
bool is_lsb = false;
|
||||
QString val = "-";
|
||||
|
||||
+183
-149
@@ -9,8 +9,8 @@
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QRubberBand>
|
||||
#include <QPushButton>
|
||||
#include <QToolBar>
|
||||
#include <QToolButton>
|
||||
#include <QToolTip>
|
||||
#include <QtConcurrent>
|
||||
|
||||
@@ -36,17 +36,19 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
|
||||
stretch_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
toolbar->addWidget(stretch_label);
|
||||
|
||||
toolbar->addWidget(new QLabel(tr("Range:")));
|
||||
toolbar->addWidget(range_lb = new QLabel(this));
|
||||
range_lb_action = toolbar->addWidget(range_lb = new QLabel(this));
|
||||
range_slider = new QSlider(Qt::Horizontal, this);
|
||||
range_slider->setToolTip(tr("Set the chart range"));
|
||||
range_slider->setRange(1, settings.max_cached_minutes * 60);
|
||||
range_slider->setSingleStep(1);
|
||||
range_slider->setPageStep(60); // 1 min
|
||||
toolbar->addWidget(range_slider);
|
||||
range_slider_action = toolbar->addWidget(range_slider);
|
||||
|
||||
reset_zoom_action = toolbar->addWidget(reset_zoom_btn = new QToolButton());
|
||||
reset_zoom_btn->setIcon(utils::icon("zoom-out"));
|
||||
reset_zoom_btn->setToolTip(tr("Reset zoom"));
|
||||
reset_zoom_btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
|
||||
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(utils::icon("x"), "");
|
||||
remove_all_btn->setToolTip(tr("Remove all charts"));
|
||||
dock_btn = toolbar->addAction("");
|
||||
@@ -84,7 +86,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
|
||||
QObject::connect(range_slider, &QSlider::valueChanged, this, &ChartsWidget::setMaxChartRange);
|
||||
QObject::connect(new_plot_btn, &QAction::triggered, this, &ChartsWidget::newChart);
|
||||
QObject::connect(remove_all_btn, &QAction::triggered, this, &ChartsWidget::removeAll);
|
||||
QObject::connect(reset_zoom_btn, &QAction::triggered, this, &ChartsWidget::zoomReset);
|
||||
QObject::connect(reset_zoom_btn, &QToolButton::clicked, this, &ChartsWidget::zoomReset);
|
||||
QObject::connect(columns_cb, SIGNAL(activated(int)), SLOT(setColumnCount(int)));
|
||||
QObject::connect(&settings, &Settings::changed, this, &ChartsWidget::settingChanged);
|
||||
QObject::connect(dock_btn, &QAction::triggered, [this]() {
|
||||
@@ -149,7 +151,6 @@ void ChartsWidget::updateState() {
|
||||
for (auto c : charts) {
|
||||
c->updatePlot(cur_sec, range.first, range.second);
|
||||
}
|
||||
alignCharts();
|
||||
charts_layout->parentWidget()->setUpdatesEnabled(true);
|
||||
}
|
||||
|
||||
@@ -160,12 +161,15 @@ 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')));
|
||||
title_label->setText(tr("Charts: %1").arg(charts.size()));
|
||||
dock_btn->setIcon(utils::icon(docking ? "arrow-up-right" : "arrow-down-left"));
|
||||
dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts"));
|
||||
range_lb->setText(QString("Range: %1:%2 ").arg(max_chart_range / 60, 2, 10, QLatin1Char('0')).arg(max_chart_range % 60, 2, 10, QLatin1Char('0')));
|
||||
range_lb_action->setVisible(!is_zoomed);
|
||||
range_slider_action->setVisible(!is_zoomed);
|
||||
reset_zoom_action->setVisible(is_zoomed);
|
||||
reset_zoom_btn->setText(is_zoomed ? tr("Zoomin: %1-%2").arg(zoomed_range.first, 0, 'f', 1).arg(zoomed_range.second, 0, 'f', 1) : "");
|
||||
remove_all_btn->setEnabled(!charts.isEmpty());
|
||||
reset_zoom_btn->setEnabled(is_zoomed);
|
||||
dock_btn->setIcon(utils::icon(docking ? "arrow-up-right-square" : "arrow-down-left-square"));
|
||||
dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts"));
|
||||
}
|
||||
|
||||
void ChartsWidget::settingChanged() {
|
||||
@@ -186,13 +190,14 @@ ChartView *ChartsWidget::createChart() {
|
||||
auto chart = new ChartView(this);
|
||||
chart->setFixedHeight(settings.chart_height);
|
||||
chart->setMinimumWidth(CHART_MIN_WIDTH);
|
||||
chart->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
chart->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
|
||||
chart->chart()->setTheme(use_dark_theme ? QChart::QChart::ChartThemeDark : QChart::ChartThemeLight);
|
||||
QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); });
|
||||
QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn);
|
||||
QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset);
|
||||
QObject::connect(chart, &ChartView::seriesRemoved, this, &ChartsWidget::seriesChanged);
|
||||
QObject::connect(chart, &ChartView::seriesAdded, this, &ChartsWidget::seriesChanged);
|
||||
QObject::connect(chart, &ChartView::axisYLabelWidthChanged, this, &ChartsWidget::alignCharts);
|
||||
charts.push_back(chart);
|
||||
updateLayout();
|
||||
return chart;
|
||||
@@ -231,14 +236,14 @@ void ChartsWidget::updateLayout() {
|
||||
columns_cb_action->setVisible(show_column_cb);
|
||||
|
||||
n = std::min(column_count, n);
|
||||
if (charts.size() != charts_layout->count() || n != charts_layout->columnCount()) {
|
||||
if (charts.size() != charts_layout->count() || n != current_column_count) {
|
||||
current_column_count = n;
|
||||
charts_layout->parentWidget()->setUpdatesEnabled(false);
|
||||
for (int i = 0; i < charts.size(); ++i) {
|
||||
charts_layout->addWidget(charts[charts.size() - i - 1], i / n, i % n);
|
||||
}
|
||||
QTimer::singleShot(0, [this]() { charts_layout->parentWidget()->setUpdatesEnabled(true); });
|
||||
}
|
||||
alignCharts(true);
|
||||
}
|
||||
|
||||
void ChartsWidget::resizeEvent(QResizeEvent *event) {
|
||||
@@ -247,12 +252,14 @@ void ChartsWidget::resizeEvent(QResizeEvent *event) {
|
||||
}
|
||||
|
||||
void ChartsWidget::newChart() {
|
||||
SeriesSelector dlg(this);
|
||||
SeriesSelector dlg(tr("New Chart"), this);
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
QList<QStringList> series_list = dlg.series();
|
||||
if (!series_list.isEmpty()) {
|
||||
auto items = dlg.seletedItems();
|
||||
if (!items.isEmpty()) {
|
||||
auto c = createChart();
|
||||
c->addSeries(series_list);
|
||||
for (auto it : items) {
|
||||
c->addSeries(it->msg_id, it->sig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,16 +282,14 @@ void ChartsWidget::removeAll() {
|
||||
emit seriesChanged();
|
||||
}
|
||||
|
||||
void ChartsWidget::alignCharts(bool force) {
|
||||
void ChartsWidget::alignCharts() {
|
||||
int plot_left = 0;
|
||||
for (auto c : charts) {
|
||||
plot_left = std::max(plot_left, c->y_label_width);
|
||||
}
|
||||
plot_left = std::max((plot_left / 10) * 10 + 10, 50);
|
||||
if (std::exchange(align_to, plot_left) != align_to || force) {
|
||||
for (auto c : charts) {
|
||||
c->updatePlotArea(align_to);
|
||||
}
|
||||
for (auto c : charts) {
|
||||
c->updatePlotArea(plot_left);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +313,7 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
|
||||
axis_y->setLabelFormat("%.1f");
|
||||
chart->addAxis(axis_x, Qt::AlignBottom);
|
||||
chart->addAxis(axis_y, Qt::AlignLeft);
|
||||
chart->legend()->layout()->setContentsMargins(0, 0, 40, 0);
|
||||
chart->legend()->layout()->setContentsMargins(16, 0, 40, 0);
|
||||
chart->legend()->setShowToolTips(true);
|
||||
chart->setMargins({0, 0, 0, 0});
|
||||
|
||||
@@ -317,6 +322,9 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
|
||||
background->setPen(Qt::NoPen);
|
||||
background->setZValue(chart->zValue() - 1);
|
||||
|
||||
move_icon = new QGraphicsPixmapItem(utils::icon("grip-horizontal"), chart);
|
||||
move_icon->setToolTip(tr("Drag and drop to combine charts"));
|
||||
|
||||
QToolButton *remove_btn = new QToolButton();
|
||||
remove_btn->setIcon(utils::icon("x"));
|
||||
remove_btn->setAutoRaise(true);
|
||||
@@ -420,33 +428,24 @@ void ChartView::msgRemoved(uint32_t address) {
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::addSeries(const QList<QStringList> &series_list) {
|
||||
for (auto &s : series_list) {
|
||||
if (auto m = dbc()->msg(s[0])) {
|
||||
auto it = m->sigs.find(s[2]);
|
||||
if (it != m->sigs.end() && !hasSeries(s[0], &(it->second))) {
|
||||
addSeries(s[0], &(it->second));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::manageSeries() {
|
||||
SeriesSelector dlg(this);
|
||||
SeriesSelector dlg(tr("Mange Chart"), this);
|
||||
for (auto &s : sigs) {
|
||||
dlg.addSeries(s.msg_id, msgName(s.msg_id), QString::fromStdString(s.sig->name));
|
||||
dlg.addSelected(s.msg_id, s.sig);
|
||||
}
|
||||
|
||||
int ret = dlg.exec();
|
||||
if (ret == QDialog::Accepted) {
|
||||
QList<QStringList> series_list = dlg.series();
|
||||
if (series_list.isEmpty()) {
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
auto items = dlg.seletedItems();
|
||||
if (items.isEmpty()) {
|
||||
emit remove();
|
||||
} else {
|
||||
addSeries(series_list);
|
||||
for (auto s : items) {
|
||||
if (!hasSeries(s->msg_id, s->sig)) {
|
||||
addSeries(s->msg_id, s->sig);
|
||||
}
|
||||
}
|
||||
for (auto it = sigs.begin(); it != sigs.end(); /**/) {
|
||||
bool exists = std::any_of(series_list.cbegin(), series_list.cend(), [&](auto &s) {
|
||||
return s[0] == it->msg_id && s[2] == it->sig->name.c_str();
|
||||
bool exists = std::any_of(items.cbegin(), items.cend(), [&](auto &s) {
|
||||
return s->msg_id == it->msg_id && s->sig == it->sig;
|
||||
});
|
||||
it = exists ? ++it : removeItem(it);
|
||||
}
|
||||
@@ -456,24 +455,31 @@ void ChartView::manageSeries() {
|
||||
|
||||
void ChartView::resizeEvent(QResizeEvent *event) {
|
||||
QChartView::resizeEvent(event);
|
||||
updatePlotArea();
|
||||
updatePlotArea(align_to);
|
||||
int x = event->size().width() - close_btn_proxy->size().width() - 11;
|
||||
close_btn_proxy->setPos(x, 8);
|
||||
manage_btn_proxy->setPos(x - manage_btn_proxy->size().width() - 5, 8);
|
||||
move_icon->setPos(11, 8);
|
||||
}
|
||||
|
||||
void ChartView::updatePlotArea(int left) {
|
||||
align_to = left > 0 ? left : align_to;
|
||||
QRect r = rect();
|
||||
background->setRect(r);
|
||||
chart()->legend()->setGeometry(QRect(r.left(), r.top(), r.width(), 45));
|
||||
chart()->setPlotArea(QRect(align_to, r.top() + 45, r.width() - align_to - 22, r.height() - 80));
|
||||
chart()->layout()->invalidate();
|
||||
if (align_to != left || r != background->rect()) {
|
||||
align_to = left;
|
||||
background->setRect(r);
|
||||
chart()->legend()->setGeometry(QRect(r.left(), r.top(), r.width(), 45));
|
||||
chart()->setPlotArea(QRect(align_to, r.top() + 45, r.width() - align_to - 22, r.height() - 80));
|
||||
chart()->layout()->invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::updateTitle() {
|
||||
for (QLegendMarker *marker : chart()->legend()->markers()) {
|
||||
QObject::connect(marker, &QLegendMarker::clicked, this, &ChartView::handleMarkerClicked, Qt::UniqueConnection);
|
||||
}
|
||||
for (auto &s : sigs) {
|
||||
s.series->setName(QString("<b>%1</b> <font color=\"gray\">%2 %3</font>").arg(s.sig->name.c_str()).arg(msgName(s.msg_id)).arg(s.msg_id));
|
||||
auto decoration = s.series->isVisible() ? "none" : "line-through";
|
||||
s.series->setName(QString("<span style=\"text-decoration:%1\"><b>%2</b> <font color=\"gray\">%3 %4</font></span>").arg(decoration, s.sig->name.c_str(), msgName(s.msg_id), s.msg_id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,7 +498,7 @@ 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; });
|
||||
auto end = std::lower_bound(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;
|
||||
@@ -501,21 +507,6 @@ void ChartView::updateSeriesPoints() {
|
||||
((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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -528,21 +519,40 @@ void ChartView::updateSeries(const Signal *sig, const std::vector<Event *> *even
|
||||
s.vals.clear();
|
||||
s.vals.reserve(settings.max_cached_minutes * 60 * 100); // [n]seconds * 100hz
|
||||
s.last_value_mono_time = 0;
|
||||
s.series->setColor(getColor(s.sig));
|
||||
}
|
||||
double route_start_time = can->routeStartTime();
|
||||
|
||||
struct Chunk {
|
||||
std::vector<Event *>::const_iterator first, second;
|
||||
QVector<QPointF> vals;
|
||||
};
|
||||
// split into one minitue chunks
|
||||
QVector<Chunk> chunks;
|
||||
Event begin_event(cereal::Event::Which::INIT_DATA, s.last_value_mono_time);
|
||||
auto begin = std::upper_bound(events->begin(), events->end(), &begin_event, Event::lessThan());
|
||||
for (auto it = begin; it != events->end(); ++it) {
|
||||
if ((*it)->which == cereal::Event::Which::CAN) {
|
||||
for (const auto &c : (*it)->event.getCan()) {
|
||||
if (s.address == c.getAddress() && s.source == c.getSrc()) {
|
||||
auto dat = c.getDat();
|
||||
double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *s.sig);
|
||||
double ts = ((*it)->mono_time / (double)1e9) - route_start_time; // seconds
|
||||
s.vals.push_back({ts, value});
|
||||
for (auto it = begin, second = begin; it != events->end(); it = second) {
|
||||
second = std::lower_bound(it, events->end(), (*it)->mono_time + 1e9 * 60, [](auto &e, uint64_t ts) { return e->mono_time < ts; });
|
||||
chunks.push_back({it, second});
|
||||
}
|
||||
|
||||
QtConcurrent::blockingMap(chunks, [&](Chunk &chunk) {
|
||||
chunk.vals.reserve(60 * 100); // 100 hz
|
||||
double route_start_time = can->routeStartTime();
|
||||
for (auto it = chunk.first; it != chunk.second; ++it) {
|
||||
if ((*it)->which == cereal::Event::Which::CAN) {
|
||||
for (const auto &c : (*it)->event.getCan()) {
|
||||
if (s.address == c.getAddress() && s.source == c.getSrc()) {
|
||||
auto dat = c.getDat();
|
||||
double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *s.sig);
|
||||
double ts = ((*it)->mono_time / (double)1e9) - route_start_time; // seconds
|
||||
chunk.vals.push_back({ts, value});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
for (auto &c : chunks) {
|
||||
s.vals.append(c.vals);
|
||||
}
|
||||
if (events->size()) {
|
||||
s.last_value_mono_time = events->back()->mono_time;
|
||||
@@ -560,8 +570,10 @@ void ChartView::updateAxisY() {
|
||||
double min = std::numeric_limits<double>::max();
|
||||
double max = std::numeric_limits<double>::lowest();
|
||||
for (auto &s : sigs) {
|
||||
if (!s.series->isVisible()) continue;
|
||||
|
||||
auto first = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; });
|
||||
auto last = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->max(), [](auto &p, double x) { return p.x() < x; });
|
||||
auto last = std::lower_bound(first, s.vals.end(), axis_x->max(), [](auto &p, double x) { return p.x() < x; });
|
||||
for (auto it = first; it != last; ++it) {
|
||||
if (it->y() < min) min = it->y();
|
||||
if (it->y() > max) max = it->y();
|
||||
@@ -572,13 +584,14 @@ void ChartView::updateAxisY() {
|
||||
|
||||
double delta = std::abs(max - min) < 1e-3 ? 1 : (max - min) * 0.05;
|
||||
auto [min_y, max_y, tick_count] = getNiceAxisNumbers(min - delta, max + delta, axis_y->tickCount());
|
||||
if (min_y != axis_y->min() || max_y != axis_y->max()) {
|
||||
if (min_y != axis_y->min() || max_y != axis_y->max() || y_label_width == 0) {
|
||||
axis_y->setRange(min_y, max_y);
|
||||
axis_y->setTickCount(tick_count);
|
||||
|
||||
QFontMetrics fm(axis_y->labelsFont());
|
||||
int n = qMax(int(-qFloor(std::log10((max_y - min_y) / (tick_count - 1)))), 0) + 1;
|
||||
y_label_width = qMax(fm.width(QString::number(min_y, 'f', n)), fm.width(QString::number(max_y, 'f', n))) + 20; // left margin 20
|
||||
emit axisYLabelWidthChanged(y_label_width);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,8 +629,7 @@ void ChartView::leaveEvent(QEvent *event) {
|
||||
}
|
||||
|
||||
void ChartView::mousePressEvent(QMouseEvent *event) {
|
||||
if (event->button() == Qt::LeftButton && !chart()->plotArea().contains(event->pos()) &&
|
||||
!manage_btn_proxy->geometry().contains(event->pos()) && !close_btn_proxy->geometry().contains(event->pos())) {
|
||||
if (event->button() == Qt::LeftButton && move_icon->sceneBoundingRect().contains(event->pos())) {
|
||||
QMimeData *mimeData = new QMimeData;
|
||||
mimeData->setData(mime_type, QByteArray::number((qulonglong)this));
|
||||
QDrag *drag = new QDrag(this);
|
||||
@@ -719,11 +731,9 @@ void ChartView::dropEvent(QDropEvent *event) {
|
||||
event->accept();
|
||||
} else {
|
||||
ChartView *source_chart = (ChartView *)event->source();
|
||||
QList<QStringList> series;
|
||||
for (auto &s : source_chart->sigs) {
|
||||
series.push_back({s.msg_id, msgName(s.msg_id), QString::fromStdString(s.sig->name)});
|
||||
addSeries(s.msg_id, s.sig);
|
||||
}
|
||||
addSeries(series);
|
||||
emit source_chart->remove();
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
@@ -752,6 +762,19 @@ void ChartView::drawForeground(QPainter *painter, const QRectF &rect) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// paint points. OpenGL mode lacks certain features (such as showing points)
|
||||
painter->setPen(Qt::NoPen);
|
||||
for (auto &s : sigs) {
|
||||
if (s.series->useOpenGL() && s.series->isVisible() && s.series->pointsVisible()) {
|
||||
auto first = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; });
|
||||
auto last = std::lower_bound(first, s.vals.end(), axis_x->max(), [](auto &p, double x) { return p.x() < x; });
|
||||
for (auto it = first; it != last; ++it) {
|
||||
painter->setBrush(s.series->color());
|
||||
painter->drawEllipse(chart()->mapToPosition(*it), 4, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QXYSeries *ChartView::createSeries(QAbstractSeries::SeriesType type) {
|
||||
@@ -770,11 +793,10 @@ QXYSeries *ChartView::createSeries(QAbstractSeries::SeriesType type) {
|
||||
}
|
||||
|
||||
void ChartView::setSeriesType(QAbstractSeries::SeriesType type) {
|
||||
line_series_action->setChecked(type == QAbstractSeries::SeriesTypeLine);
|
||||
scatter_series_action->setChecked(type == QAbstractSeries::SeriesTypeScatter);
|
||||
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();
|
||||
@@ -792,42 +814,51 @@ void ChartView::setSeriesType(QAbstractSeries::SeriesType type) {
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::handleMarkerClicked() {
|
||||
auto marker = qobject_cast<QLegendMarker *>(sender());
|
||||
Q_ASSERT(marker);
|
||||
if (sigs.size() > 1) {
|
||||
auto series = marker->series();
|
||||
series->setVisible(!series->isVisible());
|
||||
marker->setVisible(true);
|
||||
updateAxisY();
|
||||
updateTitle();
|
||||
}
|
||||
}
|
||||
|
||||
// SeriesSelector
|
||||
|
||||
SeriesSelector::SeriesSelector(QWidget *parent) {
|
||||
setWindowTitle(tr("Manage Chart Series"));
|
||||
QHBoxLayout *contents_layout = new QHBoxLayout();
|
||||
SeriesSelector::SeriesSelector(QString title, QWidget *parent) : QDialog(parent) {
|
||||
setWindowTitle(title);
|
||||
QGridLayout *main_layout = new QGridLayout(this);
|
||||
|
||||
QVBoxLayout *left_layout = new QVBoxLayout();
|
||||
left_layout->addWidget(new QLabel(tr("Select Signals:")));
|
||||
|
||||
msgs_combo = new QComboBox(this);
|
||||
// left column
|
||||
main_layout->addWidget(new QLabel(tr("Available Signals")), 0, 0);
|
||||
main_layout->addWidget(msgs_combo = new QComboBox(this), 1, 0);
|
||||
msgs_combo->setEditable(true);
|
||||
msgs_combo->lineEdit()->setPlaceholderText(tr("Select Msg"));
|
||||
msgs_combo->lineEdit()->setPlaceholderText(tr("Select a msg..."));
|
||||
msgs_combo->setInsertPolicy(QComboBox::NoInsert);
|
||||
msgs_combo->completer()->setCompletionMode(QCompleter::PopupCompletion);
|
||||
msgs_combo->completer()->setFilterMode(Qt::MatchContains);
|
||||
|
||||
left_layout->addWidget(msgs_combo);
|
||||
sig_list = new QListWidget(this);
|
||||
sig_list->setSortingEnabled(true);
|
||||
sig_list->setToolTip(tr("Double click on an item to add signal to chart"));
|
||||
left_layout->addWidget(sig_list);
|
||||
main_layout->addWidget(available_list = new QListWidget(this), 2, 0);
|
||||
|
||||
QVBoxLayout *right_layout = new QVBoxLayout();
|
||||
right_layout->addWidget(new QLabel(tr("Chart Signals:")));
|
||||
chart_series = new QListWidget(this);
|
||||
chart_series->setSortingEnabled(true);
|
||||
chart_series->setToolTip(tr("Double click on an item to remove signal from chart"));
|
||||
right_layout->addWidget(chart_series);
|
||||
contents_layout->addLayout(left_layout);
|
||||
contents_layout->addLayout(right_layout);
|
||||
// buttons
|
||||
QVBoxLayout *btn_layout = new QVBoxLayout();
|
||||
QPushButton *add_btn = new QPushButton(utils::icon("chevron-right"), "", this);
|
||||
QPushButton *remove_btn = new QPushButton(utils::icon("chevron-left"), "", this);
|
||||
btn_layout->addStretch(0);
|
||||
btn_layout->addWidget(add_btn);
|
||||
btn_layout->addWidget(remove_btn);
|
||||
btn_layout->addStretch(0);
|
||||
main_layout->addLayout(btn_layout, 0, 1, 3, 1);
|
||||
|
||||
// right column
|
||||
main_layout->addWidget(new QLabel(tr("Selected Signals")), 0, 2);
|
||||
main_layout->addWidget(selected_list = new QListWidget(this), 1, 2, 2, 1);
|
||||
|
||||
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->addLayout(contents_layout);
|
||||
main_layout->addWidget(buttonBox);
|
||||
main_layout->addWidget(buttonBox, 3, 2);
|
||||
|
||||
for (auto it = can->can_msgs.cbegin(); it != can->can_msgs.cend(); ++it) {
|
||||
if (auto m = dbc()->msg(it.key())) {
|
||||
@@ -835,56 +866,59 @@ SeriesSelector::SeriesSelector(QWidget *parent) {
|
||||
}
|
||||
}
|
||||
msgs_combo->model()->sort(0);
|
||||
msgs_combo->setCurrentIndex(-1);
|
||||
|
||||
QObject::connect(msgs_combo, qOverload<int>(&QComboBox::currentIndexChanged), this, &SeriesSelector::updateAvailableList);
|
||||
QObject::connect(available_list, &QListWidget::currentRowChanged, [=](int row) { add_btn->setEnabled(row != -1); });
|
||||
QObject::connect(selected_list, &QListWidget::currentRowChanged, [=](int row) { remove_btn->setEnabled(row != -1); });
|
||||
QObject::connect(available_list, &QListWidget::itemDoubleClicked, this, &SeriesSelector::add);
|
||||
QObject::connect(selected_list, &QListWidget::itemDoubleClicked, this, &SeriesSelector::remove);
|
||||
QObject::connect(add_btn, &QPushButton::clicked, [this]() { if (auto item = available_list->currentItem()) add(item); });
|
||||
QObject::connect(remove_btn, &QPushButton::clicked, [this]() { if (auto item = selected_list->currentItem()) remove(item);});
|
||||
QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
QObject::connect(msgs_combo, SIGNAL(currentIndexChanged(int)), SLOT(msgSelected(int)));
|
||||
QObject::connect(sig_list, &QListWidget::itemDoubleClicked, this, &SeriesSelector::addSignal);
|
||||
QObject::connect(chart_series, &QListWidget::itemDoubleClicked, [](QListWidgetItem *item) { delete item; });
|
||||
|
||||
if (int index = msgs_combo->currentIndex(); index >= 0) {
|
||||
msgSelected(index);
|
||||
}
|
||||
}
|
||||
|
||||
void SeriesSelector::msgSelected(int index) {
|
||||
void SeriesSelector::add(QListWidgetItem *item) {
|
||||
auto it = (ListItem *)item;
|
||||
addItemToList(selected_list, it->msg_id, it->sig, true);
|
||||
delete item;
|
||||
}
|
||||
|
||||
void SeriesSelector::remove(QListWidgetItem *item) {
|
||||
auto it = (ListItem *)item;
|
||||
if (it->msg_id == msgs_combo->currentData().toString()) {
|
||||
addItemToList(available_list, it->msg_id, it->sig);
|
||||
}
|
||||
delete item;
|
||||
}
|
||||
|
||||
void SeriesSelector::updateAvailableList(int index) {
|
||||
if (index == -1) return;
|
||||
available_list->clear();
|
||||
QString msg_id = msgs_combo->itemData(index).toString();
|
||||
sig_list->clear();
|
||||
if (auto m = dbc()->msg(msg_id)) {
|
||||
for (auto &[name, s] : m->sigs) {
|
||||
QStringList data({msg_id, m->name, name});
|
||||
QListWidgetItem *item = new QListWidgetItem(name, sig_list);
|
||||
item->setData(Qt::UserRole, data);
|
||||
sig_list->addItem(item);
|
||||
auto selected_items = seletedItems();
|
||||
for (auto &[name, s] : dbc()->msg(msg_id)->sigs) {
|
||||
bool is_selected = std::any_of(selected_items.begin(), selected_items.end(), [=, sig=&s](auto it) { return it->msg_id == msg_id && it->sig == sig; });
|
||||
if (!is_selected) {
|
||||
addItemToList(available_list, msg_id, &s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SeriesSelector::addSignal(QListWidgetItem *item) {
|
||||
QStringList data = item->data(Qt::UserRole).toStringList();
|
||||
addSeries(data[0], data[1], data[2]);
|
||||
}
|
||||
void SeriesSelector::addItemToList(QListWidget *parent, const QString id, const Signal *sig, bool show_msg_name) {
|
||||
QString text = QString("<span style=\"color:%0;\">■ </span> %1").arg(getColor(sig).name(), sig->name.c_str());
|
||||
if (show_msg_name) text += QString(" <font color=\"gray\">%0 %1</font>").arg(msgName(id), id);
|
||||
|
||||
void SeriesSelector::addSeries(const QString &id, const QString &msg_name, const QString &sig_name) {
|
||||
QStringList data({id, msg_name, sig_name});
|
||||
for (int i = 0; i < chart_series->count(); ++i) {
|
||||
if (chart_series->item(i)->data(Qt::UserRole).toStringList() == data) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
QListWidgetItem *new_item = new QListWidgetItem(chart_series);
|
||||
new_item->setData(Qt::UserRole, data);
|
||||
chart_series->addItem(new_item);
|
||||
QLabel *label = new QLabel(QString("%0 <font color=\"gray\">%1 %2</font>").arg(data[2]).arg(data[1]).arg(data[0]), chart_series);
|
||||
QLabel *label = new QLabel(text);
|
||||
label->setContentsMargins(5, 0, 5, 0);
|
||||
auto new_item = new ListItem(id, sig, parent);
|
||||
new_item->setSizeHint(label->sizeHint());
|
||||
chart_series->setItemWidget(new_item, label);
|
||||
parent->setItemWidget(new_item, label);
|
||||
}
|
||||
|
||||
QList<QStringList> SeriesSelector::series() {
|
||||
QList<QStringList> ret;
|
||||
for (int i = 0; i < chart_series->count(); ++i) {
|
||||
ret.push_back(chart_series->item(i)->data(Qt::UserRole).toStringList());
|
||||
}
|
||||
QList<SeriesSelector::ListItem *> SeriesSelector::seletedItems() {
|
||||
QList<SeriesSelector::ListItem *> ret;
|
||||
for (int i = 0; i < selected_list->count(); ++i) ret.push_back((ListItem *)selected_list->item(i));
|
||||
return ret;
|
||||
}
|
||||
|
||||
+29
-16
@@ -5,9 +5,12 @@
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <QGraphicsProxyWidget>
|
||||
#include <QSlider>
|
||||
#include <QToolButton>
|
||||
#include <QtCharts/QChartView>
|
||||
#include <QtCharts/QLegendMarker>
|
||||
#include <QtCharts/QLineSeries>
|
||||
#include <QtCharts/QScatterSeries>
|
||||
#include <QtCharts/QValueAxis>
|
||||
@@ -25,13 +28,12 @@ class ChartView : public QChartView {
|
||||
public:
|
||||
ChartView(QWidget *parent = nullptr);
|
||||
void addSeries(const QString &msg_id, const Signal *sig);
|
||||
void addSeries(const QList<QStringList> &series_list);
|
||||
void removeSeries(const QString &msg_id, const Signal *sig);
|
||||
bool hasSeries(const QString &msg_id, const Signal *sig) const;
|
||||
void updateSeries(const Signal *sig = nullptr, const std::vector<Event*> *events = nullptr, bool clear = true);
|
||||
void updatePlot(double cur, double min, double max);
|
||||
void setSeriesType(QAbstractSeries::SeriesType type);
|
||||
void updatePlotArea(int left = 0);
|
||||
void updatePlotArea(int left);
|
||||
|
||||
struct SigItem {
|
||||
QString msg_id;
|
||||
@@ -49,6 +51,7 @@ signals:
|
||||
void zoomIn(double min, double max);
|
||||
void zoomReset();
|
||||
void remove();
|
||||
void axisYLabelWidthChanged(int w);
|
||||
|
||||
private slots:
|
||||
void msgRemoved(uint32_t address);
|
||||
@@ -56,6 +59,7 @@ private slots:
|
||||
void signalUpdated(const Signal *sig);
|
||||
void signalRemoved(const Signal *sig);
|
||||
void manageSeries();
|
||||
void handleMarkerClicked();
|
||||
|
||||
private:
|
||||
QList<ChartView::SigItem>::iterator removeItem(const QList<ChartView::SigItem>::iterator &it);
|
||||
@@ -75,11 +79,12 @@ private:
|
||||
QXYSeries *createSeries(QAbstractSeries::SeriesType type);
|
||||
void updateSeriesPoints();
|
||||
|
||||
int y_label_width = 50;
|
||||
int y_label_width = 0;
|
||||
int align_to = 0;
|
||||
QValueAxis *axis_x;
|
||||
QValueAxis *axis_y;
|
||||
QVector<QPointF> track_pts;
|
||||
QGraphicsPixmapItem *move_icon;
|
||||
QGraphicsProxyWidget *close_btn_proxy;
|
||||
QGraphicsProxyWidget *manage_btn_proxy;
|
||||
QGraphicsRectItem *background;
|
||||
@@ -111,7 +116,7 @@ signals:
|
||||
|
||||
private:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void alignCharts(bool force = false);
|
||||
void alignCharts();
|
||||
void newChart();
|
||||
ChartView * createChart();
|
||||
void removeChart(ChartView *chart);
|
||||
@@ -129,9 +134,12 @@ private:
|
||||
QLabel *title_label;
|
||||
QLabel *range_lb;
|
||||
QSlider *range_slider;
|
||||
QAction *range_lb_action;
|
||||
QAction *range_slider_action;
|
||||
bool docking = true;
|
||||
QAction *dock_btn;
|
||||
QAction *reset_zoom_btn;
|
||||
QAction *reset_zoom_action;
|
||||
QToolButton *reset_zoom_btn;
|
||||
QAction *remove_all_btn;
|
||||
QGridLayout *charts_layout;
|
||||
QList<ChartView *> charts;
|
||||
@@ -144,23 +152,28 @@ private:
|
||||
QAction *columns_cb_action;
|
||||
QComboBox *columns_cb;
|
||||
int column_count = 1;
|
||||
int align_to = 0;
|
||||
int current_column_count = 0;
|
||||
};
|
||||
|
||||
class SeriesSelector : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SeriesSelector(QWidget *parent);
|
||||
void addSeries(const QString &id, const QString& msg_name, const QString &sig_name);
|
||||
QList<QStringList> series();
|
||||
struct ListItem : public QListWidgetItem {
|
||||
ListItem(const QString &msg_id, const Signal *sig, QListWidget *parent) : msg_id(msg_id), sig(sig), QListWidgetItem(parent) {}
|
||||
QString msg_id;
|
||||
const Signal *sig;
|
||||
};
|
||||
|
||||
private slots:
|
||||
void msgSelected(int index);
|
||||
void addSignal(QListWidgetItem *item);
|
||||
SeriesSelector(QString title, QWidget *parent);
|
||||
QList<ListItem *> seletedItems();
|
||||
inline void addSelected(const QString &id, const Signal *sig) { addItemToList(selected_list, id, sig, true); }
|
||||
|
||||
private:
|
||||
void updateAvailableList(int index);
|
||||
void addItemToList(QListWidget *parent, const QString id, const Signal *sig, bool show_msg_name = false);
|
||||
void add(QListWidgetItem *item);
|
||||
void remove(QListWidgetItem *item);
|
||||
|
||||
QComboBox *msgs_combo;
|
||||
QListWidget *sig_list;
|
||||
QListWidget *chart_series;
|
||||
QListWidget *available_list;
|
||||
QListWidget *selected_list;
|
||||
};
|
||||
|
||||
@@ -232,9 +232,14 @@ WelcomeWidget::WelcomeWidget(QWidget *parent) : QWidget(parent) {
|
||||
return hlayout;
|
||||
};
|
||||
|
||||
auto lb = new QLabel(tr("<-Select a message to to view details"));
|
||||
lb->setAlignment(Qt::AlignHCenter);
|
||||
main_layout->addWidget(lb);
|
||||
main_layout->addLayout(newShortcutRow("Pause", "Space"));
|
||||
main_layout->addLayout(newShortcutRow("Help", "Alt + H"));
|
||||
main_layout->addStretch(0);
|
||||
|
||||
setStyleSheet("QLabel{color:darkGray;}");
|
||||
setBackgroundRole(QPalette::Base);
|
||||
setAutoFillBackground(true);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i
|
||||
}
|
||||
return show_signals ? QString::fromStdString(sigs[section - 1]->name).replace('_', ' ') : "Data";
|
||||
} else if (role == Qt::BackgroundRole && section > 0 && show_signals) {
|
||||
return QBrush(QColor(getColor(section - 1)));
|
||||
return QBrush(getColor(sigs[section - 1]));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
|
||||
@@ -279,7 +279,7 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
|
||||
}
|
||||
|
||||
// color label
|
||||
auto bg_color = QColor(getColor(item->row()));
|
||||
auto bg_color = getColor(item->sig);
|
||||
QRect rc{option.rect.left() + 3, option.rect.top(), 22, option.rect.height()};
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(item->highlight ? bg_color.darker(125) : bg_color);
|
||||
|
||||
@@ -34,6 +34,7 @@ bool AbstractStream::updateEvent(const Event *event) {
|
||||
change_trackers[id].compute(data.dat, data.ts, data.freq);
|
||||
data.colors = change_trackers[id].colors;
|
||||
data.last_change_t = change_trackers[id].last_change_t;
|
||||
data.bit_change_counts = change_trackers[id].bit_change_counts;
|
||||
}
|
||||
|
||||
double ts = millis_since_boot();
|
||||
|
||||
@@ -16,6 +16,7 @@ struct CanData {
|
||||
QByteArray dat;
|
||||
QVector<QColor> colors;
|
||||
QVector<double> last_change_t;
|
||||
QVector<std::array<uint32_t, 8>> bit_change_counts;
|
||||
};
|
||||
|
||||
class AbstractStream : public QObject {
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
#include <QApplication>
|
||||
#include <QFontDatabase>
|
||||
#include <QPainter>
|
||||
#include <QDebug>
|
||||
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
|
||||
@@ -14,6 +18,7 @@ void ChangeTracker::compute(const QByteArray &dat, double ts, uint32_t freq) {
|
||||
if (prev_dat.size() != dat.size()) {
|
||||
colors.resize(dat.size());
|
||||
last_change_t.resize(dat.size());
|
||||
bit_change_counts.resize(dat.size());
|
||||
std::fill(colors.begin(), colors.end(), QColor(0, 0, 0, 0));
|
||||
std::fill(last_change_t.begin(), last_change_t.end(), ts);
|
||||
} else {
|
||||
@@ -35,6 +40,13 @@ void ChangeTracker::compute(const QByteArray &dat, double ts, uint32_t freq) {
|
||||
colors[i] = blend(colors[i], QColor(102, 86, 169, start_alpha / 2)); // Greyish/Blue
|
||||
}
|
||||
|
||||
// Track bit level changes
|
||||
for (int bit = 0; bit < 8; bit++){
|
||||
if ((cur ^ last) & (1 << bit)) {
|
||||
bit_change_counts[i][bit] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
last_change_t[i] = ts;
|
||||
} else {
|
||||
// Fade out
|
||||
@@ -96,6 +108,17 @@ void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
|
||||
}
|
||||
}
|
||||
|
||||
QColor getColor(const Signal *sig) {
|
||||
float h = 19 * (float)sig->lsb / 64.0;
|
||||
h = fmod(h, 1.0);
|
||||
|
||||
size_t hash = qHash(QString::fromStdString(sig->name));
|
||||
float s = 0.25 + 0.25 * (float)(hash & 0xff) / 255.0;
|
||||
float v = 0.75 + 0.25 * (float)((hash >> 8) & 0xff) / 255.0;
|
||||
|
||||
return QColor::fromHsvF(h, s, v);
|
||||
}
|
||||
|
||||
NameValidator::NameValidator(QObject *parent) : QRegExpValidator(QRegExp("^(\\w+)"), parent) { }
|
||||
|
||||
QValidator::State NameValidator::validate(QString &input, int &pos) const {
|
||||
|
||||
+4
-5
@@ -7,6 +7,8 @@
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QVector>
|
||||
|
||||
#include "opendbc/can/common_dbc.h"
|
||||
|
||||
class ChangeTracker {
|
||||
public:
|
||||
void compute(const QByteArray &dat, double ts, uint32_t freq);
|
||||
@@ -15,6 +17,7 @@ public:
|
||||
|
||||
QVector<double> last_change_t;
|
||||
QVector<QColor> colors;
|
||||
QVector<std::array<uint32_t, 8>> bit_change_counts;
|
||||
|
||||
private:
|
||||
const int periodic_threshold = 10;
|
||||
@@ -33,11 +36,7 @@ public:
|
||||
|
||||
inline QString toHex(const QByteArray &dat) { return dat.toHex(' ').toUpper(); }
|
||||
inline char toHex(uint value) { return "0123456789ABCDEF"[value & 0xF]; }
|
||||
inline const QString &getColor(int i) {
|
||||
// TODO: add more colors
|
||||
static const QString SIGNAL_COLORS[] = {"#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF", "#FF7F50", "#FFBF00"};
|
||||
return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)];
|
||||
}
|
||||
QColor getColor(const Signal *sig);
|
||||
|
||||
class NameValidator : public QRegExpValidator {
|
||||
Q_OBJECT
|
||||
|
||||
Reference in New Issue
Block a user