Compare commits

..

19 Commits

Author SHA1 Message Date
firestarsdog 5b896476d6 rm historic adopted speed on engage 2026-11-09 00:08:14 -05:00
whoisdomi 7e72a4bd8e Force stop fix 2 2026-11-08 15:54:38 -06:00
whoisdomi d7d1fa02a1 Twitch 5 2026-11-08 13:17:07 -06:00
whoisdomi c86a4ac8d6 Force Stop Signs behind leads 2026-11-08 12:35:58 -06:00
firestarsdog 3513840095 build 2026-11-08 05:03:34 -05:00
firestarsdog 4e4d258ad3 Visual Polish + Suppress "LAST" in UI 2026-11-08 03:47:24 -05:00
whoisdomi 54f3897174 twitch 4 2026-11-08 03:47:24 -05:00
firestarsdog 891454e47c Align speed HUD for clearer SLC prompts 2026-11-08 03:47:24 -05:00
whoisdomi ec0221c16f twitch 3 2026-11-08 03:47:24 -05:00
whoisdomi 00d46b482d Force Stop: use Vision Stop distance 2026-11-08 03:47:24 -05:00
whoisdomi f831010290 force stop edge case 2026-11-08 03:47:24 -05:00
whoisdomi 233a386ab4 twitch 2 fix 2026-11-08 03:47:24 -05:00
whoisdomi 2a4676dfca Force Stop fix 2026-11-08 03:47:24 -05:00
whoisdomi 3426045d1e twitch 2 2026-11-08 03:47:24 -05:00
firestarsdog a400b93be7 Build? lol 2026-11-08 03:47:23 -05:00
firestarsdog fc981552b3 Clarify SLC targets and confirmation prompts 2026-11-08 03:46:02 -05:00
firestarsdog e54b3f9e36 Normalize stopped speeds to preserve SLC overrides 2026-11-08 03:46:02 -05:00
firestarsdog 5f3ea81785 SLC Squish 2026-11-08 03:46:01 -05:00
whoisdomi 64df004be1 No twitch test? 2026-11-08 03:46:01 -05:00
339 changed files with 9408 additions and 11070 deletions
-2
View File
@@ -62,8 +62,6 @@ jobs:
run: >-
tools/op.sh test
openpilot/starpilot/flm/tests/test_offroad_backend.py
openpilot/starpilot/flm/tests/test_offroad_coordinate_policy.py
openpilot/starpilot/flm/tests/test_offroad_equal_native_output.py
--json-output ci-results/flm-offroad.json
- name: Test completed-drive and steering-result presentation
timeout-minutes: 1
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+5 -1
View File
@@ -360,6 +360,7 @@ class CarController(CarControllerBase):
self.volt_cc_cancel_pending = False
self.volt_cc_last_cancel_frame = 0
self.volt_cc_metric = False
self.volt_cc_speed_ceiling_mps = None
self.volt_cc_last_button_frame = 0
self.volt_cc_last_direction_frame = None
self.volt_cc_consumed_source_ns = 0
@@ -1396,7 +1397,10 @@ class CarController(CarControllerBase):
self.volt_cc_consumed_source_ns = source_ns
can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.POWERTRAIN, (CS.buttons_counter + 1) % 4, button))
elif self.frame % 4 == 0:
button, rate = button_request(CS.out.vEgo, CS.out.cruiseState.speed, actuators.accel, CS.out.vCruise,
cruise_kph = CS.out.vCruise
if self.volt_cc_speed_ceiling_mps is not None:
cruise_kph = min(cruise_kph, self.volt_cc_speed_ceiling_mps * CV.MS_TO_KPH)
button, rate = button_request(CS.out.vEgo, CS.out.cruiseState.speed, actuators.accel, cruise_kph,
bool(CC.hudControl.leadVisible), self.volt_cc_metric)
source_ns = physical.button_credit_ns
if ((self.frame - self.volt_cc_last_button_frame) * DT_CTRL > rate and
@@ -335,8 +335,8 @@ def test_actual_card_active_observation_requires_fresh_selfdrive_state(monkeypat
host.observe_ioniq6_long_authority = lambda *args: None
host.update_vehicle_state_context = lambda *args: None
def update_cruise_speed(_state, _pending, *, approved=None):
assert approved is None
def update_cruise_speed(_state, _pending, *, wheel_context=None):
assert wheel_context is None
host.update_cruise_speed = update_cruise_speed
host.v_cruise_helper = SimpleNamespace(slc_consumed_button=None, toyota=None)
host.startup_panda_configured = lambda: True
@@ -118,7 +118,6 @@ class CarController(CarControllerBase):
self.angle_vm = VehicleModel(CP) if CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING else None
self.apply_angle_last = 0.0
self.angle_gain_last = 0.0
self.angle_fault_driver_override = False
self.angle_tx_recovery = (AngleTxRecovery(0xCB, self.CAN.ECAN)
if pe_long_qualified(CP) or ev9_long_qualified(CP) else AngleTxRecovery(0x110, self.CAN.ACAN)
if CP.carFingerprint in (CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9) and
@@ -437,8 +436,6 @@ class CarController(CarControllerBase):
# steering control
if ev9_long:
self.angle_fault_driver_override = bool(CS.out.steeringPressed and (
self.angle_fault_driver_override or CS.out.steerFaultTemporary or CS.out.steerFaultPermanent))
drive = CS.out.gearShifter == structs.CarState.GearShifter.drive
measured = CS.angle_steering_angle
if self.angle_tx_recovery is not None and self.angle_tx_recovery.take_rejection(self.frame):
@@ -453,8 +450,7 @@ class CarController(CarControllerBase):
ev9_lateral_allowed = lateral_request_allowed(CC, CS.out, CS.angle_steering_fault,
independent=(ev9_long_aol_qualified(self.CP, marked_only=True) or
pe_long_aol_qualified(self.CP, marked_only=True)))
active = bool(ev9_lateral_allowed and CC.latActive and not self.angle_fault_driver_override and
abs(measured) <= bound and abs(self.apply_angle_last) <= bound)
active = bool(ev9_lateral_allowed and CC.latActive and abs(measured) <= bound and abs(self.apply_angle_last) <= bound)
self.ev9_angle_filter.update_alpha(float(np.interp(CS.out.vEgo, [5.0, 10.0, 20.0], [0.2, 0.1, 0.0])))
desired = self.ev9_angle_filter.update(float(np.clip(CC.actuators.steeringAngleDeg,
-(self.params.ANGLE_LIMITS.STEER_ANGLE_MAX if pe_long else EV9_OP_LONG_ANGLE_TARGET_MAX),
+1 -2
View File
@@ -348,10 +348,9 @@ class CarState(CarStateBase):
self.lkas11 = copy.copy(cp_cam.vl["LKAS11"])
self.clu11 = copy.copy(cp.vl["CLU11"])
from opendbc.car.hyundai.classic_long_aol import independent_buttons_qualified
from opendbc.car.hyundai.blended_stock_aol import qualified_alpha
self.ev6_aol_samples = ()
self.ev6_aol_source_healthy = False
if independent_buttons_qualified(self.CP) or qualified_alpha(self.CP, marked_only=True):
if independent_buttons_qualified(self.CP):
from opendbc.can.parser import MAX_BAD_COUNTER
source = cp.message_states.get(cp.dbc.name_to_msg['CLU11'].address)
self.ev6_aol_sample_stamp_ns = int(source.timestamps[-1]) if source is not None and source.timestamps else 0
@@ -58,8 +58,6 @@ def test_actual_controller_fault_and_full_lateral_withdrawal_restart_gain_from_z
cs = ci.CS
cs.out.vEgoRaw = cs.out.vEgo = 5.
cs.out.steeringAngleDeg = cs.angle_steering_angle = 20.
cs.out.steeringPressed = False
cs.out.steeringTorque = 0.
command = structs.CarControl(enabled=True, latActive=True)
command.actuators.steeringAngleDeg = 50.
ci.CC.apply_angle_last = 20.
@@ -126,59 +124,3 @@ def test_long_controller_consumes_current_rejection_without_retaining_filtered_h
_, frames = ci.apply(command.as_reader(), now[0])
assert abs(ci.CC.apply_angle_last + 20.) <= 5.
assert ci.CC.angle_gain_last == .004
@pytest.mark.parametrize('identity', (CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9))
@pytest.mark.parametrize('long', (True,))
@pytest.mark.parametrize('aol', (False, True))
def test_fault_recovery_yields_until_driver_releases_steering(identity, long, aol):
ci, _ = controller(identity, long, aol)
cs = ci.CS
cs.out.vEgoRaw = cs.out.vEgo = 3.
cs.out.steeringAngleDeg = cs.angle_steering_angle = 175.
cs.out.steeringPressed = True
cs.out.steeringTorque = 500.
command = structs.CarControl(enabled=not aol, latActive=True)
command.actuators.steeringAngleDeg = 360.
ci.CC.apply_angle_last = 175.
ci.CC.angle_gain_last = .1
address, gain_index = (0xCB, 6) if long else (0x110, 12)
active_index, active_mask = (3, 15) if long else (9, 3)
for frame in range(80):
cs.out.steerFaultTemporary = cs.angle_steering_fault = frame % 40 < 16
cs.out.steeringAngleDeg = cs.angle_steering_angle = 175. - frame * 3.
ci.CC.frame = frame + 21
_, output = ci.apply(command.as_reader(), 200_000_000 + frame * 10_000_000)
payload = next((data for addr, data, _ in output if addr == address), None)
if payload is not None:
assert (payload[active_index] >> 4 & active_mask) == 1, 'FAULT_RECOVERY_FIGHTS_DRIVER'
assert payload[gain_index] == 0
assert ci.CC.apply_angle_last == pytest.approx(cs.out.steeringAngleDeg)
cs.out.steerFaultTemporary = cs.angle_steering_fault = False
cs.out.steeringPressed = False
cs.out.steeringTorque = 0.
ci.CC.frame += 1
_, output = ci.apply(command.as_reader(), 1_000_000_000)
payload = next(data for addr, data, _ in output if addr == address)
assert (payload[active_index] >> 4 & active_mask) == 2
assert payload[gain_index] == 1
assert abs(ci.CC.apply_angle_last - cs.out.steeringAngleDeg) <= 5.
@pytest.mark.parametrize('identity', (CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9))
@pytest.mark.parametrize('long', (False, True))
def test_driver_pressure_without_fault_keeps_existing_angle_blending(identity, long):
ci, _ = controller(identity, long, True)
cs = ci.CS
cs.out.vEgoRaw = cs.out.vEgo = 3.
cs.out.steeringAngleDeg = cs.angle_steering_angle = 20.
cs.out.steeringPressed = True
cs.out.steeringTorque = 500.
command = structs.CarControl(enabled=False, latActive=True)
command.actuators.steeringAngleDeg = 40.
ci.CC.apply_angle_last = 20.
ci.CC.frame = 21
_, output = ci.apply(command.as_reader(), 200_000_000)
address, active_index, active_mask = (0xCB, 3, 15) if long else (0x110, 9, 3)
payload = next(data for addr, data, _ in output if addr == address)
assert (payload[active_index] >> 4 & active_mask) == 2
@@ -870,7 +870,7 @@ class TestIoniq6PrepublicationHandoff(unittest.TestCase):
card_owner.is_metric = True
card_owner.experimental_mode = False
card_owner.initial_cruise_speed = 40
card_owner.v_cruise_helper = SimpleNamespace(update_v_cruise=lambda *_, slc_enabled=False, slc_approved=None: None,
card_owner.v_cruise_helper = SimpleNamespace(update_v_cruise=lambda *_, slc_enabled=False, wheel_context=None: None,
initialize_v_cruise=lambda *_, **kwargs: None, slc_consumed_button=None, toyota=None,
slc_cruise_change=None, v_cruise_kph=60.0, v_cruise_cluster_kph=60.0)
card_owner.RI = SimpleNamespace(update=lambda _: None)
@@ -1,14 +1,14 @@
"""StarPilot adapter for jacobw's jul-angle-based Crosstrek control.
Source: jacobwaller/opendbc 8acbeccdab45a47aa7af170d2d17b61f0eb5e4f0.
Original low-speed filtering: lukasloetkolben; target-first limiting: elkoled.
Low-speed filtering: lukasloetkolben; target-first limiting: elkoled.
Jacob Waller integrated the implementation and corrected its VM limit type.
Keep StarPilot's model, stricter rate, and withdrawal guards. See CREDITS.md.
"""
import numpy as np
from opendbc.car import rate_limit
from opendbc.car.lateral import get_max_angle_delta_vm, get_max_angle_vm
from opendbc.car.lateral import apply_center_deadzone, get_max_angle_delta_vm, get_max_angle_vm
MAX_ANGLE = 190.0
@@ -16,6 +16,9 @@ MAX_ANGLE = 190.0
def limit_angle(target, previous, speed, measured, active, limits, model):
if not active:
return float(np.clip(measured, -MAX_ANGLE, MAX_ANGLE)), False
if speed < 10.0:
deadzone = float(np.interp(speed, [2.0, 10.0], [6.0, 3.0]))
target = previous + apply_center_deadzone(target - previous, deadzone)
speed = max(speed, 1.0)
maximum = min(MAX_ANGLE, get_max_angle_vm(speed, model, limits))
delta = min(get_max_angle_delta_vm(speed, model, limits), limits.ANGLE_LIMITS.MAX_ANGLE_RATE)
@@ -95,5 +95,5 @@ class TestIndependentAngleEnvelope(unittest.TestCase):
self.assertTrue(decoded['LKAS_Request'])
if car == CAR.SUBARU_CROSSTREK_2025:
# The EPS bound applies to transmitted commands, not measured-angle admission.
# Follow the small target without jumping to the measured angle.
self.assertAlmostEqual(decoded['LKAS_Output'], -1.45)
# Preserve the zero command history: this target is inside the low-speed deadzone.
self.assertEqual(decoded['LKAS_Output'], 0.)
@@ -12,17 +12,19 @@ from opendbc.safety.tests.libsafety import libsafety_py
from openpilot.starpilot.car.subaru.tests.caller_helpers import CompleteCan
@pytest.mark.parametrize('speed', (1.0, 2.0, 6.0, 9.0))
@pytest.mark.parametrize('speed,deadzone', ((1.0, 6.0), (2.0, 6.0), (6.0, 4.5), (9.0, 3.375)))
@pytest.mark.parametrize('sign', (-1, 1))
def test_real_controller_tracks_small_low_speed_changes(speed, sign):
def test_real_controller_suppresses_small_low_speed_changes(speed, deadzone, sign):
_, controller, command, state = setup_controller(CAR.SUBARU_CROSSTREK_2025)
state.out.vEgoRaw = speed
command.actuators.steeringAngleDeg = sign * 2.0
for _ in range(20):
_, decoded = step(controller, command, state)
assert decoded['LKAS_Request'] == 1
assert sign * decoded['LKAS_Output'] > 0
assert decoded['LKAS_Output'] == pytest.approx(sign * 2.0, abs=.01)
command.actuators.steeringAngleDeg = sign * (deadzone - .01)
_, decoded = step(controller, command, state)
assert decoded['LKAS_Request'] == 1
assert decoded['LKAS_Output'] == 0
command.actuators.steeringAngleDeg = sign * (deadzone + .01)
_, decoded = step(controller, command, state)
assert decoded['LKAS_Request'] == 1
assert 0 < sign * decoded['LKAS_Output'] <= 1.0
def test_target_first_limits_recover_to_moving_acceleration_bound():
+1 -2
View File
@@ -9,8 +9,7 @@ from opendbc.car.tesla.values import CAR, DBC, CANBUS, GEAR_MAP, STEER_THRESHOLD
class TeslaScreenCANParser(CANParser):
def __init__(self):
# Screen gestures are optional; their silence is not a control-bus fault.
super().__init__('tesla_model3_vehicle', [('UI_status2', float('nan'))], CANBUS.vehicle)
super().__init__('tesla_model3_vehicle', [('UI_status2', 0)], CANBUS.vehicle)
def update(self, strings, sendcan=False):
if strings and not isinstance(strings[0], list | tuple):
@@ -53,7 +53,7 @@ def test_actual_automatic_ap1_vin_fw_can_and_factory_use_normal_wiring(alpha, re
assert ci.CP.carFw and all(fw.brand == 'tesla' and fw.bus == 0 for fw in ci.CP.carFw)
assert any(fw.fwVersion == HW1_FIRMWARE for fw in ci.CP.carFw)
assert ci.CP.safetyConfigs[0].safetyParam == 16 + int(alpha)
assert ci.CP.openpilotLongitudinalControl == alpha and ci.CP.pcmCruise
assert ci.CP.openpilotLongitudinalControl == alpha and ci.CP.pcmCruise != alpha
@pytest.mark.parametrize('source', list(HW1_NORMAL_BUS0_LAYOUT))
@@ -80,36 +80,3 @@ def test_literal_optional_screen_frames_baseline_edge_and_exact_dlc():
(structs.CarState.ButtonEvent.Type.lkas, True)]
parser.update([(1_030_000_000, [(frame[0], frame[1][:-1], 1), (frame[0], frame[1], 0)])])
assert cs.update_screen_button(parser) == []
@pytest.mark.parametrize('screen_seen', [False, True])
def test_optional_screen_silence_does_not_report_vehicle_bus_loss(screen_seen):
parser = TeslaScreenCANParser()
if screen_seen:
parser.update([(1_000_000_000, [(0x3DF, bytes(8), CANBUS.vehicle)])])
for stamp in (1_500_000_001, 12_000_000_000):
parser.update([(stamp, [(0x111, bytes(8), CANBUS.vehicle)])])
assert not parser.bus_timeout
assert parser.can_valid
assert parser.vl_all['UI_status2']['UI_activeTouchPoints'] == []
@pytest.mark.parametrize('missing_bus', [None, CANBUS.party, CANBUS.autopilot_party])
def test_screen_silence_keeps_required_vehicle_bus_timeouts(missing_bus):
cp = screen_params()
apply_screen_button(cp, True, False)
ci = CarInterface(cp)
packer = CANPacker('tesla_model3_party')
messages = {
CANBUS.party: ['DI_speed', 'DI_systemStatus', 'ESP_B', 'EPAS3S_sysStatus', 'ESP_status', 'UI_warning', 'DI_state'],
CANBUS.autopilot_party: ['SCCM_steeringAngleSensor', 'DAS_status', 'DAS_control', 'DAS_steeringControl', 'DAS_settings'],
}
for index in range(150):
frames = [packer.make_can_msg(name, bus, {}) for bus, names in messages.items()
if index < 50 or bus != missing_bus for name in names]
if index == 0:
frames.append((0x3DF, bytes(8), CANBUS.vehicle))
result = ci.update([(1_000_000_000 + index * 10_000_000, frames)])
assert result.canTimeout is (missing_bus is not None)
if missing_bus is None:
assert result.canValid
@@ -231,15 +231,12 @@ typedef void (*rx_hook)(const CANPacket_t *msg);
typedef bool (*tx_hook)(const CANPacket_t *msg); // returns true if the message is allowed
typedef bool (*fwd_hook)(int bus_num, int addr); // returns true if the message should be blocked from forwarding
typedef bool (*fwd_packet_hook)(const CANPacket_t *msg);
typedef struct {
safety_hook_init init;
rx_hook rx;
rx_hook optional_rx;
tx_hook tx;
fwd_hook fwd;
fwd_packet_hook fwd_packet;
get_checksum_t get_checksum;
compute_checksum_t compute_checksum;
get_counter_t get_counter;
@@ -370,7 +367,6 @@ extern uint16_t current_safety_param;
extern safety_config current_safety_config;
int safety_fwd_hook(int bus_num, int addr);
int safety_fwd_packet_hook(const CANPacket_t *msg);
int set_safety_hooks(uint16_t mode, uint16_t param);
extern const safety_hooks body_hooks;
@@ -970,7 +970,6 @@ static void hyundai_optional_rx_hook(const CANPacket_t *msg) {
}
const safety_hooks hyundai_hooks = {
.fwd_packet = hyundai_daw_fwd,
.fwd = blended_aol_fwd,
.init = hyundai_init,
.optional_rx = hyundai_optional_rx_hook,
@@ -982,7 +981,6 @@ const safety_hooks hyundai_hooks = {
};
const safety_hooks hyundai_legacy_hooks = {
.fwd_packet = hyundai_daw_fwd,
.init = hyundai_legacy_init,
.optional_rx = hyundai_optional_rx_hook,
.rx = hyundai_rx_hook,
@@ -158,29 +158,10 @@ static void blended_aol_rx(const CANPacket_t *msg) {
bool pressed = false;
if ((msg->bus == pt) && (msg->addr == 0x4F1U) && (GET_LEN(msg) == 4U)) {
if ((msg->data[0] & 7U) == 4U) {
// Mixed LONG Pause/Resume still clears ordinary/LONG authority in the
// cruise hook. Retain only an already claimed, current lateral token.
// CANCEL cannot arm, renew a request/gesture lease, or clear an inhibit.
const bool source_current = (blended_aol_token_source < 3U) &&
(safety_get_ts_elapsed(now, blended_aol_source_ts[blended_aol_token_source]) <= 300000U);
const bool retain_lateral = blended_aol_long && !GET_BIT(msg, 3U) &&
blended_aol_token && blended_aol_claimed && blended_aol_session && heartbeat_engaged &&
blended_aol_main_seen && blended_aol_main &&
(safety_get_ts_elapsed(now, blended_aol_main_ts) <= 100000U) && source_current &&
blended_aol_request_seen && ((aol_host_axis_mask & 1U) != 0U) &&
(safety_get_ts_elapsed(now, aol_host_request_ts) <= AOL_HOST_REQUEST_TIMEOUT_US) &&
aol_rx_healthy() && !relay_malfunction && !safety_rx_checks_invalid;
if (!retain_lateral) {
blended_aol_clear();
}
// A simultaneous MAIN is not an arming gesture. The advancing physical
// source remains observable without changing its token or host lease.
blended_aol_pressed[0] = GET_BIT(msg, 3U);
blended_aol_source_ts[0] = now;
} else {
source = 0U;
pressed = GET_BIT(msg, 3U);
blended_aol_clear();
}
source = 0U;
pressed = GET_BIT(msg, 3U);
} else if ((msg->bus == pt) && (msg->addr == 0x391U) && (GET_LEN(msg) == 8U)) {
source = 1U;
pressed = GET_BIT(msg, 4U);
@@ -14,7 +14,6 @@ static uint32_t hyundai_daw_tx_ts;
static bool hyundai_daw_seen;
static bool hyundai_daw_counter_seen;
static bool hyundai_daw_sent;
static uint8_t hyundai_daw_last_classic_tx[6];
static void hyundai_daw_init(bool canfd, bool enabled) {
hyundai_daw_addr = enabled ? (canfd ? 0x11AU : 0x53EU) : 0U;
@@ -97,31 +96,10 @@ static bool hyundai_daw_tx(const CANPacket_t *msg) {
if (valid) {
hyundai_daw_sent = true;
hyundai_daw_tx_ts = now;
if (!canfd) {
(void)memcpy(hyundai_daw_last_classic_tx, msg->data, 6U);
}
}
return valid;
}
static bool hyundai_daw_fwd(const CANPacket_t *msg) {
const uint32_t now = microsecond_timer_get();
bool blocked = (msg->bus == 2U) && (msg->addr == 0x53EU) && (GET_LEN(msg) == 6U) && (hyundai_daw_addr == 0x53EU) &&
(hyundai_daw_len == 6U) && hyundai_daw_seen && hyundai_daw_sent &&
!relay_malfunction && !safety_rx_checks_invalid &&
(safety_get_ts_elapsed(now, hyundai_daw_rx_ts) <= 100000U) &&
(safety_get_ts_elapsed(now, hyundai_daw_tx_ts) <= 100000U);
if (blocked) {
const uint8_t status = msg->data[5] & 7U;
blocked = (status >= 1U) && (status <= 3U);
for (unsigned int i = 0U; i < 6U; i++) {
const uint8_t mask = (i == 5U) ? 0xF8U : 0xFFU;
blocked &= (msg->data[i] & mask) == (hyundai_daw_last_classic_tx[i] & mask);
}
}
return blocked;
}
static safety_config hyundai_daw_configure(safety_config ret, bool canfd, bool enabled) {
hyundai_daw_init(canfd, false);
if (enabled && (ret.tx_msgs_len > 0) && (ret.tx_msgs_len < 64)) {
@@ -13,7 +13,6 @@ static bool tesla_screen_belt_latched = false;
static bool tesla_screen_eps_ready = false;
static bool tesla_screen_session = false;
static bool tesla_screen_autopark = false;
static bool tesla_screen_acc_fault = false;
static int tesla_screen_override = 0;
static void tesla_screen_clear(void) {
@@ -33,7 +32,6 @@ static void tesla_screen_reset(void) {
tesla_screen_eps_ready = false;
tesla_screen_session = false;
tesla_screen_autopark = false;
tesla_screen_acc_fault = false;
}
static uint8_t tesla_screen_request(void) {
@@ -56,7 +54,7 @@ static uint8_t tesla_screen_request(void) {
static bool tesla_screen_physical_ready(void) {
return tesla_screen_drive && tesla_screen_occupant_ready && tesla_screen_eps_ready &&
!steering_disengage && !tesla_screen_autopark && !tesla_screen_acc_fault;
!steering_disengage && !tesla_screen_autopark;
}
static uint8_t tesla_screen_permission(void) {
@@ -110,17 +108,14 @@ static void tesla_screen_rx(const CANPacket_t *msg, bool autopark) {
}
if (msg_matches(msg, 0x286U, 0U, 8U)) {
const unsigned int state = (msg->data[1] >> 4U) & 7U;
tesla_screen_acc_fault = state == 5U;
const bool cruise = ((state == 2U) || (state == 3U) || (state == 4U) || (state == 6U) || (state == 7U)) && !tesla_screen_autopark;
const bool main_available = ((state == 1U) || cruise) && !tesla_screen_autopark;
if (cruise && !tesla_screen_cruise) {
tesla_screen_override = 0;
}
// Stock availability cannot revoke an explicit screen selection.
if (tesla_screen_main && !main_available && !brake_pressed && (tesla_screen_override != 1)) {
if (tesla_screen_main && !main_available && !brake_pressed) {
tesla_screen_override = -1;
}
if (tesla_screen_acc_fault) { tesla_screen_clear(); }
tesla_screen_main = main_available;
tesla_screen_cruise = cruise;
}
@@ -4,9 +4,6 @@ static bool vw_aol_enabled = false;
static bool vw_aol_longitudinal = false;
static bool vw_aol_long_armed = false;
static uint8_t vw_aol_buttons = 0U;
static bool vw_aol_pending = false;
static bool vw_aol_session = false;
static uint32_t vw_aol_grant_ts = 0U;
static bool vw_aol_manual = false;
static bool vw_aol_direct = false;
static bool vw_aol_direct_counter_seen = false;
@@ -30,8 +27,6 @@ static void vw_aol_clear(void) {
vw_aol_token = false;
vw_aol_long_armed = false;
vw_aol_buttons = 0U;
vw_aol_pending = false;
vw_aol_session = false;
vw_aol_main_neutral = false;
vw_aol_owned = false;
for (unsigned int i = 0U; i < 3U; i++) {
@@ -41,7 +36,6 @@ static void vw_aol_clear(void) {
static void vw_aol_reset(void) {
vw_aol_clear();
vw_aol_grant_ts = 0U;
vw_aol_enabled = false;
vw_aol_initial_main = true;
vw_aol_longitudinal = false;
@@ -83,23 +77,12 @@ static void vw_aol_request(uint8_t mask) {
static uint8_t vw_aol_request_mask(void) {
const bool expired = vw_aol_requested &&
(safety_get_ts_elapsed(microsecond_timer_get(), aol_host_request_ts) > AOL_HOST_REQUEST_TIMEOUT_US);
const uint32_t grant_age = safety_get_ts_elapsed(microsecond_timer_get(), vw_aol_grant_ts);
const bool pending_expired = vw_aol_pending && (grant_age > AOL_HOST_REQUEST_TIMEOUT_US);
const bool vw_controls_heartbeat_lost = !heartbeat_engaged && (!vw_aol_longitudinal || vw_aol_session);
uint8_t request = 0U;
if (!vw_aol_enabled || relay_malfunction || expired || safety_rx_checks_invalid ||
vw_controls_heartbeat_lost || pending_expired || !aol_rx_healthy() || !vw_aol_current()) {
!heartbeat_engaged || !aol_rx_healthy() || !vw_aol_current()) {
vw_aol_clear();
} else if (heartbeat_engaged) {
// A physical release may precede the host join; it cannot authorize output.
const bool acknowledged = !vw_aol_pending || (((aol_host_axis_mask & 2U) != 0U) &&
(safety_get_ts_elapsed(microsecond_timer_get(), aol_host_request_ts) < grant_age));
if (acknowledged) {
vw_aol_pending = false;
vw_aol_session = true;
request = aol_host_axis_mask;
}
} else {
request = aol_host_axis_mask;
}
return request;
}
@@ -178,15 +161,14 @@ static void vw_aol_rx(const CANPacket_t *msg) {
vw_aol_cruise_engaged = engaged;
vw_aol_seen |= 1U;
vw_aol_main_ts = microsecond_timer_get();
if ((!heartbeat_engaged && (!vw_aol_longitudinal || vw_aol_session)) ||
!aol_rx_healthy() || !vw_aol_current() || safety_rx_checks_invalid) {
if (!heartbeat_engaged || !aol_rx_healthy() || !vw_aol_current() || safety_rx_checks_invalid) {
vw_aol_clear();
} else if (!acc_main_on) {
vw_aol_initial_main = false;
vw_aol_clear();
// Only a valid OFF observation may prepare a subsequent main-ON edge.
vw_aol_main_neutral = ((msg->data[3] & 7U) < 2U);
} else if (heartbeat_engaged && vw_aol_initial_main && vw_aol_gear_drive && vw_aol_eps_ready && !brake_pressed) {
} else if (vw_aol_initial_main && vw_aol_gear_drive && vw_aol_eps_ready && !brake_pressed) {
// Fresh complete physical MAIN-on startup grants LAT only. It never restores a cleared drive session.
vw_aol_initial_main = false;
vw_aol_token = true;
@@ -204,19 +186,15 @@ static void vw_aol_rx(const CANPacket_t *msg) {
vw_aol_initial_main = false;
vw_aol_clear();
} else if (vw_aol_longitudinal) {
const bool healthy = (heartbeat_engaged || !vw_aol_session) && !relay_malfunction && aol_rx_healthy() && vw_aol_current() && !safety_rx_checks_invalid;
const bool healthy = heartbeat_engaged && aol_rx_healthy() && vw_aol_current() && !safety_rx_checks_invalid;
const uint8_t buttons = (GET_BIT(msg, 16U) ? 1U : 0U) | (GET_BIT(msg, 19U) ? 2U : 0U);
if (!healthy) {
vw_aol_clear();
} else if (buttons == 0U) {
if (((vw_aol_buttons & 3U) != 0U) && controls_allowed && acc_main_on &&
vw_aol_gear_drive && vw_aol_eps_ready && (!brake_pressed || (!vw_aol_manual && !vehicle_moving))) {
vw_aol_initial_main = false;
vw_aol_token = true;
vw_aol_long_armed = true;
vw_aol_pending = !heartbeat_engaged;
vw_aol_session = heartbeat_engaged;
vw_aol_grant_ts = microsecond_timer_get();
}
vw_aol_buttons = 4U;
} else if ((vw_aol_buttons & 4U) != 0U) {
-8
View File
@@ -358,14 +358,6 @@ int safety_fwd_hook(int bus_num, int addr) {
return blocked ? -1 : destination_bus;
}
int safety_fwd_packet_hook(const CANPacket_t *msg) {
int destination_bus = safety_fwd_hook(msg->bus, msg->addr);
if ((destination_bus >= 0) && (current_hooks->fwd_packet != NULL) && current_hooks->fwd_packet(msg)) {
destination_bus = -1;
}
return destination_bus;
}
// 1Hz safety function called by main. Now just a check for lagging safety messages
void safety_tick(void) {
const uint8_t MAX_MISSED_MSGS = 10U;
@@ -10,7 +10,6 @@ SAFETY_UNUSED(heartbeat_engaged);
SAFETY_UNUSED(safety_rx_hook);
SAFETY_UNUSED(safety_tx_hook);
SAFETY_UNUSED(safety_fwd_hook);
SAFETY_UNUSED(safety_fwd_packet_hook);
SAFETY_UNUSED(safety_tick);
SAFETY_UNUSED(set_safety_hooks);
// Exported to Panda's USB status handler and the native safety test library.
@@ -313,8 +313,8 @@ class TestHyundaiBlendedAlphaAol(unittest.TestCase):
self.assertEqual(self.request(1), 0)
self.assertFalse(self.safety.get_controls_allowed())
def test_main_off_and_heartbeat_loss_revoke_physical_token(self):
for cause in ('main_off', 'heartbeat'):
def test_main_off_cancel_and_heartbeat_loss_revoke_physical_token(self):
for cause in ('main_off', 'cancel', 'heartbeat'):
self.now = 1_000_000
self.counter = 0
self.reset()
@@ -325,98 +325,13 @@ class TestHyundaiBlendedAlphaAol(unittest.TestCase):
continue
if cause == 'main_off':
self.feed(available=False)
elif cause == 'cancel':
self.feed(button=4)
else:
self.safety.set_aol_test_heartbeat(False)
self.assertEqual(self.request(1), 0)
self.assertFalse(self.safety.get_controls_allowed())
def test_cancel_retains_only_existing_claimed_lateral_and_stops_long(self):
for word in (0x2004, 0x2014):
for held in (7, 23): # Both reported short and longer Pause/Resume holds.
self.counter, self.now = 0, 1_000_000
self.reset(word)
self.warm()
self.feed(lda=True)
self.assertEqual(self.request(1), 0 if self.release else 1)
if self.release:
continue
self.feed(button=2)
self.feed()
self.assertTrue(self.safety.get_controls_allowed())
self.assertEqual(self.request(3), 3)
for _ in range(held):
self.feed(button=4)
self.assertFalse(self.safety.get_controls_allowed())
self.assertEqual(self.request(1), 1)
self.feed()
self.assertFalse(self.safety.get_controls_allowed())
self.assertEqual(self.request(1), 1)
if self.bus:
self.primary_counter = 0
steering = TestHyundaiBlendedAlpha.primary(self, 1, True)
else:
from opendbc.car.hyundai.hyundaican import hyundai_checksum
def steering_checksum(message):
address, data, bus = message
return address, bytes([hyundai_checksum(data[1:8])]) + data[1:8], bus
steering = self.packer.make_can_msg_safety('LKAS11', 0,
{'CF_Lkas_MsgCount': 0, 'CR_Lkas_StrToqReq': 1, 'CF_Lkas_ActToi': 1}, fix_checksum=steering_checksum)
self.assertTrue(self.safety.safety_tx_hook(steering), 'Only retained LAT can authorize steering')
accel = self.packer.make_can_msg_safety('SCC11', self.bus, {'aReqRaw': .1})
self.assertFalse(self.safety.safety_tx_hook(accel))
self.assertEqual(self.request(0), 0) # Configured cancellation remains authoritative.
def test_cancel_cannot_arm_or_restore_expired_or_withdrawn_authority(self):
for word in (0x2004, 0x2014):
for cause in ('cold', 'unclaimed', 'withdrawn', 'stale_request', 'heartbeat', 'main_off', 'relay'):
with self.subTest(word=word, cause=cause):
self.counter, self.now = 0, 1_000_000
self.reset(word)
self.warm()
if cause != 'cold':
self.feed(lda=True)
if cause not in ('cold', 'unclaimed'):
self.assertEqual(self.request(1), 0 if self.release else 1)
if self.release:
continue
if cause == 'withdrawn':
self.request(0)
elif cause == 'stale_request':
for _ in range(32):
self.feed() # Fresh main and token source, without a host renewal.
elif cause == 'heartbeat':
self.safety.set_aol_test_heartbeat(False)
elif cause == 'relay':
self.safety.set_relay_malfunction(True)
self.feed(button=4, available=cause != 'main_off')
self.assertEqual(self.request(1), 0, cause)
def test_cancel_cannot_restore_expired_source_simultaneous_main_or_bad_rx(self):
for word in (0x2004, 0x2014):
for cause in ('source', 'simultaneous_main', 'bad_rx'):
with self.subTest(word=word, cause=cause):
self.counter, self.now = 0, 1_000_000
self.reset(word)
self.warm()
self.feed(lda=True)
self.assertEqual(self.request(1), 0 if self.release else 1)
if self.release:
continue
if cause == 'source':
for _ in range(32):
self.feed(omit='BCM_PO_11')
self.safety.aol_set_host_request(1) # Fresh request, expired physical LDA source.
elif cause == 'bad_rx':
invalid = self.packer.make_can_msg_safety('TCS13', self.bus, {'AliveCounterTCS': self.counter % 8}, fix_checksum=checksum)
invalid[0].data[6] ^= 1
self.assertFalse(self.safety.safety_rx_hook(invalid))
packet = self.packer.make_can_msg_safety('CLU11', self.bus,
{'CF_Clu_CruiseSwState': 4, 'CF_Clu_CruiseSwMain': int(cause == 'simultaneous_main'),
'CF_Clu_AliveCnt1': self.counter % 16})
self.assertTrue(self.safety.safety_rx_hook(packet))
self.assertEqual(self.request(1), 0, cause)
def test_cluster_lda_source_needs_its_own_neutral_edge(self):
self.reset()
self.warm()
@@ -136,75 +136,3 @@ def test_one_genuine_counter_predecessor_cannot_hide_newer_hands_off_request():
owner.update([(BASE + 30_000_000, [(0x11A, bytes(hands_off), 1)])])
_, current_output, _ = advisory_candidate(owner, BASE + 30_000_000)
assert safety.safety_tx_hook(packet(0x11A, current_output, 1))
@pytest.mark.parametrize('mode,word', [(structs.CarParams.SafetyModel.hyundai, 0),
(structs.CarParams.SafetyModel.hyundai, 0xC02), (structs.CarParams.SafetyModel.hyundaiLegacy, 0)])
@pytest.mark.parametrize('status', range(8))
def test_classic_advisory_forwarding_uses_only_current_accepted_replacement(mode, word, status):
safety, _ = setup(False, word)
assert safety.set_safety_hooks(mode, word) == 0
safety.init_tests()
safety.set_timer(1_000_000)
owner = DawSource(params(False))
original = source(False, 0)
incoming = packet(0x53E, original, 2)
assert safety.safety_fwd_packet_hook(incoming) == 0
safety.safety_rx_hook(incoming)
owner.update([(BASE, [(0x53E, original, 2)])])
addr, output, bus = advisory_candidate(owner, BASE)
assert safety.safety_tx_hook(packet(addr, output, bus))
safety.set_timer(1_010_000)
incoming = packet(0x53E, source(False, status), 2)
expected = -1 if 1 <= status <= 3 else 0
# Both Panda drivers forward before their RX hook sees the current packet.
assert safety.safety_fwd_packet_hook(incoming) == expected
safety.safety_rx_hook(incoming)
assert safety.safety_fwd_packet_hook(incoming) == expected
assert safety.safety_fwd_packet_hook(packet(0x53E, source(False, status), 0)) == 2
safety.set_timer(1_100_001)
assert safety.safety_fwd_packet_hook(incoming) == 0
@pytest.mark.parametrize('change', ['opaque', 'length', 'reset', 'no_replacement', 'expired_source', 'relay', 'hands_off', 'unknown'])
def test_classic_advisory_forwarding_fails_open_without_exact_live_replacement(change):
safety, _ = setup(False, 0xC02)
owner = DawSource(params(False))
original = source(False, 1)
safety.safety_rx_hook(packet(0x53E, original, 2))
owner.update([(BASE, [(0x53E, original, 2)])])
addr, output, bus = advisory_candidate(owner, BASE)
if change != 'no_replacement':
assert safety.safety_tx_hook(packet(addr, output, bus))
safety.set_timer(1_010_000)
incoming = bytearray(source(False, 1))
if change == 'opaque':
incoming[0] ^= 1
elif change == 'length':
incoming.pop()
elif change == 'reset':
setup(False, 0xC02)
elif change == 'expired_source':
safety.set_timer(1_100_001)
elif change == 'relay':
safety.set_relay_malfunction(True)
elif change in ('hands_off', 'unknown'):
incoming = source(False, 4 if change == 'hands_off' else 7)
message = packet(0x53E, bytes(incoming), 2)
expected = -1 if change == 'relay' else 0
assert safety.safety_fwd_packet_hook(message) == expected
safety.safety_rx_hook(message)
assert safety.safety_fwd_packet_hook(message) == expected
def test_packet_forwarding_preserves_existing_other_profile_rules():
safety, _ = setup(False, 0)
data = source(False, 1)
for mode, word in ((structs.CarParams.SafetyModel.hyundaiCanfd, 0),
(structs.CarParams.SafetyModel.toyota, 0),
(structs.CarParams.SafetyModel.hyundai, 0x8800)):
assert safety.set_safety_hooks(mode, word) == 0
safety.init_tests()
for bus in range(3):
for addr in (0x53E, 0x340, 0x4F1, 0x421, 0x110, 0x11A):
assert safety.safety_fwd_packet_hook(packet(addr, data, bus)) == safety.safety_fwd_hook(bus, addr)
@@ -33,8 +33,7 @@ class TestTeslaScreenButton(unittest.TestCase):
('DAS_steeringControl', 2, {'DAS_steeringControlType': stock_type}),
('DI_speed', 0, {'DI_vehicleSpeed': 0}),
('ESP_B', 0, {'ESP_vehicleSpeed': 0, 'ESP_wheelSpeedsQF': 1, 'ESP_vehicleStandstillSts': 1}),
('EPAS3S_sysStatus', 0, {'EPAS3S_internalSAS': 0, 'EPAS3S_eacStatus': eps, 'EPAS3S_handsOnLevel': hands,
'EPAS3S_torsionBarTorque': 0}),
('EPAS3S_sysStatus', 0, {'EPAS3S_internalSAS': 0, 'EPAS3S_eacStatus': eps, 'EPAS3S_handsOnLevel': hands}),
('DI_systemStatus', 0, {'DI_gear': gear, 'DI_accelPedalPos': 0}),
('ESP_status', 0, {'ESP_driverBrakeApply': 2 if brake else 1}),
('UI_warning', 0, {'anyDoorOpen': int(door), 'buckleStatus': int(belt)}),
@@ -107,64 +106,6 @@ class TestTeslaScreenButton(unittest.TestCase):
self.assertEqual(self.permission(), 0)
self.arm()
def test_availability_loss_preserves_only_explicit_screen_arm(self):
tx_packer = CANPackerSafety('tesla_model3_party')
for word in (512, 768, 1536, 1792):
with self.subTest(word=word):
self.start(word)
self.arm()
self.touch(0)
for state in (1, 0, 1, 0, 1):
self.feed(state=state)
self.assertEqual(self.permission(3), 1)
positive = tx_packer.make_can_msg_safety('DAS_control', 0, {'DAS_accelMin': 1, 'DAS_accelMax': 1})
self.assertFalse(self.lib.safety_tx_hook(positive))
self.touch(3)
self.assertEqual(self.permission(), 0)
for state in (0, 1, 0, 1):
self.feed(state=state)
self.assertEqual(self.permission(), 0)
self.start(word)
self.feed(state=1)
self.assertEqual(self.permission(), 1)
self.feed(state=0)
self.assertEqual(self.permission(), 0)
self.feed(state=1)
self.assertEqual(self.permission(), 0)
def test_cruise_fault_clears_screen_arm_and_blocks_gestures(self):
for word in (512, 768, 1536, 1792):
with self.subTest(word=word):
self.start(word)
self.arm()
self.feed(state=1)
self.feed(state=5)
self.assertEqual(self.permission(), 0)
self.touch(0)
self.touch(3)
self.assertEqual(self.permission(), 0)
for state in (0, 1):
self.feed(state=state)
self.assertEqual(self.permission(), 0)
self.arm()
def test_availability_loss_cannot_hide_physical_fault_or_brake(self):
for word in (512, 768, 1536, 1792):
faults = [{'door': True}, {'eps': 3}, {'hands': 3}, {'gear': 0}, {'autopark': 3}]
if word & 1024:
faults.append({'brake': True})
for fault in faults:
with self.subTest(word=word, fault=fault):
self.start(word)
self.arm()
self.feed(state=1)
self.feed(state=0, **fault)
self.assertEqual(self.permission(), 0)
for state in (0, 1, 0, 1):
self.feed(state=state)
self.assertEqual(self.permission(), 0)
def test_brake_disengagement_cannot_be_undone_by_availability(self):
self.start(1536)
self.arm()
@@ -1,109 +0,0 @@
"""A physical SET/RES release precedes the cold host join; output still needs a new acknowledgement."""
import unittest
import pytest
from opendbc.safety.tests.libsafety import libsafety_py
from opendbc.safety.tests import test_volkswagen_stock_aol as stock
from opendbc.car import structs
class Owner(stock.TestVolkswagenStockAol):
direct = False
def packet(self, name, fields, bus=0):
if self.direct and name == 'Gateway_73':
name, fields = 'Motor_EV_01', {'MO_Waehlpos': 5 if fields['GE_Fahrstufe'] == 8 else 3}
return super().packet(name, fields, bus)
def setup(word, *, brake=False):
debug = libsafety_py.libsafety.set_safety_hooks(structs.CarParams.SafetyModel.allOutput, 0) == 0
o = Owner()
o.direct = word == 41
o.configure(word)
o.manual = word == 25
o.safety.set_aol_test_heartbeat(False)
for _ in range(12):
o.feed(moving=not brake, brake=brake)
o.safety.aol_set_host_request(0)
return o, debug
def press(o, button, brake):
assert o.safety.safety_rx_hook(o.packet('GRA_ACC_01', {button: 1}))
o.feed(moving=not brake, brake=brake)
o.safety.aol_set_host_request(0)
assert o.safety.aol_get_permission_mask() == 0
assert not o.safety.safety_tx_hook(o.packet('ACC_06', {'ACC_Sollbeschleunigung_02': 0.5}))
@pytest.mark.parametrize('word', (9, 25, 41))
@pytest.mark.parametrize('button', ('GRA_Tip_Setzen', 'GRA_Tip_Wiederaufnahme'))
@pytest.mark.parametrize('brake', (False, True))
def test_first_press_before_heartbeat_needs_fresh_host_join(word, button, brake):
o, debug = setup(word, brake=brake)
try:
press(o, button, brake)
o.safety.set_aol_test_heartbeat(True)
assert o.safety.aol_get_permission_mask() == 0
o.time += 10_000
o.safety.set_timer(o.time)
o.safety.aol_set_host_request(3)
if brake:
assert o.safety.aol_get_permission_mask() == 0
o.feed(moving=False)
o.safety.aol_set_host_request(3)
expected = (2 if brake else 3) if debug and not (brake and word == 25) else 0
assert o.safety.aol_get_permission_mask() == expected, 'FIRST_PHYSICAL_RELEASE_LOST_BEFORE_HOST_JOIN'
o.safety.set_aol_test_heartbeat(False)
assert o.safety.aol_get_permission_mask() == 0
o.safety.set_aol_test_heartbeat(True)
o.feed(moving=not brake)
o.safety.aol_set_host_request(3)
assert o.safety.aol_get_permission_mask() == 0
finally:
o.tearDown()
@pytest.mark.parametrize('word', (9, 25, 41))
@pytest.mark.parametrize('fault', ('timeout', 'cancel', 'main', 'reverse', 'permanent', 'relay', 'crc', 'brake'))
def test_pending_physical_release_cannot_survive_fault_or_expiry(word, fault):
o, _ = setup(word)
try:
press(o, 'GRA_Tip_Setzen', False)
if fault == 'timeout':
o.time += 300_001
o.safety.set_timer(o.time)
elif fault == 'relay':
o.safety.set_relay_malfunction(True)
elif fault == 'crc':
bad = o.packet('TSK_06', {'TSK_Status': 2})
bad[0].data[0] ^= 255
assert not o.safety.safety_rx_hook(bad)
else:
o.feed(
**{'cancel': {'cancel': True}, 'main': {'main': False}, 'reverse': {'gear': False}, 'permanent': {'permanent': True}, 'brake': {'brake': True}}[fault]
)
assert o.safety.aol_get_permission_mask() == 0
o.safety.set_relay_malfunction(False)
o.safety.set_aol_test_heartbeat(True)
for _ in range(12):
o.feed()
o.safety.aol_set_host_request(3)
assert o.safety.aol_get_permission_mask() & 2 == 0
finally:
o.tearDown()
class TestVolkswagenFirstPress(unittest.TestCase):
def test_first_physical_release_before_host_join(self):
for word in (9, 25, 41):
for button in ('GRA_Tip_Setzen', 'GRA_Tip_Wiederaufnahme'):
for brake in (False, True):
test_first_press_before_heartbeat_needs_fresh_host_join(word, button, brake)
def test_pending_release_native_vetoes(self):
for word in (9, 25, 41):
for fault in ('timeout', 'cancel', 'main', 'reverse', 'permanent', 'relay', 'crc', 'brake'):
test_pending_physical_release_cannot_survive_fault_or_expiry(word, fault)
+7 -1
View File
@@ -339,7 +339,7 @@ struct SlcState @0xa1680744031fdb2d {
hasPendingAdjustedLimit @45 :Bool;
presentationAdjustedLimit @46 :Float32;
hasPresentationAdjustedLimit @47 :Bool;
overrideBasis @48 :Text; # none/pedal/persistent; qualified contribution only
overrideBasis @48 :Text; # none/pedal/persistent/wheel; qualified contribution only
retainedOverride @49 :Bool;
commandActionSequenceId @50 :UInt64; # UI sequence that issued this command
automaticAdoptionPending @51 :Bool;
@@ -352,6 +352,10 @@ struct SlcState @0xa1680744031fdb2d {
# Qualified adoption proposal; the current displayed observation stays above.
upcomingSpeedLimit @57 :Float32;
upcomingBoundaryId @58 :Text;
acceptedIsPreview @59 :Bool; # a temporary ceiling, ineligible for initializing SET
wheelOverrideSince @60 :UInt64; # nonzero while a wheel tap may cross the current adjusted limit
wheelAdoptMonoTime @61 :UInt64; # Card's original wheel event timestamp, only on the approval frame
wheelAdoptTargetMps @62 :Float32; # exact adjusted SET approved for that event
struct SourceReading {
source @0 :Text;
enabled @1 :Bool;
@@ -453,6 +457,7 @@ struct SlcAction @0x9ccdc8676701b412 {
conditionalManual @5 :ConditionalManualAction;
controllerCruise @6 :ControllerCruiseAction;
controllerMode @7 :ControllerCruiseAction;
wheelAdoptMonoTime @8 :UInt64; # original timestamp of a qualified Adopt Speed Limit wheel event
struct ControllerCruiseAction {
version @0 :UInt16;
@@ -520,6 +525,7 @@ struct SlcCruiseEvent @0xcd96dafb67a082d0 {
trafficMode @13 :TrafficModeGesture;
wheelAction @14 :SlcAction.ControllerCruiseAction;
teslaStockCruise @15 :TeslaStockCruise;
wheelOverrideSince @16 :UInt64; # SLC eligibility captured by a physical + tap; zero for ordinary SET changes
struct TeslaStockCruise {
version @0 :UInt16;
File diff suppressed because it is too large Load Diff
+117 -3
View File
@@ -749,7 +749,7 @@ struct SlcState {
struct ConditionalModeState;
struct _capnpPrivate {
CAPNP_DECLARE_STRUCT_HEADER(a1680744031fdb2d, 18, 22)
CAPNP_DECLARE_STRUCT_HEADER(a1680744031fdb2d, 21, 22)
#if !CAPNP_LITE
static constexpr ::capnp::_::RawBrandedSchema const* brand() { return &schema->defaultBrand; }
#endif // !CAPNP_LITE
@@ -845,7 +845,7 @@ struct SlcAction {
struct _capnpPrivate {
CAPNP_DECLARE_STRUCT_HEADER(9ccdc8676701b412, 4, 4)
CAPNP_DECLARE_STRUCT_HEADER(9ccdc8676701b412, 5, 4)
#if !CAPNP_LITE
static constexpr ::capnp::_::RawBrandedSchema const* brand() { return &schema->defaultBrand; }
#endif // !CAPNP_LITE
@@ -897,7 +897,7 @@ struct SlcCruiseEvent {
struct ManualModeGesture;
struct _capnpPrivate {
CAPNP_DECLARE_STRUCT_HEADER(cd96dafb67a082d0, 7, 6)
CAPNP_DECLARE_STRUCT_HEADER(cd96dafb67a082d0, 8, 6)
#if !CAPNP_LITE
static constexpr ::capnp::_::RawBrandedSchema const* brand() { return &schema->defaultBrand; }
#endif // !CAPNP_LITE
@@ -4524,6 +4524,14 @@ public:
inline bool hasUpcomingBoundaryId() const;
inline ::capnp::Text::Reader getUpcomingBoundaryId() const;
inline bool getAcceptedIsPreview() const;
inline ::uint64_t getWheelOverrideSince() const;
inline ::uint64_t getWheelAdoptMonoTime() const;
inline float getWheelAdoptTargetMps() const;
private:
::capnp::_::StructReader _reader;
template <typename, ::capnp::Kind>
@@ -4817,6 +4825,18 @@ public:
inline void adoptUpcomingBoundaryId(::capnp::Orphan< ::capnp::Text>&& value);
inline ::capnp::Orphan< ::capnp::Text> disownUpcomingBoundaryId();
inline bool getAcceptedIsPreview();
inline void setAcceptedIsPreview(bool value);
inline ::uint64_t getWheelOverrideSince();
inline void setWheelOverrideSince( ::uint64_t value);
inline ::uint64_t getWheelAdoptMonoTime();
inline void setWheelAdoptMonoTime( ::uint64_t value);
inline float getWheelAdoptTargetMps();
inline void setWheelAdoptTargetMps(float value);
private:
::capnp::_::StructBuilder _builder;
template <typename, ::capnp::Kind>
@@ -5669,6 +5689,8 @@ public:
inline bool hasControllerMode() const;
inline ::cereal::SlcAction::ControllerCruiseAction::Reader getControllerMode() const;
inline ::uint64_t getWheelAdoptMonoTime() const;
private:
::capnp::_::StructReader _reader;
template <typename, ::capnp::Kind>
@@ -5737,6 +5759,9 @@ public:
inline void adoptControllerMode(::capnp::Orphan< ::cereal::SlcAction::ControllerCruiseAction>&& value);
inline ::capnp::Orphan< ::cereal::SlcAction::ControllerCruiseAction> disownControllerMode();
inline ::uint64_t getWheelAdoptMonoTime();
inline void setWheelAdoptMonoTime( ::uint64_t value);
private:
::capnp::_::StructBuilder _builder;
template <typename, ::capnp::Kind>
@@ -6128,6 +6153,8 @@ public:
inline bool hasTeslaStockCruise() const;
inline ::cereal::SlcCruiseEvent::TeslaStockCruise::Reader getTeslaStockCruise() const;
inline ::uint64_t getWheelOverrideSince() const;
private:
::capnp::_::StructReader _reader;
template <typename, ::capnp::Kind>
@@ -6228,6 +6255,9 @@ public:
inline void adoptTeslaStockCruise(::capnp::Orphan< ::cereal::SlcCruiseEvent::TeslaStockCruise>&& value);
inline ::capnp::Orphan< ::cereal::SlcCruiseEvent::TeslaStockCruise> disownTeslaStockCruise();
inline ::uint64_t getWheelOverrideSince();
inline void setWheelOverrideSince( ::uint64_t value);
private:
::capnp::_::StructBuilder _builder;
template <typename, ::capnp::Kind>
@@ -20214,6 +20244,62 @@ inline ::capnp::Orphan< ::capnp::Text> SlcState::Builder::disownUpcomingBoundary
::capnp::bounded<21>() * ::capnp::POINTERS));
}
inline bool SlcState::Reader::getAcceptedIsPreview() const {
return _reader.getDataField<bool>(
::capnp::bounded<141>() * ::capnp::ELEMENTS);
}
inline bool SlcState::Builder::getAcceptedIsPreview() {
return _builder.getDataField<bool>(
::capnp::bounded<141>() * ::capnp::ELEMENTS);
}
inline void SlcState::Builder::setAcceptedIsPreview(bool value) {
_builder.setDataField<bool>(
::capnp::bounded<141>() * ::capnp::ELEMENTS, value);
}
inline ::uint64_t SlcState::Reader::getWheelOverrideSince() const {
return _reader.getDataField< ::uint64_t>(
::capnp::bounded<18>() * ::capnp::ELEMENTS);
}
inline ::uint64_t SlcState::Builder::getWheelOverrideSince() {
return _builder.getDataField< ::uint64_t>(
::capnp::bounded<18>() * ::capnp::ELEMENTS);
}
inline void SlcState::Builder::setWheelOverrideSince( ::uint64_t value) {
_builder.setDataField< ::uint64_t>(
::capnp::bounded<18>() * ::capnp::ELEMENTS, value);
}
inline ::uint64_t SlcState::Reader::getWheelAdoptMonoTime() const {
return _reader.getDataField< ::uint64_t>(
::capnp::bounded<19>() * ::capnp::ELEMENTS);
}
inline ::uint64_t SlcState::Builder::getWheelAdoptMonoTime() {
return _builder.getDataField< ::uint64_t>(
::capnp::bounded<19>() * ::capnp::ELEMENTS);
}
inline void SlcState::Builder::setWheelAdoptMonoTime( ::uint64_t value) {
_builder.setDataField< ::uint64_t>(
::capnp::bounded<19>() * ::capnp::ELEMENTS, value);
}
inline float SlcState::Reader::getWheelAdoptTargetMps() const {
return _reader.getDataField<float>(
::capnp::bounded<40>() * ::capnp::ELEMENTS);
}
inline float SlcState::Builder::getWheelAdoptTargetMps() {
return _builder.getDataField<float>(
::capnp::bounded<40>() * ::capnp::ELEMENTS);
}
inline void SlcState::Builder::setWheelAdoptTargetMps(float value) {
_builder.setDataField<float>(
::capnp::bounded<40>() * ::capnp::ELEMENTS, value);
}
inline bool SlcState::SourceReading::Reader::hasSource() const {
return !_reader.getPointerField(
::capnp::bounded<0>() * ::capnp::POINTERS).isNull();
@@ -21721,6 +21807,20 @@ inline ::capnp::Orphan< ::cereal::SlcAction::ControllerCruiseAction> SlcAction::
::capnp::bounded<3>() * ::capnp::POINTERS));
}
inline ::uint64_t SlcAction::Reader::getWheelAdoptMonoTime() const {
return _reader.getDataField< ::uint64_t>(
::capnp::bounded<4>() * ::capnp::ELEMENTS);
}
inline ::uint64_t SlcAction::Builder::getWheelAdoptMonoTime() {
return _builder.getDataField< ::uint64_t>(
::capnp::bounded<4>() * ::capnp::ELEMENTS);
}
inline void SlcAction::Builder::setWheelAdoptMonoTime( ::uint64_t value) {
_builder.setDataField< ::uint64_t>(
::capnp::bounded<4>() * ::capnp::ELEMENTS, value);
}
inline ::uint16_t SlcAction::ControllerCruiseAction::Reader::getVersion() const {
return _reader.getDataField< ::uint16_t>(
::capnp::bounded<0>() * ::capnp::ELEMENTS);
@@ -22589,6 +22689,20 @@ inline ::capnp::Orphan< ::cereal::SlcCruiseEvent::TeslaStockCruise> SlcCruiseEve
::capnp::bounded<5>() * ::capnp::POINTERS));
}
inline ::uint64_t SlcCruiseEvent::Reader::getWheelOverrideSince() const {
return _reader.getDataField< ::uint64_t>(
::capnp::bounded<7>() * ::capnp::ELEMENTS);
}
inline ::uint64_t SlcCruiseEvent::Builder::getWheelOverrideSince() {
return _builder.getDataField< ::uint64_t>(
::capnp::bounded<7>() * ::capnp::ELEMENTS);
}
inline void SlcCruiseEvent::Builder::setWheelOverrideSince( ::uint64_t value) {
_builder.setDataField< ::uint64_t>(
::capnp::bounded<7>() * ::capnp::ELEMENTS, value);
}
inline ::uint16_t SlcCruiseEvent::TeslaStockCruise::Reader::getVersion() const {
return _reader.getDataField< ::uint16_t>(
::capnp::bounded<0>() * ::capnp::ELEMENTS);
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+8 -7
View File
@@ -327,12 +327,12 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"HideDMIcon", {PERSISTENT, BOOL, "0"}},
{"HidePersonalityAlerts", {PERSISTENT, BOOL, "0"}},
{"ShowSpeedLimits", {PERSISTENT, BOOL, "1"}},
{"SLCConfirmation", {PERSISTENT, BOOL, "0"}},
{"SLCAdvanceSeconds", {PERSISTENT, INT, "0"}},
{"SLCConfirmationHigher", {PERSISTENT, BOOL, "0"}},
{"SLCConfirmationLower", {PERSISTENT, BOOL, "0"}},
{"SLCFallback", {PERSISTENT, INT}}, // Retired; preserve saved bytes for older versions.
{"SLCOverride", {PERSISTENT, INT, "1"}},
{"SLCOnEngage", {PERSISTENT, BOOL, "0"}},
{"SLCConfirmation", {PERSISTENT, INT, "0"}},
{"SLCAdvanceHigherSeconds", {PERSISTENT, INT, "0"}},
{"SLCAdvanceLowerSeconds", {PERSISTENT, INT, "0"}},
{"SLCOverrideMode", {PERSISTENT, INT, "1"}},
{"SLCWheelOverride", {PERSISTENT, BOOL, "0"}},
{"SLCQualifiedHistory", {PERSISTENT, JSON}},
{"ConditionalModeConfig", {PERSISTENT | DONT_LOG, JSON}},
{"ConditionalManualState", {PERSISTENT | DONT_LOG, JSON}},
@@ -361,7 +361,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"VASMPreferences", {PERSISTENT | DONT_LOG, JSON}},
{"LaneChangePreferences", {PERSISTENT | DONT_LOG, JSON}},
{"SLCPriority1", {PERSISTENT, STRING, "Vision"}},
{"SLCPriority2", {PERSISTENT, STRING, "Dashboard"}},
{"SLCPriority2", {PERSISTENT, STRING, "Map Data"}},
{"SLCPriority3", {PERSISTENT, STRING, "Off"}},
{"SpeedLimitController", {PERSISTENT, BOOL, "0"}},
{"RouteCount", {PERSISTENT, INT, "0"}},
{"SnoozeUpdate", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
+72 -96
View File
@@ -4,7 +4,6 @@ import os
import time
import threading
import uuid
from collections import deque
from typing import cast
import openpilot.cereal.messaging as messaging
@@ -32,7 +31,7 @@ from opendbc.car.hyundai.ioniq6_media import Ioniq6MediaButtons
from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR
from opendbc.car.interfaces import CarInterfaceBase, RadarInterfaceBase
from openpilot.selfdrive.pandad import can_capnp_to_list, can_list_to_can_capnp
from openpilot.selfdrive.car.cruise import VCruiseHelper, SlcPendingConfirmation
from openpilot.selfdrive.car.cruise import SlcWheelContext, VCruiseHelper
from openpilot.starpilot.speed_limits import physical_actions as slc_physical
from openpilot.starpilot.aol.intent import AolProcessFaultContext, independent_axis_requested, read_settings
from openpilot.starpilot.aol.transport_pause import TransportPauseFeedback, cp_identity
@@ -60,7 +59,6 @@ from opendbc.car.ford.aol import (
temporary_restriction as ford_temporary_restriction,
)
from opendbc.car.hyundai.canfd_angle_aol import native_observation, temporary_restriction, qualified as qualified_angle_aol
from opendbc.car.hyundai.blended_stock_aol import qualified_alpha as qualified_blended_alpha_aol
from opendbc.car.hyundai.canfd_owner import config_index as hyundai_canfd_config_index
from opendbc.car.hyundai.captured_adrv_startup import CapturedADRVStartup
from openpilot.starpilot.car.nissan.aol import (
@@ -95,7 +93,7 @@ from openpilot.starpilot.vehicle_selection import startup_candidate
from openpilot.starpilot.vehicle_preferences import VehicleStartupPreferences
from openpilot.starpilot.controller_extensions import configure_controller, observe_controller_can, order_controller_can
from openpilot.starpilot.vehicle_startup import VehicleStartupOwner
from openpilot.starpilot.controllers.wheel_actions import WheelPublisher, key_for
from openpilot.starpilot.controllers.wheel_actions import LIFETIME_NS as WHEEL_ACTION_MAX_AGE_NS, WheelPublisher, key_for
from openpilot.starpilot.controllers.cruise_action import CruiseActionConsumer, eligible as cruise_action_eligible
from openpilot.starpilot.schema_cache import get_cache, prewarm_cache_contracts, put_cache
@@ -141,15 +139,15 @@ def can_comm_callbacks(logcan: messaging.SubSocket, sendcan: messaging.PubSocket
def curve_press_receipt(CS: car.CarState, helper: VCruiseHelper, sm, *, curve_replay: bool,
slc_replay: bool, host_long_active: bool, observed_ns: int):
"""Reserve SLC presentation presses even when their command is stale or rejected."""
"""A confirmation or native standstill RES must not also override Curve control."""
accelerate = frozenset((car.CarState.ButtonEvent.Type.accelCruise,
car.CarState.ButtonEvent.Type.resumeCruise))
pressed = {b.type.raw for b in CS.buttonEvents if b.pressed and b.type.raw in accelerate}
if (not curve_replay or not host_long_active or not CS.canValid or CS.canTimeout or not CS.cruiseState.available or
not pressed):
CS.standstill or CS.cruiseState.standstill or not pressed):
return None
if slc_replay and (helper.slc_consumed_button is not None or bool(pressed & helper.slc_suppressed_buttons) or
bool(sm['slcState'].hasPending)):
if (helper.slc_consumed_button is not None or bool(pressed & helper.slc_suppressed_buttons) or
slc_replay and bool(sm['slcState'].hasPending)):
return None
return ('curveAccelPress', None, float(helper.v_cruise_kph_last / 3.6),
float(helper.v_cruise_kph / 3.6), observed_ns)
@@ -189,10 +187,6 @@ class Car:
ignore_valid=['autoSteeringLearning'], ignore_avg_freq=['autoSteeringLearning'])
self.slc_cruise_event_id = 0
self.slc_producer_session = uuid.uuid4().hex
self.slc_command_sock = messaging.sub_sock('slcCruiseCommand', conflate=False) if self.slc_replay else None
self.slc_commands = deque(maxlen=8)
self.slc_last_command_id = 0
self.slc_command_session = ''
self.slc_receipts: list[tuple[str, object, float, float, int]] = []
self.manual_receipt = None
self.manual_event_sequence = 0
@@ -429,14 +423,13 @@ class Car:
for ignored in (self.sm.ignore_alive, self.sm.ignore_valid, self.sm.ignore_average_freq):
if 'slcState' not in ignored:
ignored.append('slcState')
if self.slc_command_sock is None:
self.slc_command_sock = messaging.sub_sock('slcCruiseCommand', conflate=False)
if 'slcDashboardObservation' not in self.pm.sock:
self.pm.sock['slcDashboardObservation'] = messaging.pub_sock('slcDashboardObservation')
self.slc_requested_configuration = self.read_slc_configuration()
self.slc_configuration = self.slc_requested_configuration
self.slc_replay = self.slc_configuration[0]
self.slc_control_enabled = self.slc_replay and self.slc_configuration[1].enabled
self.slc_match_on_engage = self.slc_transport_available and self.read_slc_match_on_engage()
self.slc_source_floor_ns = 0
# Write previous route's CarParams
@@ -477,6 +470,9 @@ class Car:
feature_enabled(self.params, self.CP, 'slc', os.environ))
return bool(admitted), settings
def read_slc_match_on_engage(self) -> bool:
return read_saved(self.params, 'SLCOnEngage', 1) == (b'1', True)
def refresh_slc_configuration(self, now_ns: int) -> None:
configuration = getattr(self, 'slc_requested_configuration', None)
if configuration is None or configuration == getattr(self, 'slc_configuration', None):
@@ -484,12 +480,8 @@ class Car:
self.slc_configuration = configuration
self.slc_replay = configuration[0]
self.slc_control_enabled = self.slc_replay and configuration[1].enabled
# Invalidate old SLC choices while keeping consumed buttons latched until release.
self.slc_source_floor_ns = max(getattr(self, 'slc_source_floor_ns', 0), now_ns)
self.slc_commands.clear()
self.slc_command_session = ''
self.slc_last_command_id = 0
# Keep the shared event producer and held suppression until physical release.
# Only SLC's sourced/issued work predating this admission is retired.
def observe_distance_personality(self, CS: car.CarState, now_ns: int) -> None:
tracker = getattr(self, 'distance_personality_tracker', None)
@@ -582,18 +574,22 @@ class Car:
0 < control_log_ns <= now_ns and now_ns - control_log_ns <= slc_physical.STATE_MAX_AGE_NS and
self.sm['carControl'].enabled)
host_control_enabled = bool(self.CP.openpilotLongitudinalControl and not self.CP.pcmCruise and host_enabled)
host_long_active = host_control_enabled and self.sm['carControl'].longActive
host_long_active = bool(self.CP.openpilotLongitudinalControl and host_enabled and self.sm['carControl'].longActive)
slc_long_active = bool(getattr(self, 'slc_control_enabled', self.slc_replay) and self.CP.openpilotLongitudinalControl and
host_enabled and (self.sm['carControl'].longActive or CS.gasPressed))
pending = None
if self.slc_replay:
pending = slc_physical.pending_confirmation(
self.sm, now_ns, long_active=slc_long_active, after_ns=floor_ns)
approved = (slc_physical.state_current(self.sm, observation, self.slc_producer_session, now_ns)
if self.v_cruise_helper.toyota is not None and self.slc_control_enabled else None)
if approved is not None and int(approved.frameMonoTime) <= floor_ns:
approved = None
self.update_cruise_speed(CS, pending, approved=approved)
wheel_context = None
slc_settings = getattr(self, 'slc_configuration', (False, None))[1]
cruise_helper = self.v_cruise_helper
if (self.slc_replay and getattr(slc_settings, 'wheel_override', False) and host_long_active and can_rcv_valid and
not self.CP.pcmCruise and pending is None and (cruise_helper.slc_wheel_press is not None or len(CS.buttonEvents) > 0) and
not (CS.brakePressed or CS.gasPressed or CS.standstill or CS.cruiseState.standstill)):
wheel_context = slc_physical.wheel_override_context(self.sm, now_ns, after_ns=floor_ns,
selected_kph=cruise_helper.v_cruise_kph, is_metric=self.is_metric)
self.update_cruise_speed(CS, pending, wheel_context=wheel_context)
if self.controller_cruise_sock is not None:
for _ in range(8):
request = messaging.recv_one_or_none(self.controller_cruise_sock)
@@ -602,6 +598,8 @@ class Car:
self.controller_cruise_consumer.apply(request, self.CP, CS, self.sm, self.v_cruise_helper,
now_ns=now_ns, is_metric=self.is_metric,
enabled=bool(host_control_enabled and can_rcv_valid and not self.params.get_bool("SafeMode")))
if self.slc_replay:
self.apply_slc_adopt(now_ns, host_control_enabled=host_control_enabled, after_ns=floor_ns)
consumed = self.v_cruise_helper.slc_consumed_button
if self.aol_card_intent is not None:
if self.aol_process_fault_context is None:
@@ -616,10 +614,9 @@ class Car:
self.aol_card_intent, cancel_source_ns=cancel_source_ns, now_ns=now_ns,
producer=getattr(self, 'slc_producer_session', ''))
cancel_feedback = self.aol_transport_feedback
if (hkg_cancel_retained and (self.CP.carFingerprint in (HYUNDAI_CAR.KIA_EV9, HYUNDAI_CAR.HYUNDAI_IONIQ_5_PE) or
qualified_blended_alpha_aol(self.CP, marked_only=True)) and
if (hkg_cancel_retained and self.CP.carFingerprint in (HYUNDAI_CAR.KIA_EV9, HYUNDAI_CAR.HYUNDAI_IONIQ_5_PE) and
cancel_feedback is not None and not cancel_feedback.longitudinal_cancel_ns):
# The pure guard proved this exact raw CANCEL/FULL join. Bind
# The pure guard proved this exact CCNC raw CANCEL/FULL join. Bind
# its first local receipt for the existing bounded withdrawal handshake;
# held samples never renew it or any source/native/output deadline.
if cancel_source_ns > cancel_feedback.last_cancel_source_ns:
@@ -800,52 +797,6 @@ class Car:
slc_replay=self.slc_replay, host_long_active=host_long_active, observed_ns=now_ns)
if curve_receipt is not None:
self.slc_receipts.append(curve_receipt)
if self.slc_command_sock is not None:
for _ in range(8):
command_message = messaging.recv_one_or_none(self.slc_command_sock)
if command_message is None:
break
if self.slc_replay and command_message.valid:
self.slc_commands.append(command_message)
for _ in range(len(self.slc_commands)):
command_message = self.slc_commands.popleft()
command = command_message.slcCruiseCommand
if int(command.issuedMonoTime) <= floor_ns:
continue
if now_ns < int(command.issuedMonoTime):
if now_ns <= int(command.expiresMonoTime):
self.slc_commands.append(command_message)
continue
state = slc_physical.state_current(self.sm, observation, self.slc_producer_session, now_ns)
if (state is None or int(self.sm.logMonoTime['slcState']) < int(command.issuedMonoTime)) and now_ns < int(command.expiresMonoTime):
self.slc_commands.append(command_message)
continue
if state is not None and str(state.sessionId) != self.slc_command_session:
self.slc_command_session = str(state.sessionId)
self.slc_last_command_id = 0
if not self.slc_command_session:
self.slc_command_session = str(command.sessionId)
if str(command.sessionId) != self.slc_command_session or int(command.commandId) <= self.slc_last_command_id:
continue
self.slc_last_command_id = int(command.commandId)
previous_mps = float(self.v_cruise_helper.v_cruise_kph / 3.6)
origin = SlcPendingConfirmation(str(command.sessionId), int(command.decisionId), int(command.presentationId))
owners = {**self.v_cruise_helper.slc_suppression_owner, **self.v_cruise_helper.slc_released_owner}
allowed_buttons = {button for button, owner in owners.items() if owner == origin} if int(command.decisionId) > 0 else set()
intervening_button = (bool(self.v_cruise_helper.slc_cruise_change) or
any(b.type.raw not in allowed_buttons for b in CS.buttonEvents) or
any(timer > 0 and button not in allowed_buttons
for button, timer in self.v_cruise_helper.button_timers.items()))
applicable = slc_physical.command_applicable(
command, state, observation, self.slc_producer_session, now_ns, previous_mps,
long_active=slc_long_active,
button_event=intervening_button)
if self.v_cruise_helper.toyota is not None:
applicable = applicable and self.v_cruise_helper.toyota.can_apply(float(command.targetMps))
if applicable:
self.v_cruise_helper.apply_slc_target(float(command.targetMps), self.is_metric)
self.slc_receipts.append(('commandApplied' if applicable else 'commandRejected', command,
previous_mps, float(self.v_cruise_helper.v_cruise_kph / 3.6), now_ns))
blocked_keys = set()
if self.manual_receipt is not None:
blocked_keys.add(key_for(self.manual_receipt[0]))
@@ -861,28 +812,49 @@ class Car:
return CS, RD
def update_cruise_speed(self, CS, pending=None, *, approved=None):
def apply_slc_adopt(self, now_ns: int, *, host_control_enabled: bool, after_ns: int = 0) -> None:
pending = getattr(self, 'slc_wheel_adopt', None)
if pending is None:
return
wheel_ns, selected_kph = pending
helper = self.v_cruise_helper
if (not after_ns < wheel_ns <= now_ns <= wheel_ns + WHEEL_ACTION_MAX_AGE_NS or
not host_control_enabled or not self.slc_control_enabled or
not helper.v_cruise_initialized or helper.v_cruise_kph != selected_kph or any(helper.button_timers.values())):
self.slc_wheel_adopt = None
return
target = slc_physical.adopted_target(self.sm, now_ns, wheel_ns=wheel_ns)
if target is not None:
self.slc_wheel_adopt = None
helper.apply_slc_target(target, self.is_metric)
def update_cruise_speed(self, CS, pending=None, *, wheel_context: SlcWheelContext | None = None):
self.v_cruise_helper.update_v_cruise(CS, self.sm['carControl'].enabled, self.is_metric, pending,
slc_enabled=getattr(self, 'slc_control_enabled', getattr(self, 'slc_replay', False)),
slc_approved=approved)
wheel_context=wheel_context)
if self.sm['carControl'].enabled and not self.CC_prev.enabled:
initial = None
target, initial = None, None
now_ns = int(self.can_log_mono_time) if REPLAY and hasattr(self, 'can_log_mono_time') else time.monotonic_ns()
if (getattr(self, 'slc_control_enabled', False) and self.CP.openpilotLongitudinalControl and
if (getattr(self, 'slc_match_on_engage', False) and getattr(self, 'slc_control_enabled', False) and
not self.CP.pcmCruise and self.CP.openpilotLongitudinalControl and
CS.canValid and not CS.canTimeout and CS.cruiseState.available and
self.sm.valid['carControl'] and self.sm.alive['carControl'] and
0 < int(self.sm.logMonoTime['carControl']) <= now_ns and
now_ns - int(self.sm.logMonoTime['carControl']) <= slc_physical.STATE_MAX_AGE_NS and
(self.sm['carControl'].longActive or CS.gasPressed)):
evidence = getattr(self.CI.CS, 'dashboard_limit', None)
initial = slc_physical.engagement_state(
self.sm, evidence.observation if evidence is not None else None, self.slc_producer_session,
now_ns, after_ns=getattr(self, 'slc_source_floor_ns', 0))
# Initialization owns this edge; evaluate commands against its final speed.
now_ns - int(self.sm.logMonoTime['carControl']) <= slc_physical.STATE_MAX_AGE_NS):
long_requested = self.sm['carControl'].longActive or CS.gasPressed
if not long_requested and 'aolAxisState' in self.sm.logMonoTime:
# AOL can acknowledge the request before longitudinal actuators become active.
axis = current_axis(self.sm, now_ns=now_ns)
long_requested = axis is not None and axis.nativeAcknowledged and axis.desiredLongitudinal
if long_requested:
evidence = getattr(self.CI.CS, 'dashboard_limit', None)
target, initial = slc_physical.engagement_target(
self.sm, evidence.observation if evidence is not None else None, self.slc_producer_session,
now_ns, after_ns=getattr(self, 'slc_source_floor_ns', 0))
adopted = self.v_cruise_helper.initialize_v_cruise(
self.CS_prev, self.experimental_mode, resume=self.vehicle_startup.consume_cruise_resume(), initial_speed=self.initial_cruise_speed,
slc_target_mps=float(initial.presentationAdjustedLimit) if initial is not None else None, is_metric=self.is_metric)
if adopted:
slc_target_mps=target, is_metric=self.is_metric)
if adopted and initial is not None:
self.slc_receipts.append(('engagementAdopt', initial, 0.0, float(self.v_cruise_helper.v_cruise_kph / 3.6), now_ns))
def aol_cancel_source(self, CS, now_ns: int) -> int:
@@ -1151,11 +1123,6 @@ class Car:
elif self.CP.brand == 'hyundai':
from openpilot.starpilot.car.hyundai.dashboard import compose as hyundai_compose
companion = hyundai_compose(self.CP, self.CI.CS, CS, companion, car_state_stamp=int(cs_send.logMonoTime))
if self.v_cruise_helper.toyota is not None:
if companion is None:
companion = messaging.new_message('starpilotCarState', valid=bool(cs_send.valid), logMonoTime=int(cs_send.logMonoTime))
companion.starpilotCarState.sourceCarStateMonoTime = int(cs_send.logMonoTime)
self.v_cruise_helper.toyota.publish(companion.starpilotCarState.toyotaCruise)
self.car_gps_publisher.update(self.CI.CS, self.pm, companion)
self.pm.send('carState', cs_send)
if getattr(self, 'learning_stamp_observer', None) is not None:
@@ -1163,6 +1130,9 @@ class Car:
wheel = getattr(self, 'wheel_publisher', None)
if wheel is not None:
if (getattr(self, 'slc_control_enabled', False) and self.CC_prev.enabled and self.sm['carControl'].enabled and
any(action == 10 for _, action in self.wheel_commands)):
self.slc_wheel_adopt = (int(cs_send.logMonoTime), self.v_cruise_helper.v_cruise_kph)
wheel.publish(self.wheel_commands, self.CP, self.pm, now_ns=int(cs_send.logMonoTime),
drive_id=int(self.sm['deviceState'].startedMonoTime), source_car_ns=int(cs_send.logMonoTime),
source_control_ns=int(self.sm.logMonoTime['carControl']))
@@ -1202,6 +1172,8 @@ class Car:
if kind == 'driverChange':
assert isinstance(detail, tuple) and len(detail) == 4
record.button, record.longPress = detail[2], detail[3]
if self.v_cruise_helper.slc_wheel_tap is not None:
record.sessionId, record.wheelOverrideSince = self.v_cruise_helper.slc_wheel_tap
elif kind in ('confirmationAccept', 'confirmationReject'):
record.button = detail.button
record.sessionId = detail.pending.session_id
@@ -1209,13 +1181,11 @@ class Car:
record.presentationId = detail.pending.presentation_id
elif kind == 'curveAccelPress':
record.button = 'accel'
else:
elif kind == 'engagementAdopt':
record.sessionId = str(detail.sessionId)
record.commandId = 0 if kind == 'engagementAdopt' else int(detail.commandId)
record.decisionId = int(detail.decisionId)
record.presentationId = int(detail.presentationId)
if kind == 'engagementAdopt':
record.button = 'decel'
record.button = 'decel'
self.pm.send('slcCruiseEvent', event)
if getattr(self, 'conditional_replay', False) and self.manual_receipt is not None:
@@ -1637,6 +1607,10 @@ class Car:
if physical.button_credit_ns - self.volt_cc_boot_offset_ns <= max(self.volt_cc_drive_id, self.volt_cc_source_floor_ns):
self.CI.CC.volt_cc_consumed_source_ns = physical.button_credit_ns
self.CI.CC.volt_cc_metric = self.is_metric
self.CI.CC.volt_cc_speed_ceiling_mps = (slc_physical.active_ceiling(
self.sm, self.volt_cc_now_mono_ns,
after_ns=max(self.volt_cc_drive_id, self.volt_cc_source_floor_ns, getattr(self, 'slc_source_floor_ns', 0)))
if getattr(self, 'slc_control_enabled', False) else None)
if getattr(self.CI.CC, "bolt_cc_profile", False):
self.CI.CC.bolt_cc_metric = self.is_metric
@@ -1898,6 +1872,8 @@ class Car:
now = time.monotonic()
if now >= next_cruise_read:
self.slc_requested_configuration = self.read_slc_configuration()
if getattr(self, 'slc_transport_available', False):
self.slc_match_on_engage = self.read_slc_match_on_engage()
self.v_cruise_helper.intervals = read_cruise_intervals(self.params, self.CP)
pitch_input = getattr(self.CI.CC, 'long_pitch_input', None)
if pitch_input is not None:
+40 -59
View File
@@ -43,6 +43,9 @@ class SlcConsumedButton:
button: str
SlcWheelContext = tuple[str, int]
def validated_initial_cruise_speed(value: object) -> int:
if type(value) is int and V_CRUISE_INITIAL <= value <= V_CRUISE_MAX:
return value
@@ -66,35 +69,27 @@ class VCruiseHelper:
self.button_change_states = {btn: {"standstill": False, "enabled": False} for btn in self.button_timers}
self.pcm_slc_sample: tuple[float, int] | None = None
self.pcm_slc_button = ButtonType.unknown
# Set by software cruise steps or a checked Toyota PCM physical +/- change.
self.slc_cruise_change: tuple[float, float, str, bool] | None = None
self.slc_consumed_button: SlcConsumedButton | None = None
self.slc_suppressed_buttons: set[ButtonType] = set()
self.slc_suppression_owner: dict[ButtonType, SlcPendingConfirmation] = {}
self.slc_released_owner: dict[ButtonType, SlcPendingConfirmation] = {}
self.slc_released_suppressed = False
self.slc_pcm = False
from openpilot.starpilot.speed_limits.toyota import eligible, ToyotaCruise
self.toyota = ToyotaCruise() if eligible(CP) else None
self.slc_wheel_press: SlcWheelContext | None = None
self.slc_wheel_tap: SlcWheelContext | None = None
@property
def v_cruise_initialized(self):
return self.v_cruise_kph != V_CRUISE_UNSET
def update_v_cruise(self, CS, enabled, is_metric, slc_pending: SlcPendingConfirmation | None = None, *, slc_enabled: bool = False,
slc_approved=None):
self.slc_pcm = bool(slc_enabled and self.CP.pcmCruise and self.CP.openpilotLongitudinalControl)
def update_v_cruise(self, CS, enabled, is_metric, slc_pending: SlcPendingConfirmation | None = None, *,
slc_enabled: bool = False, wheel_context: SlcWheelContext | None = None):
from opendbc.car.subaru.software_cruise import selected as subaru_software_selected
subaru_software = subaru_software_selected(self.CP)
software_cruise = not self.CP.pcmCruise or self.slc_pcm or subaru_software
software_cruise = not self.CP.pcmCruise or subaru_software
self.slc_cruise_change = None
self.slc_consumed_button = None
self.slc_released_suppressed = False
self.slc_released_owner = {}
self.slc_wheel_tap = None
self.v_cruise_kph_last = self.v_cruise_kph
if self.toyota is not None:
self.slc_pcm = self.toyota.update(self, CS, enabled, slc_enabled, slc_approved, is_metric)
software_cruise = self.slc_pcm
now_ns = monotonic_ns()
if not CS.cruiseState.available or now_ns - self.pending_resume_ns > 500_000_000:
self.pending_resume_ns = 0
@@ -106,21 +101,26 @@ class VCruiseHelper:
button.type in (ButtonType.accelCruise, ButtonType.resumeCruise) and self.v_cruise_initialized):
self.pending_resume_ns = now_ns
if software_cruise or self.slc_suppressed_buttons:
if software_cruise or slc_enabled and self.CP.openpilotLongitudinalControl or self.slc_suppressed_buttons:
self._update_slc_button_latches(CS, enabled, slc_pending)
if (not slc_enabled or not enabled or self.CP.pcmCruise or slc_pending is not None or
not CS.cruiseState.available or ButtonType.accelCruise in self.slc_suppressed_buttons):
wheel_context = None
if wheel_context != self.slc_wheel_press:
self.slc_wheel_press = None
if wheel_context is not None and self.button_timers[ButtonType.accelCruise] == 0:
pressed = {b.type.raw for b in CS.buttonEvents if b.pressed}
if ButtonType.accelCruise in pressed and ButtonType.resumeCruise not in pressed:
self.slc_wheel_press = wheel_context
if CS.cruiseState.available:
if software_cruise:
if self.toyota is None and (self.slc_pcm or subaru_software) and (not enabled or not self.v_cruise_initialized):
# PCM still owns engagement; SLC owns the software set speed while system long is selected.
if subaru_software and (not enabled or not self.v_cruise_initialized):
stock_kph = CS.cruiseState.speed * CV.MS_TO_KPH
self.v_cruise_kph = stock_kph if 0 < stock_kph < V_CRUISE_UNSET else V_CRUISE_UNSET
# if stock cruise is completely disabled, then we can use our own set speed logic
self._update_v_cruise_non_pcm(CS, enabled, is_metric)
if self.toyota is None:
self.v_cruise_cluster_kph = self.v_cruise_kph
if self.toyota is not None:
self.toyota.constrain(self)
self.v_cruise_cluster_kph = self.v_cruise_kph
self.update_button_timers(CS, enabled)
else:
self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH
@@ -139,6 +139,8 @@ class VCruiseHelper:
self.pcm_slc_button = ButtonType.unknown
if software_cruise:
self.update_button_timers(CS, enabled)
if self.button_timers[ButtonType.accelCruise] == 0:
self.slc_wheel_press = None
def _update_toyota_pcm_slc_change(self, CS, enabled: bool, now_ns: int) -> None:
"""Observe a physical PCM speed change without writing stock selected speed."""
@@ -165,8 +167,9 @@ class VCruiseHelper:
held = ButtonType.unknown
if continuous:
delta = self.v_cruise_kph - previous[0]
if ((button == ButtonType.accelCruise and delta > 0.) or
(button == ButtonType.decelCruise and delta < 0.)):
if (not self.slc_suppressed_buttons and not self.slc_released_suppressed and
((button == ButtonType.accelCruise and delta > 0.) or
(button == ButtonType.decelCruise and delta < 0.))):
self.slc_cruise_change = (previous[0] / 3.6, self.v_cruise_kph / 3.6,
'accel' if delta > 0. else 'decel', False)
self.pcm_slc_button = held
@@ -179,17 +182,13 @@ class VCruiseHelper:
continue
if not b.pressed and button in self.slc_suppressed_buttons:
self.slc_suppressed_buttons.remove(button)
prior_owner = self.slc_suppression_owner.pop(button, None)
if prior_owner is not None:
self.slc_released_owner[button] = prior_owner
self.slc_released_suppressed = True
self.button_timers[button] = 0
elif (b.pressed and enabled and CS.cruiseState.available and slc_pending is not None and
not (self.toyota is not None and button == ButtonType.accelCruise and
not (self.CP.pcmCruise and button == ButtonType.accelCruise and
(CS.standstill or CS.cruiseState.standstill or not CS.cruiseState.enabled)) and
button not in self.slc_suppressed_buttons):
self.slc_suppressed_buttons.add(button)
self.slc_suppression_owner[button] = slc_pending
self.slc_consumed_button = SlcConsumedButton(slc_pending, 'accel' if button == ButtonType.accelCruise else 'decel')
def _update_v_cruise_non_pcm(self, CS, enabled, is_metric):
@@ -231,26 +230,20 @@ class VCruiseHelper:
if not self.button_change_states[button_type]["enabled"]:
return
self.adjust_v_cruise(button_type, CS, is_metric, long_press=long_press)
changed = self.adjust_v_cruise(button_type, CS, is_metric, long_press=long_press)
if changed and button_type == ButtonType.accelCruise and not long_press and self.button_timers[button_type] > 0:
self.slc_wheel_tap = self.slc_wheel_press
def adjust_v_cruise(self, button_type, CS, is_metric: bool, *, long_press: bool = False) -> bool:
"""Apply the normal software cruise step without synthesizing button events."""
from opendbc.car.subaru.software_cruise import selected as subaru_software_selected
subaru_software = subaru_software_selected(self.CP)
if ((self.CP.pcmCruise and not self.slc_pcm and not subaru_software) or button_type not in (ButtonType.accelCruise, ButtonType.decelCruise) or
if ((self.CP.pcmCruise and not subaru_software) or button_type not in (ButtonType.accelCruise, ButtonType.decelCruise) or
not 0 < self.v_cruise_kph < V_CRUISE_UNSET or
(button_type == ButtonType.accelCruise and CS.cruiseState.standstill)):
return False
v_cruise_delta = 1. if is_metric else IMPERIAL_INCREMENT
previous_kph = self.v_cruise_kph
toyota = self.toyota if self.slc_pcm else None
previous_cluster_kph = self.v_cruise_cluster_kph
if toyota is not None:
if toyota.requested is None:
return False
if button_type == ButtonType.decelCruise:
previous_kph = toyota.requested[0]
self.v_cruise_kph = toyota.requested[1]
interval = self.intervals.held if long_press else self.intervals.short
v_cruise_delta = v_cruise_delta * interval
display_speed = round(self.v_cruise_kph if is_metric else self.v_cruise_kph * CV.KPH_TO_MPH)
@@ -266,11 +259,6 @@ class VCruiseHelper:
self.v_cruise_kph = np.clip(round(self.v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX)
self.v_cruise_cluster_kph = self.v_cruise_kph
if toyota is not None:
toyota.requested = (float(self.v_cruise_kph), float(self.v_cruise_cluster_kph))
toyota.manual_step = (previous_kph, previous_cluster_kph, 'accel' if button_type == ButtonType.accelCruise else 'decel', long_press)
toyota.constrain(self)
return self.v_cruise_kph != previous_kph
if 0 < previous_kph < V_CRUISE_UNSET and self.v_cruise_kph != previous_kph:
button = "accel" if button_type == ButtonType.accelCruise else "decel"
self.slc_cruise_change = (float(previous_kph / 3.6), float(self.v_cruise_kph / 3.6), button, long_press)
@@ -291,23 +279,19 @@ class VCruiseHelper:
self.button_change_states[b.type.raw] = {"standstill": CS.cruiseState.standstill, "enabled": enabled}
def apply_slc_target(self, target_mps: float, is_metric: bool) -> None:
"""Apply a qualified planner command to the card-owned software set speed."""
"""Set the software cruise ceiling for engagement or a qualified driver request."""
# Match ordinary button precision so stepping away and back restores this target.
self.v_cruise_kph = slc_target_kph(target_mps, is_metric)
self.v_cruise_cluster_kph = self.v_cruise_kph
if self.toyota is not None:
self.toyota.manual_step = None
self.toyota.constrain(self)
self.toyota.requested = (self.v_cruise_kph, self.v_cruise_cluster_kph)
def initialize_v_cruise(self, CS, experimental_mode: bool, *, resume: bool | None = None,
initial_speed: object = V_CRUISE_INITIAL, slc_target_mps: float | None = None, is_metric: bool = False) -> bool:
"""Return whether engagement adopts the qualified current SLC limit."""
"""Return whether engagement used the supplied SLC target."""
if (not self.CP.openpilotLongitudinalControl or slc_target_mps is None or type(slc_target_mps) not in (int, float) or
not math.isfinite(slc_target_mps) or not V_CRUISE_MIN / 3.6 - 1e-5 <= slc_target_mps <= V_CRUISE_MAX / 3.6 + 1e-5):
slc_target_mps = None
# PCM initializes when SLC does not own SET.
if self.CP.pcmCruise and (not self.slc_pcm or slc_target_mps is None):
# Native cruise owns PCM SET, including when SLC supplies a planner cap.
if self.CP.pcmCruise:
return False
# A car-owned acknowledgment can outlive the physical button event.
if resume is None:
@@ -315,15 +299,12 @@ class VCruiseHelper:
resume = (0 < self.pending_resume_ns and 0 <= age <= 500_000_000) or any(
b.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for b in CS.buttonEvents)
self.pending_resume_ns = 0
if slc_target_mps is None and resume and self.v_cruise_initialized:
if not self.CP.pcmCruise:
self.v_cruise_kph = self.v_cruise_kph_last
self.v_cruise_cluster_kph = self.v_cruise_kph
if resume and self.v_cruise_initialized:
self.v_cruise_kph = self.v_cruise_kph_last
self.v_cruise_cluster_kph = self.v_cruise_kph
return False
elif slc_target_mps is not None:
# Toyota still needs its native capacity/release approval before application.
if self.toyota is None:
self.apply_slc_target(slc_target_mps, is_metric)
self.apply_slc_target(slc_target_mps, is_metric)
return True
else:
self.v_cruise_kph = int(round(np.clip(max(CS.vEgo * CV.MS_TO_KPH, validated_initial_cruise_speed(initial_speed)), V_CRUISE_INITIAL, V_CRUISE_MAX)))
@@ -52,7 +52,7 @@ class TestCruiseIntervals(unittest.TestCase):
Path(self.params.get_param_path("CustomCruiseLong")).write_bytes(b"200")
self.assertEqual(read_cruise_intervals(self.params, self.cp), CruiseIntervals())
def test_slc_uses_saved_intervals_for_every_other_make_and_pcm_mode(self):
def test_slc_preserves_software_intervals_and_native_pcm_set(self):
brands = [brand.__module__.split('.')[-2] for brand in BRANDS if brand.__module__.split('.')[-2] != 'toyota'] + ['future']
self.params.put("CustomCruise", 3.0, block=True)
self.params.put("CustomCruiseLong", 7.0, block=True)
@@ -67,6 +67,11 @@ class TestCruiseIntervals(unittest.TestCase):
helper.v_cruise_kph = helper.v_cruise_cluster_kph = initial
helper.intervals = read_cruise_intervals(self.params, cp)
helper.update_v_cruise(state(button, True, native=140.), True, metric, slc_enabled=True)
if pcm:
helper.update_v_cruise(state(button, False, native=140.), True, metric, slc_enabled=True)
self.assertAlmostEqual(helper.v_cruise_kph, 140., places=4)
self.assertIsNone(helper.slc_cruise_change)
continue
if held:
for _ in range(CRUISE_LONG_PRESS):
helper.update_v_cruise(state(native=140.), True, metric, slc_enabled=True)
File diff suppressed because one or more lines are too long
@@ -37,7 +37,6 @@ from openpilot.starpilot.longitudinal.lead_takeoff import LeadTakeoff, project a
from openpilot.starpilot.longitudinal.lead_behavior import adjust as adjust_lead_behavior
from openpilot.starpilot.longitudinal.hkg_vision_stop import owner_for as vision_stop_owner
from openpilot.starpilot.car.ford.stopped_lead import owner_for as ford_stop_owner
from openpilot.starpilot.car.volkswagen.stopped_lead import owner_for as vw_stop_owner
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import STOP_DISTANCE
from openpilot.starpilot.longitudinal.lead_approach import LeadApproach, LeadApproachKey, project as project_approach
from openpilot.starpilot.lateral.lane_change_preferences import LaneChangePolicy
@@ -133,7 +132,7 @@ class LongitudinalPlanner:
self.lead_takeoff = LeadTakeoff()
self.vehicle_far_follow = far_follow_owner(CP, dt)
self.vision_stop_buffer = vision_stop_owner(CP)
self.ford_stop_approach = ford_stop_owner(CP, STOP_DISTANCE - .5) or vw_stop_owner(CP, STOP_DISTANCE - .5)
self.ford_stop_approach = ford_stop_owner(CP, STOP_DISTANCE - .5)
self.preap_adaptive = preap_adaptive_owner(CP)
def update(self, sm, *, cruise_ceiling: CruiseCeiling | None = None,
@@ -374,12 +373,11 @@ class LongitudinalPlanner:
cruise_profile = replace(cruise_profile, cruise_brake_magnitude=global_magnitude)
if sm['controlsState'].forceDecel:
cruise_profile = None
elif (ceiling.effective_mps is not None or curve.effective_mps is not None) and cruise_profile is not None:
# An eligible lower ceiling must retain at least native approach
# braking even when the saved profile requests a softer cruise response.
elif curve.effective_mps is not None and cruise_profile is not None:
# Curve approach retains native braking when the saved cruise response is softer.
cruise_profile = replace(cruise_profile, cruise_brake_magnitude=max(cruise_profile.cruise_brake_magnitude,
abs(A_CRUISE_MIN)))
if ceiling.effective_mps is not None or curve.effective_mps is not None:
if curve.effective_mps is not None:
global_floor = min(global_floor, A_CRUISE_MIN)
if selected_acceleration is not None and cruise_profile is not None:
# The selected curve owns acceleration; following/jerks retain their owner.
+37 -116
View File
@@ -1,8 +1,6 @@
#!/usr/bin/env python3
import os
import json
from collections import deque
from dataclasses import replace
from opendbc.car.structs import car
from openpilot.common.params import Params
@@ -12,8 +10,9 @@ from openpilot.selfdrive.controls.lib.ldw import LaneDepartureWarning
from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner, NATIVE_PLAN_INPUTS
import openpilot.cereal.messaging as messaging
from openpilot.cereal.services import SERVICE_LIST
from openpilot.starpilot.speed_limits import history as slc_history
from openpilot.starpilot.speed_limits.runtime import Action as SlcAction, CruiseEvent as SlcCruiseEvent, Runtime as SlcRuntime
from openpilot.starpilot.speed_limits.planner_host import (
SLC_ACTIONS, SavedSlcLifecycle, action_from_message, cruise_event_from_message, current_cruise_event, receive_slc_events,
)
from openpilot.starpilot.longitudinal.profile_runtime import ProfileHost
from openpilot.starpilot.longitudinal.lane_change_gap import LaneGapPreferences
from openpilot.starpilot.longitudinal.experimental_release import ConditionalHandoff
@@ -40,62 +39,8 @@ from openpilot.starpilot.feature_capabilities import longitudinal_supported
from openpilot.starpilot.conditional_mode.projection import paired_clocks_ns
from openpilot.starpilot.conditional_mode.ui_action import observation as ui_manual_observation
from openpilot.starpilot.longitudinal.cruise_ceiling import CurveCeiling
from openpilot.starpilot.feature_runtime import (enabled as feature_enabled, requested as feature_requested,
slc_runtime_settings, slc_transport_capable, vision_control_enabled)
from openpilot.starpilot.feature_runtime import enabled as feature_enabled, requested as feature_requested, slc_transport_capable
import time
from openpilot.starpilot.speed_limits.runtime_settings import read_params as read_slc_settings
class SavedSlcLifecycle:
"""Refresh saved admission; offsets and display units preserve driver choices."""
def __init__(self, params, cp, environment):
self.params, self.cp, self.environment = params, cp, dict(environment)
self.runtime = None
self.configuration = None
self.last_refresh_ns = None
def refresh(self, now_ns: int, *, pending_actions=None, pending_cruise=None):
if self.last_refresh_ns is not None and 0 <= now_ns - self.last_refresh_ns < 1_000_000_000:
return self.runtime, False
self.last_refresh_ns = now_ns
# One immutable parsed snapshot owns this refresh. Repeated qualification
# used to reopen the same saved keys five times in a due model iteration.
saved_slc = read_slc_settings(self.params)
admitted = feature_enabled(self.params, self.cp, 'slc', self.environment, saved_slc=saved_slc)
settings = slc_runtime_settings(self.params, self.cp, self.environment, saved_slc=saved_slc)
vision = admitted and feature_enabled(self.params, self.cp, 'vision', self.environment, saved_slc=saved_slc)
configuration = (admitted and not settings.errors, settings, vision,
vision_control_enabled(self.params, self.cp, saved_slc=saved_slc),
feature_enabled(self.params, self.cp, 'vision', {}, saved_slc=saved_slc),
self.params.get_bool('IsMetric'))
if configuration == self.configuration:
return self.runtime, False
previous_runtime = self.runtime
if (self.runtime is not None and self.configuration is not None and
configuration[:-1] == (self.configuration[0], replace(self.runtime.settings, offsets=settings.offsets,
advance_seconds=settings.advance_seconds),
*self.configuration[2:-1])):
if settings.offsets != self.runtime.settings.offsets or configuration[5] != self.configuration[5]:
self.runtime.update_offsets(settings.offsets, is_metric=configuration[5])
if settings.advance_seconds != self.runtime.settings.advance_seconds:
self.runtime.update_advance(settings.advance_seconds)
else:
set_speed_after_ns = self.runtime.set_speed_after_ns if self.runtime is not None else 0
if self.runtime is not None:
self.runtime.reset()
self.runtime = (SlcRuntime(settings, vision_enabled=vision,
vision_control_qualified=configuration[3],
vision_display_qualified=configuration[4], is_metric=configuration[5])
if configuration[0] else None)
if self.runtime is not None:
self.runtime.set_speed_after_ns = set_speed_after_ns
self.configuration = configuration
if pending_actions is not None:
pending_actions.clear()
if pending_cruise is not None and (self.runtime is None or self.runtime is not previous_runtime):
pending_cruise.clear()
return self.runtime, True
def profile_for_frame(host: ProfileHost | None, sm, CP, now_ns: int, *, traffic_mode: bool | None = False):
@@ -159,11 +104,13 @@ def traffic_profile_status(mode: bool | None, target, applied, settings) -> tupl
def curve_for_frame(host: CurveHost | None, sm, CP, now_ns: int, *, drive_id: int = 0,
event: CurveDriverEvent | None = None, follow_time_s: float | None = None):
event: CurveDriverEvent | None = None, follow_time_s: float | None = None,
cruise_ceiling_mps: float | None = None):
"""A diagnostic runtime result is not planner authority by itself."""
if host is None:
return None, None
result = host.sample(sm, CP, now_ns, drive_id=drive_id, event=event, follow_time_s=follow_time_s)
result = host.sample(sm, CP, now_ns, drive_id=drive_id, event=event, follow_time_s=follow_time_s,
cruise_ceiling_mps=cruise_ceiling_mps)
if result.ceiling_mps is None:
return None, result
model_ns = sm.logMonoTime.get('modelV2', 0)
@@ -177,6 +124,12 @@ def confirm_curve_frame(host: CurveHost | None, planner: LongitudinalPlanner, no
host.runtime.confirm_applied(now_ns, applied=bool(planner.last_curve_ceiling_applied))
def wheel_override_blocked(force_stop: ForceStopRuntime | None, curve_host: CurveHost | None, *, curve_backlog: bool = False) -> bool:
# Force Stop can consume RES on the tick it commits, before a forcing plan is published.
return ((force_stop is not None and (force_stop.policy.can_commit_next_frame or force_stop.plan.speed_ceiling_mps is not None)) or
(curve_host is not None and (curve_backlog or curve_host.runtime.was_controlling)))
def conditional_handoff_for_frame(host: ConditionalPlannerHost | None, sm, CP, now_ns: int,
drive_id: int) -> ConditionalHandoff | None:
if host is None or not CP.openpilotLongitudinalControl or CP.passive or CP.dashcamOnly:
@@ -290,7 +243,8 @@ def update_curve_frame(planner: LongitudinalPlanner, sm, CP, now_ns: int, *, hos
results = []
def provider(follow_time_s: float | None):
ceiling, result = curve_for_frame(host, sm, CP, now_ns, drive_id=drive_id,
event=event, follow_time_s=follow_time_s)
event=event, follow_time_s=follow_time_s,
cruise_ceiling_mps=cruise_ceiling.speed_mps if cruise_ceiling is not None else None)
results.append(result)
return ceiling
planner.update(sm, cruise_ceiling=cruise_ceiling, profile_tuning=profile_tuning,
@@ -320,18 +274,7 @@ def queue_cruise_event(event, slc_events, curve_events, conditional_events=None,
curve_events.append(CurveDriverEvent(str(cruise.producerSessionId), int(cruise.eventId),
int(cruise.observedMonoTime), kind, str(cruise.button), int(event.logMonoTime)))
else:
slc_events.append(SlcCruiseEvent(int(cruise.eventId), int(cruise.observedMonoTime),
float(cruise.previousMps), float(cruise.selectedMps), str(cruise.button),
str(cruise.producerSessionId), int(event.logMonoTime), kind,
str(cruise.sessionId), int(cruise.decisionId),
int(cruise.presentationId), int(cruise.commandId)))
def current_cruise_event(events, now_ns: int):
max_event_age_ns = int(2e9 / SERVICE_LIST['modelV2'].frequency)
while events and now_ns - events[0].observed_ns > max_event_age_ns:
events.popleft()
return events.popleft() if events and events[0].observed_ns <= now_ns else None
slc_events.append(cruise_event_from_message(event))
def queue_ui_action(event, slc_actions, conditional_actions, mode_actions=None) -> None:
@@ -343,9 +286,8 @@ def queue_ui_action(event, slc_actions, conditional_actions, mode_actions=None)
mode_actions.append(event)
elif kind == 'conditionalModeCycle':
conditional_actions.append(event)
elif kind in ('accept', 'reject', 'adopt'):
slc_actions.append((int(event.logMonoTime), SlcAction(str(request.sessionId), int(request.sequenceId),
int(request.decisionId), int(request.presentationId), kind)))
elif kind in SLC_ACTIONS:
slc_actions.append(action_from_message(event))
def current_ui_manual_event(events, now_ns: int):
@@ -417,7 +359,7 @@ def starpilot_main():
pending_switchback = deque(maxlen=8)
mode_settings = ConditionalSettingsOwner(params) if mode_owner is not None else None
slc_lifecycle = SavedSlcLifecycle(params, CP, os.environ)
slc_runtime, _ = slc_lifecycle.refresh(0)
slc_lifecycle.refresh(0)
slc_action_sock = (messaging.sub_sock("slcAction", conflate=False)
if slc_available or conditional_host is not None or mode_owner is not None else None)
slc_cruise_sock = messaging.sub_sock("slcCruiseEvent", conflate=False)
@@ -431,18 +373,14 @@ def starpilot_main():
publish_services = ['longitudinalPlan', 'starpilotLongitudinalPlan', 'driverAssistance']
if slc_available or curve_host is not None or conditional_host is not None or traffic_owner is not None or mode_owner is not None:
publish_services.append('slcState')
if slc_available:
publish_services.append('slcCruiseCommand')
pm = messaging.PubMaster(publish_services)
native_plan_checks = list(NATIVE_PLAN_INPUTS)
subscribed_services = list(NATIVE_PLAN_INPUTS)
subscribed_services.append('deviceState')
if conditional_host is not None:
subscribed_services.append('starpilotRadarState')
optional_slc = ['slcDashboardObservation', 'slcVisionObservation', 'mapdOut', 'starpilotCarState'] if slc_available else []
if slc_available and feature_enabled(params, CP, 'aol', os.environ):
optional_slc.append('aolAxisState')
if force_stop_owner is not None and 'starpilotCarState' not in optional_slc:
optional_slc = slc_lifecycle.source_services() if slc_available else []
if force_stop_owner is not None:
optional_slc.append('starpilotCarState')
if force_stop_owner is not None and 'slcVisionObservation' not in optional_slc:
optional_slc.append('slcVisionObservation')
@@ -461,16 +399,14 @@ def starpilot_main():
# Due saved-setting verification can block on Params. Complete it
# before acquiring the model and fast-source batch, not after that
# batch has already begun aging toward the CEM freshness deadline.
slc_runtime, _ = slc_lifecycle.refresh(time.monotonic_ns(), pending_actions=pending_slc_actions,
pending_cruise=pending_slc_cruise)
slc_lifecycle.refresh(time.monotonic_ns(), pending_actions=pending_slc_actions, pending_cruise=pending_slc_cruise)
sm.update()
if sm.updated['modelV2']:
# Only explicit replay uses the recorded model timeline. In a live host,
# modelV2 may lag newer inputs in the same SubMaster update.
now_ns = int(sm.logMonoTime['modelV2']) if 'REPLAY' in os.environ else time.monotonic_ns()
if 'REPLAY' in os.environ:
slc_runtime, _ = slc_lifecycle.refresh(now_ns, pending_actions=pending_slc_actions,
pending_cruise=pending_slc_cruise)
slc_lifecycle.refresh(now_ns, pending_actions=pending_slc_actions, pending_cruise=pending_slc_cruise)
current_start_ns = (int(sm['deviceState'].startedMonoTime)
if 'deviceState' in subscribed_services and sm.valid['deviceState'] and
sm.alive['deviceState'] and 0 < sm.logMonoTime['deviceState'] <= now_ns else 0)
@@ -481,10 +417,7 @@ def starpilot_main():
planner_status = None
drive_changed = current_start_ns > 0 and drive_start_ns > 0 and current_start_ns != drive_start_ns
if slc_cruise_sock is not None:
for _ in range(8):
event = messaging.recv_one_or_none(slc_cruise_sock)
if event is None:
break
for event in receive_slc_events(slc_cruise_sock):
if str(event.slcCruiseEvent.kind) == 'wheelAction' and event.valid:
pending_wheel.append(event)
continue
@@ -507,10 +440,7 @@ def starpilot_main():
if traffic_owner is not None:
traffic_owner.reset()
if slc_action_sock is not None:
for _ in range(8):
event = messaging.recv_one_or_none(slc_action_sock)
if event is None:
break
for event in receive_slc_events(slc_action_sock):
queue_ui_action(event, pending_slc_actions, pending_conditional_ui, pending_modes)
waiting_wheel = deque(maxlen=32)
while pending_wheel:
@@ -522,25 +452,13 @@ def starpilot_main():
else:
wheel_coast.receive(event, params, CP, sm, now_ns=now_ns)
pending_wheel = waiting_wheel
if slc_runtime is not None:
if drive_changed or slc_runtime.state.reset_required or slc_runtime.ledger.reset_required:
slc_runtime.reset()
pending_slc_actions.clear()
pending_slc_cruise.clear()
max_event_age_ns = int(2e9 / SERVICE_LIST['modelV2'].frequency)
while pending_slc_actions and now_ns - pending_slc_actions[0][0] > max_event_age_ns:
pending_slc_actions.popleft()
cruise_event = current_cruise_event(pending_slc_cruise, now_ns)
request = (pending_slc_actions.popleft()[1] if cruise_event is None and pending_slc_actions and
pending_slc_actions[0][0] <= now_ns else None)
output = slc_runtime.step(sm, CP, now_ns=now_ns, request=request, cruise_event=cruise_event)
planner_status = output.message
if output.command is not None:
pm.send('slcCruiseCommand', output.command)
history_write = output.result.acceptance.history_write if output.result.acceptance is not None else None
# A camera episode has no road-location identity for a later drive.
if history_write is not None and history_write.candidate.source != 'vision':
params.put("SLCQualifiedHistory", json.loads(slc_history.encode(history_write, time.time_ns())))
# Curve observes the press before SLC sees the release. A queued Curve press can still own the tap.
curve_event = current_cruise_event(pending_curve_events, now_ns)
slc_output = slc_lifecycle.step(sm, now_ns, pending_slc_actions, pending_slc_cruise, drive_changed=drive_changed,
wheel_override_blocked=wheel_override_blocked(force_stop_owner, curve_host, curve_backlog=bool(pending_curve_events)))
cruise_ceiling = slc_output.result.ceiling if slc_output is not None else None
if slc_output is not None:
planner_status = slc_output.message
needs_clock = conditional_host is not None or traffic_owner is not None or force_stop_owner is not None or mode_owner is not None
clock_pair = paired_clocks_ns() if needs_clock and 'REPLAY' not in os.environ else None
controller_traffic_toggle = bool(wheel_coast.traffic_toggles % 2)
@@ -600,7 +518,6 @@ def starpilot_main():
lead_approach_key = lead_approach_for_frame(approach_preferences, sm, CP, now_ns)
if curve_preferences is not None and curve_host is not None:
curve_preferences.refresh(curve_host, now_ns)
curve_event = current_cruise_event(pending_curve_events, now_ns)
conditional_handoff = (conditional_handoff_for_frame(conditional_host, sm, CP, clock_pair[0], current_start_ns)
if clock_pair is not None else None)
wheel_coast.conditional_stop = bool(conditional_handoff is not None and conditional_host is not None and
@@ -617,6 +534,7 @@ def starpilot_main():
drive_id=current_start_ns, follow_seconds=follow_seconds,
traffic_mode=traffic_mode, takeoff_enabled=faster_lead_takeoff)
curve_result = update_curve_frame(longitudinal_planner, sm, CP, now_ns, host=curve_host,
cruise_ceiling=cruise_ceiling,
profile_tuning=profile_tuning,
traffic_mode=traffic_mode, lane_change_policy=lane_change_policy,
global_braking_response=global_braking_response, selected_profiles=selected_profiles,
@@ -643,6 +561,7 @@ def starpilot_main():
proposal, snapshot = conditional_host.sample(
sm, CP, longitudinal_planner, now_mono_ns=conditional_now_ns, now_boot_ns=conditional_boot_ns,
sample_skew_ns=conditional_skew_ns, drive_id=current_start_ns,
cruise_ceiling_mps=cruise_ceiling.speed_mps if cruise_ceiling is not None else None,
owner_context=ConditionalOwnerContext(
traffic_mode=traffic_observation(traffic_verdict, model_ns=int(sm.logMonoTime['modelV2']),
owner_present=traffic_owner is not None),
@@ -677,6 +596,8 @@ def starpilot_main():
mode_owner.sample('switchback', sm, CP, now_ns=now_ns), session=mode_session,
sequence=mode_sequence, now_ns=now_ns, source_car_control_ns=int(sm.logMonoTime['carControl']))
if planner_status is not None:
if planner_status.slcState.wheelOverrideSince and wheel_override_blocked(force_stop_owner, curve_host):
planner_status.slcState.wheelOverrideSince = 0
pm.send('slcState', planner_status)
if current_start_ns > 0:
drive_start_ns = current_start_ns
@@ -9,6 +9,7 @@ import unittest
from openpilot.cereal import messaging
from openpilot.common.params import Params
from openpilot.starpilot import feature_runtime
from openpilot.starpilot.speed_limits import planner_host as slc_host
from openpilot.starpilot.speed_limits.runtime_settings import read_params as read_slc_settings
from openpilot.selfdrive.controls import plannerd
from openpilot.starpilot.conditional_mode.tests import test_status_consumer as transport
@@ -98,7 +99,7 @@ class TestCemInputAcquisitionOrder(unittest.TestCase):
def read(params):
reads.append(True)
return read_slc_settings(params)
with patch.object(plannerd, 'read_slc_settings', side_effect=read, create=True), \
with patch.object(slc_host, 'read_slc_settings', side_effect=read), \
patch.object(feature_runtime, 'read_slc_settings', side_effect=read):
runtime, changed = owner.refresh(MONO)
self.assertTrue(changed)
@@ -23,21 +23,21 @@ from openpilot.starpilot.conditional_mode.projection import ConditionalOwnerCont
from openpilot.starpilot.conditional_mode.runtime_settings import DOCUMENT_KEY
from openpilot.starpilot.conditional_mode.status import settings_fingerprint
from openpilot.starpilot.conditional_mode.tests.test_projection import BOOT, MONO, FakeSubMaster, serialized_scene
from openpilot.starpilot.speed_limits.runtime import Action as SlcAction, CruiseEvent as SlcCruiseEvent
class TestConditionalRuntimeLoop(unittest.TestCase):
def test_slc_unit_change_keeps_receipts_for_the_current_command(self):
from openpilot.selfdrive.car.cruise import slc_target_kph
def test_slc_unit_change_preserves_cap_and_queued_driver_event(self):
from openpilot.starpilot.speed_limits import offset_document as od
from openpilot.starpilot.speed_limits.runtime import Action, CruiseEvent
from openpilot.starpilot.speed_limits.tests.test_runtime_replay import RuntimeReplayTests
fixture = RuntimeReplayTests()
from openpilot.starpilot.speed_limits.tests import test_runtime_replay as replay
fixture = replay.RuntimeReplayTests()
fixture.setUp()
sm, cp = fixture.sm, fixture.cp
with tempfile.TemporaryDirectory() as directory:
params = Params(directory)
params.put_bool('SpeedLimitController', True, block=True)
params.put_bool('SLCConfirmation', True, block=True)
params.put('SLCConfirmation', 1, block=True)
params.put('SLCPriority1', 'Dashboard', block=True)
params.put('SLCOffsetSchedule', od.to_value(od.default_document(False)), block=True)
owner = plannerd.SavedSlcLifecycle(params, cp, {})
@@ -46,20 +46,19 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
sm.advance(2_900_000_000)
pending = runtime.step(sm, cp, now_ns=2_900_000_000).message.slcState
sm.advance(2_950_000_000)
issued = runtime.step(sm, cp, now_ns=2_950_000_000,
accepted = runtime.step(sm, cp, now_ns=2_950_000_000,
request=Action(runtime.session_id, 1, pending.decisionId, pending.presentationId, 'accept'))
command = issued.command.slcCruiseCommand
selected = slc_target_kph(command.targetMps, False)
receipts = deque([CruiseEvent(1, 2_990_000_000, 60 / 3.6, selected / 3.6, '', 'card-producer',
2_990_000_000, 'commandApplied', runtime.session_id, command.decisionId, command.presentationId, command.commandId)])
self.assertEqual(accepted.result.ceiling.speed_mps, 25.)
events = deque([CruiseEvent(1, 2_990_000_000, 60/3.6, 65/3.6, 'accel', 'card-producer', 2_990_000_000)])
params.put_bool('IsMetric', True, block=True)
metric, _ = owner.refresh(3_000_000_000, pending_cruise=receipts)
self.assertEqual(len(receipts), 1)
sm['carState'].vCruise = sm['carState'].vCruiseCluster = selected
metric, _ = owner.refresh(3_000_000_000, pending_cruise=events)
self.assertIs(metric, runtime)
self.assertEqual(len(events), 1)
sm['carState'].vCruise = sm['carState'].vCruiseCluster = 65.
sm.advance(3_000_000_000)
applied = metric.step(sm, cp, now_ns=3_000_000_000, cruise_event=receipts.popleft())
self.assertEqual(applied.message.slcState.commandStatus, 'applied')
self.assertIsNone(applied.command)
output = metric.step(sm, cp, now_ns=3_000_000_000, cruise_event=events.popleft())
self.assertEqual(output.result.ceiling.speed_mps, 25.)
self.assertEqual(sm['carState'].vCruise, 65.)
def test_slc_unit_changes_preserve_set_speed_and_driver_choices(self):
from openpilot.common.constants import CV
@@ -68,14 +67,14 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
from openpilot.starpilot.speed_limits.runtime import Action, CruiseEvent
from openpilot.starpilot.speed_limits.tests.test_runtime_replay import RuntimeReplayTests
now = 2_000_000_000
for confirmation, choice in ((False, 'applied'), (True, 'applied'), (False, 'override'), (True, 'decline')):
for confirmation, choice in ((False, 'applied'), (True, 'applied'), (False, 'ceiling'), (True, 'decline')):
with self.subTest(confirmation=confirmation, choice=choice), tempfile.TemporaryDirectory() as directory:
fixture = RuntimeReplayTests()
fixture.setUp()
sm, cp = fixture.sm, fixture.cp
params = Params(directory)
params.put_bool('SpeedLimitController', True, block=True)
params.put_bool('SLCConfirmation', confirmation, block=True)
params.put('SLCConfirmation', int(confirmation), block=True)
params.put('SLCPriority1', 'Dashboard', block=True)
params.put('SLCOffsetSchedule', od.to_value(od.default_document(False)), block=True)
owner = plannerd.SavedSlcLifecycle(params, cp, {})
@@ -84,14 +83,14 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
baseline = slc_target_kph(65 * CV.MPH_TO_MS, False)
sm['carState'].vCruise = sm['carState'].vCruiseCluster = baseline
aligned = original.step(sm, cp, now_ns=now)
self.assertIsNone(aligned.command)
if choice == 'override':
self.assertTrue(aligned.message.slcState.hasAccepted)
if choice == 'ceiling':
selected = slc_target_kph(70 * CV.MPH_TO_MS, False)
sm['carState'].vCruise = sm['carState'].vCruiseCluster = selected
sm.advance(now + 50_000_000)
chosen = original.step(sm, cp, now_ns=now + 50_000_000, cruise_event=CruiseEvent(
1, now + 50_000_000, baseline / 3.6, selected / 3.6, 'accel', 'card-producer', now + 50_000_000))
self.assertTrue(chosen.message.slcState.driverOverrideActive)
self.assertFalse(chosen.message.slcState.driverOverrideActive)
elif choice == 'decline':
sm['slcDashboardObservation'].speedMps = 55 * CV.MPH_TO_MS
sm.advance(now + 50_000_000)
@@ -105,11 +104,10 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
self.assertTrue(changed)
sm.advance(now + 1_000_000_000)
output = metric.step(sm, cp, now_ns=now + 1_000_000_000)
self.assertIsNone(output.command)
self.assertFalse(output.message.slcState.hasPending)
self.assertTrue(output.message.slcState.hasAccepted)
self.assertEqual(sm['carState'].vCruise, selected)
self.assertEqual(output.message.slcState.driverOverrideActive, choice == 'override')
self.assertFalse(output.message.slcState.driverOverrideActive)
if choice == 'decline':
self.assertEqual(output.result.acceptance.basis, 'rejected')
@@ -122,13 +120,11 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
owner = plannerd.SavedSlcLifecycle(params, cp, {})
imperial, _ = owner.refresh(MONO)
self.assertFalse(imperial.is_metric)
imperial.set_speed_after_ns = MONO + 1_050_000_000
actions, cruise = deque([object()]), deque([object()])
params.put_bool('IsMetric', True, block=True)
metric, changed = owner.refresh(MONO + 1_000_000_000, pending_actions=actions, pending_cruise=cruise)
self.assertTrue(changed)
self.assertTrue(metric.is_metric)
self.assertEqual(metric.set_speed_after_ns, MONO + 1_050_000_000)
self.assertIs(metric, imperial)
self.assertFalse(actions)
self.assertEqual(len(cruise), 1)
@@ -168,7 +164,7 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
# Status and transport are reserved independently of saved SLC/CEM
# admission, so a later saved change can attach without restarting.
self.assertEqual(publisher.call_args.args[0], ['longitudinalPlan', 'starpilotLongitudinalPlan',
'driverAssistance', 'slcState', 'slcCruiseCommand'])
'driverAssistance', 'slcState'])
self.assertEqual(conditional_ctor.call_count, int(enabled))
if enabled:
self.assertIs(conditional_ctor.call_args.args[0], params)
@@ -200,8 +196,8 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
self.assertFalse(vision.vision_control_qualified)
self.assertEqual(vision.settings.selection.slots[0], Source.VISION)
session = vision.session_id
pending_actions = deque([(3_000_000_000, plannerd.SlcAction(session, 1, 1, 1, 'accept'))])
pending_cruise = deque([plannerd.SlcCruiseEvent(1, 3_000_000_000, 20., 21., 'set', session_id=session)])
pending_actions = deque([(3_000_000_000, SlcAction(session, 1, 1, 1, 'accept'))])
pending_cruise = deque([SlcCruiseEvent(1, 3_000_000_000, 20., 21., 'set', session_id=session)])
self.assertEqual(owner.refresh(3_050_000_000, pending_actions=pending_actions,
pending_cruise=pending_cruise), (vision, False))
self.assertEqual(len(pending_actions), 1)
@@ -217,8 +213,8 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
self.assertNotEqual(vision.session_id, session) # Retired session was reset.
params.put_bool('ShowSpeedLimits', False, block=True)
params.put_bool('SpeedLimitController', False, block=True)
pending_actions.append((4_050_000_000, plannerd.SlcAction(control.session_id, 1, 1, 1, 'accept')))
pending_cruise.append(plannerd.SlcCruiseEvent(2, 4_050_000_000, 20., 21., 'set', session_id=control.session_id))
pending_actions.append((4_050_000_000, SlcAction(control.session_id, 1, 1, 1, 'accept')))
pending_cruise.append(SlcCruiseEvent(2, 4_050_000_000, 20., 21., 'set', session_id=control.session_id))
self.assertEqual(owner.refresh(5_000_000_000, pending_actions=pending_actions,
pending_cruise=pending_cruise), (None, True))
self.assertFalse(pending_actions)
@@ -251,7 +247,7 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
patch.object(messaging, 'SubMaster', return_value=sm) as subscriber, patch.object(messaging, 'sub_sock'):
with self.assertRaises(EndLoop):
plannerd.starpilot_main()
self.assertIn('slcCruiseCommand', publisher.call_args.args[0])
self.assertNotIn('slcCruiseCommand', publisher.call_args.args[0])
for service in ('slcDashboardObservation', 'slcVisionObservation'):
self.assertIn(service, subscriber.call_args.args[0])
for check in ('ignore_alive', 'ignore_valid', 'ignore_avg_freq'):
@@ -156,7 +156,7 @@ class CurveLoopTests(unittest.TestCase):
stamp = NOW + n * 50_000_000
sm = bus(stamp)
result = update_curve_frame(planner, sm, cp, stamp, host=host, cruise_ceiling=slc)
self.assertIsNotNone(result.ceiling_mps)
self.assertIsNone(result.ceiling_mps)
self.assertFalse(planner.last_curve_ceiling_applied)
self.assertFalse(host.runtime.was_controlling)
self.assertEqual(host.runtime.curve.revision, 0)
@@ -45,326 +45,326 @@ const static double MAHA_THRESH_31 = 3.8414588206941227;
* *
* This file is part of 'ekf' *
******************************************************************************/
void err_fun(double *nom_x, double *delta_x, double *out_999653426340036453) {
out_999653426340036453[0] = delta_x[0] + nom_x[0];
out_999653426340036453[1] = delta_x[1] + nom_x[1];
out_999653426340036453[2] = delta_x[2] + nom_x[2];
out_999653426340036453[3] = delta_x[3] + nom_x[3];
out_999653426340036453[4] = delta_x[4] + nom_x[4];
out_999653426340036453[5] = delta_x[5] + nom_x[5];
out_999653426340036453[6] = delta_x[6] + nom_x[6];
out_999653426340036453[7] = delta_x[7] + nom_x[7];
out_999653426340036453[8] = delta_x[8] + nom_x[8];
void err_fun(double *nom_x, double *delta_x, double *out_90543357710769815) {
out_90543357710769815[0] = delta_x[0] + nom_x[0];
out_90543357710769815[1] = delta_x[1] + nom_x[1];
out_90543357710769815[2] = delta_x[2] + nom_x[2];
out_90543357710769815[3] = delta_x[3] + nom_x[3];
out_90543357710769815[4] = delta_x[4] + nom_x[4];
out_90543357710769815[5] = delta_x[5] + nom_x[5];
out_90543357710769815[6] = delta_x[6] + nom_x[6];
out_90543357710769815[7] = delta_x[7] + nom_x[7];
out_90543357710769815[8] = delta_x[8] + nom_x[8];
}
void inv_err_fun(double *nom_x, double *true_x, double *out_4610969769563887824) {
out_4610969769563887824[0] = -nom_x[0] + true_x[0];
out_4610969769563887824[1] = -nom_x[1] + true_x[1];
out_4610969769563887824[2] = -nom_x[2] + true_x[2];
out_4610969769563887824[3] = -nom_x[3] + true_x[3];
out_4610969769563887824[4] = -nom_x[4] + true_x[4];
out_4610969769563887824[5] = -nom_x[5] + true_x[5];
out_4610969769563887824[6] = -nom_x[6] + true_x[6];
out_4610969769563887824[7] = -nom_x[7] + true_x[7];
out_4610969769563887824[8] = -nom_x[8] + true_x[8];
void inv_err_fun(double *nom_x, double *true_x, double *out_17966199294720670) {
out_17966199294720670[0] = -nom_x[0] + true_x[0];
out_17966199294720670[1] = -nom_x[1] + true_x[1];
out_17966199294720670[2] = -nom_x[2] + true_x[2];
out_17966199294720670[3] = -nom_x[3] + true_x[3];
out_17966199294720670[4] = -nom_x[4] + true_x[4];
out_17966199294720670[5] = -nom_x[5] + true_x[5];
out_17966199294720670[6] = -nom_x[6] + true_x[6];
out_17966199294720670[7] = -nom_x[7] + true_x[7];
out_17966199294720670[8] = -nom_x[8] + true_x[8];
}
void H_mod_fun(double *state, double *out_1018390387391438144) {
out_1018390387391438144[0] = 1.0;
out_1018390387391438144[1] = 0.0;
out_1018390387391438144[2] = 0.0;
out_1018390387391438144[3] = 0.0;
out_1018390387391438144[4] = 0.0;
out_1018390387391438144[5] = 0.0;
out_1018390387391438144[6] = 0.0;
out_1018390387391438144[7] = 0.0;
out_1018390387391438144[8] = 0.0;
out_1018390387391438144[9] = 0.0;
out_1018390387391438144[10] = 1.0;
out_1018390387391438144[11] = 0.0;
out_1018390387391438144[12] = 0.0;
out_1018390387391438144[13] = 0.0;
out_1018390387391438144[14] = 0.0;
out_1018390387391438144[15] = 0.0;
out_1018390387391438144[16] = 0.0;
out_1018390387391438144[17] = 0.0;
out_1018390387391438144[18] = 0.0;
out_1018390387391438144[19] = 0.0;
out_1018390387391438144[20] = 1.0;
out_1018390387391438144[21] = 0.0;
out_1018390387391438144[22] = 0.0;
out_1018390387391438144[23] = 0.0;
out_1018390387391438144[24] = 0.0;
out_1018390387391438144[25] = 0.0;
out_1018390387391438144[26] = 0.0;
out_1018390387391438144[27] = 0.0;
out_1018390387391438144[28] = 0.0;
out_1018390387391438144[29] = 0.0;
out_1018390387391438144[30] = 1.0;
out_1018390387391438144[31] = 0.0;
out_1018390387391438144[32] = 0.0;
out_1018390387391438144[33] = 0.0;
out_1018390387391438144[34] = 0.0;
out_1018390387391438144[35] = 0.0;
out_1018390387391438144[36] = 0.0;
out_1018390387391438144[37] = 0.0;
out_1018390387391438144[38] = 0.0;
out_1018390387391438144[39] = 0.0;
out_1018390387391438144[40] = 1.0;
out_1018390387391438144[41] = 0.0;
out_1018390387391438144[42] = 0.0;
out_1018390387391438144[43] = 0.0;
out_1018390387391438144[44] = 0.0;
out_1018390387391438144[45] = 0.0;
out_1018390387391438144[46] = 0.0;
out_1018390387391438144[47] = 0.0;
out_1018390387391438144[48] = 0.0;
out_1018390387391438144[49] = 0.0;
out_1018390387391438144[50] = 1.0;
out_1018390387391438144[51] = 0.0;
out_1018390387391438144[52] = 0.0;
out_1018390387391438144[53] = 0.0;
out_1018390387391438144[54] = 0.0;
out_1018390387391438144[55] = 0.0;
out_1018390387391438144[56] = 0.0;
out_1018390387391438144[57] = 0.0;
out_1018390387391438144[58] = 0.0;
out_1018390387391438144[59] = 0.0;
out_1018390387391438144[60] = 1.0;
out_1018390387391438144[61] = 0.0;
out_1018390387391438144[62] = 0.0;
out_1018390387391438144[63] = 0.0;
out_1018390387391438144[64] = 0.0;
out_1018390387391438144[65] = 0.0;
out_1018390387391438144[66] = 0.0;
out_1018390387391438144[67] = 0.0;
out_1018390387391438144[68] = 0.0;
out_1018390387391438144[69] = 0.0;
out_1018390387391438144[70] = 1.0;
out_1018390387391438144[71] = 0.0;
out_1018390387391438144[72] = 0.0;
out_1018390387391438144[73] = 0.0;
out_1018390387391438144[74] = 0.0;
out_1018390387391438144[75] = 0.0;
out_1018390387391438144[76] = 0.0;
out_1018390387391438144[77] = 0.0;
out_1018390387391438144[78] = 0.0;
out_1018390387391438144[79] = 0.0;
out_1018390387391438144[80] = 1.0;
void H_mod_fun(double *state, double *out_844907361945072597) {
out_844907361945072597[0] = 1.0;
out_844907361945072597[1] = 0.0;
out_844907361945072597[2] = 0.0;
out_844907361945072597[3] = 0.0;
out_844907361945072597[4] = 0.0;
out_844907361945072597[5] = 0.0;
out_844907361945072597[6] = 0.0;
out_844907361945072597[7] = 0.0;
out_844907361945072597[8] = 0.0;
out_844907361945072597[9] = 0.0;
out_844907361945072597[10] = 1.0;
out_844907361945072597[11] = 0.0;
out_844907361945072597[12] = 0.0;
out_844907361945072597[13] = 0.0;
out_844907361945072597[14] = 0.0;
out_844907361945072597[15] = 0.0;
out_844907361945072597[16] = 0.0;
out_844907361945072597[17] = 0.0;
out_844907361945072597[18] = 0.0;
out_844907361945072597[19] = 0.0;
out_844907361945072597[20] = 1.0;
out_844907361945072597[21] = 0.0;
out_844907361945072597[22] = 0.0;
out_844907361945072597[23] = 0.0;
out_844907361945072597[24] = 0.0;
out_844907361945072597[25] = 0.0;
out_844907361945072597[26] = 0.0;
out_844907361945072597[27] = 0.0;
out_844907361945072597[28] = 0.0;
out_844907361945072597[29] = 0.0;
out_844907361945072597[30] = 1.0;
out_844907361945072597[31] = 0.0;
out_844907361945072597[32] = 0.0;
out_844907361945072597[33] = 0.0;
out_844907361945072597[34] = 0.0;
out_844907361945072597[35] = 0.0;
out_844907361945072597[36] = 0.0;
out_844907361945072597[37] = 0.0;
out_844907361945072597[38] = 0.0;
out_844907361945072597[39] = 0.0;
out_844907361945072597[40] = 1.0;
out_844907361945072597[41] = 0.0;
out_844907361945072597[42] = 0.0;
out_844907361945072597[43] = 0.0;
out_844907361945072597[44] = 0.0;
out_844907361945072597[45] = 0.0;
out_844907361945072597[46] = 0.0;
out_844907361945072597[47] = 0.0;
out_844907361945072597[48] = 0.0;
out_844907361945072597[49] = 0.0;
out_844907361945072597[50] = 1.0;
out_844907361945072597[51] = 0.0;
out_844907361945072597[52] = 0.0;
out_844907361945072597[53] = 0.0;
out_844907361945072597[54] = 0.0;
out_844907361945072597[55] = 0.0;
out_844907361945072597[56] = 0.0;
out_844907361945072597[57] = 0.0;
out_844907361945072597[58] = 0.0;
out_844907361945072597[59] = 0.0;
out_844907361945072597[60] = 1.0;
out_844907361945072597[61] = 0.0;
out_844907361945072597[62] = 0.0;
out_844907361945072597[63] = 0.0;
out_844907361945072597[64] = 0.0;
out_844907361945072597[65] = 0.0;
out_844907361945072597[66] = 0.0;
out_844907361945072597[67] = 0.0;
out_844907361945072597[68] = 0.0;
out_844907361945072597[69] = 0.0;
out_844907361945072597[70] = 1.0;
out_844907361945072597[71] = 0.0;
out_844907361945072597[72] = 0.0;
out_844907361945072597[73] = 0.0;
out_844907361945072597[74] = 0.0;
out_844907361945072597[75] = 0.0;
out_844907361945072597[76] = 0.0;
out_844907361945072597[77] = 0.0;
out_844907361945072597[78] = 0.0;
out_844907361945072597[79] = 0.0;
out_844907361945072597[80] = 1.0;
}
void f_fun(double *state, double dt, double *out_8437871470129484601) {
out_8437871470129484601[0] = state[0];
out_8437871470129484601[1] = state[1];
out_8437871470129484601[2] = state[2];
out_8437871470129484601[3] = state[3];
out_8437871470129484601[4] = state[4];
out_8437871470129484601[5] = dt*((-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4]))*state[6] - 9.8100000000000005*state[8] + stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*state[1]) + (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*state[4])) + state[5];
out_8437871470129484601[6] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*state[4])) + state[6];
out_8437871470129484601[7] = state[7];
out_8437871470129484601[8] = state[8];
void f_fun(double *state, double dt, double *out_7814100581081982154) {
out_7814100581081982154[0] = state[0];
out_7814100581081982154[1] = state[1];
out_7814100581081982154[2] = state[2];
out_7814100581081982154[3] = state[3];
out_7814100581081982154[4] = state[4];
out_7814100581081982154[5] = dt*((-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4]))*state[6] - 9.8100000000000005*state[8] + stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*state[1]) + (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*state[4])) + state[5];
out_7814100581081982154[6] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*state[4])) + state[6];
out_7814100581081982154[7] = state[7];
out_7814100581081982154[8] = state[8];
}
void F_fun(double *state, double dt, double *out_7222922179546997313) {
out_7222922179546997313[0] = 1;
out_7222922179546997313[1] = 0;
out_7222922179546997313[2] = 0;
out_7222922179546997313[3] = 0;
out_7222922179546997313[4] = 0;
out_7222922179546997313[5] = 0;
out_7222922179546997313[6] = 0;
out_7222922179546997313[7] = 0;
out_7222922179546997313[8] = 0;
out_7222922179546997313[9] = 0;
out_7222922179546997313[10] = 1;
out_7222922179546997313[11] = 0;
out_7222922179546997313[12] = 0;
out_7222922179546997313[13] = 0;
out_7222922179546997313[14] = 0;
out_7222922179546997313[15] = 0;
out_7222922179546997313[16] = 0;
out_7222922179546997313[17] = 0;
out_7222922179546997313[18] = 0;
out_7222922179546997313[19] = 0;
out_7222922179546997313[20] = 1;
out_7222922179546997313[21] = 0;
out_7222922179546997313[22] = 0;
out_7222922179546997313[23] = 0;
out_7222922179546997313[24] = 0;
out_7222922179546997313[25] = 0;
out_7222922179546997313[26] = 0;
out_7222922179546997313[27] = 0;
out_7222922179546997313[28] = 0;
out_7222922179546997313[29] = 0;
out_7222922179546997313[30] = 1;
out_7222922179546997313[31] = 0;
out_7222922179546997313[32] = 0;
out_7222922179546997313[33] = 0;
out_7222922179546997313[34] = 0;
out_7222922179546997313[35] = 0;
out_7222922179546997313[36] = 0;
out_7222922179546997313[37] = 0;
out_7222922179546997313[38] = 0;
out_7222922179546997313[39] = 0;
out_7222922179546997313[40] = 1;
out_7222922179546997313[41] = 0;
out_7222922179546997313[42] = 0;
out_7222922179546997313[43] = 0;
out_7222922179546997313[44] = 0;
out_7222922179546997313[45] = dt*(stiffness_front*(-state[2] - state[3] + state[7])/(mass*state[1]) + (-stiffness_front - stiffness_rear)*state[5]/(mass*state[4]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[6]/(mass*state[4]));
out_7222922179546997313[46] = -dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*pow(state[1], 2));
out_7222922179546997313[47] = -dt*stiffness_front*state[0]/(mass*state[1]);
out_7222922179546997313[48] = -dt*stiffness_front*state[0]/(mass*state[1]);
out_7222922179546997313[49] = dt*((-1 - (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*pow(state[4], 2)))*state[6] - (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*pow(state[4], 2)));
out_7222922179546997313[50] = dt*(-stiffness_front*state[0] - stiffness_rear*state[0])/(mass*state[4]) + 1;
out_7222922179546997313[51] = dt*(-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4]));
out_7222922179546997313[52] = dt*stiffness_front*state[0]/(mass*state[1]);
out_7222922179546997313[53] = -9.8100000000000005*dt;
out_7222922179546997313[54] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front - pow(center_to_rear, 2)*stiffness_rear)*state[6]/(rotational_inertia*state[4]));
out_7222922179546997313[55] = -center_to_front*dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*pow(state[1], 2));
out_7222922179546997313[56] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_7222922179546997313[57] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_7222922179546997313[58] = dt*(-(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*pow(state[4], 2)) - (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*pow(state[4], 2)));
out_7222922179546997313[59] = dt*(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(rotational_inertia*state[4]);
out_7222922179546997313[60] = dt*(-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])/(rotational_inertia*state[4]) + 1;
out_7222922179546997313[61] = center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_7222922179546997313[62] = 0;
out_7222922179546997313[63] = 0;
out_7222922179546997313[64] = 0;
out_7222922179546997313[65] = 0;
out_7222922179546997313[66] = 0;
out_7222922179546997313[67] = 0;
out_7222922179546997313[68] = 0;
out_7222922179546997313[69] = 0;
out_7222922179546997313[70] = 1;
out_7222922179546997313[71] = 0;
out_7222922179546997313[72] = 0;
out_7222922179546997313[73] = 0;
out_7222922179546997313[74] = 0;
out_7222922179546997313[75] = 0;
out_7222922179546997313[76] = 0;
out_7222922179546997313[77] = 0;
out_7222922179546997313[78] = 0;
out_7222922179546997313[79] = 0;
out_7222922179546997313[80] = 1;
void F_fun(double *state, double dt, double *out_8005261171107713583) {
out_8005261171107713583[0] = 1;
out_8005261171107713583[1] = 0;
out_8005261171107713583[2] = 0;
out_8005261171107713583[3] = 0;
out_8005261171107713583[4] = 0;
out_8005261171107713583[5] = 0;
out_8005261171107713583[6] = 0;
out_8005261171107713583[7] = 0;
out_8005261171107713583[8] = 0;
out_8005261171107713583[9] = 0;
out_8005261171107713583[10] = 1;
out_8005261171107713583[11] = 0;
out_8005261171107713583[12] = 0;
out_8005261171107713583[13] = 0;
out_8005261171107713583[14] = 0;
out_8005261171107713583[15] = 0;
out_8005261171107713583[16] = 0;
out_8005261171107713583[17] = 0;
out_8005261171107713583[18] = 0;
out_8005261171107713583[19] = 0;
out_8005261171107713583[20] = 1;
out_8005261171107713583[21] = 0;
out_8005261171107713583[22] = 0;
out_8005261171107713583[23] = 0;
out_8005261171107713583[24] = 0;
out_8005261171107713583[25] = 0;
out_8005261171107713583[26] = 0;
out_8005261171107713583[27] = 0;
out_8005261171107713583[28] = 0;
out_8005261171107713583[29] = 0;
out_8005261171107713583[30] = 1;
out_8005261171107713583[31] = 0;
out_8005261171107713583[32] = 0;
out_8005261171107713583[33] = 0;
out_8005261171107713583[34] = 0;
out_8005261171107713583[35] = 0;
out_8005261171107713583[36] = 0;
out_8005261171107713583[37] = 0;
out_8005261171107713583[38] = 0;
out_8005261171107713583[39] = 0;
out_8005261171107713583[40] = 1;
out_8005261171107713583[41] = 0;
out_8005261171107713583[42] = 0;
out_8005261171107713583[43] = 0;
out_8005261171107713583[44] = 0;
out_8005261171107713583[45] = dt*(stiffness_front*(-state[2] - state[3] + state[7])/(mass*state[1]) + (-stiffness_front - stiffness_rear)*state[5]/(mass*state[4]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[6]/(mass*state[4]));
out_8005261171107713583[46] = -dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*pow(state[1], 2));
out_8005261171107713583[47] = -dt*stiffness_front*state[0]/(mass*state[1]);
out_8005261171107713583[48] = -dt*stiffness_front*state[0]/(mass*state[1]);
out_8005261171107713583[49] = dt*((-1 - (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*pow(state[4], 2)))*state[6] - (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*pow(state[4], 2)));
out_8005261171107713583[50] = dt*(-stiffness_front*state[0] - stiffness_rear*state[0])/(mass*state[4]) + 1;
out_8005261171107713583[51] = dt*(-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4]));
out_8005261171107713583[52] = dt*stiffness_front*state[0]/(mass*state[1]);
out_8005261171107713583[53] = -9.8100000000000005*dt;
out_8005261171107713583[54] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front - pow(center_to_rear, 2)*stiffness_rear)*state[6]/(rotational_inertia*state[4]));
out_8005261171107713583[55] = -center_to_front*dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*pow(state[1], 2));
out_8005261171107713583[56] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_8005261171107713583[57] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_8005261171107713583[58] = dt*(-(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*pow(state[4], 2)) - (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*pow(state[4], 2)));
out_8005261171107713583[59] = dt*(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(rotational_inertia*state[4]);
out_8005261171107713583[60] = dt*(-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])/(rotational_inertia*state[4]) + 1;
out_8005261171107713583[61] = center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_8005261171107713583[62] = 0;
out_8005261171107713583[63] = 0;
out_8005261171107713583[64] = 0;
out_8005261171107713583[65] = 0;
out_8005261171107713583[66] = 0;
out_8005261171107713583[67] = 0;
out_8005261171107713583[68] = 0;
out_8005261171107713583[69] = 0;
out_8005261171107713583[70] = 1;
out_8005261171107713583[71] = 0;
out_8005261171107713583[72] = 0;
out_8005261171107713583[73] = 0;
out_8005261171107713583[74] = 0;
out_8005261171107713583[75] = 0;
out_8005261171107713583[76] = 0;
out_8005261171107713583[77] = 0;
out_8005261171107713583[78] = 0;
out_8005261171107713583[79] = 0;
out_8005261171107713583[80] = 1;
}
void h_25(double *state, double *unused, double *out_5845994727189855200) {
out_5845994727189855200[0] = state[6];
void h_25(double *state, double *unused, double *out_3451529468624779566) {
out_3451529468624779566[0] = state[6];
}
void H_25(double *state, double *unused, double *out_1618166537623988050) {
out_1618166537623988050[0] = 0;
out_1618166537623988050[1] = 0;
out_1618166537623988050[2] = 0;
out_1618166537623988050[3] = 0;
out_1618166537623988050[4] = 0;
out_1618166537623988050[5] = 0;
out_1618166537623988050[6] = 1;
out_1618166537623988050[7] = 0;
out_1618166537623988050[8] = 0;
void H_25(double *state, double *unused, double *out_339572919744775885) {
out_339572919744775885[0] = 0;
out_339572919744775885[1] = 0;
out_339572919744775885[2] = 0;
out_339572919744775885[3] = 0;
out_339572919744775885[4] = 0;
out_339572919744775885[5] = 0;
out_339572919744775885[6] = 1;
out_339572919744775885[7] = 0;
out_339572919744775885[8] = 0;
}
void h_24(double *state, double *unused, double *out_2733857150290724482) {
out_2733857150290724482[0] = state[4];
out_2733857150290724482[1] = state[5];
void h_24(double *state, double *unused, double *out_3799606392279303524) {
out_3799606392279303524[0] = state[4];
out_3799606392279303524[1] = state[5];
}
void H_24(double *state, double *unused, double *out_3790816136629487616) {
out_3790816136629487616[0] = 0;
out_3790816136629487616[1] = 0;
out_3790816136629487616[2] = 0;
out_3790816136629487616[3] = 0;
out_3790816136629487616[4] = 1;
out_3790816136629487616[5] = 0;
out_3790816136629487616[6] = 0;
out_3790816136629487616[7] = 0;
out_3790816136629487616[8] = 0;
out_3790816136629487616[9] = 0;
out_3790816136629487616[10] = 0;
out_3790816136629487616[11] = 0;
out_3790816136629487616[12] = 0;
out_3790816136629487616[13] = 0;
out_3790816136629487616[14] = 1;
out_3790816136629487616[15] = 0;
out_3790816136629487616[16] = 0;
out_3790816136629487616[17] = 0;
void H_24(double *state, double *unused, double *out_3383890129749124077) {
out_3383890129749124077[0] = 0;
out_3383890129749124077[1] = 0;
out_3383890129749124077[2] = 0;
out_3383890129749124077[3] = 0;
out_3383890129749124077[4] = 1;
out_3383890129749124077[5] = 0;
out_3383890129749124077[6] = 0;
out_3383890129749124077[7] = 0;
out_3383890129749124077[8] = 0;
out_3383890129749124077[9] = 0;
out_3383890129749124077[10] = 0;
out_3383890129749124077[11] = 0;
out_3383890129749124077[12] = 0;
out_3383890129749124077[13] = 0;
out_3383890129749124077[14] = 1;
out_3383890129749124077[15] = 0;
out_3383890129749124077[16] = 0;
out_3383890129749124077[17] = 0;
}
void h_30(double *state, double *unused, double *out_2208545198935832850) {
out_2208545198935832850[0] = state[4];
void h_30(double *state, double *unused, double *out_2912064509677036295) {
out_2912064509677036295[0] = state[4];
}
void H_30(double *state, double *unused, double *out_6145862867751596248) {
out_6145862867751596248[0] = 0;
out_6145862867751596248[1] = 0;
out_6145862867751596248[2] = 0;
out_6145862867751596248[3] = 0;
out_6145862867751596248[4] = 1;
out_6145862867751596248[5] = 0;
out_6145862867751596248[6] = 0;
out_6145862867751596248[7] = 0;
out_6145862867751596248[8] = 0;
void H_30(double *state, double *unused, double *out_6577117421746840870) {
out_6577117421746840870[0] = 0;
out_6577117421746840870[1] = 0;
out_6577117421746840870[2] = 0;
out_6577117421746840870[3] = 0;
out_6577117421746840870[4] = 1;
out_6577117421746840870[5] = 0;
out_6577117421746840870[6] = 0;
out_6577117421746840870[7] = 0;
out_6577117421746840870[8] = 0;
}
void h_26(double *state, double *unused, double *out_604006904334020533) {
out_604006904334020533[0] = state[7];
void h_26(double *state, double *unused, double *out_2089608757983809350) {
out_2089608757983809350[0] = state[7];
}
void H_26(double *state, double *unused, double *out_5359669856498044274) {
out_5359669856498044274[0] = 0;
out_5359669856498044274[1] = 0;
out_5359669856498044274[2] = 0;
out_5359669856498044274[3] = 0;
out_5359669856498044274[4] = 0;
out_5359669856498044274[5] = 0;
out_5359669856498044274[6] = 0;
out_5359669856498044274[7] = 1;
out_5359669856498044274[8] = 0;
void H_26(double *state, double *unused, double *out_4081076238618832109) {
out_4081076238618832109[0] = 0;
out_4081076238618832109[1] = 0;
out_4081076238618832109[2] = 0;
out_4081076238618832109[3] = 0;
out_4081076238618832109[4] = 0;
out_4081076238618832109[5] = 0;
out_4081076238618832109[6] = 0;
out_4081076238618832109[7] = 1;
out_4081076238618832109[8] = 0;
}
void h_27(double *state, double *unused, double *out_844321459529009555) {
out_844321459529009555[0] = state[3];
void h_27(double *state, double *unused, double *out_7992451901291706271) {
out_7992451901291706271[0] = state[3];
}
void H_27(double *state, double *unused, double *out_8320626179552021159) {
out_8320626179552021159[0] = 0;
out_8320626179552021159[1] = 0;
out_8320626179552021159[2] = 0;
out_8320626179552021159[3] = 1;
out_8320626179552021159[4] = 0;
out_8320626179552021159[5] = 0;
out_8320626179552021159[6] = 0;
out_8320626179552021159[7] = 0;
out_8320626179552021159[8] = 0;
void H_27(double *state, double *unused, double *out_2643675178688440866) {
out_2643675178688440866[0] = 0;
out_2643675178688440866[1] = 0;
out_2643675178688440866[2] = 0;
out_2643675178688440866[3] = 1;
out_2643675178688440866[4] = 0;
out_2643675178688440866[5] = 0;
out_2643675178688440866[6] = 0;
out_2643675178688440866[7] = 0;
out_2643675178688440866[8] = 0;
}
void h_29(double *state, double *unused, double *out_2793128068725012795) {
out_2793128068725012795[0] = state[1];
void h_29(double *state, double *unused, double *out_99512406407182912) {
out_99512406407182912[0] = state[1];
}
void H_29(double *state, double *unused, double *out_5635631523437204064) {
out_5635631523437204064[0] = 0;
out_5635631523437204064[1] = 1;
out_5635631523437204064[2] = 0;
out_5635631523437204064[3] = 0;
out_5635631523437204064[4] = 0;
out_5635631523437204064[5] = 0;
out_5635631523437204064[6] = 0;
out_5635631523437204064[7] = 0;
out_5635631523437204064[8] = 0;
void H_29(double *state, double *unused, double *out_41319477426376229) {
out_41319477426376229[0] = 0;
out_41319477426376229[1] = 1;
out_41319477426376229[2] = 0;
out_41319477426376229[3] = 0;
out_41319477426376229[4] = 0;
out_41319477426376229[5] = 0;
out_41319477426376229[6] = 0;
out_41319477426376229[7] = 0;
out_41319477426376229[8] = 0;
}
void h_28(double *state, double *unused, double *out_4484828697352558873) {
out_4484828697352558873[0] = state[0];
void h_28(double *state, double *unused, double *out_4705680922922981175) {
out_4705680922922981175[0] = state[0];
}
void H_28(double *state, double *unused, double *out_3672001251871877813) {
out_3672001251871877813[0] = 1;
out_3672001251871877813[1] = 0;
out_3672001251871877813[2] = 0;
out_3672001251871877813[3] = 0;
out_3672001251871877813[4] = 0;
out_3672001251871877813[5] = 0;
out_3672001251871877813[6] = 0;
out_3672001251871877813[7] = 0;
out_3672001251871877813[8] = 0;
void H_28(double *state, double *unused, double *out_2004949748991702480) {
out_2004949748991702480[0] = 1;
out_2004949748991702480[1] = 0;
out_2004949748991702480[2] = 0;
out_2004949748991702480[3] = 0;
out_2004949748991702480[4] = 0;
out_2004949748991702480[5] = 0;
out_2004949748991702480[6] = 0;
out_2004949748991702480[7] = 0;
out_2004949748991702480[8] = 0;
}
void h_31(double *state, double *unused, double *out_2046944767694668159) {
out_2046944767694668159[0] = state[8];
void h_31(double *state, double *unused, double *out_1000579080861491944) {
out_1000579080861491944[0] = state[8];
}
void H_31(double *state, double *unused, double *out_5985877958731395750) {
out_5985877958731395750[0] = 0;
out_5985877958731395750[1] = 0;
out_5985877958731395750[2] = 0;
out_5985877958731395750[3] = 0;
out_5985877958731395750[4] = 0;
out_5985877958731395750[5] = 0;
out_5985877958731395750[6] = 0;
out_5985877958731395750[7] = 0;
out_5985877958731395750[8] = 1;
void H_31(double *state, double *unused, double *out_308926957867815457) {
out_308926957867815457[0] = 0;
out_308926957867815457[1] = 0;
out_308926957867815457[2] = 0;
out_308926957867815457[3] = 0;
out_308926957867815457[4] = 0;
out_308926957867815457[5] = 0;
out_308926957867815457[6] = 0;
out_308926957867815457[7] = 0;
out_308926957867815457[8] = 1;
}
#include <eigen3/Eigen/Dense>
#include <iostream>
@@ -518,68 +518,68 @@ void car_update_28(double *in_x, double *in_P, double *in_z, double *in_R, doubl
void car_update_31(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea) {
update<1, 3, 0>(in_x, in_P, h_31, H_31, NULL, in_z, in_R, in_ea, MAHA_THRESH_31);
}
void car_err_fun(double *nom_x, double *delta_x, double *out_999653426340036453) {
err_fun(nom_x, delta_x, out_999653426340036453);
void car_err_fun(double *nom_x, double *delta_x, double *out_90543357710769815) {
err_fun(nom_x, delta_x, out_90543357710769815);
}
void car_inv_err_fun(double *nom_x, double *true_x, double *out_4610969769563887824) {
inv_err_fun(nom_x, true_x, out_4610969769563887824);
void car_inv_err_fun(double *nom_x, double *true_x, double *out_17966199294720670) {
inv_err_fun(nom_x, true_x, out_17966199294720670);
}
void car_H_mod_fun(double *state, double *out_1018390387391438144) {
H_mod_fun(state, out_1018390387391438144);
void car_H_mod_fun(double *state, double *out_844907361945072597) {
H_mod_fun(state, out_844907361945072597);
}
void car_f_fun(double *state, double dt, double *out_8437871470129484601) {
f_fun(state, dt, out_8437871470129484601);
void car_f_fun(double *state, double dt, double *out_7814100581081982154) {
f_fun(state, dt, out_7814100581081982154);
}
void car_F_fun(double *state, double dt, double *out_7222922179546997313) {
F_fun(state, dt, out_7222922179546997313);
void car_F_fun(double *state, double dt, double *out_8005261171107713583) {
F_fun(state, dt, out_8005261171107713583);
}
void car_h_25(double *state, double *unused, double *out_5845994727189855200) {
h_25(state, unused, out_5845994727189855200);
void car_h_25(double *state, double *unused, double *out_3451529468624779566) {
h_25(state, unused, out_3451529468624779566);
}
void car_H_25(double *state, double *unused, double *out_1618166537623988050) {
H_25(state, unused, out_1618166537623988050);
void car_H_25(double *state, double *unused, double *out_339572919744775885) {
H_25(state, unused, out_339572919744775885);
}
void car_h_24(double *state, double *unused, double *out_2733857150290724482) {
h_24(state, unused, out_2733857150290724482);
void car_h_24(double *state, double *unused, double *out_3799606392279303524) {
h_24(state, unused, out_3799606392279303524);
}
void car_H_24(double *state, double *unused, double *out_3790816136629487616) {
H_24(state, unused, out_3790816136629487616);
void car_H_24(double *state, double *unused, double *out_3383890129749124077) {
H_24(state, unused, out_3383890129749124077);
}
void car_h_30(double *state, double *unused, double *out_2208545198935832850) {
h_30(state, unused, out_2208545198935832850);
void car_h_30(double *state, double *unused, double *out_2912064509677036295) {
h_30(state, unused, out_2912064509677036295);
}
void car_H_30(double *state, double *unused, double *out_6145862867751596248) {
H_30(state, unused, out_6145862867751596248);
void car_H_30(double *state, double *unused, double *out_6577117421746840870) {
H_30(state, unused, out_6577117421746840870);
}
void car_h_26(double *state, double *unused, double *out_604006904334020533) {
h_26(state, unused, out_604006904334020533);
void car_h_26(double *state, double *unused, double *out_2089608757983809350) {
h_26(state, unused, out_2089608757983809350);
}
void car_H_26(double *state, double *unused, double *out_5359669856498044274) {
H_26(state, unused, out_5359669856498044274);
void car_H_26(double *state, double *unused, double *out_4081076238618832109) {
H_26(state, unused, out_4081076238618832109);
}
void car_h_27(double *state, double *unused, double *out_844321459529009555) {
h_27(state, unused, out_844321459529009555);
void car_h_27(double *state, double *unused, double *out_7992451901291706271) {
h_27(state, unused, out_7992451901291706271);
}
void car_H_27(double *state, double *unused, double *out_8320626179552021159) {
H_27(state, unused, out_8320626179552021159);
void car_H_27(double *state, double *unused, double *out_2643675178688440866) {
H_27(state, unused, out_2643675178688440866);
}
void car_h_29(double *state, double *unused, double *out_2793128068725012795) {
h_29(state, unused, out_2793128068725012795);
void car_h_29(double *state, double *unused, double *out_99512406407182912) {
h_29(state, unused, out_99512406407182912);
}
void car_H_29(double *state, double *unused, double *out_5635631523437204064) {
H_29(state, unused, out_5635631523437204064);
void car_H_29(double *state, double *unused, double *out_41319477426376229) {
H_29(state, unused, out_41319477426376229);
}
void car_h_28(double *state, double *unused, double *out_4484828697352558873) {
h_28(state, unused, out_4484828697352558873);
void car_h_28(double *state, double *unused, double *out_4705680922922981175) {
h_28(state, unused, out_4705680922922981175);
}
void car_H_28(double *state, double *unused, double *out_3672001251871877813) {
H_28(state, unused, out_3672001251871877813);
void car_H_28(double *state, double *unused, double *out_2004949748991702480) {
H_28(state, unused, out_2004949748991702480);
}
void car_h_31(double *state, double *unused, double *out_2046944767694668159) {
h_31(state, unused, out_2046944767694668159);
void car_h_31(double *state, double *unused, double *out_1000579080861491944) {
h_31(state, unused, out_1000579080861491944);
}
void car_H_31(double *state, double *unused, double *out_5985877958731395750) {
H_31(state, unused, out_5985877958731395750);
void car_H_31(double *state, double *unused, double *out_308926957867815457) {
H_31(state, unused, out_308926957867815457);
}
void car_predict(double *in_x, double *in_P, double *in_Q, double dt) {
predict(in_x, in_P, in_Q, dt);
@@ -9,27 +9,27 @@ void car_update_27(double *in_x, double *in_P, double *in_z, double *in_R, doubl
void car_update_29(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_update_28(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_update_31(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_err_fun(double *nom_x, double *delta_x, double *out_999653426340036453);
void car_inv_err_fun(double *nom_x, double *true_x, double *out_4610969769563887824);
void car_H_mod_fun(double *state, double *out_1018390387391438144);
void car_f_fun(double *state, double dt, double *out_8437871470129484601);
void car_F_fun(double *state, double dt, double *out_7222922179546997313);
void car_h_25(double *state, double *unused, double *out_5845994727189855200);
void car_H_25(double *state, double *unused, double *out_1618166537623988050);
void car_h_24(double *state, double *unused, double *out_2733857150290724482);
void car_H_24(double *state, double *unused, double *out_3790816136629487616);
void car_h_30(double *state, double *unused, double *out_2208545198935832850);
void car_H_30(double *state, double *unused, double *out_6145862867751596248);
void car_h_26(double *state, double *unused, double *out_604006904334020533);
void car_H_26(double *state, double *unused, double *out_5359669856498044274);
void car_h_27(double *state, double *unused, double *out_844321459529009555);
void car_H_27(double *state, double *unused, double *out_8320626179552021159);
void car_h_29(double *state, double *unused, double *out_2793128068725012795);
void car_H_29(double *state, double *unused, double *out_5635631523437204064);
void car_h_28(double *state, double *unused, double *out_4484828697352558873);
void car_H_28(double *state, double *unused, double *out_3672001251871877813);
void car_h_31(double *state, double *unused, double *out_2046944767694668159);
void car_H_31(double *state, double *unused, double *out_5985877958731395750);
void car_err_fun(double *nom_x, double *delta_x, double *out_90543357710769815);
void car_inv_err_fun(double *nom_x, double *true_x, double *out_17966199294720670);
void car_H_mod_fun(double *state, double *out_844907361945072597);
void car_f_fun(double *state, double dt, double *out_7814100581081982154);
void car_F_fun(double *state, double dt, double *out_8005261171107713583);
void car_h_25(double *state, double *unused, double *out_3451529468624779566);
void car_H_25(double *state, double *unused, double *out_339572919744775885);
void car_h_24(double *state, double *unused, double *out_3799606392279303524);
void car_H_24(double *state, double *unused, double *out_3383890129749124077);
void car_h_30(double *state, double *unused, double *out_2912064509677036295);
void car_H_30(double *state, double *unused, double *out_6577117421746840870);
void car_h_26(double *state, double *unused, double *out_2089608757983809350);
void car_H_26(double *state, double *unused, double *out_4081076238618832109);
void car_h_27(double *state, double *unused, double *out_7992451901291706271);
void car_H_27(double *state, double *unused, double *out_2643675178688440866);
void car_h_29(double *state, double *unused, double *out_99512406407182912);
void car_H_29(double *state, double *unused, double *out_41319477426376229);
void car_h_28(double *state, double *unused, double *out_4705680922922981175);
void car_H_28(double *state, double *unused, double *out_2004949748991702480);
void car_h_31(double *state, double *unused, double *out_1000579080861491944);
void car_H_31(double *state, double *unused, double *out_308926957867815457);
void car_predict(double *in_x, double *in_P, double *in_Q, double dt);
void car_set_mass(double x);
void car_set_rotational_inertia(double x);
File diff suppressed because it is too large Load Diff
@@ -5,18 +5,18 @@ void pose_update_4(double *in_x, double *in_P, double *in_z, double *in_R, doubl
void pose_update_10(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void pose_update_13(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void pose_update_14(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void pose_err_fun(double *nom_x, double *delta_x, double *out_8942860553158829980);
void pose_inv_err_fun(double *nom_x, double *true_x, double *out_2254641408306223903);
void pose_H_mod_fun(double *state, double *out_76292315910108330);
void pose_f_fun(double *state, double dt, double *out_1412378285720339068);
void pose_F_fun(double *state, double dt, double *out_5059879879978958987);
void pose_h_4(double *state, double *unused, double *out_8306941651867152905);
void pose_H_4(double *state, double *unused, double *out_41214293034027689);
void pose_h_10(double *state, double *unused, double *out_4127406154043783729);
void pose_H_10(double *state, double *unused, double *out_7440694459650338948);
void pose_h_13(double *state, double *unused, double *out_2904306600296367164);
void pose_H_13(double *state, double *unused, double *out_3171059532298305112);
void pose_h_14(double *state, double *unused, double *out_6520989930447577741);
void pose_H_14(double *state, double *unused, double *out_3922026563305456840);
void pose_err_fun(double *nom_x, double *delta_x, double *out_6057666269876821352);
void pose_inv_err_fun(double *nom_x, double *true_x, double *out_5874123099794783116);
void pose_H_mod_fun(double *state, double *out_3022146375872577553);
void pose_f_fun(double *state, double dt, double *out_5302456385426177188);
void pose_F_fun(double *state, double dt, double *out_1927640881032223950);
void pose_h_4(double *state, double *unused, double *out_61275514649772420);
void pose_H_4(double *state, double *unused, double *out_8870228182624536274);
void pose_h_10(double *state, double *unused, double *out_3113540078229992172);
void pose_H_10(double *state, double *unused, double *out_8133353293446248542);
void pose_h_13(double *state, double *unused, double *out_9190534258819938312);
void pose_H_13(double *state, double *unused, double *out_5657954357292203473);
void pose_h_14(double *state, double *unused, double *out_4475366908773028757);
void pose_H_14(double *state, double *unused, double *out_9141399364440131743);
void pose_predict(double *in_x, double *in_P, double *in_Q, double dt);
}
+3 -6
View File
@@ -41,9 +41,7 @@ from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
from openpilot.common.transformations.model import get_warp_matrix
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
from openpilot.starpilot.lateral.lane_change_preferences import LiveLaneChangePolicy
from openpilot.starpilot.lateral.auto_lane_change import (
ClockEpochGuard, auto_evidence, auto_trigger_engaged, paired_clocks_ns, session_policy, steering_capable,
)
from openpilot.starpilot.lateral.auto_lane_change import ClockEpochGuard, auto_evidence, paired_clocks_ns, session_policy, steering_capable
from openpilot.starpilot.lateral.lane_change_status_wire import (
Direction as StatusDirection, LaneChangeStatus, Phase as StatusPhase, encode_optional as encode_lane_status,
)
@@ -85,7 +83,7 @@ def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.
lat_action_t)
else:
desired_accel = model_output['action'][0,1]
desired_curvature = model_output['action'][0,0] / (max(2.5, v_ego))**2
desired_curvature = model_output['action'][0,0] / (max(1.0, v_ego))**2
stop = should_stop(v_ego, desired_accel)
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS)
if v_ego > MIN_LAT_CONTROL_SPEED:
@@ -651,8 +649,7 @@ def main(demo=False):
model_valid=bool(modelv2_send.valid), vehicle_capable=auto_vehicle_capable))
engaged = bool(sm['carControl'].latActive)
DH.update(car_state, sm['carControl'].latActive, lane_change_prob,
auto_evidence=auto_ready, engaged=auto_trigger_engaged(sm, runtime_lane_policy,
openpilot_longitudinal=bool(CP.openpilotLongitudinalControl), now_mono_ns=mono_now_ns),
auto_evidence=auto_ready, engaged=engaged,
navigation_turn=matching_turn_signal(sm, mono_now_ns, supported=turn_supported))
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
@@ -25,7 +25,7 @@ def action_function():
def test_action_curvature_low_speed_threshold(speed):
previous = log.ModelDataV2.Action(desiredCurvature=0.01, desiredAcceleration=0.1)
action = action_function()({"action": np.array([[0.25, 0.5]])}, previous, 0.2, 0.2, speed)
expected = smooth_value(0.25 / max(2.5, speed) ** 2, 0.01, LAT_SMOOTH_SECONDS) if speed > 0.3 else 0.01
expected = smooth_value(0.25 / max(1.0, speed) ** 2, 0.01, LAT_SMOOTH_SECONDS) if speed > 0.3 else 0.01
assert action.desiredCurvature == pytest.approx(expected)
assert action.desiredAcceleration == pytest.approx(smooth_value(0.5, 0.1, 0.3))
assert not action.shouldStop
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+10 -15
View File
@@ -215,8 +215,9 @@ class SelfdriveD:
car_recognized = self.CP.brand != 'mock'
# A stock-ACC startup can be temporary. Preserve saved driving choices;
# experimental_requested still gates their use on current longitudinal capability.
# Capability changes affect runtime authority, never the saved Alpha Long choice.
if not self.CP.openpilotLongitudinalControl:
self.params.remove("ExperimentalMode")
self.CS_prev = car.CarState.new_message()
self.AM = AlertManager()
@@ -1274,8 +1275,7 @@ class SelfdriveD:
not 0 < intent.carStateLogMonoTime <= now_ns or now_ns - intent.carStateLogMonoTime > INTENT_MAX_AGE_NS or
intent.carStateLogMonoTime <= anchor[2].carStateLogMonoTime or
intent.producerSessionId != anchor[0][4] or companion is None or companion.integrity_epoch != anchor[0][5] or
(self.events.contains(ET.USER_DISABLE) and not stock_lateral_role(self, CS, native, intent,
allow_independent_brake=True, allow_independent_gas=True)) or
(self.events.contains(ET.USER_DISABLE) and not stock_lateral_role(self, CS, native, intent, allow_independent_gas=True)) or
self.events.contains(ET.IMMEDIATE_DISABLE) or
native is None or native is not getattr(self.aol_transport_pause, 'observed_native', None) or
native.pandaSerial != anchor[1].pandaSerial or native.observedMonoTime < anchor[1].observedMonoTime or
@@ -1458,13 +1458,12 @@ class SelfdriveD:
if user_disable:
from opendbc.car.hyundai.canfd_angle_aol import qualified as angle_qualified
from opendbc.car.hyundai.canfd_stock_aol import qualified as stock_canfd_qualified
from opendbc.car.hyundai.classic_scc_aol import qualified as stock_classic_qualified
from openpilot.starpilot.car.toyota.aol import qualified as toyota_qualified
if (toyota_qualified(self.CP, marked_only=True) and CS.standstill or
angle_qualified(self.CP, marked_only=True) or
self.CP.brand == 'hyundai' and len(self.CP.safetyConfigs) == 1 and
(stock_canfd_qualified(self.CP, marked_only=True) or stock_classic_qualified(self.CP, marked_only=True)) or
stock_canfd_qualified(self.CP, marked_only=True) or
self.CP.brand == 'gm' and qualified_gm(self.CP) and self.CP.pcmCruise and not self.CP.openpilotLongitudinalControl):
native = current_native(self.sm, self.CP, now_ns=now_ns, axis_session_id=self.aol_session_id)
if stock_lateral_role(self, CS, native, intent, allow_independent_cancel=True,
@@ -1781,13 +1780,12 @@ class SelfdriveD:
def _decide_aol_axes(self, CS, intent, native, *, now_ns=0, ordinary_lag=False):
from openpilot.starpilot.car.gm.aol import physical_cancel_lateral_allowed, stock_ascm_independent_lateral, independent_lateral_below_engage
from openpilot.starpilot.car.gm.aol import physical_cancel_lateral_allowed, stock_ascm_independent_lateral, cc_independent_lateral_below_engage
from openpilot.starpilot.car.hyundai.aol import physical_cancel_lateral_allowed as hkg_cancel_lateral_allowed
from openpilot.starpilot.car.mazda.aol import physical_cancel_lateral_allowed as mazda_cancel_lateral_allowed
from openpilot.starpilot.car.ford.aol import physical_cancel_lateral_allowed as ford_cancel_lateral_allowed
from openpilot.starpilot.car.subaru.aol import sng_standstill_lateral_inhibit
from openpilot.starpilot.car.volkswagen.aol import (lateral_output_inhibited as volkswagen_lateral_output_inhibited,
below_engage_lateral_allowed as volkswagen_below_engage_lateral_allowed)
from openpilot.starpilot.car.volkswagen.aol import lateral_output_inhibited as volkswagen_lateral_output_inhibited
from openpilot.starpilot.aol.dm import lateral_event_context
from openpilot.starpilot.car.toyota.legacy_lateral import below_engage_lateral_allowed as toyota_below_engage_lateral_allowed
retained_dm_lateral, lateral_events = lateral_event_context(self, CS, intent, native, now_ns, ordinary_lag)
@@ -1821,13 +1819,12 @@ class SelfdriveD:
cruise_main_required=getattr(self, 'aol_cruise_main_required', True),
allow_below_engage_speed=bool((is_volt_ascm_longitudinal(self.CP) or
stock_ascm_independent_lateral(self.CP) or
independent_lateral_below_engage(self.CP, intent,
cc_independent_lateral_below_engage(self.CP, intent,
enabled=self.enabled, active=self.active)) and
not self.CP.flags & GMFlags.PEDAL_LONG) or
toyota_below_engage_lateral_allowed(self.CP, CS, intent, native,
source_ns=getattr(self, 'aol_car_state_log_ns', 0), now_ns=now_ns,
axis_session_id=getattr(self, 'aol_session_id', '')) or
volkswagen_below_engage_lateral_allowed(self.CP, CS, intent)),
axis_session_id=getattr(self, 'aol_session_id', ''))),
immediate_disable=self.events.contains(ET.IMMEDIATE_DISABLE),
dm_lockout=bool(not self.sm.all_checks(['driverMonitoringState']) or
not retained_dm_lateral and (self.sm['driverMonitoringState'].lockout or
@@ -1883,9 +1880,7 @@ class SelfdriveD:
ordinary_ford = bool(self.CP.brand == 'ford' and self.CP.openpilotLongitudinalControl and self.CP.safetyConfigs and
ford_native_accepts_cp(self.CP, int(car.CarParams.SafetyModel.ford), self.CP.safetyConfigs[-1].safetyParam))
# Driver steering does not invalidate a still-current native LAT grant.
from opendbc.car.volkswagen.long_aol import qualified as volkswagen_long_qualified
steering_override_allowed = (ordinary_ford or self.CP.brand == 'hyundai' or
volkswagen_long_qualified(self.CP, marked_only=True))
steering_override_allowed = ordinary_ford or self.CP.brand == 'hyundai'
proof = getattr(self, 'aol_published_input_proof', None)
prior = getattr(self, 'aol_published_input_native', None)
anchor = getattr(self, 'aol_ordinary_source_anchor', None)
@@ -189,9 +189,11 @@ class TogglesLayout(Widget):
self._toggles["ExperimentalMode"].set_description(e2e_description)
self._long_personality_setting.action_item.set_enabled(True)
else:
# Unavailable now, but retain the saved choice for a qualified drive.
# no long for now
self._toggles["ExperimentalMode"].action_item.set_enabled(False)
self._toggles["ExperimentalMode"].action_item.set_state(False)
self._long_personality_setting.action_item.set_enabled(False)
self._params.remove("ExperimentalMode")
unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.")
@@ -136,9 +136,11 @@ class TogglesLayoutMici(NavScroller):
self._experimental_btn.set_visible(True)
self._personality_toggle.set_visible(True)
else:
# Unavailable now, but retain the saved choice for a qualified drive.
# no long for now
self._experimental_btn.set_visible(False)
self._experimental_btn.set_checked(False)
self._personality_toggle.set_visible(False)
ui_state.params.remove("ExperimentalMode")
# Refresh toggles from params to mirror external changes
for key, item in self._refresh_toggles:
+9 -5
View File
@@ -16,6 +16,8 @@ GearShifter = car.CarState.GearShifter
AOL_TOGGLE = 9
PAUSE_LATERAL = 3
PAUSE_LONGITUDINAL = 4
# The only wheel action WheelPublisher delivers from cruise MAIN.
ADOPT_SPEED_LIMIT = 10
@dataclass(frozen=True)
class AolSettings:
@@ -28,6 +30,7 @@ class AolSettings:
mode_actions: tuple[int, int, int] = (0, 0, 0)
custom_actions: tuple[int, int, int] = (0, 0, 0)
default_physical_buttons: bool = True
main_wheel_action: bool = False
def _button_value(params, key: str) -> int | None:
@@ -64,6 +67,7 @@ def read_settings(params) -> AolSettings:
enabled=params.get_bool('AlwaysOnLateral') and valid_pause, pause_brake_mps=pause_mps if valid_pause else 0.0,
lkas_action=_axis_action(lkas), main_action=_axis_action(main),
default_physical_buttons=lkas == 0 and main == 0,
main_wheel_action=main == ADOPT_SPEED_LIMIT,
distance_actions=(_button_action(params, 'DistanceButtonControl'),
_button_action(params, 'LongDistanceButtonControl'),
_button_action(params, 'VeryLongDistanceButtonControl')),
@@ -141,11 +145,10 @@ def disarming_fault(events, CS, *, temporary_ui_process_failure: bool = False,
class AolCardIntent:
def __init__(self, settings: AolSettings, *, explicit_latch: bool = False, lkas_button_supported: bool = True,
independent_cruise_buttons: bool = False, preserve_unassigned_cancel: bool = False):
independent_cruise_buttons: bool = False):
self.settings = settings
self.explicit_latch = explicit_latch
self.independent_cruise_buttons = independent_cruise_buttons
self.preserve_unassigned_cancel = preserve_unassigned_cancel
self.lkas_button_supported = lkas_button_supported
self.allowed_latch = False
# Observational only: records the exact branch that changed True to False.
@@ -320,7 +323,8 @@ class AolCardIntent:
independent_buttons = self.independent_cruise_buttons and (main_managed or lkas_managed)
# The opt-in is bound to existing HKG torque owners. Other physical
# owners keep their current event and native contracts.
main_toggle = not independent_buttons or main_managed
main_toggle = (not independent_buttons or main_managed) and not (
self.independent_cruise_buttons and self.settings.main_wheel_action)
lkas_toggle = not independent_buttons or lkas_managed
previously_held = (self._main_held and main_toggle) or (self._lkas_held and lkas_toggle)
cancel_pressed = False
@@ -342,12 +346,12 @@ class AolCardIntent:
(not independent_buttons or int(ButtonType.mainCruise) not in consumed_buttons)) or
(self._lkas_held and lkas_toggle and
(not independent_buttons or int(ButtonType.lkas) not in consumed_buttons)))
if not previously_held and gesture_held and (not (independent_buttons or self.preserve_unassigned_cancel) or not cancel_pressed):
if not previously_held and gesture_held and (not independent_buttons or not cancel_pressed):
self._last_latch_edge_ns = now_ns
if cancel_pressed:
# An unassigned cruise CANCEL cannot toggle, arm or renew independent
# lateral. Keep the existing withdrawal for configured CANCEL actions.
if not (self.independent_cruise_buttons or self.preserve_unassigned_cancel) or any(self.settings.cancel_actions):
if not self.independent_cruise_buttons or any(self.settings.cancel_actions):
self._record_latch_clear('cancel_press')
self.allowed_latch = False
elif not self._gesture_neutral_seen:
+1 -1
View File
@@ -121,7 +121,7 @@ class IntentCompanion:
verified = ((stamp, *raw_verified) if raw_current else self.verified_sm_projection)
if (verified is not None and verified[0] == stamp and verified[2] == self.integrity_epoch and
verified[3] == self.session and sm.valid.get(INTENT_SERVICE, False) and
0 < stamp <= now_ns <= min(stamp + SAFETY_MAX_AGE_NS, verified[1].validUntilMonoTime) and
sm.alive.get(INTENT_SERVICE, False) and 0 < stamp <= now_ns <= stamp + SAFETY_MAX_AGE_NS and
(stamp < source_ns or (source_ns == 0 or source_ns == stamp) and
0 < stamp <= now_ns <= stamp + SAFETY_MAX_AGE_NS) and
now_ns - stamp > INTENT_MAX_AGE_NS and decode_intent(sm[INTENT_SERVICE]) == verified[1]):
+9 -21
View File
@@ -15,10 +15,6 @@ AlertLevel = log.DriverMonitoringState.AlertLevel
def stock_lateral_role(self, CS, native, intent=None, *, allow_independent_cancel=False, allow_independent_brake=False, allow_independent_gas=False, now_ns=0):
"""Current stock USER role while an established independent LAT owner remains."""
from opendbc.car.hyundai.classic_scc_aol import qualified as classic_stock_qualified
hkg_stock = bool(self.CP.brand == 'hyundai' and classic_stock_qualified(self.CP, marked_only=True))
main_off_role = bool(hkg_stock and not getattr(self, 'aol_cruise_main_required', True) and
not CS.cruiseState.available)
cancel_role = False
if allow_independent_cancel:
from opendbc.car.hyundai.canfd_angle_aol import qualified as angle_qualified
@@ -37,13 +33,11 @@ def stock_lateral_role(self, CS, native, intent=None, *, allow_independent_cance
settings = getattr(self, 'aol_settings', None)
previous = getattr(self, 'aol_axis_decision', None)
brake_role = held_lateral_pedal(self, CS) or bool(settings is not None and settings.enabled and CS.brakePressed and not CS.regenBraking and
previous is not None and (previous.lateral_active or hkg_stock and
getattr(self, 'aol_fault_reason', 'none') == 'availabilityPause' and previous.desired_lateral) and
previous.native_acknowledged and not previous.longitudinal_active and
previous is not None and previous.lateral_active and previous.native_acknowledged and not previous.longitudinal_active and
(toyota_qualified(self.CP, marked_only=True) and CS.standstill and not CS.cruiseState.enabled and
CS.gearShifter == car.CarState.GearShifter.drive and isfinite(CS.vEgoRaw) and isfinite(CS.vEgo) or
CS.vEgo >= settings.pause_brake_mps and command_allowed(self.CP, CS) or
len(self.CP.safetyConfigs) == 1 and (stock_canfd_qualified(self.CP, marked_only=True) or hkg_stock) and
len(self.CP.safetyConfigs) == 1 and stock_canfd_qualified(self.CP, marked_only=True) and
CS.gearShifter == car.CarState.GearShifter.drive and isfinite(CS.vEgoRaw) and isfinite(CS.vEgo) and
(settings.pause_brake_mps <= 0 or CS.vEgo >= settings.pause_brake_mps or CS.standstill)))
gas_role = False
@@ -56,11 +50,11 @@ def stock_lateral_role(self, CS, native, intent=None, *, allow_independent_cance
from opendbc.car.subaru.stock_aol import qualified as subaru_qualified
gas_role = bool((self.CP.brand == 'hyundai' and len(self.CP.safetyConfigs) == 1 and
(stock_canfd_qualified(self.CP, marked_only=True) or hkg_stock) or self.CP.brand == 'gm' and qualified_gm(self.CP) and self.CP.pcmCruise and
stock_canfd_qualified(self.CP, marked_only=True) or self.CP.brand == 'gm' and qualified_gm(self.CP) and self.CP.pcmCruise and
not self.CP.openpilotLongitudinalControl or
self.CP.brand == 'subaru' and subaru_qualified(self.CP, marked_only=True)) and settings is not None and settings.enabled and
CS.gasPressed and not CS.brakePressed and not CS.regenBraking and
previous is not None and (previous.lateral_active or (self.CP.brand in ('gm', 'subaru') or hkg_stock) and
previous is not None and (previous.lateral_active or self.CP.brand in ('gm', 'subaru') and
getattr(self, 'aol_fault_reason', 'none') == 'availabilityPause' and previous.desired_lateral) and
previous.native_acknowledged and not previous.longitudinal_active and
(CS.gearShifter == car.CarState.GearShifter.drive or self.CP.brand == 'gm' and
@@ -69,7 +63,7 @@ def stock_lateral_role(self, CS, native, intent=None, *, allow_independent_cance
stock_user = stock_lateral_user_event(self.CP)
users = [name for name in self.events.names if ET.USER_DISABLE in rules.get(name, {})]
if stock_user is None or not users or any((name != stock_user and not (cancel_role and name == EventName.buttonCancel) and
not (brake_role and name == EventName.pedalPressed) and not (main_off_role and name == EventName.wrongCarMode)) or
not (brake_role and name == EventName.pedalPressed)) or
ET.SOFT_DISABLE in rules.get(name, {}) or ET.IMMEDIATE_DISABLE in rules.get(name, {}) for name in users):
return False
anchor = getattr(self, 'aol_ordinary_source_anchor', None)
@@ -83,8 +77,7 @@ def stock_lateral_role(self, CS, native, intent=None, *, allow_independent_cance
self.sm['driverMonitoringState'].lockout or self.sm['driverMonitoringState'].alwaysOnLockout or
not stock_lateral_off(self.CP, CS, native, allow_independent_cancel=cancel_role,
allow_independent_brake=brake_role,
allow_independent_gas=gas_role or brake_role and self.CP.brand == 'toyota' and CS.gasPressed,
allow_independent_main=main_off_role)):
allow_independent_gas=gas_role or brake_role and self.CP.brand == 'toyota' and CS.gasPressed)):
return False
accepted = intent if intent is not None else anchor[2]
companion = getattr(self, 'aol_intent_companion', None)
@@ -141,11 +134,7 @@ def ordinary_source_availability(self, CS, now_ns, native=None, *, replay: bool,
from openpilot.starpilot.car.gm.aol import held_gm_lateral_pedal
from openpilot.starpilot.car.toyota.aol import held_lateral_pedal
toyota_pedal = held_lateral_pedal(self, CS)
hkg_brake = False
if self.CP.brand == 'hyundai' and CS.brakePressed and anchor is not None and CS is anchor[4]:
native = native or native_provider(self.sm, self.CP, now_ns=now_ns, axis_session_id=self.aol_session_id)
hkg_brake = stock_lateral_role(self, CS, native, allow_independent_brake=True)
held_pedal = held_gm_lateral_pedal(self, CS) or toyota_pedal or hkg_brake
held_pedal = held_gm_lateral_pedal(self, CS) or toyota_pedal
if held_pedal:
events = [event for event in events if not (event.name == EventName.pedalPressed and
event.userDisable and not event.softDisable and not event.immediateDisable)]
@@ -158,9 +147,8 @@ def ordinary_source_availability(self, CS, now_ns, native=None, *, replay: bool,
if stock_lateral_role(self, CS, native, allow_independent_brake=held_pedal, allow_independent_gas=True):
stock_gas = bool(CS.gasPressed and anchor is not None and anchor[4].gasPressed)
stock_user = stock_lateral_user_event(self.CP)
events = [event for event in events if not ((event.name == stock_user or
self.CP.brand == 'hyundai' and not CS.cruiseState.available and event.name == EventName.wrongCarMode) and
event.userDisable and not event.softDisable and not event.immediateDisable)]
events = [event for event in events if not (event.name == stock_user and event.userDisable and
not event.softDisable and not event.immediateDisable)]
if (replay or not self.aol_replay or not self.axis_transport_required or anchor is None or
not self.initialized or self.aol_authority_lost or
getattr(self, 'aol_availability_long_inhibit', False) and stock_user is None or
@@ -59,14 +59,10 @@ def temporary_steering_intent_current(self, sm, cp, cs, axis, stamp, now_ns, *,
not axis.desiredLongitudinal and not axis.longitudinalActive and not cs.steeringDisengage and
cs.cruiseState.available and not cs.cruiseState.enabled and stock_user is not None and
any(event.name == stock_user and event.userDisable for event in sm['onroadEvents']))
from openpilot.starpilot.car.subaru.aol import temporary_eps_brake_recovery
subaru_brake = bool(stock_eps_user and temporary_eps_brake_recovery(cp, cs))
# Seatbelt blocks normal LONG, not admitted LAT intent. Keep warnings/native/output checks.
events = tuple(event for event in sm['onroadEvents'] if not (
stock_eps_user and event.name == stock_user and event.userDisable and
not event.softDisable and not event.immediateDisable) and not (
subaru_brake and event.name == log.OnroadEvent.EventName.pedalPressed and event.userDisable and
not event.softDisable and not event.immediateDisable) and not (
self.lateral_only_baseline and cs.seatbeltUnlatched and not axis.desiredLongitudinal and
not axis.longitudinalActive and event.name == log.OnroadEvent.EventName.seatbeltNotLatched and
event.softDisable and event.noEntry and not event.immediateDisable and not event.userDisable))
@@ -1,98 +0,0 @@
"""An aged conflating cache is metadata, never a new raw integrity violation."""
from contextlib import ExitStack
from dataclasses import replace
from unittest.mock import patch
import pytest
from opendbc.car.gm.tests.test_bolt_pedal import params
from opendbc.car.gm.values import CAR
from openpilot.cereal import messaging
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.selfdrive.selfdrived.events import EventName
from openpilot.starpilot.aol.intent_companion import IntentCompanion
from openpilot.starpilot.aol.wire import decode_intent, encode_intent
from openpilot.starpilot.aol.tests import test_ioniq_availability as fixture
from openpilot.starpilot.aol.tests.test_input_deadline_continuity import intent
BASE = 10_000_000_000
SERVICE = 'aolIntentWire'
def drain(companion, message):
with patch('openpilot.starpilot.aol.intent_companion.messaging.recv_one_or_none', side_effect=[message.as_reader(), None]):
assert companion.drain()
@pytest.mark.parametrize('bad', ['none', 'cached_invalid', 'cached_tampered', 'raw_invalid', 'expired', 'short_lease'])
def test_aged_known_sm_does_not_poison_valid_raw_successor_or_grant_output(bad):
with OpenpilotPrefix():
sm = messaging.SubMaster([SERVICE])
companion = IntentCompanion(sock=object())
original = intent(BASE, 1)
if bad == 'short_lease':
original.aolIntentWire = encode_intent(replace(decode_intent(bytes(original.aolIntentWire)),
validUntilMonoTime=BASE + 50_000_000))
sm.update_msgs(BASE / 1e9, [original.as_reader()])
drain(companion, original)
assert companion.current(sm, source_ns=BASE, now_ns=BASE + 1_000_000, drain=False) is not None
# A normal source wait prunes the output cache, retaining only its original metadata proof.
assert companion.current(sm, source_ns=0, now_ns=BASE + 60_000_000, drain=False) is None
assert BASE not in companion.samples
epoch = companion.integrity_epoch
now = BASE + (210_000_000 if bad == 'expired' else 110_000_000)
successor = intent(now - 1_000_000, 2)
if bad == 'raw_invalid':
successor.valid = False
drain(companion, successor)
sm.alive[SERVICE] = False
if bad == 'cached_invalid':
sm.valid[SERVICE] = False
elif bad == 'cached_tampered':
changed = intent(BASE, 1, latched=False)
sm.update_msgs(now / 1e9, [changed.as_reader()])
sm.alive[SERVICE] = False
assert companion.current(sm, source_ns=0, now_ns=now, drain=False) is None
assert companion.current(sm, source_ns=successor.logMonoTime, now_ns=now, drain=False) is None
if bad == 'none':
assert companion.latest is not None
assert companion.integrity_epoch == epoch and companion.latest[1]
sm.update_msgs(now / 1e9, [successor.as_reader()])
assert companion.current(sm, source_ns=successor.logMonoTime, now_ns=now, drain=False) is not None
else:
assert companion.integrity_epoch > epoch
def test_actual_bolt_source_wait_survives_raw_successor_before_conflated_cache_recovers():
with OpenpilotPrefix(), ExitStack() as cleanup:
case = fixture.IoniqAvailabilityTests(methodName='runTest')
cleanup.callback(case.doCleanups)
cp = params(CAR.CHEVROLET_BOLT_CC_2018_2021, pedal=True, camera=True)
sd, controls, step = case._authority_callers(False, cp=cp, genuine_events=True)
for tick in (0, 1):
axis, _, command = step(tick, native_request_long=False, panda_controls_allowed=False)
assert axis.lateralActive and command.latActive and not command.longActive
old = intent(BASE + 10_000_000, 2)
for tick in (4, 7):
axis, _, command = step(tick, offset_ns=1_000_000 if tick == 4 else 0, timeout=True, state_stamp=BASE + 10_000_000,
intent_message=old, native_request_long=False, panda_controls_allowed=False)
assert str(axis.faultReason) == 'availabilityPause'
assert not axis.lateralActive and not command.latActive and not command.longActive
epoch = sd.aol_intent_companion.integrity_epoch
original_update = sd.sm.update_msgs
def cached_poll(now, messages):
original_update(now, [m for m in messages if m.which() != SERVICE])
sd.sm.alive[SERVICE] = False
successor = intent(BASE + 110_000_000, 12)
with patch.object(sd.sm, 'update_msgs', side_effect=cached_poll):
axis, state, command = step(11, timeout=True, state_stamp=BASE + 10_000_000,
intent_message=successor, native_request_long=False, panda_controls_allowed=False)
assert sd.aol_intent_companion.integrity_epoch == epoch
assert str(axis.faultReason) == 'availabilityPause' and axis.desiredLateral and axis.nativeAcknowledged
assert not axis.lateralActive and not axis.longitudinalActive and axis.sourceCarStateMonoTime == 0
assert not command.latActive and not command.longActive and not state.enabled
assert EventName.controlsMismatch not in sd.events.names and not sd.aol_authority_lost
axis, _, command = step(12, native_request_long=False, panda_controls_allowed=False)
assert axis.lateralActive and command.latActive and not command.longActive
assert EventName.controlsMismatch not in sd.events.names
@@ -21,59 +21,6 @@ IDENTITIES = (CAR.HYUNDAI_PALISADE, CAR.HYUNDAI_SONATA, CAR.HYUNDAI_SONATA_HYBRI
CAR.HYUNDAI_ELANTRA_2021, CAR.HYUNDAI_KONA_EV, 'palisade_hdai', 'palisade_hdaii')
def brake_health_before_permission_echo(card, cs, now):
native = withdrawal(card, cs, now, controls=True)
native = native_change(card.sm, native, longitudinalAllowed=True)
change(card.sm, 'pandaStates', lambda pandas: setattr(pandas[0], 'controlsAllowed', False))
card.sm.logMonoTime['pandaStates'] = now + 1_000_000
return native
@pytest.mark.parametrize('identity', IDENTITIES)
def test_current_brake_health_precedes_old_full_permission_echo(identity):
card, cs = armed_card(identity)
now = NOW + 10_000_000
cs.brakePressed = True
native = brake_health_before_permission_echo(card, cs, now)
fault = card.aol_disarming_fault(cs, now, now + 2_000_000)
card.aol_card_intent.update(cs, fault_active=fault, now_ns=now + 2_000_000)
assert not fault and card.aol_card_intent.allowed_latch, 'BRAKE_HEALTH_FIRST_ERASED_AOL'
result = axes(card, cs, native, now + 2_000_000)
assert result.lateral_active and not result.longitudinal_active
@pytest.mark.parametrize('fault', ('equal_health', 'older_health', 'not_braking', 'gas', 'regen',
'rx', 'heartbeat', 'native_fault', 'latest_fault', 'lat_denied',
'expired', 'producer', 'epoch', 'source', 'unlatched'))
def test_brake_health_first_never_admits_unproven_or_faulted_lateral(fault):
card, cs = armed_card(CAR.HYUNDAI_ELANTRA_2021)
now = NOW + 10_000_000
cs.brakePressed = True
native = brake_health_before_permission_echo(card, cs, now)
if fault in ('equal_health', 'older_health'):
card.sm.logMonoTime['pandaStates'] = now - int(fault == 'older_health')
elif fault in ('not_braking', 'gas', 'regen'):
setattr(cs, {'not_braking':'brakePressed', 'gas':'gasPressed', 'regen':'regenBraking'}[fault], fault != 'not_braking')
elif fault in ('rx', 'heartbeat', 'native_fault'):
field, value = {'rx': ('safetyRxChecksInvalid', True), 'heartbeat': ('heartbeatLost', True), 'native_fault': ('faults', 1)}[fault]
native_change(card.sm, native, pandaInventory=(replace(native.pandaInventory[0], **{field: value}),))
elif fault == 'latest_fault':
change(card.sm, 'pandaStates', lambda pandas: setattr(pandas[0], 'faults', ['relayMalfunction']))
elif fault == 'lat_denied':
native_change(card.sm, native, lateralAllowed=False)
elif fault == 'expired':
native_change(card.sm, native, observedMonoTime=now - 200_000_001, validUntilMonoTime=now - 1)
elif fault in ('producer', 'epoch', 'source'):
field, value = {'producer': ('intentProducerSessionId', 'foreign'), 'epoch': ('intentIntegrityEpoch', 99),
'source': ('sourceCarStateMonoTime', now - 30_000_001)}[fault]
change(card.sm, 'aolAxisState', lambda axis: setattr(axis, field, value))
else:
card.aol_card_intent.allowed_latch = False
rejected = card.aol_disarming_fault(cs, now, now + 2_000_000)
card.aol_card_intent.update(cs, fault_active=rejected, now_ns=now + 2_000_000)
assert not card.aol_card_intent.allowed_latch
def profile(identity):
if identity in ('palisade_hdai', 'palisade_hdaii'):
from opendbc.car.hyundai.tests.test_palisade_2023 import params as mixed_params
@@ -1,60 +0,0 @@
"""Cruise's engagement floor cannot clear independent GM steering intent."""
from contextlib import ExitStack
import pytest
from opendbc.car.gm.tests.test_ascm_intercept import params
from opendbc.car.gm.values import CAR
from openpilot.cereal import messaging
from openpilot.common.params import Params
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.selfdrive.selfdrived.events import EventName
from openpilot.starpilot.aol.intent import read_settings
from openpilot.starpilot.aol.tests import test_gm as gm_fixture, test_ioniq_availability as fixture
from openpilot.starpilot.aol.wire import IntentState, encode_intent
from openpilot.starpilot.audio.tests.test_axis_alerts import AudioFixture, AudibleAlert
@pytest.mark.parametrize('candidate', [CAR.CHEVROLET_BLAZER, CAR.CHEVROLET_TRAVERSE, CAR.CHEVROLET_MALIBU_ASCM])
@pytest.mark.parametrize('braking', [False, True])
def test_acc_cruise_floor_does_not_clear_main_intent_or_chime_on_pedals(candidate, braking):
with OpenpilotPrefix(), ExitStack() as cleanup:
saved = Params()
saved.put_bool('AlwaysOnLateral', True, block=True)
card = gm_fixture.TestGmAol.card(params(candidate, sascm=True, alpha=True), saved)
cleanup.callback(card.vehicle_startup.close)
card.slc_producer_session = 'authority-card'
card.aol_card_intent.allowed_latch = True
case = fixture.IoniqAvailabilityTests(methodName='runTest')
cleanup.callback(case.doCleanups)
sd, _, step = case._authority_callers(False, cp=card.CP.as_reader().as_builder(), genuine_events=True)
sd.aol_settings = read_settings(sd.params)
audio = AudioFixture()
sounds = []
for tick, speed in enumerate((5., 4., 3.2, 2., 1.5, 1.3, .5, 0., .5, 1.3, 2., 3.2, 4.)):
now = 10_000_000_000 + tick * 10_000_000
cs = fixture.car_state(brake=braking and tick < 8)
cs.vEgo = cs.vEgoRaw = speed
cs.standstill = speed == 0.
cs.cruiseState.enabled = False
axis, host, control = step(tick, observed_state=cs, gas=tick >= 8,
latched=card.aol_card_intent.allowed_latch, native_request_long=False, panda_controls_allowed=False)
assert not host.enabled and not control.longActive and not axis.longitudinalActive
assert axis.desiredLateral and axis.lateralActive and axis.nativeAcknowledged
assert bool(control.latActive) == (speed >= card.CP.minSteerSpeed)
below_cruise_minimum = speed < card.CP.minEnableSpeed and not (cs.brakePressed and cs.standstill)
assert (EventName.belowEngageSpeed in sd.events.names) == below_cruise_minimum
frames = [sd.pm.messages[n] for n in ('aolAxisState', 'onroadEvents', 'selfdriveState')]
for service in ('pandaStates', 'aolSafetyWire', 'deviceState'):
msg = messaging.new_message(service, len(sd.sm[service]) if service in ('pandaStates', 'aolSafetyWire') else None,
valid=sd.sm.valid[service], logMonoTime=sd.sm.logMonoTime[service])
setattr(msg, service, sd.sm[service])
frames.append(msg.as_reader())
card.sm.update_msgs(now / 1e9, frames)
rejected = card.aol_disarming_fault(sd.CS_prev, int(card.sm.logMonoTime['onroadEvents']), now)
card.aol_card_intent.update(sd.CS_prev, fault_active=rejected, now_ns=now)
assert not rejected and card.aol_card_intent.allowed_latch
wire = messaging.new_message('aolIntentWire', 0, valid=True, logMonoTime=now)
wire.aolIntentWire = encode_intent(IntentState('authority-card', tick+1, now, now, now+200_000_000,
True, False, False, True, card.aol_card_intent.allowed_latch))
sounds.append(audio.publish(intent_message=wire, enabled=False, native=host.alertSound))
assert sounds == [AudibleAlert.none] * len(sounds)
@@ -16,7 +16,7 @@ from openpilot.starpilot.aol.runtime import decide_axes
from openpilot.starpilot.aol.tests.test_gm_parked_startup import _exercise_parked_main
from openpilot.starpilot.aol.tests.test_gm_startup_main import cases
from openpilot.starpilot.aol.wire import IntentState
from openpilot.starpilot.car.gm.aol import independent_lateral_below_engage
from openpilot.starpilot.car.gm.aol import cc_independent_lateral_below_engage
from openpilot.starpilot.nostalgia import aol_no_entry
@@ -35,7 +35,7 @@ def test_actual_bolt_main_park_then_low_speed_drive_without_main_edge(main_actio
@pytest.mark.parametrize('identity', [CAR.CHEVROLET_BOLT_CC_2017, CAR.CHEVROLET_BOLT_CC_2018_2021, CAR.CHEVROLET_BOLT_CC_2022_2023])
def test_exact_cc_long_factory_owners(identity):
cp, intent = setup(identity)
assert independent_lateral_below_engage(cp, intent, enabled=False, active=False)
assert cc_independent_lateral_below_engage(cp, intent, enabled=False, active=False)
@pytest.mark.parametrize('enabled,active', [(False, False), (True, False), (False, True), (True, True)])
@@ -44,7 +44,7 @@ def test_minimum_exemption_cannot_engage_ordinary_long(enabled, active, long_gra
cp, intent = setup()
cs = car.CarState(canValid=True, gearShifter='drive', vEgo=4.8, standstill=False)
cs.cruiseState.available = True
allowed = independent_lateral_below_engage(cp, intent, enabled=enabled, active=active)
allowed = cc_independent_lateral_below_engage(cp, intent, enabled=enabled, active=active)
no_entry = aol_no_entry([EventName.belowEngageSpeed], cs, paddle_only_cancel=False, allow_below_engage_speed=allowed)
native = SimpleNamespace(requestedLateral=True, lateralAllowed=True, requestedLongitudinal=long_grant, longitudinalAllowed=long_grant)
decision = decide_axes(
@@ -68,22 +68,22 @@ def test_minimum_exemption_cannot_engage_ordinary_long(enabled, active, long_gra
@pytest.mark.parametrize('field', ['settingsQualified', 'allowedLatch', 'lateralArmed'])
def test_current_armed_intent_required(field):
cp, intent = setup()
assert not independent_lateral_below_engage(cp, replace(intent, **{field: False}), enabled=False, active=False)
assert not cc_independent_lateral_below_engage(cp, replace(intent, **{field: False}), enabled=False, active=False)
def test_lateral_pause_and_missing_intent_remain_denied():
cp, intent = setup()
assert not independent_lateral_below_engage(cp, None, enabled=False, active=False)
assert not independent_lateral_below_engage(cp, replace(intent, pauseLateral=True), enabled=False, active=False)
assert independent_lateral_below_engage(cp, replace(intent, pauseLongitudinal=True), enabled=False, active=False)
assert not cc_independent_lateral_below_engage(cp, None, enabled=False, active=False)
assert not cc_independent_lateral_below_engage(cp, replace(intent, pauseLateral=True), enabled=False, active=False)
assert cc_independent_lateral_below_engage(cp, replace(intent, pauseLongitudinal=True), enabled=False, active=False)
@pytest.mark.parametrize('index', [0, 1, 3, 4, 5, 6, 7])
def test_factory_profiles_use_independent_steering_minimum(index):
def test_other_factory_profiles_keep_existing_admission(index):
_, intent = setup()
cp = list(cases())[index]
cp.alternativeExperience = 32
assert independent_lateral_below_engage(cp, intent, enabled=False, active=False)
assert not cc_independent_lateral_below_engage(cp, intent, enabled=False, active=False)
@pytest.mark.parametrize(
@@ -93,7 +93,7 @@ def test_factory_profiles_use_independent_steering_minimum(index):
def test_changed_configuration_is_not_a_cc_long_owner(field, value):
cp, intent = setup()
setattr(cp, field, value)
assert not independent_lateral_below_engage(cp, intent, enabled=False, active=False)
assert not cc_independent_lateral_below_engage(cp, intent, enabled=False, active=False)
@pytest.mark.parametrize('event', [EventName.buttonCancel, EventName.doorOpen, EventName.controlsMismatch, EventName.commIssue, EventName.steerUnavailable])
@@ -101,5 +101,5 @@ def test_only_cruise_speed_event_is_removed(event):
cp, intent = setup()
cs = car.CarState(canValid=True, gearShifter='drive', vEgo=4.8)
cs.cruiseState.available = True
allowed = independent_lateral_below_engage(cp, intent, enabled=False, active=False)
allowed = cc_independent_lateral_below_engage(cp, intent, enabled=False, active=False)
assert aol_no_entry([EventName.belowEngageSpeed, event], cs, paddle_only_cancel=False, allow_below_engage_speed=allowed) == (event != EventName.doorOpen)
@@ -18,7 +18,7 @@ from openpilot.common.prefix import OpenpilotPrefix
from openpilot.starpilot.aol.intent import read_settings
from openpilot.starpilot.aol.tests import test_gm_parked_startup as parked, test_ioniq_availability as fixture
from openpilot.starpilot.aol.tests.test_gm_cc_lateral_minimum import setup
from openpilot.starpilot.car.gm.aol import independent_lateral_below_engage
from openpilot.starpilot.car.gm.aol import cc_independent_lateral_below_engage
def cc_params(candidate):
@@ -31,16 +31,16 @@ def cc_params(candidate):
def test_all_exact_ordinary_cc_owners_keep_independent_steering_minimum(candidate):
cp = cc_params(candidate)
_, intent = setup()
assert independent_lateral_below_engage(cp, intent, enabled=False, active=False)
assert cc_independent_lateral_below_engage(cp, intent, enabled=False, active=False)
for enabled, active in ((True, True), (True, False), (False, True)):
assert not independent_lateral_below_engage(cp, intent, enabled=enabled, active=active)
assert not cc_independent_lateral_below_engage(cp, intent, enabled=enabled, active=active)
for key in ('settingsQualified', 'allowedLatch', 'lateralArmed'):
assert not independent_lateral_below_engage(cp, replace(intent, **{key: False}), enabled=False, active=False)
assert not independent_lateral_below_engage(cp, replace(intent, pauseLateral=True), enabled=False, active=False)
assert not cc_independent_lateral_below_engage(cp, replace(intent, **{key: False}), enabled=False, active=False)
assert not cc_independent_lateral_below_engage(cp, replace(intent, pauseLateral=True), enabled=False, active=False)
for flag in (GMFlags.PEDAL_LONG, GMFlags.NO_CAMERA, 1 << 30):
denied = cp.as_reader().as_builder()
denied.flags |= int(flag)
assert not independent_lateral_below_engage(denied, intent, enabled=False, active=False)
assert not cc_independent_lateral_below_engage(denied, intent, enabled=False, active=False)
@pytest.mark.parametrize('main_action', [0, 9])
@@ -1,74 +0,0 @@
"""Classic stock ACC user roles do not revoke an independent lateral owner."""
from contextlib import ExitStack
import pytest
from openpilot.cereal import log, messaging
from openpilot.common.prefix import OpenpilotPrefix
from opendbc.car.hyundai.values import CAR
from openpilot.starpilot.aol.intent import read_settings
from openpilot.starpilot.aol.tests import test_ioniq_availability as fixture
from openpilot.starpilot.aol.tests.test_classic_scc_profiles import params
from openpilot.starpilot.aol.tests.test_ordinary_source_availability import settings
BASE = 10_000_000_000
def sample(stamp, pedal):
message = messaging.new_message('carState', valid=True, logMonoTime=stamp)
state = fixture.car_state(brake=pedal == 'brake')
state.gasPressed = pedal == 'gas'
state.cruiseState.available = state.cruiseState.enabled = False
message.carState = state
return message
@pytest.mark.parametrize('identity', [CAR.HYUNDAI_SONATA_HYBRID, CAR.HYUNDAI_SONATA, CAR.HYUNDAI_ELANTRA_2021])
@pytest.mark.parametrize('pedal', ['brake', 'gas'])
@pytest.mark.parametrize('fresh', [False, True])
def test_stock_hkg_main_off_pedal_source_join_preserves_only_valid_lateral(identity, pedal, fresh):
case = fixture.IoniqAvailabilityTests(methodName='runTest')
with ExitStack() as cleanup:
cleanup.callback(case.doCleanups)
cleanup.enter_context(OpenpilotPrefix())
sd, controls, drive, card, receive = case._ioniq_availability_callers(False, cp=params(identity, 0x391), cold_start=True)
sd.params.put('PauseAOLOnBrake', 0.0, block=True)
sd.aol_settings = read_settings(sd.params)
for tick in (0, 1, 2):
stamp = BASE + tick * 10_000_000
state = sample(stamp, pedal)
axis, _, command = drive(tick, car_state_message=state, offset_ns=5_000_000 if tick == 2 else 0,
long_allowed=False, **settings('lateral_only', stamp, tick + 1))
assert axis.lateralActive and command.latActive and not command.longActive
receive(stamp + (5_000_000 if tick == 2 else 0), axis=sd.pm.messages['aolAxisState'],
event=sd.pm.messages['onroadEvents'], state=state.carState, preserve_cruise_state=True)
assert card.aol_card_intent.allowed_latch
original = axis.to_dict()
stamp = BASE + (37_000_000 if fresh else 20_000_000)
state = sample(stamp, pedal)
axis, _, command = drive(6, car_state_message=state, timeout=not fresh, offset_ns=5_000_000,
long_allowed=False, **settings('lateral_only', stamp, 4 if fresh else 3))
assert log.OnroadEvent.EventName.controlsMismatch not in sd.events.names, 'STOCK_HKG_HELD_PEDAL_MISMATCH'
assert not sd.aol_authority_lost and not command.longActive
if fresh:
assert axis.lateralActive and command.latActive
assert axis.sourceCarStateMonoTime == stamp
assert axis.validUntilMonoTime != original['validUntilMonoTime']
else:
assert str(axis.faultReason) == 'availabilityPause' and axis.desiredLateral
assert not axis.lateralActive and not command.latActive and axis.sourceCarStateMonoTime == 0
receive(BASE + 65_000_000, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents'], state=state.carState, preserve_cruise_state=True)
assert card.aol_card_intent.allowed_latch, 'STOCK_HKG_HELD_PEDAL_ERASED_LATCH'
if not fresh:
axis, _, command = drive(7, car_state_message=state, timeout=True, long_allowed=False,
**settings('lateral_only', stamp, 3))
assert str(axis.faultReason) == 'availabilityPause' and axis.desiredLateral
assert not axis.lateralActive and not command.latActive and not command.longActive
assert not sd.aol_authority_lost and log.OnroadEvent.EventName.controlsMismatch not in sd.events.names
receive(BASE + 70_000_000, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents'],
state=state.carState, preserve_cruise_state=True)
assert card.aol_card_intent.allowed_latch
stamp = BASE + 80_000_000
state = sample(stamp, pedal)
axis, _, command = drive(8, car_state_message=state, long_allowed=False,
**settings('lateral_only', stamp, 5))
assert axis.lateralActive and command.latActive and not command.longActive
@@ -75,12 +75,12 @@ def cleanup(cp):
def test_actual_cleanup_preserves_request_without_enabling_long():
cp = stock(True)
assert cleanup(cp) == []
assert cleanup(cp) == ['ExperimentalMode']
cp.passive = True
cp.dashcamOnly = True
assert not qualified_ioniq6(cp) and not ioniq6_settings_capable(cp)
assert cleanup(cp) == []
assert cleanup(cp) == ['ExperimentalMode']
cp.carFingerprint = CAR.HYUNDAI_IONIQ_5
assert cleanup(cp) == []
assert cleanup(cp) == ['ExperimentalMode']
cp.openpilotLongitudinalControl = True
assert cleanup(cp) == []
@@ -0,0 +1,73 @@
"""Cruise MAIN assigned to Adopt speed limit is not also the Hyundai physical lateral toggle."""
import pytest
from opendbc.car import structs
from openpilot.starpilot.aol.intent import AolCardIntent, AolSettings, read_settings
from openpilot.starpilot.aol.tests.test_ioniq6_stock_repair import stock
from openpilot.starpilot.car.hyundai.aol import create_intent, qualified_ioniq6
def long_cp():
cp = stock(False)
cp.pcmCruise = False
cp.openpilotLongitudinalControl = True
cp.safetyConfigs[0].safetyParam = 0x8815
assert qualified_ioniq6(cp)
return cp
def press(owner, cs, button, now_ns, *, engaged=False):
cs.buttonEvents = [structs.CarState.ButtonEvent(type=button, pressed=True)]
owner.update(cs, now_ns=now_ns, fault_active=False, standard_enabled=engaged)
cs.buttonEvents = [structs.CarState.ButtonEvent(type=button, pressed=False)]
owner.update(cs, now_ns=now_ns + 1, fault_active=False, standard_enabled=engaged)
def lateral(owner):
return owner.allowed_latch and not owner.pause_lateral
def armed(owner):
cs = structs.CarState(canValid=True, gearShifter='drive')
cs.cruiseState.available = True
owner.update(cs, now_ns=1, fault_active=False)
press(owner, cs, 'lkas', 2)
assert lateral(owner)
return cs
@pytest.mark.parametrize('main_wheel_action', [False, True])
def test_ioniq6_main_assigned_to_adopt_keeps_lkas_armed_lateral(main_wheel_action):
owner = create_intent(long_cp(), AolSettings(True, 0., 0, 0, (0, 0, 0), (0, 0, 0), main_wheel_action=main_wheel_action))
assert owner.independent_cruise_buttons
cs = armed(owner)
press(owner, cs, 'mainCruise', 10, engaged=True)
assert lateral(owner) == main_wheel_action
press(owner, cs, 'lkas', 20, engaged=True)
assert lateral(owner) != main_wheel_action
def test_main_toggle_is_unchanged_without_independent_cruise_buttons():
owner = AolCardIntent(AolSettings(True, 0., 0, 0, (0, 0, 0), (0, 0, 0), main_wheel_action=True), explicit_latch=True)
cs = armed(owner)
press(owner, cs, 'mainCruise', 10, engaged=True)
assert not lateral(owner)
class SavedParams:
def __init__(self, values):
self.values = values
def get(self, key, return_default=False):
return self.values.get(key)
def get_bool(self, key):
return self.values.get(key) == b'1'
def get_param_path(self, key):
return f'/nonexistent/{key}'
@pytest.mark.parametrize('lkas,main,main_wheel', [(b'0', b'10', True), (b'0', b'9', False), (b'10', b'0', False), (b'0', b'8', False)])
def test_only_main_adopt_is_marked_as_a_main_wheel_action(lkas, main, main_wheel):
settings = read_settings(SavedParams({'AlwaysOnLateral': b'1', 'LKASButtonControl': lkas, 'MainCruiseButtonControl': main}))
assert settings.main_wheel_action == main_wheel
@@ -378,7 +378,7 @@ class IoniqAvailabilityTests(unittest.TestCase):
cs.buttonEvents = []
self.assertEqual(card.aol_card_intent.allowed_latch, initially_armed)
def receive(now, *, axis=None, event=None, state=None, preserve_cruise_state=False):
def receive(now, *, axis=None, event=None, state=None):
frames = []
for service in ('pandaStates', 'aolSafetyWire', 'deviceState', 'carControl'):
message = messaging.new_message(service, len(sd.sm[service]) if service == 'pandaStates' else
@@ -391,8 +391,7 @@ class IoniqAvailabilityTests(unittest.TestCase):
observed = state if state is not None else car_state()
if stock_angle and state is None:
observed.vEgoRaw = observed.vEgo
if not preserve_cruise_state:
observed.cruiseState.available = True
observed.cruiseState.available = True
card.CC_prev = card.sm['carControl']
with mock.patch('openpilot.selfdrive.car.card.messaging.drain_sock_raw', return_value=[b'fixture']), \
mock.patch('openpilot.selfdrive.car.card.can_capnp_to_list', return_value=[]), \
@@ -114,7 +114,7 @@ def test_meb_full_engagement_brake_reverse_drive_actual_callers(identity, stock,
# Explicit ambient producers added since the original shared caller fixture.
# Keep production SubMaster health/frequency checks; never ignore a required service.
from openpilot.cereal.services import SERVICE_LIST
ambient_services = ('laneChangeAssistWire',
ambient_services = ('alertDebug', 'lateralManeuverPlan', 'laneChangeAssistWire',
'starpilotLongitudinalPlan', 'slcState', 'gpsLocation')
original_sd_update = sd.sm.update_msgs
ambient_due = {}
@@ -31,7 +31,7 @@ from openpilot.starpilot.aol.tests.stock_lateral_test_helpers import authority_c
@pytest.mark.parametrize('fixed', (False,))
def test_mqb_full_engagement_brake_reverse_drive_actual_callers(gearbox, alpha, offset, fixed, engage_on_set,
stop_and_resume=False, candidate=CAR.VOLKSWAGEN_GOLF_MK7,
brake_held_engagement=False, creep_start=False, early_brake=False):
brake_held_engagement=False):
with ExitStack() as cleanup:
cleanup.enter_context(OpenpilotPrefix())
cleanup.enter_context(mock.patch.dict('os.environ', {'SIMULATION': '0', 'AOL_REPLAY_RUNTIME': '1'}))
@@ -116,7 +116,7 @@ def test_mqb_full_engagement_brake_reverse_drive_actual_callers(gearbox, alpha,
# Explicit ambient producers added since the original shared caller fixture.
# Keep production SubMaster health/frequency checks; never ignore a required service.
from openpilot.cereal.services import SERVICE_LIST
ambient_services = ('laneChangeAssistWire',
ambient_services = ('alertDebug', 'lateralManeuverPlan', 'laneChangeAssistWire',
'starpilotLongitudinalPlan', 'slcState', 'gpsLocation')
original_sd_update = sd.sm.update_msgs
ambient_due = {}
@@ -156,8 +156,6 @@ def test_mqb_full_engagement_brake_reverse_drive_actual_callers(gearbox, alpha,
for tick in range(120):
now = 10_000_000_000 + tick * 10_000_000
native.set_timer(1_000_000 + tick * 10_000)
if creep_start and tick >= 12:
native.set_aol_test_heartbeat(bool(card.aol_card_intent.allowed_latch or request))
if quiet is not None:
quiet.set_timer(1_000_000 + tick * 10_000)
quiet.safety_tick()
@@ -165,12 +163,12 @@ def test_mqb_full_engagement_brake_reverse_drive_actual_callers(gearbox, alpha,
assert not quiet.get_controls_allowed()
assert native.get_current_safety_mode() != quiet.get_current_safety_mode()
brake = (60 if early_brake else 65) <= tick < 75 or (brake_held_engagement and tick < 29)
brake = 65 <= tick < 75 or (brake_held_engagement and tick < 29)
reverse = 85 <= tick < 95
ordinary = engage_on_set and 21 <= tick < 65
overrides = {
'TSK_06': {'TSK_Status': 3 if ordinary and not alpha else 2},
'LH_EPS_03': {'EPS_HCA_Status': 3, 'EPS_Lenkmoment': 100 if early_brake and 52 <= tick <= 61 else 0},
'LH_EPS_03': {'EPS_HCA_Status': 3, 'EPS_Lenkmoment': 0},
'Motor_20': {'MO_Fahrpedalrohwert_01': 0},
'Motor_14': {'MO_Fahrer_bremst': int(brake), 'MO_Kuppl_schalter': 0},
'ESP_05': {'ESP_Fahrer_bremst': int(brake)},
@@ -178,10 +176,9 @@ def test_mqb_full_engagement_brake_reverse_drive_actual_callers(gearbox, alpha,
'Gateway_72': {'BCM1_Rueckfahrlicht_Schalter': int(reverse)},
'Motor_EV_01': {'MO_Waehlpos': 3 if reverse else 5},
'ESP_19': dict.fromkeys(('ESP_VL_Radgeschw_02', 'ESP_VR_Radgeschw_02', 'ESP_HL_Radgeschw_02', 'ESP_HR_Radgeschw_02'),
(72 if tick < 12 else 0 if tick < 20 else 1 if tick < 24 else 2 if tick < 46 else 72) if creep_start else
0 if (stop_and_resume and 45 <= tick < 56) or (brake_held_engagement and tick < 30) else 72),
'Airbag_02': {'AB_Gurtschloss_FA': 0 if 105 <= tick < 111 else 3},
'GRA_ACC_01': {'GRA_Hauptschalter': 1, 'GRA_Tip_Setzen': int(engage_on_set and (tick in (20, 50) if creep_start else tick == 20))},
'GRA_ACC_01': {'GRA_Hauptschalter': 1, 'GRA_Tip_Setzen': int(engage_on_set and tick == 20)},
'ACC_02': {'ACC_Gesetzte_Zeitluecke': 1},
}
incoming = []
@@ -199,9 +196,6 @@ def test_mqb_full_engagement_brake_reverse_drive_actual_callers(gearbox, alpha,
native.safety_tick()
native.aol_set_host_request(request)
permission = native.aol_get_permission_mask()
if early_brake and tick == 60:
incoming = [(address, data, bus) for address, data, bus in incoming if address not in (0x106, 0x3BE)]
assert not native.get_controls_allowed() and not permission & 2
original_message = messaging.new_message
def clocked_card_message(*args, original_message=original_message, now=now, **kwargs):
message = original_message(*args, **kwargs)
@@ -224,11 +218,6 @@ def test_mqb_full_engagement_brake_reverse_drive_actual_callers(gearbox, alpha,
allowed=bool(permission & 1), long_allowed=bool(permission & 2),
native_request_lat=bool(request & 1), native_request_long=bool(request & 2),
panda_controls_allowed=native.get_controls_allowed())
if early_brake and tick == 60:
assert not state.brakePressed and state.steeringPressed
assert log.OnroadEvent.EventName.controlsMismatch not in sd.events.names, 'VW_EARLY_BRAKE_STEERING_MISMATCH'
assert not command.longActive
assert card.aol_card_intent.allowed_latch
if brake_held_engagement and 22 <= tick < 29:
assert state.standstill and state.brakePressed
assert selfdrive.state == log.SelfdriveState.OpenpilotState.preEnabled
@@ -273,9 +262,6 @@ def test_mqb_full_engagement_brake_reverse_drive_actual_callers(gearbox, alpha,
emitted.extend(sent)
lat_commands.append(command.latActive)
long_commands.append(command.longActive)
if creep_start:
assert any(lat_commands[35:45]), 'AOL must request and obtain native steering below the manual long-enable speed'
assert not any(long_commands[:46]), 'Creep AOL must not enable longitudinal below its independent minimum'
assert any(lat_commands[35:60])
assert any(lat_commands[76:84]) # braking released long, not qualified lateral intent
assert not any(lat_commands[86:95])
@@ -284,7 +270,7 @@ def test_mqb_full_engagement_brake_reverse_drive_actual_callers(gearbox, alpha,
assert not any(long_commands[66:]) # no automatic longitudinal restoration
assert emitted
if alpha and engage_on_set:
assert any(long_commands[50:65] if creep_start else long_commands[35:60])
assert any(long_commands[35:60])
else:
assert not any(long_commands)
@@ -301,50 +287,3 @@ def test_mqb_brake_held_engagement_waits_for_release_actual_callers(gearbox):
test_mqb_full_engagement_brake_reverse_drive_actual_callers(
gearbox, True, 0, False, True, brake_held_engagement=True,
candidate=CAR.SEAT_ATECA_MK1 if gearbox == 'automatic' else CAR.VOLKSWAGEN_GOLF_MK7)
@pytest.mark.parametrize('offset,fixed', [(0, False), (0, True), (4, False), (4, True)])
def test_mqb_manual_alpha_stopped_creep_preserves_native_arm_and_fresh_speed_engagement(offset, fixed):
test_mqb_full_engagement_brake_reverse_drive_actual_callers(
'manual', True, offset, fixed, True, creep_start=True)
@pytest.mark.parametrize('veto', ('none', 'can', 'timeout', 'temporary', 'permanent', 'main', 'reverse', 'latch', 'pause', 'automatic', 'stock'))
def test_manual_cruise_minimum_exception_retains_physical_and_intent_vetoes(veto):
from types import SimpleNamespace
from openpilot.starpilot.car.volkswagen.aol import below_engage_lateral_allowed
from openpilot.starpilot.aol.vehicle import policy_for
fp = {bus: {} for bus in range(8)}
fp[1][0xFD] = 8
cp = CarInterface.get_params(CAR.VOLKSWAGEN_GOLF_MK7, fp, [], True, False, False)
policy = policy_for(cp)
cp.safetyConfigs[-1].safetyParam |= policy.safety_param_addition
cp.alternativeExperience |= policy.alternative_experience_addition
state = structs.CarState(canValid=True, gearShifter=structs.CarState.GearShifter.drive)
state.cruiseState.available = True
intent = SimpleNamespace(allowedLatch=True, pauseLateral=False)
if veto in ('can', 'timeout', 'temporary', 'permanent'):
setattr(state, {'can': 'canValid', 'timeout': 'canTimeout', 'temporary': 'steerFaultTemporary',
'permanent': 'steerFaultPermanent'}[veto], veto != 'can')
elif veto == 'main':
state.cruiseState.available = False
elif veto == 'reverse':
state.gearShifter = structs.CarState.GearShifter.reverse
elif veto == 'latch':
intent.allowedLatch = False
elif veto == 'pause':
intent.pauseLateral = True
elif veto == 'automatic':
cp.transmissionType = structs.CarParams.TransmissionType.automatic
cp.safetyConfigs[-1].safetyParam = 9
elif veto == 'stock':
cp.openpilotLongitudinalControl, cp.pcmCruise = False, True
cp.safetyConfigs[-1].safetyParam = 24
assert below_engage_lateral_allowed(cp, state, intent) is (veto == 'none')
@pytest.mark.parametrize('gearbox', ('automatic', 'manual', 'direct'))
def test_native_brake_precedes_parsed_pedal_during_driver_steering(gearbox):
test_mqb_full_engagement_brake_reverse_drive_actual_callers(
gearbox, True, 0, False, True, early_brake=True,
candidate=CAR.SEAT_ATECA_MK1 if gearbox == 'automatic' else CAR.VOLKSWAGEN_GOLF_MK7)
@@ -1,214 +0,0 @@
"""Mixed Palisade Pause/Resume retains only existing, current lateral authority."""
from dataclasses import replace
from types import SimpleNamespace
from typing import cast
import pytest
from opendbc.car import structs
from openpilot.cereal import log
from openpilot.selfdrive.selfdrived.events import Events
from openpilot.selfdrive.selfdrived.selfdrived import SelfdriveD
from openpilot.starpilot.aol.intent import AolSettings, AOL_TOGGLE
from openpilot.starpilot.aol.runtime import AxisDecision
from openpilot.starpilot.aol.wire import IntentState, SafetyState, PandaSlot
from openpilot.starpilot.aol.tests.test_classic_brake_withdrawal import profile
from openpilot.starpilot.car.hyundai.aol import create_intent
class Messages(dict):
ready = True
def all_checks(self, services=None):
return self.ready
@pytest.mark.parametrize('identity', ('palisade_hdai', 'palisade_hdaii'))
@pytest.mark.parametrize('case', ['retain', 'prior_off', 'settings_off', 'configured_cancel', 'intent_off', 'stale_source',
'native_denied', 'native_expired', 'wrong_session', 'wrong_inventory', 'hard_event',
'model', 'dm', 'reverse', 'steer_fault'])
def test_actual_selfdrived_cancel_keeps_only_current_owned_lateral(identity, case):
now = 4_000_000_000
cp = profile(identity)
config = cp.safetyConfigs[0]
model, word = int(config.safetyModel.raw), int(config.safetyParam)
intent = IntentState('card', 1, now, now, now + 200_000_000, True, False, False, True, True)
slot = PandaSlot(0, 'panda', model, word, cp.alternativeExperience)
inventory = (slot,)
native = SafetyState(1, True, now, now + 200_000_000, model, word, True, False, True, False,
'panda', 'axis', now, inventory)
cs = structs.CarState(canValid=True, gearShifter='drive', vEgo=10.)
cs.cruiseState.available = True
cs.buttonEvents = [structs.CarState.ButtonEvent(type='cancel', pressed=True)]
sm = Messages(driverMonitoringState=SimpleNamespace(alertLevel=0, lockout=False, alwaysOnLockout=False),
extrinsicsCalibration=SimpleNamespace(calStatus=log.ExtrinsicsCalibration.Status.calibrated))
sd = SimpleNamespace(CP=cp, active=False, enabled=False, initialized=True, sm=sm,
aol_settings=AolSettings(True, 0., AOL_TOGGLE, 0, (0,0,0), (0,0,0)),
aol_axis_decision=AxisDecision(lateral_active=True, native_acknowledged=True),
aol_car_state_log_ns=now, aol_session_id='axis', aol_dm_lateral_inhibit=False,
nostalgia_paddle_cancel=False, events=Events())
sd.events.add(log.OnroadEvent.EventName.buttonCancel)
if case == 'prior_off':
sd.aol_axis_decision = AxisDecision()
elif case == 'settings_off':
sd.aol_settings = replace(sd.aol_settings, enabled=False)
elif case == 'configured_cancel':
sd.aol_settings = replace(sd.aol_settings, cancel_actions=(3,0,0))
elif case == 'intent_off':
intent = replace(intent, allowedLatch=False)
elif case == 'stale_source':
sd.aol_car_state_log_ns = now - 30_000_001
intent = replace(intent, carStateLogMonoTime=sd.aol_car_state_log_ns)
elif case == 'native_denied':
native = replace(native, lateralAllowed=False)
elif case == 'native_expired':
native = replace(native, validUntilMonoTime=now-1)
elif case == 'wrong_session':
native = replace(native, axisSessionId='other')
elif case == 'wrong_inventory':
native = replace(native, pandaInventory=(*inventory[:-1], replace(slot, safetyParam=word ^ 0x80)))
elif case == 'hard_event':
sd.events.add(log.OnroadEvent.EventName.controlsMismatch)
elif case == 'model':
sm.ready = False
elif case == 'dm':
sm['driverMonitoringState'].lockout = True
elif case == 'reverse':
cs.gearShifter = 'reverse'
elif case == 'steer_fault':
cs.steerFaultPermanent = True
result = SelfdriveD._decide_aol_axes(cast(SelfdriveD, sd), cs, intent, native, now_ns=now)
assert result.lateral_active == (case == 'retain')
assert not result.longitudinal_active
assert log.OnroadEvent.EventName.buttonCancel in sd.events.names
@pytest.mark.parametrize('identity', ('palisade_hdai', 'palisade_hdaii'))
@pytest.mark.parametrize('duration', (61_047_724, 221_004_624))
@pytest.mark.parametrize('cancel_actions', ((0, 0, 0), (3, 0, 0), (0, 3, 0), (0, 0, 3)))
def test_host_cancel_hold_keeps_only_unassigned_preexisting_latch(identity, duration, cancel_actions):
cp = profile(identity)
settings = AolSettings(True, 0., AOL_TOGGLE, 0, (0, 0, 0), cancel_actions)
owner = create_intent(cp, settings)
cs = structs.CarState(canValid=True, gearShifter='drive')
cs.cruiseState.available = True
owner.update(cs, now_ns=1_000_000_000)
cs.buttonEvents = [structs.CarState.ButtonEvent(type='lkas', pressed=True)]
owner.update(cs, now_ns=1_010_000_000)
assert owner.allowed_latch
edge, arm = owner._last_latch_edge_ns, owner.physical_arm_ns
cs.buttonEvents = [structs.CarState.ButtonEvent(type='lkas', pressed=False)]
owner.update(cs, now_ns=1_020_000_000)
for stamp, events in ((1_030_000_000, [('cancel', True)]),
(1_040_000_000, []), (1_030_000_000 + duration, [('cancel', False)])):
cs.buttonEvents = [structs.CarState.ButtonEvent(type=kind, pressed=pressed) for kind, pressed in events]
owner.update(cs, now_ns=stamp)
assert owner.allowed_latch == (not any(cancel_actions))
assert (owner._last_latch_edge_ns, owner.physical_arm_ns) == (edge, arm)
assert not owner.independent_cruise_buttons # MAIN/LKAS semantics are not changed.
@pytest.mark.parametrize('identity', ('palisade_hdai', 'palisade_hdaii'))
@pytest.mark.parametrize('simultaneous', (None, 'lkas', 'mainCruise'))
def test_cold_cancel_never_arms_or_renews_gesture(identity, simultaneous):
owner = create_intent(profile(identity), AolSettings(True, 0., AOL_TOGGLE, 0, (0,0,0), (0,0,0)))
cs = structs.CarState(canValid=True, gearShifter='drive')
cs.cruiseState.available = True
owner.update(cs, now_ns=1_000_000_000)
buttons = [structs.CarState.ButtonEvent(type='cancel', pressed=True)]
if simultaneous:
buttons.append(structs.CarState.ButtonEvent(type=simultaneous, pressed=True))
cs.buttonEvents = buttons
owner.update(cs, now_ns=1_010_000_000)
assert not owner.allowed_latch
assert owner._last_latch_edge_ns == owner.physical_arm_ns == 0
@pytest.mark.parametrize('identity', ('palisade_hdai', 'palisade_hdaii'))
def test_card_retains_original_cancel_receipt_until_lateral_only_ack(identity):
"""Real Card caller, synthetic transport; parser receipt is tested separately."""
from unittest.mock import Mock, patch
from opendbc.car.hyundai.blended_longitudinal import BlendedStartup, candidate_from_stock
from opendbc.car.hyundai.tests.test_palisade_2023 import params as mixed_params
from openpilot.common.params import Params
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.starpilot.aol.tests.test_ioniq_availability import IoniqAvailabilityTests, car_state
case = IoniqAvailabilityTests(methodName='runTest')
with OpenpilotPrefix():
try:
stock = mixed_params('hdaii' if identity == 'palisade_hdaii' else 'hdai')
cp = candidate_from_stock(stock, alpha_requested=True, native_qualified=True)
startup = BlendedStartup(stock, cp.as_reader().as_builder(), (Mock(), Mock()))
startup.prepare(admission=lambda: True)
case.addCleanup(startup.close)
Params().put('LKASButtonControl', AOL_TOGGLE, block=True)
sd, _, drive, card, receive = case._ioniq_availability_callers(True, cp=cp, startup_owner=startup)
sd.aol_settings = card.aol_settings
source = 10_020_000_000
cs = car_state()
cs.cruiseState.available = True
cs.buttonEvents = [structs.CarState.ButtonEvent(type='cancel', pressed=True)]
# Native withdrawal arrives before the next SelfdriveD publication.
drive(2, native_request_lat=True, native_request_long=True, long_allowed=False,
panda_controls_allowed=False)
old_axis = sd.pm.messages['aolAxisState'].as_builder()
old_axis.logMonoTime = old_axis.aolAxisState.observedMonoTime = source - 1_000_000
old_axis.aolAxisState.sourceCarStateMonoTime = source - 1_000_000
old_axis.aolAxisState.validUntilMonoTime = source + 29_000_000
old_axis.aolAxisState.mode = 'combined'
old_axis.aolAxisState.desiredLongitudinal = old_axis.aolAxisState.longitudinalActive = True
with patch.object(card, 'aol_cancel_source', return_value=source):
assert receive(source, axis=old_axis.as_reader(), state=cs)
assert card.aol_transport_feedback.longitudinal_cancel_ns == source
# No second physical press, no newer axis yet, now the cancellation event.
event = log.Event.new_message(logMonoTime=source)
event.init('onroadEvents', 1)
event.onroadEvents[0].name = 'buttonCancel'
event.onroadEvents[0].userDisable = True
event.onroadEvents[0].noEntry = True
cs.buttonEvents = []
with patch.object(card, 'aol_cancel_source', return_value=0):
assert receive(source + 10_000_000, event=event.as_reader(), state=cs)
assert card.aol_transport_feedback.longitudinal_cancel_ns == source
assert not card.aol_transport_feedback.failed
# SelfdriveD receives the same physical press; its actual LAT-only output
# and the native LAT-only request close the bounded withdrawal handshake.
cancel = car_state()
cancel.buttonEvents = [structs.CarState.ButtonEvent(type='cancel', pressed=True)]
axis, state, command = drive(4, observed_state=cancel, native_request_lat=True,
native_request_long=False, long_allowed=False, panda_controls_allowed=False)
assert not state.enabled and axis.lateralActive and command.latActive
assert not axis.longitudinalActive and not command.longActive
with patch.object(card, 'aol_cancel_source', return_value=0):
assert receive(source + 20_000_000, axis=sd.pm.messages['aolAxisState'],
event=sd.pm.messages['onroadEvents'], state=cs)
assert card.aol_card_intent.allowed_latch
assert card.aol_transport_feedback.longitudinal_cancel_ns == 0
finally:
case.doCleanups()
@pytest.mark.parametrize('identity', ('palisade_hdai', 'palisade_hdaii'))
@pytest.mark.parametrize('fault', ('none', 'wrong_bus', 'stale', 'unmarked', 'stock'))
def test_cancel_source_is_bound_to_real_mixed_parser_receipt(identity, fault):
from opendbc.can import CANPacker
from opendbc.car import Bus
from opendbc.car.hyundai.interface import CarInterface
from openpilot.selfdrive.car.card import Car
cp = profile(identity)
if fault == 'unmarked':
cp.alternativeExperience = 0
elif fault == 'stock':
cp.openpilotLongitudinalControl, cp.pcmCruise = False, True
cp.safetyConfigs[0].safetyParam &= ~4
ci = CarInterface(cp)
parser = ci.can_parsers[Bus.pt]
packer = CANPacker(parser.dbc.name)
for tick in range(15):
now = 2_000_000_000 + tick * 20_000_000
packet = packer.make_can_msg('CLU11', 2 if fault == 'wrong_bus' else parser.bus,
{'CF_Clu_CruiseSwState': 4 if tick == 14 else 0, 'CF_Clu_AliveCnt1': tick})
cs = ci.update([(now, [packet])])
if fault == 'wrong_bus':
cs.buttonEvents = [structs.CarState.ButtonEvent(type='cancel', pressed=True)]
received = now - 30_000_001 if fault == 'stale' else now
caller = SimpleNamespace(CI=ci, _aol_can_observation=(cs, received, now, now))
assert Car.aol_cancel_source(cast(Car, caller), cs, now) == (now if fault == 'none' else 0)
@@ -63,18 +63,11 @@ def test_native_eps_denial_retains_intent_without_renewing_permission(gas, withd
'door', 'sensors', 'disengage', 'reverse', 'acc_fault', 'native_invalid', 'native_fault', 'native_heartbeat',
'wrong_session', 'wrong_producer', 'wrong_epoch', 'expired', 'warning_stale', 'native_stale',
'native_missing', 'request_off', 'long_request', 'hard_event', 'other_user'))
@pytest.mark.parametrize('brake', (False, True))
def test_eps_retention_does_not_mask_independent_vetoes(fault, brake):
def test_eps_retention_does_not_mask_independent_vetoes(fault):
cp, state, feedback = baseline()
state.steerFaultTemporary = True
state.brakePressed = brake
now = NOW + 10_000_000
sm, native = sample(cp, now, now, 2, allowed=False)
if brake:
event = sm.messages['onroadEvents'].as_builder()
event.onroadEvents = [{'name': 'pcmDisable', 'userDisable': True},
{'name': 'pedalPressed', 'userDisable': True}]
sm.messages['onroadEvents'] = event.as_reader()
physical = {'no_eps': ('steerFaultTemporary', False), 'permanent_eps': ('steerFaultPermanent', True),
'invalid_can': ('canValid', False), 'timeout': ('canTimeout', True), 'door': ('doorOpen', True),
'sensors': ('vehicleSensorsInvalid', True), 'disengage': ('steeringDisengage', True), 'acc_fault': ('accFaulted', True)}
@@ -209,28 +202,3 @@ def test_continuous_warning_keeps_intent_but_gap_or_stale_source_cannot_renew_it
if stale_source:
change(sm, 'aolAxisState', lambda axis: setattr(axis, 'sourceCarStateMonoTime', NOW))
assert feedback.observe(sm, cp, state, now, latched=True, intent_producer_session=PRODUCER)
@pytest.mark.parametrize('axis_first', (False, True))
def test_brake_warning_event_change_preserves_existing_eps_intent(axis_first):
cp, state, feedback = baseline()
state.steerFaultTemporary = state.brakePressed = True
before = leases(feedback)
warning = NOW + 10_000_000
for sequence in range(2, 10):
now = NOW + (sequence - 1) * 10_000_000
event_stamp = warning if sequence < 6 else NOW + 50_000_000
axis_stamp = warning if sequence < 6 or sequence == 6 and not axis_first else NOW + 50_000_000
sm, native = sample(cp, now, now, sequence, active=False, requested=sequence == 2, allowed=False, event_stamp=event_stamp)
change(sm, 'aolAxisState', lambda axis, stamp=axis_stamp: setattr(axis, 'faultEventMonoTime', stamp))
event = sm.messages['onroadEvents'].as_builder()
event.onroadEvents = [{'name': 'pcmDisable', 'userDisable': True},
{'name': 'pedalPressed', 'userDisable': True},
{'name': 'steerTempUnavailableSilent', 'warning': True}]
if sequence == 6 and axis_first:
event.logMonoTime = warning
sm.logMonoTime['onroadEvents'] = warning
sm.messages['onroadEvents'] = event.as_reader()
assert not feedback.observe(sm, cp, state, now, latched=True, intent_producer_session=PRODUCER)
assert not feedback.failed and leases(feedback) == before
assert not native.lateralAllowed and not native.longitudinalAllowed
@@ -122,19 +122,6 @@ class TestHighlanderAol(unittest.TestCase):
self._assert_card_full_axis_transport(car, True, prepared_cp=cp,
filter_disabled=disabled, late_disable=late)
def test_continental_radar_metadata_keeps_exact_stock_owner(self):
for car in (CAR.TOYOTA_CHR_TSS2, CAR.TOYOTA_RAV4_TSS2_2022):
for alpha in (False, True):
with self.subTest(car=car, alpha=alpha):
cp = CarInterface.get_params(car, gen_empty_fingerprint(), [], alpha, False, False)
cp.flags |= int(ToyotaFlags.ARS510_RADAR)
expected = car == CAR.TOYOTA_RAV4_TSS2_2022 and not alpha
self.assertEqual(qualified(cp), expected)
cp.alternativeExperience = 32
self.assertEqual(qualified(cp, marked_only=True), expected)
cp.flags |= int(ToyotaFlags.ARS510_FUSED)
self.assertFalse(qualified(cp))
def test_radar_torque_factory_stock_and_alpha_transport(self):
from opendbc.car.toyota.carcontroller import CarController
from opendbc.car.toyota.carstate import CarState
@@ -9,7 +9,7 @@ from openpilot.cereal import log
from openpilot.selfdrive.car.car_events import CarEvents
from openpilot.starpilot.aol.tests.test_gm_cc_lateral_minimum import setup
from openpilot.starpilot.aol.tests.test_gm_parked_startup import _exercise_parked_main
from openpilot.starpilot.car.gm.aol import independent_lateral_below_engage
from openpilot.starpilot.car.gm.aol import cc_independent_lateral_below_engage
def params(f1):
@@ -24,9 +24,9 @@ def test_volt_cc_steering_minimum_keeps_cruise_minimum(f1):
_, intent = setup()
assert cp.minSteerSpeed == pytest.approx(7 * CV.MPH_TO_MS)
assert cp.minEnableSpeed == pytest.approx(24 * CV.MPH_TO_MS)
assert independent_lateral_below_engage(cp, intent, enabled=False, active=False)
assert not independent_lateral_below_engage(cp, replace(intent, allowedLatch=False), enabled=False, active=False)
assert not independent_lateral_below_engage(cp, intent, enabled=True, active=True)
assert cc_independent_lateral_below_engage(cp, intent, enabled=False, active=False)
assert not cc_independent_lateral_below_engage(cp, replace(intent, allowedLatch=False), enabled=False, active=False)
assert not cc_independent_lateral_below_engage(cp, intent, enabled=True, active=True)
@pytest.mark.parametrize('f1', [False, True])
+8 -17
View File
@@ -114,7 +114,7 @@ def stock_lateral_user_event(cp):
def stock_lateral_off(cp, cs, native, *, allow_independent_cancel=False, allow_independent_brake=False,
allow_independent_gas=False, allow_independent_main=False):
allow_independent_gas=False):
from openpilot.starpilot.nostalgia import physical_cancel
from opendbc.car.hyundai.canfd_angle_aol import qualified as angle_qualified
@@ -127,20 +127,18 @@ def stock_lateral_off(cp, cs, native, *, allow_independent_cancel=False, allow_i
stock_gas = bool(cp.brand == 'gm' and qualified_gm(cp) and cp.pcmCruise and not cp.openpilotLongitudinalControl or
cp.brand == 'subaru' and subaru_qualified(cp, marked_only=True))
from opendbc.car.hyundai.classic_scc_aol import qualified as classic_stock_qualified
classic_stock = classic_stock_qualified(cp, marked_only=True)
independent_angle = angle_qualified(cp, marked_only=True)
independent_brake = independent_angle or (allow_independent_brake and (
toyota_qualified(cp, marked_only=True) and not cs.cruiseState.enabled or
cp.brand == 'hyundai' and len(cp.safetyConfigs) == 1 and (stock_canfd_qualified(cp, marked_only=True) or classic_stock)))
return bool(cp.pcmCruise and (cs.cruiseState.available or allow_independent_main and classic_stock) and
cp.brand == 'hyundai' and len(cp.safetyConfigs) == 1 and stock_canfd_qualified(cp, marked_only=True)))
return bool(cp.pcmCruise and cs.cruiseState.available and
(not cs.cruiseState.enabled or independent_brake and allow_independent_brake and cs.brakePressed) and
cs.canValid and not cs.canTimeout and not cs.steerFaultPermanent and not cs.steerFaultTemporary and
not cs.accFaulted and not cs.doorOpen and not cs.seatbeltUnlatched and
(not cs.brakePressed or independent_brake and allow_independent_brake) and
not cs.regenBraking and (not cs.gasPressed or independent_angle or
allow_independent_gas and (stock_gas or toyota_qualified(cp, marked_only=True) or
cp.brand == 'hyundai' and (stock_canfd_qualified(cp, marked_only=True) or classic_stock))) and
cp.brand == 'hyundai' and stock_canfd_qualified(cp, marked_only=True))) and
(not physical_cancel(cs, cp) or independent_angle and allow_independent_cancel) and
native is not None and native.requestedLateral and native.lateralAllowed and not native.requestedLongitudinal)
@@ -364,7 +362,6 @@ class TransportPauseFeedback:
early_long = gm or cp.brand == 'hyundai' and early_native_long_withdrawal_supported(cp) or ev9 or ford_withdrawal or ford_long
from openpilot.starpilot.car.gm.aol import f1_conventional_lateral_owner, gm_physical_pedal_withdrawal
gm_pedal = gm_physical_pedal_withdrawal(cp, cs) and not f1_conventional_lateral_owner(cp)
classic_brake = classic_brake_withdrawal(cp, cs)
event_ns = int(sm.logMonoTime['onroadEvents'])
main_off_recovery = bool(0 < event_ns <= self.ccnc_main_off_source_ns <= now_ns and
now_ns - self.ccnc_main_off_source_ns <= AXIS_MAX_AGE_NS)
@@ -389,7 +386,7 @@ class TransportPauseFeedback:
now_ns - int(sm.logMonoTime['onroadEvents']) > 1_500_000_000 or
any(event.immediateDisable or event.softDisable or
event.userDisable and not (ev9_main_off and event.name == log.OnroadEvent.EventName.wrongCarMode or
(gm_pedal or classic_brake) and event.name == log.OnroadEvent.EventName.pedalPressed or
(gm_pedal or classic_brake_withdrawal(cp, cs)) and event.name == log.OnroadEvent.EventName.pedalPressed or
ford_withdrawal and event.name in (log.OnroadEvent.EventName.pedalPressed, log.OnroadEvent.EventName.pcmDisable,
log.OnroadEvent.EventName.buttonCancel))
for event in sm['onroadEvents'])):
@@ -397,13 +394,13 @@ class TransportPauseFeedback:
native = independent_lateral_health(sm, cp, cs, self.session, now_ns)
# Separate health may report brake withdrawal before the matching axis status.
# Retain only the existing LAT intent; LONG still follows its physical pedal.
brake_health = bool((ford_withdrawal or classic_brake) and cs.brakePressed and not cs.gasPressed and
ford_brake_health = bool(ford_withdrawal and cs.brakePressed and not cs.gasPressed and
native is not None and native.sourcePandaStatesMonoTime > 0 and
native.sourcePandaStatesMonoTime < int(sm.logMonoTime['pandaStates']) and
all(not slot.controlsAllowed for slot, config in zip(sm['pandaStates'], cp.safetyConfigs, strict=True)
if str(config.safetyModel) not in ('silent', 'noOutput')))
if (native is None or not native.pandaInventory or not native.requestedLongitudinal or
native.longitudinalAllowed and not brake_health or
native.longitudinalAllowed and not ford_brake_health or
native.observedMonoTime < self.lateral_native_ns or
(native.pandaSerial, tuple(slot.hardwareSerial for slot in native.pandaInventory)) != self.lateral_inventory):
return None
@@ -575,11 +572,6 @@ class TransportPauseFeedback:
not axis.desiredLongitudinal and not axis.longitudinalActive and
all(event.name in (stock_user, log.OnroadEvent.EventName.pedalPressed) and
not event.softDisable and not event.immediateDisable for event in users))
from openpilot.starpilot.car.subaru.aol import temporary_eps_brake_recovery as subaru_eps_brake_recovery
stock_temporary_user |= bool(eps_recovery and self.lateral_only_baseline and subaru_eps_brake_recovery(cp, cs) and users and
not axis.desiredLongitudinal and not axis.longitudinalActive and
all(event.name in (stock_user, log.OnroadEvent.EventName.pedalPressed) and
not event.softDisable and not event.immediateDisable for event in users))
from openpilot.starpilot.car.gm.aol import f1_conventional_lateral_owner, gm_physical_pedal_withdrawal
# Receipt-bound GM owns F1 physical withdrawal.
pedal = bool(not f1_conventional_lateral_owner(cp) and
@@ -813,12 +805,11 @@ class TransportPauseFeedback:
def _longitudinal_cancel_current(self, sm, cp, cs, axis, now_ns, *, latched, cancel_source_ns, withdrawal_required=True) -> SafetyState | None:
from opendbc.car.hyundai.canfd_stock_aol import qualified_long as qualified
from openpilot.starpilot.car.hyundai.aol import qualified_ccnc_long_aol as ev9_qualified
from opendbc.car.hyundai.blended_stock_aol import qualified_alpha as mixed_qualified
from opendbc.car.structs import car
if (not latched or self.failed or self.paused or self.pending_event or
not self.session or str(axis.sessionId) != self.session or
not (qualified(cp, marked_only=True) or ev9_qualified(cp, marked_only=True) or mixed_qualified(cp, marked_only=True)) or
not (qualified(cp, marked_only=True) or ev9_qualified(cp, marked_only=True)) or
not self.lateral_baseline_ns or not 0 <= now_ns - self.lateral_baseline_ns <= AXIS_MAX_AGE_NS or
str(axis.faultReason) != 'none' or not axis.nativeAcknowledged or
not axis.desiredLateral or not axis.lateralActive or cs.accFaulted or
Binary file not shown.
+5 -17
View File
@@ -210,23 +210,11 @@ class FordBluePilotController(BluePilotLateral):
if self.starpilot_selected:
angle = cast("StarPilotFordController", self.angle)
angle.native_curvature_bounds = None
if self.measured_seen and CS.out.vEgoRaw > 10.:
if self.native_pinion_model is not None:
# Pinion native limits use 150 CAN curvature units, without learned geometry.
error = 150 * .00002
low = -max(self.measured_history) * .00002 - error
high = -min(self.measured_history) * .00002 + error
else:
# An old yaw extreme can expire before the next steering packet consumes its metadata.
history = list(self.measured_history)
sample_step = max((abs(b - a) for a, b in zip(history[:-1], history[1:], strict=True)), default=0)
# Native RX may advance one sample before the host receives the same batch.
error = max(0., CarControllerParams.CURVATURE_ERROR - (sample_step + 1) * .00002)
current_native = -self.measured_history[-1] * .00002
low = current_native - error
high = current_native + error
low = max(low, measured - CarControllerParams.CURVATURE_ERROR)
high = min(high, measured + CarControllerParams.CURVATURE_ERROR)
if self.native_pinion_model is not None and self.measured_seen and CS.out.vEgoRaw > 10.:
# Pinion native limits use 150 CAN curvature units, without learned geometry.
error = 150 * .00002
low = max(-max(self.measured_history) * .00002 - error, measured - CarControllerParams.CURVATURE_ERROR)
high = min(-min(self.measured_history) * .00002 + error, measured + CarControllerParams.CURVATURE_ERROR)
# Tighten the host envelope only where both measurements agree on a command.
if low <= high:
angle.native_curvature_bounds = (low, high)
@@ -57,7 +57,6 @@ _STALL_HOLD_S = 0.5 # accumulated clip-binding time before a pulse fire
_STALL_BLIP_FRAMES = 6 # mode-0 pulse length (6 frames @ 20 Hz = 300 ms)
_STALL_COOLDOWN_S = 2.0 # re-arm delay after a pulse (release ramp + PSCM response time)
_STALL_MAX_BLIPS = 3 # give up on a stuck episode; devLim telemetry keeps recording the stall
_STALL_PROGRESS_EPS = 0.00002 # one native CAN curvature unit
# The pulse releases steering for 300 ms; never fire it in a curve.
_BLIP_MAX_PATH_ANGLE = 0.10 # rad
@@ -139,7 +138,6 @@ class StarPilotFordController:
self.stall_blip_frames_left = 0 # remaining pulse frames; > 0 -> mode 0 on the wire
self.stall_blip_cooldown_s = 0.0 # re-arm delay after a pulse
self.stall_blip_count = 0 # pulses fired this stall episode
self.stall_measured_last: float | None = None
self.angle_stall_blip_active = False
def update_angle_params(self, params):
@@ -205,7 +203,6 @@ class StarPilotFordController:
self.stall_blip_frames_left = 0
self.stall_blip_cooldown_s = 0.0
self.stall_blip_count = 0
self.stall_measured_last = None
self.angle_stall_blip_active = False
self.shared.precision_type = 1
return LateralResult(
@@ -244,7 +241,6 @@ class StarPilotFordController:
self.stall_blip_frames_left = 0
self.stall_blip_cooldown_s = 0.0
self.stall_blip_count = 0
self.stall_measured_last = None
self.angle_stall_blip_active = False
self.shared.precision_type = 1
return LateralResult(
@@ -257,17 +253,6 @@ class StarPilotFordController:
lateralUncertainty=0.0,
)
progress = 0.0 if self.stall_measured_last is None else measured - self.stall_measured_last
steering_responding = (abs(progress) > _STALL_PROGRESS_EPS and
progress * (float(actuators.curvature) - measured) > 0.0)
self.stall_measured_last = measured
if steering_responding:
# A clipped tracking correction is not a stall while the car is responding.
self.stall_blip_hold_s = 0.0
if self.stall_blip_frames_left > 0 and not self.angle_stall_blip_active:
self.stall_blip_frames_left = 0
self.stall_blip_count = max(0, self.stall_blip_count - 1)
# Stall-blip pulse in progress: hold lateral inactive (mode 0, all-zero signals -- the same
# wire pattern as the human-turn override, no ford.h involvement) for _STALL_BLIP_FRAMES so the
# PSCM drops its post-override attenuation, then release; path_angle ramps back in from zero
@@ -454,8 +439,7 @@ class StarPilotFordController:
and abs(_stall_gap) > _STALL_GAP_MIN
and abs(desired_curvature) > abs(current_curvature))
if _stalled:
if (self.shared.bp_curvature_deviation_limited and self.stall_blip_cooldown_s <= 0.0 and
not steering_responding):
if self.shared.bp_curvature_deviation_limited and self.stall_blip_cooldown_s <= 0.0:
self.stall_blip_hold_s += _STEER_DT
if (self.stall_blip_hold_s >= _STALL_HOLD_S and self.stall_blip_count < _STALL_MAX_BLIPS
and abs(self.shared.path_angle_last) < _BLIP_MAX_PATH_ANGLE):
+4 -3
View File
@@ -28,10 +28,11 @@ def native_accepts_cp(cp, model: int, param: int) -> bool:
native_profile_supported(model, param))
def independent_lateral_below_engage(cp, intent, *, enabled, active):
"""An armed independent GM LAT request uses its steering speed gate."""
def cc_independent_lateral_below_engage(cp, intent, *, enabled, active):
"""An armed conventional-cruise LAT request uses its steering speed gate."""
from opendbc.car.gm.values import is_bolt_cc_profile, is_volt_cc_profile, is_ordinary_cc_profile
return bool(not enabled and not active and qualified_gm(cp) and aol_enabled(cp) and
not cp.flags & GMFlags.PEDAL_LONG and
(is_bolt_cc_profile(cp) or is_volt_cc_profile(cp) or is_ordinary_cc_profile(cp)) and
intent is not None and intent.settingsQualified and
intent.allowedLatch and intent.lateralArmed and not intent.pauseLateral)
+3 -5
View File
@@ -388,8 +388,7 @@ def physical_cancel_lateral_allowed(CP, CS, intent, native, *, settings, previou
not any(e.type == car.CarState.ButtonEvent.Type.cancel for e in CS.buttonEvents)):
return False
ev6_long = CP.carFingerprint == HyundaiCAR.KIA_EV6 and qualified_canfd_long(CP, marked_only=True)
return bool((ev6_long or qualified_blended_alpha(CP, marked_only=True) or
independent_cruise_buttons_supported(CP, marked_only=True)) and
return bool((ev6_long or independent_cruise_buttons_supported(CP, marked_only=True)) and
previous is not None and previous.lateral_active and previous.native_acknowledged and
intent is not None and intent.settingsQualified and intent.allowedLatch and intent.lateralArmed and
not intent.pauseLateral and intent.producerSessionId and intent.carStateLogMonoTime == source_ns and
@@ -428,10 +427,9 @@ def retained_shared_long_cancel_intent(CP, CS, sm, feedback, owner, *, cancel_so
from openpilot.starpilot.car.hyundai.ev6_intent import Ev6CardIntent
ev6_long = bool(isinstance(owner, Ev6CardIntent) and owner._buttons_current and
owner.physical.authorized and owner._last_latch_edge_ns > 0)
mixed_long = qualified_blended_alpha(CP, marked_only=True)
if (not (ev6_long or mixed_long or qualified_sonata_independent(CP) or qualified_ccnc_long_aol(CP, marked_only=True) or
if (not (ev6_long or qualified_sonata_independent(CP) or qualified_ccnc_long_aol(CP, marked_only=True) or
CP.carFingerprint in INDEPENDENT_LONG_BUTTON_CARS and qualified_canfd_long(CP, marked_only=True)) or
owner is None or not owner.explicit_latch or not (owner.independent_cruise_buttons or ev6_long or mixed_long) or
owner is None or not owner.explicit_latch or not (owner.independent_cruise_buttons or ev6_long) or
not owner.allowed_latch or owner.pause_lateral or owner.pause_longitudinal or owner._fault_inhibit):
return False
settings = owner.settings
@@ -1,5 +1,4 @@
from opendbc.car.hyundai.classic_long_aol import independent_buttons_qualified
from opendbc.car.hyundai.blended_stock_aol import qualified_alpha
from openpilot.starpilot.aol.intent import AolCardIntent
@@ -8,8 +7,7 @@ class ClassicLongCardIntent(AolCardIntent):
def __init__(self, cp, settings):
super().__init__(settings, explicit_latch=True, independent_cruise_buttons=(
independent_buttons_qualified(cp, marked_only=False)),
preserve_unassigned_cancel=qualified_alpha(cp))
independent_buttons_qualified(cp, marked_only=False)))
self.requires_fault_observation = True
self.observe_stock_engagement = True
-9
View File
@@ -97,12 +97,3 @@ def sng_standstill_lateral_inhibit(cp, state):
# All stock Subaru native profiles require movement. Withdraw the request before its expected denial,
# preserving ordinary stock ACC without granting either active axis.
return bool(qualified(cp, marked_only=True) and state.standstill)
def temporary_eps_brake_recovery(cp, state):
return bool(qualified(cp, marked_only=True) and state.canValid and not state.canTimeout and
not state.steerFaultPermanent and not state.accFaulted and not state.doorOpen and
not state.vehicleSensorsInvalid and not state.steeringDisengage and not state.buttonEvents and
state.cruiseState.available and not state.cruiseState.enabled and
state.gearShifter == structs.CarState.GearShifter.drive and
state.brakePressed and not state.regenBraking and not state.gasPressed)
@@ -25,7 +25,7 @@ from openpilot.starpilot.car.subaru.tests.native_fixture import subaru_native_pa
def test_actual_full_engage_brake_reverse_temporary_recovery_mainoff(candidate, subaru_native_pair, tmp_path, aol=True, manual=False, stop_resume=False,
hard_stop_native_first=False, main_only=False, preferences=None, firmware=None,
deadline_gap=False, fixed_angle_handoff=False, host_first_stop=False,
source_outage=False, vm_angle_handoff=0, crawl=False, eps_brake_join=False):
source_outage=False, vm_angle_handoff=0):
angle_handoff = fixed_angle_handoff or vm_angle_handoff
fw = firmware_records(candidate, FW_VERSIONS, 'subaru') if firmware is None else firmware
rows = []
@@ -68,11 +68,10 @@ def test_actual_full_engage_brake_reverse_temporary_recovery_mainoff(candidate,
gap_tick = 75 if main_only else 210
last_card_published = {}
pending_host_can = []
pending_eps_event = None
for elapsed, tick in enumerate(ticks):
now = base + elapsed * 10_000_000 + (22_504_479 if deadline_gap and elapsed >= gap_tick else 0)
cruise = not main_only and (30 <= tick < 110 or tick >= 410)
brake = 90 <= tick < 110 or eps_brake_join and 225 <= tick < 270
brake = 90 <= tick < 110
reverse = 150 <= tick < 185
temporary = 225 <= tick < 260
main = not 310 <= tick < 350 and (tick >= 30 or not main_only)
@@ -84,11 +83,10 @@ def test_actual_full_engage_brake_reverse_temporary_recovery_mainoff(candidate,
'Steering_Torque': {'Steer_Warning': int(temporary),
'Steer_Torque_Sensor': (250 if 70 <= tick < 76 else
80 if vm_angle_handoff and 76 <= tick < 90 else
143 if 76 <= tick < 90 else 0) if angle_handoff else 94 if eps_brake_join and 235 <= tick < 237 else 0},
143 if 76 <= tick < 90 else 0) if angle_handoff else 0},
'Steering_2': {'Steering_Angle': (vm_angle_handoff * (5.65 if tick < 78 else 5.60) if vm_angle_handoff else
(5.65 if tick < 78 else 5.61) if fixed_angle_handoff else 0)},
'Wheel_Speeds': dict.fromkeys(('FL', 'FR', 'RL', 'RR'),
0 if stopped else 98.2 if vm_angle_handoff else 121.5 if fixed_angle_handoff else 7.2 if crawl else 54),
'Wheel_Speeds': dict.fromkeys(('FL', 'FR', 'RL', 'RR'), 0 if stopped else 98.2 if vm_angle_handoff else 121.5 if fixed_angle_handoff else 54),
'Brake_Status': {'Brake': int(brake)},
'CruiseControl': {'Cruise_On': int(main), 'Cruise_Activated': int(cruise)},
'ES_Status': {'Cruise_Activated': int(cruise)},
@@ -122,17 +120,7 @@ def test_actual_full_engage_brake_reverse_temporary_recovery_mainoff(candidate,
peers.append(panda.as_reader())
if 'carControl' in latest_controls:
peers.append(latest_controls['carControl'])
if pending_eps_event is not None:
peers.append(pending_eps_event)
pending_eps_event = None
for service in ('onroadEvents', 'selfdriveState', 'aolAxisState'):
if service not in driven_new or service not in card.sm.data:
continue
if eps_brake_join and service == 'onroadEvents' and tick == 236:
# Deliver the existing event one Card cycle after its axis update.
pending_eps_event = driven_new[service]
else:
peers.append(driven_new[service])
peers.extend(driven_new[s] for s in ('onroadEvents', 'selfdriveState', 'aolAxisState') if s in driven_new and s in card.sm.data)
if receipt is not None and (tick % 10 == 1 or hard_stop_receipt and not host_first_stop or host_first_stop and elapsed == 127):
peers.append(receipt.as_reader())
card.sm.update_msgs((now + 1000) / 1e9, peers)
@@ -231,9 +219,6 @@ def test_actual_full_engage_brake_reverse_temporary_recovery_mainoff(candidate,
request = bool(decoder.vl[steering_name]['LKAS_Request'])
output = decoder.vl[steering_name]['LKAS_Output']
decoded_steering.append({'request': request, 'output': output, 'nativeAccepted': accepted})
if crawl and 95 <= tick < 105:
assert aol and main_only and cs.brakePressed and not command.enabled
assert request and accepted, ('CRAWL_AOL_BRAKE_WITHDREW', tick, decoded_steering)
if not stopped and (65 <= tick < 90 and not (source_outage and 75 <= tick < 84) or
aol and (first_resume or 205 <= tick < 220 or 280 <= tick < 300)):
if angle_handoff and 72 <= tick < 78:
@@ -444,13 +429,3 @@ def test_actual_held_gas_source_outage_withdraws_and_recovers_subaru(candidate,
def test_actual_vm_angle_driver_handoff_keeps_native_rate_limit(candidate, direction, subaru_native_pair, tmp_path):
test_actual_full_engage_brake_reverse_temporary_recovery_mainoff(
candidate, subaru_native_pair, tmp_path, vm_angle_handoff=direction)
def test_actual_crawl_lateral_only_tracks_small_targets(subaru_native_pair, tmp_path):
test_actual_full_engage_brake_reverse_temporary_recovery_mainoff(
CAR.SUBARU_CROSSTREK_2025, subaru_native_pair, tmp_path, main_only=True, crawl=True)
def test_actual_eps_brake_event_join_preserves_armed_intent(subaru_native_pair, tmp_path):
test_actual_full_engage_brake_reverse_temporary_recovery_mainoff(
CAR.SUBARU_IMPREZA_2020, subaru_native_pair, tmp_path, main_only=True, eps_brake_join=True)
@@ -21,9 +21,7 @@ class TeslaScreenCardIntent(AolCardIntent):
super().update(filtered.as_reader(), **kwargs)
engaged = bool(CS.cruiseState.enabled)
available = bool(CS.cruiseState.available)
# Stock cruise availability does not revoke an explicit screen selection.
if (engaged and not self.previous_enabled) or (self.previous_available and not available and not CS.brakePressed and
self.screen_override is not True):
if (engaged and not self.previous_enabled) or (self.previous_available and not available and not CS.brakePressed):
self.screen_override = None if engaged and not self.previous_enabled else False
self.brake_blocked = False
self.pause_lateral = False
@@ -101,7 +101,7 @@
"discriminantOffset": 0
},
"new_storage": {
"dataWordCount": 18,
"dataWordCount": 21,
"pointerCount": 22,
"preferredListEncoding": "inlineComposite",
"isGroup": false,
@@ -252,6 +252,78 @@
"ordinal": {
"explicit": 58
}
},
{
"slot": {
"offset": 141,
"type": {
"bool": null
},
"defaultValue": {
"bool": false
},
"hadExplicitDefault": false
},
"name": "acceptedIsPreview",
"codeOrder": 59,
"discriminantValue": 65535,
"ordinal": {
"explicit": 59
}
},
{
"slot": {
"offset": 18,
"type": {
"uint64": null
},
"defaultValue": {
"uint64": 0
},
"hadExplicitDefault": false
},
"name": "wheelOverrideSince",
"codeOrder": 60,
"discriminantValue": 65535,
"ordinal": {
"explicit": 60
}
},
{
"slot": {
"offset": 19,
"type": {
"uint64": null
},
"defaultValue": {
"uint64": 0
},
"hadExplicitDefault": false
},
"name": "wheelAdoptMonoTime",
"codeOrder": 61,
"discriminantValue": 65535,
"ordinal": {
"explicit": 61
}
},
{
"slot": {
"offset": 40,
"type": {
"float32": null
},
"defaultValue": {
"float32": 0.0
},
"hadExplicitDefault": false
},
"name": "wheelAdoptTargetMps",
"codeOrder": 62,
"discriminantValue": 65535,
"ordinal": {
"explicit": 62
}
}
]
},
@@ -764,6 +836,90 @@
}
}
]
},
"0x9ccdc8676701b412": {
"kind": "struct",
"before_name": "SlcAction",
"after_name": "SlcAction",
"old_field_count": 8,
"old_storage": {
"dataWordCount": 4,
"pointerCount": 4,
"preferredListEncoding": "inlineComposite",
"isGroup": false,
"discriminantCount": 0,
"discriminantOffset": 0
},
"new_storage": {
"dataWordCount": 5,
"pointerCount": 4,
"preferredListEncoding": "inlineComposite",
"isGroup": false,
"discriminantCount": 0,
"discriminantOffset": 0
},
"added_fields": [
{
"slot": {
"offset": 4,
"type": {
"uint64": null
},
"defaultValue": {
"uint64": 0
},
"hadExplicitDefault": false
},
"name": "wheelAdoptMonoTime",
"codeOrder": 8,
"discriminantValue": 65535,
"ordinal": {
"explicit": 8
}
}
]
},
"0xcd96dafb67a082d0": {
"kind": "struct",
"before_name": "SlcCruiseEvent",
"after_name": "SlcCruiseEvent",
"old_field_count": 16,
"old_storage": {
"dataWordCount": 7,
"pointerCount": 6,
"preferredListEncoding": "inlineComposite",
"isGroup": false,
"discriminantCount": 0,
"discriminantOffset": 0
},
"new_storage": {
"dataWordCount": 8,
"pointerCount": 6,
"preferredListEncoding": "inlineComposite",
"isGroup": false,
"discriminantCount": 0,
"discriminantOffset": 0
},
"added_fields": [
{
"slot": {
"offset": 7,
"type": {
"uint64": null
},
"defaultValue": {
"uint64": 0
},
"hadExplicitDefault": false
},
"name": "wheelOverrideSince",
"codeOrder": 16,
"discriminantValue": 65535,
"ordinal": {
"explicit": 16
}
}
]
}
},
"published_addition_commits": {
@@ -181,52 +181,3 @@ def test_availability_off_still_allows_a_new_ordinary_cruise_engagement():
assert not owner.allowed_latch
owner.update(state(enabled=True).as_reader(), now_ns=50)
assert owner.allowed_latch and owner.screen_override is None
@pytest.mark.parametrize('brake_disengage', [False, True])
def test_stock_cruise_availability_cannot_erase_existing_screen_arm(brake_disengage):
owner = intent(brake_disengage)
owner.update(state(available=False, press=False).as_reader(), now_ns=10)
owner.update(state(available=False, press=True).as_reader(), now_ns=20)
owner.update(state(available=True, press=False).as_reader(), now_ns=30)
assert owner.screen_override is True
for index, available in enumerate((False, True, False, True)):
cs = state(available=available)
cs.gasPressed = bool(index % 2)
owner.update(cs.as_reader(), now_ns=40 + index)
assert owner.allowed_latch and owner.screen_override is True
owner.update(state(press=True).as_reader(), now_ns=50)
assert not owner.allowed_latch and owner.pause_lateral
owner.update(state(available=False, press=False).as_reader(), now_ns=60)
owner.update(state(available=True).as_reader(), now_ns=70)
assert not owner.allowed_latch
def test_cruise_baseline_availability_loss_does_not_create_screen_arm():
owner = intent()
owner.update(state().as_reader(), now_ns=10)
assert owner.allowed_latch and owner.screen_override is None
owner.update(state(available=False).as_reader(), now_ns=20)
assert not owner.allowed_latch and owner.screen_override is False
owner.update(state().as_reader(), now_ns=30)
assert not owner.allowed_latch
@pytest.mark.parametrize('fault', ['canTimeout', 'doorOpen', 'steerFaultPermanent', 'steeringDisengage', 'accFaulted', 'cancel', 'brake'])
def test_availability_loss_cannot_hide_fault_cancel_or_configured_brake(fault):
owner = intent(brake=fault == 'brake')
owner.update(state(available=False, press=False).as_reader(), now_ns=10)
owner.update(state(available=False, press=True).as_reader(), now_ns=20)
owner.update(state(press=False).as_reader(), now_ns=30)
cs = state(available=False)
if fault == 'cancel':
cs.buttonEvents = [structs.CarState.ButtonEvent(type=ButtonType.cancel, pressed=True)]
elif fault == 'brake':
cs.brakePressed = True
else:
setattr(cs, fault, True)
owner.update(cs.as_reader(), now_ns=40)
assert not owner.allowed_latch
for index, available in enumerate((False, True, False, True)):
owner.update(state(available=available).as_reader(), now_ns=50 + index)
assert not owner.allowed_latch
@@ -1,101 +0,0 @@
"""Independent screen arming through real parser, intent, native and axis callers."""
from types import SimpleNamespace
from typing import cast
import pytest
from opendbc.can import CANPacker
from opendbc.car.tesla.interface import CarInterface
from opendbc.car.tesla.screen_button import apply_screen_button
from opendbc.car.tesla.tests.test_screen_button import screen_params
from opendbc.safety.tests.libsafety import libsafety_py
from openpilot.cereal import log
from openpilot.selfdrive.selfdrived.events import Events
from openpilot.selfdrive.selfdrived.selfdrived import SelfdriveD
from openpilot.starpilot.aol.intent import AolSettings
from openpilot.starpilot.aol.runtime import AxisDecision
from openpilot.starpilot.aol.wire import IntentState, PandaSlot, SafetyState
from openpilot.starpilot.car.tesla.screen_intent import TeslaScreenCardIntent
class HealthyPeers(dict):
def all_checks(self, services):
return all(service in self for service in services)
@pytest.mark.parametrize('cooperative', [False, True])
@pytest.mark.parametrize('brake_disengage', [False, True])
def test_explicit_screen_axis_survives_sparse_touch_and_stock_availability(cooperative, brake_disengage):
cp = screen_params()
apply_screen_button(cp, True, brake_disengage)
cp.safetyConfigs[0].safetyParam |= 256 * cooperative
cp.alternativeExperience = 32
ci = CarInterface(cp)
owner = TeslaScreenCardIntent(cp, AolSettings(True, 0., 0, 0, (0, 0, 0), (0, 0, 0)))
packer = CANPacker('tesla_model3_party')
peers = HealthyPeers(modelV2=SimpleNamespace(),
extrinsicsCalibration=SimpleNamespace(calStatus=log.ExtrinsicsCalibration.Status.calibrated),
driverMonitoringState=SimpleNamespace(lockout=False, alwaysOnLockout=False, alertLevel=0))
sd = SimpleNamespace(CP=cp, active=False, enabled=False, initialized=True, sm=peers, aol_settings=owner.settings,
aol_axis_decision=AxisDecision(), aol_car_state_log_ns=0, aol_session_id='screen-test',
aol_dm_lateral_inhibit=False, nostalgia_paddle_cancel=False, aol_cruise_main_required=False,
events=Events())
native = libsafety_py.libsafety
native.init_tests()
native.set_alternative_experience(32)
native.set_safety_hooks(10, cp.safetyConfigs[0].safetyParam)
native.set_aol_test_heartbeat(True)
try:
for tick in range(250):
now = 1_000_000_000 + tick * 10_000_000
native.set_timer(now // 1000)
stock_state = 1 if 40 <= tick < 80 or 140 <= tick < 180 else 0
messages: tuple[tuple[str, int, dict[str, float]], ...] = (
('DAS_control', 2, {}), ('DAS_steeringControl', 2, {}),
('SCCM_steeringAngleSensor', 2, {}), ('DAS_status', 2, {}), ('DAS_settings', 2, {}),
('DI_speed', 0, {'DI_vehicleSpeed': 0}),
('ESP_B', 0, {'ESP_vehicleSpeed': 0, 'ESP_wheelSpeedsQF': 1, 'ESP_vehicleStandstillSts': 1}),
('EPAS3S_sysStatus', 0, {'EPAS3S_internalSAS': 0, 'EPAS3S_eacStatus': 1,
'EPAS3S_handsOnLevel': 3 if tick >= 240 else 0, 'EPAS3S_torsionBarTorque': 0}),
('DI_systemStatus', 0, {'DI_gear': 4, 'DI_accelPedalPos': 20 if tick % 30 < 15 else 0}),
('ESP_status', 0, {'ESP_driverBrakeApply': 2 if not brake_disengage and 100 <= tick < 120 else 1}),
('UI_warning', 0, {'buckleStatus': 1}),
('DI_state', 0, {'DI_cruiseState': stock_state}),
)
frames = [packer.make_can_msg(name, bus, values) for name, bus, values in messages]
if tick in (20, 21):
touch = bytearray(8)
touch[3] = 3 if tick == 21 else 0
frames.append((0x3DF, bytes(touch), 1))
for address, data, bus in frames:
assert native.safety_rx_hook(libsafety_py.make_CANPacket(address, bus, data))
native.safety_tick()
cs = ci.update([(now, frames)]).as_reader()
if tick > 1:
assert cs.canValid and not cs.canTimeout
owner.update(cs, now_ns=now)
allowed, pause, long_pause = owner.output(cs)
intention = IntentState('screen-card', tick + 1, now, now, now + 30_000_000, allowed, pause, long_pause, True, allowed)
permission = native.aol_get_permission_mask()
request = native.aol_get_request_mask()
safety = SafetyState(1, True, now, now + 200_000_000, 10, cp.safetyConfigs[0].safetyParam,
bool(permission & 1), bool(permission & 2), bool(request & 1), bool(request & 2),
'test-panda', 'screen-test', now,
(PandaSlot(0, 'test-panda', 10, cp.safetyConfigs[0].safetyParam, 32, bool(native.get_controls_allowed())),))
sd.aol_car_state_log_ns = now
sd.events = Events()
sd.events.add(log.OnroadEvent.EventName.pcmDisable)
if not cs.cruiseState.available:
sd.events.add(log.OnroadEvent.EventName.wrongCarMode)
decision = SelfdriveD._decide_aol_axes(cast(SelfdriveD, sd), cs, intention, safety, now_ns=now)
sd.aol_axis_decision = decision
native.aol_set_host_request(int(decision.desired_lateral))
assert not decision.longitudinal_active
if 22 <= tick < 240:
assert decision.lateral_active
elif tick >= 240:
assert not decision.lateral_active
finally:
native.init_tests()
native.set_alternative_experience(0)
native.set_safety_hooks(0, 0)

Some files were not shown because too many files have changed in this diff Show More