Skip to content

Commit c9e446a

Browse files
committed
Merge remote-tracking branch 'upstream/master' into enable-planner
2 parents e6c4106 + 879a7c3 commit c9e446a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+857
-621
lines changed

.github/workflows/selfdrive_tests.yaml

+5-2
Original file line numberDiff line numberDiff line change
@@ -299,17 +299,20 @@ jobs:
299299
$UNIT_TEST selfdrive/loggerd && \
300300
$UNIT_TEST selfdrive/car && \
301301
$UNIT_TEST selfdrive/locationd && \
302+
selfdrive/locationd/test/_test_locationd_lib.py && \
302303
$UNIT_TEST selfdrive/athena && \
303304
$UNIT_TEST selfdrive/thermald && \
304305
$UNIT_TEST selfdrive/hardware/tici && \
305306
$UNIT_TEST selfdrive/modeld && \
306307
$UNIT_TEST tools/lib/tests && \
308+
./selfdrive/ui/tests/create_test_translations.sh && \
309+
QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \
307310
./common/tests/test_util && \
308311
./common/tests/test_swaglog && \
309312
./selfdrive/boardd/tests/test_boardd_usbprotocol && \
310313
./selfdrive/loggerd/tests/test_logger &&\
311314
./system/proclogd/tests/test_proclog && \
312-
./selfdrive/ui/replay/tests/test_replay && \
315+
./tools/replay/tests/test_replay && \
313316
./system/camerad/test/ae_gray_test && \
314317
coverage xml"
315318
- name: "Upload coverage to Codecov"
@@ -364,7 +367,7 @@ jobs:
364367
CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only"
365368
- name: "Upload coverage to Codecov"
366369
uses: codecov/codecov-action@v2
367-
370+
368371
model_replay_onnx:
369372
name: model replay onnx
370373
runs-on: ubuntu-20.04

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ Directory Structure
121121
├── modeld # Driving and monitoring model runners
122122
├── proclogd # Logs information from proc
123123
├── sensord # IMU interface code
124+
├── navd # Turn-by-turn navigation
124125
├── test # Unit tests, system tests, and a car simulator
125126
└── ui # The UI
126127

SConstruct

+15-8
Original file line numberDiff line numberDiff line change
@@ -356,22 +356,27 @@ Export('cereal', 'messaging', 'visionipc')
356356

357357
# Build rednose library and ekf models
358358

359+
rednose_deps = [
360+
"#selfdrive/locationd/models/constants.py",
361+
"#selfdrive/locationd/models/gnss_helpers.py",
362+
]
363+
359364
rednose_config = {
360365
'generated_folder': '#selfdrive/locationd/models/generated',
361366
'to_build': {
362-
'gnss': ('#selfdrive/locationd/models/gnss_kf.py', True, []),
363-
'live': ('#selfdrive/locationd/models/live_kf.py', True, ['live_kf_constants.h']),
364-
'car': ('#selfdrive/locationd/models/car_kf.py', True, []),
367+
'gnss': ('#selfdrive/locationd/models/gnss_kf.py', True, [], rednose_deps),
368+
'live': ('#selfdrive/locationd/models/live_kf.py', True, ['live_kf_constants.h'], rednose_deps),
369+
'car': ('#selfdrive/locationd/models/car_kf.py', True, [], rednose_deps),
365370
},
366371
}
367372

368373
if arch != "larch64":
369374
rednose_config['to_build'].update({
370-
'loc_4': ('#selfdrive/locationd/models/loc_kf.py', True, []),
371-
'pos_computer_4': ('#rednose/helpers/lst_sq_computer.py', False, []),
372-
'pos_computer_5': ('#rednose/helpers/lst_sq_computer.py', False, []),
373-
'feature_handler_5': ('#rednose/helpers/feature_handler.py', False, []),
374-
'lane': ('#xx/pipeline/lib/ekf/lane_kf.py', True, []),
375+
'loc_4': ('#selfdrive/locationd/models/loc_kf.py', True, [], rednose_deps),
376+
'pos_computer_4': ('#rednose/helpers/lst_sq_computer.py', False, [], []),
377+
'pos_computer_5': ('#rednose/helpers/lst_sq_computer.py', False, [], []),
378+
'feature_handler_5': ('#rednose/helpers/feature_handler.py', False, [], []),
379+
'lane': ('#xx/pipeline/lib/ekf/lane_kf.py', True, [], rednose_deps),
375380
})
376381

