feat(k1): stabilize LAB bridge and isolate onboard device integration

This commit is contained in:
DCCONSTRUCTIONS
2026-09-07 10:31:33 +03:00
parent 020a878915
commit 63ea2bed67
115 changed files with 13700 additions and 988 deletions
+20 -6
View File
@@ -215,8 +215,8 @@ def default_environment_settings() -> EnvironmentSettingsDocument:
description=(
"Свести все сенсоры в синхронную и управляемую операторскую картину."
),
primary_workspace_id="spatial-scene",
secondary_workspace_id="cameras",
primary_workspace_id="cameras",
secondary_workspace_id="world-map",
background=background.model_copy(),
),
missions=EnvironmentPage(
@@ -372,10 +372,24 @@ def _upgrade_environment(payload: object) -> EnvironmentSettingsDocument:
raise ValueError("environment document must be an object")
schema_version = payload.get("schema_version")
if schema_version == "missioncore.operator-environment/v1":
return _upgrade_v1_environment(payload)
if schema_version == "missioncore.operator-environment/v2":
return _upgrade_v2_environment(payload)
return EnvironmentSettingsDocument.model_validate(payload)
document = _upgrade_v1_environment(payload)
elif schema_version == "missioncore.operator-environment/v2":
document = _upgrade_v2_environment(payload)
else:
document = EnvironmentSettingsDocument.model_validate(payload)
# The owner moved the local live scene to LAB. Retire only its old
# Control shortcuts, preserving custom copy, backgrounds and other links.
page = document.pages.observation
if page.primary_workspace_id == "spatial-scene":
page.primary_workspace_id = (
page.secondary_workspace_id
if page.secondary_workspace_id not in (None, "spatial-scene")
else "cameras"
)
page.secondary_workspace_id = None
elif page.secondary_workspace_id == "spatial-scene":
page.secondary_workspace_id = None
return document
class EnvironmentSettingsStore:
+49
View File
@@ -136,3 +136,52 @@ def sensor_operation(
return operation(fleet, vehicle_id, operation_id)
except PairingError as error:
raise HTTPException(404, str(error)) from None
@router.get("/{vehicle_id}/devices/enrollment")
def enrollment_state(
vehicle_id: str, response: Response, fleet: Annotated[FleetRegistry, Depends(local_operator)]
):
response.headers["Cache-Control"] = "no-store"
try:
with fleet.lock:
row = fleet.find(vehicle_id)
return {
**row.get("device_enrollment", {"available": False}),
"node_id": row["node_id"],
"name": row["name"],
"fresh": fleet.public(row)["connectivity"] == "online",
}
except PairingError as error:
raise HTTPException(404, str(error)) from None
@router.post("/{vehicle_id}/devices/enrollment/operations")
def enrollment_submit(
vehicle_id: str,
body: dict,
response: Response,
fleet: Annotated[FleetRegistry, Depends(local_operator)],
):
response.headers["Cache-Control"] = "no-store"
try:
return fleet.device_enrollment.submit(fleet, vehicle_id, body)
except (PairingError, ValueError, TypeError):
# Validation diagnostics must never echo a supplied credential.
raise HTTPException(
409, "Запрос подключения не принят. Обновите БК и проверьте параметры сети."
) from None
@router.get("/{vehicle_id}/devices/enrollment/operations/{operation_id}")
def enrollment_operation(
vehicle_id: str,
operation_id: str,
response: Response,
fleet: Annotated[FleetRegistry, Depends(local_operator)],
):
response.headers["Cache-Control"] = "no-store"
try:
return fleet.device_enrollment.operation(fleet, vehicle_id, operation_id)
except PairingError as error:
raise HTTPException(404, str(error)) from None
+35
View File
@@ -18,6 +18,7 @@ _EXTRA_FIELDS: Final = (
"connection_mode",
"error_category",
"error_code",
"transport_error_code",
"reason_code",
"failed_phase",
"dialogue_stage",
@@ -32,6 +33,20 @@ _EXTRA_FIELDS: Final = (
"device_write_confirmed",
"ble_att_error_code",
"ble_att_error_name",
"resolved_write_mode",
"max_write_without_response_size",
"mtu_size",
"frame_length",
"bridge_frame_length",
"quick_connect_frame_length",
"scan_elapsed_ms",
"scanner_start_ms",
"initial_window_ms",
"first_candidate_ms",
"scan_extended",
"candidate_count",
"likely_k1_candidate_count",
"discovery_generation",
"helper_stage",
"helper_elapsed_ms",
"status_reconciliation",
@@ -102,6 +117,26 @@ class ScannerDiagnosticJsonFormatter(logging.Formatter):
value = getattr(record, field, None)
if value is not None and isinstance(value, (str, int, bool)):
document[field] = value
if record.exc_info is not None:
exception_type, _exception, traceback = record.exc_info
if exception_type is not None:
document["exception_type"] = exception_type.__name__[:128]
if traceback is not None:
while traceback.tb_next is not None:
traceback = traceback.tb_next
code = traceback.tb_frame.f_code
# Preserve the failure location, never exception text, locals,
# source lines or absolute paths that may contain private data.
document["exception_site"] = (
f"{Path(code.co_filename).name}:{traceback.tb_lineno}:{code.co_name}"
)[:256]
properties = getattr(record, "write_characteristic_properties", None)
if isinstance(properties, (list, tuple)) and all(
isinstance(value, str)
and value in {"read", "write", "write-without-response", "notify", "indicate"}
for value in properties
):
document["write_characteristic_properties"] = sorted(set(properties))
return json.dumps(document, ensure_ascii=False, separators=(",", ":"))