Merge branch 'upstream/panda/master' into sync-20260401

This commit is contained in:
Jason Wen
2026-04-01 22:39:14 -04:00
12 changed files with 59 additions and 51 deletions

4
Jenkinsfile vendored
View File

@@ -114,7 +114,7 @@ pipeline {
["build", "scons"], ["build", "scons"],
["flash", "cd scripts/ && ./reflash_internal_panda.py"], ["flash", "cd scripts/ && ./reflash_internal_panda.py"],
["flash jungle", "cd board/jungle && ./flash.py --all"], ["flash jungle", "cd board/jungle && ./flash.py --all"],
["test", "cd tests/hitl && pytest --durations=0 2*.py [5-9]*.py"], ["test", "cd tests/hitl && pytest -o 'addopts=' --durations=0 2*.py [5-9]*.py"],
]) ])
} }
} }
@@ -126,7 +126,7 @@ pipeline {
["build", "scons"], ["build", "scons"],
["flash", "cd scripts/ && ./reflash_internal_panda.py"], ["flash", "cd scripts/ && ./reflash_internal_panda.py"],
["flash jungle", "cd board/jungle && ./flash.py --all"], ["flash jungle", "cd board/jungle && ./flash.py --all"],
["test", "cd tests/hitl && pytest --durations=0 2*.py [5-9]*.py"], ["test", "cd tests/hitl && pytest -o 'addopts=' --durations=0 2*.py [5-9]*.py"],
]) ])
} }
} }

View File

@@ -1,4 +1,5 @@
import os import os
import hashlib
import opendbc import opendbc
import subprocess import subprocess
@@ -150,6 +151,14 @@ with open("board/obj/cert.h", "w") as f:
for cert in certs: for cert in certs:
f.write("\n".join(cert) + "\n") f.write("\n".join(cert) + "\n")
# Packet version defines: SHA hash of the struct header files
def version_hash(path):
with open(path, "rb") as f:
return int.from_bytes(hashlib.sha256(f.read()).digest()[:4], 'little')
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")
common_flags += [f"-DHEALTH_PACKET_VERSION=0x{hh:08X}U", f"-DCAN_PACKET_VERSION_HASH=0x{ch:08X}U",
f"-DJUNGLE_HEALTH_PACKET_VERSION=0x{jh:08X}U"]
# panda fw # panda fw
build_project("panda_h7", base_project_h7, "./board/main.c", []) build_project("panda_h7", base_project_h7, "./board/main.c", [])

View File

@@ -89,13 +89,13 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) {
break; break;
} }
// **** 0xdd: get healthpacket and CANPacket versions // **** 0xdd: get healthpacket and CANPacket version hashes
case 0xdd: case 0xdd: {
resp[0] = HEALTH_PACKET_VERSION; uint32_t versions[2] = {HEALTH_PACKET_VERSION, CAN_PACKET_VERSION_HASH};
resp[1] = CAN_PACKET_VERSION; (void)memcpy(resp, (uint8_t *)versions, sizeof(versions));
resp[2] = CAN_HEALTH_PACKET_VERSION; resp_len = sizeof(versions);
resp_len = 3U;
break; break;
}
default: default:
// Ignore unhandled requests // Ignore unhandled requests

View File

