from __future__ import annotations import json import tempfile import time from pathlib import Path import pytest from cognitive_agent.application.calibration import protocol from cognitive_agent.application.calibration.service import ApiError, CalibrationService def test_video_settings_use_webrtc_and_migrate_legacy_rtsp() -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) data_dir = root / "data" data_dir.mkdir() (data_dir / "settings.json").write_text( json.dumps( { "mqtt": {"host": "172.20.10.12", "port": 1883, "topic": "zed/seatpose"}, "video": { "scheme": "http", "host": "172.20.10.12", "port": 8554, "path": "zed", "transport": "webrtc", }, } ), encoding="utf-8", ) service = CalibrationService(root) settings = service.snapshot()["settings"] assert settings["mqtt"]["host"] == "172.20.10.6" assert settings["video"] == { "scheme": "http", "host": "172.20.10.6", "port": 8889, "path": "zed", "transport": "webrtc", } assert service.start_video()["video"]["state"] == "ready" 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() def test_virtual_session_restart_resets_axes_without_touching_hardware() -> None: with tempfile.TemporaryDirectory() as directory: service = CalibrationService(Path(directory)) service.start() try: service.connect_can({"mode": "simulation"}) service.move(1, 720) time.sleep(0.15) reset = service.reset_virtual_session() assert reset["transport"]["mode"] == "simulation" assert reset["transport"]["state"] == "disconnected" assert reset["controller"]["armed"] is False assert [axis["permille"] for axis in reset["axes"]] == [0, 0, 0, 0] assert reset["operation"]["phase"] == protocol.PHASE_IDLE finally: service.close() def test_virtual_session_restart_rejects_real_bench_state() -> None: with tempfile.TemporaryDirectory() as directory: service = CalibrationService(Path(directory)) with pytest.raises(ApiError, match="真实台架会话"): service.reset_virtual_session()