forked from IQ.Lvbs/IQ.Pilot
149 lines
4.7 KiB
Python
149 lines
4.7 KiB
Python
"""
|
|
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
|
"""
|
|
from typing import Literal
|
|
|
|
ModeType = Literal['acc', 'blended']
|
|
IQ_DYNAMIC_MODE_PARAM = "IQDynamicMode"
|
|
IQ_DYNAMIC_CONDITIONAL_CURVES_PARAM = "IQDynamicConditionalCurves"
|
|
IQ_DYNAMIC_CONDITIONAL_SLOWER_LEAD_PARAM = "IQDynamicConditionalSlowerLead"
|
|
IQ_DYNAMIC_CONDITIONAL_STOPPED_LEAD_PARAM = "IQDynamicConditionalStoppedLead"
|
|
IQ_DYNAMIC_CONDITIONAL_MODEL_STOPS_PARAM = "IQDynamicConditionalModelStops"
|
|
IQ_DYNAMIC_CONDITIONAL_SLC_FALLBACK_PARAM = "IQDynamicConditionalSLCFallback"
|
|
IQ_DYNAMIC_CONDITIONAL_SPEED_PARAM = "IQDynamicConditionalSpeed"
|
|
IQ_DYNAMIC_CONDITIONAL_LEAD_SPEED_PARAM = "IQDynamicConditionalLeadSpeed"
|
|
IQ_DYNAMIC_MODEL_STOP_TIME_PARAM = "IQDynamicModelStopTime"
|
|
IQ_DYNAMIC_MINIMUM_FORCE_STOP_LENGTH_PARAM = "IQDynamicMinimumForceStopLength"
|
|
IQ_FORCE_STOPS_PARAM = "IQForceStops"
|
|
|
|
|
|
class IQConstants:
|
|
CRUISING_SPEED = 3.0
|
|
SIGNAL_QUEUE_DEPTH = 6
|
|
LEAD_LOCK_GATE = 0.45
|
|
BRAKE_CURVE_QUEUE_DEPTH = 5
|
|
BRAKE_CURVE_GATE = 0.3
|
|
BRAKE_CURVE_SPEED_AXIS = [0., 10., 20., 30., 40., 50., 55., 60.]
|
|
BRAKE_CURVE_DISTANCE_AXIS = [32., 46., 64., 86., 108., 130., 145., 165.]
|
|
CRUISE_LAG_QUEUE_DEPTH = 10
|
|
CRUISE_LAG_GATE = 0.55
|
|
CRUISE_LAG_RATIO_GATE = 1.025
|
|
CONDITIONAL_SPEED_DEFAULT = 18.0
|
|
CONDITIONAL_LEAD_SPEED_DEFAULT = 24.0
|
|
MODEL_STOP_TIME_DEFAULT = 3.0
|
|
MODEL_STOP_THRESHOLD = 0.55
|
|
SLOW_LEAD_THRESHOLD = 0.55
|
|
FORCE_STOP_PLANNER_TIME = 3.0
|
|
MINIMUM_FORCE_STOP_LENGTH_DEFAULT = 0.0
|
|
|
|
|
|
def compute_slowdown_need(kph: float, horizon_dist: float, desired_dist: float) -> float:
|
|
if horizon_dist >= desired_dist or desired_dist <= 0.0:
|
|
return 0.0
|
|
|
|
shortage = desired_dist - horizon_dist
|
|
shortage_ratio = shortage / desired_dist
|
|
need = min(1.0, shortage_ratio * 2.0)
|
|
|
|
if horizon_dist < desired_dist * 0.3:
|
|
need = min(1.0, need * 2.0)
|
|
|
|
if kph > 25.0:
|
|
need = min(1.0, need * (1.0 + (kph - 25.0) / 80.0))
|
|
|
|
return need
|
|
|
|
|
|
class IQFilterEngine:
|
|
def __init__(self, initial=0.0, measurement_noise=0.1, process_noise=0.01, process_decay=1.0, smoothing_floor=0.85):
|
|
self._value = initial
|
|
self._variance = 1.0
|
|
self._measurement_noise = measurement_noise
|
|
self._process_noise = process_noise
|
|
self._process_decay = process_decay
|
|
self._smoothing_floor = smoothing_floor
|
|
self._initialized = False
|
|
self._samples = []
|
|
self._sample_limit = 10
|
|
self._confidence = 0.0
|
|
|
|
def push(self, measurement: float) -> None:
|
|
if len(self._samples) >= self._sample_limit:
|
|
self._samples.pop(0)
|
|
self._samples.append(measurement)
|
|
|
|
if not self._initialized:
|
|
self._value = measurement
|
|
self._initialized = True
|
|
self._confidence = 0.1
|
|
return
|
|
|
|
self._variance = self._process_decay * self._variance + self._process_noise
|
|
gain = self._variance / (self._variance + self._measurement_noise)
|
|
effective_gain = gain * (1.0 - self._smoothing_floor) + self._smoothing_floor * 0.1
|
|
|
|
innovation = measurement - self._value
|
|
self._value = self._value + effective_gain * innovation
|
|
self._variance = (1.0 - effective_gain) * self._variance
|
|
|
|
if abs(innovation) < 0.1:
|
|
self._confidence = min(1.0, self._confidence + 0.05)
|
|
else:
|
|
self._confidence = max(0.1, self._confidence - 0.02)
|
|
|
|
def value(self):
|
|
return self._value if self._initialized else None
|
|
|
|
def confidence(self) -> float:
|
|
return self._confidence
|
|
|
|
def reset(self) -> None:
|
|
self._initialized = False
|
|
self._samples = []
|
|
self._confidence = 0.0
|
|
|
|
|
|
class IQModeEngine:
|
|
def __init__(self):
|
|
self._state: ModeType = 'acc'
|
|
self._scores = {'acc': 1.0, 'blended': 0.0}
|
|
self._switching_timer = 0
|
|
self._mode_age = 0
|
|
self._forced_takeover = False
|
|
|
|
def request(self, mode: ModeType, urgency: float = 1.0, emergency: bool = False) -> None:
|
|
if emergency:
|
|
self._forced_takeover = True
|
|
self._state = mode
|
|
self._switching_timer = 15
|
|
self._mode_age = 0
|
|
return
|
|
|
|
self._scores[mode] = min(1.0, self._scores[mode] + 0.1 * urgency)
|
|
for key in self._scores:
|
|
if key != mode:
|
|
self._scores[key] = max(0.0, self._scores[key] - 0.05)
|
|
|
|
if self._mode_age < 10 and not self._forced_takeover:
|
|
return
|
|
|
|
threshold = 0.6 if mode != self._state else 0.3
|
|
if self._scores[mode] > threshold and mode != self._state and self._switching_timer == 0:
|
|
self._switching_timer = 15
|
|
self._state = mode
|
|
self._mode_age = 0
|
|
|
|
def update(self) -> None:
|
|
if self._switching_timer > 0:
|
|
self._switching_timer -= 1
|
|
|
|
self._mode_age += 1
|
|
if self._forced_takeover and self._mode_age > 20:
|
|
self._forced_takeover = False
|
|
|
|
for key in self._scores:
|
|
self._scores[key] *= 0.98
|
|
|
|
def get_mode(self) -> ModeType:
|
|
return self._state
|