Merge branch 'master-upstream-1d5b89956b32bbda2940724ce70c5166e44668c5' into sync-20250309

# Conflicts:
#	tests/safety_replay/replay_drive.py
This commit is contained in:
DevTekVE
2025-03-09 14:36:40 +01:00
13 changed files with 29 additions and 280 deletions

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v5.0.0
hooks:
- id: check-ast
- id: check-yaml
@@ -9,12 +9,12 @@ repos:
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.2
rev: v1.15.0
hooks:
- id: mypy
additional_dependencies: ['numpy', 'types-requests', 'types-atomicwrites',
'types-pycurl']
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.8
rev: v0.9.9
hooks:
- id: ruff

View File

@@ -82,8 +82,11 @@ And to send one on bus 0:
Note that you may have to setup [udev rules](https://github.com/commaai/panda/tree/master/drivers/linux) for Linux, such as
``` bash
sudo tee /etc/udev/rules.d/11-panda.rules <<EOF
SUBSYSTEM=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddcc", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddee", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddcc", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddee", MODE="0666"
EOF
sudo udevadm control --reload-rules && sudo udevadm trigger
```

View File

@@ -182,8 +182,8 @@ void ignition_can_hook(CANPacket_t *to_push) {
int counter = GET_BYTE(to_push, 1) & 0xFU; // max is only 14
if ((counter == ((prev_counter + 1) % 15)) && (prev_counter != -1)) {
// VDM_OutputSignals
ignition_can = GET_BIT(to_push, 60U);
// VDM_OutputSignals->VDM_EpasPowerMode
ignition_can = ((GET_BYTE(to_push, 7) >> 4U) & 0x3U) == 1U; // VDM_EpasPowerMode_Drive_On=1
ignition_can_cnt = 0U;
}
prev_counter = counter;

View File

@@ -1,7 +1,5 @@
#include "usb_declarations.h"
bool usb_enumerated = false;
static uint8_t response[USBPACKET_MAX_SIZE];
// current packet
@@ -124,13 +122,6 @@ static char to_hex_char(uint8_t a) {
return ret;
}
void usb_tick(void) {
static uint16_t usb_last_frame_num = 0U;
uint16_t current_frame_num = (USBx_DEVICE->DSTS & USB_OTG_DSTS_FNSOF_Msk) >> USB_OTG_DSTS_FNSOF_Pos;
usb_enumerated = (current_frame_num != usb_last_frame_num);
usb_last_frame_num = current_frame_num;
}
static void usb_setup(void) {
static uint8_t device_desc[] = {
DSCR_DEVICE_LEN, USB_DESC_TYPE_DEVICE, //Length, Type

View File

@@ -23,8 +23,6 @@ typedef union _USB_Setup {
} b;
} USB_Setup_TypeDef;
extern bool usb_enumerated;
void usb_init(void);
void refresh_can_tx_slots_available(void);
@@ -109,7 +107,5 @@ void refresh_can_tx_slots_available(void);
#define ENDPOINT_RCV 0x80
#define ENDPOINT_SND 0x00
// packet read and write
void usb_tick(void);
// ***************************** USB port *****************************
void can_tx_comms_resume_usb(void);

View File

@@ -60,9 +60,6 @@ void tick_handler(void) {
}
}
// tick drivers at 8Hz
usb_tick();
// decimated to 1Hz
if ((loop_counter % 8) == 0U) {
#ifdef DEBUG

View File

@@ -155,7 +155,6 @@ static void tick_handler(void) {
// tick drivers at 8Hz
fan_tick();
usb_tick();
harness_tick();
simple_watchdog_kick();
sound_tick();
@@ -266,11 +265,9 @@ static void tick_handler(void) {
// Also disable IR when the heartbeat goes missing
current_board->set_ir_power(0U);
// Run fan when device is up, but not talking to us
// * bootloader enables the SOM GPIO on boot
// * fallback to USB enumerated where supported
bool enabled = usb_enumerated || current_board->read_som_gpio();
fan_set_power(enabled ? 50U : 0U);
// Run fan when device is up but not talking to us.
// The bootloader enables the SOM GPIO on boot.
fan_set_power(current_board->read_som_gpio() ? 30U : 0U);
}
}

View File

@@ -49,16 +49,26 @@ static void DMA1_Stream0_IRQ_Handler(void) {
// Playback processing
static void BDMA_Channel0_IRQ_Handler(void) {
static uint8_t playback_buf = 0U;
BDMA->IFCR |= BDMA_IFCR_CGIF0; // clear flag
uint8_t rx_buf_idx = (((BDMA_Channel0->CCR & BDMA_CCR_CT) >> BDMA_CCR_CT_Pos) == 1U) ? 0U : 1U;
uint8_t tx_buf_idx = (((DMA1_Stream1->CR & DMA_SxCR_CT) >> DMA_SxCR_CT_Pos) == 1U) ? 0U : 1U;
playback_buf = 1U - playback_buf;
// wait until we're done playing the current buf
for (uint32_t timeout_counter = 100000U; timeout_counter > 0U; timeout_counter--){
uint8_t playing_buf = (DMA1_Stream1->CR & DMA_SxCR_CT) >> DMA_SxCR_CT_Pos;
if ((playing_buf != playback_buf) || (!(DMA1_Stream1->CR & DMA_SxCR_EN))) {
break;
}
}
// process samples (shift to 12b and bias to be unsigned)
bool sound_playing = false;
for (uint16_t i=0U; i < SOUND_RX_BUF_SIZE; i += 2U) {
// since we are playing mono and receiving stereo, we take every other sample
sound_tx_buf[tx_buf_idx][i/2U] = ((sound_rx_buf[rx_buf_idx][i] + (1UL << 14)) >> 3);
sound_tx_buf[playback_buf][i/2U] = ((sound_rx_buf[rx_buf_idx][i] + (1UL << 14)) >> 3);
if (sound_rx_buf[rx_buf_idx][i] > 0U) {
sound_playing = true;
}
@@ -71,9 +81,10 @@ static void BDMA_Channel0_IRQ_Handler(void) {
// empty the other buf and start playing that
for (uint16_t i=0U; i < SOUND_TX_BUF_SIZE; i++) {
sound_tx_buf[1U - tx_buf_idx][i] = (1UL << 11);
sound_tx_buf[1U - playback_buf][i] = (1UL << 11);
}
register_set(&DMA1_Stream1->CR, (1UL - tx_buf_idx) << DMA_SxCR_CT_Pos, DMA_SxCR_CT_Msk);
register_clear_bits(&DMA1_Stream1->CR, DMA_SxCR_EN);
register_set(&DMA1_Stream1->CR, (1UL - playback_buf) << DMA_SxCR_CT_Pos, DMA_SxCR_CT_Msk);
register_set_bits(&DMA1_Stream1->CR, DMA_SxCR_EN);
}
sound_idle_count = SOUND_IDLE_TIMEOUT;

View File

@@ -710,6 +710,9 @@ class Panda:
# ******************* configuration *******************
def set_alternative_experience(self, alternative_experience):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xdf, int(alternative_experience), 0, b'')
def set_power_save(self, power_save_enabled=0):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe7, int(power_save_enabled), 0, b'')

View File

@@ -1 +0,0 @@
*.bz2

View File

@@ -1,85 +0,0 @@
from opendbc.car.toyota.values import ToyotaSafetyFlags
from opendbc.car.structs import CarParams
from opendbc.safety.tests.libsafety import libsafety_py
def to_signed(d, bits):
ret = d
if d >= (1 << (bits - 1)):
ret = d - (1 << bits)
return ret
def is_steering_msg(mode, param, addr):
ret = False
if mode in (CarParams.SafetyModel.hondaNidec, CarParams.SafetyModel.hondaBosch):
ret = (addr == 0xE4) or (addr == 0x194) or (addr == 0x33D) or (addr == 0x33DA) or (addr == 0x33DB)
elif mode == CarParams.SafetyModel.toyota:
ret = addr == (0x191 if param & ToyotaSafetyFlags.LTA else 0x2E4)
elif mode == CarParams.SafetyModel.gm:
ret = addr == 384
elif mode == CarParams.SafetyModel.hyundai:
ret = addr == 832
elif mode == CarParams.SafetyModel.chrysler:
ret = addr == 0x292
elif mode == CarParams.SafetyModel.subaru:
ret = addr == 0x122
elif mode == CarParams.SafetyModel.ford:
ret = addr == 0x3d3
elif mode == CarParams.SafetyModel.nissan:
ret = addr == 0x169
elif mode == CarParams.SafetyModel.rivian:
ret = addr == 0x120
return ret
def get_steer_value(mode, param, to_send):
torque, angle = 0, 0
if mode in (CarParams.SafetyModel.hondaNidec, CarParams.SafetyModel.hondaBosch):
torque = (to_send.data[0] << 8) | to_send.data[1]
torque = to_signed(torque, 16)
elif mode == CarParams.SafetyModel.toyota:
if param & ToyotaSafetyFlags.LTA:
angle = (to_send.data[1] << 8) | to_send.data[2]
angle = to_signed(angle, 16)
else:
torque = (to_send.data[1] << 8) | (to_send.data[2])
torque = to_signed(torque, 16)
elif mode == CarParams.SafetyModel.gm:
torque = ((to_send.data[0] & 0x7) << 8) | to_send.data[1]
torque = to_signed(torque, 11)
elif mode == CarParams.SafetyModel.hyundai:
torque = (((to_send.data[3] & 0x7) << 8) | to_send.data[2]) - 1024
elif mode == CarParams.SafetyModel.chrysler:
torque = (((to_send.data[0] & 0x7) << 8) | to_send.data[1]) - 1024
elif mode == CarParams.SafetyModel.subaru:
torque = ((to_send.data[3] & 0x1F) << 8) | to_send.data[2]
torque = -to_signed(torque, 13)
elif mode == CarParams.SafetyModel.ford:
angle = ((to_send.data[0] << 3) | (to_send.data[1] >> 5)) - 1000
elif mode == CarParams.SafetyModel.nissan:
angle = (to_send.data[0] << 10) | (to_send.data[1] << 2) | (to_send.data[2] >> 6)
angle = -angle + (1310 * 100)
elif mode == CarParams.SafetyModel.rivian:
torque = ((to_send.data[2] << 3) | (to_send.data[3] >> 5)) - 1024
return torque, angle
def package_can_msg(msg):
return libsafety_py.make_CANPacket(msg.address, msg.src % 4, msg.dat)
def init_segment(safety, lr, mode, param):
sendcan = (msg for msg in lr if msg.which() == 'sendcan')
steering_msgs = (can for msg in sendcan for can in msg.sendcan if is_steering_msg(mode, param, can.address))
msg = next(steering_msgs, None)
if msg is None:
# no steering msgs
return
to_send = package_can_msg(msg)
torque, angle = get_steer_value(mode, param, to_send)
if torque != 0:
safety.set_controls_allowed(1)
safety.set_desired_torque_last(torque)
elif angle != 0:
safety.set_controls_allowed(1)
safety.set_desired_angle_last(angle)
safety.set_angle_meas(angle, angle)
assert safety.safety_tx_hook(to_send), "failed to initialize panda safety for segment"

View File

@@ -1,163 +0,0 @@
#!/usr/bin/env python3
import argparse
import os
from collections import Counter, defaultdict
from opendbc.safety.tests.libsafety import libsafety_py
from opendbc.safety import ALTERNATIVE_EXPERIENCE
from panda.tests.safety_replay.helpers import package_can_msg, init_segment
# Define debug variables and their getter methods
DEBUG_VARS = {
'lat_active': lambda safety: safety.get_lat_active(),
'controls_allowed': lambda safety: safety.get_controls_allowed(),
'controls_requested_lat': lambda safety: safety.get_controls_requested_lat(),
'controls_allowed_lat': lambda safety: safety.get_controls_allowed_lat(),
'current_disengage_reason': lambda safety: safety.mads_get_current_disengage_reason(),
'stock_acc_main': lambda safety: safety.get_acc_main_on(),
'mads_acc_main': lambda safety: safety.get_mads_acc_main(),
}
# replay a drive to check for safety violations
def replay_drive(lr, safety_mode, param, alternative_experience, segment=False):
safety = libsafety_py.libsafety
err = safety.set_safety_hooks(safety_mode, param)
assert err == 0, "invalid safety mode: %d" % safety_mode
safety.set_alternative_experience(alternative_experience)
_enable_mads = bool(alternative_experience & ALTERNATIVE_EXPERIENCE.ENABLE_MADS)
_disengage_lateral_on_brake = bool(alternative_experience & ALTERNATIVE_EXPERIENCE.DISENGAGE_LATERAL_ON_BRAKE)
safety.set_mads_params(_enable_mads, _disengage_lateral_on_brake)
print("alternative experience:")
print(f" enable mads: {_enable_mads}")
print(f" disengage lateral on brake: {_disengage_lateral_on_brake}")
if segment:
init_segment(safety, lr, safety_mode, param)
lr.reset()
rx_tot, rx_invalid, tx_tot, tx_blocked, tx_controls, tx_controls_lat, tx_controls_blocked, tx_controls_lat_blocked, mads_mismatch = 0, 0, 0, 0, 0, 0, 0, 0, 0
safety_tick_rx_invalid = False
blocked_addrs = Counter()
invalid_addrs = set()
# Track last good state for each address
last_good_states = defaultdict(lambda: {
'timestamp': None,
**{var: None for var in DEBUG_VARS}
})
can_msgs = [m for m in lr if m.which() in ('can', 'sendcan')]
start_t = can_msgs[0].logMonoTime
end_t = can_msgs[-1].logMonoTime
for msg in can_msgs:
safety.set_timer((msg.logMonoTime // 1000) % 0xFFFFFFFF)
# skip start and end of route, warm up/down period
if msg.logMonoTime - start_t > 1e9 and end_t - msg.logMonoTime > 1e9:
safety.safety_tick_current_safety_config()
safety_tick_rx_invalid |= not safety.safety_config_valid() or safety_tick_rx_invalid
if msg.which() == 'sendcan':
for canmsg in msg.sendcan:
to_send = package_can_msg(canmsg)
sent = safety.safety_tx_hook(to_send)
# mismatched
if (safety.get_controls_allowed() and not safety.get_controls_allowed_lat()):
mads_mismatch += 1
print(f"controls allowed but not controls allowed lat [{mads_mismatch}]")
print(f"msg:{canmsg.address} ({hex(canmsg.address)})")
for var, getter in DEBUG_VARS.items():
print(f" {var}: {getter(safety)}")
if not sent:
tx_blocked += 1
tx_controls_blocked += safety.get_controls_allowed()
tx_controls_lat_blocked += safety.get_controls_allowed_lat()
blocked_addrs[canmsg.address] += 1
if "DEBUG" in os.environ:
last_good = last_good_states[canmsg.address]
print(f"\nBlocked message at {(msg.logMonoTime - start_t) / 1e9:.3f}s:")
print(f"Address: {hex(canmsg.address)} (bus {canmsg.src})")
print("Current state:")
for var, getter in DEBUG_VARS.items():
print(f" {var}: {getter(safety)}")
if last_good['timestamp'] is not None:
print(f"\nLast good state ({last_good['timestamp']:.3f}s):")
for var in DEBUG_VARS:
print(f" {var}: {last_good[var]}")
else:
print("\nNo previous good state found for this address")
print("-" * 80)
else: # Update last good state if message is allowed
last_good_states[canmsg.address].update({
'timestamp': (msg.logMonoTime - start_t) / 1e9,
**{var: getter(safety) for var, getter in DEBUG_VARS.items()}
})
tx_controls += safety.get_controls_allowed()
tx_controls_lat += safety.get_controls_allowed_lat()
tx_tot += 1
elif msg.which() == 'can':
# ignore msgs we sent
for canmsg in filter(lambda m: m.src < 128, msg.can):
to_push = package_can_msg(canmsg)
recv = safety.safety_rx_hook(to_push)
if not recv:
rx_invalid += 1
invalid_addrs.add(canmsg.address)
rx_tot += 1
print("\nRX")
print("total rx msgs:", rx_tot)
print("invalid rx msgs:", rx_invalid)
print("safety tick rx invalid:", safety_tick_rx_invalid)
print("invalid addrs:", invalid_addrs)
print("\nTX")
print("total openpilot msgs:", tx_tot)
print("total msgs with controls allowed:", tx_controls)
print("total msgs with controls_lat allowed:", tx_controls_lat)
print("blocked msgs:", tx_blocked)
print("blocked with controls allowed:", tx_controls_blocked)
print("blocked with controls_lat allowed:", tx_controls_lat_blocked)
print("blocked addrs:", blocked_addrs)
print("mads enabled:", safety.get_enable_mads())
return tx_controls_blocked == 0 and tx_controls_lat_blocked == 0 and rx_invalid == 0 and not safety_tick_rx_invalid
if __name__ == "__main__":
from openpilot.tools.lib.logreader import LogReader
parser = argparse.ArgumentParser(description="Replay CAN messages from a route or segment through a safety mode",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("route_or_segment_name", nargs='+')
parser.add_argument("--mode", type=int, help="Override the safety mode from the log")
parser.add_argument("--param", type=int, help="Override the safety param from the log")
parser.add_argument("--alternative-experience", type=int, help="Override the alternative experience from the log")
args = parser.parse_args()
lr = LogReader(args.route_or_segment_name[0], sort_by_time=True)
if None in (args.mode, args.param, args.alternative_experience):
for msg in lr:
if msg.which() == 'carParams':
if args.mode is None:
args.mode = msg.carParams.safetyConfigs[-1].safetyModel.raw
if args.param is None:
args.param = msg.carParams.safetyConfigs[-1].safetyParam
if args.alternative_experience is None:
args.alternative_experience = msg.carParams.alternativeExperience
break
else:
raise Exception("carParams not found in log. Set safety mode and param manually.")
lr.reset()
print(f"replaying {args.route_or_segment_name[0]} with safety mode {args.mode}, param {args.param}, alternative experience {args.alternative_experience}")
replay_drive(lr, args.mode, args.param, args.alternative_experience, segment=len(lr.logreader_identifiers) == 1)