diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/byd/bydcan.py b/artifacts/package_sources/iqdbc/iqdbc/car/byd/bydcan.py index 9c5a85214..c5f1dee1d 100644 --- a/artifacts/package_sources/iqdbc/iqdbc/car/byd/bydcan.py +++ b/artifacts/package_sources/iqdbc/iqdbc/car/byd/bydcan.py @@ -3,10 +3,18 @@ # Measured off the stock camera while it was actively steering (route 0000007b--88dd577c32): # every engaged 0x1E2 carries +500/-500 and byte5 = 0x64. 251/-252/0xFF came from Atto 3 notes. -ANGLE_RATE_LIMIT_UPPER = 500 -ANGLE_RATE_LIMIT_LOWER = -500 +OEM_ANGLE_RATE_LIMIT = 500 SET_ME_FF_VALUE = 0x64 +# This symmetric pair is the EPS authority ceiling, not a demand: the stock camera can hold it wide +# open because its own angle command never asks for much torque, but openpilot's does, and the EPS +# was measured saturating at +-470 against the +-500 ceiling while the wheel jittered. Other BYD +# angle-mode ports ship the equivalent field at 200-300. Lowered to 300 so the EPS runs out of +# authority before it can fight the driver or slam the wheel; raise back to OEM_ANGLE_RATE_LIMIT to +# restore byte-exact stock frames. +ANGLE_RATE_LIMIT_UPPER = 300 +ANGLE_RATE_LIMIT_LOWER = -300 + # 0x316 LKAS_STATE, in the order the stock camera walks them on an engage LKAS_STATE_IDLE = 1 LKAS_STATE_SUSPENDED = 2 diff --git a/artifacts/package_sources/iqdbc/iqdbc/car/byd/tests/test_byd.py b/artifacts/package_sources/iqdbc/iqdbc/car/byd/tests/test_byd.py index b3bbd3b12..412732099 100644 --- a/artifacts/package_sources/iqdbc/iqdbc/car/byd/tests/test_byd.py +++ b/artifacts/package_sources/iqdbc/iqdbc/car/byd/tests/test_byd.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import unittest +import unittest.mock import numpy as np @@ -87,9 +88,18 @@ class TestBydSteeringControl(unittest.TestCase): self.assertEqual(vals["ANGLE_RATE_LIMIT_UPPER"], bydcan.ANGLE_RATE_LIMIT_UPPER) self.assertEqual(vals["ANGLE_RATE_LIMIT_LOWER"], bydcan.ANGLE_RATE_LIMIT_LOWER) + def _oem_ceiling(self): + return unittest.mock.patch.multiple( + bydcan, + ANGLE_RATE_LIMIT_UPPER=bydcan.OEM_ANGLE_RATE_LIMIT, + ANGLE_RATE_LIMIT_LOWER=-bydcan.OEM_ANGLE_RATE_LIMIT, + ) + def test_matches_stock_camera_idle_frame(self): - # stock idle: f4 31 c8 .. .. 64 (47625 samples); only the angle bytes differ - _, dat, _ = bydcan.create_steering_control(self.packer, 0.0, False) + # stock idle: f4 31 c8 .. .. 64 (47625 samples); only the angle bytes differ. Checked with the + # OEM ceiling so the frame stays provably stock-shaped independent of the shipped ceiling. + with self._oem_ceiling(): + _, dat, _ = bydcan.create_steering_control(self.packer, 0.0, False) self.assertEqual(bytes(dat[:3]), bytes.fromhex("f431c8"), f"idle 0x1E2 diverges from stock: {bytes(dat[:3]).hex(' ')} != f4 31 c8") self.assertEqual(dat[5], 0x64) @@ -99,15 +109,18 @@ class TestBydSteeringControl(unittest.TestCase): # dashcam mode (route 0000007b--88dd577c32): f4 31 e8 01 00 64 9f ee at +0.1 deg. # Only COUNTER/CHECKSUM (byte 6 high nibble, byte 7) may differ. OEM = bytes.fromhex("f431e8010064") - _, dat, _ = bydcan.create_steering_control(self.packer, 0.1, True) + with self._oem_ceiling(): + _, dat, _ = bydcan.create_steering_control(self.packer, 0.1, True) self.assertEqual(bytes(dat[:6]), OEM, f"0x1E2 diverges from stock: {bytes(dat[:6]).hex(' ')} != {OEM.hex(' ')}") self.assertEqual(dat[6] & 0x0F, 0x0F) - def test_rate_limits_and_set_me_match_stock(self): - # 251/-252/0xFF came from the Atto 3 notes; this car's camera sends 500/-500/0x64 - self.assertEqual(bydcan.ANGLE_RATE_LIMIT_UPPER, 500) - self.assertEqual(bydcan.ANGLE_RATE_LIMIT_LOWER, -500) + def test_authority_ceiling_is_symmetric_and_within_stock(self): + # The ceiling caps how hard the EPS may push, so it must never exceed what stock authorises, + # and it must stay symmetric or the EPS gets more authority one way than the other. + self.assertEqual(bydcan.ANGLE_RATE_LIMIT_LOWER, -bydcan.ANGLE_RATE_LIMIT_UPPER) + self.assertLessEqual(bydcan.ANGLE_RATE_LIMIT_UPPER, bydcan.OEM_ANGLE_RATE_LIMIT) + self.assertGreater(bydcan.ANGLE_RATE_LIMIT_UPPER, 0) self.assertEqual(bydcan.SET_ME_FF_VALUE, 0x64) _, dat, _ = bydcan.create_steering_control(self.packer, 0.0, True) self.assertEqual(_unpack(DBC_NAME, "STEERING_MODULE_ADAS", dat)["SET_ME_FF"], 0x64)