"""CAN/CAN-FD bus load calculator."""
from dataclasses import dataclass
from typing import List
@dataclass
class CanFrame:
name: str
can_id: int
dlc_bytes: int
cycle_ms: float
extended: bool = False # 29-bit vs 11-bit ID
def bit_length_classic(self) -> int:
"""CAN 2.0 frame size in bits (worst case with bit stuffing)."""
id_bits = 29 if self.extended else 11
overhead = 1+id_bits+1+1+1+4+15+1+1+1+7 # SOF+ID+RTR+IDE+r0+DLC+CRC+CRC_delim+ACK+ACK_delim+EOF
data_bits = self.dlc_bytes * 8
# Worst-case bit stuffing: +20% for classic CAN
return int((overhead + data_bits) * 1.2)
def utilisation_pct(self, bitrate_kbps: int) -> float:
"""Fraction of bus capacity used by this frame."""
bits_per_s = self.bit_length_classic()
frames_per_s = 1000.0 / self.cycle_ms
load_kbps = bits_per_s * frames_per_s / 1000.0
return load_kbps / bitrate_kbps * 100
def analyse_can_bus(frames: List[CanFrame],
bitrate_kbps: int = 500,
limit_pct: float = 60.0) -> dict:
total_load = sum(f.utilisation_pct(bitrate_kbps) for f in frames)
for f in sorted(frames, key=lambda x: -x.utilisation_pct(bitrate_kbps)):
print(f" {f.name:<30} {f.utilisation_pct(bitrate_kbps):.2f}%")
print(f" Total: {total_load:.1f}% (limit {limit_pct}%) "
f"[{'OK' if total_load <= limit_pct else 'OVER LIMIT'}]")
return {"total_pct": total_load, "ok": total_load <= limit_pct}
CHASSIS_CAN = [
CanFrame("VehicleSpeed", 0x1A3, 8, 10.0),
CanFrame("WheelSpeeds", 0x1A4, 8, 5.0),
CanFrame("SteeringAngle", 0x2B1, 8, 10.0),
CanFrame("BrakeRequest", 0x3C2, 4, 5.0),
CanFrame("EngineStatus", 0x4D0, 8, 20.0),
CanFrame("TransmissionData", 0x5E1, 8, 20.0),
]
analyse_can_bus(CHASSIS_CAN)