feat: complete dual-seat whole-vehicle orchestration
This commit is contained in:
@@ -30,7 +30,14 @@ from .signal_diagnostic import (
|
||||
from .zcan_bridge import (
|
||||
ZcanBridgeManager, run_self_test as run_bridge_manager_self_test,
|
||||
)
|
||||
from .can_runtime import StandaloneCanRuntimeManager
|
||||
from cognitive_agent.infrastructure.can.gateway import ZlgChannelConfig
|
||||
from cognitive_agent.infrastructure.can.hardware import (
|
||||
SeatHardwareSettings,
|
||||
VehicleCanSettings,
|
||||
)
|
||||
from cognitive_agent.infrastructure.firmware.t2g_seat import (
|
||||
EXPECTED_FIRMWARE_BUILD_ID,
|
||||
EXPECTED_FIRMWARE_REVISION,
|
||||
)
|
||||
|
||||
@@ -123,7 +130,7 @@ FIRMWARE_LOG_ROOT = _env_path(
|
||||
FIRMWARE_SOURCE_ROOTS = tuple(
|
||||
PROJECT_DIR / name for name in ("App", "Bsp", "Config", "Driver"))
|
||||
FIRMWARE_SOURCE_SUFFIXES = frozenset((".c", ".h", ".s", ".asm"))
|
||||
REQUIRED_BRIDGE_VERSION = "2026-08-09-real-self-heal-v32"
|
||||
REQUIRED_BRIDGE_VERSION = "2026-08-10-legrest-runtime-v36"
|
||||
ZCANPRO_ROOTS = (
|
||||
Path(r"C:\Program Files (x86)\ZCANPRO"),
|
||||
Path(r"C:\Program Files\ZCANPRO"),
|
||||
@@ -136,13 +143,76 @@ ZCANPRO_SCRIPT_CONFIG = _env_or_installed_path(
|
||||
"SELF_REPAIR_ZCANPRO_SCRIPT_CONFIG",
|
||||
tuple(root / "config" / "extend_script.ini" for root in ZCANPRO_ROOTS),
|
||||
REPOSITORY_ROOT / "__unconfigured_zcanpro_script_config__")
|
||||
BRIDGE_MANAGER = ZcanBridgeManager(
|
||||
STATUS_PATH, BRIDGE_SCRIPT_PATH,
|
||||
required_version=REQUIRED_BRIDGE_VERSION,
|
||||
zcanpro_exe=ZCANPRO_EXE,
|
||||
script_config_path=ZCANPRO_SCRIPT_CONFIG,
|
||||
auto_start=_env_flag("SELF_REPAIR_AUTO_START_BRIDGE", False),
|
||||
)
|
||||
CAN_RUNTIME_BACKEND = os.getenv(
|
||||
"COGNITIVE_AGENT_CAN_RUNTIME_BACKEND", "standalone-zlg"
|
||||
).strip().lower()
|
||||
|
||||
|
||||
def _build_bridge_manager():
|
||||
auto_start = _env_flag("SELF_REPAIR_AUTO_START_BRIDGE", False)
|
||||
if CAN_RUNTIME_BACKEND == "zcanpro":
|
||||
return ZcanBridgeManager(
|
||||
STATUS_PATH, BRIDGE_SCRIPT_PATH,
|
||||
required_version=REQUIRED_BRIDGE_VERSION,
|
||||
zcanpro_exe=ZCANPRO_EXE,
|
||||
script_config_path=ZCANPRO_SCRIPT_CONFIG,
|
||||
auto_start=auto_start,
|
||||
)
|
||||
if CAN_RUNTIME_BACKEND != "standalone-zlg":
|
||||
raise ValueError(
|
||||
"COGNITIVE_AGENT_CAN_RUNTIME_BACKEND must be standalone-zlg or zcanpro"
|
||||
)
|
||||
|
||||
seat = SeatHardwareSettings.from_env(REPOSITORY_ROOT)
|
||||
vehicle = VehicleCanSettings.from_env(REPOSITORY_ROOT)
|
||||
seat_channel = int(seat.channel, 0)
|
||||
channels = [
|
||||
ZlgChannelConfig(
|
||||
channel=seat_channel,
|
||||
frame_format="classic_can",
|
||||
arbitration_bitrate=seat.bitrate,
|
||||
data_bitrate=vehicle.data_bitrate,
|
||||
can_fd_iso=True,
|
||||
termination_enabled=seat.termination_enabled,
|
||||
)
|
||||
]
|
||||
if vehicle.enabled:
|
||||
if vehicle.channel == seat_channel:
|
||||
raise ValueError("seat CH0 and vehicle CH1 must use different channels")
|
||||
if (
|
||||
vehicle.device_type != seat.device_type
|
||||
or vehicle.device_index != seat.device_index
|
||||
or vehicle.zlg_python != seat.zlg_python
|
||||
or vehicle.zlg_dll != seat.zlg_dll
|
||||
):
|
||||
raise ValueError(
|
||||
"seat and vehicle channels must share one configured ZLG device runtime"
|
||||
)
|
||||
channels.append(vehicle.channel_config())
|
||||
return StandaloneCanRuntimeManager(
|
||||
STATUS_PATH,
|
||||
BRIDGE_SCRIPT_PATH,
|
||||
required_version=REQUIRED_BRIDGE_VERSION,
|
||||
python_executable=seat.zlg_python,
|
||||
zlg_bridge_script=seat.bridge_script,
|
||||
dll_path=seat.zlg_dll,
|
||||
device_type=seat.device_type,
|
||||
device_index=seat.device_index,
|
||||
channels=channels,
|
||||
seat_channel=seat_channel,
|
||||
firmware_programmer=(
|
||||
seat._programmer() if seat.auto_flash_enabled else None
|
||||
),
|
||||
firmware_auto_flash=seat.auto_flash_enabled,
|
||||
expected_firmware_revision=EXPECTED_FIRMWARE_REVISION,
|
||||
expected_firmware_build_id=EXPECTED_FIRMWARE_BUILD_ID,
|
||||
position_sync_enabled=seat.position_sync_enabled,
|
||||
firmware_boot_wait_s=seat.firmware_boot_wait_ms / 1000.0,
|
||||
auto_start=auto_start,
|
||||
)
|
||||
|
||||
|
||||
BRIDGE_MANAGER = _build_bridge_manager()
|
||||
|
||||
|
||||
def _read_json(path):
|
||||
@@ -278,14 +348,14 @@ class ConsoleState(object):
|
||||
live bridge without fresh 0x301~0x305, a missing local image, or local
|
||||
firmware sources newer than that image are treated as recoverable
|
||||
firmware-qualification problems. A missing bridge is not: flashing
|
||||
cannot repair a closed adapter or a stopped ZCANPRO script.
|
||||
cannot repair a closed adapter or a stopped CAN runtime.
|
||||
"""
|
||||
bridge = bridge or {}
|
||||
if not bridge.get("online"):
|
||||
return {
|
||||
"ready": False, "auto_flash": False,
|
||||
"reason_code": "BRIDGE_OFFLINE",
|
||||
"detail": "ZCANPRO桥接未在线;该问题不能通过烧录修复",
|
||||
"detail": "CAN Runtime未在线;该问题不能通过烧录修复",
|
||||
}
|
||||
if not hex_exists:
|
||||
reason = "FIRMWARE_IMAGE_MISSING"
|
||||
@@ -308,6 +378,13 @@ class ConsoleState(object):
|
||||
"需要自动重新构建并烧录" %
|
||||
(bridge.get("firmware_revision", "未知"),
|
||||
EXPECTED_FIRMWARE_REVISION))
|
||||
elif not bridge.get("firmware_build_id_ok"):
|
||||
reason = "FIRMWARE_BUILD_MISMATCH"
|
||||
detail = (
|
||||
"Seat_Controller_demo firmware build=%s, expected build=%s; "
|
||||
"the repository image must be restored" %
|
||||
(bridge.get("firmware_build_id", "unknown"),
|
||||
EXPECTED_FIRMWARE_BUILD_ID))
|
||||
else:
|
||||
return {
|
||||
"ready": True, "auto_flash": False,
|
||||
@@ -375,7 +452,8 @@ class ConsoleState(object):
|
||||
if (last_bridge.get("online") and
|
||||
(not require_protocol or
|
||||
(last_bridge.get("protocol_online") and
|
||||
last_bridge.get("firmware_revision_ok")))):
|
||||
last_bridge.get("firmware_revision_ok") and
|
||||
last_bridge.get("firmware_build_id_ok")))):
|
||||
return last_status, last_bridge
|
||||
time.sleep(0.25)
|
||||
return last_status, last_bridge or {}
|
||||
@@ -427,7 +505,8 @@ class ConsoleState(object):
|
||||
status, bridge = self._wait_bridge(require_protocol=True,
|
||||
timeout_s=12.0)
|
||||
if (not bridge.get("protocol_online") or
|
||||
not bridge.get("firmware_revision_ok")):
|
||||
not bridge.get("firmware_revision_ok") or
|
||||
not bridge.get("firmware_build_id_ok")):
|
||||
detail = ("自动烧录已执行,但仍未收到0x301~0x305;"
|
||||
"硬件诊断未启动,请检查ECU供电或烧录日志")
|
||||
with self.lock:
|
||||
@@ -473,6 +552,9 @@ class ConsoleState(object):
|
||||
system = {} if not isinstance(status, dict) else (
|
||||
status.get("system") or {})
|
||||
firmware_revision = system.get("firmware_revision")
|
||||
firmware_identity = {} if not isinstance(status, dict) else (
|
||||
status.get("firmware_identity") or {})
|
||||
firmware_build_id = firmware_identity.get("firmware_build_id")
|
||||
frame_epochs = {} if not isinstance(status, dict) else (
|
||||
status.get("frame_epochs") or {})
|
||||
recent_frame_ids = []
|
||||
@@ -485,12 +567,12 @@ class ConsoleState(object):
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
recent_frame_ids.sort()
|
||||
protocol_epoch = frame_epochs.get("0x301")
|
||||
try:
|
||||
protocol_online = (online and protocol_epoch is not None and
|
||||
max(0.0, now - float(protocol_epoch)) <= 0.8)
|
||||
except (TypeError, ValueError):
|
||||
protocol_online = False
|
||||
protocol_required = {
|
||||
"0x301", "0x302", "0x303", "0x304", "0x305", "0x306", "0x307"
|
||||
}
|
||||
protocol_online = online and protocol_required.issubset(
|
||||
set(recent_frame_ids)
|
||||
)
|
||||
return {
|
||||
"online": online,
|
||||
"age_s": None if age is None else round(age, 2),
|
||||
@@ -507,6 +589,12 @@ class ConsoleState(object):
|
||||
"expected_firmware_revision": EXPECTED_FIRMWARE_REVISION,
|
||||
"firmware_revision_ok":
|
||||
firmware_revision == EXPECTED_FIRMWARE_REVISION,
|
||||
"firmware_build_id": firmware_build_id,
|
||||
"expected_firmware_build_id": EXPECTED_FIRMWARE_BUILD_ID,
|
||||
"firmware_build_id_ok": (
|
||||
firmware_build_id == EXPECTED_FIRMWARE_BUILD_ID
|
||||
and bool(firmware_identity.get("qualified"))
|
||||
),
|
||||
"active_mask": int(system.get("active_mask", 0)),
|
||||
"faulted_mask": int(system.get("faulted_mask", 0)),
|
||||
"bus": {} if not isinstance(status, dict) else (
|
||||
@@ -514,7 +602,9 @@ class ConsoleState(object):
|
||||
"bus_traffic": bool(recent_frame_ids),
|
||||
"protocol_online": protocol_online,
|
||||
"recent_frame_ids": recent_frame_ids,
|
||||
"expected_frame_ids": ["0x301", "0x302", "0x303", "0x304", "0x305"],
|
||||
"expected_frame_ids": [
|
||||
"0x301", "0x302", "0x303", "0x304", "0x305", "0x306", "0x307"
|
||||
],
|
||||
"frame_epochs": frame_epochs,
|
||||
}
|
||||
|
||||
@@ -911,7 +1001,7 @@ class ConsoleState(object):
|
||||
require_protocol=True, timeout_s=12.0)
|
||||
now = time.time()
|
||||
if not bridge.get("online"):
|
||||
raise ValueError("ZCANPRO桥接未在线,不能向实物ECU写入故障")
|
||||
raise ValueError("CAN Runtime未在线,不能向实物ECU写入故障")
|
||||
if not bridge.get("protocol_online"):
|
||||
raise ValueError("未识别到本工程座椅ECU协议,不能执行台架故障注入")
|
||||
self._sync_bench_reset(status, now)
|
||||
@@ -1133,7 +1223,7 @@ class ConsoleState(object):
|
||||
bridge = self._bridge_summary(status, now)
|
||||
if not bridge.get("online"):
|
||||
raise ValueError(
|
||||
"固件资格检查后ZCANPRO桥接离线,未启动21针主动检测")
|
||||
"固件资格检查后CAN Runtime离线,未启动21针主动检测")
|
||||
if not bridge.get("protocol_online"):
|
||||
raise ValueError(
|
||||
"固件资格检查后仍未识别0x301~0x305,未启动21针主动检测")
|
||||
@@ -1451,6 +1541,7 @@ class SelfRepairService(object):
|
||||
"diagnostic_config": str(CONFIG_PATH),
|
||||
"bridge_status_path": str(STATUS_PATH),
|
||||
"bridge_script_path": str(BRIDGE_SCRIPT_PATH),
|
||||
"can_runtime_backend": CAN_RUNTIME_BACKEND,
|
||||
"zcanpro_exe": str(ZCANPRO_EXE),
|
||||
"firmware_root": str(PROJECT_DIR),
|
||||
"iar_build_path": str(IAR_BUILD_PATH),
|
||||
@@ -1458,6 +1549,15 @@ class SelfRepairService(object):
|
||||
"bridge_script_available": BRIDGE_SCRIPT_PATH.is_file(),
|
||||
"diagnostic_config_available": CONFIG_PATH.is_file(),
|
||||
"zcanpro_configured": ZCANPRO_EXE.is_file(),
|
||||
"standalone_can_runtime_configured": bool(
|
||||
CAN_RUNTIME_BACKEND == "standalone-zlg"
|
||||
and getattr(BRIDGE_MANAGER, "configured", False)
|
||||
),
|
||||
"can_runtime_configured": bool(
|
||||
getattr(BRIDGE_MANAGER, "configured", False)
|
||||
if CAN_RUNTIME_BACKEND == "standalone-zlg"
|
||||
else ZCANPRO_EXE.is_file()
|
||||
),
|
||||
"firmware_root_configured": firmware_root_configured,
|
||||
"firmware_tools_configured": (
|
||||
firmware_root_configured
|
||||
|
||||
Reference in New Issue
Block a user