@@ -1,8 +1,5 @@
#pragma once #pragma once
// When changing these structs, python/__init__.py needs to be kept up to date!
#define HEALTH_PACKET_VERSION 18
struct __attribute__((packed)) health_t { struct __attribute__((packed)) health_t {
uint32_t uptime_pkt; uint32_t uptime_pkt;
uint32_t voltage_pkt; uint32_t voltage_pkt;
@@ -32,7 +29,6 @@ struct __attribute__((packed)) health_t {
uint16_t sound_output_level_pkt; uint16_t sound_output_level_pkt;
}; };
#define CAN_HEALTH_PACKET_VERSION 5
typedef struct __attribute__((packed)) { typedef struct __attribute__((packed)) {
uint8_t bus_off; uint8_t bus_off;
uint32_t bus_off_cnt; uint32_t bus_off_cnt;

View File

@@ -4,7 +4,7 @@ import struct
from functools import wraps from functools import wraps
from panda import Panda, PandaDFU from panda import Panda, PandaDFU
from panda.python.constants import McuType from panda.python.constants import McuType, compute_version_hash
BASEDIR = os.path.dirname(os.path.realpath(__file__)) BASEDIR = os.path.dirname(os.path.realpath(__file__))
FW_PATH = os.path.join(BASEDIR, "../obj/") FW_PATH = os.path.join(BASEDIR, "../obj/")
@@ -39,7 +39,7 @@ class PandaJungle(Panda):
H7_DEVICES = [HW_TYPE_V2, ] H7_DEVICES = [HW_TYPE_V2, ]
SUPPORTED_DEVICES = H7_DEVICES SUPPORTED_DEVICES = H7_DEVICES
HEALTH_PACKET_VERSION = 1 HEALTH_PACKET_VERSION = compute_version_hash(os.path.join(BASEDIR, "jungle_health.h"))
HEALTH_STRUCT = struct.Struct("<IffffffHHHHHHHHHHHH") HEALTH_STRUCT = struct.Struct("<IffffffHHHHHHHHHHHH")
HARNESS_ORIENTATION_NONE = 0 HARNESS_ORIENTATION_NONE = 0
@@ -108,13 +108,11 @@ class PandaJungle(Panda):
# ******************* control ******************* # ******************* control *******************
# Returns tuple with health packet version and CAN packet/USB packet version
def get_packets_versions(self): def get_packets_versions(self):
dat = self._handle.controlRead(PandaJungle.REQUEST_IN, 0xdd, 0, 0, 3) dat = self._handle.controlRead(PandaJungle.REQUEST_IN, 0xdd, 0, 0, 8)
if dat and len(dat) == 3: if dat and len(dat) == 8:
a = struct.unpack("BBB", dat) return struct.unpack("<II", dat)
return (a[0], a[1], a[2]) return (0, 0)
return (-1, -1, -1)
# ******************* jungle stuff ******************* # ******************* jungle stuff *******************

View File

@@ -1,6 +1,3 @@
// When changing these structs, python/__init__.py needs to be kept up to date!
#define JUNGLE_HEALTH_PACKET_VERSION 1
struct __attribute__((packed)) jungle_health_t { struct __attribute__((packed)) jungle_health_t {
uint32_t uptime_pkt; uint32_t uptime_pkt;
float ch1_power; float ch1_power;

View File

@@ -180,13 +180,13 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) {
current_board->set_can_mode(CAN_MODE_NORMAL); current_board->set_can_mode(CAN_MODE_NORMAL);
} }
break; break;
// **** 0xdd: get healthpacket and CANPacket versions // **** 0xdd: get healthpacket and CANPacket version hashes
case 0xdd: case 0xdd: {
resp[0] = JUNGLE_HEALTH_PACKET_VERSION; uint32_t versions[2] = {JUNGLE_HEALTH_PACKET_VERSION, CAN_PACKET_VERSION_HASH};
resp[1] = CAN_PACKET_VERSION; (void)memcpy(resp, (uint8_t *)versions, sizeof(versions));
resp[2] = CAN_HEALTH_PACKET_VERSION; resp_len = sizeof(versions);
resp_len = 3;
break; break;
}
// **** 0xde: set can bitrate // **** 0xde: set can bitrate
case 0xde: case 0xde:
if ((req->param1 < PANDA_CAN_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) { if ((req->param1 < PANDA_CAN_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) {

View File

@@ -226,13 +226,13 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) {
case 0xdc: case 0xdc:
set_safety_mode(req->param1, (uint16_t)req->param2); set_safety_mode(req->param1, (uint16_t)req->param2);
break; break;
// **** 0xdd: get healthpacket and CANPacket versions // **** 0xdd: get health and CAN packet versions
case 0xdd: case 0xdd: {
resp[0] = HEALTH_PACKET_VERSION; uint32_t versions[2] = {HEALTH_PACKET_VERSION, CAN_PACKET_VERSION_HASH};
resp[1] = CAN_PACKET_VERSION; (void)memcpy(resp, (uint8_t *)versions, sizeof(versions));
resp[2] = CAN_HEALTH_PACKET_VERSION; resp_len = sizeof(versions);
resp_len = 3;
break; break;
}
// **** 0xde: set can bitrate // **** 0xde: set can bitrate
case 0xde: case 0xde:
if ((req->param1 < PANDA_CAN_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) { if ((req->param1 < PANDA_CAN_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) {

View File

@@ -32,8 +32,8 @@ dev = [
"pytest-timeout", "pytest-timeout",
"ruff", "ruff",
"setuptools", "setuptools",
"gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=gcc-arm-none-eabi", "gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@release-gcc-arm-none-eabi#subdirectory=gcc-arm-none-eabi",
"cppcheck @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=cppcheck", "cppcheck @ git+https://github.com/commaai/dependencies.git@release-cppcheck#subdirectory=cppcheck",
] ]
[build-system] [build-system]
@@ -49,6 +49,10 @@ packages = [
"panda.board.jungle", "panda.board.jungle",
] ]
[tool.setuptools.package-data]
"panda.board" = ["health.h"]
"panda.board.jungle" = ["jungle_health.h"]
[tool.setuptools.package-dir] [tool.setuptools.package-dir]
panda = "." panda = "."

View File

@@ -10,10 +10,11 @@ import ctypes
from functools import wraps, partial from functools import wraps, partial
from itertools import accumulate from itertools import accumulate
import opendbc
from opendbc.car.structs import CarParams from opendbc.car.structs import CarParams
from .base import BaseHandle from .base import BaseHandle
from .constants import FW_PATH, McuType from .constants import BASEDIR, FW_PATH, McuType, compute_version_hash
from .dfu import PandaDFU from .dfu import PandaDFU
from .spi import PandaSpiHandle, PandaSpiException, PandaProtocolMismatch from .spi import PandaSpiHandle, PandaSpiException, PandaProtocolMismatch
from .usb import PandaUsbHandle from .usb import PandaUsbHandle
@@ -104,7 +105,6 @@ def ensure_version(desc, lib_field, panda_field, fn):
return fn(self, *args, **kwargs) return fn(self, *args, **kwargs)
return wrapper return wrapper
ensure_can_packet_version = partial(ensure_version, "CAN", "CAN_PACKET_VERSION", "can_version") ensure_can_packet_version = partial(ensure_version, "CAN", "CAN_PACKET_VERSION", "can_version")
ensure_can_health_packet_version = partial(ensure_version, "CAN health", "CAN_HEALTH_PACKET_VERSION", "can_health_version")
ensure_health_packet_version = partial(ensure_version, "health", "HEALTH_PACKET_VERSION", "health_version") ensure_health_packet_version = partial(ensure_version, "health", "HEALTH_PACKET_VERSION", "health_version")
@@ -126,9 +126,8 @@ class Panda:
HW_TYPE_CUATRO = b'\x0a' HW_TYPE_CUATRO = b'\x0a'
HW_TYPE_BODY = b'\xb1' HW_TYPE_BODY = b'\xb1'
CAN_PACKET_VERSION = 4 CAN_PACKET_VERSION = compute_version_hash(os.path.join(opendbc.INCLUDE_PATH, "opendbc/safety/can.h"))
HEALTH_PACKET_VERSION = 18 HEALTH_PACKET_VERSION = compute_version_hash(os.path.join(BASEDIR, "board/health.h"))
CAN_HEALTH_PACKET_VERSION = 5
HEALTH_STRUCT = struct.Struct("<IIIIIIIIBBBBBHBBBHfBBHHHBH") HEALTH_STRUCT = struct.Struct("<IIIIIIIIBBBBBHBBBHfBBHHHBH")
CAN_HEALTH_STRUCT = struct.Struct("<BIBBBBBBBBIIIIIIIHHBBBIIII") CAN_HEALTH_STRUCT = struct.Struct("<BIBBBBBBBBIIIIIIIHHBBBIIII")
@@ -210,7 +209,7 @@ class Panda:
self._serial = serial self._serial = serial
self._connect_serial = serial self._connect_serial = serial
self._handle_open = True self._handle_open = True
self.health_version, self.can_version, self.can_health_version = self.get_packets_versions() self.health_version, self.can_version = self.get_packets_versions()
logger.debug("connected") logger.debug("connected")
# disable openpilot's heartbeat checks # disable openpilot's heartbeat checks
@@ -536,7 +535,7 @@ class Panda:
"sound_output_level": a[25], "sound_output_level": a[25],
} }
@ensure_can_health_packet_version @ensure_health_packet_version
def can_health(self, can_number): def can_health(self, can_number):
LEC_ERROR_CODE = { LEC_ERROR_CODE = {
0: "No error", 0: "No error",
@@ -598,14 +597,11 @@ class Panda:
def get_type(self): def get_type(self):
return self._handle.controlRead(Panda.REQUEST_IN, 0xc1, 0, 0, 0x40) return self._handle.controlRead(Panda.REQUEST_IN, 0xc1, 0, 0, 0x40)
# Returns tuple with health packet version and CAN packet/USB packet version
def get_packets_versions(self): def get_packets_versions(self):
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xdd, 0, 0, 3) dat = self._handle.controlRead(Panda.REQUEST_IN, 0xdd, 0, 0, 8)
if dat and len(dat) == 3: if dat and len(dat) == 8:
a = struct.unpack("BBB", dat) return struct.unpack("<II", dat)
return (a[0], a[1], a[2]) return (0, 0)
else:
return (0, 0, 0)
def is_internal(self): def is_internal(self):
return self.get_type() in Panda.INTERNAL_DEVICES return self.get_type() in Panda.INTERNAL_DEVICES

View File

@@ -1,10 +1,15 @@
import os import os
import enum import enum
import hashlib
from typing import NamedTuple from typing import NamedTuple
BASEDIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../") BASEDIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")
FW_PATH = os.path.join(BASEDIR, "board/obj/") FW_PATH = os.path.join(BASEDIR, "board/obj/")
def compute_version_hash(filepath):
with open(filepath, "rb") as f:
return int.from_bytes(hashlib.sha256(f.read()).digest()[:4], 'little')
USBPACKET_MAX_SIZE = 0x40 USBPACKET_MAX_SIZE = 0x40
class McuConfig(NamedTuple): class McuConfig(NamedTuple):

View File

@@ -53,6 +53,9 @@ def pytest_configure(config):
config.addinivalue_line( config.addinivalue_line(
"markers", "panda_expect_can_error: mark test to ignore CAN health errors" "markers", "panda_expect_can_error: mark test to ignore CAN health errors"
) )
config.addinivalue_line(
"markers", "timeout: set test timeout in seconds"
)
@pytest.hookimpl(tryfirst=True) @pytest.hookimpl(tryfirst=True)
def pytest_collection_modifyitems(items): def pytest_collection_modifyitems(items):