mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-03 07:22:05 +08:00
GM Gateway Steering
This commit is contained in:
@@ -12,7 +12,10 @@ from opendbc.car.gm.values import (control_flags,
|
||||
from opendbc.car.gm.lateral import lane_centering_supported
|
||||
|
||||
GM_AOL_ALTERNATIVE_EXPERIENCE = 32
|
||||
GM_BASE_GATEWAY_IDS = frozenset((CAR.GMC_ACADIA, CAR.BUICK_LACROSSE, CAR.CADILLAC_ESCALADE,
|
||||
CAR.CADILLAC_ESCALADE_ESV, CAR.CADILLAC_ESCALADE_ESV_2019, CAR.CHEVROLET_SUBURBAN))
|
||||
GM_AOL_WORDS = frozenset((
|
||||
0, 0x80,
|
||||
0x1001, 0x1401, 0x3001, 0x3401, 0x1003, 0x1403,
|
||||
0x201, 0x601, 0xA01, 0xE01, 0x203, 0x603, 0xA03, 0xE03,
|
||||
5, 7, 20, 0xBD, 0x9D, 0x19D, 0x1CD,
|
||||
@@ -44,6 +47,15 @@ def qualified_gm(cp) -> bool:
|
||||
tune = cp.lateralTuning.pid
|
||||
if not all(math.isfinite(value) for value in (*tune.kpBP, *tune.kpV, *tune.kiBP, *tune.kiV, tune.kf)):
|
||||
return False
|
||||
if cp.carFingerprint in GM_BASE_GATEWAY_IDS:
|
||||
return (cp.networkLocation == CarParams.NetworkLocation.gateway and
|
||||
cp.transmissionType == CarParams.TransmissionType.automatic and
|
||||
not cp.pcmCruise and not cp.alphaLongitudinalAvailable and
|
||||
not cp.radarUnavailable and control_flags(cp) == 0 and
|
||||
(int(cp.safetyConfigs[0].safetyParam) == 0 and
|
||||
(cp.openpilotLongitudinalControl or cp.carFingerprint == CAR.BUICK_LACROSSE) or
|
||||
int(cp.safetyConfigs[0].safetyParam) == 0x80 and
|
||||
cp.carFingerprint == CAR.BUICK_LACROSSE and cp.openpilotLongitudinalControl))
|
||||
if is_ordinary_camera_profile(cp, longitudinal=cp.openpilotLongitudinalControl):
|
||||
return True
|
||||
if is_ordinary_cc_profile(cp) or is_conventional_cc_pedal_profile(cp):
|
||||
|
||||
@@ -72,6 +72,7 @@ static void gm_aol_initialize(bool profile_supported) {
|
||||
static bool gm_aol_profile_word(uint16_t word) {
|
||||
bool supported = false;
|
||||
switch (word) {
|
||||
case 0U: case 0x80U:
|
||||
case 0x201U: case 0x601U: case 0xA01U: case 0xE01U:
|
||||
case 0xC171U: case 0xC172U: case 5U:
|
||||
case 0xBDU: case 0x9DU: case 0x19DU: case 0x1CDU:
|
||||
|
||||
@@ -118,3 +118,49 @@ class TestGmAol(unittest.TestCase):
|
||||
self.assertFalse(fixture.tx(button=3))
|
||||
fixture.safety.set_controls_allowed(False)
|
||||
self.assertFalse(fixture.tx(button=3))
|
||||
|
||||
def test_base_gateway_typed_axes_and_ordinary_controls(self):
|
||||
from opendbc.safety.tests.test_gm_volt_auto_hold import TestVoltAutoHold
|
||||
for word in (0, 0x80):
|
||||
for alternative in (0, 32):
|
||||
with self.subTest(word=word, alternative=alternative):
|
||||
f = TestVoltAutoHold()
|
||||
f.init(word=word, alternative=alternative)
|
||||
f.observations(regen_source=False)
|
||||
f.safety.set_aol_test_heartbeat(True)
|
||||
f.safety.aol_set_host_request(3)
|
||||
self.assertFalse(f.safety.get_controls_allowed())
|
||||
self.assertEqual(f.safety.aol_get_permission_mask(), 1 if alternative == 32 else 0)
|
||||
steering = gmcan.create_steering_control(f.packer, 0, 1, 0, True)
|
||||
self.assertEqual(f.safety.safety_tx_hook(self.packet(steering)), alternative == 32)
|
||||
self.assertFalse(f.tx(80))
|
||||
f.rx(0x1E1, [0, 0, 0, 0, 0, 0x20, 0])
|
||||
self.assertTrue(f.safety.get_controls_allowed())
|
||||
if alternative == 32:
|
||||
self.assertEqual(f.safety.aol_get_permission_mask(), 3)
|
||||
self.assertTrue(f.safety.safety_tx_hook(self.packet(steering)))
|
||||
self.assertTrue(f.tx(400, 0xA))
|
||||
self.assertFalse(f.tx(401, 0xA))
|
||||
f.observations(brake=True, regen_source=False)
|
||||
self.assertFalse(f.safety.get_controls_allowed())
|
||||
if alternative == 32:
|
||||
self.assertEqual(f.safety.aol_get_permission_mask(), 1)
|
||||
f.observations(main=False, regen_source=False)
|
||||
self.assertFalse(f.safety.safety_tx_hook(self.packet(steering)))
|
||||
|
||||
def test_base_gateway_main_health_and_unknown_neighbors(self):
|
||||
from opendbc.safety.tests.test_gm_volt_auto_hold import TestVoltAutoHold
|
||||
for word in (0, 0x80):
|
||||
f = TestVoltAutoHold()
|
||||
f.init(word=word, alternative=32)
|
||||
f.observations(regen_source=False)
|
||||
f.safety.set_aol_test_heartbeat(True)
|
||||
self.assertEqual(self.request(1), 1)
|
||||
f.safety.set_timer(f.time + 300001)
|
||||
self.assertEqual(self.request(1), 0)
|
||||
f.observations(main=False, regen_source=False)
|
||||
self.assertEqual(self.request(1), 0)
|
||||
for word in (0x40, 0x81, 0x82, 0x84, 0x180):
|
||||
self.reset(word)
|
||||
self.feed()
|
||||
self.assertEqual(self.request(3), 0)
|
||||
|
||||
@@ -142,7 +142,7 @@ class TestGmAutoHoldSources(unittest.TestCase):
|
||||
f.safety.set_aol_test_heartbeat(True)
|
||||
f.safety.aol_set_host_request(1)
|
||||
steering = gmcan.create_steering_control(f.packer, 0, 1, 0, True)
|
||||
self.assertEqual(f.safety.safety_tx_hook(libsafety_py.make_CANPacket(steering[0], steering[2], steering[1])), alternative == 32 and word != 0x80)
|
||||
self.assertEqual(f.safety.safety_tx_hook(libsafety_py.make_CANPacket(steering[0], steering[2], steering[1])), alternative == 32)
|
||||
f.observations(speed=100, regen_source=word != 0x80)
|
||||
f.rx(0x1E1, [0, 0, 0, 0, 0, 0x20, 0])
|
||||
self.assertTrue(f.safety.get_controls_allowed())
|
||||
|
||||
@@ -180,6 +180,31 @@ int main() {
|
||||
forte.safety_mode = HYUNDAI_AOL_PROFILE.mode;
|
||||
assert(!aol_capable(forte, HYUNDAI_AOL_PROFILE.mode, VEHICLE_REGISTRY));
|
||||
}
|
||||
for (const uint16_t word : {0U, 0x80U}) {
|
||||
AolAxisNegotiator negotiator(VEHICLE_REGISTRY);
|
||||
FakeTransport transport;
|
||||
auto gateway = status();
|
||||
gateway.safety_mode = GM_AOL_PROFILE.mode;
|
||||
gateway.safety_param = word;
|
||||
assert(gm_aol_param(word));
|
||||
queue(transport, gateway, gateway);
|
||||
const auto first = run(negotiator, transport, axis("gateway"), NOW, GM_AOL_PROFILE.mode);
|
||||
assert(first.plan.capable && first.plan.request_mask == 0U && first.outcome.compatible);
|
||||
auto active = gateway;
|
||||
active.request_mask = 3U;
|
||||
active.permission_mask = 1U;
|
||||
queue(transport, gateway, active);
|
||||
const auto second = run(negotiator, transport, axis("gateway", true, true), NOW, GM_AOL_PROFILE.mode);
|
||||
assert(second.plan.capable && second.plan.request_mask == 3U && second.outcome.compatible);
|
||||
assert(second.outcome.status && second.outcome.status->permission_mask == 1U);
|
||||
}
|
||||
for (const uint16_t word : {0x40U, 0x81U, 0x82U, 0x84U, 0x180U}) {
|
||||
assert(!gm_aol_param(word));
|
||||
auto unknown = status();
|
||||
unknown.safety_mode = GM_AOL_PROFILE.mode;
|
||||
unknown.safety_param = word;
|
||||
assert(!aol_capable(unknown, GM_AOL_PROFILE.mode, VEHICLE_REGISTRY));
|
||||
}
|
||||
for (uint32_t word = 0U; word <= 65535U; word++) {
|
||||
auto gm = status();
|
||||
gm.safety_mode = GM_AOL_PROFILE.mode;
|
||||
|
||||
@@ -6,7 +6,8 @@ from unittest.mock import patch
|
||||
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.car.gm.aol import GM_AOL_WORDS, qualified_gm
|
||||
from opendbc.car.gm.aol import GM_AOL_WORDS, GM_BASE_GATEWAY_IDS, qualified_gm
|
||||
from opendbc.car.gm.lateral import lane_centering_supported
|
||||
from opendbc.car.gm.interface import CarInterface
|
||||
from opendbc.car.gm.tests.test_bolt_cc import params as bolt_params, feed as feed_car, control, native
|
||||
from opendbc.safety.tests.libsafety import libsafety_py
|
||||
@@ -33,7 +34,17 @@ BOLT_IDS = (CAR.CHEVROLET_BOLT_CC_2017, CAR.CHEVROLET_BOLT_CC_2018_2021,
|
||||
CAR.CHEVROLET_BOLT_CC_2022_2023, CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL)
|
||||
|
||||
|
||||
def configurations():
|
||||
def _configurations():
|
||||
for identity in GM_BASE_GATEWAY_IDS:
|
||||
cp = ordinary_params(identity, radar=True)
|
||||
yield cp
|
||||
if identity == CAR.BUICK_LACROSSE:
|
||||
disabled = cp.as_reader().as_builder()
|
||||
VehicleStartupPreferences(disable_bolt_long=True).prepare(disabled)
|
||||
yield disabled
|
||||
held = cp.as_reader().as_builder()
|
||||
VehicleStartupPreferences(gm_auto_hold=True).prepare(held)
|
||||
yield held
|
||||
for identity in BOLT_IDS:
|
||||
for pedal in (False, True):
|
||||
for removed in (False, True):
|
||||
@@ -49,6 +60,7 @@ def configurations():
|
||||
yield factory_params(alpha=alpha)
|
||||
yield camera_params(alpha=alpha)
|
||||
yield removed_params(alpha=alpha)
|
||||
yield removed_params(alpha=alpha, alternate=True)
|
||||
for c9 in (False, True):
|
||||
yield sdgm_params(alpha=alpha, brake_c9=c9)
|
||||
for c9 in (False, True):
|
||||
@@ -87,6 +99,15 @@ def configurations():
|
||||
yield silverado_pedal(removed=removed, disabled=disabled)
|
||||
|
||||
|
||||
def configurations():
|
||||
for cp in _configurations():
|
||||
yield cp
|
||||
selected = cp.as_reader().as_builder()
|
||||
VehicleStartupPreferences(gm_auto_hold=True).prepare(selected)
|
||||
if selected.safetyConfigs[0].safetyParam != cp.safetyConfigs[0].safetyParam:
|
||||
yield selected
|
||||
|
||||
|
||||
class TestGmAol(unittest.TestCase):
|
||||
def test_actual_final_cp_registry_and_isolation(self):
|
||||
words = set()
|
||||
@@ -105,7 +126,29 @@ class TestGmAol(unittest.TestCase):
|
||||
denied = cp.as_reader().as_builder()
|
||||
denied.alternativeExperience = 33
|
||||
self.assertFalse(qualified_gm(denied))
|
||||
self.assertEqual(words, GM_AOL_WORDS)
|
||||
self.assertEqual(words, GM_AOL_WORDS, {"missing": GM_AOL_WORDS - words, "unexpected": words - GM_AOL_WORDS})
|
||||
|
||||
def test_base_gateway_aol_preserves_tuning_and_lane_capability(self):
|
||||
for identity in GM_BASE_GATEWAY_IDS:
|
||||
cp = ordinary_params(identity, radar=True)
|
||||
with self.subTest(identity=identity):
|
||||
self.assertTrue(qualified_gm(cp))
|
||||
self.assertFalse(lane_centering_supported(cp))
|
||||
selected = cp.as_reader().as_builder()
|
||||
selected.alternativeExperience = 32
|
||||
self.assertTrue(qualified_gm(selected))
|
||||
self.assertFalse(lane_centering_supported(selected))
|
||||
for field, value in (('pcmCruise', True), ('radarUnavailable', True), ('alphaLongitudinalAvailable', True),
|
||||
('networkLocation', 'fwdCamera'), ('transmissionType', 'direct'), ('flags', 1)):
|
||||
denied = cp.as_reader().as_builder()
|
||||
setattr(denied, field, value)
|
||||
self.assertFalse(qualified_gm(denied))
|
||||
denied = cp.as_reader().as_builder()
|
||||
denied.safetyConfigs[0].safetyParam = 0x80
|
||||
self.assertEqual(qualified_gm(denied), identity == CAR.BUICK_LACROSSE)
|
||||
denied = cp.as_reader().as_builder()
|
||||
denied.openpilotLongitudinalControl = False
|
||||
self.assertEqual(qualified_gm(denied), identity == CAR.BUICK_LACROSSE)
|
||||
|
||||
@staticmethod
|
||||
def card(cp, settings):
|
||||
@@ -207,6 +250,121 @@ class TestGmAol(unittest.TestCase):
|
||||
self.assertFalse(command.longActive)
|
||||
|
||||
|
||||
def test_base_gateway_actual_card_controls_parser_and_native_axes(self):
|
||||
cases = [(identity, False, False) for identity in GM_BASE_GATEWAY_IDS]
|
||||
cases += [(CAR.BUICK_LACROSSE, True, False), (CAR.BUICK_LACROSSE, False, True)]
|
||||
safety = libsafety_py.libsafety
|
||||
for identity, held, disabled in cases:
|
||||
with self.subTest(identity=identity, held=held, disabled=disabled), OpenpilotPrefix(), \
|
||||
patch.dict(os.environ, {'SIMULATION': '1', 'REPLAY': '1', 'AOL_REPLAY_RUNTIME': '0'}):
|
||||
settings = Params()
|
||||
settings.put_bool('GMAutoHold', held, block=True)
|
||||
settings.put_bool('DisableOpenpilotLongitudinal', disabled, block=True)
|
||||
factory = ordinary_params(identity, radar=True)
|
||||
default = self.card(factory, settings)
|
||||
self.assertIsNone(default.aol_card_intent)
|
||||
self.assertEqual(default.CP.alternativeExperience, 0)
|
||||
settings.put_bool('AlwaysOnLateral', True, block=True)
|
||||
selected = self.card(factory, settings)
|
||||
self.assertEqual(selected.CP.safetyConfigs[0].safetyParam, default.CP.safetyConfigs[0].safetyParam)
|
||||
self.assertEqual(selected.CP.lateralTuning.to_dict(), default.CP.lateralTuning.to_dict())
|
||||
self.assertEqual(selected.CP.alternativeExperience, 32)
|
||||
self.assertEqual(selected.CP.openpilotLongitudinalControl, not disabled)
|
||||
self.assertFalse(selected.CP.pcmCruise)
|
||||
self.assertEqual(selected.CP.safetyConfigs[0].safetyParam, 0x80 if held else 0)
|
||||
self.assertEqual(selected.CP.lateralTuning.to_dict(), factory.lateralTuning.to_dict())
|
||||
self.assertFalse(lane_centering_supported(selected.CP))
|
||||
controls = Controls()
|
||||
self.assertEqual(controls.CP.lateralTuning.to_dict(), factory.lateralTuning.to_dict())
|
||||
self.assertEqual(controls.CP.lateralTuning.which(), factory.lateralTuning.which())
|
||||
ci = selected.CI
|
||||
packer = CANPacker(DBC[identity][Bus.pt])
|
||||
safety.set_alternative_experience(32)
|
||||
self.assertEqual(safety.set_safety_hooks(structs.CarParams.SafetyModel.gm, selected.CP.safetyConfigs[0].safetyParam), 0)
|
||||
safety.init_tests()
|
||||
seen_lateral = seen_longitudinal = False
|
||||
for tick in range(150):
|
||||
now = 1_000_000_000 + tick * 10_000_000
|
||||
ordinary = 41 <= tick < 60 and not disabled
|
||||
main = not 20 <= tick < 30
|
||||
gas, brake, reverse = 70 <= tick < 80, 80 <= tick < 90, 90 <= tick < 100
|
||||
_, packets = feed_car(SimpleNamespace(update=lambda _: None), packer, now, counter=tick % 4,
|
||||
active=ordinary, speed=20., camera=False)
|
||||
packets = [packet for packet in packets if packet[0] not in (0xC9, 0x1C4, 0xBE, 0x1E1, 0x1F5, 0xBD)]
|
||||
packets += [packer.make_can_msg('AcceleratorPedal2', 0, {'CruiseState': 2 if ordinary else 0,
|
||||
'AcceleratorPedal2': 30 if gas else 0}),
|
||||
packer.make_can_msg('ECMAcceleratorPos', 0, {'BrakePedalPos': 20 if brake else 0}),
|
||||
packer.make_can_msg('ECMPRDNL2', 0, {'PRNDL2': 2 if reverse else 4}),
|
||||
packer.make_can_msg('ASCMSteeringButton', 0, {'ACCButtons': 3 if tick == 40 else 6 if tick == 60 else 1,
|
||||
'RollingCounter': tick % 4})]
|
||||
if not 100 <= tick < 140:
|
||||
packets.append(packer.make_can_msg('ECMEngineStatus', 0, {'CruiseMainOn': int(main)}))
|
||||
ci.update([(now - 1_000_000, packets)])
|
||||
cs = ci.update([(now, packets)])
|
||||
for packet in packets:
|
||||
native('rx', packet, now // 1000)
|
||||
safety.safety_tick()
|
||||
safety.set_aol_test_heartbeat(True)
|
||||
selected.aol_card_intent.update(cs, now_ns=now, standard_enabled=ordinary)
|
||||
intent = IntentState('card', tick + 1, now, now, now + 30_000_000,
|
||||
selected.aol_card_intent.allowed_latch, False, False, True, True)
|
||||
wanted = decide_axes(standard_lateral=ordinary, standard_longitudinal=ordinary, intent=intent, native=None,
|
||||
car_state=cs, initialized=True, model_ready=True, no_entry=False,
|
||||
immediate_disable=False, dm_lockout=False, pause_brake_mps=25.)
|
||||
safety.aol_set_host_request(int(wanted.desired_lateral) | (int(wanted.desired_longitudinal) << 1))
|
||||
safety.set_timer(now // 1000 + 1)
|
||||
mask = safety.aol_get_permission_mask()
|
||||
receipt_state = SafetyState(1, True, now, now + 200_000_000, int(structs.CarParams.SafetyModel.gm),
|
||||
selected.CP.safetyConfigs[0].safetyParam,
|
||||
bool(mask & 1), bool(mask & 2),
|
||||
wanted.desired_lateral, wanted.desired_longitudinal, 'panda', 'gm-gateway-test')
|
||||
decision = decide_axes(standard_lateral=ordinary, standard_longitudinal=ordinary, intent=intent, native=receipt_state,
|
||||
car_state=cs, initialized=True, model_ready=True, no_entry=False,
|
||||
immediate_disable=False, dm_lockout=False, pause_brake_mps=25.)
|
||||
feed(controls, now, tick, active=ordinary, enabled=ordinary)
|
||||
axis = messaging.new_message('aolAxisState', valid=True, logMonoTime=now)
|
||||
axis.aolAxisState.qualified = True
|
||||
axis.aolAxisState.nativeAcknowledged = True
|
||||
axis.aolAxisState.desiredLateral = decision.desired_lateral
|
||||
axis.aolAxisState.desiredLongitudinal = decision.desired_longitudinal
|
||||
axis.aolAxisState.lateralActive = decision.lateral_active
|
||||
axis.aolAxisState.longitudinalActive = decision.longitudinal_active
|
||||
axis.aolAxisState.sessionId = 'gm-gateway-test'
|
||||
axis.aolAxisState.observedMonoTime = now
|
||||
axis.aolAxisState.validUntilMonoTime = now + 30_000_000
|
||||
receipt = messaging.new_message('aolSafetyWire', 0, valid=True, logMonoTime=now)
|
||||
from dataclasses import replace
|
||||
transport_receipt = receipt_state
|
||||
if tick == 11:
|
||||
transport_receipt = replace(receipt_state, safetyParam=5)
|
||||
elif tick == 12:
|
||||
transport_receipt = replace(receipt_state, axisSessionId='wrong-session')
|
||||
elif tick == 13:
|
||||
transport_receipt = replace(receipt_state, observedMonoTime=now + 1)
|
||||
elif tick == 14:
|
||||
transport_receipt = replace(receipt_state, validUntilMonoTime=now - 1)
|
||||
receipt.aolSafetyWire = encode_safety(transport_receipt)
|
||||
state = messaging.new_message('carState', valid=True, logMonoTime=now)
|
||||
state.carState = cs
|
||||
controls.sm.update_msgs((now + 1_000) / 1e9, [axis.as_reader(), receipt.as_reader(), state.as_reader()])
|
||||
command, _ = controls.state_control()
|
||||
self.assertEqual(command.latActive, decision.lateral_active and tick not in (11, 12, 13, 14))
|
||||
self.assertEqual(command.longActive, ordinary)
|
||||
self.assertEqual(bool(safety.get_controls_allowed()), 41 <= tick < 60)
|
||||
if not main or brake or reverse or 131 <= tick < 140:
|
||||
self.assertFalse(command.latActive)
|
||||
if tick == 10:
|
||||
self.assertTrue(command.latActive)
|
||||
command.actuators.torque = .02 if command.latActive else 0.
|
||||
_, messages = ci.apply(command.as_reader(), now + 2)
|
||||
for message in messages:
|
||||
self.assertTrue(native('tx', message, now // 1000 + 1), (tick, message))
|
||||
seen_lateral |= command.latActive and not command.longActive
|
||||
seen_longitudinal |= command.longActive
|
||||
self.assertTrue(seen_lateral)
|
||||
self.assertEqual(seen_longitudinal, not disabled)
|
||||
safety.set_alternative_experience(0)
|
||||
|
||||
def test_actual_card_forwards_shared_disarming_events(self):
|
||||
class IntentUpdated(Exception):
|
||||
pass
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
inline bool gm_aol_param(uint16_t param) {
|
||||
switch (param) {
|
||||
case 0U: case 0x80U:
|
||||
case 0x201U: case 0x601U: case 0xA01U: case 0xE01U:
|
||||
case 0x203U: case 0x603U: case 0xA03U: case 0xE03U:
|
||||
case 0xC172U: case 0xC173U: case 0xC171U: case 5U: case 7U: case 20U:
|
||||
|
||||
Reference in New Issue
Block a user