feat(n7web): enhance control workflows and status feedback
This commit is contained in:
@@ -55,6 +55,7 @@ from cognitive_agent.application.mode_scene_catalog import ( # noqa: E402
|
||||
DEFAULT_N7_SCENE_SERVER_ID,
|
||||
DEFAULT_N7_SCENE_SERVER_NAME,
|
||||
ModeSceneCatalogPublisher,
|
||||
ModeSceneMqttBurstPublisher,
|
||||
build_mode_scene_catalog,
|
||||
)
|
||||
from cognitive_agent.application.mode_profile_intent import ( # noqa: E402
|
||||
@@ -129,6 +130,7 @@ from cognitive_agent.infrastructure.can.hardware import ( # noqa: E402
|
||||
VehicleMqttSettings,
|
||||
)
|
||||
from cognitive_agent.infrastructure.can.mqtt_transport import ( # noqa: E402
|
||||
MQTT_MODE_SCENE_TOPIC,
|
||||
MqttCanTransport,
|
||||
)
|
||||
from cognitive_agent.infrastructure.llm.openai_responses import ( # noqa: E402
|
||||
@@ -440,6 +442,10 @@ class SeatDemoService:
|
||||
"scene_count": 0,
|
||||
"last_error": None,
|
||||
}
|
||||
self._mode_scene_mqtt_publisher = ModeSceneMqttBurstPublisher(
|
||||
lambda scene: self._vehicle_mqtt_transport.publish_mode_scene(scene),
|
||||
topic=MQTT_MODE_SCENE_TOPIC,
|
||||
)
|
||||
self.scene_templates = SceneTemplateRegistry(
|
||||
repository_root / "config" / "scene_templates"
|
||||
)
|
||||
@@ -540,6 +546,51 @@ class SeatDemoService:
|
||||
def mode_scene_catalog_status(self) -> dict[str, Any]:
|
||||
return deepcopy(self._mode_scene_catalog_status)
|
||||
|
||||
def publish_saved_mode_scene(
|
||||
self,
|
||||
mode_id: str,
|
||||
vehicle_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Queue the just-saved card from the canonical scene projection.
|
||||
|
||||
Both a new user mode and a user override of a repository preset are
|
||||
projected by the same registry. Reading the card back from that
|
||||
projection guarantees that MQTT and ``/n7/scenes`` expose exactly the
|
||||
same post-save JSON object.
|
||||
"""
|
||||
|
||||
clean_mode_id = str(mode_id).strip()
|
||||
if not clean_mode_id:
|
||||
raise ValueError("mode scene id must not be empty")
|
||||
resolved_vehicle_id = vehicle_id or self.vehicle_profiles.default().id
|
||||
catalog = self.mode_scene_catalog(resolved_vehicle_id)
|
||||
catalog_publisher = self._mode_scene_catalog_publisher
|
||||
if catalog_publisher is not None and (
|
||||
catalog_publisher.vehicle_id != resolved_vehicle_id
|
||||
or self._mode_scene_catalog_status.get("state") != "ready"
|
||||
):
|
||||
raise RuntimeError("mode scene catalog is not synchronized for this vehicle")
|
||||
scene = next(
|
||||
(
|
||||
item
|
||||
for item in catalog.get("scenes", [])
|
||||
if isinstance(item, dict) and item.get("id") == clean_mode_id
|
||||
),
|
||||
None,
|
||||
)
|
||||
if scene is None:
|
||||
raise KeyError(clean_mode_id)
|
||||
return self._mode_scene_mqtt_publisher.queue(scene)
|
||||
|
||||
def publish_created_mode_scene(
|
||||
self,
|
||||
mode_id: str,
|
||||
vehicle_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Backward-compatible alias for callers predating update publishing."""
|
||||
|
||||
return self.publish_saved_mode_scene(mode_id, vehicle_id)
|
||||
|
||||
@property
|
||||
def driver_only_ch1_execution_active(self) -> bool:
|
||||
"""Return whether deployment policy authorizes only the CH1 vehicle path."""
|
||||
@@ -697,6 +748,7 @@ class SeatDemoService:
|
||||
"mode_profile_storage": "persistent_yaml",
|
||||
"mode_profile_user_root": str(self.mode_profile_user_root),
|
||||
"mode_scene_catalog": self.mode_scene_catalog_status(),
|
||||
"mode_scene_mqtt": self._mode_scene_mqtt_publisher.snapshot(),
|
||||
"preset_storage": "conversation_durable",
|
||||
"inferred_level_range": [1, 3],
|
||||
"motion_position_range_pct": [0, 100],
|
||||
@@ -1326,11 +1378,13 @@ class SeatDemoService:
|
||||
def start(self) -> None:
|
||||
if self._vehicle_state_monitor is not None:
|
||||
self._vehicle_state_monitor.start()
|
||||
self._mode_scene_mqtt_publisher.start()
|
||||
self._vehicle_mqtt_transport.start()
|
||||
|
||||
def close(self) -> None:
|
||||
if self._vehicle_state_monitor is not None:
|
||||
self._vehicle_state_monitor.close()
|
||||
self._mode_scene_mqtt_publisher.close()
|
||||
self._vehicle_mqtt_transport.close()
|
||||
|
||||
def publish_pet_message(self, payload: dict[str, str]) -> dict[str, Any]:
|
||||
@@ -1338,6 +1392,30 @@ class SeatDemoService:
|
||||
|
||||
return self._vehicle_mqtt_transport.publish_pet_message(payload)
|
||||
|
||||
def publish_extension_toy_message(
|
||||
self,
|
||||
payload: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Publish one NFC-detected extension capability announcement."""
|
||||
|
||||
return self._vehicle_mqtt_transport.publish_extension_toy_message(payload)
|
||||
|
||||
def set_extension_control_handler(
|
||||
self,
|
||||
handler: Callable[[bytes], None] | None,
|
||||
) -> None:
|
||||
"""Route ``extension/ctl`` payloads from the shared broker connection."""
|
||||
|
||||
self._vehicle_mqtt_transport.set_extension_control_handler(handler)
|
||||
|
||||
def set_extension_control_rejection_handler(
|
||||
self,
|
||||
handler: Callable[[str, str], None] | None,
|
||||
) -> None:
|
||||
"""Project broker-level ``extension/ctl`` rejections into the UI status."""
|
||||
|
||||
self._vehicle_mqtt_transport.set_extension_control_rejection_handler(handler)
|
||||
|
||||
def _synchronize_vehicle_state_once(
|
||||
self,
|
||||
conversation_id: str,
|
||||
|
||||
Reference in New Issue
Block a user