feat: integrate calibration extension and self-repair modules

This commit is contained in:
vee
2026-08-07 23:39:45 +08:00
parent 156b10fda1
commit 3bceb0d5a1
41 changed files with 21310 additions and 314 deletions
+91
View File
@@ -0,0 +1,91 @@
from __future__ import annotations
import tempfile
import time
from pathlib import Path
import pytest
from cognitive_agent.application.calibration import protocol
from cognitive_agent.application.calibration.service import CalibrationService
def test_protocol_matches_android_golden_vectors() -> None:
assert protocol.crc8(b"123456789") == 0x4B
vectors = (
(
protocol.command(0x00, protocol.OP_HELLO, protocol.AXIS_ALL),
"A1 00 01 FF 00 00 00 55",
),
(
protocol.command(0x01, protocol.OP_ARM, protocol.AXIS_ALL, 0x4E37),
"A1 01 02 FF 37 4E 00 0A",
),
(
protocol.command(0x23, protocol.OP_CALIBRATE, protocol.AXIS_ALL),
"A1 23 10 FF 00 00 00 33",
),
(
protocol.command(0x24, protocol.OP_MOVE_PERMILLE, 0, 1000),
"A1 24 11 00 E8 03 00 78",
),
(
protocol.command(0x25, protocol.OP_STOP, protocol.AXIS_ALL),
"A1 25 12 FF 00 00 00 6E",
),
)
for actual, expected in vectors:
assert actual == bytes.fromhex(expected)
def test_protocol_parses_status_and_rejects_bad_crc() -> None:
ack = protocol.parse(0x621, bytes.fromhex("B1 22 10 02 00 07 01 A9"))
assert isinstance(ack, protocol.Ack)
assert ack.accepted
controller = protocol.parse(0x622, bytes.fromhex("C1 01 00 17 07 FF 02 4E"))
assert isinstance(controller, protocol.ControllerStatus)
assert controller.can_ready
assert controller.motor_ready
assert controller.armed
frame = bytearray.fromhex("D1 00 F0 00 E0 01 17 40")
frame[2] ^= 1
with pytest.raises(protocol.ProtocolError):
protocol.parse(0x630, bytes(frame))
def test_simulation_uses_real_wire_boundary_and_generates_pose() -> None:
with tempfile.TemporaryDirectory() as directory:
service = CalibrationService(Path(directory))
service.start()
try:
initial = service.snapshot()
assert initial["transport"] == {
"mode": "hardware",
"state": "disconnected",
"detail": "尚未连接",
}
connected = service.connect_can({"mode": "simulation"})
assert connected["transport"]["state"] == "online"
assert connected["controller"]["can_ready"]
assert connected["controller"]["motor_ready"]
assert [axis["range"] for axis in connected["axes"]] == [480, 647, 917, 373]
service.move(0, 800)
time.sleep(0.2)
moving = service.snapshot()
assert moving["controller"]["armed"]
assert moving["axes"][0]["moving"]
time.sleep(0.45)
pose = service.snapshot()["mqtt"]
assert pose["state"] == "simulated"
assert pose["count"] >= 1
assert "Seat_cushion" in pose["areas"]
stopped = service.stop()
assert not stopped["controller"]["armed"]
finally:
service.close()
+120
View File
@@ -0,0 +1,120 @@
from __future__ import annotations
import json
import os
import unittest
from unittest.mock import Mock, patch
from cognitive_agent.application.self_extension import (
GatewayError,
SelfExtensionService,
SimulationGateway,
build_gateway_command,
validate_slot,
)
class SelfExtensionTests(unittest.TestCase):
def test_gateway_command_contracts_and_slot_validation(self) -> None:
command = build_gateway_command("learn", {"slot": 17})
self.assertEqual(command["type"], "infrared")
self.assertEqual(command["action"], "learn")
self.assertEqual(command["data"], {"no": 17})
self.assertTrue(str(command["messageId"]).startswith("bridge-"))
self.assertEqual(validate_slot("248"), 248)
with self.assertRaises(GatewayError):
validate_slot(0)
with self.assertRaises(GatewayError):
build_gateway_command("erase", {"confirm": "wrong"})
def test_simulation_gateway_executes_without_physical_confirmation(self) -> None:
service = SelfExtensionService(SimulationGateway())
result = service.execute_gateway("emit", {"slot": 3, "repeat": 2})
self.assertTrue(result["success"])
self.assertEqual(result["transport"], "simulation")
self.assertFalse(result["physical_state_confirmed"])
self.assertEqual(len(result["device_response"]), 2)
def test_plan_only_accepts_learned_catalog_capabilities(self) -> None:
service = SelfExtensionService(SimulationGateway())
proposed = {
"summary": "先开玩具灯,再打开阅读灯",
"needs_confirmation": False,
"question": "",
"steps": [
{
"kind": "peripheral",
"capability_id": "cap-light-on",
"command": "",
"label": "ignored",
},
{
"kind": "peripheral",
"capability_id": "invented-capability",
"command": "",
"label": "ignored",
},
{
"kind": "vehicle",
"capability_id": "",
"command": "打开全部阅读灯",
"label": "车内灯光 · 阅读灯",
},
],
}
with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}, clear=False):
with patch.object(
service,
"_request_gpt_plan",
return_value=json.dumps(proposed, ensure_ascii=False),
):
result = service.plan(
{
"utterance": "打开玩具灯和阅读灯",
"peripherals": [
{
"id": "cap-light-on",
"status": "learned",
"device_name": "灯光测试玩具",
"action": "开启",
"risk": "low",
}
],
"vehicle_devices": [],
}
)
self.assertEqual(len(result["steps"]), 2)
self.assertEqual(result["steps"][0]["capability_id"], "cap-light-on")
self.assertEqual(result["steps"][1]["command"], "打开全部阅读灯")
def test_vehicle_execution_reuses_the_same_orchestration_service(self) -> None:
service = SelfExtensionService(SimulationGateway())
vehicle_service = Mock()
vehicle_service.process.return_value = {
"status": "succeeded",
"provider_used": "local_rules",
"request_id": "req-extension-1",
"decision_summary": ["打开阅读灯"],
"validation": [],
"execution_report": {"spec": {"results": [{"status": "succeeded"}]}},
"deferred_requests": [],
}
result = service.execute_vehicle(
{"command": "打开全部阅读灯", "vehicle_id": "denza_n7"},
vehicle_service,
hardware_access_bypass=True,
)
self.assertTrue(result["success"])
self.assertEqual(result["request_id"], "req-extension-1")
vehicle_service.process.assert_called_once()
self.assertEqual(vehicle_service.process.call_args.args[0], "打开全部阅读灯")
self.assertTrue(vehicle_service.process.call_args.kwargs["hardware_access_bypass"])
if __name__ == "__main__":
unittest.main()
+83
View File
@@ -0,0 +1,83 @@
from __future__ import annotations
from pathlib import Path
from cognitive_agent.application.self_repair.healing_model import (
PHASE_DIAGNOSED,
PHASE_INJECTED,
PHASE_PASSED,
SCENARIOS,
SelfHealingEngine,
public_scenarios,
)
from cognitive_agent.application.self_repair.service import SelfRepairService
from cognitive_agent.application.self_repair.signal_diagnostic import SignalDiagnosticModel
from cognitive_agent.application.self_repair.zcan_bridge import ZcanBridgeManager
def test_six_production_cases_complete_the_shadow_repair_loop() -> None:
assert len(SCENARIOS) == 6
assert len(public_scenarios()) == 6
for scenario in SCENARIOS:
engine = SelfHealingEngine(clock=lambda: 1000.0)
started = engine.start(scenario["id"], now=1000.0)
assert started["execution_mode"] == "shadow"
assert started["injection_scope"] == "SHADOW_TELEMETRY_ONLY"
assert engine.snapshot(1006.2)["phase"] == PHASE_INJECTED
assert engine.snapshot(1008.7)["phase"] == PHASE_DIAGNOSED
completed = engine.snapshot(1018.2)
assert completed["phase"] == PHASE_PASSED
assert completed["pass"] is True
assert completed["telemetry"]["safety_protection"] is True
assert completed["telemetry"]["task_restored"] is True
def test_connector_inventory_matches_physical_housings() -> None:
snapshot = SignalDiagnosticModel().analyze(None)
connectors = snapshot["connectors"]
def pin(pin_id: str) -> dict[str, object]:
connector, number = pin_id.split("-", maxsplit=1)
return next(
item
for item in connectors[connector]
if item["number"] == int(number)
)
assert [pin["number"] for pin in connectors["JA1"]] == list(range(1, 15))
assert [pin["number"] for pin in connectors["JB1"]] == list(range(1, 19))
assert [pin["number"] for pin in connectors["JC1"]] == list(range(1, 41))
assert sum(len(pins) for pins in connectors.values()) == 72
assert snapshot["connector_enabled_count"] == 21
assert pin("JA1-14")["enabled"] is False
assert pin("JC1-21")["enabled"] is True
def test_service_is_safe_without_machine_specific_tool_paths() -> None:
service = SelfRepairService()
health = service.health()
state = service.snapshot()
assert health["ok"] is True
assert health["service"] == "seat-self-repair"
assert health["configuration"]["diagnostic_config_available"] is True
assert health["configuration"]["bridge_script_available"] is True
assert len(state["scenarios"]) == 6
assert state["integration"]["auto_start_bridge"] is False
assert state["model"]["type"] == "PROCEDURAL_SEAT"
assert service.run_self_test() is True
def test_bridge_manager_does_not_arm_visible_tooling_by_default(tmp_path: Path) -> None:
manager = ZcanBridgeManager(
tmp_path / "bridge.json",
tmp_path / "bridge.py",
zcanpro_exe=tmp_path / "ZCANPRO.exe",
script_config_path=tmp_path / "zcanpro.ini",
)
snapshot = manager.snapshot()
assert snapshot["state"] == "IDLE"
assert snapshot["retry_armed"] is False
assert snapshot["attempts"] == 0