完善模式卡片、NFC及多设备控制流程
This commit is contained in:
@@ -795,15 +795,16 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
||||
conversation_id: str | None,
|
||||
execution_mode: str,
|
||||
) -> dict[str, object]:
|
||||
"""Execute already validated local-rule infrared capabilities."""
|
||||
"""Execute already validated infrared capabilities."""
|
||||
|
||||
capabilities = [
|
||||
{
|
||||
**dict(entry["capability"]),
|
||||
"parse_source": "local_rule",
|
||||
**capability,
|
||||
"parse_source": str(capability.get("parse_source") or "local_rule"),
|
||||
}
|
||||
for entry in entries
|
||||
if isinstance(entry, dict) and isinstance(entry.get("capability"), dict)
|
||||
for capability in [dict(entry["capability"])]
|
||||
]
|
||||
if not capabilities:
|
||||
raise ExtensionIntegrationError("没有可执行的已学习红外能力。")
|
||||
@@ -1454,6 +1455,14 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
||||
)
|
||||
|
||||
def _handle_mode_profile_post(self, path: str) -> None:
|
||||
execute_match = re.fullmatch(
|
||||
r"/api/mode-profiles/(?P<mode_id>[a-z0-9][a-z0-9_-]{0,63})/execute",
|
||||
path,
|
||||
)
|
||||
if execute_match is not None:
|
||||
self._handle_mode_profile_execute(execute_match.group("mode_id"))
|
||||
return
|
||||
|
||||
mode_id = path.removeprefix("/api/mode-profiles/").strip()
|
||||
if not mode_id:
|
||||
self._json(
|
||||
@@ -1482,6 +1491,214 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
|
||||
HTTPStatus.BAD_REQUEST,
|
||||
)
|
||||
|
||||
def _saved_mode_infrared_entries(
|
||||
self,
|
||||
profile: object,
|
||||
) -> list[dict[str, object]]:
|
||||
command_flow = getattr(profile, "command_flow", None)
|
||||
if not isinstance(command_flow, dict):
|
||||
return []
|
||||
configurations = command_flow.get("function_configs") or {}
|
||||
extension_config = None
|
||||
if isinstance(configurations, dict):
|
||||
extension_config = configurations.get("self_extension")
|
||||
if not isinstance(extension_config, dict):
|
||||
extension_config = next(
|
||||
(
|
||||
config
|
||||
for config in configurations.values()
|
||||
if isinstance(config, dict)
|
||||
and str((config.get("spec") or {}).get("domain"))
|
||||
== "self_extension"
|
||||
),
|
||||
None,
|
||||
)
|
||||
operations = (
|
||||
(extension_config.get("spec") or {}).get("operations") or []
|
||||
if isinstance(extension_config, dict)
|
||||
else []
|
||||
)
|
||||
if not operations:
|
||||
return []
|
||||
|
||||
snapshot = self.nfc.snapshot()
|
||||
card = snapshot.get("card") if isinstance(snapshot, dict) else None
|
||||
active_uid = re.sub(
|
||||
r"[^0-9A-Fa-f]",
|
||||
"",
|
||||
str((card or {}).get("uid") or ""),
|
||||
).upper()
|
||||
if not active_uid or str((card or {}).get("action") or "present") != "present":
|
||||
raise ExtensionIntegrationError(
|
||||
"该模式包含 NFC 红外指令,请先放置生成此模式时绑定的 NFC 卡。"
|
||||
)
|
||||
|
||||
catalog = self.extension.list_peripheral_capabilities()
|
||||
entries: list[dict[str, object]] = []
|
||||
for index, operation in enumerate(operations):
|
||||
if not isinstance(operation, dict) or operation.get("capability") != "infrared.emit":
|
||||
continue
|
||||
parameters = operation.get("parameters") or {}
|
||||
target = operation.get("target") or {}
|
||||
try:
|
||||
slot = int(parameters.get("slot"))
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise ExtensionIntegrationError(
|
||||
"已保存模式包含无效的红外槽位。"
|
||||
) from exc
|
||||
device_id = str(target.get("device") or "").strip()
|
||||
capability = next(
|
||||
(
|
||||
item
|
||||
for item in catalog
|
||||
if item.get("status") == "learned"
|
||||
and item.get("enabled", True) is True
|
||||
and int(item.get("slot") or 0) == slot
|
||||
and str(item.get("device_id") or "") == device_id
|
||||
and re.sub(
|
||||
r"[^0-9A-Fa-f]",
|
||||
"",
|
||||
str(item.get("card_uid") or ""),
|
||||
).upper()
|
||||
== active_uid
|
||||
),
|
||||
None,
|
||||
)
|
||||
if capability is None:
|
||||
raise ExtensionIntegrationError(
|
||||
f"当前 NFC 未授权模式中的红外命令:{device_id} / IR {slot}。"
|
||||
)
|
||||
entries.append(
|
||||
{
|
||||
"index": index,
|
||||
"clause": str(parameters.get("action") or f"IR {slot}"),
|
||||
"capability": {
|
||||
**capability,
|
||||
"parse_source": "saved_mode_command_flow",
|
||||
},
|
||||
}
|
||||
)
|
||||
return entries
|
||||
|
||||
def _handle_mode_profile_execute(self, mode_id: str) -> None:
|
||||
utterance = ""
|
||||
source = "web_console_mode_card"
|
||||
try:
|
||||
payload = self._read_json_body(max_bytes=16 * 1024)
|
||||
vehicle_id = str(payload.get("vehicle_id", "")) or None
|
||||
profile = self.service.get_mode_profile_card(mode_id, vehicle_id)
|
||||
utterance = profile.name
|
||||
source = str(payload.get("client_type") or source)
|
||||
execution_mode = str(payload.get("execution_mode", "dry_run"))
|
||||
control_transport = str(
|
||||
payload.get("control_transport", "wired_can")
|
||||
)
|
||||
conversation_id = (
|
||||
str(payload["conversation_id"])
|
||||
if payload.get("conversation_id")
|
||||
else None
|
||||
)
|
||||
hardware_client_id = (
|
||||
str(payload["hardware_client_id"])
|
||||
if payload.get("hardware_client_id")
|
||||
else None
|
||||
)
|
||||
self.vehicle_display_sync.update_task(
|
||||
status="running",
|
||||
utterance=utterance,
|
||||
source=source,
|
||||
)
|
||||
# Validate the complete saved flow, including the live NFC
|
||||
# binding, before any transport is allowed to execute.
|
||||
entries = self._saved_mode_infrared_entries(profile)
|
||||
can_result = self.service.execute_mode_profile_card(
|
||||
mode_id,
|
||||
conversation_id=conversation_id,
|
||||
execution_mode=execution_mode,
|
||||
approval_token=os.getenv(
|
||||
"COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN"
|
||||
),
|
||||
vehicle_id=vehicle_id,
|
||||
simulation_fault=str(payload.get("simulation_fault", "normal")),
|
||||
hardware_client_id=hardware_client_id,
|
||||
hardware_access_bypass=self._is_unproxied_loopback_request(),
|
||||
control_transport=control_transport,
|
||||
)
|
||||
if entries:
|
||||
infrared_result = self._execute_local_peripheral_actions(
|
||||
utterance,
|
||||
entries,
|
||||
provider="saved_mode_command_flow",
|
||||
conversation_id=conversation_id,
|
||||
execution_mode=execution_mode,
|
||||
)
|
||||
result = self._merge_can_and_peripheral_results(
|
||||
utterance,
|
||||
can_result,
|
||||
infrared_result,
|
||||
entries,
|
||||
)
|
||||
result["provider_used"] = "saved_mode_command_flow"
|
||||
result["orchestration_route"] = "saved_mode_command_flow"
|
||||
result["mode_profile"] = profile.public_dict()
|
||||
self.service.persist_composed_result(result)
|
||||
else:
|
||||
result = can_result
|
||||
self.vehicle_display_sync.update_task(
|
||||
status=str(result.get("status") or "succeeded"),
|
||||
utterance=utterance,
|
||||
source=source,
|
||||
result=result,
|
||||
)
|
||||
self._json(result, HTTPStatus.OK)
|
||||
except KeyError:
|
||||
self._json(
|
||||
{"error": "not_found", "message": "未找到需要执行的模式卡片。"},
|
||||
HTTPStatus.NOT_FOUND,
|
||||
)
|
||||
except HardwareAccessRequiredError as exc:
|
||||
self._json(
|
||||
{
|
||||
"error": "hardware_access_required",
|
||||
"message": str(exc),
|
||||
"access_state": exc.state,
|
||||
},
|
||||
HTTPStatus.FORBIDDEN,
|
||||
)
|
||||
except HardwareBusyError as exc:
|
||||
self._json(
|
||||
{
|
||||
"error": "hardware_busy",
|
||||
"message": str(exc),
|
||||
"retry_after_seconds": exc.retry_after_seconds,
|
||||
},
|
||||
HTTPStatus.CONFLICT,
|
||||
)
|
||||
except (HardwareSafetyError, CanGatewayError, GatewayError) as exc:
|
||||
if utterance:
|
||||
self.vehicle_display_sync.update_task(
|
||||
status="failed",
|
||||
utterance=utterance,
|
||||
source=source,
|
||||
error={"code": "mode_execution_failed", "message": str(exc)},
|
||||
)
|
||||
self._json(
|
||||
{"error": "mode_execution_failed", "message": str(exc)},
|
||||
HTTPStatus.SERVICE_UNAVAILABLE,
|
||||
)
|
||||
except (ExtensionIntegrationError, ValueError, json.JSONDecodeError) as exc:
|
||||
if utterance:
|
||||
self.vehicle_display_sync.update_task(
|
||||
status="failed",
|
||||
utterance=utterance,
|
||||
source=source,
|
||||
error={"code": "invalid_mode_command_flow", "message": str(exc)},
|
||||
)
|
||||
self._json(
|
||||
{"error": "invalid_mode_command_flow", "message": str(exc)},
|
||||
HTTPStatus.BAD_REQUEST,
|
||||
)
|
||||
|
||||
def _handle_extension_post(self, path: str) -> None:
|
||||
action_map = {
|
||||
"/api/extension/gscu/info": "info",
|
||||
|
||||
Reference in New Issue
Block a user