This commit is contained in:
orin
2026-07-30 08:45:38 +08:00
commit 25b24ac4cd
1333 changed files with 170092 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
# Seat Tests
`unit/` 覆盖姿态参数和运动学转换,`contract/` 覆盖配置与插件契约,`integration/` 使用模拟 CAN Gateway 覆盖命令、回读、互锁和超时。实车范围需与平台安全标定分别测试,不能只依赖 JSON Schema 的宽范围。
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
Binary file not shown.
Binary file not shown.
+104
View File
@@ -0,0 +1,104 @@
from __future__ import annotations
import unittest
from pathlib import Path
import yaml
from cognitive_agent.runtime.contracts import ContractValidator
from seat_domain.generator import build_function_config
from seat_domain.intent import parse_local_intent
from seat_domain.matrix import SeatCanMatrix
from seat_domain.source_matrix import read_source_signals
ROOT = Path(__file__).resolve().parents[4]
DOMAIN_ROOT = ROOT / "domains" / "seat"
class SeatContractTests(unittest.TestCase):
def setUp(self) -> None:
self.validator = ContractValidator(ROOT)
def test_generated_function_config_matches_both_schemas(self) -> None:
parsed = parse_local_intent("打开副驾座椅加热")
config = build_function_config(parsed, "req-contract-001", "local_rules")
self.assertEqual(self.validator.validate(config), [])
def test_inferred_level_function_config_matches_both_schemas(self) -> None:
parsed = parse_local_intent("主驾有点热,有点闷")
config = build_function_config(parsed, "req-contract-002", "local_rules")
self.assertEqual(config["spec"]["operations"][0]["parameters"]["level"], 1)
self.assertEqual(self.validator.validate(config), [])
def test_motion_function_config_matches_both_schemas(self) -> None:
parsed = {
"detected_domains": ["seat"],
"actions": [
{
"action_type": "position",
"target": "passenger",
"axis": "legrest_extension",
"position_pct": 70,
}
],
"deferred_requests": [],
"ambiguities": [],
"decision_summary": ["Extend passenger leg rest."],
"confidence": 1.0,
}
config = build_function_config(parsed, "req-contract-motion", "test")
self.assertEqual(config["spec"]["operations"][0]["parameters"], {"position_pct": 70})
self.assertEqual(self.validator.validate(config), [])
def test_all_active_presets_match_both_schemas(self) -> None:
for path in (DOMAIN_ROOT / "config" / "presets").glob("*.yaml"):
with self.subTest(path=path.name):
document = yaml.safe_load(path.read_text(encoding="utf-8"))
self.assertEqual(self.validator.validate(document), [])
def test_normalized_matrix_matches_source_scope(self) -> None:
matrix = SeatCanMatrix(DOMAIN_ROOT / "can" / "seat-thermal-matrix.yaml")
self.assertEqual(len(matrix.data["messages"]), 6)
self.assertEqual(sum(len(item["signals"]) for item in matrix.data["messages"]), 23)
self.assertFalse(matrix.hardware_execution_allowed)
def test_synthetic_motion_matrix_is_explicitly_non_hardware(self) -> None:
matrix = SeatCanMatrix(DOMAIN_ROOT / "can" / "seat-motion-virtual-matrix.yaml")
self.assertTrue(matrix.data["synthetic"])
self.assertFalse(matrix.hardware_execution_allowed)
self.assertEqual(len(matrix.data["messages"]), 4)
self.assertEqual(sum(len(item["signals"]) for item in matrix.data["messages"]), 34)
def test_normalized_signal_layout_matches_supplied_xlsx(self) -> None:
source = {
item["name"]: item
for item in read_source_signals(ROOT / "座椅部分功能CAN矩阵表.xlsx")
}
matrix = SeatCanMatrix(DOMAIN_ROOT / "can" / "seat-thermal-matrix.yaml")
normalized = {
signal["name"]: {
"frame_id": int(message["frame_id"]),
"start_bit": int(signal["start_bit"]),
"bit_length": int(signal["bit_length"]),
}
for message in matrix.data["messages"]
for signal in message["signals"]
}
self.assertEqual(set(source), set(normalized))
for name, layout in normalized.items():
with self.subTest(signal=name):
self.assertEqual(layout["frame_id"], source[name]["frame_id"])
self.assertEqual(layout["start_bit"], source[name]["start_bit"])
self.assertEqual(layout["bit_length"], source[name]["bit_length"])
if __name__ == "__main__":
unittest.main()
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
Binary file not shown.
Binary file not shown.
+610
View File
@@ -0,0 +1,610 @@
from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
class SeatServiceIntegrationTests(unittest.TestCase):
def setUp(self) -> None:
self.service = SeatDemoService(REPOSITORY_ROOT)
self.temporary_directory = tempfile.TemporaryDirectory()
self.addCleanup(self.temporary_directory.cleanup)
self.service.artifact_root = Path(self.temporary_directory.name)
def test_supported_request_completes_full_dry_run(self) -> None:
result = self.service.process(
"打开主驾座椅通风,同时关闭副驾座椅加热", provider="local"
)
self.assertEqual(result["status"], "succeeded")
self.assertEqual(len(result["function_config"]["spec"]["operations"]), 2)
telemetry = result["execution_report"]["spec"]["results"][0]["telemetry"]
self.assertEqual(telemetry["command_frame"]["data_hex"], "01 80")
self.assertTrue(Path(result["artifact_path"]).is_absolute())
self.assertTrue(
(self.service.artifact_root / result["request_id"] / "result.json").is_file()
)
for event in result["trace_events"]:
self.assertEqual(self.service.contracts.validate(event), [])
def test_gear_request_is_preserved_and_simulated(self) -> None:
result = self.service.process("主驾座椅加热开到三档", provider="local")
self.assertEqual(result["status"], "succeeded")
operation = result["function_config"]["spec"]["operations"][0]
self.assertEqual(operation["parameters"]["level"], 3)
telemetry = result["execution_report"]["spec"]["results"][0]["telemetry"]
self.assertEqual(telemetry["effective_level"], 3)
self.assertTrue(telemetry["level_command_simulated"])
self.assertIn(
"LEVEL_COMMAND_SIMULATED", {issue["code"] for issue in result["validation"]}
)
def test_consecutive_feedback_uses_previous_successful_state(self) -> None:
conversation_id = "conv-test-context"
first = self.service.process(
"主驾有点热,有点闷",
provider="local",
conversation_id=conversation_id,
)
second = self.service.process(
"主驾还有点闷",
provider="local",
conversation_id=conversation_id,
)
self.assertEqual(first["status"], "succeeded")
self.assertEqual(first["actuator_state"]["driver"]["ventilation"]["level"], 1)
self.assertEqual(second["status"], "succeeded")
self.assertEqual(second["conversation_turn"], 2)
self.assertEqual(second["actuator_state"]["driver"]["ventilation"]["level"], 2)
operation = second["function_config"]["spec"]["operations"][0]
self.assertEqual(operation["parameters"], {"state": "on", "level": 2})
self.assertIn("提升到2档", "".join(second["decision_summary"]))
def test_four_turn_causal_feedback_sequence(self) -> None:
conversation_id = "conv-causal-sequence"
utterances = [
"我有点热",
"我有点冷",
"我还有点冷",
"副驾吹的我有点冷",
]
first, second, third, fourth = [
self.service.process(text, "local", conversation_id) for text in utterances
]
self.assertEqual(
first["function_config"]["spec"]["operations"][0]["parameters"],
{"state": "on", "level": 1},
)
second_operation = second["function_config"]["spec"]["operations"][0]
self.assertEqual(second_operation["capability"], "seat.set_ventilation")
self.assertEqual(second_operation["parameters"], {"state": "off"})
third_operation = third["function_config"]["spec"]["operations"][0]
self.assertEqual(third_operation["capability"], "seat.set_heating")
self.assertEqual(third_operation["parameters"], {"state": "on", "level": 1})
fourth_operation = fourth["function_config"]["spec"]["operations"][0]
self.assertEqual(fourth_operation["target"]["instance"], "passenger")
self.assertEqual(fourth_operation["capability"], "seat.set_ventilation")
self.assertEqual(fourth_operation["parameters"], {"state": "off"})
self.assertEqual(fourth["actuator_state"]["driver"]["heating"], {"state": "on", "level": 1})
self.assertEqual(fourth["conversation_turn"], 4)
def test_compound_cold_and_stuffy_feedback_generates_balanced_dry_run(self) -> None:
result = self.service.process(
"主驾有点冷、有点闷",
provider="local",
conversation_id="conv-compound-comfort",
)
self.assertEqual(result["status"], "succeeded")
operations = result["function_config"]["spec"]["operations"]
self.assertEqual(
{operation["capability"] for operation in operations},
{"seat.set_heating", "seat.set_ventilation"},
)
command = result["command_plan"]["spec"]["commands"][0]
self.assertEqual(
command["signals"],
{"Driver_Seat_Heating_S": 1, "Driver_Seat_Ventilation_S": 1},
)
self.assertIn(
"COMBINED_THERMAL_SIMULATED",
{issue["code"] for issue in result["validation"]},
)
def test_vehicle_scope_is_routed_away_from_seat_domain(self) -> None:
result = self.service.process(
"车上太闷了",
provider="local",
conversation_id="conv-vehicle-scope",
)
self.assertEqual(result["status"], "needs_confirmation")
self.assertEqual(result["intent_ir"]["spec"]["detected_domains"], ["climate"])
self.assertIsNone(result["function_config"])
self.assertIsNone(result["command_plan"])
self.assertEqual(result["actuator_state"]["passenger"]["ventilation"]["state"], "off")
route = next(event for event in result["trace_events"] if event["stage"] == "domain_route")
self.assertEqual(route["details"]["domains"], ["climate"])
def test_openai_output_is_not_rewritten_by_local_semantic_planner(self) -> None:
model_output = {
"detected_domains": ["seat"],
"requests": [
{
"domain": "seat",
"capability": "seat.set_ventilation",
"target_scope": "passenger",
"operation": "deactivate",
"parameters": [{"name": "state", "value": "off"}],
"reason": "model-owned-plan",
}
],
"ambiguities": [],
"decision_summary": ["model-owned-plan"],
"confidence": 0.91,
}
context = self.service.conversations.snapshot("conv-model-owned-plan")
self.service.openai.api_key = "test-key"
with patch.object(self.service.openai, "interpret", return_value=model_output) as interpret:
parsed, provider, notice = self.service._interpret(
"车上太闷了", "openai", context
)
self.assertEqual(parsed["decision_summary"], ["model-owned-plan"])
self.assertEqual(parsed["model_requests"], model_output["requests"])
self.assertEqual(
parsed["actions"],
[
{
"target": "passenger",
"function": "ventilation",
"state": "off",
"requested_level": None,
}
],
)
self.assertEqual(provider, f"openai:{self.service.openai.model}")
self.assertIsNone(notice)
interpret.assert_called_once_with(
"车上太闷了", context, self.service.capability_context
)
def test_model_climate_request_completes_domain_loop(self) -> None:
model_output = {
"detected_domains": ["climate"],
"requests": [
{
"domain": "climate",
"capability": "climate.set_fan_level",
"target_scope": "vehicle",
"operation": "increase",
"parameters": [{"name": "level", "value": 3}],
"reason": "Increase cabin airflow.",
}
],
"ambiguities": [],
"decision_summary": ["整车舱风量增加需求。"],
"confidence": 0.94,
}
self.service.openai.api_key = "test-key"
with patch.object(self.service.openai, "interpret", return_value=model_output):
result = self.service.process(
"车上太闷了",
"openai",
"conv-model-deferred",
)
self.assertEqual(result["status"], "succeeded")
self.assertEqual(result["model_requests"], model_output["requests"])
self.assertEqual(result["deferred_requests"], [])
self.assertIn("climate", result["function_configs"])
request = result["intent_ir"]["spec"]["requests"][0]
self.assertEqual(request["domain"], "climate")
self.assertEqual(request["parameters"]["capability"], "climate.set_fan_level")
def test_climate_and_seat_requests_generate_scene(self) -> None:
model_output = {
"detected_domains": ["seat", "climate"],
"requests": [
{
"domain": "seat",
"capability": "seat.set_heating",
"target_scope": "driver",
"operation": "activate",
"parameters": [
{"name": "state", "value": "on"},
{"name": "level", "value": 1},
],
"reason": "Warm the driver seat.",
},
{
"domain": "climate",
"capability": "climate.set_fan_level",
"target_scope": "vehicle",
"operation": "set",
"parameters": [{"name": "level", "value": 2}],
"reason": "Improve cabin airflow.",
},
],
"ambiguities": [],
"decision_summary": ["座椅加热与整车空调风量并行执行。"],
"confidence": 0.9,
}
self.service.openai.api_key = "test-key"
with patch.object(self.service.openai, "interpret", return_value=model_output):
result = self.service.process(
"主驾有点冷、有点闷",
"openai",
"conv-model-partial",
)
self.assertEqual(result["status"], "succeeded")
self.assertIsNotNone(result["execution_report"])
self.assertEqual(result["actuator_state"]["driver"]["heating"], {"state": "on", "level": 1})
self.assertEqual(result["domain_state"]["climate"]["fan_level"], 2)
self.assertEqual(set(result["function_configs"]), {"seat", "climate"})
self.assertEqual(result["scene_config"]["kind"], "SceneConfig")
def test_model_motion_requests_complete_control_and_feedback_loop(self) -> None:
model_output = {
"detected_domains": ["seat"],
"requests": [
{
"domain": "seat",
"capability": "seat.set_longitudinal_position",
"target_scope": "driver",
"operation": "set",
"parameters": [{"name": "position_pct", "value": 35}],
"reason": "Move the driver seat backward from its current midpoint.",
},
{
"domain": "seat",
"capability": "seat.set_backrest_recline",
"target_scope": "driver",
"operation": "set",
"parameters": [{"name": "position_pct", "value": 60}],
"reason": "Recline the driver backrest.",
},
{
"domain": "seat",
"capability": "seat.set_legrest_height",
"target_scope": "passenger",
"operation": "set",
"parameters": [{"name": "position_pct", "value": 65}],
"reason": "Raise the passenger leg rest.",
},
{
"domain": "seat",
"capability": "seat.set_legrest_extension",
"target_scope": "passenger",
"operation": "set",
"parameters": [{"name": "position_pct", "value": 70}],
"reason": "Extend the passenger leg rest.",
},
],
"ambiguities": [],
"decision_summary": ["四个电机轴使用绝对位置目标。"],
"confidence": 0.93,
}
self.service.openai.api_key = "test-key"
with patch.object(self.service.openai, "interpret", return_value=model_output):
result = self.service.process(
"主驾往后一点并把靠背躺下,副驾腿托升起并伸长",
"openai",
"conv-model-motion",
)
self.assertEqual(result["status"], "succeeded")
self.assertEqual(len(result["function_config"]["spec"]["operations"]), 4)
self.assertEqual(len(result["command_plan"]["spec"]["commands"]), 4)
self.assertEqual(len(result["execution_report"]["spec"]["results"]), 4)
driver = result["actuator_state"]["driver"]["position"]
passenger = result["actuator_state"]["passenger"]["position"]
self.assertEqual(driver["longitudinal"]["position_pct"], 35)
self.assertEqual(driver["backrest_recline"]["position_pct"], 60)
self.assertEqual(passenger["legrest_height"]["position_pct"], 65)
self.assertEqual(passenger["legrest_extension"]["position_pct"], 70)
telemetry = result["execution_report"]["spec"]["results"][0]["telemetry"]
self.assertEqual(telemetry["start_position_pct"], 50)
self.assertEqual(telemetry["effective_position_pct"], 35)
self.assertEqual(telemetry["feedback_frame"]["frame_id"], "0x558")
self.assertEqual(telemetry["fault_mask"], 0)
codes = {issue["code"] for issue in result["validation"]}
self.assertIn("SYNTHETIC_MOTION_CAN", codes)
self.assertIn("DRIVER_MOTION_INTERLOCK_NOT_IMPLEMENTED", codes)
def test_motion_follow_up_state_is_supplied_to_model(self) -> None:
def output(position: int) -> dict[str, object]:
return {
"detected_domains": ["seat"],
"requests": [
{
"domain": "seat",
"capability": "seat.set_longitudinal_position",
"target_scope": "driver",
"operation": "decrease",
"parameters": [{"name": "position_pct", "value": position}],
"reason": "Model resolved the relative request.",
}
],
"ambiguities": [],
"decision_summary": [f"Move to {position}%."],
"confidence": 0.9,
}
self.service.openai.api_key = "test-key"
with patch.object(
self.service.openai,
"interpret",
side_effect=[output(40), output(30)],
) as interpret:
first = self.service.process(
"主驾往后一点", "openai", "conv-motion-follow-up"
)
second = self.service.process(
"还是靠前,再往后一点", "openai", "conv-motion-follow-up"
)
self.assertEqual(
first["actuator_state"]["driver"]["position"]["longitudinal"][
"position_pct"
],
40,
)
self.assertEqual(
second["actuator_state"]["driver"]["position"]["longitudinal"][
"position_pct"
],
30,
)
second_context = interpret.call_args_list[1].args[1]
self.assertEqual(
second_context["devices"]["driver"]["position"]["longitudinal"]["position_pct"],
40,
)
def test_motion_adapter_does_not_resolve_missing_absolute_target(self) -> None:
model_output = {
"detected_domains": ["seat"],
"requests": [
{
"domain": "seat",
"capability": "seat.set_longitudinal_position",
"target_scope": "driver",
"operation": "decrease",
"parameters": [],
"reason": "No absolute target was produced.",
}
],
"ambiguities": [],
"decision_summary": ["Unresolved relative request."],
"confidence": 0.6,
}
self.service.openai.api_key = "test-key"
with patch.object(self.service.openai, "interpret", return_value=model_output):
result = self.service.process(
"主驾往后一点", "openai", "conv-motion-missing-target"
)
self.assertEqual(result["status"], "needs_confirmation")
self.assertIsNone(result["function_config"])
self.assertIn(
"AMBIGUOUS_INTENT", {issue["code"] for issue in result["validation"]}
)
self.assertNotIn(
"NO_EXECUTABLE_OPERATION",
{issue["code"] for issue in result["validation"]},
)
self.assertNotIn(
"PUBLIC_SCHEMA", {issue["code"] for issue in result["validation"]}
)
def test_exact_angles_and_named_preset_complete_conversation_loop(self) -> None:
def plan(requests: list[dict[str, object]], summary: str) -> dict[str, object]:
return {
"detected_domains": ["seat"],
"requests": requests,
"ambiguities": [],
"decision_summary": [summary],
"confidence": 0.96,
}
def request(
capability: str,
operation: str,
parameters: list[dict[str, object]],
) -> dict[str, object]:
return {
"domain": "seat",
"capability": capability,
"target_scope": "passenger",
"operation": operation,
"parameters": parameters,
"reason": "Model resolved the user's posture goal against runtime context.",
}
first_plan = plan(
[
request(
"seat.set_backrest_recline",
"set",
[{"name": "angle_deg", "value": 128}],
),
request(
"seat.set_legrest_height",
"set",
[{"name": "angle_deg", "value": 133}],
),
],
"副驾靠背/坐垫夹角设为128度,坐垫/腿托夹角设为133度。",
)
recline_plan = plan(
[
request(
"seat.set_backrest_recline",
"increase",
[{"name": "angle_deg", "value": 133}],
)
],
"根据当前128度姿态,将副驾靠背继续后仰至133度。",
)
save_plan = plan(
[
request(
"seat.save_posture_preset",
"save",
[{"name": "preset_name", "value": "零重力模式"}],
)
],
"将副驾当前五轴姿态保存为零重力模式。",
)
leave_preset_plan = plan(
[
request(
"seat.set_backrest_recline",
"set",
[{"name": "angle_deg", "value": 105}],
)
],
"将副驾靠背恢复到105度用于验证预设回调。",
)
apply_plan = plan(
[
request(
"seat.apply_posture_preset",
"apply",
[{"name": "preset_name", "value": "零重力模式"}],
)
],
"调用已保存的副驾零重力模式。",
)
self.service.openai.api_key = "test-key"
conversation_id = "conv-angle-preset-loop"
with patch.object(
self.service.openai,
"interpret",
side_effect=[
first_plan,
recline_plan,
save_plan,
leave_preset_plan,
apply_plan,
],
) as interpret:
first = self.service.process(
"副驾调整为零重力模式,要求靠背座椅夹角128度,坐垫脚托夹角133度",
"openai",
conversation_id,
)
second = self.service.process(
"靠背再向后一点", "openai", conversation_id
)
saved = self.service.process(
"将当前状态保存为零重力模式", "openai", conversation_id
)
changed = self.service.process(
"副驾靠背恢复直立一些", "openai", conversation_id
)
applied = self.service.process(
"副驾恢复到零重力模式", "openai", conversation_id
)
first_operations = first["function_config"]["spec"]["operations"]
self.assertEqual(
first_operations[0]["parameters"],
{"position_pct": 73, "angle_deg": 128},
)
self.assertEqual(
first_operations[1]["parameters"],
{"position_pct": 58, "angle_deg": 133},
)
self.assertEqual(
first["actuator_state"]["passenger"]["position"]["backrest_recline"][
"angle_deg"
],
128,
)
first_telemetry = first["execution_report"]["spec"]["results"][0]["telemetry"]
self.assertEqual(first_telemetry["requested_angle_deg"], 128)
self.assertEqual(first_telemetry["effective_position_pct"], 73)
self.assertEqual(
second["actuator_state"]["passenger"]["position"]["backrest_recline"][
"angle_deg"
],
133,
)
relative_motion = second["function_config"]["spec"]["operations"][0][
"motion"
]
self.assertEqual(relative_motion["mode"], "relative")
self.assertEqual(relative_motion["direction"], "increase")
self.assertEqual(relative_motion["start_position_pct"], 73)
self.assertEqual(relative_motion["delta_position_pct"], 11)
self.assertEqual(relative_motion["delta_angle_deg"], 5)
second_context = interpret.call_args_list[1].args[1]
self.assertEqual(
second_context["devices"]["passenger"]["position"]["backrest_recline"][
"angle_deg"
],
128,
)
self.assertEqual(saved["status"], "succeeded")
self.assertIsNone(saved["function_config"])
self.assertEqual(saved["preset_config"]["kind"], "FunctionPreset")
self.assertEqual(saved["preset_config"]["metadata"]["name"], "零重力模式")
self.assertFalse(
saved["execution_report"]["spec"]["results"][0]["telemetry"][
"can_command_emitted"
]
)
self.assertEqual(
saved["saved_presets"],
[{"name": "零重力模式", "target": "passenger"}],
)
preset_path = (
self.service.artifact_root
/ "user-presets"
/ f"{conversation_id}.json"
)
self.assertTrue(preset_path.is_file())
self.assertEqual(
changed["actuator_state"]["passenger"]["position"]["backrest_recline"][
"angle_deg"
],
105,
)
self.assertEqual(applied["status"], "succeeded")
restored = applied["actuator_state"]["passenger"]["position"]
self.assertEqual(restored["backrest_recline"]["angle_deg"], 133)
self.assertEqual(restored["legrest_height"]["angle_deg"], 133)
self.assertEqual(len(applied["function_config"]["spec"]["operations"]), 5)
apply_context = interpret.call_args_list[4].args[1]
self.assertEqual(apply_context["presets"][0]["name"], "零重力模式")
def test_missing_target_requests_confirmation(self) -> None:
result = self.service.process("打开座椅通风", provider="local")
self.assertEqual(result["status"], "needs_confirmation")
self.assertIsNone(result["function_config"])
if __name__ == "__main__":
unittest.main()
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+140
View File
@@ -0,0 +1,140 @@
from __future__ import annotations
import unittest
from seat_domain.intent import parse_local_intent
class LocalIntentParserTests(unittest.TestCase):
@staticmethod
def context(level: int = 0) -> dict:
return {
"last_target": "driver" if level else None,
"devices": {
"driver": {
"ventilation": {"state": "on" if level else "off", "level": level},
"heating": {"state": "off", "level": 0},
},
"passenger": {
"ventilation": {"state": "off", "level": 0},
"heating": {"state": "off", "level": 0},
},
},
}
def test_parses_driver_ventilation_on(self) -> None:
result = parse_local_intent("打开主驾座椅通风")
self.assertEqual(
result["actions"],
[
{
"target": "driver",
"function": "ventilation",
"state": "on",
"requested_level": None,
}
],
)
self.assertEqual(result["ambiguities"], [])
def test_expands_front_seats(self) -> None:
result = parse_local_intent("主副驾座椅通风都打开")
self.assertEqual({item["target"] for item in result["actions"]}, {"driver", "passenger"})
self.assertTrue(all(item["state"] == "on" for item in result["actions"]))
def test_keeps_requested_level_for_safety_validation(self) -> None:
result = parse_local_intent("主驾座椅加热开到三档")
self.assertEqual(result["actions"][0]["requested_level"], 3)
def test_requires_target(self) -> None:
result = parse_local_intent("打开座椅通风")
self.assertEqual(result["actions"], [])
self.assertEqual(result["ambiguities"][0]["field"], "target")
def test_maps_mild_hot_and_stuffy_feedback_to_level_one(self) -> None:
result = parse_local_intent("主驾有点热,有点闷", self.context())
self.assertEqual(
result["actions"],
[
{
"target": "driver",
"function": "ventilation",
"state": "on",
"requested_level": 1,
}
],
)
self.assertIn("热、闷", "".join(result["decision_summary"]))
def test_increments_active_level_for_continuing_discomfort(self) -> None:
result = parse_local_intent("还有点闷", self.context(level=1))
self.assertEqual(result["actions"][0]["requested_level"], 2)
self.assertEqual(result["actions"][0]["target"], "driver")
self.assertIn("提升到2档", "".join(result["decision_summary"]))
def test_cold_feedback_reduces_active_ventilation_before_heating(self) -> None:
result = parse_local_intent("我有点冷", self.context(level=1))
self.assertEqual(
result["actions"],
[
{
"target": "driver",
"function": "ventilation",
"state": "off",
"requested_level": None,
}
],
)
self.assertIn("不立即开启加热", "".join(result["decision_summary"]))
def test_airflow_cause_targets_ventilation_not_heating(self) -> None:
context = self.context()
context["devices"]["passenger"]["ventilation"] = {"state": "on", "level": 2}
result = parse_local_intent("副驾吹的我有点冷", context)
self.assertEqual(
result["actions"],
[
{
"target": "passenger",
"function": "ventilation",
"state": "on",
"requested_level": 1,
}
],
)
self.assertNotIn("heating", {action["function"] for action in result["actions"]})
self.assertIn("致冷来源", "".join(result["decision_summary"]))
def test_cold_and_stuffy_are_independent_comfort_dimensions(self) -> None:
result = parse_local_intent("主驾有点冷、有点闷", self.context())
self.assertEqual(
{
(action["function"], action["state"], action["requested_level"])
for action in result["actions"]
},
{("heating", "on", 1), ("ventilation", "on", 1)},
)
self.assertEqual(result["ambiguities"], [])
self.assertIn("不将其判定为语义冲突", "".join(result["decision_summary"]))
def test_vehicle_scope_does_not_inherit_a_seat_target(self) -> None:
result = parse_local_intent("车上太闷了", self.context(level=1))
self.assertEqual(result["actions"], [])
self.assertEqual(result["ambiguities"][0]["field"], "domain")
self.assertEqual(result["detected_domains"], ["climate"])
self.assertEqual(result["scope"], "vehicle")
self.assertIn("整车舱范围", "".join(result["decision_summary"]))
if __name__ == "__main__":
unittest.main()
+84
View File
@@ -0,0 +1,84 @@
from __future__ import annotations
import unittest
from pathlib import Path
from seat_domain.compiler import SeatCompiler
from seat_domain.generator import build_function_config
from seat_domain.intent import parse_local_intent
from seat_domain.matrix import SeatCanMatrix
DOMAIN_ROOT = Path(__file__).resolve().parents[2]
class MatrixCompilerTests(unittest.TestCase):
def setUp(self) -> None:
self.matrix = SeatCanMatrix(DOMAIN_ROOT / "can" / "seat-thermal-matrix.yaml")
self.combined_matrix = SeatCanMatrix(
[
DOMAIN_ROOT / "can" / "seat-thermal-matrix.yaml",
DOMAIN_ROOT / "can" / "seat-motion-virtual-matrix.yaml",
]
)
self.compiler = SeatCompiler(DOMAIN_ROOT / "can" / "signal-mapping.yaml")
def test_encodes_combined_control_bits(self) -> None:
parsed = parse_local_intent("打开主驾座椅通风,同时关闭副驾座椅加热")
config = build_function_config(parsed, "req-test-001", "local_rules")
plan = self.compiler.compile(config)
command = plan["spec"]["commands"][0]
frame = self.matrix.encode(command["message"], command["signals"])
self.assertEqual(frame["frame_id"], "0x42A")
self.assertEqual(frame["data_hex"], "01 80")
def test_compilation_is_deterministic(self) -> None:
parsed = parse_local_intent("关闭副驾座椅通风")
config = build_function_config(parsed, "req-test-002", "local_rules")
self.assertEqual(self.compiler.compile(config), self.compiler.compile(config))
def test_compiles_each_motion_axis_with_feedback_completion(self) -> None:
parsed = {
"detected_domains": ["seat"],
"actions": [
{
"action_type": "position",
"target": "driver",
"axis": "longitudinal",
"position_pct": 35,
},
{
"action_type": "position",
"target": "driver",
"axis": "backrest_recline",
"position_pct": 60,
},
],
"deferred_requests": [],
"ambiguities": [],
"decision_summary": ["Move two driver-seat axes."],
"confidence": 1.0,
}
config = build_function_config(parsed, "req-motion-001", "test")
plan = self.compiler.compile(config)
self.assertEqual(len(plan["spec"]["commands"]), 2)
first, second = plan["spec"]["commands"]
self.assertEqual(first["message"], "Driver_Seat_Motion_Command_550")
self.assertEqual(first["signals"]["Longitudinal_Target_Pct"], 35)
self.assertEqual(first["signals"]["Motion_Enable_Mask"], 1)
self.assertEqual(first["completion"]["message"], "Driver_Seat_Motion_Status_558")
self.assertEqual(first["completion"]["signal"], "Longitudinal_Actual_Pct")
self.assertEqual(second["signals"]["Backrest_Recline_Target_Pct"], 60)
self.assertEqual(second["signals"]["Motion_Enable_Mask"], 4)
frame = self.combined_matrix.encode(first["message"], first["signals"])
self.assertEqual(frame["frame_id"], "0x550")
self.assertEqual(frame["payload_size"], 8)
if __name__ == "__main__":
unittest.main()
+835
View File
@@ -0,0 +1,835 @@
from __future__ import annotations
import os
import sys
import unittest
from collections import deque
from dataclasses import replace
from pathlib import Path
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[4]
SEAT_SOURCE = ROOT / "domains" / "seat" / "src"
if str(SEAT_SOURCE) not in sys.path:
sys.path.insert(0, str(SEAT_SOURCE))
from cognitive_agent.infrastructure.can.gateway import CanFrame # noqa: E402
from cognitive_agent.application.seat_service import SeatDemoService # noqa: E402
from cognitive_agent.infrastructure.can.hardware import ( # noqa: E402
HardwareSafetyError,
SeatHardwareSettings,
)
from cognitive_agent.infrastructure.firmware import ( # noqa: E402
EXPECTED_IDENTITY_PAYLOAD,
FirmwareProgrammingError,
T2GFirmwareGuard,
T2GSeatProgrammer,
)
from seat_domain import HardwareSeatExecutor, SeatCanMatrix, T2GSeatCompiler # noqa: E402
def function_config(*operations: dict[str, object]) -> dict[str, object]:
return {
"apiVersion": "cognitive-agent/v1alpha1",
"kind": "FunctionConfig",
"metadata": {
"id": "function-test-hardware",
"name": "T2G hardware test",
"version": "1.0.0",
"request_id": "req-hardware-test",
},
"spec": {"domain": "seat", "operations": list(operations)},
}
def operation(
capability: str,
parameters: dict[str, object],
*,
operation_id: str = "op-hardware-test",
instance: str = "driver",
zone: str = "front_left",
motion: dict[str, object] | None = None,
) -> dict[str, object]:
result = {
"id": operation_id,
"capability": capability,
"target": {"instance": instance, "zone": zone},
"parameters": parameters,
"timeout_ms": 20,
}
if motion is not None:
result["motion"] = motion
return result
class ReactiveFakeBus:
def __init__(
self,
matrix: SeatCanMatrix,
*,
complete_motion: bool = True,
position_valid: bool = True,
timed_failure_result: int | None = None,
timed_failure_event: int = 4,
):
self.matrix = matrix
self.complete_motion = complete_motion
self.position_valid = position_valid
self.timed_failure_result = timed_failure_result
self.timed_failure_event = timed_failure_event
self.hall = [0, 0, 0, 0]
self.max_hall = [480, 790, 1590, 960]
self.sent: list[CanFrame] = []
self.received: deque[CanFrame] = deque()
self.closed = False
def _frame(self, message: str, values: dict[str, int]) -> CanFrame:
encoded = self.matrix.encode(message, values)
return CanFrame(int(encoded["frame_id"], 16), bytes.fromhex(encoded["data_hex"]))
def send(self, arbitration_id: int, data: bytes) -> CanFrame:
frame = CanFrame(arbitration_id, bytes(data))
self.sent.append(frame)
if arbitration_id == 0x300:
command = self.matrix.decode("T2G_Seat_Move_Command_300", data)
sequence = command["Sequence"]
opcode = command["Opcode"]
self.received.append(
self._frame(
"T2G_Seat_Move_System_Status_301",
{
"Ack": 1,
"Reject_Reason": 0,
"Last_Opcode": opcode,
"Last_Sequence": sequence,
"Safety_Inhibit": 0,
"Safety_Fault": 0,
},
)
)
if opcode == 9:
for motor in range(4):
self.received.append(
self._frame(
"T2G_Seat_Move_Motor_Status_302",
{
"Motor_Sub_ID": motor,
"Task_State": 0,
"Result": 0,
"Position_Valid": int(self.position_valid),
"Motor_Sequence": sequence,
"Value_X100": 0,
"Duty_Percent": 0,
},
)
)
for motor in range(4):
self.received.append(
self._frame(
"T2G_Seat_Move_Diagnostic_303",
{
"Motor_Sub_ID": motor,
"Hall_Count": self.hall[motor],
"Max_Hall_Count": self.max_hall[motor],
"Max_Speed_Pps_X100": 9000,
},
)
)
if opcode == 1 and self.complete_motion:
self.received.append(
self._frame(
"T2G_Seat_Move_Motor_Status_302",
{
"Motor_Sub_ID": command["Motor_Selector"],
"Task_State": 8,
"Result": 2,
"Position_Valid": 1,
"Motor_Sequence": sequence,
"Value_X100": command["Param_A"],
"Duty_Percent": command["Param_B"],
},
)
)
if opcode == 3 and self.timed_failure_result is not None:
motor = command["Motor_Selector"]
self.received.append(
self._frame(
"T2G_Seat_Move_Motor_Status_302",
{
"Motor_Sub_ID": motor,
"Task_State": 9,
"Result": self.timed_failure_result,
"Direction": command["Direction"],
"Position_Valid": int(self.position_valid),
"Motor_Sequence": sequence,
"Value_X100": 0,
"Duty_Percent": command["Param_B"],
},
)
)
self.received.append(
self._frame(
"T2G_Seat_Move_Diagnostic_303",
{
"Motor_Sub_ID": motor,
"Last_Motor_Event": self.timed_failure_event,
"Hall_Count": self.hall[motor],
"Max_Hall_Count": self.max_hall[motor],
"Max_Speed_Pps_X100": 9000,
},
)
)
elif opcode == 3 and self.complete_motion:
motor = command["Motor_Selector"]
delta = max(1, command["Param_A"] // 35)
if command["Direction"] == 1:
self.hall[motor] = min(
self.max_hall[motor], self.hall[motor] + delta
)
else:
self.hall[motor] = max(0, self.hall[motor] - delta)
self.received.append(
self._frame(
"T2G_Seat_Move_Motor_Status_302",
{
"Motor_Sub_ID": motor,
"Task_State": 8,
"Result": 2,
"Direction": command["Direction"],
"Position_Valid": int(self.position_valid),
"Motor_Sequence": sequence,
"Value_X100": 0,
"Duty_Percent": command["Param_B"],
},
)
)
self.received.append(
self._frame(
"T2G_Seat_Move_Diagnostic_303",
{
"Motor_Sub_ID": motor,
"Hall_Count": self.hall[motor],
"Max_Hall_Count": self.max_hall[motor],
"Max_Speed_Pps_X100": 9000,
},
)
)
elif arbitration_id == 0x29E:
control = self.matrix.decode("T2G_Seat_Thermal_Control_29E", data)
heating = control["Driver_Seat_Heating_On"]
ventilation = control["Driver_Seat_Ventilation_On"]
self.received.append(
self._frame(
"T2G_Seat_Thermal_Status_4FA",
{
"Driver_Seat_Heating_Status": (
min(control["Driver_Seat_Heating_Gear"], 3) + 1
if heating == 1
else 1
),
"Driver_Seat_Ventilation_Status": (
min(control["Driver_Seat_Ventilation_Gear"], 3) + 1
if ventilation == 1
else 1
),
},
)
)
return frame
def receive(self, timeout_ms: int) -> CanFrame | None:
del timeout_ms
return self.received.popleft() if self.received else None
def close(self) -> None:
self.closed = True
class IdentityFakeBus:
def __init__(self, identity: bytes | None):
self.identity = identity
self.sent: list[CanFrame] = []
self.closed = False
def send(self, arbitration_id: int, data: bytes) -> CanFrame:
frame = CanFrame(arbitration_id, bytes(data))
self.sent.append(frame)
return frame
def receive(self, timeout_ms: int) -> CanFrame | None:
del timeout_ms
if self.identity is None:
return None
identity = self.identity
self.identity = None
return CanFrame(0x305, identity)
def close(self) -> None:
self.closed = True
class FailingIdentityBus(IdentityFakeBus):
def send(self, arbitration_id: int, data: bytes) -> CanFrame:
del arbitration_id, data
raise RuntimeError("CAN target did not ACK the identity probe")
class FakeProgrammer:
def __init__(self):
self.calls = 0
def program(self) -> dict[str, object]:
self.calls += 1
return {
"backend": "fake",
"programmed": True,
"image": "Seat_Controller_demo.hex",
"image_sha256": "0" * 64,
}
class T2GHardwareTests(unittest.TestCase):
def setUp(self) -> None:
self.matrix = SeatCanMatrix(
[
ROOT / "domains" / "seat" / "can" / "seat-thermal-hardware-matrix.yaml",
ROOT / "domains" / "seat" / "can" / "seat-motion-hardware-matrix.yaml",
]
)
self.compiler = T2GSeatCompiler(
ROOT / "domains" / "seat" / "can" / "signal-mapping.hardware.yaml",
ROOT / "platforms" / "t2g_seat_bench" / "calibration" / "seat.yaml",
)
def test_compiler_emits_verified_heating_payload(self) -> None:
config = function_config(
operation("seat.set_heating", {"state": "on", "level": 3})
)
plan = self.compiler.compile(config, {})
frames = [
self.matrix.encode(item["message"], item["signals"])
for item in plan["spec"]["commands"]
]
self.assertEqual(frames[0]["frame_id"], "0x3CE")
self.assertEqual(frames[0]["data_hex"], "10 00 00 00 00 00 00 00")
self.assertEqual(frames[1]["frame_id"], "0x29E")
self.assertEqual(frames[1]["data_hex"], "16 00 30 00 00 00 00 00")
def test_compiler_emits_verified_backrest_payload(self) -> None:
config = function_config(
operation("seat.set_backrest_recline", {"position_pct": 50})
)
plan = self.compiler.compile(config, {})
command = plan["spec"]["commands"][0]
frame = self.matrix.encode(command["message"], command["signals"])
self.assertEqual(frame["frame_id"], "0x300")
self.assertEqual(frame["data_hex"], "01 12 00 00 94 11 32 00")
def test_compiler_maps_small_relative_backrest_request_to_timed_duty(self) -> None:
config = function_config(
operation(
"seat.set_backrest_recline",
{"position_pct": 31, "angle_deg": 109},
motion={
"mode": "relative",
"direction": "increase",
"start_position_pct": 20,
"delta_position_pct": 11,
"start_angle_deg": 104,
"delta_angle_deg": 5,
},
)
)
command = self.compiler.compile(config, {})["spec"]["commands"][0]
self.assertEqual(command["signals"]["Opcode"], 3)
self.assertEqual(command["signals"]["Direction"], 1)
self.assertEqual(command["signals"]["Param_A"], 5500)
self.assertEqual(command["signals"]["Param_B"], 50)
self.assertEqual(command["completion"]["signal"], "Task_State")
def test_compiler_maps_large_relative_backrest_request_to_absolute_target(self) -> None:
config = function_config(
operation(
"seat.set_backrest_recline",
{"position_pct": 64, "angle_deg": 124},
motion={
"mode": "relative",
"direction": "increase",
"start_position_pct": 20,
"delta_position_pct": 44,
"start_angle_deg": 104,
"delta_angle_deg": 20,
},
)
)
command = self.compiler.compile(config, {})["spec"]["commands"][0]
self.assertEqual(command["signals"]["Opcode"], 1)
self.assertEqual(command["signals"]["Direction"], 0)
self.assertEqual(command["signals"]["Param_A"], 5760)
self.assertEqual(command["completion"]["signal"], "Value_X100")
def test_passenger_alias_emits_the_same_physical_payload(self) -> None:
driver = function_config(
operation("seat.set_heating", {"state": "on", "level": 2})
)
passenger = function_config(
operation(
"seat.set_heating",
{"state": "on", "level": 2},
instance="passenger",
zone="front_right",
)
)
driver_plan = self.compiler.compile(driver, {})
passenger_plan = self.compiler.compile(passenger, {})
self.assertEqual(
driver_plan["spec"]["commands"], passenger_plan["spec"]["commands"]
)
def test_driver_and_passenger_identical_motion_is_sent_once(self) -> None:
config = function_config(
operation(
"seat.set_backrest_recline",
{"position_pct": 60},
operation_id="op-driver",
),
operation(
"seat.set_backrest_recline",
{"position_pct": 60},
operation_id="op-passenger",
instance="passenger",
zone="front_right",
),
)
plan = self.compiler.compile(config, {})
self.assertEqual(len(plan["spec"]["commands"]), 1)
self.assertEqual(
plan["spec"]["commands"][0]["operation_ref"],
"op-driver,op-passenger",
)
bus = ReactiveFakeBus(self.matrix)
report = HardwareSeatExecutor(
self.matrix, lambda: bus, ack_timeout_ms=20
).execute(plan, config)
self.assertEqual([item.data[0] for item in bus.sent], [9, 1])
operation_results = [
item for item in report["spec"]["results"] if item["ref"].startswith("op-")
]
self.assertEqual({item["ref"] for item in operation_results}, {"op-driver", "op-passenger"})
def test_executor_waits_for_ack_and_physical_motion_feedback(self) -> None:
config = function_config(
operation("seat.set_backrest_recline", {"position_pct": 50})
)
plan = self.compiler.compile(config, {})
bus = ReactiveFakeBus(self.matrix)
report = HardwareSeatExecutor(
self.matrix, lambda: bus, ack_timeout_ms=20
).execute(plan, config)
self.assertEqual(report["spec"]["mode"], "hil")
self.assertEqual(report["spec"]["status"], "succeeded")
self.assertFalse(report["spec"]["results"][0]["telemetry"]["simulated"])
self.assertTrue(report["spec"]["results"][0]["telemetry"]["hardware_feedback"])
self.assertEqual([item.data[0] for item in bus.sent], [9, 1])
self.assertTrue(bus.closed)
def test_executor_blocks_absolute_motion_when_board_position_is_invalid(self) -> None:
config = function_config(
operation("seat.set_backrest_recline", {"position_pct": 50})
)
plan = self.compiler.compile(config, {})
bus = ReactiveFakeBus(self.matrix, position_valid=False)
report = HardwareSeatExecutor(
self.matrix, lambda: bus, ack_timeout_ms=20
).execute(plan, config)
self.assertEqual(report["spec"]["status"], "failed")
self.assertEqual([item.data[0] for item in bus.sent], [9, 9])
failure = report["spec"]["results"][-1]
self.assertIn("Position_Valid=0", failure["message"])
self.assertTrue(failure["telemetry"]["calibration_required"])
def test_large_relative_request_is_blocked_without_sending_absolute_opcode(self) -> None:
config = function_config(
operation(
"seat.set_backrest_recline",
{"position_pct": 64, "angle_deg": 124},
motion={
"mode": "relative",
"direction": "increase",
"start_position_pct": 20,
"delta_position_pct": 44,
"start_angle_deg": 104,
"delta_angle_deg": 20,
},
)
)
plan = self.compiler.compile(config, {})
bus = ReactiveFakeBus(self.matrix, position_valid=False)
report = HardwareSeatExecutor(
self.matrix, lambda: bus, ack_timeout_ms=20
).execute(plan, config)
self.assertEqual(report["spec"]["status"], "failed")
self.assertEqual([item.data[0] for item in bus.sent], [9, 9])
self.assertNotIn(1, [item.data[0] for item in bus.sent])
self.assertTrue(
report["spec"]["results"][-1]["telemetry"]["calibration_required"]
)
def test_executor_accepts_hall_confirmed_timed_motion_without_position(self) -> None:
config = function_config(
operation(
"seat.set_backrest_recline",
{"position_pct": 31, "angle_deg": 109},
motion={
"mode": "relative",
"direction": "increase",
"start_position_pct": 20,
"delta_position_pct": 11,
"start_angle_deg": 104,
"delta_angle_deg": 5,
},
)
)
plan = self.compiler.compile(config, {})
bus = ReactiveFakeBus(self.matrix, position_valid=False)
report = HardwareSeatExecutor(
self.matrix, lambda: bus, ack_timeout_ms=20
).execute(plan, config)
self.assertEqual(report["spec"]["status"], "succeeded")
self.assertEqual([item.data[0] for item in bus.sent], [9, 3])
telemetry = report["spec"]["results"][0]["telemetry"]
self.assertEqual(telemetry["motion_mode"], "relative_timed_duty")
self.assertFalse(telemetry["position_valid"])
self.assertGreater(telemetry["hall_delta"], 0)
self.assertTrue(telemetry["hall_direction_verified"])
def test_executor_reports_stall_with_post_failure_hall_diagnostics(self) -> None:
config = function_config(
operation(
"seat.set_backrest_recline",
{"position_pct": 31, "angle_deg": 109},
motion={
"mode": "relative",
"direction": "increase",
"start_position_pct": 20,
"delta_position_pct": 11,
"start_angle_deg": 104,
"delta_angle_deg": 5,
},
)
)
plan = self.compiler.compile(config, {})
bus = ReactiveFakeBus(
self.matrix,
position_valid=False,
timed_failure_result=4,
timed_failure_event=4,
)
report = HardwareSeatExecutor(
self.matrix, lambda: bus, ack_timeout_ms=20
).execute(plan, config)
self.assertEqual(report["spec"]["status"], "failed")
self.assertEqual([item.data[0] for item in bus.sent], [9, 3, 9])
failure = report["spec"]["results"][-1]
self.assertIn("STALL", failure["message"])
self.assertEqual(failure["telemetry"]["motor_result_name"], "STALL")
self.assertEqual(failure["telemetry"]["last_motor_event_name"], "STALL")
self.assertEqual(
failure["telemetry"]["failure_category"], "stall_no_hall_activity"
)
self.assertEqual(failure["telemetry"]["hall_before"], 0)
self.assertEqual(failure["telemetry"]["hall_after"], 0)
self.assertEqual(
failure["telemetry"]["diagnostic_frame"]["frame_id"], "0x303"
)
def test_executor_sends_stop_all_after_motion_feedback_timeout(self) -> None:
config = function_config(
operation("seat.set_legrest_extension", {"position_pct": 50})
)
plan = self.compiler.compile(config, {})
bus = ReactiveFakeBus(self.matrix, complete_motion=False)
report = HardwareSeatExecutor(
self.matrix, lambda: bus, ack_timeout_ms=20
).execute(plan, config)
self.assertEqual(report["spec"]["status"], "failed")
self.assertEqual([item.data[0] for item in bus.sent], [9, 1, 9])
self.assertTrue(
report["spec"]["results"][-1]["telemetry"]["stop_all_attempted"]
)
def test_executor_waits_for_real_thermal_status(self) -> None:
config = function_config(
operation("seat.set_ventilation", {"state": "on", "level": 2})
)
plan = self.compiler.compile(config, {})
bus = ReactiveFakeBus(self.matrix)
report = HardwareSeatExecutor(
self.matrix, lambda: bus, thermal_timeout_ms=20
).execute(plan, config)
self.assertEqual(report["spec"]["status"], "succeeded")
telemetry = report["spec"]["results"][0]["telemetry"]
self.assertEqual(telemetry["feedback_frame"]["frame_id"], "0x4FA")
self.assertEqual(telemetry["feedback_value"], 3)
def test_settings_allow_shared_target_aliases_but_fail_closed_for_other_targets(self) -> None:
environment = {
"COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED": "true",
"COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN": "bench-secret",
"COGNITIVE_AGENT_SEAT_FIRMWARE_PROFILES": "combined",
"COGNITIVE_AGENT_ENABLE_UNVERIFIED_LONGITUDINAL": "false",
}
with patch.dict(os.environ, environment, clear=False):
settings = SeatHardwareSettings.from_env(ROOT)
settings = replace(
settings, firmware_images={"combined": ROOT / "pyproject.toml"}
)
settings.authorize("bench-secret")
settings.validate_function_config(
function_config(
operation(
"seat.set_heating",
{"state": "on", "level": 1},
instance="passenger",
zone="front_right",
)
)
)
with self.assertRaises(HardwareSafetyError):
settings.validate_function_config(
function_config(
operation(
"seat.set_heating",
{"state": "on", "level": 1},
instance="rear_left",
zone="rear_left",
)
)
)
with self.assertRaises(HardwareSafetyError):
settings.validate_function_config(
function_config(
operation(
"seat.set_longitudinal_position", {"position_pct": 40}
)
)
)
with self.assertRaises(HardwareSafetyError):
settings.authorize("wrong-secret")
def test_settings_reject_conflicting_shared_alias_requests(self) -> None:
environment = {
"COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED": "true",
"COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN": "bench-secret",
"COGNITIVE_AGENT_SEAT_FIRMWARE_PROFILES": "combined",
}
with patch.dict(os.environ, environment, clear=False):
settings = SeatHardwareSettings.from_env(ROOT)
with self.assertRaises(HardwareSafetyError):
settings.validate_function_config(
function_config(
operation(
"seat.set_heating",
{"state": "on", "level": 1},
operation_id="op-driver",
),
operation(
"seat.set_heating",
{"state": "on", "level": 3},
operation_id="op-passenger",
instance="passenger",
zone="front_right",
),
)
)
def test_firmware_guard_reuses_bus_when_identity_matches(self) -> None:
bus = IdentityFakeBus(EXPECTED_IDENTITY_PAYLOAD)
guard = T2GFirmwareGuard(
lambda: bus,
None,
auto_flash=False,
probe_timeout_ms=20,
)
prepared, telemetry = guard.prepare_bus()
self.assertIs(prepared, bus)
self.assertFalse(telemetry["programmed_this_request"])
self.assertEqual(bus.sent[0].arbitration_id, 0x304)
self.assertFalse(bus.closed)
def test_firmware_guard_programs_then_reverifies(self) -> None:
before = IdentityFakeBus(None)
after = IdentityFakeBus(EXPECTED_IDENTITY_PAYLOAD)
buses = deque([before, after])
programmer = FakeProgrammer()
guard = T2GFirmwareGuard(
buses.popleft,
programmer, # type: ignore[arg-type]
auto_flash=True,
probe_timeout_ms=20,
boot_wait_ms=0,
)
prepared, telemetry = guard.prepare_bus()
self.assertIs(prepared, after)
self.assertEqual(programmer.calls, 1)
self.assertTrue(before.closed)
self.assertTrue(telemetry["programmed_this_request"])
def test_firmware_guard_programs_when_unflashed_target_does_not_ack(self) -> None:
before = FailingIdentityBus(None)
after = IdentityFakeBus(EXPECTED_IDENTITY_PAYLOAD)
buses = deque([before, after])
programmer = FakeProgrammer()
guard = T2GFirmwareGuard(
buses.popleft,
programmer, # type: ignore[arg-type]
auto_flash=True,
probe_timeout_ms=20,
boot_wait_ms=0,
)
prepared, telemetry = guard.prepare_bus()
self.assertIs(prepared, after)
self.assertEqual(programmer.calls, 1)
self.assertTrue(before.closed)
self.assertTrue(telemetry["programmed_this_request"])
def test_firmware_guard_fails_closed_when_auto_flash_is_disabled(self) -> None:
bus = IdentityFakeBus(None)
guard = T2GFirmwareGuard(
lambda: bus,
None,
auto_flash=False,
probe_timeout_ms=20,
)
with self.assertRaises(FirmwareProgrammingError):
guard.prepare_bus()
self.assertTrue(bus.closed)
def test_programmer_command_downloads_and_hands_off_to_running_target(self) -> None:
firmware_root = ROOT / "firmware" / "t2g" / "Seat_Controller_demo"
output_image = (
firmware_root / "IAR" / "rev_d" / "Exe" / "Seat_Controller_demo.out"
)
program_macro = firmware_root / "IAR" / "Seat_Controller_program.mac"
programmer = T2GSeatProgrammer(
cspybat=ROOT / "fake-iar" / "common" / "bin" / "cspybat.exe",
debug_file=output_image,
hex_image=(
firmware_root / "IAR" / "rev_d" / "Exe" / "Seat_Controller_demo.hex"
),
macro_file=program_macro,
)
# Dependency discovery is covered by settings/configuration tests. This
# test only validates the command and never starts C-SPY or a probe.
with patch.object(T2GSeatProgrammer, "required_files", return_value=()):
command = programmer.command()
self.assertEqual(command[3], str(output_image))
self.assertIn(f"--macro={program_macro}", command)
self.assertNotIn("--download_only", command)
self.assertIn("--leave_target_running", command)
timeout_index = command.index("--timeout")
self.assertEqual(command[timeout_index + 1], "3000")
self.assertIn("--jet_probe=cmsisdap", command)
self.assertNotIn("Seat_Controller_demo.mac", " ".join(command))
def test_hardware_validation_replaces_reference_dry_run_warnings(self) -> None:
validation = [
{"severity": "warning", "code": code, "message": "dry-run"}
for code in (
"LEVEL_COMMAND_SIMULATED",
"SYNTHETIC_MOTION_CAN",
"DRIVER_MOTION_INTERLOCK_NOT_IMPLEMENTED",
"COMBINED_THERMAL_SIMULATED",
"VIRTUAL_EXECUTION_ONLY",
)
]
SeatDemoService._adapt_validation_for_hardware(validation)
codes = {issue["code"] for issue in validation}
self.assertEqual(
codes,
{
"LEVEL_COMMAND_T2G_MAPPED",
"T2G_MOTION_PROTOCOL_MAPPED",
"BENCH_ONLY_NO_VEHICLE_INTERLOCKS",
"COMBINED_THERMAL_T2G_MAPPED",
"REFERENCE_MATRIX_OVERRIDDEN_BY_T2G_BENCH",
},
)
messages = " ".join(issue["message"] for issue in validation)
self.assertNotIn("禁止实车下发", messages)
self.assertNotIn("只能虚拟执行", messages)
def test_physical_feedback_is_mirrored_to_both_logical_seats(self) -> None:
actions = [
{
"target": "passenger",
"function": "ventilation",
"state": "on",
"requested_level": 2,
}
]
config = function_config(
operation(
"seat.set_ventilation",
{"state": "on", "level": 2},
instance="passenger",
zone="front_right",
)
)
report = {
"spec": {
"results": [
{
"ref": "op-hardware-test",
"status": "succeeded",
"telemetry": {"feedback_value": 3},
}
]
}
}
projected = SeatDemoService._hardware_feedback_actions(
actions, config, report
)
self.assertEqual({item["target"] for item in projected}, {"driver", "passenger"})
self.assertTrue(
all(item["requested_level"] == 2 for item in projected)
)
if __name__ == "__main__":
unittest.main()