Skip to content

Commit caa0585

Browse files
committed
Merge branch 'master' of ssh://github.com/commaai/panda into simplify
2 parents c3099be + 7ffc916 commit caa0585

53 files changed

Lines changed: 5221 additions & 934 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test.yaml

Lines changed: 19 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,24 @@ concurrency:
1010
group: ${{ github.workflow }}-${{ github.ref != 'refs/heads/master' && github.ref || github.run_id }}-${{ github.event_name }}
1111
cancel-in-progress: true
1212

13+
env:
14+
RUN: source .venv/bin/activate && /bin/bash -c
15+
1316
jobs:
17+
build:
18+
name: build
19+
runs-on: ubuntu-latest
20+
timeout-minutes: 10
21+
steps:
22+
- uses: actions/checkout@v6
23+
- run: ./setup.sh
24+
- name: Test python package installer
25+
run: ${{ env.RUN }} "pip install --break-system-packages .[dev]"
26+
- name: Build debug FW
27+
run: ${{ env.RUN }} "scons"
28+
- name: Build release FW
29+
run: ${{ env.RUN }} "CERT=board/certs/debug RELEASE=1 scons"
30+
1431
test:
1532
name: ./test.sh
1633
runs-on: ${{ matrix.os }}
@@ -22,19 +39,13 @@ jobs:
2239
steps:
2340
- uses: actions/checkout@v6
2441
- run: ./test.sh
25-
- name: Build panda in release mode
26-
run: |
27-
source .venv/bin/activate
28-
CERT=certs/debug RELEASE=1 scons
29-
- name: Test python package installer
30-
run: pip install --break-system-packages .[dev]
3142

3243
windows:
3344
name: windows pip package test
3445
runs-on: windows-latest
35-
timeout-minutes: 10
46+
timeout-minutes: 3
3647
steps:
37-
- uses: actions/checkout@v4
48+
- uses: actions/checkout@v6
3849
- uses: actions/setup-python@v5
3950
with:
4051
python-version: "3.12"
@@ -43,27 +54,3 @@ jobs:
4354
run: uv pip install --system .
4455
- name: Verify importing panda
4556
run: python -c "from panda import Panda"
46-
47-
48-
misra_linter:
49-
name: MISRA C:2012 Linter
50-
runs-on: ubuntu-latest
51-
timeout-minutes: 3
52-
steps:
53-
- uses: actions/checkout@v6
54-
- run: ./setup.sh
55-
- name: Run MISRA C:2012 analysis
56-
run: ./tests/misra/test_misra.sh
57-
58-
misra_mutation:
59-
name: MISRA C:2012 Mutation
60-
runs-on: ubuntu-latest
61-
timeout-minutes: 3
62-
steps:
63-
- uses: actions/checkout@v6
64-
- run: ./setup.sh
65-
- name: MISRA mutation tests
66-
run: |
67-
source .venv/bin/activate
68-
cd tests/misra
69-
pytest test_mutation.py

Jenkinsfile

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ def docker_run(String step_label, int timeout_mins, String cmd) {
66
--volume /var/run/dbus:/var/run/dbus \
77
--net host \
88
${env.DOCKER_IMAGE_TAG} \
9-
bash -c 'scons -j8 && ${cmd}'", \
9+
bash -c 'scons && ${cmd}'", \
1010
label: step_label
1111
}
1212
}
@@ -111,10 +111,10 @@ pipeline {
111111
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
112112
steps {
113113
phone_steps("panda-cuatro", [
114-
["build", "scons -j4"],
114+
["build", "scons"],
115115
["flash", "cd scripts/ && ./reflash_internal_panda.py"],
116116
["flash jungle", "cd board/jungle && ./flash.py --all"],
117-
["test", "cd tests/hitl && pytest --durations=0 2*.py [5-9]*.py"],
117+
["test", "cd tests/hitl && pytest -o 'addopts=' --durations=0 2*.py [5-9]*.py"],
118118
])
119119
}
120120
}
@@ -123,23 +123,14 @@ pipeline {
123123
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
124124
steps {
125125
phone_steps("panda-tres", [
126-
["build", "scons -j4"],
126+
["build", "scons"],
127127
["flash", "cd scripts/ && ./reflash_internal_panda.py"],
128128
["flash jungle", "cd board/jungle && ./flash.py --all"],
129-
["test", "cd tests/hitl && pytest --durations=0 2*.py [5-9]*.py"],
129+
["test", "cd tests/hitl && pytest -o 'addopts=' --durations=0 2*.py [5-9]*.py"],
130130
])
131131
}
132132
}
133133

134-
/*
135-
stage('bootkick tests') {
136-
steps {
137-
script {
138-
docker_run("test", 10, "pytest ./tests/som/test_bootkick.py")
139-
}
140-
}
141-
}
142-
*/
143134
}
144135
}
145136
}

README.md

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ panda speaks CAN and CAN FD, and it runs on the [STM32H725](https://www.st.com/r
77
```
88
.
99
├── board # Code that runs on the STM32
10-
├── drivers # Drivers (not needed for use with Python)
1110
├── python # Python userspace library for interfacing with the panda
1211
├── tests # Tests for panda
1312
├── 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
1615

1716
## Safety Model
1817

19-
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.
18+
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.
2019

2120
## Code Rigor
2221

@@ -26,19 +25,18 @@ The panda firmware is written for its use in conjunction with [openpilot](https:
2625
These are the [CI regression tests](https://github.com/commaai/panda/actions) we have in place:
2726
* A generic static code analysis is performed by [cppcheck](https://github.com/danmar/cppcheck/).
2827
* 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).
29-
* Compiler options are relatively strict: the flags `-Wall -Wextra -Wstrict-prototypes -Werror` are enforced.
28+
* Compiler options are strict: the flags `-Wall -Wextra -Wstrict-prototypes -Werror` are enforced.
3029
* 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.
3130
to ensure that the behavior remains unchanged.
3231
* A hardware-in-the-loop test verifies panda's functionalities on all active panda variants, including:
3332
* additional safety model checks
3433
* compiling and flashing the bootstub and app code
3534
* receiving, sending, and forwarding CAN messages on all buses
36-
* CAN loopback and latency tests through USB and SPI
35+
* CAN loopback and latency tests through SPI
3736

3837
The above tests are themselves tested by:
3938
* a [mutation test](tests/misra/test_mutation.py) on the MISRA coverage
40-
41-
In addition, we run the [ruff linter](https://github.com/astral-sh/ruff) and [mypy](https://mypy-lang.org/) on panda's Python library.
39+
* 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
4240

4341
## Usage
4442

@@ -81,11 +79,6 @@ sudo udevadm control --reload-rules && sudo udevadm trigger
8179

8280
The panda jungle uses different udev rules. See [the repo](https://github.com/commaai/panda_jungle#udev-rules) for instructions.
8381

84-
## Software interface support
85-
86-
- [Python library](https://github.com/commaai/panda/tree/master/python)
87-
- [C++ library](https://github.com/commaai/openpilot/tree/master/selfdrive/pandad)
88-
8982
## Licensing
9083

9184
panda software is released under the MIT license unless otherwise specified.

SConscript

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import hashlib
23
import opendbc
34
import subprocess
45

@@ -14,7 +15,7 @@ if os.getenv("RELEASE"):
1415
assert os.path.exists(cert_fn), 'Certificate file not found. Please specify absolute path'
1516
else:
1617
BUILD_TYPE = "DEBUG"
17-
cert_fn = File("./certs/debug").srcnode().relpath
18+
cert_fn = File("./board/certs/debug").srcnode().relpath
1819
common_flags += ["-DALLOW_DEBUG"]
1920

2021
if os.getenv("DEBUG"):
@@ -33,7 +34,7 @@ def get_version(builder, build_type):
3334
def get_key_header(name):
3435
from Crypto.PublicKey import RSA
3536

36-
public_fn = File(f'./certs/{name}.pub').srcnode().get_path()
37+
public_fn = File(f'./board/certs/{name}.pub').srcnode().get_path()
3738
with open(public_fn) as f:
3839
rsa = RSA.importKey(f.read())
3940
assert(rsa.size_in_bits() == 1024)
@@ -105,8 +106,8 @@ def build_project(project_name, project, main, extra_flags):
105106
bs_env.Append(CFLAGS="-DBOOTSTUB", ASFLAGS="-DBOOTSTUB", LINKFLAGS="-DBOOTSTUB")
106107
bs_elf = bs_env.Program(f"{project_dir}/bootstub.elf", [
107108
startup,
108-
"./crypto/rsa.c",
109-
"./crypto/sha.c",
109+
"./board/crypto/rsa.c",
110+
"./board/crypto/sha.c",
110111
"./board/bootstub.c",
111112
])
112113
bs_env.Objcopy(f"./board/obj/bootstub.{project_name}.bin", bs_elf)
@@ -117,7 +118,7 @@ def build_project(project_name, project, main, extra_flags):
117118
main
118119
], LINKFLAGS=[f"-Wl,--section-start,.isr_vector={project['APP_START_ADDRESS']}"] + flags)
119120
main_bin = env.Objcopy(f"{project_dir}/main.bin", main_elf)
120-
sign_py = File(f"./crypto/sign.py").srcnode().relpath
121+
sign_py = File(f"./board/crypto/sign.py").srcnode().relpath
121122
env.Command(f"./board/obj/{project_name}.bin.signed", main_bin, f"SETLEN=1 {sign_py} $SOURCE $TARGET {cert_fn}")
122123

123124

@@ -150,11 +151,23 @@ with open("board/obj/cert.h", "w") as f:
150151
for cert in certs:
151152
f.write("\n".join(cert) + "\n")
152153

154+
# Packet version defines: SHA hash of the struct header files
155+
def version_hash(path):
156+
with open(path, "rb") as f:
157+
# Normalize line endings on Windows
158+
return int.from_bytes(hashlib.sha256(f.read().replace(b'\r', b'')).digest()[:4], 'little')
159+
hh, ch, jh = version_hash("board/health.h"), version_hash(os.path.join(opendbc.INCLUDE_PATH, "opendbc/safety/can.h")), version_hash("board/jungle/jungle_health.h")
160+
common_flags += [f"-DHEALTH_PACKET_VERSION=0x{hh:08X}U", f"-DCAN_PACKET_VERSION_HASH=0x{ch:08X}U",
161+
f"-DJUNGLE_HEALTH_PACKET_VERSION=0x{jh:08X}U"]
162+
153163
# panda fw
154164
build_project("panda_h7", base_project_h7, "./board/main.c", [])
155165

156166
# panda jungle fw
157-
build_project("panda_jungle_h7", base_project_h7, "./board/jungle/main.c", ["-DPANDA_JUNGLE"])
167+
flags = [
168+
"-DPANDA_JUNGLE",
169+
]
170+
build_project("panda_jungle_h7", base_project_h7, "./board/jungle/main.c", flags)
158171

159172
# body fw
160173
build_project("body_h7", base_project_h7, "./board/body/main.c", ["-DPANDA_BODY"])

board/body/__init__.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,37 @@ class PandaBody(Panda):
88
MOTOR_LEFT: int = 1
99
MOTOR_RIGHT: int = 2
1010

11+
def __init__(self, *args, **kwargs):
12+
super().__init__(*args, **kwargs)
13+
self._rpm_left: int = 0
14+
self._rpm_right: int = 0
15+
16+
@property
17+
def rpm_left(self) -> int:
18+
return self._rpm_left
19+
20+
@rpm_left.setter
21+
def rpm_left(self, value: int) -> None:
22+
self._rpm_left = int(value)
23+
self._handle.controlWrite(Panda.REQUEST_OUT, 0xb3, self._rpm_left, self._rpm_right, b'')
24+
25+
@property
26+
def rpm_right(self) -> int:
27+
return self._rpm_right
28+
29+
@rpm_right.setter
30+
def rpm_right(self, value: int) -> None:
31+
self._rpm_right = int(value)
32+
self._handle.controlWrite(Panda.REQUEST_OUT, 0xb3, self._rpm_left, self._rpm_right, b'')
33+
1134
# ****************** Motor Control *****************
1235
@staticmethod
1336
def _ensure_valid_motor(motor: int) -> None:
1437
if motor not in (PandaBody.MOTOR_LEFT, PandaBody.MOTOR_RIGHT):
1538
raise ValueError("motor must be MOTOR_LEFT or MOTOR_RIGHT")
1639

17-
def motor_set_speed(self, motor: int, speed: int) -> None:
18-
self._ensure_valid_motor(motor)
19-
assert -100 <= speed <= 100, "speed must be between -100 and 100"
20-
speed_param = speed if speed >= 0 else (256 + speed)
21-
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe0, motor, speed_param, b'')
22-
23-
def motor_set_target_rpm(self, motor: int, rpm: float) -> None:
24-
self._ensure_valid_motor(motor)
25-
target_deci_rpm = int(round(rpm * 10.0))
26-
assert -32768 <= target_deci_rpm <= 32767, "target rpm out of range (-3276.8 to 3276.7)"
27-
target_param = target_deci_rpm if target_deci_rpm >= 0 else (target_deci_rpm + (1 << 16))
28-
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe4, motor, target_param, b'')
29-
30-
def motor_stop(self, motor: int) -> None:
31-
self._ensure_valid_motor(motor)
32-
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe1, motor, 0, b'')
33-
3440
def motor_get_encoder_state(self, motor: int) -> tuple[int, float]:
3541
self._ensure_valid_motor(motor)
3642
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xe2, motor, 0, 8)
3743
position, rpm_milli = struct.unpack("<ii", dat)
3844
return position, rpm_milli / 1000.0
39-
40-
def motor_reset_encoder(self, motor: int) -> None:
41-
self._ensure_valid_motor(motor)
42-
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe3, motor, 0, b'')

0 commit comments

Comments
 (0)