Sync: commaai/panda:mastersunnypilot/panda:master

This commit is contained in:
Jason Wen
2026-03-17 17:48:30 -04:00
committed by GitHub
30 changed files with 111 additions and 357 deletions

View File

@@ -23,14 +23,10 @@ jobs:
- run: ./setup.sh
- name: Test python package installer
run: ${{ env.RUN }} "pip install --break-system-packages .[dev]"
- name: Build panda images and bootstub
run: ${{ env.RUN }} "scons -j4"
- name: Build with UBSan
run: ${{ env.RUN }} "scons -j4 --ubsan"
- name: Build jungle firmware with FINAL_PROVISIONING support
run: ${{ env.RUN }} "FINAL_PROVISIONING=1 scons -j4 board/jungle"
- name: Build panda in release mode
run: ${{ env.RUN }} "CERT=certs/debug RELEASE=1 scons -j4"
- name: Build debug FW
run: ${{ env.RUN }} "scons"
- name: Build release FW
run: ${{ env.RUN }} "CERT=board/certs/debug RELEASE=1 scons"
test:
name: ./test.sh
@@ -58,28 +54,3 @@ jobs:
run: uv pip install --system .
- name: Verify importing panda
run: python -c "from panda import Panda"
misra_linter:
name: MISRA C:2012 Linter
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- run: ./setup.sh
- name: Build FW
run: ${{ env.RUN }} "scons -j$(nproc)"
- name: Run MISRA C:2012 analysis
run: ${{ env.RUN }} "cd tests/misra && ./test_misra.sh"
misra_mutation:
name: MISRA C:2012 Mutation
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- run: ./setup.sh
- name: Build FW
run: ${{ env.RUN }} "scons -j$(nproc)"
- name: MISRA mutation tests
run: ${{ env.RUN }} "cd tests/misra && pytest test_mutation.py"

15
Jenkinsfile vendored
View File