377382
Export('rednose_config')
@@ -411,6 +416,8 @@ SConscript(['selfdrive/locationd/SConscript'])
411416
SConscript(['selfdrive/sensord/SConscript'])
412417
SConscript(['selfdrive/ui/SConscript'])
413418

419+
SConscript(['tools/replay/SConscript'])
420+
414421
if GetOption('test'):
415422
SConscript('panda/tests/safety/SConscript')
416423

cereal

common/params.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ std::unordered_map<std::string, uint32_t> keys = {
128128
{"IsTakingSnapshot", CLEAR_ON_MANAGER_START},
129129
{"IsUpdateAvailable", CLEAR_ON_MANAGER_START},
130130
{"JoystickDebugMode", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF},
131-
{"LaikadEphemeris", PERSISTENT},
131+
{"LaikadEphemeris", PERSISTENT | DONT_LOG},
132132
{"LastAthenaPingTime", CLEAR_ON_MANAGER_START},
133133
{"LastGPSPosition", PERSISTENT},
134134
{"LastManagerExitReason", CLEAR_ON_MANAGER_START},

docs/c_docs.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ soundd
5454
replay
5555
""""""
5656
.. autodoxygenindex::
57-
:project: selfdrive_ui_replay
57+
:project: tools_replay
5858

5959
qt
6060
""

release/files_common

+2-2
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,8 @@ selfdrive/ui/qt/offroad/*.qml
296296
selfdrive/ui/qt/widgets/*.cc
297297
selfdrive/ui/qt/widgets/*.h
298298

299-
selfdrive/ui/replay/*.cc
300-
selfdrive/ui/replay/*.h
299+
tools/replay/*.cc
300+
tools/replay/*.h
301301

302302
selfdrive/ui/qt/maps/*.cc
303303
selfdrive/ui/qt/maps/*.h

selfdrive/car/chrysler/carstate.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ def update(self, cp, cp_cam):
4747

4848
ret.leftBlinker = cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 1
4949
ret.rightBlinker = cp.vl["STEERING_LEVERS"]["TURN_SIGNALS"] == 2
50-
ret.steeringAngleDeg = cp.vl["STEERING"]["STEER_ANGLE"]
51-
ret.steeringRateDeg = cp.vl["STEERING"]["STEERING_RATE"]
5250
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(cp.vl["GEAR"]["PRNDL"], None))
5351

5452
ret.cruiseState.available = cp.vl["DAS_3"]["ACC_AVAILABLE"] == 1 # ACC is white
@@ -58,6 +56,8 @@ def update(self, cp, cp_cam):
5856
ret.cruiseState.nonAdaptive = cp.vl["DASHBOARD"]["CRUISE_STATE"] in (1, 2)
5957
ret.accFaulted = cp.vl["DAS_3"]["ACC_FAULTED"] != 0
6058

59+
ret.steeringAngleDeg = cp.vl["STEERING"]["STEER_ANGLE"]
60+
ret.steeringRateDeg = cp.vl["STEERING"]["STEERING_RATE"]
6161
ret.steeringTorque = cp.vl["EPS_STATUS"]["TORQUE_DRIVER"]
6262
ret.steeringTorqueEps = cp.vl["EPS_STATUS"]["TORQUE_MOTOR"]
6363
ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD

selfdrive/car/hyundai/carstate.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from common.conversions import Conversions as CV
66
from opendbc.can.parser import CANParser
77
from opendbc.can.can_define import CANDefine
8-
from selfdrive.car.hyundai.values import DBC, STEER_THRESHOLD, FEATURES, HDA2_CAR, EV_CAR, HYBRID_CAR, Buttons
8+
from selfdrive.car.hyundai.values import DBC, FEATURES, HDA2_CAR, EV_CAR, HYBRID_CAR, Buttons, CarControllerParams
99
from selfdrive.car.interfaces import CarStateBase
1010

1111
PREV_BUTTON_SAMPLES = 4
@@ -32,6 +32,8 @@ def __init__(self, CP):
3232
self.park_brake = False
3333
self.buttons_counter = 0
3434

35+
self.params = CarControllerParams(CP)
36+
3537
def update(self, cp, cp_cam):
3638
if self.CP.carFingerprint in HDA2_CAR:
3739
return self.update_hda2(cp, cp_cam)
@@ -61,7 +63,7 @@ def update(self, cp, cp_cam):
6163
50, cp.vl["CGW1"]["CF_Gway_TurnSigLh"], cp.vl["CGW1"]["CF_Gway_TurnSigRh"])
6264
ret.steeringTorque = cp.vl["MDPS12"]["CR_Mdps_StrColTq"]
6365
ret.steeringTorqueEps = cp.vl["MDPS12"]["CR_Mdps_OutTq"]
64-
ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD
66+
ret.steeringPressed = abs(ret.steeringTorque) > self.params.STEER_THRESHOLD
6567
ret.steerFaultTemporary = cp.vl["MDPS12"]["CF_Mdps_ToiUnavail"] != 0 or cp.vl["MDPS12"]["CF_Mdps_ToiFlt"] != 0
6668

6769
# cruise state
@@ -157,7 +159,7 @@ def update_hda2(self, cp, cp_cam):
157159
ret.steeringAngleDeg = cp.vl["STEERING_SENSORS"]["STEERING_ANGLE"] * -1
158160
ret.steeringTorque = cp.vl["MDPS"]["STEERING_COL_TORQUE"]
159161
ret.steeringTorqueEps = cp.vl["MDPS"]["STEERING_OUT_TORQUE"]
160-
ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD
162+
ret.steeringPressed = abs(ret.steeringTorque) > self.params.STEER_THRESHOLD
161163

162164
ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["BLINKERS"]["LEFT_LAMP"],
163165
cp.vl["BLINKERS"]["RIGHT_LAMP"])

selfdrive/car/hyundai/interface.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from selfdrive.car import STD_CARGO_KG, create_button_enable_events, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config
88
from selfdrive.car.interfaces import CarInterfaceBase
99
from selfdrive.car.disable_ecu import disable_ecu
10-
from selfdrive.controls.lib.latcontrol_torque import set_torque_tune
1110

1211
ButtonType = car.CarState.ButtonEvent.Type
1312
EventName = car.CarEvent.EventName
@@ -51,7 +50,6 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[], disabl
5150
ret.stopAccel = 0.0
5251

5352
ret.longitudinalActuatorDelayUpperBound = 1.0 # s
54-
torque_params = CarInterfaceBase.get_torque_params(candidate)
5553
if candidate in (CAR.SANTA_FE, CAR.SANTA_FE_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022):
5654
ret.lateralTuning.pid.kf = 0.00005
5755
ret.mass = 3982. * CV.LB_TO_KG + STD_CARGO_KG
@@ -66,7 +64,7 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[], disabl
6664
ret.wheelbase = 2.84
6765
ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable
6866
tire_stiffness_factor = 0.65
69-
set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION'])
67+
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
7068
elif candidate == CAR.SONATA_LF:
7169
ret.lateralTuning.pid.kf = 0.00005
7270
ret.mass = 4497. * CV.LB_TO_KG
@@ -96,13 +94,13 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[], disabl
9694
ret.wheelbase = 2.72
9795
ret.steerRatio = 12.9
9896
tire_stiffness_factor = 0.65
99-
set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION'])
97+
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
10098
elif candidate == CAR.ELANTRA_HEV_2021:
10199
ret.mass = (3017. * CV.LB_TO_KG) + STD_CARGO_KG
102100
ret.wheelbase = 2.72
103101
ret.steerRatio = 12.9
104102
tire_stiffness_factor = 0.65
105-
set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION'])
103+
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
106104
elif candidate == CAR.HYUNDAI_GENESIS:
107105
ret.lateralTuning.pid.kf = 0.00005
108106
ret.mass = 2060. + STD_CARGO_KG
@@ -204,7 +202,7 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[], disabl
204202
ret.wheelbase = 2.80
205203
ret.steerRatio = 13.75
206204
tire_stiffness_factor = 0.5
207-
set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION'])
205+
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
208206
elif candidate == CAR.KIA_STINGER:
209207
ret.lateralTuning.pid.kf = 0.00005
210208
ret.mass = 1825. + STD_CARGO_KG
@@ -244,7 +242,7 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[], disabl
244242
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.noOutput),
245243
get_safety_config(car.CarParams.SafetyModel.hyundaiHDA2)]
246244
tire_stiffness_factor = 0.65
247-
set_torque_tune(ret.lateralTuning, torque_params['LAT_ACCEL_FACTOR'], torque_params['FRICTION'])
245+
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
248246

249247
# Genesis
250248
elif candidate == CAR.GENESIS_G70:

selfdrive/car/hyundai/values.py

+15-14
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,27 @@ class CarControllerParams:
1313
ACCEL_MAX = 2.0 # m/s
1414

1515
def __init__(self, CP):
16+
self.STEER_DELTA_UP = 3
17+
self.STEER_DELTA_DOWN = 7
18+
self.STEER_DRIVER_ALLOWANCE = 50
19+
self.STEER_DRIVER_MULTIPLIER = 2
20+
self.STEER_DRIVER_FACTOR = 1
21+
self.STEER_THRESHOLD = 150
22+
23+
if CP.carFingerprint in HDA2_CAR:
24+
self.STEER_MAX = 270
25+
self.STEER_DRIVER_ALLOWANCE = 250
26+
self.STEER_DRIVER_MULTIPLIER = 2
27+
self.STEER_THRESHOLD = 250
28+
1629
# To determine the limit for your car, find the maximum value that the stock LKAS will request.
1730
# If the max stock LKAS request is <384, add your car to this list.
18-
if CP.carFingerprint in HDA2_CAR:
19-
self.STEER_MAX = 150
2031
elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.HYUNDAI_GENESIS, CAR.ELANTRA_GT_I30, CAR.IONIQ,
21-
CAR.IONIQ_EV_LTD, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_HEV,
22-
CAR.KIA_OPTIMA_H, CAR.KIA_SORENTO, CAR.KIA_STINGER):
32+
CAR.IONIQ_EV_LTD, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_HEV,
33+
CAR.KIA_OPTIMA_H, CAR.KIA_SORENTO, CAR.KIA_STINGER):
2334
self.STEER_MAX = 255
2435
else:
2536
self.STEER_MAX = 384
26-
self.STEER_DELTA_UP = 3
27-
self.STEER_DELTA_DOWN = 7
28-
self.STEER_DRIVER_ALLOWANCE = 50
29-
self.STEER_DRIVER_MULTIPLIER = 2
30-
self.STEER_DRIVER_FACTOR = 1
3137

3238

3339
class CAR:
@@ -1140,9 +1146,6 @@ class Buttons:
11401146
b'\xf1\000DNhe SCC F-CUP 1.00 1.02 99110-L5000 ',
11411147
b'\xf1\x8799110L5000\xf1\000DNhe SCC F-CUP 1.00 1.02 99110-L5000 ',
11421148
],
1143-
(Ecu.esp, 0x7b0, None): [
1144-
b'\xf1\x87\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81\x00\x00\x00\x00\x00\x00\x00\x00',
1145-
],
11461149
(Ecu.eps, 0x7d4, None): [
11471150
b'\xf1\x8756310-L5500\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5500 4DNHC102',
11481151
b'\xf1\x8756310-L5450\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5450 4DNHC102',
@@ -1268,5 +1271,3 @@ class Buttons:
12681271
CAR.KIA_EV6: dbc_dict('kia_ev6', None),
12691272
CAR.SONATA_HYBRID: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'),
12701273
}
1271-
1272-
STEER_THRESHOLD = 150

selfdrive/car/interfaces.py

+36-23
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,36 @@
2020
MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS
2121
ACCEL_MAX = 2.0
2222
ACCEL_MIN = -3.5
23+
2324
TORQUE_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/params.yaml')
2425
TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/override.yaml')
2526
TORQUE_SUBSTITUTE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/substitute.yaml')
2627

2728

29+
def get_torque_params(candidate):
30+
with open(TORQUE_SUBSTITUTE_PATH) as f:
31+
sub = yaml.load(f, Loader=yaml.CSafeLoader)
32+
if candidate in sub:
33+
candidate = sub[candidate]
34+
35+
with open(TORQUE_PARAMS_PATH) as f:
36+
params = yaml.load(f, Loader=yaml.CSafeLoader)
37+
with open(TORQUE_OVERRIDE_PATH) as f:
38+
override = yaml.load(f, Loader=yaml.CSafeLoader)
39+
40+
# Ensure no overlap
41+
if sum([candidate in x for x in [sub, params, override]]) > 1:
42+
raise RuntimeError(f'{candidate} is defined twice in torque config')
43+
44+
if candidate in override:
45+
out = override[candidate]
46+
elif candidate in params:
47+
out = params[candidate]
48+
else:
49+
raise NotImplementedError(f"Did not find torque params for {candidate}")
50+
return {key: out[i] for i, key in enumerate(params['legend'])}
51+
52+
2853
# generic car and radar interfaces
2954

3055
class CarInterfaceBase(ABC):
@@ -85,7 +110,7 @@ def get_std_params(candidate, fingerprint):
85110
ret.steerControlType = car.CarParams.SteerControlType.torque
86111
ret.minSteerSpeed = 0.
87112
ret.wheelSpeedFactor = 1.0
88-
ret.maxLateralAccel = CarInterfaceBase.get_torque_params(candidate)['MAX_LAT_ACCEL_MEASURED']
113+
ret.maxLateralAccel = get_torque_params(candidate)['MAX_LAT_ACCEL_MEASURED']
89114

90115
ret.pcmCruise = True # openpilot's state is tied to the PCM's cruise state on most cars
91116
ret.minEnableSpeed = -1. # enable is done by stock ACC, so ignore this
@@ -110,28 +135,16 @@ def get_std_params(candidate, fingerprint):
110135
return ret
111136

112137
@staticmethod
113-
def get_torque_params(candidate, default=float('NaN')):
114-
with open(TORQUE_SUBSTITUTE_PATH) as f:
115-
sub = yaml.load(f, Loader=yaml.FullLoader)
116-
if candidate in sub:
117-
candidate = sub[candidate]
118-
119-
with open(TORQUE_PARAMS_PATH) as f:
120-
params = yaml.load(f, Loader=yaml.FullLoader)
121-
with open(TORQUE_OVERRIDE_PATH) as f:
122-
override = yaml.load(f, Loader=yaml.FullLoader)
123-
124-
# Ensure no overlap
125-
if sum([candidate in x for x in [sub, params, override]]) > 1:
126-
raise RuntimeError(f'{candidate} is defined twice in torque config')
127-
128-
if candidate in override:
129-
out = override[candidate]
130-
elif candidate in params:
131-
out = params[candidate]
132-
else:
133-
raise NotImplementedError(f"Did not find torque params for {candidate}")
134-
return {key:out[i] for i, key in enumerate(params['legend'])}
138+
def configure_torque_tune(candidate, tune, steering_angle_deadzone_deg=0.0):
139+
params = get_torque_params(candidate)
140+
141+
tune.init('torque')
142+
tune.torque.useSteeringAngle = True
143+
tune.torque.kp = 1.0 / params['LAT_ACCEL_FACTOR']
144+
tune.torque.kf = 1.0 / params['LAT_ACCEL_FACTOR']
145+
tune.torque.ki = 0.1 / params['LAT_ACCEL_FACTOR']
146+
tune.torque.friction = params['FRICTION']
147+
tune.torque.steeringAngleDeadzoneDeg = steering_angle_deadzone_deg
135148

136149
@abstractmethod
137150
def _update(self, c: car.CarControl) -> car.CarState:

selfdrive/car/torque_data/override.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
legend: [LAT_ACCEL_FACTOR, MAX_LAT_ACCEL_MEASURED, FRICTION]
22
### angle control
33
# Nissan appears to have torque
4-
NISSAN X-TRAIL 2017: [.nan, 1.5, .nan]
4+
NISSAN X-TRAIL 2017: [.nan, 1.5, .nan]
55
NISSAN ALTIMA 2020: [.nan, 1.5, .nan]
66
NISSAN LEAF 2018 Instrument Cluster: [.nan, 1.5, .nan]
77
NISSAN LEAF 2018: [.nan, 1.5, .nan]
@@ -20,7 +20,7 @@ FORD FOCUS 4TH GEN: [.nan, 1.5, .nan]
2020
COMMA BODY: [.nan, 1000, .nan]
2121

2222
# Totally new car
23-
KIA EV6 2022: [3.0, 2.5, 0.05]
23+
KIA EV6 2022: [3.0, 2.5, 0.0]
2424

2525
# Dashcam or fallback configured as ideal car
2626
mock: [10.0, 10, 0.0]

0 commit comments

Comments
 (0)