完善模式卡片、NFC及多设备控制流程
This commit is contained in:
@@ -112,10 +112,16 @@ class ModeOperation:
|
||||
)
|
||||
|
||||
def to_request(self, mode_name: str) -> dict[str, Any]:
|
||||
target_scope = self.target_scope
|
||||
if self.domain == "seat" and target_scope == "front_right":
|
||||
# Compiled seat operations use the physical zone name, while the
|
||||
# model-plan adapter exposes the logical passenger target. Normalize
|
||||
# persisted cards so a validated target is not queried again.
|
||||
target_scope = "passenger"
|
||||
return {
|
||||
"domain": self.domain,
|
||||
"capability": self.capability,
|
||||
"target_scope": self.target_scope,
|
||||
"target_scope": target_scope,
|
||||
"operation": self.operation,
|
||||
"parameters": [
|
||||
{"name": name, "value": value}
|
||||
@@ -450,6 +456,26 @@ class ModeProfileRegistry:
|
||||
public.append(item)
|
||||
return public
|
||||
|
||||
def get_profile(
|
||||
self,
|
||||
mode_id: str,
|
||||
vehicle_id: str | None = None,
|
||||
) -> ModeProfile:
|
||||
"""Resolve the effective preset or user override by its stable ID."""
|
||||
|
||||
clean_mode_id = str(mode_id).strip()
|
||||
profile = next(
|
||||
(
|
||||
item
|
||||
for item in self._profiles_for_vehicle(vehicle_id)
|
||||
if item.id == clean_mode_id
|
||||
),
|
||||
None,
|
||||
)
|
||||
if profile is None:
|
||||
raise KeyError(clean_mode_id)
|
||||
return profile
|
||||
|
||||
def match_text(
|
||||
self,
|
||||
utterance: str,
|
||||
|
||||
@@ -608,6 +608,59 @@ class SeatDemoService:
|
||||
"vehicle_id": resolved_vehicle_id,
|
||||
}
|
||||
|
||||
def get_mode_profile_card(
|
||||
self,
|
||||
mode_id: str,
|
||||
vehicle_id: str | None = None,
|
||||
) -> ModeProfile:
|
||||
resolved_vehicle_id = vehicle_id or self.vehicle_profiles.default().id
|
||||
return self.mode_profiles.get_profile(mode_id, resolved_vehicle_id)
|
||||
|
||||
def execute_mode_profile_card(
|
||||
self,
|
||||
mode_id: str,
|
||||
*,
|
||||
conversation_id: str | None = None,
|
||||
execution_mode: str = "dry_run",
|
||||
approval_token: str | None = None,
|
||||
vehicle_id: str | None = None,
|
||||
simulation_fault: str = "normal",
|
||||
hardware_client_id: str | None = None,
|
||||
hardware_access_bypass: bool = True,
|
||||
control_transport: str = WIRED_CAN_TRANSPORT,
|
||||
) -> dict[str, Any]:
|
||||
"""Execute a persisted mode by ID without interpreting natural language."""
|
||||
|
||||
resolved_vehicle_id = vehicle_id or self.vehicle_profiles.default().id
|
||||
profile = self.mode_profiles.get_profile(mode_id, resolved_vehicle_id)
|
||||
if not profile.command_flow or not profile.command_flow.get("plans"):
|
||||
raise ValueError("该模式尚未生成可直接执行的控制指令流。")
|
||||
result = self.process(
|
||||
profile.instruction or f"进入{profile.name}",
|
||||
provider="local",
|
||||
conversation_id=conversation_id,
|
||||
execution_mode=execution_mode,
|
||||
approval_token=approval_token,
|
||||
vehicle_id=resolved_vehicle_id,
|
||||
simulation_fault=simulation_fault,
|
||||
hardware_client_id=hardware_client_id,
|
||||
hardware_access_bypass=hardware_access_bypass,
|
||||
control_transport=control_transport,
|
||||
_direct_mode_profile_id=profile.id,
|
||||
)
|
||||
public_profile = profile.public_dict()
|
||||
result["provider_used"] = "saved_mode_command_flow"
|
||||
result["utterance"] = profile.name
|
||||
result["matched_modes"] = [public_profile]
|
||||
result["mode_profile"] = public_profile
|
||||
result["orchestration_route"] = "saved_mode_command_flow"
|
||||
result["decision_summary"] = [
|
||||
f"直接载入已保存的模式“{profile.name}”,未再次解析自然语言。",
|
||||
*list(result.get("decision_summary") or []),
|
||||
]
|
||||
self._persist(result)
|
||||
return result
|
||||
|
||||
def save_mode_profile_card(
|
||||
self,
|
||||
mode_id: str,
|
||||
@@ -616,7 +669,7 @@ class SeatDemoService:
|
||||
source_request_id: str,
|
||||
vehicle_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Persist one edited mode from a server-generated CAN command flow."""
|
||||
"""Persist one edited mode from a server-generated control flow."""
|
||||
|
||||
resolved_vehicle_id = vehicle_id or self.vehicle_profiles.default().id
|
||||
clean_mode_id = str(mode_id).strip()
|
||||
@@ -632,7 +685,7 @@ class SeatDemoService:
|
||||
if len(clean_instruction) > 1000:
|
||||
raise ValueError("自然语言指令不能超过 1000 个字符。")
|
||||
if not MODE_EDITOR_REQUEST_ID_PATTERN.fullmatch(clean_request_id):
|
||||
raise ValueError("CAN 指令流来源请求编号无效。")
|
||||
raise ValueError("控制指令流来源请求编号无效。")
|
||||
|
||||
visible_profiles = self.mode_profiles._profiles_for_vehicle( # noqa: SLF001
|
||||
resolved_vehicle_id
|
||||
@@ -653,15 +706,15 @@ class SeatDemoService:
|
||||
|
||||
result_path = self.artifact_root / clean_request_id / "result.json"
|
||||
if not result_path.is_file():
|
||||
raise ValueError("未找到执行按钮生成的 CAN 指令流,请重新执行。")
|
||||
raise ValueError("未找到“生成指令”按钮产生的控制指令流,请重新生成。")
|
||||
try:
|
||||
source_result = json.loads(result_path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise ValueError("CAN 指令流来源结果无法读取,请重新执行。") from exc
|
||||
raise ValueError("控制指令流来源结果无法读取,请重新生成。") from exc
|
||||
if not isinstance(source_result, dict) or source_result.get(
|
||||
"request_id"
|
||||
) != clean_request_id:
|
||||
raise ValueError("CAN 指令流来源结果不完整,请重新执行。")
|
||||
raise ValueError("控制指令流来源结果不完整,请重新生成。")
|
||||
recorded_instruction = str(source_result.get("utterance", "")).strip()
|
||||
if not recorded_instruction:
|
||||
source_spans = {
|
||||
@@ -676,12 +729,12 @@ class SeatDemoService:
|
||||
if len(source_spans) == 1:
|
||||
recorded_instruction = next(iter(source_spans))
|
||||
if recorded_instruction and recorded_instruction != clean_instruction:
|
||||
raise ValueError("自然语言指令已修改,请重新点击执行生成 CAN 指令流。")
|
||||
raise ValueError("自然语言指令已修改,请重新点击“生成指令”。")
|
||||
|
||||
plans = source_result.get("command_plans") or {}
|
||||
configs = source_result.get("function_configs") or {}
|
||||
if not isinstance(plans, dict) or not isinstance(configs, dict):
|
||||
raise ValueError("执行结果中没有可永久化的 CAN 指令流。")
|
||||
raise ValueError("解析结果中没有可永久化的控制指令流。")
|
||||
executable_plan_keys = {
|
||||
key
|
||||
for key, plan in plans.items()
|
||||
@@ -691,7 +744,7 @@ class SeatDemoService:
|
||||
and plan["spec"]["commands"]
|
||||
}
|
||||
if not executable_plan_keys:
|
||||
raise ValueError("请先点击执行并成功生成 CAN 控制指令流。")
|
||||
raise ValueError("请先点击“生成指令”并成功生成控制指令流。")
|
||||
|
||||
operations: list[dict[str, Any]] = []
|
||||
signatures: set[tuple[str, str, str, str]] = set()
|
||||
@@ -707,7 +760,12 @@ class SeatDemoService:
|
||||
if not isinstance(operation, dict):
|
||||
continue
|
||||
capability = str(operation.get("capability", "")).strip()
|
||||
target = str((operation.get("target") or {}).get("zone", "")).strip()
|
||||
operation_target = operation.get("target") or {}
|
||||
target = str(
|
||||
operation_target.get("instance")
|
||||
or operation_target.get("zone")
|
||||
or ""
|
||||
).strip()
|
||||
parameters = operation.get("parameters") or {}
|
||||
if not domain or not capability or not target or not isinstance(
|
||||
parameters, dict
|
||||
@@ -733,37 +791,8 @@ class SeatDemoService:
|
||||
}
|
||||
)
|
||||
if not operations:
|
||||
raise ValueError("CAN 指令流没有关联到可复用的模式操作。")
|
||||
raise ValueError("控制指令流没有关联到可复用的模式操作。")
|
||||
|
||||
execution_steps = []
|
||||
for item in (
|
||||
(source_result.get("execution_report") or {}).get("spec", {}).get(
|
||||
"results", []
|
||||
)
|
||||
):
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
telemetry = item.get("telemetry") or {}
|
||||
command_evidence = {
|
||||
key: deepcopy(telemetry[key])
|
||||
for key in (
|
||||
"command_frame",
|
||||
"sent_frames",
|
||||
"mqtt_messages",
|
||||
"response_frame",
|
||||
"requested_parameters",
|
||||
)
|
||||
if key in telemetry
|
||||
}
|
||||
if command_evidence:
|
||||
execution_steps.append(
|
||||
{
|
||||
"ref": item.get("ref"),
|
||||
"domain": item.get("domain"),
|
||||
"status": item.get("status"),
|
||||
**command_evidence,
|
||||
}
|
||||
)
|
||||
command_flow = {
|
||||
"schema": "cognitive-agent-mode-command-flow/v1",
|
||||
"source_request_id": clean_request_id,
|
||||
@@ -776,7 +805,10 @@ class SeatDemoService:
|
||||
if key in executable_plan_keys and key in configs
|
||||
},
|
||||
"plans": {key: deepcopy(plans[key]) for key in plans if key in executable_plan_keys},
|
||||
"execution_steps": execution_steps,
|
||||
# A mode card stores only the validated, deterministic plan. Live
|
||||
# delivery evidence belongs to each later execution, not to the
|
||||
# generation workflow.
|
||||
"execution_steps": [],
|
||||
}
|
||||
saved = self.mode_profiles.save_mode(
|
||||
clean_name,
|
||||
@@ -1677,6 +1709,7 @@ class SeatDemoService:
|
||||
_allow_empty_control_result: bool = False,
|
||||
_on_commands_validated: Callable[[dict[str, Any]], None] | None = None,
|
||||
_direct_can_batch: tuple[list[str], dict[str, Any]] | None = None,
|
||||
_direct_mode_profile_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
control_transport = self._validate_control_transport(control_transport)
|
||||
# Creating a reusable scene is a configuration workflow. Synthesize
|
||||
@@ -1702,6 +1735,7 @@ class SeatDemoService:
|
||||
_allow_empty_control_result,
|
||||
_on_commands_validated,
|
||||
_direct_can_batch,
|
||||
_direct_mode_profile_id,
|
||||
)
|
||||
if control_transport == WIRELESS_MQTT_TRANSPORT:
|
||||
# The head unit owns both physical CAN channels in wireless mode.
|
||||
@@ -1723,6 +1757,7 @@ class SeatDemoService:
|
||||
_allow_empty_control_result,
|
||||
_on_commands_validated,
|
||||
_direct_can_batch,
|
||||
_direct_mode_profile_id,
|
||||
)
|
||||
if hardware_access_bypass or self._driver_only_vehicle_execution_active(
|
||||
control_transport
|
||||
@@ -1744,6 +1779,7 @@ class SeatDemoService:
|
||||
_allow_empty_control_result,
|
||||
_on_commands_validated,
|
||||
_direct_can_batch,
|
||||
_direct_mode_profile_id,
|
||||
)
|
||||
authorized_client_id = self.hardware_access.ensure_authorized(hardware_client_id)
|
||||
with self.hardware_execution.reserve():
|
||||
@@ -1764,6 +1800,7 @@ class SeatDemoService:
|
||||
_allow_empty_control_result,
|
||||
_on_commands_validated,
|
||||
_direct_can_batch,
|
||||
_direct_mode_profile_id,
|
||||
)
|
||||
|
||||
def _process_once(
|
||||
@@ -1783,6 +1820,7 @@ class SeatDemoService:
|
||||
allow_empty_control_result: bool = False,
|
||||
on_commands_validated: Callable[[dict[str, Any]], None] | None = None,
|
||||
direct_can_batch: tuple[list[str], dict[str, Any]] | None = None,
|
||||
direct_mode_profile_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
control_transport = self._validate_control_transport(control_transport)
|
||||
utterance = utterance.strip()
|
||||
@@ -1831,6 +1869,14 @@ class SeatDemoService:
|
||||
if not CONVERSATION_ID_PATTERN.fullmatch(conversation_id):
|
||||
raise ValueError("conversation_id must start with conv- and contain only lowercase letters, numbers, or hyphens")
|
||||
vehicle_profile = self.vehicle_profiles.get(vehicle_id)
|
||||
direct_mode_profile = (
|
||||
self.mode_profiles.get_profile(
|
||||
direct_mode_profile_id,
|
||||
vehicle_profile.id,
|
||||
)
|
||||
if direct_mode_profile_id
|
||||
else None
|
||||
)
|
||||
|
||||
if mode_create_name is not None:
|
||||
return self._process_mode_profile_create(
|
||||
@@ -1852,7 +1898,9 @@ class SeatDemoService:
|
||||
)
|
||||
context["mode_profiles"] = self.mode_profiles.public_modes(vehicle_profile.id)
|
||||
matched_modes = (
|
||||
[]
|
||||
[direct_mode_profile]
|
||||
if direct_mode_profile is not None
|
||||
else []
|
||||
if direct_can_batch is not None
|
||||
else self.mode_profiles.match_text(
|
||||
utterance,
|
||||
@@ -1941,7 +1989,56 @@ class SeatDemoService:
|
||||
result,
|
||||
traces,
|
||||
)
|
||||
if direct_can_batch is not None:
|
||||
if direct_mode_profile is not None:
|
||||
mode_requests = self.mode_profiles.requests_for_profiles(
|
||||
[direct_mode_profile]
|
||||
)
|
||||
detected_domains = list(
|
||||
dict.fromkeys(
|
||||
str(item.get("domain") or "")
|
||||
for item in mode_requests
|
||||
if str(item.get("domain") or "")
|
||||
)
|
||||
)
|
||||
parsed = self.model_plan_adapter.adapt(
|
||||
{
|
||||
"detected_domains": detected_domains or ["seat"],
|
||||
"requests": mode_requests,
|
||||
"ambiguities": [],
|
||||
"decision_summary": [
|
||||
f"已载入模式“{direct_mode_profile.name}”保存的结构化操作。"
|
||||
],
|
||||
"confidence": 1.0,
|
||||
},
|
||||
context,
|
||||
vehicle_profile,
|
||||
)
|
||||
saved_native_actions: list[dict[str, Any]] = []
|
||||
for request in mode_requests:
|
||||
parameters = {
|
||||
str(item.get("name")): deepcopy(item.get("value"))
|
||||
for item in request.get("parameters", [])
|
||||
if isinstance(item, dict) and str(item.get("name") or "")
|
||||
}
|
||||
action = {
|
||||
"domain": str(request.get("domain") or ""),
|
||||
"capability": str(request.get("capability") or ""),
|
||||
"target": str(request.get("target_scope") or "vehicle"),
|
||||
"parameters": parameters,
|
||||
"operation": str(request.get("operation") or "set"),
|
||||
"reason": str(request.get("reason") or ""),
|
||||
"execution_backend": "native_can",
|
||||
"parse_source": "saved_mode_command_flow",
|
||||
}
|
||||
if self.native_can_runtime.supports(action):
|
||||
saved_native_actions.append(action)
|
||||
parsed["native_can_actions"] = [
|
||||
*list(parsed.get("native_can_actions", [])),
|
||||
*saved_native_actions,
|
||||
]
|
||||
provider_used = "saved_mode_command_flow"
|
||||
provider_notice = None
|
||||
elif direct_can_batch is not None:
|
||||
parsed = deepcopy(direct_can_batch[1])
|
||||
provider_used = "direct_can_control"
|
||||
provider_notice = None
|
||||
@@ -1980,6 +2077,11 @@ class SeatDemoService:
|
||||
relative_resolution.claimed_controls if relative_resolution else None
|
||||
),
|
||||
)
|
||||
if direct_mode_profile is not None:
|
||||
parsed = self._mark_control_parse_source(
|
||||
parsed,
|
||||
"saved_mode_command_flow",
|
||||
)
|
||||
if local_preview:
|
||||
parsed["ambiguities"] = []
|
||||
parsed["unresolved_spans"] = []
|
||||
@@ -1993,11 +2095,14 @@ class SeatDemoService:
|
||||
parsed = self._partition_native_actions(
|
||||
parsed,
|
||||
promote_supported=(
|
||||
hardware_request
|
||||
and (
|
||||
self.vehicle_mqtt_settings.enabled
|
||||
if control_transport == WIRELESS_MQTT_TRANSPORT
|
||||
else self.vehicle_can_settings.enabled
|
||||
direct_mode_profile is not None
|
||||
or (
|
||||
hardware_request
|
||||
and (
|
||||
self.vehicle_mqtt_settings.enabled
|
||||
if control_transport == WIRELESS_MQTT_TRANSPORT
|
||||
else self.vehicle_can_settings.enabled
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
@@ -35,6 +35,7 @@ MAX_PLAN_STEPS = 12
|
||||
MIN_PLAN_DELAY_MS = 1
|
||||
MAX_PLAN_DELAY_MS = 30_000
|
||||
MAX_TOTAL_PLAN_DELAY_MS = 60_000
|
||||
ADJACENT_INFRARED_COMMAND_DELAY_SECONDS = 0.8
|
||||
DEFAULT_GSCU_MQTT_HOST = "127.0.0.1"
|
||||
DEFAULT_GSCU_MQTT_PORT = 1883
|
||||
DEFAULT_GSCU_COMMAND_TOPIC = "topic/from_n7"
|
||||
@@ -2542,7 +2543,7 @@ class SelfExtensionService:
|
||||
and isinstance(next_step, dict)
|
||||
and next_step.get("kind") == "peripheral"
|
||||
):
|
||||
time.sleep(0.25)
|
||||
time.sleep(ADJACENT_INFRARED_COMMAND_DELAY_SECONDS)
|
||||
|
||||
succeeded = not interrupted and completed_steps == len(steps)
|
||||
status = "succeeded" if succeeded else (
|
||||
|
||||
@@ -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