feat: notify vehicle of published firmware

This commit is contained in:
Miayui
2026-09-18 12:52:36 +08:00
parent e920a92505
commit 0ab1d76781
14 changed files with 301 additions and 12 deletions
@@ -13,12 +13,14 @@ import threading
import time
import uuid
import zipfile
from collections.abc import Callable
from collections.abc import Callable, Mapping
from copy import deepcopy
from datetime import datetime
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
from cognitive_agent.infrastructure.can.gateway import CanGatewayError
PIPELINE_STAGES = (
("instruction", "指令输入"),
("requirements", "需求解析"),
@@ -41,6 +43,8 @@ BASELINE_CATALOG = {
SOFTWARE_NAME = "副驾座椅控制器"
FIRMWARE_VERSION = "SMV8-r15-b16"
FINAL_RELEASE_MESSAGE = "已发布,待车机端下载升级"
FIRMWARE_NOTIFICATION_TOPIC = "from_pc/firmware/notification"
FIRMWARE_NOTIFICATION_FRAME_COUNT = 3
BuildRunner = Callable[[str, Callable[[str, str], None], threading.Event], Path]
@@ -459,6 +463,113 @@ class FirmwareWorkbenchService:
with self._lock:
return deepcopy(self._find_artifact_locked(artifact_id))
def notify_artifact(
self,
artifact_id: str,
publisher: Callable[[Mapping[str, Any]], Mapping[str, Any]],
) -> dict[str, Any]:
"""Send a published artifact notification as one three-frame burst."""
with self._lock:
artifact = self._find_artifact_locked(artifact_id)
if artifact.get("is_baseline") or artifact.get("status") == "baseline":
raise FirmwareWorkbenchError(
"时间控制基线固件受保护,不能通知车端。",
403,
"baseline_protected",
)
if artifact.get("status") != "published":
raise FirmwareWorkbenchError(
"只有已发布的固件版本才能通知车端。",
409,
"artifact_not_published",
)
required_text_fields = (
"software_name",
"version",
"release_id",
"firmware_filename",
"build_time",
"sha256",
)
for field in required_text_fields:
value = artifact.get(field)
if not isinstance(value, str) or not value.strip():
raise FirmwareWorkbenchError(
f"已发布固件缺少通知字段:{field}。",
409,
"artifact_metadata_incomplete",
)
size_bytes = artifact.get("size_bytes")
if isinstance(size_bytes, bool) or not isinstance(size_bytes, int) or size_bytes < 0:
raise FirmwareWorkbenchError(
"已发布固件的包大小无效。",
409,
"artifact_metadata_incomplete",
)
sha256 = str(artifact["sha256"])
if len(sha256) != 64 or any(
character not in "0123456789abcdefABCDEF" for character in sha256
):
raise FirmwareWorkbenchError(
"已发布固件的 SHA-256 无效。",
409,
"artifact_metadata_incomplete",
)
message_id = f"fw-notify-{uuid.uuid4().hex[:20]}"
sent_at = datetime.now(UTC).isoformat().replace("+00:00", "Z")
common_payload: dict[str, Any] = {
"schema": "n7.firmware.notification.v1",
"event": "firmware_release_available",
"message_id": message_id,
"sequence_total": FIRMWARE_NOTIFICATION_FRAME_COUNT,
"sent_at": sent_at,
"source": "n7web",
"target": {"ecu": "passenger_seat_controller"},
"firmware": {
"software_name": artifact["software_name"],
"version": artifact["version"],
"status": "published",
"release_id": artifact["release_id"],
"artifact_name": artifact["firmware_filename"],
"size_bytes": size_bytes,
"sha256": sha256,
"build_at": artifact["build_time"],
},
"action": "notify_only",
"auto_upgrade": False,
}
published_count = 0
for sequence in range(1, FIRMWARE_NOTIFICATION_FRAME_COUNT + 1):
frame = deepcopy(common_payload)
frame["sequence"] = sequence
try:
result = publisher(frame)
except CanGatewayError as exc:
raise FirmwareWorkbenchError(
f"车端通知发送失败,已发送 {published_count}/{FIRMWARE_NOTIFICATION_FRAME_COUNT} 帧:{exc}",
503,
"notification_publish_failed",
) from exc
if not isinstance(result, Mapping):
raise FirmwareWorkbenchError(
f"车端通知发布器返回无效结果,已发送 {published_count}/{FIRMWARE_NOTIFICATION_FRAME_COUNT} 帧。",
503,
"notification_publish_failed",
)
published_count += 1
return {
"topic": FIRMWARE_NOTIFICATION_TOPIC,
"message_id": message_id,
"burst_count": FIRMWARE_NOTIFICATION_FRAME_COUNT,
"published_count": published_count,
"status": "published",
}
def publish_artifact(self, artifact_id: str) -> dict[str, Any]:
with self._lock:
artifact = self._find_artifact_locked(artifact_id)
@@ -1807,6 +1807,14 @@ class SeatDemoService(ModeScheduleServiceMixin):
return self._vehicle_mqtt_transport.publish_extension_toy_message(payload)
def publish_firmware_notification(
self,
payload: Mapping[str, Any],
) -> dict[str, Any]:
"""Publish one firmware release notification through the vehicle broker."""
return self._vehicle_mqtt_transport.publish_firmware_notification(payload)
def set_extension_control_handler(
self,
handler: Callable[[bytes], None] | None,