@@ -6,7 +6,7 @@ def docker_run(String step_label, int timeout_mins, String cmd) {
--volume /var/run/dbus:/var/run/dbus \
--net host \
${env.DOCKER_IMAGE_TAG} \
bash -c 'scons -j8 && ${cmd}'", \
bash -c 'scons && ${cmd}'", \
label: step_label
}
}
@@ -111,7 +111,7 @@ pipeline {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("panda-cuatro", [
["build", "scons -j4"],
["build", "scons"],
["flash", "cd scripts/ && ./reflash_internal_panda.py"],
["flash jungle", "cd board/jungle && ./flash.py --all"],
["test", "cd tests/hitl && pytest --durations=0 2*.py [5-9]*.py"],
@@ -123,7 +123,7 @@ pipeline {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("panda-tres", [
["build", "scons -j4"],
["build", "scons"],
["flash", "cd scripts/ && ./reflash_internal_panda.py"],
["flash jungle", "cd board/jungle && ./flash.py --all"],
["test", "cd tests/hitl && pytest --durations=0 2*.py [5-9]*.py"],
@@ -131,15 +131,6 @@ pipeline {
}
}
/*
stage('bootkick tests') {
steps {
script {
docker_run("test", 10, "pytest ./tests/som/test_bootkick.py")
}
}
}
*/
}
}
}

View File

@@ -7,7 +7,6 @@ panda speaks CAN and CAN FD, and it runs on the [STM32H725](https://www.st.com/r
```
.
├── board # Code that runs on the STM32
├── drivers # Drivers (not needed for use with Python)
├── python # Python userspace library for interfacing with the panda
├── tests # Tests for panda
├── scripts # Miscellaneous used for panda development and debugging
@@ -16,7 +15,7 @@ panda speaks CAN and CAN FD, and it runs on the [STM32H725](https://www.st.com/r
## Safety Model
panda is compiled with safety firmware provided by [opendbc](https://github.com/commaai/opendbc). See details about the car safety models, safety testing, and code rigor in that repository.
panda is compiled with vehicle-specific safety logic provided by [opendbc](https://github.com/commaai/opendbc). See details about the car safety models, safety testing, and code rigor in that repository.
## Code Rigor
@@ -26,19 +25,18 @@ The panda firmware is written for its use in conjunction with [openpilot](https:
These are the [CI regression tests](https://github.com/commaai/panda/actions) we have in place:
* A generic static code analysis is performed by [cppcheck](https://github.com/danmar/cppcheck/).
* In addition, [cppcheck](https://github.com/danmar/cppcheck/) has a specific addon to check for [MISRA C:2012](https://misra.org.uk/) violations. See [current coverage](https://github.com/commaai/panda/blob/master/tests/misra/coverage_table).
* Compiler options are relatively strict: the flags `-Wall -Wextra -Wstrict-prototypes -Werror` are enforced.
* Compiler options are strict: the flags `-Wall -Wextra -Wstrict-prototypes -Werror` are enforced.
* The [safety logic](https://github.com/commaai/panda/tree/master/opendbc/safety) is tested and verified by [unit tests](https://github.com/commaai/panda/tree/master/opendbc/safety/tests) for each supported car variant.
to ensure that the behavior remains unchanged.
* A hardware-in-the-loop test verifies panda's functionalities on all active panda variants, including:
* additional safety model checks
* compiling and flashing the bootstub and app code
* receiving, sending, and forwarding CAN messages on all buses
* CAN loopback and latency tests through USB and SPI
* CAN loopback and latency tests through SPI
The above tests are themselves tested by:
* a [mutation test](tests/misra/test_mutation.py) on the MISRA coverage
In addition, we run the [ruff linter](https://github.com/astral-sh/ruff) and [mypy](https://mypy-lang.org/) on panda's Python library.
* a [mutation test]([tests/misra/test_mutation.py](https://github.com/commaai/opendbc/blob/master/opendbc/safety/tests/mutation.sh)) on the vehicle-specific safety logic
## Usage
@@ -81,11 +79,6 @@ sudo udevadm control --reload-rules && sudo udevadm trigger
The panda jungle uses different udev rules. See [the repo](https://github.com/commaai/panda_jungle#udev-rules) for instructions.
## Software interface support
- [Python library](https://github.com/commaai/panda/tree/master/python)
- [C++ library](https://github.com/commaai/openpilot/tree/master/selfdrive/pandad)
## Licensing
panda software is released under the MIT license unless otherwise specified.

View File

@@ -14,7 +14,7 @@ if os.getenv("RELEASE"):
assert os.path.exists(cert_fn), 'Certificate file not found. Please specify absolute path'
else:
BUILD_TYPE = "DEBUG"
cert_fn = File("./certs/debug").srcnode().relpath
cert_fn = File("./board/certs/debug").srcnode().relpath
common_flags += ["-DALLOW_DEBUG"]
if os.getenv("DEBUG"):
@@ -33,7 +33,7 @@ def get_version(builder, build_type):
def get_key_header(name):
from Crypto.PublicKey import RSA
public_fn = File(f'./certs/{name}.pub').srcnode().get_path()
public_fn = File(f'./board/certs/{name}.pub').srcnode().get_path()
with open(public_fn) as f:
rsa = RSA.importKey(f.read())
assert(rsa.size_in_bits() == 1024)
@@ -105,8 +105,8 @@ def build_project(project_name, project, main, extra_flags):
bs_env.Append(CFLAGS="-DBOOTSTUB", ASFLAGS="-DBOOTSTUB", LINKFLAGS="-DBOOTSTUB")
bs_elf = bs_env.Program(f"{project_dir}/bootstub.elf", [
startup,
"./crypto/rsa.c",
"./crypto/sha.c",
"./board/crypto/rsa.c",
"./board/crypto/sha.c",
"./board/bootstub.c",
])
bs_env.Objcopy(f"./board/obj/bootstub.{project_name}.bin", bs_elf)
@@ -117,7 +117,7 @@ def build_project(project_name, project, main, extra_flags):
main
], LINKFLAGS=[f"-Wl,--section-start,.isr_vector={project['APP_START_ADDRESS']}"] + flags)
main_bin = env.Objcopy(f"{project_dir}/main.bin", main_elf)
sign_py = File(f"./crypto/sign.py").srcnode().relpath
sign_py = File(f"./board/crypto/sign.py").srcnode().relpath
env.Command(f"./board/obj/{project_name}.bin.signed", main_bin, f"SETLEN=1 {sign_py} $SOURCE $TARGET {cert_fn}")
@@ -157,13 +157,10 @@ build_project("panda_h7", base_project_h7, "./board/main.c", [])
flags = [
"-DPANDA_JUNGLE",
]
if os.getenv("FINAL_PROVISIONING"):
flags += ["-DFINAL_PROVISIONING"]
build_project("panda_jungle_h7", base_project_h7, "./board/jungle/main.c", flags)
# body fw
build_project("body_h7", base_project_h7, "./board/body/main.c", ["-DPANDA_BODY"])
# test files
if GetOption('extras'):
SConscript('tests/libpanda/SConscript')
SConscript('tests/libpanda/SConscript')

View File

@@ -1,18 +1,12 @@
AddOption('--minimal',
action='store_false',
dest='extras',
default=True,
help='the minimum build. no tests, tools, etc.')
AddOption('--ubsan',
action='store_true',
help='turn on UBSan')
import os
env = Environment(
COMPILATIONDB_USE_ABSPATH=True,
tools=["default", "compilation_db"],
)
SetOption('num_jobs', max(1, int((os.cpu_count() or 1)-1)))
env.CompilationDatabase("compile_commands.json")
# panda fw & test files

View File

@@ -11,8 +11,8 @@
#include "board/early_init.h"
#include "board/provision.h"
#include "crypto/rsa.h"
#include "crypto/sha.h"
#include "board/crypto/rsa.h"
#include "board/crypto/sha.h"
#include "board/obj/cert.h"
#include "board/obj/gitversion.h"

View File

@@ -3,6 +3,28 @@
a certain number of CANPacket_t. The transaction is split
into multiple transfers or chunks.
CAN packet byte layout (wire format used by comms_can_{read,write}):
+--------+--------+--------+--------+--------+--------+--------+------------------------------+
| byte 0 | byte 1 | byte 2 | byte 3 | byte 4 | byte 5 | byte 6 | ... byte 13 / byte 69 |
+--------+--------+--------+--------+--------+--------+--------+------------------------------+
| DLC | addr | addr | addr | flags | cksum | data0 | ... data7 / data63 |
| bus | | | | | | | (classic CAN / CAN FD) |
| fd | | | | | | | |
+--------+--------+--------+--------+--------+--------+--------+------------------------------+
Byte/bit fields:
byte 0: DLC[7:4], bus[3:1], fd[0]
bytes 1..4: (addr << 3) | (extended << 2) | (returned << 1) | rejected
byte 5: checksum = XOR(header[0..4] + payload)
bytes 6..13 (classic CAN, up to 8 bytes) / bytes 6..69 (CAN FD, up to 64 bytes): payload
USB/SPI transfer chunking used by this file:
+--------------------------------------------+ ... +--------------------------------------------+
| transport chunk 0 | | transport chunk N |
+--------------------------------------------+ +--------------------------------------------+
| concatenated CANPacket_t bytes | | continuation and/or next CANPacket_t bytes |
| (no per-64-byte counter/header in protocol)| | |
+--------------------------------------------+ +--------------------------------------------+
* comms_can_read outputs this buffer in chunks of a specified length.
chunks are always the given length, except the last one.
* comms_can_write reads in this buffer in chunks.

View File

@@ -7,9 +7,10 @@ void clock_source_set_timer_params(uint16_t param1, uint16_t param2) {
// Pulse length of each channel
register_set(&(TIM1->CCR1), (((param1 & 0xFF00U) >> 8U)*10U), 0xFFFFU);
register_set(&(TIM1->CCR2), ((param1 & 0x00FFU)*10U), 0xFFFFU);
register_set(&(TIM1->CCR3), (((param2 & 0xFF00U) >> 8U)*10U), 0xFFFFU);
register_set(&(TIM8->CCR3), (((param2 & 0xFF00U) >> 8U)*10U), 0xFFFFU);
// Timer period
register_set(&(TIM1->ARR), (((param2 & 0x00FFU)*10U) - 1U), 0xFFFFU);
register_set(&(TIM1->CCR4), ((TIM1->ARR + 1U) / 2U), 0xFFFFU);
}
void clock_source_init(bool enable_channel1) {
@@ -17,12 +18,12 @@ void clock_source_init(bool enable_channel1) {
register_set(&(TIM1->PSC), ((APB2_TIMER_FREQ*100U)-1U), 0xFFFFU); // Tick on 0.1 ms
register_set(&(TIM1->ARR), ((CLOCK_SOURCE_PERIOD_MS*10U) - 1U), 0xFFFFU); // Period
register_set(&(TIM1->CCMR1), 0U, 0xFFFFU); // No output on compare
register_set(&(TIM1->CCER), TIM_CCER_CC1E, 0xFFFFU); // Enable compare 1
register_set(&(TIM1->CCER), TIM_CCER_CC1E | TIM_CCER_CC2NE, 0xFFFFU); // Enable compares
register_set(&(TIM1->CCR1), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value
register_set(&(TIM1->CCR2), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value
register_set(&(TIM1->CCR3), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value
register_set(&(TIM1->CCR2), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 2 value
register_set(&(TIM1->CCR4), (CLOCK_SOURCE_PERIOD_MS*5U), 0xFFFFU); // For slave timer
register_set_bits(&(TIM1->DIER), TIM_DIER_UIE | TIM_DIER_CC1IE); // Enable interrupts
register_set(&(TIM1->CR1), TIM_CR1_CEN, 0x3FU); // Enable timer
// No interrupts
NVIC_DisableIRQ(TIM1_UP_TIM10_IRQn);
@@ -33,15 +34,33 @@ void clock_source_init(bool enable_channel1) {
set_gpio_alternate(GPIOA, 8, GPIO_AF1_TIM1);
}
set_gpio_alternate(GPIOB, 14, GPIO_AF1_TIM1);
set_gpio_alternate(GPIOB, 15, GPIO_AF1_TIM1);
// Set PWM mode
register_set(&(TIM1->CCMR1), (0b110UL << TIM_CCMR1_OC1M_Pos) | (0b110UL << TIM_CCMR1_OC2M_Pos), 0xFFFFU);
register_set(&(TIM1->CCMR2), (0b110UL << TIM_CCMR2_OC3M_Pos), 0xFFFFU);
register_set(&(TIM1->CCMR2), (0b110UL << TIM_CCMR2_OC3M_Pos) | (0b111UL << TIM_CCMR2_OC4M_Pos), 0xFFFFU);
// Enable output
register_set(&(TIM1->BDTR), TIM_BDTR_MOE, 0xFFFFU);
// Enable complementary compares
register_set_bits(&(TIM1->CCER), TIM_CCER_CC2NE | TIM_CCER_CC3NE);
// Sync with slave
register_set(&(TIM1->SMCR), TIM_SMCR_MSM , 0xFFFFU);
register_set(&(TIM1->CR2), (0b0111U << TIM_CR2_MMS_Pos), 0xFFFFU);
register_set(&(TIM8->SMCR), (0b0100U << TIM_SMCR_SMS_Pos) | (0b000U << TIM_SMCR_TS_Pos), 0xFFFFU);
// Setup slave timer (TIM8)
register_set(&(TIM8->PSC), TIM1->PSC, 0xFFFFU);
register_set(&(TIM8->ARR), TIM1->ARR, 0xFFFFU);
register_set(&(TIM8->CCMR2), (0b110UL << TIM_CCMR2_OC3M_Pos), 0xFFFFU);
register_set(&(TIM8->CCR3), (CLOCK_SOURCE_PULSE_LEN_MS * 10U), 0xFFFFU);
register_set(&(TIM8->CCER), TIM_CCER_CC3NE, 0xFFFFU);
// MOE for TIM8 as well
register_set(&(TIM8->BDTR), TIM_BDTR_MOE, 0xFFFFU);
// Set GPIO
set_gpio_alternate(GPIOB, 15, GPIO_AF3_TIM8);
// Enable timers
register_set(&(TIM1->CR1), TIM_CR1_CEN, 0x3FU);
register_set(&(TIM8->CR1), TIM_CR1_CEN, 0x3FU);
}

View File

@@ -75,11 +75,6 @@ void tick_handler(void) {
// turn off the blue LED, turned on by CAN
led_set(LED_BLUE, false);
// Blink and OBD CAN
#ifdef FINAL_PROVISIONING
current_board->set_can_mode(can_mode == CAN_MODE_NORMAL ? CAN_MODE_OBD_CAN2 : CAN_MODE_NORMAL);
#endif
// on to the next one
uptime_cnt += 1U;
}
@@ -91,33 +86,12 @@ void tick_handler(void) {
current_board->set_panda_power(!panda_power);
}
#ifdef FINAL_PROVISIONING
// Ignition blinking
uint8_t ignition_bitmask = 0U;
for (uint8_t i = 0U; i < 6U; i++) {
ignition_bitmask |= ((loop_counter % 12U) < ((uint32_t) i + 2U)) << i;
}
current_board->set_individual_ignition(ignition_bitmask);
// SBU voltage reporting
for (uint8_t i = 0U; i < 6U; i++) {
CANPacket_t pkt = { 0 };
pkt.data_len_code = 8U;
pkt.addr = 0x100U + i;
*(uint16_t *) &pkt.data[0] = current_board->get_sbu_mV(i + 1U, SBU1);
*(uint16_t *) &pkt.data[2] = current_board->get_sbu_mV(i + 1U, SBU2);
pkt.data[4] = (ignition_bitmask >> i) & 1U;
can_set_checksum(&pkt);
can_send(&pkt, 0U, false);
}
#else
// toggle ignition on button press
static bool prev_button_status = false;
if (!current_button_status && prev_button_status && button_press_cnt < 10){
current_board->set_ignition(!ignition);
}
prev_button_status = current_button_status;
#endif
button_press_cnt = current_button_status ? button_press_cnt + 1 : 0;
@@ -181,12 +155,6 @@ int main(void) {
can_init_all();
current_board->set_harness_orientation(HARNESS_ORIENTATION_1);
#ifdef FINAL_PROVISIONING
print("---- FINAL PROVISIONING BUILD ---- \n");
can_set_forwarding(0, 2);
can_set_forwarding(1, 2);
#endif
// LED should keep on blinking all the time
uint32_t cnt = 0;
for (cnt=0;;cnt++) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -13,7 +13,11 @@ classifiers = [
]
dependencies = [
"libusb1",
"libusb-package",
"opendbc @ git+https://github.com/sunnypilot/opendbc.git@master#egg=opendbc",
# runtime dependency on comma four
#"spidev; platform_system == 'Linux'",
]
[project.optional-dependencies]
@@ -23,13 +27,11 @@ dev = [
"cffi",
"flaky",
"pytest",
"pytest-xdist",
"pytest-mock",
"pytest-timeout",
"pytest-randomly",
"ruff",
"mypy",
"setuptools",
"spidev; platform_system == 'Linux'",
"gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=gcc-arm-none-eabi",
"cppcheck @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=cppcheck",
]
@@ -50,18 +52,6 @@ packages = [
[tool.setuptools.package-dir]
panda = "."
[tool.mypy]
# third-party packages
ignore_missing_imports = true
# helpful warnings
warn_redundant_casts = true
warn_unreachable = true
warn_unused_ignores = true
# restrict dynamic typing
warn_return_any = true
# https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml
[tool.ruff]
line-length = 160
@@ -76,7 +66,7 @@ flake8-implicit-str-concat.allow-multiline=false
"pytest.main".msg = "pytest.main requires special handling that is easy to mess up!"
[tool.pytest.ini_options]
addopts = "-Werror --strict-config --strict-markers --durations=10 --ignore-glob='*.sh' --ignore=tests/misra --ignore=tests/som --ignore=tests/hitl"
addopts = "-Werror --strict-config --strict-markers --maxprocesses=8 -nauto --durations=10 --ignore-glob='*.sh' --ignore=tests/som --ignore=tests/hitl"
python_files = "test_*.py"
testpaths = [
"tests/"

View File

@@ -6,6 +6,7 @@ import usb1
import struct
import hashlib
import binascii
import ctypes
from functools import wraps, partial
from itertools import accumulate
@@ -18,6 +19,14 @@ from .spi import PandaSpiHandle, PandaSpiException, PandaProtocolMismatch
from .usb import PandaUsbHandle
from .utils import logger
# load libusb from pip package
try:
import libusb_package
usb1._libusb1.loadLibrary(ctypes.CDLL(str(libusb_package.get_library_path())))
except ImportError:
# TODO: remove this on next AGNOS update
pass
__version__ = '0.0.10'
CANPACKET_HEAD_SIZE = 0x6

View File

@@ -17,10 +17,8 @@ elif [[ $PLATFORM == "Linux" ]]; then
fi
sudo apt-get install -y --no-install-recommends \
curl ca-certificates \
make g++ git \
libusb-1.0-0 \
python3-dev python3-pip python3-venv
curl ca-certificates gcc git \
python3-dev
else
echo "WARNING: unsupported platform. skipping apt/brew install."
fi

12
test.sh
View File

@@ -8,14 +8,8 @@ cd $DIR
source ./setup.sh
# *** build ***
scons -j8
scons
# *** lint ***
# *** lint + test ***
ruff check .
mypy python/
# *** test ***
# TODO: make randomly work
pytest --randomly-dont-reorganize tests/
pytest

View File

@@ -1,19 +1,6 @@
import opendbc
import platform
CC = 'gcc'
system = platform.system()
mac_ver = platform.mac_ver()
# gcc installed by homebrew has version suffix (e.g. gcc-12) in order to be
# distinguishable from system one - which acts as a symlink to clang
# clang works on macOS 15 and greater but has issues on earlier macOS versions.
# see: https://github.com/commaai/openpilot/issues/35093
if system == 'Darwin' and mac_ver[0] and mac_ver[0] < '15':
CC += '-13'
env = Environment(
CC=CC,
CFLAGS=[
'-nostdlib',
'-fno-builtin',
@@ -23,16 +10,6 @@ env = Environment(
],
CPPPATH=[".", "../../", "../../board/", opendbc.INCLUDE_PATH],
)
if system == "Darwin":
env.PrependENVPath('PATH', '/opt/homebrew/bin')
if GetOption('ubsan'):
flags = [
"-fsanitize=undefined",
"-fno-sanitize-recover=undefined",
]
env['CFLAGS'] += flags
env['LINKFLAGS'] += flags
panda = env.SharedObject("panda.os", "panda.c")
libpanda = env.SharedLibrary("libpanda.so", [panda])

View File

@@ -24,7 +24,7 @@ fi
cd $PANDA_DIR
if [ -z "${SKIP_BUILD}" ]; then
scons -j8
scons
fi
CHECKLIST=$DIR/checkers.txt

View File

@@ -10,6 +10,7 @@ import random
HERE = os.path.abspath(os.path.dirname(__file__))
ROOT = os.path.join(HERE, "../../")
# skip mutating these paths
IGNORED_PATHS = (
'board/obj',
'board/jungle',
@@ -53,14 +54,16 @@ patterns = [
]
all_files = glob.glob('board/**', root_dir=ROOT, recursive=True)
files = [f for f in all_files if f.endswith(('.c', '.h')) and not f.startswith(IGNORED_PATHS)]
files = sorted(f for f in all_files if f.endswith(('.c', '.h')) and not f.startswith(IGNORED_PATHS))
assert len(files) > 50, all(d in files for d in ('board/main.c', 'board/stm32h7/llfdcan.h'))
# fixed seed so every xdist worker collects the same test params
rng = random.Random(len(files))
for p in patterns:
mutations.append((random.choice(files), p, True))
mutations.append((rng.choice(files), p, True))
# TODO: remove sampling once test_misra.sh is faster
mutations = random.sample(mutations, 2)
# sample to keep CI fast, but always include the no-mutation case
mutations = [mutations[0]] + rng.sample(mutations[1:], min(2, len(mutations) - 1))
@pytest.mark.parametrize("fn, patch, should_fail", mutations)
def test_misra_mutation(fn, patch, should_fail):
@@ -69,8 +72,16 @@ def test_misra_mutation(fn, patch, should_fail):
# apply patch
if fn is not None:
r = os.system(f"cd {tmp}/panda && sed -i '{patch}' {fn}")
assert r == 0
fpath = os.path.join(tmp, "panda", fn)
with open(fpath) as f:
content = f.read()
if patch.startswith("s/"):
old, new = patch[2:].rsplit("/g", 1)[0].split("/", 1)
content = content.replace(old, new)
elif patch.startswith("$a "):
content += patch[3:].replace(r"\n", "\n")
with open(fpath, "w") as f:
f.write(content)
# run test
r = subprocess.run("SKIP_TABLES_DIFF=1 panda/tests/misra/test_misra.sh", cwd=tmp, shell=True)

View File

@@ -1,25 +0,0 @@
#!/usr/bin/env python3
import os
import time
from opendbc.car.structs import CarParams
from panda import Panda
if __name__ == "__main__":
flag_set = False
while True:
try:
with Panda(disable_checks=False) as p:
if not flag_set:
p.set_heartbeat_disabled()
p.set_safety_mode(CarParams.SafetyModel.elm327, 30)
flag_set = True
# shutdown when told
ch = p.can_health(0)
if ch['can_data_speed'] == 1000:
os.system("sudo poweroff")
except Exception as e:
print(str(e))
time.sleep(0.5)

View File

@@ -1,154 +0,0 @@
import time
import pytest
from opendbc.car.structs import CarParams
from panda import Panda, PandaJungle
PANDA_SERIAL = "300008001851333037333932"
JUNGLE_SERIAL = "26001c001451313236343430"
OBDC_PORT = 1
@pytest.fixture(autouse=True, scope="function")
def pj():
jungle = PandaJungle(JUNGLE_SERIAL)
jungle.flash()
jungle.reset()
jungle.set_ignition(False)
yield jungle
#jungle.set_panda_power(False)
jungle.close()
@pytest.fixture(scope="function")
def p(pj):
# note that the 3X's panda lib isn't updated, which
# shold be fine since it only uses stable APIs
pj.set_panda_power(True)
assert Panda.wait_for_panda(PANDA_SERIAL, 10)
p = Panda(PANDA_SERIAL)
p.flash()
p.reset()
yield p
p.close()
def setup_state(panda, jungle, state):
jungle.set_panda_power(0)
if state == "off":
wait_for_full_poweroff(jungle)
elif state == "normal boot":
jungle.set_panda_individual_power(OBDC_PORT, 1)
elif state == "QDL":
time.sleep(0.5)
jungle.set_panda_individual_power(OBDC_PORT, 1)
elif state == "ready to bootkick":
wait_for_full_poweroff(jungle)
jungle.set_panda_individual_power(OBDC_PORT, 1)
wait_for_boot(panda, jungle)
set_som_shutdown_flag(panda)
panda.set_safety_mode(CarParams.SafetyModel.silent)
panda.send_heartbeat()
wait_for_som_shutdown(panda, jungle)
else:
raise ValueError(f"unkown state: {state}")
def wait_for_som_shutdown(panda, jungle):
st = time.monotonic()
while panda.read_som_gpio():
# can take a while for the SOM to fully shutdown
if time.monotonic() - st > 120:
raise Exception("SOM didn't shutdown in time")
if check_som_boot_flag(panda):
raise Exception(f"SOM rebooted instead of shutdown: {time.monotonic() - st}s")
time.sleep(0.5)
dt = time.monotonic() - st
print("waiting for shutdown", round(dt))
dt = time.monotonic() - st
print(f"took {dt:.2f}s for SOM to shutdown")
def wait_for_full_poweroff(jungle, timeout=30):
st = time.monotonic()
time.sleep(15)
while PANDA_SERIAL in Panda.list():
if time.monotonic() - st > timeout:
raise Exception("took too long for device to turn off")
health = jungle.health()
assert all(health[f"ch{i}_power"] < 0.1 for i in range(1, 7))
def check_som_boot_flag(panda):
h = panda.health()
return h['safety_mode'] == CarParams.SafetyModel.elm327 and h['safety_param'] == 30
def set_som_shutdown_flag(panda):
panda.set_can_data_speed_kbps(0, 1000)
def wait_for_boot(panda, jungle, reset_expected=False, bootkick=False, timeout=120):
st = time.monotonic()
Panda.wait_for_panda(PANDA_SERIAL, timeout)
panda.reconnect()
if bootkick:
assert panda.health()['uptime'] > 20
else:
assert panda.health()['uptime'] < 3
for i in range(3):
assert not check_som_boot_flag(panda)
time.sleep(1)
# wait for SOM to bootup
while not check_som_boot_flag(panda):
if time.monotonic() - st > timeout:
raise Exception("SOM didn't boot in time")
time.sleep(1.0)
assert panda.health()['som_reset_triggered'] == reset_expected
def test_cold_boot(p, pj):
setup_state(p, pj, "off")
setup_state(p, pj, "normal boot")
wait_for_boot(p, pj)
def test_bootkick_ignition_line(p, pj):
setup_state(p, pj, "ready to bootkick")
pj.set_ignition(True)
wait_for_boot(p, pj, bootkick=True)
@pytest.mark.skip("test isn't reliable yet")
def test_bootkick_can_ignition(p, pj):
setup_state(p, pj, "ready to bootkick")
for _ in range(10):
# Mazda ignition signal
pj.can_send(0x9E, b'\xc0\x00\x00\x00\x00\x00\x00\x00', 0)
time.sleep(0.5)
wait_for_boot(p, pj, bootkick=True)
def test_recovery_from_qdl(p, pj):
setup_state(p, pj, "ready to bootkick")
# put into QDL using the FORCE_USB_BOOT pin
for i in range(10):
pj.set_header_pin(i, 1)
# try to boot
time.sleep(1)
pj.set_ignition(True)
time.sleep(3)
# release FORCE_USB_BOOT
for i in range(10):
pj.set_header_pin(i, 0)
# normally, this GPIO is set immediately since it's first enabled in the ABL
for i in range(17):
assert not p.read_som_gpio()
time.sleep(1)
# should boot after 45s
wait_for_boot(p, pj, reset_expected=True, bootkick=True, timeout=120)

View File

@@ -31,6 +31,7 @@ def random_can_messages(n, bus=None):
class TestPandaComms(unittest.TestCase):
def setUp(self):
lpp.set_safety_hooks(CarParams.SafetyModel.allOutput, 0)
lpp.comms_can_reset()
def test_tx_queues(self):
@@ -102,8 +103,6 @@ class TestPandaComms(unittest.TestCase):
def test_can_send_usb(self):
lpp.set_safety_hooks(CarParams.SafetyModel.allOutput, 0)
for bus in range(3):
with self.subTest(bus=bus):
for _ in range(100):