diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 623a67e2..31ebf6ec 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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 diff --git a/README.md b/README.md index 188c6e02..89521756 100644 --- a/README.md +++ b/README.md @@ -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 <VDM_EpasPowerMode + ignition_can = ((GET_BYTE(to_push, 7) >> 4U) & 0x3U) == 1U; // VDM_EpasPowerMode_Drive_On=1 ignition_can_cnt = 0U; } prev_counter = counter; diff --git a/board/drivers/usb.h b/board/drivers/usb.h index cfc9c4fb..8fd3d8c4 100644 --- a/board/drivers/usb.h +++ b/board/drivers/usb.h @@ -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 diff --git a/board/drivers/usb_declarations.h b/board/drivers/usb_declarations.h index 77f6f5e8..379b162f 100644 --- a/board/drivers/usb_declarations.h +++ b/board/drivers/usb_declarations.h @@ -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); diff --git a/board/jungle/main.c b/board/jungle/main.c index 6cbc160c..6b660f6c 100644 --- a/board/jungle/main.c +++ b/board/jungle/main.c @@ -60,9 +60,6 @@ void tick_handler(void) { } } - // tick drivers at 8Hz - usb_tick(); - // decimated to 1Hz if ((loop_counter % 8) == 0U) { #ifdef DEBUG diff --git a/board/main.c b/board/main.c index 7c068113..e63aa32a 100644 --- a/board/main.c +++ b/board/main.c @@ -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); } } diff --git a/board/stm32h7/sound.h b/board/stm32h7/sound.h index 8424413e..de35a123 100644 --- a/board/stm32h7/sound.h +++ b/board/stm32h7/sound.h @@ -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; diff --git a/python/__init__.py b/python/__init__.py index 84f99815..1639e07f 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -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'') diff --git a/tests/safety_replay/.gitignore b/tests/safety_replay/.gitignore deleted file mode 100644 index 192fb094..00000000 --- a/tests/safety_replay/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.bz2 diff --git a/tests/safety_replay/__init__.py b/tests/safety_replay/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/safety_replay/helpers.py b/tests/safety_replay/helpers.py deleted file mode 100644 index e8dac688..00000000 --- a/tests/safety_replay/helpers.py +++ /dev/null @@ -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" diff --git a/tests/safety_replay/replay_drive.py b/tests/safety_replay/replay_drive.py deleted file mode 100755 index 9c54e02f..00000000 --- a/tests/safety_replay/replay_drive.py +++ /dev/null @@ -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)