feat: notify vehicle of published firmware
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user