Hyundai: HDA2 longitudinal control support (#1096)

* ev6 long

* update steering
This commit is contained in:
Adeeb Shihadeh
2022-10-11 20:50:23 -07:00
committed by GitHub
parent d68b1b0a98
commit ffb3109e28
7 changed files with 209 additions and 102 deletions

View File

@@ -48,7 +48,7 @@ ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}"
ENV PANDA_PATH=/tmp/openpilot/panda
ENV OPENPILOT_REF="96e8d5c9fe1a8084dfa5d97c78d4ea2037272420"
ENV OPENDBC_REF="3270c931c07bd3a47839a1a84c109eb2a7d295a6"
ENV OPENDBC_REF="04cc54d5e662aaf708f72cabb65507c7dbb5136d"
COPY requirements.txt /tmp/
RUN pyenv install 3.8.10 && \

View File

@@ -17,6 +17,22 @@ const CanMsg HYUNDAI_CANFD_HDA2_TX_MSGS[] = {
{0x2A4, 0, 24}, // CAM_0x2A4
};
const CanMsg HYUNDAI_CANFD_HDA2_LONG_TX_MSGS[] = {
{0x50, 0, 16}, // LKAS
{0x1CF, 1, 8}, // CRUISE_BUTTON
{0x2A4, 0, 24}, // CAM_0x2A4
{0x51, 0, 32}, // ADRV_0x51
{0x730, 1, 8}, // tester present for ADAS ECU disable
{0x12A, 1, 16}, // LFA
{0x160, 1, 16}, // ADRV_0x160
{0x1E0, 1, 16}, // LFAHDA_CLUSTER
{0x1A0, 1, 32}, // CRUISE_INFO
{0x1EA, 1, 32}, // ADRV_0x1ea
{0x200, 1, 8}, // ADRV_0x200
{0x345, 1, 8}, // ADRV_0x345
{0x1DA, 1, 32}, // ADRV_0x1da
};
const CanMsg HYUNDAI_CANFD_HDA1_TX_MSGS[] = {
{0x12A, 0, 16}, // LFA
{0x1A0, 0, 32}, // CRUISE_INFO
@@ -49,6 +65,7 @@ uint16_t hyundai_canfd_crc_lut[256];
const int HYUNDAI_PARAM_CANFD_HDA2 = 1;
const int HYUNDAI_PARAM_CANFD_ALT_BUTTONS = 2;
const int HYUNDAI_PARAM_CANFD_LONG = 4;
bool hyundai_canfd_hda2 = false;
bool hyundai_canfd_alt_buttons = false;
@@ -160,7 +177,12 @@ static int hyundai_canfd_rx_hook(CANPacket_t *to_push) {
}
const int steer_addr = hyundai_canfd_hda2 ? 0x50 : 0x12a;
generic_rx_checks((addr == steer_addr) && (bus == 0));
bool stock_ecu_detected = (addr == steer_addr) && (bus == 0);
if (hyundai_longitudinal) {
// ensure ADRV ECU is still knocked out
stock_ecu_detected = stock_ecu_detected || ((addr == 0x1a0) && (bus == 1));
}
generic_rx_checks(stock_ecu_detected);
return valid;
}
@@ -172,15 +194,16 @@ static int hyundai_canfd_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed
int addr = GET_ADDR(to_send);
int bus = GET_BUS(to_send);
if (hyundai_canfd_hda2) {
if (hyundai_canfd_hda2 && !hyundai_longitudinal) {
tx = msg_allowed(to_send, HYUNDAI_CANFD_HDA2_TX_MSGS, sizeof(HYUNDAI_CANFD_HDA2_TX_MSGS)/sizeof(HYUNDAI_CANFD_HDA2_TX_MSGS[0]));
} else if (hyundai_canfd_hda2 && hyundai_longitudinal) {
tx = msg_allowed(to_send, HYUNDAI_CANFD_HDA2_LONG_TX_MSGS, sizeof(HYUNDAI_CANFD_HDA2_LONG_TX_MSGS)/sizeof(HYUNDAI_CANFD_HDA2_LONG_TX_MSGS[0]));
} else {
tx = msg_allowed(to_send, HYUNDAI_CANFD_HDA1_TX_MSGS, sizeof(HYUNDAI_CANFD_HDA1_TX_MSGS)/sizeof(HYUNDAI_CANFD_HDA1_TX_MSGS[0]));
}
// steering
const int steer_addr = hyundai_canfd_hda2 ? 0x50 : 0x12a;
if ((addr == steer_addr) && (bus == 0)) {
if ((addr == 0x50) || (addr == 0x12a)) {
int desired_torque = ((GET_BYTE(to_send, 6) & 0xFU) << 7U) | (GET_BYTE(to_send, 5) >> 1U);
desired_torque -= 1024;
@@ -202,11 +225,44 @@ static int hyundai_canfd_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed
}
}
// UDS: only tester present ("\x02\x3E\x80\x00\x00\x00\x00\x00") allowed on diagnostics address
if (addr == 0x730) {
if ((GET_BYTES_04(to_send) != 0x00803E02U) || (GET_BYTES_48(to_send) != 0x0U)) {
tx = 0;
}
}
// ACCEL: safety check
if (addr == 0x1a0) {
int desired_accel_raw = (((GET_BYTE(to_send, 17) & 0x7U) << 8) | GET_BYTE(to_send, 16)) - 1023U;
int desired_accel_val = ((GET_BYTE(to_send, 18) << 4) | (GET_BYTE(to_send, 17) >> 4)) - 1023U;
bool violation = false;
if (hyundai_longitudinal) {
if (!longitudinal_allowed) {
if ((desired_accel_raw != 0) || (desired_accel_val != 0)) {
violation = true;
}
}
violation |= max_limit_check(desired_accel_raw, HYUNDAI_MAX_ACCEL, HYUNDAI_MIN_ACCEL);
violation |= max_limit_check(desired_accel_val, HYUNDAI_MAX_ACCEL, HYUNDAI_MIN_ACCEL);
} else {
// only used to cancel on here
if ((desired_accel_raw != 0) || (desired_accel_val != 0)) {
violation = true;
}
}
if (violation) {
tx = 0;
}
}
return tx;
}
static int hyundai_canfd_fwd_hook(int bus_num, CANPacket_t *to_fwd) {
int bus_fwd = -1;
int addr = GET_ADDR(to_fwd);
@@ -236,7 +292,11 @@ static const addr_checks* hyundai_canfd_init(uint16_t param) {
hyundai_canfd_hda2 = GET_FLAG(param, HYUNDAI_PARAM_CANFD_HDA2);
hyundai_canfd_alt_buttons = GET_FLAG(param, HYUNDAI_PARAM_CANFD_ALT_BUTTONS);
#ifdef ALLOW_DEBUG
hyundai_longitudinal = GET_FLAG(param, HYUNDAI_PARAM_CANFD_LONG) && hyundai_canfd_hda2;
#else
hyundai_longitudinal = false;
#endif
return &hyundai_canfd_rx_checks;
}

View File

@@ -216,6 +216,7 @@ class Panda:
FLAG_HYUNDAI_CANFD_HDA2 = 1
FLAG_HYUNDAI_CANFD_ALT_BUTTONS = 2
FLAG_HYUNDAI_CANFD_LONG = 4
FLAG_TESLA_POWERTRAIN = 1
FLAG_TESLA_LONG_CONTROL = 2

View File

@@ -278,7 +278,7 @@ class TorqueSteeringSafetyTestBase(PandaSafetyTestBase):
# Normally, sending MIN_VALID_STEERING_FRAMES valid frames should always allow
self.safety.set_timer(max(rt_us, 0))
should_tx = rt_us >= self.MIN_VALID_STEERING_RT_INTERVAL
for idx in range(self.MAX_INVALID_STEERING_FRAMES):
for _ in range(self.MAX_INVALID_STEERING_FRAMES):
self.assertEqual(should_tx, self._tx(self._torque_cmd_msg(self.MAX_TORQUE, steer_req=0)))
# Keep blocking after one steer_req mismatch
@@ -712,11 +712,16 @@ class PandaSafetyTest(PandaSafetyTestBase):
continue
if attr.startswith('TestHyundaiCanfd') and current_test.startswith('TestHyundaiCanfd'):
continue
# overlapping TX addrs, but they're not actuating messages for either car
if attr == 'TestHyundaiCanfdHDA2Long' and current_test.startswith('TestToyota'):
tx = list(filter(lambda m: m[0] not in [0x160, ], tx))
# TODO: Temporary, should be fixed in panda firmware, safety_honda.h
if attr.startswith('TestHonda'):
# exceptions for common msgs across different hondas
tx = list(filter(lambda m: m[0] not in [0x1FA, 0x30C, 0x33D], tx))
all_tx.append(list([m[0], m[1], attr[4:]] for m in tx))
all_tx.append(list([m[0], m[1], attr] for m in tx))
# make sure we got all the msgs
self.assertTrue(len(all_tx) >= len(test_files)-1)

View File

@@ -1,14 +1,24 @@
import numpy as np
from typing import Tuple
import panda.tests.safety.common as common
from panda.tests.safety.common import make_msg
class Buttons:
NONE = 0
RESUME = 1
SET = 2
CANCEL = 4
MAX_ACCEL = 2.0
MIN_ACCEL = -3.5
PREV_BUTTON_SAMPLES = 8
ENABLE_BUTTONS = (Buttons.RESUME, Buttons.SET, Buttons.CANCEL)
class HyundaiButtonBase: #(common.PandaSafetyTest):
class HyundaiButtonBase:
# pylint: disable=no-member,abstract-method
BUTTONS_BUS = 0 # tx on this bus, rx on 0. added to all `self._tx(self._button_msg(...))`
SCC_BUS = 0 # rx on this bus
@@ -62,4 +72,85 @@ class HyundaiButtonBase: #(common.PandaSafetyTest):
self._rx(self._pcm_status_msg(True))
controls_allowed = i < PREV_BUTTON_SAMPLES
self.assertEqual(controls_allowed, self.safety.get_controls_allowed())
self._rx(self._button_msg(Buttons.NONE))
self._rx(self._button_msg(Buttons.NONE))
class HyundaiLongitudinalBase:
# pylint: disable=no-member,abstract-method
DISABLED_ECU_UDS_MSG: Tuple[int, int]
DISABLED_ECU_ACTUATION_MSG: Tuple[int, int]
# override these tests from PandaSafetyTest, hyundai longitudinal uses button enable
def test_disable_control_allowed_from_cruise(self):
pass
def test_enable_control_allowed_from_cruise(self):
pass
def test_sampling_cruise_buttons(self):
pass
def test_cruise_engaged_prev(self):
pass
def test_button_sends(self):
pass
def _pcm_status_msg(self, enable):
raise Exception
def _accel_msg(self, accel, aeb_req=False, aeb_decel=0):
raise NotImplementedError
def test_set_resume_buttons(self):
"""
SET and RESUME enter controls allowed on their falling edge.
"""
for btn in range(8):
self.safety.set_controls_allowed(0)
for _ in range(10):
self._rx(self._button_msg(btn))
self.assertFalse(self.safety.get_controls_allowed())
# should enter controls allowed on falling edge
if btn in (Buttons.RESUME, Buttons.SET):
self._rx(self._button_msg(Buttons.NONE))
self.assertTrue(self.safety.get_controls_allowed())
def test_cancel_button(self):
self.safety.set_controls_allowed(1)
self._rx(self._button_msg(Buttons.CANCEL))
self.assertFalse(self.safety.get_controls_allowed())
def test_accel_safety_check(self):
for controls_allowed in [True, False]:
for accel in np.arange(MIN_ACCEL - 1, MAX_ACCEL + 1, 0.01):
accel = round(accel, 2) # floats might not hit exact boundary conditions without rounding
self.safety.set_controls_allowed(controls_allowed)
send = MIN_ACCEL <= accel <= MAX_ACCEL if controls_allowed else accel == 0
self.assertEqual(send, self._tx(self._accel_msg(accel)), (controls_allowed, accel))
def test_tester_present_allowed(self):
"""
Ensure tester present diagnostic message is allowed to keep ECU knocked out
for longitudinal control.
"""
addr, bus = self.DISABLED_ECU_UDS_MSG
tester_present = common.package_can_msg((addr, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", bus))
self.assertTrue(self.safety.safety_tx_hook(tester_present))
not_tester_present = common.package_can_msg((addr, 0, b"\x03\xAA\xAA\x00\x00\x00\x00\x00", bus))
self.assertFalse(self.safety.safety_tx_hook(not_tester_present))
def test_disabled_ecu_alive(self):
"""
If the ECU knockout failed, make sure the relay malfunction is shown
"""
addr, bus = self.DISABLED_ECU_ACTUATION_MSG
self.assertFalse(self.safety.get_relay_malfunction())
self._rx(make_msg(bus, addr, 8))
self.assertTrue(self.safety.get_relay_malfunction())

View File

@@ -1,25 +1,10 @@
#!/usr/bin/env python3
import unittest
import numpy as np
from panda import Panda
from panda.tests.safety import libpandasafety_py
import panda.tests.safety.common as common
from panda.tests.safety.common import CANPackerPanda, make_msg
from panda.tests.safety.hyundai_common import HyundaiButtonBase
MAX_ACCEL = 2.0
MIN_ACCEL = -3.5
class Buttons:
NONE = 0
RESUME = 1
SET = 2
CANCEL = 4
PREV_BUTTON_SAMPLES = 8
ENABLE_BUTTONS = (Buttons.RESUME, Buttons.SET, Buttons.CANCEL)
from panda.tests.safety.common import CANPackerPanda
from panda.tests.safety.hyundai_common import HyundaiButtonBase, HyundaiLongitudinalBase
# 4 bit checkusm used in some hyundai messages
@@ -57,9 +42,6 @@ def checksum(msg):
return addr, t, ret, bus
class TestHyundaiSafety(HyundaiButtonBase, common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest):
TX_MSGS = [[832, 0], [1265, 0], [1157, 0]]
STANDSTILL_THRESHOLD = 30 # ~1kph
@@ -173,35 +155,20 @@ class TestHyundaiLegacySafetyHEV(TestHyundaiSafety):
values = {"CR_Vcu_AccPedDep_Pos": gas}
return self.packer.make_can_msg_panda("E_EMS11", 0, values, fix_checksum=checksum)
class TestHyundaiLongitudinalSafety(TestHyundaiSafety):
class TestHyundaiLongitudinalSafety(HyundaiLongitudinalBase, TestHyundaiSafety):
TX_MSGS = [[832, 0], [1265, 0], [1157, 0], [1056, 0], [1057, 0], [1290, 0], [905, 0], [1186, 0], [909, 0], [1155, 0], [2000, 0]]
DISABLED_ECU_UDS_MSG = (2000, 0)
DISABLED_ECU_ACTUATION_MSG = (1057, 0)
def setUp(self):
self.packer = CANPackerPanda("hyundai_kia_generic")
self.safety = libpandasafety_py.libpandasafety
self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI, Panda.FLAG_HYUNDAI_LONG)
self.safety.init_tests()
# override these tests from PandaSafetyTest, hyundai longitudinal uses button enable
def test_disable_control_allowed_from_cruise(self):
pass
def test_enable_control_allowed_from_cruise(self):
pass
def test_sampling_cruise_buttons(self):
pass
def test_cruise_engaged_prev(self):
pass
def test_button_sends(self):
pass
def _pcm_status_msg(self, enable):
raise NotImplementedError
def _send_accel_msg(self, accel, aeb_req=False, aeb_decel=0):
def _accel_msg(self, accel, aeb_req=False, aeb_decel=0):
values = {
"aReqRaw": accel,
"aReqValue": accel,
@@ -210,7 +177,7 @@ class TestHyundaiLongitudinalSafety(TestHyundaiSafety):
}
return self.packer.make_can_msg_panda("SCC12", self.SCC_BUS, values)
def _send_fca11_msg(self, idx=0, vsm_aeb_req=False, fca_aeb_req=False, aeb_decel=0):
def _fca11_msg(self, idx=0, vsm_aeb_req=False, fca_aeb_req=False, aeb_decel=0):
values = {
"CR_FCA_Alive": ((-((idx % 0xF) + 2) % 4) << 2) + 1,
"Supplemental_Counter": idx % 0xF,
@@ -222,56 +189,15 @@ class TestHyundaiLongitudinalSafety(TestHyundaiSafety):
return self.packer.make_can_msg_panda("FCA11", 0, values)
def test_no_aeb_fca11(self):
self.assertTrue(self._tx(self._send_fca11_msg()))
self.assertFalse(self._tx(self._send_fca11_msg(vsm_aeb_req=True)))
self.assertFalse(self._tx(self._send_fca11_msg(fca_aeb_req=True)))
self.assertFalse(self._tx(self._send_fca11_msg(aeb_decel=1.0)))
self.assertTrue(self._tx(self._fca11_msg()))
self.assertFalse(self._tx(self._fca11_msg(vsm_aeb_req=True)))
self.assertFalse(self._tx(self._fca11_msg(fca_aeb_req=True)))
self.assertFalse(self._tx(self._fca11_msg(aeb_decel=1.0)))
def test_no_aeb_scc12(self):
self.assertTrue(self._tx(self._send_accel_msg(0)))
self.assertFalse(self._tx(self._send_accel_msg(0, aeb_req=True)))
self.assertFalse(self._tx(self._send_accel_msg(0, aeb_decel=1.0)))
def test_set_resume_buttons(self):
"""
SET and RESUME enter controls allowed on their falling edge.
"""
for btn in range(8):
self.safety.set_controls_allowed(0)
for _ in range(10):
self._rx(self._button_msg(btn))
self.assertFalse(self.safety.get_controls_allowed())
# should enter controls allowed on falling edge
if btn in (Buttons.RESUME, Buttons.SET):
self._rx(self._button_msg(Buttons.NONE))
self.assertTrue(self.safety.get_controls_allowed())
def test_cancel_button(self):
self.safety.set_controls_allowed(1)
self._rx(self._button_msg(Buttons.CANCEL))
self.assertFalse(self.safety.get_controls_allowed())
def test_accel_safety_check(self):
for controls_allowed in [True, False]:
for accel in np.arange(MIN_ACCEL - 1, MAX_ACCEL + 1, 0.01):
accel = round(accel, 2) # floats might not hit exact boundary conditions without rounding
self.safety.set_controls_allowed(controls_allowed)
send = MIN_ACCEL <= accel <= MAX_ACCEL if controls_allowed else accel == 0
self.assertEqual(send, self._tx(self._send_accel_msg(accel)), (controls_allowed, accel))
def test_diagnostics(self):
tester_present = common.package_can_msg((0x7d0, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", 0))
self.assertTrue(self.safety.safety_tx_hook(tester_present))
not_tester_present = common.package_can_msg((0x7d0, 0, b"\x03\xAA\xAA\x00\x00\x00\x00\x00", 0))
self.assertFalse(self.safety.safety_tx_hook(not_tester_present))
def test_radar_alive(self):
# If the radar knockout failed, make sure the relay malfunction is shown
self.assertFalse(self.safety.get_relay_malfunction())
self._rx(make_msg(0, 1057, 8))
self.assertTrue(self.safety.get_relay_malfunction())
self.assertTrue(self._tx(self._accel_msg(0)))
self.assertFalse(self._tx(self._accel_msg(0, aeb_req=True)))
self.assertFalse(self._tx(self._accel_msg(0, aeb_decel=1.0)))
if __name__ == "__main__":

View File

@@ -4,7 +4,7 @@ from panda import Panda
from panda.tests.safety import libpandasafety_py
import panda.tests.safety.common as common
from panda.tests.safety.common import CANPackerPanda
from panda.tests.safety.hyundai_common import HyundaiButtonBase
from panda.tests.safety.hyundai_common import HyundaiButtonBase, HyundaiLongitudinalBase
class TestHyundaiCanfdBase(HyundaiButtonBase, common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest):
@@ -26,6 +26,7 @@ class TestHyundaiCanfdBase(HyundaiButtonBase, common.PandaSafetyTest, common.Dri
DRIVER_TORQUE_FACTOR = 2
PT_BUS = 0
STEER_BUS = 0
STEER_MSG = ""
@classmethod
@@ -41,7 +42,7 @@ class TestHyundaiCanfdBase(HyundaiButtonBase, common.PandaSafetyTest, common.Dri
def _torque_cmd_msg(self, torque, steer_req=1):
values = {"TORQUE_REQUEST": torque}
return self.packer.make_can_msg_panda(self.STEER_MSG, 0, values)
return self.packer.make_can_msg_panda(self.STEER_MSG, self.STEER_BUS, values)
def _speed_msg(self, speed):
values = {f"WHEEL_SPEED_{i}": speed * 0.03125 for i in range(1, 5)}
@@ -130,6 +131,29 @@ class TestHyundaiCanfdHDA2(TestHyundaiCanfdBase):
self.safety.init_tests()
class TestHyundaiCanfdHDA2Long(HyundaiLongitudinalBase, TestHyundaiCanfdHDA2):
TX_MSGS = [[0x50, 0], [0x1CF, 1], [0x2A4, 0], [0x51, 0], [0x730, 1], [0x12a, 1], [0x160, 1],
[0x1e0, 1], [0x1a0, 1], [0x1ea, 1], [0x200, 1], [0x345, 1], [0x1da, 1]]
DISABLED_ECU_UDS_MSG = (0x730, 1)
DISABLED_ECU_ACTUATION_MSG = (0x1a0, 1)
STEER_MSG = "LFA"
STEER_BUS = 1
def setUp(self):
self.packer = CANPackerPanda("hyundai_canfd")
self.safety = libpandasafety_py.libpandasafety
self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, Panda.FLAG_HYUNDAI_CANFD_HDA2 | Panda.FLAG_HYUNDAI_CANFD_LONG)
self.safety.init_tests()
def _accel_msg(self, accel, aeb_req=False, aeb_decel=0):
values = {
"ACCEL_REQ": accel,
"ACCEL_REQ2": accel,
}
return self.packer.make_can_msg_panda("CRUISE_INFO", 1, values)
if __name__ == "__main__":