mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-28 00:53:45 +08:00
fda22037d7
Route controller input and cruise actions through shared transport and mode-action owners. Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
148 lines
7.0 KiB
Python
148 lines
7.0 KiB
Python
import math
|
|
from dataclasses import dataclass
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CoastPlan:
|
|
force_decel: bool = False
|
|
brake_floor: float | None = None
|
|
speed_ceiling_mps: float | None = None
|
|
pulse_coasting: bool = False
|
|
|
|
|
|
class CoastOwner:
|
|
def __init__(self):
|
|
self.force_coast = False
|
|
self.pulse_glide = False
|
|
self.coasting = False
|
|
self.hill_paused = False
|
|
|
|
def reset(self):
|
|
self.__init__()
|
|
|
|
def toggle(self, action, *, long_active):
|
|
if action == 2:
|
|
self.force_coast = not self.force_coast
|
|
return True
|
|
if action == 14 and (long_active or self.pulse_glide):
|
|
self.pulse_glide = not self.pulse_glide
|
|
return True
|
|
return False
|
|
|
|
def sample(self, *, admitted, active, gas, brake, speed_mps, target_mps, delta_mps, pitch,
|
|
lead_relevant, stop_context):
|
|
values = (speed_mps, target_mps, delta_mps)
|
|
if (not admitted or not all(type(value) in (int, float) and math.isfinite(value) for value in values) or
|
|
speed_mps < 0 or delta_mps < 0 or type(active) is not bool or type(gas) is not bool or
|
|
type(brake) is not bool or type(lead_relevant) is not bool or type(stop_context) is not bool):
|
|
self.reset()
|
|
return CoastPlan()
|
|
self.force_coast &= not (gas or brake)
|
|
if not active:
|
|
return CoastPlan()
|
|
ceiling = None
|
|
if not self.pulse_glide:
|
|
self.coasting = False
|
|
self.hill_paused = False
|
|
else:
|
|
if type(pitch) in (int, float) and math.isfinite(pitch):
|
|
if self.hill_paused:
|
|
if abs(pitch) <= math.radians(2.5):
|
|
self.hill_paused = False
|
|
elif abs(pitch) >= math.radians(3.0):
|
|
self.hill_paused = True
|
|
lower = target_mps - delta_mps
|
|
if (self.hill_paused or target_mps <= 5.0 or lower < 3.0 or delta_mps <= 0 or
|
|
lead_relevant or stop_context):
|
|
self.coasting = False
|
|
elif self.coasting:
|
|
if speed_mps <= lower + 0.25:
|
|
self.coasting = False
|
|
elif speed_mps >= target_mps - 0.25:
|
|
self.coasting = True
|
|
if self.coasting:
|
|
ceiling = lower
|
|
return CoastPlan(self.force_coast, -0.6 if self.force_coast else -0.03 if self.coasting else None,
|
|
ceiling, self.coasting)
|
|
|
|
|
|
class CoastRuntime:
|
|
def __init__(self):
|
|
from openpilot.starpilot.controllers.wheel_actions import WheelConsumer
|
|
self.consumer = WheelConsumer()
|
|
self.owner = CoastOwner()
|
|
self.session = None
|
|
self.drive = 0
|
|
self.traffic_toggles = 0
|
|
self.cp_signature = None
|
|
self.conditional_stop = False
|
|
|
|
def receive(self, event, params, cp, sm, *, now_ns):
|
|
command = self.consumer.accept(event, params, cp, sm, now_ns=now_ns)
|
|
if command is None:
|
|
return False
|
|
if command.session != self.session or command.drive_id != self.drive:
|
|
self.owner.reset()
|
|
self.session, self.drive = command.session, command.drive_id
|
|
from openpilot.starpilot.controllers.wheel_actions import cp_fingerprint
|
|
self.cp_signature = cp_fingerprint(cp)
|
|
if command.action in (2, 6, 14):
|
|
from openpilot.starpilot.conditional_mode.ui_action import current_authority
|
|
from openpilot.starpilot.saved_source import read_saved
|
|
active_required = command.action == 6 or command.action == 14 and not self.owner.pulse_glide
|
|
if (not cp.openpilotLongitudinalControl or
|
|
read_saved(params, "SafeMode", 8) not in ((None, True), (b"0", True)) or
|
|
active_required and not current_authority(sm, cp, command.drive_id, now_ns)):
|
|
return False
|
|
if command.action == 6:
|
|
self.traffic_toggles += 1
|
|
return True
|
|
return self.owner.toggle(command.action, long_active=bool(sm["carControl"].longActive))
|
|
return False
|
|
|
|
def sample(self, params, cp, sm, *, now_ns, target_mps, lead_relevant, stop_context):
|
|
from openpilot.starpilot.controllers.wheel_actions import eligible, LIFETIME_NS, cp_fingerprint, WheelConsumer
|
|
from openpilot.starpilot.conditional_mode.ui_action import fresh_service
|
|
from openpilot.starpilot.saved_source import read_saved
|
|
# A zero accepted-drive/heartbeat cannot satisfy the admission below.
|
|
if (type(self) is CoastRuntime and type(getattr(self, 'owner', None)) is CoastOwner and
|
|
type(getattr(self, 'consumer', None)) is WheelConsumer and 'sample' not in vars(self.owner) and
|
|
type(getattr(self, 'drive', None)) is int and type(getattr(self.consumer, 'last_heartbeat', None)) is int and
|
|
(self.drive == 0 or self.consumer.last_heartbeat == 0) and
|
|
type(stop_context) is bool and type(getattr(self, 'conditional_stop', None)) is bool):
|
|
from capnp.lib.capnp import _DynamicStructReader
|
|
from opendbc.car.structs import car
|
|
from openpilot.cereal import log, messaging
|
|
from openpilot.common.params import Params
|
|
if (type(params) is Params and type(sm) is messaging.SubMaster and type(getattr(sm, 'data', None)) is dict and
|
|
type(cp) is _DynamicStructReader and cp.schema == car.CarParams.schema):
|
|
device = sm.data.get('deviceState')
|
|
state = sm.data.get('carState')
|
|
control = sm.data.get('carControl')
|
|
if (type(device) is _DynamicStructReader and device.schema == log.DeviceState.schema and
|
|
type(state) is _DynamicStructReader and state.schema == car.CarState.schema and
|
|
type(control) is _DynamicStructReader and control.schema == car.CarControl.schema):
|
|
self.owner.reset()
|
|
return CoastPlan()
|
|
drive = int(sm["deviceState"].startedMonoTime)
|
|
raw, readable = read_saved(params, "PulseGlideSpeedDelta", 32)
|
|
units, unit_readable = read_saved(params, "IsMetric", 8)
|
|
try:
|
|
delta = 5.0 if raw is None else float(raw)
|
|
except (ValueError, TypeError, OverflowError):
|
|
delta = math.nan
|
|
admitted = bool(eligible(cp) and self.cp_signature == cp_fingerprint(cp) and cp.openpilotLongitudinalControl and sm["deviceState"].started and
|
|
drive == self.drive and 0 < drive < self.consumer.last_heartbeat <= now_ns and
|
|
now_ns - self.consumer.last_heartbeat <= LIFETIME_NS and
|
|
all(fresh_service(sm, name, drive, now_ns) for name in ("carState", "carControl")) and
|
|
sm["carState"].canValid and not sm["carState"].canTimeout and
|
|
read_saved(params, "SafeMode", 8) in ((None, True), (b"0", True)) and
|
|
readable and unit_readable and units in (None, b"0", b"1") and
|
|
math.isfinite(delta) and 0.5 <= delta <= 30.0)
|
|
state, control = sm["carState"], sm["carControl"]
|
|
pitch = control.orientationNED[1] if len(control.orientationNED) == 3 else None
|
|
return self.owner.sample(admitted=admitted, active=bool(control.enabled and control.longActive),
|
|
gas=bool(state.gasPressed), brake=bool(state.brakePressed), speed_mps=float(state.vEgo),
|
|
target_mps=target_mps, delta_mps=delta * (1 / 3.6 if units == b"1" else 0.44704),
|
|
pitch=pitch, lead_relevant=lead_relevant, stop_context=bool(stop_context or self.conditional_stop))
|