Files
DCCONSTRUCTIONS a3c15e11e9 Add packaged Insta360 X4 integration and recover paired Node channels
Discover independent camera instances and prepare their versioned runtime
from Node or remote Core. Add isolated SDK workers, camera controls, raw
dual-fisheye WebRTC preview, and shared action/region loading states.

Recover existing Node bindings over known Tailscale addresses after a Core
LAN address change. Preserve identities and trust, pin both peers, migrate
endpoints with revision checks, and require real heartbeats for online status.
Fix the Python client certificate profile for Go X509 verification.

Pin Design Guideline 8c53f73 and retain installer/build/acceptance history.
Node 0.8.19 is installed; X4 0.1.3-3 is bundled but hardware activation is pending.

Validation: qualified DG/Node builds and Go race tests; 31 fleet tests;
Python-to-Go certificate interoperability and live tailnet recovery with five
fresh heartbeats; prior 38 X4 tests and bounded remote WebRTC acceptance.
Clean-OS, replug/power autonomy, local X4 video and long-run stability remain open.
2026-09-10 09:21:24 +03:00

155 lines
5.9 KiB
Python

"""One SDK session per OS-isolated USB instance. No public network listener."""
import os
import pwd
import threading
import time
import uuid
from datetime import UTC, datetime
from pathlib import Path
from .frames import Feed, encode
from .http import Binary, Server
from .identity import read_binding
from .lifecycle import acquisition
from .native import NativeCamera
from .operations import Operations
from .verification import Control
class Worker:
def __init__(self, binding, runtime, state):
self.binding = binding
self.session = "x4_" + uuid.uuid4().hex
self.opened_at = datetime.now(UTC).isoformat()
self.status_lock = threading.Lock()
self.status = {}
self.observed = 0.0
self.observed_at = self.opened_at
self.revision = 0
self.snapshot_signature = None
self.failure = None
self.ready = False
self.done = threading.Event()
self.camera = None
self.control = None
self.feed = None
self.operations = None
self.runtime, self.state = runtime, state
def connect(self):
# Bounded startup even if a vendor call never returns. No restart loop:
# a new attempt requires explicit preparation or physical reconnection.
timer = threading.Timer(40, lambda: os._exit(70))
timer.daemon = True
timer.start()
try:
logs = self.state / "sdk-logs"
logs.mkdir(mode=0o700, exist_ok=True)
# The prepare process starts services before releasing its lock.
# Wait for that bounded transaction without starting the SDK early.
with acquisition(wait=True):
self.camera = NativeCamera(
self.binding, self.runtime / "lib/libmissioncore_x4.so", logs
)
self.feed = Feed(self.camera)
self.feed.thread.start()
self.control = Control(self.camera, self.feed)
self.operations = Operations(
self.binding.device_id, self.session, self.state / "operations", self.control
)
self.refresh()
self.ready = True
except (RuntimeError, OSError, ValueError):
self.failure = "Не удалось открыть X4. Проверьте питание и режим USB на камере."
finally:
timer.cancel()
def refresh(self):
if read_binding(self.binding.port) != self.binding:
raise RuntimeError("Camera transport changed")
response = self.camera.call("details", {})
if response["state"] != "complete":
raise RuntimeError("Camera status is unconfirmed")
with self.status_lock:
self.status = response["result"]
self.observed = time.monotonic()
self.observed_at = datetime.now(UTC).isoformat()
self.revision += 1
def snapshot(self):
with self.status_lock:
state = dict(self.status)
fresh = time.monotonic() - self.observed < 12
connected = self.ready and fresh and state.get("connected") is True
verified = bool(self.control and self.control.verified)
signature = (self.ready, connected, fresh, verified, self.failure)
if signature != self.snapshot_signature:
self.revision += 1
self.snapshot_signature = signature
self.observed_at = datetime.now(UTC).isoformat()
revision, observed_at = self.revision, self.observed_at
return {
"id": self.binding.device_id,
"session_id": self.session,
"opened_at": self.opened_at,
"prepared": self.ready,
"online": connected,
"verified": verified,
"revision": revision,
"observed_at": observed_at,
"preparation_safe": connected
and state.get("preview") == 0
and state.get("recording") == 0,
"status": state if fresh else {},
"message": self.failure,
}
def dispatch(self, method, route, value, headers):
if method == "GET" and route == "/snapshot":
return self.snapshot()
if method == "POST" and route == "/video":
if (
not self.ready
or value.get("session_id") != self.session
or set(value) != {"session_id", "cursor"}
):
raise ValueError("Camera video session changed")
return Binary(encode(self.feed.read(value["cursor"])))
if method == "POST" and route == "/operation":
if not self.ready:
raise RuntimeError("Camera is unavailable")
with acquisition():
result = self.operations.execute(value)
# Poll asynchronously; a lost state read must not change a durable
# acknowledged command result into a different receipt.
if value.get("action_id") not in ("details", "settings.read", "files.list"):
with self.status_lock:
self.observed = 0
return result
raise ValueError("Unsupported worker route")
def poll(self):
self.connect()
while self.ready and not self.done.wait(3):
try:
self.refresh()
except (RuntimeError, OSError, ValueError):
with self.status_lock:
self.observed = 0
def main(port, runtime):
binding = read_binding(port)
state = Path(os.environ["STATE_DIRECTORY"])
socket = Path(os.environ["RUNTIME_DIRECTORY"]) / "driver.sock"
worker = Worker(binding, runtime, state)
thread = threading.Thread(target=worker.poll, daemon=True)
thread.start()
if socket.exists():
socket.unlink()
allowed = {0, pwd.getpwnam("mission-core-insta360").pw_uid}
with Server(socket, worker.dispatch, allowed) as server:
socket.chmod(0o660)
server.serve_forever(poll_interval=0.5)