IQ.Pilot Release Commit @ 0798119
This commit is contained in:
0
iqpilot/selfdrive/car/__init__.py
Normal file
0
iqpilot/selfdrive/car/__init__.py
Normal file
67
iqpilot/selfdrive/car/enhanced_stock_longitudinal_control.py
Normal file
67
iqpilot/selfdrive/car/enhanced_stock_longitudinal_control.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.selfdrive.car.cruise import V_CRUISE_MAX
|
||||
|
||||
from iqdbc.car import structs
|
||||
|
||||
ENHANCED_STOCK_LONGITUDINAL_CONTROL_SET_SPEED_KPH_KEY = "enhancedStockLongitudinalControl.setSpeedKph"
|
||||
|
||||
|
||||
def _float_param(key: str, value: float) -> dict[str, object]:
|
||||
return {"key": key, "type": "float", "value": f"{float(value):.3f}".encode("utf-8")}
|
||||
|
||||
|
||||
def _clamp_set_speed_kph(value: float) -> float:
|
||||
return max(0.0, min(V_CRUISE_MAX, float(value)))
|
||||
|
||||
|
||||
def build_iq_control_params_from_plan(CP: structs.CarParams, iq_plan, selfdrive_enabled: bool,
|
||||
current_set_speed_kph: float, previous_sync_limit_kph: float | None,
|
||||
pending_sync_limit_kph: float | None) -> tuple[list[dict[str, object]], float | None, float | None]:
|
||||
if not CP.openpilotLongitudinalControl or not selfdrive_enabled:
|
||||
return [], None, None
|
||||
|
||||
resolver = getattr(getattr(iq_plan, "speedLimit", None), "resolver", None)
|
||||
assist = getattr(getattr(iq_plan, "speedLimit", None), "assist", None)
|
||||
if resolver is None or assist is None:
|
||||
return [], None, None
|
||||
|
||||
speed_limit_final_last = float(getattr(resolver, "speedLimitFinalLast", 0.0) or 0.0)
|
||||
assist_enabled = bool(getattr(assist, "enabled", False))
|
||||
if not assist_enabled or speed_limit_final_last <= 0.0:
|
||||
return [], None, None
|
||||
|
||||
resolved_limit_kph = _clamp_set_speed_kph(speed_limit_final_last * CV.MS_TO_KPH)
|
||||
limit_changed = previous_sync_limit_kph is None or abs(resolved_limit_kph - previous_sync_limit_kph) > 0.05
|
||||
if limit_changed:
|
||||
pending_sync_limit_kph = resolved_limit_kph
|
||||
|
||||
if pending_sync_limit_kph is not None:
|
||||
if abs(current_set_speed_kph - pending_sync_limit_kph) <= 0.25:
|
||||
pending_sync_limit_kph = None
|
||||
set_speed_kph = _clamp_set_speed_kph(current_set_speed_kph or resolved_limit_kph)
|
||||
else:
|
||||
set_speed_kph = pending_sync_limit_kph
|
||||
else:
|
||||
set_speed_kph = _clamp_set_speed_kph(current_set_speed_kph or resolved_limit_kph)
|
||||
|
||||
return [_float_param(ENHANCED_STOCK_LONGITUDINAL_CONTROL_SET_SPEED_KPH_KEY, set_speed_kph)], resolved_limit_kph, pending_sync_limit_kph
|
||||
|
||||
|
||||
def get_set_speed_kph_from_params(params) -> float | None:
|
||||
for param in params:
|
||||
key = param.key if hasattr(param, "key") else param.get("key")
|
||||
if key != ENHANCED_STOCK_LONGITUDINAL_CONTROL_SET_SPEED_KPH_KEY:
|
||||
continue
|
||||
raw_value = param.value if hasattr(param, "value") else param.get("value")
|
||||
try:
|
||||
raw = raw_value.decode("utf-8") if isinstance(raw_value, (bytes, bytearray)) else str(raw_value)
|
||||
value = float(raw)
|
||||
except (AttributeError, TypeError, ValueError):
|
||||
return None
|
||||
return _clamp_set_speed_kph(value)
|
||||
return None
|
||||
52
iqpilot/selfdrive/car/gap_button_actions.py
Normal file
52
iqpilot/selfdrive/car/gap_button_actions.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
|
||||
Maps the distance/gap steering-wheel button to an IQ.Pilot action: holding it for
|
||||
long enough toggles Experimental mode exactly once per hold. Only active when
|
||||
IQ.Pilot owns longitudinal control and cruise is available.
|
||||
"""
|
||||
from cereal import car, custom
|
||||
from iqdbc.car import structs
|
||||
from openpilot.common.params import Params
|
||||
|
||||
_Button = car.CarState.ButtonEvent.Type
|
||||
_IQEvent = custom.IQOnroadEvent.EventName
|
||||
_GAP_BUTTON = _Button.gapAdjustCruise
|
||||
|
||||
HOLD_FRAMES_TO_TOGGLE = 50
|
||||
|
||||
|
||||
class GapButtonActions:
|
||||
def __init__(self, CP: structs.CarParams):
|
||||
self._CP = CP
|
||||
self._params = Params()
|
||||
self._gap_hold_frames = 0
|
||||
self._already_toggled = False
|
||||
# read (and cleared) by the personality-decrement handler in selfdrived so a
|
||||
# release that ends a toggle-hold does not also decrement personality
|
||||
self.experimental_mode_switched = False
|
||||
|
||||
def update(self, CS, events, experimental_mode) -> None:
|
||||
if not (self._CP.openpilotLongitudinalControl and CS.cruiseState.available):
|
||||
return
|
||||
self._advance_hold(CS)
|
||||
self._toggle_experimental_on_long_hold(events, experimental_mode)
|
||||
|
||||
def _advance_hold(self, CS) -> None:
|
||||
# once counting, keep incrementing each frame the hold persists
|
||||
if self._gap_hold_frames > 0:
|
||||
self._gap_hold_frames += 1
|
||||
# a fresh press seeds the counter; a release zeroes it
|
||||
for be in CS.buttonEvents:
|
||||
if be.type.raw == _GAP_BUTTON:
|
||||
self._gap_hold_frames = int(be.pressed)
|
||||
if not be.pressed:
|
||||
self._already_toggled = False
|
||||
|
||||
def _toggle_experimental_on_long_hold(self, events, experimental_mode) -> None:
|
||||
if self._already_toggled or self._gap_hold_frames < HOLD_FRAMES_TO_TOGGLE:
|
||||
return
|
||||
self._params.put_bool_nonblocking("ExperimentalMode", not experimental_mode)
|
||||
events.add(_IQEvent.experimentalToggled)
|
||||
self._already_toggled = True
|
||||
self.experimental_mode_switched = True
|
||||
76
iqpilot/selfdrive/car/interfaces.py
Normal file
76
iqpilot/selfdrive/car/interfaces.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from iqdbc.car import structs as _dbc
|
||||
from openpilot.common.params import Params as _Store
|
||||
from openpilot.common.swaglog import cloudlog as _log
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_torque import get_nn_model_path as _resolve_nn
|
||||
|
||||
import openpilot.system.sentry as _telemetry
|
||||
|
||||
_ANGLE = _dbc.CarParams.SteerControlType.angle
|
||||
|
||||
# Port tunables surfaced to the fingerprint step, flat so the read is one pass.
|
||||
_TUNABLES = (
|
||||
"IQHyundaiLongTune",
|
||||
"IQSubaruCreepAssist",
|
||||
"IQSubaruCreepAssistManualBrake",
|
||||
"IQTeslaTorqueBlend",
|
||||
"IQToyotaFactoryLong",
|
||||
"ToyotaSnGHack",
|
||||
)
|
||||
|
||||
|
||||
def initialize_params(store):
|
||||
return [{name: store.get(name, return_default=True)} for name in _TUNABLES]
|
||||
|
||||
|
||||
def log_fingerprint(cp) -> None:
|
||||
ident = cp.carFingerprint
|
||||
if ident == "MOCK":
|
||||
_telemetry.capture_fingerprint_mock()
|
||||
else:
|
||||
_telemetry.capture_fingerprint(ident, cp.brand)
|
||||
|
||||
|
||||
def set_speed_limit_controller_availability(cp, cp_iq, store=None) -> bool:
|
||||
"""Gate the speed-limit controller off on platforms that can't run it, dropping a
|
||||
stuck 'control' mode down to 'warning'."""
|
||||
store = store or _Store()
|
||||
brand = cp.brand
|
||||
off = (brand == "rivian"
|
||||
or (brand == "tesla" and store.get_bool("IsReleaseIqBranch"))
|
||||
or (not cp.openpilotLongitudinalControl and cp_iq.pcmCruiseSpeed))
|
||||
if off and store.get("IQSpeedAssistMode", return_default=True) == 3: # control -> warning
|
||||
store.put("IQSpeedAssistMode", 2)
|
||||
return not off
|
||||
|
||||
|
||||
def _stamp_lateral_model(cp, cp_iq, store) -> bool:
|
||||
where, label, precise = _resolve_nn(cp)
|
||||
nn = cp_iq.iqLateralNet
|
||||
nn.model.path, nn.model.name, nn.fuzzyFingerprint = where, label, not precise
|
||||
if label == "MOCK":
|
||||
_log.error({"nnff event": "car doesn't match any Neural Network model"})
|
||||
return False
|
||||
return cp.steerControlType != _ANGLE and store.get_bool("NeuralNetworkFeedForward")
|
||||
|
||||
|
||||
def _cleanup_unsupported_params(cp, cp_iq, store=None) -> None:
|
||||
store = store or _Store()
|
||||
doomed = {
|
||||
"NeuralNetworkFeedForward": cp.steerControlType == _ANGLE,
|
||||
"LongIncrementsEnabled": not cp.openpilotLongitudinalControl and cp_iq.pcmCruiseSpeed,
|
||||
}
|
||||
for name, gone in doomed.items():
|
||||
if gone:
|
||||
_log.warning(f"unsupported on this port, clearing {name}")
|
||||
store.remove(name)
|
||||
set_speed_limit_controller_availability(cp, cp_iq, store)
|
||||
|
||||
|
||||
def apply_iq_car_config(ci, store=None) -> None:
|
||||
store = store or _Store()
|
||||
if _stamp_lateral_model(ci.CP, ci.CP_IQ, store):
|
||||
ci.configure_torque_tune(ci.CP.carFingerprint, ci.CP.lateralTuning)
|
||||
_cleanup_unsupported_params(ci.CP, ci.CP_IQ, store)
|
||||
58
iqpilot/selfdrive/car/long_increments.py
Normal file
58
iqpilot/selfdrive/car/long_increments.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from dataclasses import dataclass
|
||||
|
||||
from openpilot.common.params import Params
|
||||
|
||||
# Cruise set-speed step (in the caller's working unit, kph or the imperial increment)
|
||||
# a user is allowed to dial in for the accel/decel cruise buttons.
|
||||
MIN_BUTTON_STEP = 1
|
||||
MAX_BUTTON_STEP = 10
|
||||
|
||||
# Once the resolved step reaches this size, we snap the set speed to the nearest
|
||||
# multiple of the step (e.g. a step of 5 lands on 45/50/55...) instead of just
|
||||
# adding it on top of whatever odd number the set speed currently sits at.
|
||||
SNAP_TO_GRID_THRESHOLD = 5
|
||||
|
||||
# Stock behavior (feature disabled): tap moves by one unit, a held button moves
|
||||
# five times faster. This mirrors what every other unmodified button-input car
|
||||
# already does, so it's kept as the fallback rather than living in this module.
|
||||
STOCK_HOLD_MULTIPLIER = 5
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LongIncrementConfig:
|
||||
enabled: bool
|
||||
tap_step: int
|
||||
hold_step: int
|
||||
|
||||
|
||||
def _clamp_step(value) -> int:
|
||||
try:
|
||||
step = int(value)
|
||||
except (TypeError, ValueError):
|
||||
return MIN_BUTTON_STEP
|
||||
return min(max(step, MIN_BUTTON_STEP), MAX_BUTTON_STEP)
|
||||
|
||||
|
||||
def read_long_increment_config(params: Params) -> LongIncrementConfig:
|
||||
return LongIncrementConfig(
|
||||
enabled=params.get_bool("LongIncrementsEnabled"),
|
||||
tap_step=_clamp_step(params.get("LongIncrementTapStep", return_default=True)),
|
||||
hold_step=_clamp_step(params.get("LongIncrementHoldStep", return_default=True)),
|
||||
)
|
||||
|
||||
|
||||
def resolve_button_step(config: LongIncrementConfig, held: bool, unit_step: float) -> tuple[bool, float]:
|
||||
"""
|
||||
Turn a single tap/hold cruise button event into a (snap_to_grid, delta) pair,
|
||||
where delta is expressed in the same unit as unit_step (kph, or the mph-derived
|
||||
increment used for imperial cars).
|
||||
"""
|
||||
if not config.enabled:
|
||||
return held, unit_step * (STOCK_HOLD_MULTIPLIER if held else 1)
|
||||
|
||||
multiplier = config.hold_step if held else config.tap_step
|
||||
snap_to_grid = multiplier >= SNAP_TO_GRID_THRESHOLD
|
||||
return snap_to_grid, unit_step * multiplier
|
||||
26
iqpilot/selfdrive/car/refresh_car_list.py
Normal file
26
iqpilot/selfdrive/car/refresh_car_list.py
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.iqpilot.selfdrive.car.vehicle_catalog import load_catalog
|
||||
|
||||
|
||||
def refresh_car_list_param() -> None:
|
||||
platforms = load_catalog()
|
||||
if not platforms:
|
||||
cloudlog.warning("vehicle catalog not found; leaving CarList param unchanged")
|
||||
return
|
||||
|
||||
params = Params()
|
||||
if params.get("CarList") == platforms:
|
||||
cloudlog.warning("CarList param already current, nothing to write")
|
||||
return
|
||||
|
||||
params.put("CarList", platforms)
|
||||
cloudlog.warning("CarList param refreshed from vehicle catalog")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
refresh_car_list_param()
|
||||
0
iqpilot/selfdrive/car/tests/__init__.py
Normal file
0
iqpilot/selfdrive/car/tests/__init__.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from cereal import custom
|
||||
from iqdbc.car import structs
|
||||
|
||||
from openpilot.iqpilot.selfdrive.car.interfaces import _cleanup_unsupported_params
|
||||
|
||||
|
||||
class DummyParams:
|
||||
def __init__(self):
|
||||
self.removed: list[str] = []
|
||||
self.values: dict[str, object] = {}
|
||||
|
||||
def remove(self, key: str) -> None:
|
||||
self.removed.append(key)
|
||||
|
||||
def get_bool(self, key: str) -> bool:
|
||||
return bool(self.values.get(key, False))
|
||||
|
||||
def get(self, key: str, return_default: bool = False):
|
||||
return self.values.get(key)
|
||||
|
||||
def put(self, key: str, value) -> None:
|
||||
self.values[key] = value
|
||||
|
||||
|
||||
class TestLongitudinalModePersistence:
|
||||
def test_iq_dynamic_mode_is_not_removed_when_openpilot_long_is_unavailable(self):
|
||||
params = DummyParams()
|
||||
cp = structs.CarParams()
|
||||
cp.openpilotLongitudinalControl = False
|
||||
cp.steerControlType = structs.CarParams.SteerControlType.torque
|
||||
|
||||
cp_iq = custom.IQCarParams()
|
||||
cp_iq.pcmCruiseSpeed = True
|
||||
|
||||
_cleanup_unsupported_params(cp, cp_iq, params)
|
||||
|
||||
assert "IQDynamicMode" not in params.removed
|
||||
assert "LongIncrementsEnabled" in params.removed
|
||||
121
iqpilot/selfdrive/car/tests/test_speed_limit_set_speed.py
Normal file
121
iqpilot/selfdrive/car/tests/test_speed_limit_set_speed.py
Normal file
@@ -0,0 +1,121 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from cereal import car, custom
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.iqpilot.selfdrive.car.enhanced_stock_longitudinal_control import build_iq_control_params_from_plan
|
||||
from openpilot.selfdrive.car.cruise import VCruiseHelper
|
||||
|
||||
|
||||
class TestSpeedLimitSetSpeedMirror:
|
||||
def setup_method(self):
|
||||
self.CP = car.CarParams(pcmCruise=True, openpilotLongitudinalControl=True)
|
||||
self.CP_IQ = custom.IQCarParams(pcmCruiseSpeed=True)
|
||||
self.v_cruise_helper = VCruiseHelper(self.CP, self.CP_IQ)
|
||||
self.v_cruise_helper.set_speed_to_limit = True
|
||||
|
||||
@staticmethod
|
||||
def _iq_plan(limit_mps: float, state) -> SimpleNamespace:
|
||||
resolver = SimpleNamespace(
|
||||
speedLimitValid=limit_mps > 0,
|
||||
speedLimitLastValid=limit_mps > 0,
|
||||
speedLimitFinalLast=limit_mps,
|
||||
)
|
||||
assist = SimpleNamespace(state=state)
|
||||
return SimpleNamespace(speedLimit=SimpleNamespace(resolver=resolver, assist=assist))
|
||||
|
||||
def test_op_long_mirrors_active_speed_limit_target_into_cluster_speed(self):
|
||||
self.v_cruise_helper.update_speed_limit_assist(False, self._iq_plan(17.88, custom.IQPlan.SpeedLimit.AssistState.active))
|
||||
|
||||
CS = car.CarState(cruiseState={"available": True, "speed": 22.35, "speedCluster": 22.35})
|
||||
self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False)
|
||||
|
||||
assert self.v_cruise_helper.v_cruise_kph == pytest.approx(17.88 * CV.MS_TO_KPH, abs=0.1)
|
||||
assert self.v_cruise_helper.v_cruise_cluster_kph == pytest.approx(17.88 * CV.MS_TO_KPH, abs=0.1)
|
||||
|
||||
def test_op_long_syncs_to_new_limit_even_when_assist_not_active(self):
|
||||
self.v_cruise_helper.update_speed_limit_assist(False, self._iq_plan(17.88, custom.IQPlan.SpeedLimit.AssistState.inactive))
|
||||
|
||||
CS = car.CarState(cruiseState={"available": True, "speed": 22.35, "speedCluster": 22.35})
|
||||
self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False)
|
||||
|
||||
assert self.v_cruise_helper.v_cruise_kph == pytest.approx(17.88 * CV.MS_TO_KPH, abs=0.1)
|
||||
assert self.v_cruise_helper.v_cruise_cluster_kph == pytest.approx(17.88 * CV.MS_TO_KPH, abs=0.1)
|
||||
|
||||
def test_op_long_allows_manual_set_speed_changes_between_limit_changes(self):
|
||||
self.v_cruise_helper.update_speed_limit_assist(False, self._iq_plan(17.88, custom.IQPlan.SpeedLimit.AssistState.inactive))
|
||||
|
||||
# First cycle after a valid limit appears will sync to the resolved target.
|
||||
CS = car.CarState(cruiseState={"available": True, "speed": 22.35, "speedCluster": 22.35})
|
||||
self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False)
|
||||
|
||||
# On later cycles with the same limit, manual set speed changes should be preserved.
|
||||
CS = car.CarState(cruiseState={"available": True, "speed": 15.64, "speedCluster": 15.64})
|
||||
self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False)
|
||||
|
||||
assert self.v_cruise_helper.v_cruise_kph == pytest.approx(15.64 * CV.MS_TO_KPH, abs=0.1)
|
||||
assert self.v_cruise_helper.v_cruise_cluster_kph == pytest.approx(15.64 * CV.MS_TO_KPH, abs=0.1)
|
||||
|
||||
def test_op_long_resyncs_when_limit_changes(self):
|
||||
self.v_cruise_helper.update_speed_limit_assist(False, self._iq_plan(17.88, custom.IQPlan.SpeedLimit.AssistState.inactive))
|
||||
|
||||
CS = car.CarState(cruiseState={"available": True, "speed": 22.35, "speedCluster": 22.35})
|
||||
self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False)
|
||||
|
||||
CS = car.CarState(cruiseState={"available": True, "speed": 15.64, "speedCluster": 15.64})
|
||||
self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False)
|
||||
|
||||
self.v_cruise_helper.update_speed_limit_assist(False, self._iq_plan(13.41, custom.IQPlan.SpeedLimit.AssistState.inactive))
|
||||
self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False)
|
||||
|
||||
assert self.v_cruise_helper.v_cruise_kph == pytest.approx(13.41 * CV.MS_TO_KPH, abs=0.1)
|
||||
assert self.v_cruise_helper.v_cruise_cluster_kph == pytest.approx(13.41 * CV.MS_TO_KPH, abs=0.1)
|
||||
|
||||
|
||||
def test_set_speed_does_not_follow_limit_when_feature_off():
|
||||
# Default off: set speed must stay the driver's value (limiter-only via planner min-blend).
|
||||
CP = car.CarParams(pcmCruise=True, openpilotLongitudinalControl=True)
|
||||
CP_IQ = custom.IQCarParams(pcmCruiseSpeed=True)
|
||||
helper = VCruiseHelper(CP, CP_IQ)
|
||||
helper.set_speed_to_limit = False
|
||||
helper.update_speed_limit_assist(False, TestSpeedLimitSetSpeedMirror._iq_plan(
|
||||
17.88, custom.IQPlan.SpeedLimit.AssistState.active))
|
||||
|
||||
CS = car.CarState(cruiseState={"available": True, "speed": 22.35, "speedCluster": 22.35})
|
||||
helper.update_v_cruise(CS, enabled=True, is_metric=False)
|
||||
|
||||
# Set speed tracks the car's cruise speed, NOT the 17.88 m/s limit.
|
||||
assert helper.v_cruise_kph == pytest.approx(22.35 * CV.MS_TO_KPH, abs=0.1)
|
||||
|
||||
|
||||
def test_enhanced_stock_longitudinal_control_syncs_once_then_follows_cluster_speed():
|
||||
CP = car.CarParams(pcmCruise=True, openpilotLongitudinalControl=True)
|
||||
resolver = SimpleNamespace(speedLimitFinalLast=17.88)
|
||||
assist = SimpleNamespace(enabled=True)
|
||||
iq_plan = SimpleNamespace(speedLimit=SimpleNamespace(resolver=resolver, assist=assist))
|
||||
|
||||
params, sync_limit, pending_limit = build_iq_control_params_from_plan(
|
||||
CP, iq_plan, True, current_set_speed_kph=100.0, previous_sync_limit_kph=None, pending_sync_limit_kph=None
|
||||
)
|
||||
assert sync_limit == pytest.approx(17.88 * CV.MS_TO_KPH, abs=0.1)
|
||||
assert pending_limit == pytest.approx(17.88 * CV.MS_TO_KPH, abs=0.1)
|
||||
assert float(params[0]["value"].decode("utf-8")) == pytest.approx(17.88 * CV.MS_TO_KPH, abs=0.1)
|
||||
|
||||
params, sync_limit, pending_limit = build_iq_control_params_from_plan(
|
||||
CP, iq_plan, True, current_set_speed_kph=22.0, previous_sync_limit_kph=sync_limit, pending_sync_limit_kph=pending_limit
|
||||
)
|
||||
assert sync_limit == pytest.approx(17.88 * CV.MS_TO_KPH, abs=0.1)
|
||||
assert pending_limit == pytest.approx(17.88 * CV.MS_TO_KPH, abs=0.1)
|
||||
assert float(params[0]["value"].decode("utf-8")) == pytest.approx(17.88 * CV.MS_TO_KPH, abs=0.1)
|
||||
|
||||
params, sync_limit, pending_limit = build_iq_control_params_from_plan(
|
||||
CP, iq_plan, True, current_set_speed_kph=17.88 * CV.MS_TO_KPH, previous_sync_limit_kph=sync_limit, pending_sync_limit_kph=pending_limit
|
||||
)
|
||||
assert sync_limit == pytest.approx(17.88 * CV.MS_TO_KPH, abs=0.1)
|
||||
assert pending_limit is None
|
||||
|
||||
params, sync_limit, pending_limit = build_iq_control_params_from_plan(
|
||||
CP, iq_plan, True, current_set_speed_kph=22.0, previous_sync_limit_kph=sync_limit, pending_sync_limit_kph=pending_limit
|
||||
)
|
||||
assert float(params[0]["value"].decode("utf-8")) == pytest.approx(22.0, abs=0.1)
|
||||
4888
iqpilot/selfdrive/car/vehicle_catalog.json
Normal file
4888
iqpilot/selfdrive/car/vehicle_catalog.json
Normal file
File diff suppressed because it is too large
Load Diff
85
iqpilot/selfdrive/car/vehicle_catalog.py
Normal file
85
iqpilot/selfdrive/car/vehicle_catalog.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
|
||||
SCHEMA = "iqlvbs/supported-vehicles"
|
||||
REV = 1
|
||||
|
||||
CATALOG_FILENAME = "vehicle_catalog.json"
|
||||
_CANDIDATE_PARTS = (
|
||||
("iqpilot", "selfdrive", "car", CATALOG_FILENAME),
|
||||
)
|
||||
|
||||
# in-memory (car-interface) field -> on-disk compact key
|
||||
_ATTR_TO_KEY = (
|
||||
("platform", "id"),
|
||||
("make", "mk"),
|
||||
("brand", "grp"),
|
||||
("model", "mdl"),
|
||||
("year", "yrs"),
|
||||
("package", "req"),
|
||||
)
|
||||
|
||||
|
||||
def _reference(platform: str, years: list[str], claimed: set[str]) -> str:
|
||||
span = f"{years[0]}-{years[-1]}" if len(years) > 1 else (years[0] if years else "na")
|
||||
stem = f"{platform}|{span}"
|
||||
ref, bump = stem, 2
|
||||
while ref in claimed:
|
||||
ref = f"{stem}#{bump}"
|
||||
bump += 1
|
||||
claimed.add(ref)
|
||||
return ref
|
||||
|
||||
|
||||
def encode(vehicles: dict[str, dict]) -> dict:
|
||||
records: dict[str, dict] = {}
|
||||
claimed: set[str] = set()
|
||||
for label, attrs in vehicles.items():
|
||||
years = list(attrs.get("year") or [])
|
||||
ref = _reference(attrs.get("platform", ""), years, claimed)
|
||||
record = {"label": label}
|
||||
for attr, key in _ATTR_TO_KEY:
|
||||
record[key] = attrs.get(attr)
|
||||
records[ref] = record
|
||||
return {"catalog": SCHEMA, "rev": REV, "vehicles": records}
|
||||
|
||||
|
||||
def decode(envelope: dict) -> dict[str, dict]:
|
||||
vehicles: dict[str, dict] = {}
|
||||
for record in (envelope.get("vehicles") or {}).values():
|
||||
attrs = {attr: record.get(key) for attr, key in _ATTR_TO_KEY}
|
||||
vehicles[record.get("label", "")] = attrs
|
||||
return vehicles
|
||||
|
||||
|
||||
def catalog_path(basedir: str = BASEDIR) -> str | None:
|
||||
for parts in _CANDIDATE_PARTS:
|
||||
candidate = os.path.join(basedir, *parts)
|
||||
if os.path.isfile(candidate):
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def load_catalog(basedir: str = BASEDIR) -> dict[str, dict]:
|
||||
path = catalog_path(basedir)
|
||||
if path is None:
|
||||
return {}
|
||||
with open(path) as handle:
|
||||
return decode(json.load(handle))
|
||||
|
||||
|
||||
def _write(vehicles: dict[str, dict], basedir: str = BASEDIR) -> str:
|
||||
out = os.path.join(basedir, "iqpilot", "selfdrive", "car", CATALOG_FILENAME)
|
||||
with open(out, "w") as handle:
|
||||
json.dump(encode(vehicles), handle, indent=2, ensure_ascii=False)
|
||||
return out
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from iqdbc.lvbs.car.car_catalog import build_car_catalog
|
||||
print("wrote", _write(build_car_catalog()))
|
||||
Reference in New Issue
Block a user