diff --git a/opendbc_repo/opendbc/car/gm/carcontroller.py b/opendbc_repo/opendbc/car/gm/carcontroller.py index fa698aaea0..37eea7a3d6 100644 --- a/opendbc_repo/opendbc/car/gm/carcontroller.py +++ b/opendbc_repo/opendbc/car/gm/carcontroller.py @@ -1200,7 +1200,7 @@ class CarController(CarControllerBase): self.bolt_removed_cancel_credit_used = CS.conventional_cancel_credit.credit_ns can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.POWERTRAIN, (CS.buttons_counter + 1) % 4, CruiseButtons.CANCEL)) - if self.CP.networkLocation == NetworkLocation.fwdCamera and not self.bolt_cc_profile: + if self.CP.networkLocation == NetworkLocation.fwdCamera and not self.bolt_cc_profile and not self.volt_camera_removed: # Silence "Take Steering" alert sent by camera, forward PSCMStatus with HandsOffSWlDetectionStatus=1 if self.frame % 10 == 0: can_sends.append(gmcan.create_pscm_status(self.packer_pt, CanBus.CAMERA, CS.pscm_status)) diff --git a/opendbc_repo/opendbc/car/gm/tests/test_volt_alternate_brake.py b/opendbc_repo/opendbc/car/gm/tests/test_volt_alternate_brake.py index 9c3d61b876..f19c54974d 100644 --- a/opendbc_repo/opendbc/car/gm/tests/test_volt_alternate_brake.py +++ b/opendbc_repo/opendbc/car/gm/tests/test_volt_alternate_brake.py @@ -91,6 +91,13 @@ class TestVoltAlternateBrake(unittest.TestCase): ci = CarInterface(cp) packer = CANPacker(DBC[cp.carFingerprint][Bus.pt]) for tick, expected in enumerate(baseline['tx']): + # Keep every frozen vehicle-side byte. Only the removed camera's + # obsolete PSCM replacement is intentionally absent in this topology. + if is_volt_camera_removed(cp): + self.assertIn(name, ('removed-0', 'removed-1')) + camera_status = [message for message in expected if message[0] == 0x184 and message[2] == 2] + self.assertEqual(len(camera_status), int(tick % 10 == 0)) + expected = [message for message in expected if not (message[0] == 0x184 and message[2] == 2)] now = 1_000_000_000 + tick * 10_000_000 speed = 20. if tick < 40 or tick >= 100 else 0. if tick < 80 else 6. active, brake, gas = tick < 60 or tick >= 90, 60 <= tick < 65, 65 <= tick < 70 diff --git a/opendbc_repo/opendbc/car/gm/tests/test_volt_camera_disconnected.py b/opendbc_repo/opendbc/car/gm/tests/test_volt_camera_disconnected.py new file mode 100644 index 0000000000..f38158ba20 --- /dev/null +++ b/opendbc_repo/opendbc/car/gm/tests/test_volt_camera_disconnected.py @@ -0,0 +1,60 @@ +"""Route 11177: a removed camera must never receive forwarded or host traffic.""" +import pytest + +from opendbc.can import CANPacker +from opendbc.car import Bus, structs +from opendbc.car.gm.interface import CarInterface +from opendbc.car.gm.tests.test_volt_camera_removed import removed_params, feed_removed +from opendbc.car.gm.tests.test_volt_camera_control import camera_params, feed_camera +from opendbc.car.gm.values import CAR, DBC +from opendbc.safety.tests.libsafety import libsafety_py + + +@pytest.mark.parametrize('alpha', [False, True]) +@pytest.mark.parametrize('alternate', [False, True]) +def test_removed_camera_controller_uses_only_vehicle_bus(alpha, alternate): + cp = removed_params(alpha=alpha, alternate=alternate) + ci = CarInterface(cp) + packer = CANPacker(DBC[cp.carFingerprint][Bus.pt]) + sent = [] + for tick in range(24): + now = 1_000_000_000 + tick * 40_000_000 + cs, _ = feed_removed(ci, packer, now, counter=tick % 4, active=False) + assert cs.canValid and not cs.canTimeout + cc = structs.CarControl(enabled=False, latActive=False, longActive=False) + _, frames = ci.apply(cc.as_reader(), now + 2) + assert all(frame[2] == 0 for frame in frames) + sent.extend(frames) + assert any(frame[0] == 0x180 for frame in sent) + + +@pytest.mark.parametrize('word', [0xC150, 0xC151]) +def test_removed_camera_native_has_no_forwarding_or_camera_transmit(word): + safety = libsafety_py.libsafety + release = safety.set_safety_hooks(structs.CarParams.SafetyModel.allOutput, 0) != 0 + assert safety.set_safety_hooks(structs.CarParams.SafetyModel.gm, word) == 0 + safety.init_tests() + if release and word == 0xC151: + assert not safety.safety_config_valid() + return + for bus in (0, 1, 2): + for address in range(0x800): + assert safety.safety_fwd_hook(bus, address) == -1 + packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CAMERA][Bus.pt]) + frame = packer.make_can_msg('PSCMStatus', 2, {}) + assert not safety.safety_tx_hook(libsafety_py.make_CANPacket(frame[0], frame[2], frame[1])) + + +@pytest.mark.parametrize('alpha', [False, True]) +def test_connected_camera_retains_its_status_delivery(alpha): + cp = camera_params(alpha=alpha) + ci = CarInterface(cp) + packer = CANPacker(DBC[cp.carFingerprint][Bus.pt]) + sent = [] + for tick in range(12): + now = 1_000_000_000 + tick * 40_000_000 + cs, _ = feed_camera(ci, packer, now, counter=tick % 4, active=False) + assert cs.canValid and not cs.canTimeout + _, frames = ci.apply(structs.CarControl().as_reader(), now + 2) + sent.extend(frames) + assert any(frame[0] == 0x184 and frame[2] == 2 for frame in sent) diff --git a/opendbc_repo/opendbc/safety/modes/gm.h b/opendbc_repo/opendbc/safety/modes/gm.h index 22a184396b..f3c0971475 100644 --- a/opendbc_repo/opendbc/safety/modes/gm.h +++ b/opendbc_repo/opendbc/safety/modes/gm.h @@ -549,6 +549,8 @@ static bool gm_ascm_adas_valid(const CANPacket_t *msg) { } static bool gm_tx_hook(const CANPacket_t *msg) { + // This topology has no camera-side receiver after harness interception. + if (gm_volt_camera_removed && (msg->bus == 2U)) { return false; } const TorqueSteeringLimits GM_STEERING_LIMITS = { .max_torque = 300, .max_rate_up = 10, @@ -1093,14 +1095,12 @@ static safety_config gm_init(uint16_t safety_param) { static const CanMsg GM_VOLT_REMOVED_STOCK_TX_MSGS[] = { {0x180, 0, 4, .check_relay = false}, {0x1E1, 0, 7, .check_relay = false}, - {0x184, 2, 8, .check_relay = false}, }; #ifdef ALLOW_DEBUG static const CanMsg GM_VOLT_REMOVED_LONG_TX_MSGS[] = { {0x180, 0, 4, .check_relay = false}, {0x315, 0, 5, .check_relay = false}, {0x2CB, 0, 8, .check_relay = false}, {0x370, 0, 6, .check_relay = false}, {0x409, 0, 7, .check_relay = false}, {0x40A, 0, 7, .check_relay = false}, - {0x184, 2, 8, .check_relay = false}, }; #endif static RxCheck gm_volt_removed_rx_checks[] = { @@ -1851,6 +1851,10 @@ static safety_config gm_init(uint16_t safety_param) { gm_pcm_cruise = false; } + // A removed Volt camera leaves bus 2 unacknowledged after relay interception. + // Forwarding vehicle traffic there fills TX queues and stalls host CAN reads. + if (gm_volt_camera_removed) { ret.disable_forwarding = true; } + // ASCM does not forward any messages if ((gm_hw == GM_ASCM) && !gm_camera_gateway) { ret.disable_forwarding = true; diff --git a/opendbc_repo/opendbc/safety/tests/test_gm_volt_camera_removed.py b/opendbc_repo/opendbc/safety/tests/test_gm_volt_camera_removed.py index 2982fc0642..50afa62fff 100644 --- a/opendbc_repo/opendbc/safety/tests/test_gm_volt_camera_removed.py +++ b/opendbc_repo/opendbc/safety/tests/test_gm_volt_camera_removed.py @@ -109,7 +109,7 @@ class TestGmVoltCameraRemoved(unittest.TestCase): self.assertEqual(self.safety.safety_fwd_hook(0, 0x184), -1) self.assertEqual(self.safety.safety_fwd_hook(2, 0x180), -1) for address in (0x315, 0x2CB, 0x370, 0x2CD): - self.assertEqual(self.safety.safety_fwd_hook(2, address), -1 if word == 0xC151 else 0) + self.assertEqual(self.safety.safety_fwd_hook(2, address), -1) self.mode(0xC150) self.physical() for middle in (5, 0x4007, 0x1005, 4):