feat(perception): integrate calibrated operator pipeline
Add calibrated K1 projection, recorded and near-live perception qualification, unified Rerun operator layers, bounded replay admission, audited viewer controls, worker experiments, and lab evidence.
This commit is contained in:
@@ -0,0 +1,422 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from collections import deque
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
import paho.mqtt.client as mqtt
|
||||
import pytest
|
||||
|
||||
from k1link.device_plugins.xgrids_k1.calibration_snapshot import (
|
||||
seal_factory_calibration_snapshot,
|
||||
)
|
||||
from k1link.device_plugins.xgrids_k1.protocol.application_bootstrap import (
|
||||
ApplicationControlAuthority,
|
||||
LiveDeviceControlBinding,
|
||||
)
|
||||
from k1link.device_plugins.xgrids_k1.protocol.calibration_file import (
|
||||
CALIBRATION_FILE_READ_COMMAND,
|
||||
CALIBRATION_FILE_REQUEST_TOPIC,
|
||||
CALIBRATION_FILE_RESPONSE_TOPIC,
|
||||
FACTORY_CALIBRATION_PATHS,
|
||||
FACTORY_CAMERA_CALIBRATION_PATH,
|
||||
FACTORY_CAMERA_LIDAR_EXTRINSIC_PATH,
|
||||
CalibrationFileProtocolError,
|
||||
CalibrationFileRejected,
|
||||
build_factory_calibration_file_read,
|
||||
decode_factory_calibration_file_response,
|
||||
)
|
||||
from k1link.device_plugins.xgrids_k1.protocol.calibration_mqtt import (
|
||||
CALIBRATION_READ_SUBSCRIPTIONS,
|
||||
FactoryCalibrationReadResult,
|
||||
ReviewedCalibrationMqttReader,
|
||||
)
|
||||
from k1link.device_plugins.xgrids_k1.protocol.modeling_control import OPENAPI_SUCCESS
|
||||
from k1link.device_plugins.xgrids_k1.protocol.protobuf_wire import iter_fields
|
||||
|
||||
APPLICATION_KEY = "11111111-2222-3333-4444-555555555555"
|
||||
VENDOR_DEVICE_ID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
|
||||
DEVICE_SERIAL = "K1SERIAL01"
|
||||
PROFILE_ID = "xgrids.lixelkity-k1.fw-3.0.2.local-network.v2"
|
||||
CALIBRATION_FIXTURE_ROOT = Path(__file__).parent / "fixtures" / "k1" / "calibration"
|
||||
|
||||
|
||||
def _authority() -> ApplicationControlAuthority:
|
||||
return ApplicationControlAuthority(openapi_key=APPLICATION_KEY)
|
||||
|
||||
|
||||
def _binding(**changes: object) -> LiveDeviceControlBinding:
|
||||
values = {
|
||||
"vendor_device_id": VENDOR_DEVICE_ID,
|
||||
"device_serial": DEVICE_SERIAL,
|
||||
"software_version": "V3.0.2",
|
||||
"system_version": "V3.0.2",
|
||||
"device_model": "LixelKity K1",
|
||||
"device_type": "A4",
|
||||
"is_activated": True,
|
||||
}
|
||||
values.update(changes)
|
||||
return LiveDeviceControlBinding(**values) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def _varint(value: int) -> bytes:
|
||||
encoded = bytearray()
|
||||
while value > 0x7F:
|
||||
encoded.append((value & 0x7F) | 0x80)
|
||||
value >>= 7
|
||||
encoded.append(value)
|
||||
return bytes(encoded)
|
||||
|
||||
|
||||
def _uint(number: int, value: int) -> bytes:
|
||||
return _varint(number << 3) + _varint(value)
|
||||
|
||||
|
||||
def _bytes(number: int, value: bytes) -> bytes:
|
||||
return _varint((number << 3) | 2) + _varint(len(value)) + value
|
||||
|
||||
|
||||
def _text(number: int, value: str) -> bytes:
|
||||
return _bytes(number, value.encode("utf-8"))
|
||||
|
||||
|
||||
def _header(session_id: str) -> bytes:
|
||||
return b"".join(
|
||||
(
|
||||
_text(4, VENDOR_DEVICE_ID),
|
||||
_text(5, session_id),
|
||||
_text(6, APPLICATION_KEY),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _device_info_response() -> bytes:
|
||||
base_info = b"".join(
|
||||
(
|
||||
_text(2, "V3.0.2"),
|
||||
_text(3, "V3.0.2"),
|
||||
_text(6, "LixelKity K1"),
|
||||
_text(7, DEVICE_SERIAL),
|
||||
_text(8, "A4"),
|
||||
)
|
||||
)
|
||||
working_status = _uint(1, 1)
|
||||
device_info = _bytes(2, base_info) + _bytes(7, working_status)
|
||||
return b"".join(
|
||||
(
|
||||
_bytes(1, _header(":DeviceInfoRequest")),
|
||||
_bytes(2, device_info),
|
||||
_bytes(15, _uint(1, OPENAPI_SUCCESS)),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _calibration_response(
|
||||
path: str,
|
||||
content: bytes,
|
||||
*,
|
||||
command: int = CALIBRATION_FILE_READ_COMMAND,
|
||||
result_code: int = OPENAPI_SUCCESS,
|
||||
session_id: str | None = None,
|
||||
) -> bytes:
|
||||
return b"".join(
|
||||
(
|
||||
_bytes(
|
||||
1,
|
||||
_header(
|
||||
session_id or f"{VENDOR_DEVICE_ID}:CalibFileRequest"
|
||||
),
|
||||
),
|
||||
_uint(2, command),
|
||||
_text(3, path),
|
||||
_bytes(4, content),
|
||||
_bytes(15, _uint(1, result_code)),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _content_for(path: str) -> bytes:
|
||||
if path == FACTORY_CAMERA_CALIBRATION_PATH:
|
||||
return (CALIBRATION_FIXTURE_ROOT / "camera.yaml").read_bytes()
|
||||
return (CALIBRATION_FIXTURE_ROOT / "extrinsic_camera_lidar.yaml").read_bytes()
|
||||
|
||||
|
||||
def test_factory_file_request_is_command_five_and_has_no_content_field() -> None:
|
||||
request = build_factory_calibration_file_read(
|
||||
_authority(),
|
||||
_binding(),
|
||||
FACTORY_CAMERA_CALIBRATION_PATH,
|
||||
)
|
||||
fields = {field.number: field.value for field in iter_fields(request.payload)}
|
||||
header = fields[1]
|
||||
assert isinstance(header, bytes)
|
||||
header_fields = {field.number: field.value for field in iter_fields(header)}
|
||||
|
||||
assert request.topic == CALIBRATION_FILE_REQUEST_TOPIC
|
||||
assert request.response_topic == CALIBRATION_FILE_RESPONSE_TOPIC
|
||||
assert request.mutates_device is False
|
||||
assert request.automatic_retry is False
|
||||
assert fields[2] == 5
|
||||
assert fields[3] == FACTORY_CAMERA_CALIBRATION_PATH.encode()
|
||||
assert 4 not in fields
|
||||
assert header_fields[4] == VENDOR_DEVICE_ID.encode()
|
||||
assert header_fields[5] == f"{VENDOR_DEVICE_ID}:CalibFileRequest".encode()
|
||||
assert header_fields[6] == APPLICATION_KEY.encode()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path",
|
||||
(
|
||||
"/mnt/system/factory-data/config/../Lixel.yaml",
|
||||
"/mnt/system/factory-data/config/extrinsic_camera_motor.yaml",
|
||||
"/tmp/camera.yaml",
|
||||
"",
|
||||
),
|
||||
)
|
||||
def test_factory_file_request_rejects_every_path_outside_two_file_allowlist(
|
||||
path: str,
|
||||
) -> None:
|
||||
with pytest.raises(CalibrationFileProtocolError, match="two-file allowlist"):
|
||||
build_factory_calibration_file_read(_authority(), _binding(), path)
|
||||
|
||||
|
||||
def test_factory_file_request_rejects_a_nonreviewed_live_binding() -> None:
|
||||
with pytest.raises(CalibrationFileProtocolError, match="reviewed activated"):
|
||||
build_factory_calibration_file_read(
|
||||
_authority(),
|
||||
_binding(system_version="V3.0.3"),
|
||||
FACTORY_CAMERA_CALIBRATION_PATH,
|
||||
)
|
||||
|
||||
|
||||
def test_correlated_factory_file_response_returns_exact_utf8_bytes() -> None:
|
||||
request = build_factory_calibration_file_read(
|
||||
_authority(),
|
||||
_binding(),
|
||||
FACTORY_CAMERA_CALIBRATION_PATH,
|
||||
)
|
||||
content = _content_for(request.path)
|
||||
|
||||
result = decode_factory_calibration_file_response(
|
||||
_calibration_response(request.path, content),
|
||||
request,
|
||||
_authority(),
|
||||
_binding(),
|
||||
)
|
||||
|
||||
assert result.path == request.path
|
||||
assert result.content == content
|
||||
assert result.content_bytes == len(content)
|
||||
assert len(result.content_sha256) == 64
|
||||
assert result.result_code == OPENAPI_SUCCESS
|
||||
|
||||
|
||||
def test_factory_file_response_rejects_write_command_path_drift_and_duplicates() -> None:
|
||||
request = build_factory_calibration_file_read(
|
||||
_authority(),
|
||||
_binding(),
|
||||
FACTORY_CAMERA_CALIBRATION_PATH,
|
||||
)
|
||||
with pytest.raises(CalibrationFileProtocolError, match="not a file-read"):
|
||||
decode_factory_calibration_file_response(
|
||||
_calibration_response(request.path, b"yaml", command=6),
|
||||
request,
|
||||
_authority(),
|
||||
_binding(),
|
||||
)
|
||||
with pytest.raises(CalibrationFileProtocolError, match="path mismatch"):
|
||||
decode_factory_calibration_file_response(
|
||||
_calibration_response(FACTORY_CAMERA_LIDAR_EXTRINSIC_PATH, b"yaml"),
|
||||
request,
|
||||
_authority(),
|
||||
_binding(),
|
||||
)
|
||||
duplicate_path = _calibration_response(request.path, b"yaml") + _text(3, request.path)
|
||||
with pytest.raises(CalibrationFileProtocolError, match="duplicated"):
|
||||
decode_factory_calibration_file_response(
|
||||
duplicate_path,
|
||||
request,
|
||||
_authority(),
|
||||
_binding(),
|
||||
)
|
||||
|
||||
|
||||
def test_factory_file_response_rejects_device_error_and_non_utf8_content() -> None:
|
||||
request = build_factory_calibration_file_read(
|
||||
_authority(),
|
||||
_binding(),
|
||||
FACTORY_CAMERA_CALIBRATION_PATH,
|
||||
)
|
||||
with pytest.raises(CalibrationFileRejected) as rejected:
|
||||
decode_factory_calibration_file_response(
|
||||
_calibration_response(request.path, b"yaml", result_code=123),
|
||||
request,
|
||||
_authority(),
|
||||
_binding(),
|
||||
)
|
||||
assert rejected.value.result_code == 123
|
||||
with pytest.raises(CalibrationFileProtocolError, match="valid UTF-8"):
|
||||
decode_factory_calibration_file_response(
|
||||
_calibration_response(request.path, b"\xff"),
|
||||
request,
|
||||
_authority(),
|
||||
_binding(),
|
||||
)
|
||||
|
||||
|
||||
class FakeReasonCode:
|
||||
def __init__(self, *, failure: bool = False) -> None:
|
||||
self.is_failure = failure
|
||||
|
||||
|
||||
class FakeCalibrationClient:
|
||||
def __init__(self) -> None:
|
||||
self.on_connect: Any = None
|
||||
self.on_subscribe: Any = None
|
||||
self.on_publish: Any = None
|
||||
self.on_message: Any = None
|
||||
self.on_disconnect: Any = None
|
||||
self.connect_timeout = 0.0
|
||||
self.events: deque[tuple[str, object]] = deque()
|
||||
self.connect_calls: list[tuple[str, int, int]] = []
|
||||
self.subscribe_calls: list[list[tuple[str, int]]] = []
|
||||
self.publish_calls: list[tuple[str, bytes, int, bool]] = []
|
||||
self.unsubscribe_calls: list[list[str]] = []
|
||||
self.next_mid = 20
|
||||
|
||||
def connect(self, host: str, port: int, keepalive: int) -> mqtt.MQTTErrorCode:
|
||||
self.connect_calls.append((host, port, keepalive))
|
||||
self.events.append(("connect", FakeReasonCode()))
|
||||
return mqtt.MQTT_ERR_SUCCESS
|
||||
|
||||
def subscribe(self, topics: list[tuple[str, int]]) -> tuple[mqtt.MQTTErrorCode, int]:
|
||||
self.subscribe_calls.append(topics)
|
||||
self.events.append(("subscribe", 7))
|
||||
return mqtt.MQTT_ERR_SUCCESS, 7
|
||||
|
||||
def publish(
|
||||
self,
|
||||
topic: str,
|
||||
payload: bytes,
|
||||
qos: int,
|
||||
retain: bool,
|
||||
) -> SimpleNamespace:
|
||||
self.publish_calls.append((topic, payload, qos, retain))
|
||||
mid = self.next_mid
|
||||
self.next_mid += 1
|
||||
self.events.append(("publish", mid))
|
||||
if topic == "lixel/application/request/device_info":
|
||||
response_topic = "lixel/application/response/device_info"
|
||||
response_payload = _device_info_response()
|
||||
else:
|
||||
fields = {field.number: field.value for field in iter_fields(payload)}
|
||||
raw_path = fields[3]
|
||||
assert isinstance(raw_path, bytes)
|
||||
path = raw_path.decode()
|
||||
response_topic = CALIBRATION_FILE_RESPONSE_TOPIC
|
||||
response_payload = _calibration_response(path, _content_for(path))
|
||||
self.events.append(("message", (response_topic, response_payload)))
|
||||
return SimpleNamespace(rc=mqtt.MQTT_ERR_SUCCESS, mid=mid)
|
||||
|
||||
def loop(self, timeout: float) -> mqtt.MQTTErrorCode:
|
||||
del timeout
|
||||
if not self.events:
|
||||
return mqtt.MQTT_ERR_SUCCESS
|
||||
kind, value = self.events.popleft()
|
||||
if kind == "connect":
|
||||
self.on_connect(self, None, SimpleNamespace(), value, None)
|
||||
elif kind == "subscribe":
|
||||
self.on_subscribe(
|
||||
self,
|
||||
None,
|
||||
value,
|
||||
[FakeReasonCode() for _topic in CALIBRATION_READ_SUBSCRIPTIONS],
|
||||
None,
|
||||
)
|
||||
elif kind == "publish":
|
||||
self.on_publish(self, None, value, FakeReasonCode(), None)
|
||||
elif kind == "message":
|
||||
topic, payload = value
|
||||
self.on_message(self, None, SimpleNamespace(topic=topic, payload=payload))
|
||||
return mqtt.MQTT_ERR_SUCCESS
|
||||
|
||||
def unsubscribe(self, topics: list[str]) -> tuple[mqtt.MQTTErrorCode, int]:
|
||||
self.unsubscribe_calls.append(topics)
|
||||
return mqtt.MQTT_ERR_SUCCESS, 50
|
||||
|
||||
def disconnect(self) -> mqtt.MQTTErrorCode:
|
||||
return mqtt.MQTT_ERR_SUCCESS
|
||||
|
||||
|
||||
def test_reviewed_mqtt_reader_performs_one_identity_read_then_two_exact_file_reads() -> None:
|
||||
client = FakeCalibrationClient()
|
||||
reader = ReviewedCalibrationMqttReader(
|
||||
"10.255.254.54",
|
||||
client_factory=lambda: client, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
result = reader.read_factory_calibration(_authority())
|
||||
|
||||
assert result.binding == _binding()
|
||||
assert [item.path for item in result.files] == list(FACTORY_CALIBRATION_PATHS)
|
||||
assert client.subscribe_calls == [list(CALIBRATION_READ_SUBSCRIPTIONS)]
|
||||
assert [topic for topic, _payload, _qos, _retain in client.publish_calls] == [
|
||||
"lixel/application/request/device_info",
|
||||
CALIBRATION_FILE_REQUEST_TOPIC,
|
||||
CALIBRATION_FILE_REQUEST_TOPIC,
|
||||
]
|
||||
assert all(qos == 2 and not retain for _topic, _payload, qos, retain in client.publish_calls)
|
||||
assert result.transport["publish_attempts"] == 3
|
||||
assert result.transport["correlated_responses"] == 3
|
||||
assert result.transport["write_command_available"] is False
|
||||
|
||||
|
||||
def test_private_snapshot_is_new_only_and_hashes_both_exact_artifacts(tmp_path: Path) -> None:
|
||||
files = tuple(
|
||||
decode_factory_calibration_file_response(
|
||||
_calibration_response(path, _content_for(path)),
|
||||
build_factory_calibration_file_read(_authority(), _binding(), path),
|
||||
_authority(),
|
||||
_binding(),
|
||||
)
|
||||
for path in FACTORY_CALIBRATION_PATHS
|
||||
)
|
||||
result = FactoryCalibrationReadResult(
|
||||
binding=_binding(),
|
||||
files=(files[0], files[1]),
|
||||
transport={"automatic_retry": False, "publish_attempts": 3},
|
||||
)
|
||||
|
||||
snapshot = seal_factory_calibration_snapshot(
|
||||
result,
|
||||
evidence_root=tmp_path,
|
||||
compatibility_profile_id=PROFILE_ID,
|
||||
captured_at=datetime(2026, 7, 20, 10, 30, tzinfo=UTC),
|
||||
)
|
||||
|
||||
assert snapshot["status"] == "available"
|
||||
internal = snapshot["device_internal_calibration"]
|
||||
assert isinstance(internal, dict)
|
||||
snapshot_path = Path(str(internal["private_snapshot_path"]))
|
||||
assert snapshot_path.is_dir()
|
||||
assert (snapshot_path / "camera.yaml").read_bytes() == _content_for(
|
||||
FACTORY_CAMERA_CALIBRATION_PATH
|
||||
)
|
||||
assert (snapshot_path / "extrinsic_camera_lidar.yaml").read_bytes() == _content_for(
|
||||
FACTORY_CAMERA_LIDAR_EXTRINSIC_PATH
|
||||
)
|
||||
manifest = json.loads((snapshot_path / "manifest.json").read_text(encoding="utf-8"))
|
||||
assert manifest["source"]["request_command"] == 5
|
||||
assert manifest["source"]["write_command_available"] is False
|
||||
assert manifest["normalized_calibration"]["transform_notation"] == (
|
||||
"T_destination_from_source"
|
||||
)
|
||||
assert [item["source_path"] for item in manifest["artifacts"]] == list(
|
||||
FACTORY_CALIBRATION_PATHS
|
||||
)
|
||||
assert snapshot_path.stat().st_mode & 0o777 == 0o700
|
||||
assert all(path.stat().st_mode & 0o777 == 0o600 for path in snapshot_path.iterdir())
|
||||
assert internal["camera_stream_mapping"]["status"] == "firmware-profile-verified"
|
||||
Reference in New Issue
Block a user