feat: complete dual-seat whole-vehicle orchestration

This commit is contained in:
vee
2026-08-12 15:12:40 +08:00
parent 32379a1573
commit 9ce64b3a01
62 changed files with 9741 additions and 3950 deletions
@@ -124,14 +124,20 @@ class ZlgChannelConfig:
"""Configuration for one channel on a shared ZLG CAN FD adapter."""
channel: int
frame_format: str = "can_fd"
arbitration_bitrate: int = 500_000
data_bitrate: int = 2_000_000
can_fd_iso: bool = True
termination_enabled: bool = True
def __post_init__(self) -> None:
if self.frame_format not in {"classic_can", "can_fd"}:
raise ValueError("frame_format must be classic_can or can_fd")
def public(self) -> dict[str, Any]:
return {
"channel": self.channel,
"frame_format": self.frame_format,
"arbitration_bitrate": self.arbitration_bitrate,
"data_bitrate": self.data_bitrate,
"can_fd_iso": self.can_fd_iso,
@@ -158,6 +164,7 @@ class ZlgCanBridgeProcess:
if len(channel_ids) != len(set(channel_ids)):
raise CanGatewayError("ZLG channel numbers must be unique")
self._channels = frozenset(channel_ids)
self._channel_configs = {item.channel: item for item in channels}
command = [
str(python_executable),
str(bridge_script),
@@ -193,7 +200,7 @@ class ZlgCanBridgeProcess:
def channel_bus(self, channel: int) -> "ZlgCanChannelBus":
if channel not in self._channels:
raise CanGatewayError(f"ZLG channel {channel} was not initialized")
return ZlgCanChannelBus(self, channel)
return ZlgCanChannelBus(self, channel, self._channel_configs[channel])
def _read_response(self) -> dict[str, Any]:
if self._process.stdout is None:
@@ -246,9 +253,15 @@ class ZlgCanBridgeProcess:
class ZlgCanChannelBus:
"""Channel-scoped view over a shared :class:`ZlgCanBridgeProcess`."""
def __init__(self, bridge: ZlgCanBridgeProcess, channel: int):
def __init__(
self,
bridge: ZlgCanBridgeProcess,
channel: int,
config: ZlgChannelConfig,
):
self._bridge = bridge
self.channel = channel
self.config = config
def send(
self,
@@ -258,6 +271,10 @@ class ZlgCanChannelBus:
is_fd: bool = False,
bitrate_switch: bool = False,
) -> CanFrame:
if is_fd and self.config.frame_format != "can_fd":
raise CanGatewayError(
f"ZLG channel {self.channel} is configured for classic CAN"
)
response = self._bridge._request(
{
"operation": "send",
@@ -279,6 +296,10 @@ class ZlgCanChannelBus:
def receive(
self, timeout_ms: int, *, include_fd: bool = False
) -> CanFrame | None:
if include_fd and self.config.frame_format != "can_fd":
raise CanGatewayError(
f"ZLG channel {self.channel} is configured for classic CAN"
)
requested_ms = max(0, int(timeout_ms))
deadline = time.monotonic() + requested_ms / 1000
while True:
@@ -334,6 +355,7 @@ class ZlgCanBridgeBus(ZlgCanChannelBus):
channels=[
ZlgChannelConfig(
channel=channel,
frame_format="classic_can",
arbitration_bitrate=bitrate,
termination_enabled=termination_enabled,
)
@@ -641,6 +641,7 @@ class VehicleCanSettings:
def channel_config(self) -> ZlgChannelConfig:
return ZlgChannelConfig(
channel=self.channel,
frame_format="can_fd",
arbitration_bitrate=self.arbitration_bitrate,
data_bitrate=self.data_bitrate,
can_fd_iso=True,
@@ -667,6 +668,8 @@ class VehicleCanSettings:
"required_channel": self.required_channel,
"channel_is_allowed": self.channel_is_allowed,
"target_policy": "ch1_body_can_driver_only",
"channel_role": "driver_front_left",
"coexists_with_seat_channel": True,
"forbidden_paths": ["ch0", "t2g_seat_bench", "passenger", "front_right"],
"mode": 0,
"work_mode": "normal",
@@ -119,6 +119,7 @@ class Driver:
)
)
self.channels = {}
self.channel_configs = {}
try:
for config in self._channel_configs(args):
self._initialize_channel(config)
@@ -134,6 +135,7 @@ class Driver:
configs = [
{
"channel": args.channel,
"frame_format": "can_fd",
"arbitration_bitrate": args.bitrate,
"data_bitrate": args.data_bitrate,
"can_fd_iso": True,
@@ -145,6 +147,9 @@ class Driver:
channel_ids = [int(item["channel"]) for item in configs]
if len(channel_ids) != len(set(channel_ids)):
raise ValueError("channel numbers must be unique")
for item in configs:
if item.get("frame_format", "can_fd") not in ("classic_can", "can_fd"):
raise ValueError("frame_format must be classic_can or can_fd")
return configs
def _set_value(self, path, value):
@@ -156,6 +161,7 @@ class Driver:
def _initialize_channel(self, config):
channel_id = int(config["channel"])
frame_format = str(config.get("frame_format", "can_fd"))
arbitration_bitrate = int(config.get("arbitration_bitrate", 500000))
data_bitrate = int(config.get("data_bitrate", 2000000))
can_fd_iso = bool(config.get("can_fd_iso", True))
@@ -181,6 +187,7 @@ class Driver:
if not handle:
raise RuntimeError("ZCAN_InitCAN failed for channel {}".format(channel_id))
self.channels[channel_id] = handle
self.channel_configs[channel_id] = frame_format
self._set_value(
prefix + "/initenal_resistance",
1 if bool(config.get("termination_enabled", True)) else 0,
@@ -266,7 +273,12 @@ class Driver:
if not 0 <= arbitration_id <= 0x7FF:
raise ValueError("only 11-bit standard CAN identifiers are allowed")
handle = self._handle(channel)
frame_format = self.channel_configs[int(channel)]
if is_fd:
if frame_format != "can_fd":
raise ValueError(
"channel {} is configured for classic CAN".format(channel)
)
valid_lengths = set(range(9)) | {12, 16, 20, 24, 32, 48, 64}
if len(data) not in valid_lengths:
raise ValueError(
@@ -320,6 +332,10 @@ class Driver:
def receive(self, channel, timeout_ms, include_fd=False):
handle = self._handle(channel)
if include_fd and self.channel_configs[int(channel)] != "can_fd":
raise ValueError(
"channel {} is configured for classic CAN".format(channel)
)
deadline = time.monotonic() + max(0, int(timeout_ms)) / 1000.0
while True:
classic = ZcanReceiveData()
@@ -350,6 +366,7 @@ class Driver:
for channel_id in sorted(self.channels, reverse=True):
handle = self.channels.pop(channel_id)
self.dll.ZCAN_ResetCAN(handle)
self.channel_configs.clear()
if self.device:
self.dll.ZCAN_CloseDevice(self.device)
self.device = None