From 74878fcbeb32fcc00543cee502232ecf722ac58b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 14 Mar 2026 13:40:20 -0700 Subject: [PATCH] automatically handle CAN and health packet ABI versions (#2371) --- SConscript | 9 +++++++++ board/body/main_comms.h | 12 ++++++------ board/health.h | 4 ---- board/jungle/__init__.py | 14 ++++++-------- board/jungle/jungle_health.h | 3 --- board/jungle/main_comms.h | 12 ++++++------ board/main_comms.h | 12 ++++++------ pyproject.toml | 4 ++++ python/__init__.py | 24 ++++++++++-------------- python/constants.py | 5 +++++ 10 files changed, 52 insertions(+), 47 deletions(-) diff --git a/SConscript b/SConscript index 6732204d..45abd074 100644 --- a/SConscript +++ b/SConscript @@ -1,4 +1,5 @@ import os +import hashlib import opendbc import subprocess @@ -150,6 +151,14 @@ with open("board/obj/cert.h", "w") as f: for cert in certs: 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 build_project("panda_h7", base_project_h7, "./board/main.c", []) diff --git a/board/body/main_comms.h b/board/body/main_comms.h index 95801332..c80096aa 100644 --- a/board/body/main_comms.h +++ b/board/body/main_comms.h @@ -89,13 +89,13 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { break; } - // **** 0xdd: get healthpacket and CANPacket versions - case 0xdd: - resp[0] = HEALTH_PACKET_VERSION; - resp[1] = CAN_PACKET_VERSION; - resp[2] = CAN_HEALTH_PACKET_VERSION; - resp_len = 3U; + // **** 0xdd: get healthpacket and CANPacket version hashes + case 0xdd: { + uint32_t versions[2] = {HEALTH_PACKET_VERSION, CAN_PACKET_VERSION_HASH}; + (void)memcpy(resp, (uint8_t *)versions, sizeof(versions)); + resp_len = sizeof(versions); break; + } default: // Ignore unhandled requests diff --git a/board/health.h b/board/health.h index 60f12460..f585cd63 100644 --- a/board/health.h +++ b/board/health.h @@ -1,8 +1,5 @@ #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 { uint32_t uptime_pkt; uint32_t voltage_pkt; @@ -32,7 +29,6 @@ struct __attribute__((packed)) health_t { uint16_t sound_output_level_pkt; }; -#define CAN_HEALTH_PACKET_VERSION 5 typedef struct __attribute__((packed)) { uint8_t bus_off; uint32_t bus_off_cnt; diff --git a/board/jungle/__init__.py b/board/jungle/__init__.py index e9e7e96b..3fe8afd7 100644 --- a/board/jungle/__init__.py +++ b/board/jungle/__init__.py @@ -4,7 +4,7 @@ import struct from functools import wraps 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__)) FW_PATH = os.path.join(BASEDIR, "../obj/") @@ -39,7 +39,7 @@ class PandaJungle(Panda): H7_DEVICES = [HW_TYPE_V2, ] 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("set_can_mode(CAN_MODE_NORMAL); } break; - // **** 0xdd: get healthpacket and CANPacket versions - case 0xdd: - resp[0] = JUNGLE_HEALTH_PACKET_VERSION; - resp[1] = CAN_PACKET_VERSION; - resp[2] = CAN_HEALTH_PACKET_VERSION; - resp_len = 3; + // **** 0xdd: get healthpacket and CANPacket version hashes + case 0xdd: { + uint32_t versions[2] = {JUNGLE_HEALTH_PACKET_VERSION, CAN_PACKET_VERSION_HASH}; + (void)memcpy(resp, (uint8_t *)versions, sizeof(versions)); + resp_len = sizeof(versions); break; + } // **** 0xde: set can bitrate case 0xde: if ((req->param1 < PANDA_CAN_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) { diff --git a/board/main_comms.h b/board/main_comms.h index 0627bf9e..8229a47a 100644 --- a/board/main_comms.h +++ b/board/main_comms.h @@ -226,13 +226,13 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { case 0xdc: set_safety_mode(req->param1, (uint16_t)req->param2); break; - // **** 0xdd: get healthpacket and CANPacket versions - case 0xdd: - resp[0] = HEALTH_PACKET_VERSION; - resp[1] = CAN_PACKET_VERSION; - resp[2] = CAN_HEALTH_PACKET_VERSION; - resp_len = 3; + // **** 0xdd: get health and CAN packet versions + case 0xdd: { + uint32_t versions[2] = {HEALTH_PACKET_VERSION, CAN_PACKET_VERSION_HASH}; + (void)memcpy(resp, (uint8_t *)versions, sizeof(versions)); + resp_len = sizeof(versions); break; + } // **** 0xde: set can bitrate case 0xde: if ((req->param1 < PANDA_CAN_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) { diff --git a/pyproject.toml b/pyproject.toml index bf5418a7..47319524 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,10 @@ packages = [ "panda.board.jungle", ] +[tool.setuptools.package-data] +"panda.board" = ["health.h"] +"panda.board.jungle" = ["jungle_health.h"] + [tool.setuptools.package-dir] panda = "." diff --git a/python/__init__.py b/python/__init__.py index 248d98bb..309c0b85 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -10,10 +10,11 @@ import ctypes from functools import wraps, partial from itertools import accumulate +import opendbc from opendbc.car.structs import CarParams 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 .spi import PandaSpiHandle, PandaSpiException, PandaProtocolMismatch from .usb import PandaUsbHandle @@ -104,7 +105,6 @@ def ensure_version(desc, lib_field, panda_field, fn): return fn(self, *args, **kwargs) return wrapper 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") @@ -126,9 +126,8 @@ class Panda: HW_TYPE_CUATRO = b'\x0a' HW_TYPE_BODY = b'\xb1' - CAN_PACKET_VERSION = 4 - HEALTH_PACKET_VERSION = 18 - CAN_HEALTH_PACKET_VERSION = 5 + CAN_PACKET_VERSION = compute_version_hash(os.path.join(opendbc.INCLUDE_PATH, "opendbc/safety/can.h")) + HEALTH_PACKET_VERSION = compute_version_hash(os.path.join(BASEDIR, "board/health.h")) HEALTH_STRUCT = struct.Struct("