mirror of
https://github.com/infiniteCable2/panda.git
synced 2026-06-08 10:54:55 +08:00
Sync: commaai/panda:master → sunnypilot/panda:master
This commit is contained in:
37
.github/workflows/test.yaml
vendored
37
.github/workflows/test.yaml
vendored
@@ -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
15
Jenkinsfile
vendored
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
README.md
15
README.md
@@ -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.
|
||||
|
||||
15
SConscript
15
SConscript
@@ -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')
|
||||
|
||||
12
SConstruct
12
SConstruct
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 |
@@ -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/"
|
||||
|
||||
@@ -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
|
||||
|
||||
6
setup.sh
6
setup.sh
@@ -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
12
test.sh
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -24,7 +24,7 @@ fi
|
||||
|
||||
cd $PANDA_DIR
|
||||
if [ -z "${SKIP_BUILD}" ]; then
|
||||
scons -j8
|
||||
scons
|
||||
fi
|
||||
|
||||
CHECKLIST=$DIR/checkers.txt
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user