feat: serialize public hardware execution
This commit is contained in:
@@ -61,6 +61,7 @@ from cognitive_agent.infrastructure.llm.openai_responses import ( # noqa: E402
|
||||
)
|
||||
from cognitive_agent.infrastructure.can.hardware import ( # noqa: E402
|
||||
HardwareSafetyError,
|
||||
PHYSICAL_BENCH_COORDINATOR,
|
||||
SeatHardwareSettings,
|
||||
)
|
||||
from cognitive_agent.runtime.contracts import ContractValidator # noqa: E402
|
||||
@@ -138,6 +139,7 @@ class SeatDemoService:
|
||||
repository_root / "platforms" / "t2g_seat_bench" / "calibration" / "seat.yaml",
|
||||
)
|
||||
self.hardware_settings = SeatHardwareSettings.from_env(repository_root)
|
||||
self.hardware_execution = PHYSICAL_BENCH_COORDINATOR
|
||||
# Shared by the short-lived hardware executor instances. A process must
|
||||
# establish a fresh physical four-axis reference before trusting stored
|
||||
# Hall counts, and a firmware replacement invalidates it again.
|
||||
@@ -177,6 +179,8 @@ class SeatDemoService:
|
||||
runtime.runtime_info() for runtime in self.domain_runtimes.values()
|
||||
]
|
||||
default_vehicle = self.vehicle_profiles.default()
|
||||
hardware_info = self.hardware_settings.public_info()
|
||||
hardware_info.update(self.hardware_execution.public_info())
|
||||
return {
|
||||
"service": "cabin-cognitive-orchestrator",
|
||||
"domain": "cabin",
|
||||
@@ -184,7 +188,7 @@ class SeatDemoService:
|
||||
"execution_mode": "dry_run",
|
||||
"execution_modes": ["dry_run", "simulated_hardware", "hardware"],
|
||||
"hardware_execution_allowed": self.hardware_settings.configured,
|
||||
"hardware": self.hardware_settings.public_info(),
|
||||
"hardware": hardware_info,
|
||||
"seat_position_memory_synchronized": bool(
|
||||
self._seat_position_memory_state.get("synchronized", False)
|
||||
),
|
||||
@@ -243,6 +247,16 @@ class SeatDemoService:
|
||||
self,
|
||||
conversation_id: str,
|
||||
vehicle_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Read the bench only when no physical execution currently owns it."""
|
||||
|
||||
with self.hardware_execution.reserve():
|
||||
return self._synchronize_hardware_state_once(conversation_id, vehicle_id)
|
||||
|
||||
def _synchronize_hardware_state_once(
|
||||
self,
|
||||
conversation_id: str,
|
||||
vehicle_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Read the bench and project it into the current passenger conversation."""
|
||||
|
||||
@@ -308,6 +322,37 @@ class SeatDemoService:
|
||||
approval_token: str | None = None,
|
||||
vehicle_id: str | None = None,
|
||||
simulation_fault: str = "normal",
|
||||
) -> dict[str, Any]:
|
||||
if execution_mode != "hardware":
|
||||
return self._process_once(
|
||||
utterance,
|
||||
provider,
|
||||
conversation_id,
|
||||
execution_mode,
|
||||
approval_token,
|
||||
vehicle_id,
|
||||
simulation_fault,
|
||||
)
|
||||
with self.hardware_execution.reserve():
|
||||
return self._process_once(
|
||||
utterance,
|
||||
provider,
|
||||
conversation_id,
|
||||
execution_mode,
|
||||
approval_token,
|
||||
vehicle_id,
|
||||
simulation_fault,
|
||||
)
|
||||
|
||||
def _process_once(
|
||||
self,
|
||||
utterance: str,
|
||||
provider: str = "auto",
|
||||
conversation_id: str | None = None,
|
||||
execution_mode: str = "dry_run",
|
||||
approval_token: str | None = None,
|
||||
vehicle_id: str | None = None,
|
||||
simulation_fault: str = "normal",
|
||||
) -> dict[str, Any]:
|
||||
utterance = utterance.strip()
|
||||
if not utterance:
|
||||
|
||||
@@ -2,15 +2,24 @@
|
||||
|
||||
from .matrix import CanMatrix, MatrixError
|
||||
from .gateway import CanBus, CanFrame, CanGatewayError, PythonCanBus, ZlgCanBridgeBus
|
||||
from .hardware import HardwareSafetyError, SeatHardwareSettings
|
||||
from .hardware import (
|
||||
HardwareBusyError,
|
||||
HardwareExecutionCoordinator,
|
||||
HardwareSafetyError,
|
||||
PHYSICAL_BENCH_COORDINATOR,
|
||||
SeatHardwareSettings,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"CanBus",
|
||||
"CanFrame",
|
||||
"CanGatewayError",
|
||||
"CanMatrix",
|
||||
"HardwareBusyError",
|
||||
"HardwareExecutionCoordinator",
|
||||
"HardwareSafetyError",
|
||||
"MatrixError",
|
||||
"PHYSICAL_BENCH_COORDINATOR",
|
||||
"PythonCanBus",
|
||||
"SeatHardwareSettings",
|
||||
"ZlgCanBridgeBus",
|
||||
|
||||
@@ -5,9 +5,12 @@ from __future__ import annotations
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
from typing import Any, Callable, Iterator
|
||||
|
||||
from cognitive_agent.infrastructure.firmware import (
|
||||
FIRMWARE_PROTOCOL_SIGNATURE,
|
||||
@@ -36,6 +39,57 @@ class HardwareSafetyError(ValueError):
|
||||
"""Raised before any CAN device is opened when a safety gate fails."""
|
||||
|
||||
|
||||
class HardwareBusyError(RuntimeError):
|
||||
"""Raised when another request already owns the single physical bench."""
|
||||
|
||||
def __init__(self, retry_after_seconds: int = 5) -> None:
|
||||
self.retry_after_seconds = retry_after_seconds
|
||||
super().__init__(
|
||||
"真实座椅台架正在执行其他请求。为避免多人指令交叉,本次请求未进入CAN链路;"
|
||||
"请等待当前动作完成后重试。"
|
||||
)
|
||||
|
||||
|
||||
class HardwareExecutionCoordinator:
|
||||
"""Provide process-wide, fail-fast exclusive ownership of the physical bench."""
|
||||
|
||||
def __init__(self, retry_after_seconds: int = 5) -> None:
|
||||
self.retry_after_seconds = retry_after_seconds
|
||||
self._execution_lock = threading.Lock()
|
||||
self._state_lock = threading.Lock()
|
||||
self._active_since: float | None = None
|
||||
|
||||
@contextmanager
|
||||
def reserve(self) -> Iterator[None]:
|
||||
if not self._execution_lock.acquire(blocking=False):
|
||||
raise HardwareBusyError(self.retry_after_seconds)
|
||||
with self._state_lock:
|
||||
self._active_since = time.monotonic()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
with self._state_lock:
|
||||
self._active_since = None
|
||||
self._execution_lock.release()
|
||||
|
||||
def public_info(self) -> dict[str, Any]:
|
||||
with self._state_lock:
|
||||
active_since = self._active_since
|
||||
return {
|
||||
"busy": active_since is not None,
|
||||
"active_for_ms": (
|
||||
max(0, int((time.monotonic() - active_since) * 1000))
|
||||
if active_since is not None
|
||||
else 0
|
||||
),
|
||||
"concurrency_policy": "single_flight_fail_fast",
|
||||
"retry_after_seconds": self.retry_after_seconds,
|
||||
}
|
||||
|
||||
|
||||
PHYSICAL_BENCH_COORDINATOR = HardwareExecutionCoordinator()
|
||||
|
||||
|
||||
def _env_bool(name: str, default: bool = False) -> bool:
|
||||
value = os.getenv(name)
|
||||
return default if value is None else value.strip().lower() in TRUE_VALUES
|
||||
|
||||
@@ -28,7 +28,10 @@ load_local_env(REPOSITORY_ROOT / ".env.auth", override=True)
|
||||
from cognitive_agent.auth import AuthConfig, AuthRuntime, verify_password # noqa: E402
|
||||
from cognitive_agent.application.seat_service import SeatDemoService # noqa: E402
|
||||
from cognitive_agent.application.gesture_bridge import GestureCapabilityBridge # noqa: E402
|
||||
from cognitive_agent.infrastructure.can.hardware import HardwareSafetyError # noqa: E402
|
||||
from cognitive_agent.infrastructure.can.hardware import ( # noqa: E402
|
||||
HardwareBusyError,
|
||||
HardwareSafetyError,
|
||||
)
|
||||
from cognitive_agent.infrastructure.llm.openai_responses import OpenAIIntentError # noqa: E402
|
||||
|
||||
|
||||
@@ -110,6 +113,16 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
||||
vehicle_id,
|
||||
)
|
||||
)
|
||||
except HardwareBusyError as exc:
|
||||
self._json(
|
||||
{
|
||||
"error": "hardware_busy",
|
||||
"message": str(exc),
|
||||
"retry_after_seconds": exc.retry_after_seconds,
|
||||
},
|
||||
HTTPStatus.CONFLICT,
|
||||
{"Retry-After": str(exc.retry_after_seconds)},
|
||||
)
|
||||
except ValueError as exc:
|
||||
self._json(
|
||||
{"error": "invalid_request", "message": str(exc)},
|
||||
@@ -189,6 +202,16 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
||||
simulation_fault=str(payload.get("simulation_fault", "normal")),
|
||||
)
|
||||
self._json(result, HTTPStatus.CREATED)
|
||||
except HardwareBusyError as exc:
|
||||
self._json(
|
||||
{
|
||||
"error": "hardware_busy",
|
||||
"message": str(exc),
|
||||
"retry_after_seconds": exc.retry_after_seconds,
|
||||
},
|
||||
HTTPStatus.CONFLICT,
|
||||
{"Retry-After": str(exc.retry_after_seconds)},
|
||||
)
|
||||
except HardwareSafetyError as exc:
|
||||
self._json(
|
||||
{"error": "hardware_safety_gate", "message": str(exc)},
|
||||
|
||||
Reference in New Issue
Block a user