优化前端,优化前端指令,优化自定义控制

This commit is contained in:
minghanchen
2026-08-24 09:40:16 +08:00
parent fff7657fea
commit 8d05815773
130 changed files with 5543 additions and 2206 deletions
@@ -4,6 +4,7 @@ from __future__ import annotations
import hashlib
import json
import os
import time
from dataclasses import dataclass
from datetime import UTC, datetime
@@ -61,11 +62,13 @@ DRIVER_SEAT_CAPABILITIES = frozenset(
"seat.set_driver_heating_native",
"seat.set_driver_massage_native",
"seat.set_driver_axis_native",
"seat.set_driver_position_native",
"seat.recall_driver_memory_native",
}
)
DRIVER_SEAT_TARGETS = frozenset({"driver", "front_left"})
UNSUPPORTED_CH1_DOOR_LOCK_CAPABILITIES = frozenset(
{"door.set_vehicle_lock", "door.set_rear_lock"}
{"door.set_vehicle_lock"}
)
@@ -76,18 +79,14 @@ def _unsupported_ch1_door_lock_message(capability: str) -> str:
"current CH1; the 0x496 smoke request caused no 0x407 vehicle-lock or "
"four-door transition, so CH1 door-lock support is observational only"
)
return (
"door.set_rear_lock is unsupported for physical execution on the current "
"CH1; 0x32B is second-row-only and is not a verified whole-vehicle lock "
"control, so CH1 door-lock support is observational only"
)
return f"{capability} is unsupported for physical execution on the current CH1"
class NativeCanRuntime:
"""Compile named capabilities into byte-exact CAN_DEV frames."""
compiler_version = "1.0.0"
mapping_version = "can-dev-verified-body-can-v4"
mapping_version = "can-dev-verified-body-can-v5"
def __init__(self, repository_root: Path):
self.root = repository_root
@@ -122,6 +121,8 @@ class NativeCanRuntime:
"climate.set_outlet_mode",
"climate.set_temperature",
"climate.set_control_mode",
"climate.set_cycle_mode",
"door.set_rear_lock",
"outlet.set_position_percent",
"outlet.set_calibrated_position",
*DRIVER_SEAT_CAPABILITIES,
@@ -185,7 +186,12 @@ class NativeCanRuntime:
"timeout_ms": (
20_000
if capability_name
in {"window.set_position", "roof.set_shade_position"}
in {
"window.set_position",
"roof.set_shade_position",
"seat.set_driver_position_native",
"seat.recall_driver_memory_native",
}
else 4_000
),
"failure_policy": "abort",
@@ -232,6 +238,7 @@ class NativeCanRuntime:
f"0x{frame_id:03X}" for frame_id in spec.feedback_ids
),
"expected_feedback": spec.expected_feedback,
"debug_command": self.operation_debug_command(operation),
}
if spec.special_handler:
signals["special_handler"] = spec.special_handler
@@ -337,8 +344,103 @@ class NativeCanRuntime:
"stop_repeat_count": special["stop_repeat_count"],
"stop_period_ms": special["stop_period_ms"],
}
elif signals.get("special_handler") == "driver_seat_memory_recall":
special = json.loads(str(signals["special_parameters"]))
public["sequence"] = {
"frame_count": len(special["frame_sequence"]),
"period_ms": special["period_ms"],
"data_hex": list(special["frame_sequence"]),
"requires_fresh_initialized_0x494": True,
}
return public
@staticmethod
def operation_debug_command(operation: dict[str, Any]) -> str:
"""Return the trusted canonical command represented by one operation."""
capability = str(operation["capability"])
parameters = operation.get("parameters", {})
target = str(operation.get("target", {}).get("zone", "vehicle")).replace(
"_", "-"
)
if capability == "window.set_motion":
return f"window {target} {parameters['motion']}"
if capability == "window.set_position":
return f"window {target} position {parameters['position_pct']}"
if capability == "climate.set_power":
return f"ac {parameters['state']}"
if capability == "climate.set_ac":
return f"ac compressor {parameters['state']}"
if capability == "climate.set_fan_level":
return f"ac fan {parameters['level']}"
if capability == "climate.set_outlet_mode":
return f"ac mode {str(parameters['mode']).replace('_', '-')}"
if capability == "climate.set_temperature":
return f"ac temp {target} {parameters['temperature_c']}"
if capability == "climate.set_control_mode":
return f"ac control {parameters['mode']}"
if capability == "climate.set_cycle_mode":
return f"ac cycle {parameters['mode']}"
if capability == "door.set_rear_lock":
return f"door rear {parameters['state']}"
if capability == "outlet.set_position_percent":
return (
f"outlet {target} percent {parameters['horizontal_pct']} "
f"{parameters['vertical_pct']}"
)
if capability == "outlet.set_calibrated_position":
return (
f"outlet {target} position {parameters['horizontal_value']} "
f"{parameters['vertical_value']}"
)
if capability in {
"seat.set_driver_heating_native",
"seat.set_driver_ventilation_native",
}:
feature = (
"heat"
if capability == "seat.set_driver_heating_native"
else "vent"
)
setting = "off"
if parameters["state"] == "on":
setting = {1: "high", 2: "low"}[int(parameters.get("level", 2))]
return f"seat driver {feature} {setting}"
if capability == "seat.set_driver_massage_native":
action = str(parameters["action"])
if action == "off":
return "seat driver massage off"
if action == "strength":
strength = {1: "weak", 2: "medium", 3: "strong"}[
int(parameters["strength"])
]
return f"seat driver massage strength {strength}"
return f"seat driver massage mode {parameters['mode']}"
if capability == "seat.set_driver_axis_native":
return (
f"seat driver {parameters['axis']} {parameters['direction']} "
f"{parameters.get('duration_ms', 500)}"
)
if capability == "seat.set_driver_position_native":
return f"seat driver position {parameters['slot']}"
if capability == "seat.recall_driver_memory_native":
return f"seat driver memory recall {parameters['slot']}"
if capability == "interior_light.set_indoor_power":
return f"light indoor {parameters['state']}"
if capability == "interior_light.set_reading_power":
return f"light reading all {parameters['state']}"
if capability == "ambient_light.set_power":
return f"light ambient {parameters['state']}"
if capability == "ambient_light.set_color_index":
return f"light ambient color {parameters['color_index']}"
if capability == "ambient_light.set_brightness_level":
return f"light ambient brightness {parameters['brightness_level']}"
if capability == "roof.set_shade_motion":
return f"shade {parameters['motion']}"
if capability == "roof.set_shade_position":
return f"shade position {parameters['position_pct']}"
return capability
@staticmethod
def _report(
function_config: dict[str, Any],
@@ -380,6 +482,23 @@ class NativeCanRuntime:
target = operation["target"]["zone"]
parameters = operation["parameters"]
if capability == "door.set_rear_lock":
self._reject_unexpected_parameters(parameters, {"state"})
state = self._enum(parameters, "state", {"lock": 1, "unlock": 2})
payload = bytearray(8)
payload[0] = 0x0A
_set_bits(payload, 14, 2, state)
return [
self._classic(
0x32B,
payload,
1,
0,
(0x407,),
f"rear_lock:{parameters['state']}",
)
]
if capability in {"climate.set_power", "climate.set_ac"}:
state = self._enum(parameters, "state", {"on": 1, "off": 2})
payload = bytearray(8)
@@ -412,6 +531,21 @@ class NativeCanRuntime:
payload = bytearray(8)
_set_bits(payload, 16, 2, mode)
return [self._classic(0x1DE, payload, 3, 100, (0x404,), f"climate_control:{mode}")]
if capability == "climate.set_cycle_mode":
self._reject_unexpected_parameters(parameters, {"mode"})
mode = self._enum(parameters, "mode", {"internal": 1, "external": 2})
payload = bytearray(8)
_set_bits(payload, 19, 2, mode)
return [
self._classic(
0x1DE,
payload,
3,
100,
(0x404,),
f"climate_cycle:{parameters['mode']}",
)
]
if capability == "climate.set_temperature":
temperature = self._integer(parameters, "temperature_c", 18, 32)
starts = {
@@ -478,6 +612,12 @@ class NativeCanRuntime:
if capability == "seat.set_driver_axis_native":
self._require_driver_seat_target(target)
return [self._driver_seat_axis_frame(parameters)]
if capability in {
"seat.set_driver_position_native",
"seat.recall_driver_memory_native",
}:
self._require_driver_seat_target(target)
return [self._driver_seat_memory_frame(parameters)]
if capability in {"outlet.set_position_percent", "outlet.set_calibrated_position"}:
return [self._outlet_frame(capability, target, parameters)]
@@ -810,6 +950,78 @@ class NativeCanRuntime:
},
)
def _driver_seat_memory_frame(
self, parameters: dict[str, Any]
) -> NativeFrameSpec:
"""Compile one learned PAD memory slot without inventing payload bytes."""
self._reject_unexpected_parameters(parameters, {"slot"})
slot = self._integer(parameters, "slot", 1, 3)
configured = os.getenv(
"COGNITIVE_AGENT_DRIVER_SEAT_MEMORY_PATH", ""
).strip()
path = (
Path(configured).expanduser()
if configured
else Path("/home/orin/project511/project-code/canfd/driver_seat_memory.json")
)
try:
document = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise NativeCanCompileError(
f"cannot load learned driver-seat memory file {path}: {exc}"
) from exc
if document.get("version") != 1 or not isinstance(
document.get("positions"), dict
):
raise NativeCanCompileError(
"driver-seat memory file must use version 1 with positions"
)
learned = document["positions"].get(str(slot))
if not isinstance(learned, dict):
raise NativeCanCompileError(
f"driver-seat memory slot {slot} has not been learned from PAD"
)
def learned_payload(name: str, sub_id: int) -> bytes:
raw = learned.get(name)
try:
payload = bytes.fromhex(str(raw))
except ValueError as exc:
raise NativeCanCompileError(
f"driver-seat memory slot {slot} has invalid {name}"
) from exc
if len(payload) != 8 or payload[0] != sub_id:
raise NativeCanCompileError(
f"driver-seat memory slot {slot} {name} must be an 8-byte "
f"0x{sub_id:02X} learned frame"
)
return payload
payload_15 = learned_payload("payload_15", 0x15)
payload_16 = learned_payload("payload_16", 0x16)
sequence = [
value.hex(" ").upper()
for _ in range(10)
for value in (payload_16, payload_15)
]
return NativeFrameSpec(
frame_id=0x4C1,
payload=payload_16,
is_fd=True,
bitrate_switch=True,
feedback_ids=(0x494,),
expected_feedback=f"driver_seat_memory_status:{slot}",
special_handler="driver_seat_memory_recall",
special_parameters={
"slot": slot,
"fresh_status_timeout_ms": 2_500,
"post_status_timeout_ms": 1_200,
"period_ms": 50,
"frame_sequence": sequence,
},
)
def _all_reading_light_frame(
self, parameters: dict[str, Any], feedback_family: str
) -> NativeFrameSpec:
@@ -876,9 +1088,11 @@ class NativeCanExecutor:
bus_factory: Callable[[], CanBus],
*,
feedback_timeout_ms: int = 1_200,
feedback_required: bool = True,
):
self.bus_factory = bus_factory
self.feedback_timeout_ms = feedback_timeout_ms
self.feedback_required = feedback_required
def execute(
self,
@@ -896,7 +1110,13 @@ class NativeCanExecutor:
try:
for command in command_plan["spec"]["commands"]:
operation = operations[command["operation_ref"]]
publication_start = len(
getattr(bus, "published_messages", ())
)
try:
set_debug_command = getattr(bus, "set_debug_command", None)
if callable(set_debug_command):
set_debug_command(command["signals"].get("debug_command", ""))
raw_feedback_ids = str(
command["signals"].get("feedback_ids", "")
).strip()
@@ -915,6 +1135,10 @@ class NativeCanExecutor:
sent_frames, feedback = self._execute_driver_seat_axis(
bus, command
)
elif special_handler == "driver_seat_memory_recall":
sent_frames, feedback = self._execute_driver_seat_memory(
bus, command
)
elif special_handler == "shade_preset":
sent_frames, feedback = self._execute_shade_preset(
bus, command
@@ -927,8 +1151,10 @@ class NativeCanExecutor:
sent_frames = self._send_command(bus, command)
feedback = self._wait_feedback(bus, command)
feedback_match = self._feedback_matches(command, feedback)
feedback_required = True
feedback_required = self.feedback_required
verified = feedback is not None and feedback_match is True
delivered = bool(sent_frames)
succeeded = verified or (delivered and not feedback_required)
physical_channel = (
feedback.channel
if feedback is not None
@@ -936,13 +1162,24 @@ class NativeCanExecutor:
if sent_frames
else None
)
mqtt_messages = list(
getattr(bus, "published_messages", ())[
publication_start:
]
)
results.append(
{
"ref": operation["id"],
"domain": function_config["spec"]["domain"],
"status": "succeeded" if verified else "failed",
"status": "succeeded" if succeeded else "failed",
"message": (
"CAN 盒已提交车端控制帧并观察到匹配反馈。"
"MQTT 白名单控制帧已发布,并收到匹配的车辆状态反馈。"
if not feedback_required and feedback_match is True
else "MQTT 白名单控制帧已发布;已收到状态帧,但目标值尚未确认。"
if not feedback_required and feedback is not None
else "MQTT 白名单控制帧已发布;车辆状态反馈尚未确认。"
if not feedback_required
else "CAN 盒已提交车端控制帧并观察到匹配反馈。"
if feedback_match is True
else "CAN 盒已提交车端控制帧,但反馈字段与目标值不一致。"
if feedback_match is False
@@ -960,8 +1197,24 @@ class NativeCanExecutor:
"feedback_match": feedback_match,
"feedback_state": self._feedback_state(feedback),
"feedback_required": feedback_required,
"delivery_status": (
"published"
if not feedback_required and delivered
else "submitted"
),
"physical_effect_confirmed": verified,
"control_gate": (
"compiled_action_and_frame_whitelist"
if not feedback_required
else "stationary_vehicle_and_feedback"
),
"channel": physical_channel,
"simulated": False,
**(
{"mqtt_messages": mqtt_messages}
if mqtt_messages
else {}
),
},
}
)
@@ -1062,6 +1315,62 @@ class NativeCanExecutor:
raise RuntimeError("; ".join(details))
return sent, self._wait_feedback(bus, command)
def _execute_driver_seat_memory(
self, bus: CanBus, command: dict[str, Any]
) -> tuple[list[CanFrame], CanFrame | None]:
"""Require fresh initialization, then replay the learned 16/15 sequence."""
signals = command["signals"]
special = json.loads(str(signals["special_parameters"]))
before = self._wait_for_id(
bus, 0x494, int(special["fresh_status_timeout_ms"])
)
initialized = (
decode_feedback(before)
.get("actuator_state", {})
.get("driver", {})
.get("position_feedback", {})
.get("initialized", {})
if before is not None
else {}
)
axes = ("horizontal", "backrest", "height", "basin")
if not all(initialized.get(axis) is True for axis in axes):
raise RuntimeError(
"no fresh 0x494 proving all four driver-seat axes initialized; "
"refusing memory recall"
)
frame_id = int(str(signals["frame_id"]), 0)
sequence = [
bytes.fromhex(str(value)) for value in special["frame_sequence"]
]
if (
len(sequence) != 20
or any(len(payload) != 8 for payload in sequence)
or any(
payload[0] != (0x16 if index % 2 == 0 else 0x15)
for index, payload in enumerate(sequence)
)
):
raise RuntimeError("compiled driver-seat memory sequence is invalid")
sent: list[CanFrame] = []
for index, payload in enumerate(sequence):
sent.append(
bus.send(
frame_id,
payload,
is_fd=True,
bitrate_switch=True,
)
)
if index + 1 < len(sequence):
time.sleep(int(special["period_ms"]) / 1_000)
feedback = self._wait_for_id(
bus, 0x494, int(special["post_status_timeout_ms"])
)
return sent, feedback
def _execute_shade_preset(
self, bus: CanBus, command: dict[str, Any]
) -> tuple[list[CanFrame], CanFrame | None]: