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.
This commit is contained in:
DCCONSTRUCTIONS
2026-09-10 09:21:24 +03:00
parent 54a85fdf50
commit a3c15e11e9
125 changed files with 11916 additions and 251 deletions
+1
View File
@@ -0,0 +1 @@
"""Mission Core's private Insta360 X4 worker implementation."""
+270
View File
@@ -0,0 +1,270 @@
"""Unprivileged model broker; public authority stays in Node/Core."""
import pwd
import re
import threading
import uuid
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from .errors import failure
from .http import Server, request
from .operations import UNKNOWN, Operations
ROOT = Path("/run/mission-core-insta360")
INSTANCES = Path("/run/mission-core-x4-instances")
IDENTIFIER = re.compile(r"instax4_[0-9a-f]{32}")
STATE = Path("/var/lib/mission-core-insta360-broker")
def worker_socket(identifier):
if not isinstance(identifier, str) or not IDENTIFIER.fullmatch(identifier):
raise ValueError("Invalid camera identity")
return INSTANCES / identifier / "driver.sock"
class Broker:
def __init__(self):
self.instance = "x4broker_" + uuid.uuid4().hex
self.media_lock = threading.Lock()
self.media = None
self.media_operations = {}
self.capture_locks = {}
def capture_lock(self, identifier):
with self.media_lock:
if identifier not in self.capture_locks and len(self.capture_locks) >= 500:
raise RuntimeError("Camera inventory exceeds the protocol limit")
return self.capture_locks.setdefault(identifier, threading.Lock())
def capture_operation(self, identifier, value):
path = worker_socket(identifier)
lock = self.capture_lock(identifier)
with self.media_lock:
if self.media is None:
from .media import Engine
self.media = Engine()
# Only one camera's START/STOP is serialized here; other devices keep
# independent locks and SDK operation journals.
with lock:
current = request(path, "/snapshot", timeout=2)
session = current["session_id"]
if current.get("id") != identifier or value.get("session") != {
"device_id": identifier,
"session_id": session,
}:
raise ValueError("Camera session changed")
key = (identifier, session)
starting = value["action_id"] == "preview.start"
starting_idle = starting and current.get("status", {}).get("preview") != 1
if starting:
if not current.get("online"):
raise RuntimeError("Camera state is unavailable")
self.media.capture(key, path, True)
try:
result = request(path, "/operation", value, timeout=55)
except (OSError, RuntimeError, ValueError):
if starting_idle:
self.media.capture(key, path, False)
raise
if (starting_idle and result.get("state") != "complete") or (
not starting and result.get("state") == "complete"
):
# Releasing a decoder never sends STOP to the camera or SD.
self.media.capture(key, path, False)
return result
def verify_operation(self, identifier, value):
path = worker_socket(identifier)
# Serialize with preview transitions, preserving one receipt namespace
# whether verification uses the live decoder or an idle camera check.
with self.capture_lock(identifier):
current = request(path, "/snapshot", timeout=2)
if current.get("id") != identifier:
raise ValueError("Camera identity changed")
operation = Operations(
identifier,
current["session_id"],
STATE / identifier / "verification",
VerificationCalls(self, path, value),
)
return operation.execute(value)
def media_operation(self, identifier, value):
path = worker_socket(identifier)
current = request(path, "/snapshot", timeout=2)
if current.get("id") != identifier:
raise ValueError("Camera identity changed")
session = current["session_id"]
with self.media_lock:
if self.media is None:
from .media import Engine
self.media = Engine()
previous = self.media_operations.get(identifier)
if previous is None or previous.session_id != session:
previous = Operations(
identifier,
session,
STATE / identifier,
MediaCalls(self.media, (identifier, session), path),
)
self.media_operations[identifier] = previous
return previous.execute(value)
def snapshots(self):
paths = sorted(INSTANCES.glob("instax4_*/driver.sock"))
if len(paths) > 500:
raise RuntimeError("Camera inventory exceeds the protocol limit")
def read(path):
if not IDENTIFIER.fullmatch(path.parent.name):
return None
try:
value = request(path, "/snapshot", timeout=0.75)
return value if value.get("id") == path.parent.name else None
except (OSError, RuntimeError, ValueError):
return None
pool = ThreadPoolExecutor(max_workers=8)
pending = [pool.submit(read, path) for path in paths]
values = []
try:
for future in as_completed(pending, timeout=2):
value = future.result()
if value is not None:
values.append(value)
except TimeoutError:
pass
finally:
pool.shutdown(wait=False, cancel_futures=True)
return sorted(values, key=lambda value: value["id"])
def item(self, value, node):
status = value["status"]
preview = status.get("preview")
return {
"id": value["id"],
"name": "Insta360 X4",
"model": "Insta360 X4",
"kind": "insta360.x4",
"initializable": True,
"configured": False,
"prepared": value["prepared"],
"verified": value["verified"],
"online": value["online"],
"preparation_safe": value["preparation_safe"],
"usb": "USB",
"firmware": status.get("firmware"),
"layers": [],
"camera_status": status,
"snapshot": {
"revision": value["revision"],
"observed_at": value["observed_at"],
"context": {
"session_id": value["session_id"],
"opened_at": value["opened_at"],
"device": {
"device_id": value["id"],
"stability": "stable",
"basis": "hardware-identifier",
"model": {
"plugin_id": "missioncore.insta360",
"plugin_version": "0.1.3",
"model_id": "insta360.x4",
},
},
"execution": {
"node_id": node,
"agent_instance_id": self.instance,
"platform": "linux",
},
},
"acquisition": "streaming"
if preview == 1
else "idle"
if preview == 0
else "failed",
"connectivity": "connected" if value["online"] else "offline",
"enrollment": "enrolled" if value["prepared"] else "empty",
"message": value["message"],
},
}
def dispatch(self, method, route, value, headers):
if method == "GET" and route == "/prepare-safe":
snapshots = self.snapshots()
expected = list(INSTANCES.glob("instax4_*"))
return {
"safe": len(snapshots) == len(expected)
and all(item["preparation_safe"] for item in snapshots)
}
node = headers.get("X-Node-Id", "")
if not re.fullmatch(r"node_[0-9a-f]{64}", node):
raise ValueError("Node identity is required")
if method == "GET" and route == "/inventory":
return {"items": [self.item(item, node) for item in self.snapshots()]}
if method == "POST" and route == "/operation":
identifier = value["session"]["device_id"]
if value.get("action_id") in ("preview.start", "preview.stop"):
return self.capture_operation(identifier, value)
if value.get("action_id") in ("offer", "close-peer"):
return self.media_operation(identifier, value)
if value.get("action_id") == "verify":
return self.verify_operation(identifier, value)
return request(worker_socket(identifier), "/operation", value, timeout=55)
raise ValueError("Unsupported model route")
class MediaCalls:
def __init__(self, engine, key, path):
self.engine, self.key, self.path = engine, key, path
def call(self, action, params):
if action == "offer":
current = request(self.path, "/snapshot", timeout=2)
if (
current.get("session_id") != self.key[1]
or not current.get("online")
or current.get("status", {}).get("preview") != 1
):
raise RuntimeError("Camera preview is not active")
return self.engine.call(self.key, self.path, action, params)
class VerificationCalls:
def __init__(self, broker, path, command):
self.broker, self.path, self.command = broker, path, command
def call(self, action, params):
current = request(self.path, "/snapshot", timeout=2)
session = self.command["session"]
if (
current.get("id") != session["device_id"]
or current.get("session_id") != session["session_id"]
or not current.get("online")
):
return dict(UNKNOWN)
status = current.get("status", {})
if status.get("recording") != 0:
return failure("verification_recording_active")
if status.get("preview") == 1:
engine = self.broker.media
if engine is None:
return failure("preview_restart_required")
return engine.verify((session["device_id"], session["session_id"]))
if status.get("preview") == 0:
# Preserve worker ownership of the bounded temporary idle preview.
return request(self.path, "/operation", self.command, timeout=55)
return dict(UNKNOWN)
def main():
path = ROOT / "driver.sock"
if path.exists():
path.unlink()
allowed = {0, pwd.getpwnam("mission-core-node").pw_uid}
with Server(path, Broker().dispatch, allowed) as server:
path.chmod(0o660)
server.serve_forever(poll_interval=0.5)
+12
View File
@@ -0,0 +1,12 @@
"""Public messages are allowlisted; vendor error text never crosses the boundary."""
MESSAGES = {
"unsupported_camera_parameter": "Параметр или действие недоступны в текущем режиме камеры.",
"verification_recording_active": "Для проверки изображения остановите запись на камере.",
"preview_no_decodable_image": "Камера не передала декодируемое изображение.",
"preview_restart_required": "Не поступают свежие кадры. Остановите и снова начните просмотр.",
}
def failure(code):
return {"state": "error", "error": code}
+191
View File
@@ -0,0 +1,191 @@
"""Bounded per-camera encoded fanout. Readers never consume each other's data."""
import json
import re
import struct
import threading
import time
from collections import deque
MAX_PACKET = 4 * 1024 * 1024
MAX_BYTES = 8 * 1024 * 1024
MAX_ENTRIES = 64
MAX_WIRE = MAX_PACKET + 1028
MAX_PARAMETERS = 65536
START_CODE = re.compile(b"\x00\x00(?:\x00)?\x01")
class Parameters:
"""Retain only codec setup, so a late viewer can decode the next keyframe.
Parameter sets describe this preview generation, never a previous session.
Media history and camera commands are not retained or replayed.
"""
def __init__(self):
self.streams = {}
def packet(self, header, data):
codec = header["codec"]
required = (7, 8) if codec == 0 else (32, 33, 34)
keyframes = (5,) if codec == 0 else (16, 17, 18, 19, 20, 21)
saved = self.streams.setdefault((header["stream_index"], codec), {})
markers = START_CODE.finditer(data)
marker = next(markers, None)
present, keyframe = set(), False
while marker is not None:
following = next(markers, None)
if marker.end() >= len(data):
break
kind = data[marker.end()] & 31 if codec == 0 else (data[marker.end()] >> 1) & 63
keyframe |= kind in keyframes
if kind in required:
end = following.start() if following is not None else len(data)
value = data[marker.start() : end]
if len(value) > MAX_PARAMETERS:
saved.clear()
return data
saved[kind] = value
present.add(kind)
marker = following
if keyframe and all(kind in saved for kind in required):
prefix = b"".join(saved[kind] for kind in required if kind not in present)
if len(prefix) + len(data) <= MAX_PACKET:
return prefix + data
return data
def encode(value):
if value is None:
return b""
header, data = value
raw = json.dumps(header, allow_nan=False, separators=(",", ":")).encode()
if len(raw) > 1024 or not 0 < len(data) <= MAX_PACKET:
raise ValueError("Invalid video envelope")
return struct.pack("!I", len(raw)) + raw + data
def decode(raw):
if not raw:
return None
if len(raw) < 5 or len(raw) > MAX_WIRE:
raise ValueError("Invalid video envelope")
size = struct.unpack("!I", raw[:4])[0]
if not 0 < size <= 1024 or len(raw) <= 4 + size:
raise ValueError("Invalid video envelope")
header, data = json.loads(raw[4 : 4 + size]), raw[4 + size :]
if (
not isinstance(header, dict)
or any(
type(header.get(key)) is not int or header[key] < 0
for key in ("cursor", "generation", "stream_index", "codec")
)
or header["stream_index"] not in (0, 1)
or header["codec"] not in (0, 1)
or type(header.get("gap")) is not bool
or not 0 < len(data) <= MAX_PACKET
):
raise ValueError("Invalid video metadata")
return header, data
class Feed:
def __init__(self, camera):
self.camera = camera
self.lock = threading.Condition()
self.source_lock = threading.Lock()
self.queue = deque()
self.bytes = self.cursor = self.generation = 0
self.source_generation = None
self.parameters = Parameters()
self.closed = threading.Event()
self.thread = threading.Thread(target=self.pump, daemon=True)
def clear(self):
with self.lock:
self.queue.clear()
self.bytes = 0
self.parameters = Parameters()
self.generation += 1
self.lock.notify_all()
def append(self, value):
source, data = value
if not 0 < len(data) <= MAX_PACKET:
self.clear()
return
with self.lock:
if self.source_generation != source["generation"]:
self.queue.clear()
self.bytes = 0
self.source_generation = source["generation"]
self.generation += 1
self.parameters = Parameters()
data = self.parameters.packet(source, data)
while self.queue and (
self.bytes + len(data) > MAX_BYTES or len(self.queue) >= MAX_ENTRIES
):
_, removed, _ = self.queue.popleft()
self.bytes -= len(removed)
self.cursor += 1
header = dict(source, cursor=self.cursor, generation=self.generation)
self.queue.append((header, data, time.monotonic()))
self.bytes += len(data)
self.lock.notify_all()
def read(self, cursor, wait=0.1):
if type(cursor) is not int or not 0 <= cursor < 2**64:
raise ValueError("Invalid video cursor")
deadline = time.monotonic() + min(max(wait, 0), 0.2)
with self.lock:
while True:
for header, data, observed in self.queue:
if header["cursor"] > cursor and time.monotonic() - observed < 2:
return dict(
header, gap=bool(cursor and header["cursor"] != cursor + 1)
), data
remaining = deadline - time.monotonic()
if remaining <= 0 or self.closed.is_set():
return None
self.lock.wait(remaining)
def reader(self):
with self.lock:
cursor = self.cursor
def read():
nonlocal cursor
value = self.read(cursor, wait=0)
if value is not None:
cursor = value[0]["cursor"]
return value
return read
def change(self, action):
# No packet read from the previous preview may be appended after a
# START/STOP boundary and mistaken for evidence of the new preview.
with self.source_lock:
self.clear()
try:
return action()
finally:
self.clear()
def pump(self):
while not self.closed.is_set():
try:
with self.source_lock:
value = self.camera.video()
if value is not None:
self.append(value)
continue
except (OSError, RuntimeError, ValueError):
self.clear()
self.closed.wait(0.01)
def close(self):
self.closed.set()
self.clear()
if self.thread.is_alive():
self.thread.join(timeout=1)
+145
View File
@@ -0,0 +1,145 @@
"""Bounded private HTTP over Unix sockets, never a public device endpoint."""
import http.client
import json
import socket
import socketserver
import struct
import threading
from dataclasses import dataclass
from http.server import BaseHTTPRequestHandler
LIMIT = 65536
INVENTORY_LIMIT = 2 * 1024 * 1024
@dataclass(frozen=True)
class Binary:
data: bytes
def request(path, route, value=None, timeout=25, binary=False):
client = http.client.HTTPConnection("driver", timeout=timeout)
client.sock = socket.socket(socket.AF_UNIX)
client.sock.settimeout(timeout)
try:
client.sock.connect(str(path))
body = None if value is None else json.dumps(value, allow_nan=False).encode()
client.request(
"GET" if value is None else "POST", route, body, {"Content-Type": "application/json"}
)
reply = client.getresponse()
limit = (
4 * 1024 * 1024 + 1028
if binary
else INVENTORY_LIMIT
if route == "/inventory"
else LIMIT
)
raw = reply.read(limit + 1)
if len(raw) > limit or reply.status != 200:
raise RuntimeError("Camera service request failed")
if binary:
if reply.getheader("Content-Type") != "application/octet-stream":
raise RuntimeError("Invalid video response")
return raw
return json.loads(raw)
except http.client.HTTPException as error:
raise RuntimeError("Camera service response is incomplete") from error
finally:
client.close()
class Server(socketserver.ThreadingMixIn, socketserver.UnixStreamServer):
daemon_threads = True
block_on_close = False
def __init__(self, path, dispatch, allowed_uids):
self.dispatch = dispatch
self.allowed_uids = frozenset(allowed_uids)
self.slots = threading.BoundedSemaphore(12)
super().__init__(str(path), Handler)
def verify_request(self, request, address):
_, uid, _ = struct.unpack(
"3i", request.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, 12)
)
return uid in self.allowed_uids
def process_request(self, request, address):
if not self.slots.acquire(blocking=False):
self.shutdown_request(request)
return
try:
super().process_request(request, address)
except BaseException:
self.slots.release()
raise
def process_request_thread(self, request, address):
try:
super().process_request_thread(request, address)
finally:
self.slots.release()
class Handler(BaseHTTPRequestHandler):
def setup(self):
self.request.settimeout(10)
super().setup()
def log_message(self, *_):
pass
def do_GET(self):
self.handle_request()
def do_POST(self):
self.handle_request()
def handle_request(self):
try:
if self.headers.get("Transfer-Encoding"):
raise ValueError("Chunked commands are not supported")
value = None
if self.command == "POST":
length = int(self.headers.get("Content-Length", "0"))
if (
not 0 < length <= LIMIT
or self.headers.get("Content-Type") != "application/json"
):
raise ValueError("Invalid command size")
raw = self.rfile.read(length)
if len(raw) != length:
raise ValueError("Truncated command")
value = json.loads(raw)
result = self.server.dispatch(self.command, self.path, value, self.headers)
status = 200
except (ValueError, KeyError, TypeError):
status, result = 400, {"error": "Некорректная команда камеры."}
except (RuntimeError, OSError, TimeoutError):
status, result = 409, {"error": "Камера не ответила. Проверьте подключение."}
binary = isinstance(result, Binary)
data = (
result.data
if binary
else json.dumps(result, ensure_ascii=False, allow_nan=False).encode()
)
limit = (
4 * 1024 * 1024 + 1028
if binary
else INVENTORY_LIMIT
if self.path == "/inventory"
else LIMIT
)
if len(data) > limit:
binary = False
status, data = 500, b'{"error":"Camera response exceeds its limit"}'
self.send_response(status)
self.send_header(
"Content-Type", "application/octet-stream" if binary else "application/json"
)
self.send_header("Content-Length", str(len(data)))
self.send_header("Connection", "close")
self.end_headers()
self.wfile.write(data)
+85
View File
@@ -0,0 +1,85 @@
"""Read-only USB admission, before vendor enumeration or library loading."""
import hashlib
import os
import re
import stat
from dataclasses import dataclass
from pathlib import Path
def device_id(serial):
return "instax4_" + hashlib.sha256(serial.encode()).hexdigest()[:32]
@dataclass(frozen=True)
class Binding:
device_id: str
port: str
bus: int
address: int
serial: str
@property
def device_path(self):
return f"/dev/bus/usb/{self.bus:03d}/{self.address:03d}"
def read_binding(port, root=Path("/sys/bus/usb/devices")):
if not isinstance(port, str) or not re.fullmatch(
r"[1-9][0-9]*-[1-9][0-9]*(?:\.[1-9][0-9]*)*", port
):
raise ValueError("Invalid camera transport binding")
path = root / port
def read(name):
return (path / name).read_text().strip()
if (read("idVendor"), read("idProduct"), read("product")) != ("2e1a", "0002", "Insta360 X4"):
raise ValueError("USB device is not the admitted X4 model")
serial = read("serial")
if not serial or len(serial) > 256 or any(ord(c) < 32 for c in serial):
raise ValueError("Camera identity is unavailable")
bus, address = int(read("busnum")), int(read("devnum"))
if not 1 <= bus <= 999 or not 1 <= address <= 127:
raise ValueError("Invalid USB bus address")
return Binding(device_id(serial), port, bus, address, serial)
def verify_isolation(binding):
"""No override switch. An ordinary host process cannot load the SDK here.
The root-owned instance service must have PrivateDevices+one BindPaths,
DevicePolicy=closed, NoNewPrivileges, no capabilities, and PrivateNetwork.
Service declarations additionally enforce cgroup access to this USB node.
"""
if os.uname().sysname != "Linux" or os.geteuid() == 0:
raise RuntimeError("X4 requires an unprivileged isolated Linux worker")
current = read_binding(binding.port)
if current != binding:
raise RuntimeError("Camera changed before SDK initialization")
status = dict(
line.split(":", 1)
for line in Path("/proc/self/status").read_text().splitlines()
if ":" in line
)
if int(status["CapEff"].strip(), 16) or status["NoNewPrivs"].strip() != "1":
raise RuntimeError("Camera process has unexpected privileges")
proof = Path("/run/mission-core-x4-control/host-net-inode")
for path in (proof.parent, proof):
info = path.lstat()
if path.is_symlink() or info.st_uid != 0 or info.st_mode & 0o022:
raise RuntimeError("Untrusted host namespace evidence")
host_network = int(proof.read_text())
if host_network <= 0 or Path("/proc/self/ns/net").stat().st_ino == host_network:
raise RuntimeError("Camera process has access to the host network")
devices = list(Path("/dev/bus/usb").glob("*/*"))
if devices != [Path(binding.device_path)]:
raise RuntimeError("Camera process can see unrelated USB devices")
info = devices[0].lstat()
expected_minor = (binding.bus - 1) * 128 + binding.address - 1
if not stat.S_ISCHR(info.st_mode) or (os.major(info.st_rdev), os.minor(info.st_rdev)) != (
189,
expected_minor,
):
raise RuntimeError("Camera device node does not match the transport")
+24
View File
@@ -0,0 +1,24 @@
"""OS lock shared by every SDK effect and exclusive profile activation."""
import fcntl
from contextlib import contextmanager
from pathlib import Path
ROOT = Path("/var/lib/mission-core-insta360")
@contextmanager
def acquisition(wait=False):
path = ROOT / "lifecycle.lock"
for item in (ROOT, path):
info = item.lstat()
if item.is_symlink() or info.st_uid != 0 or info.st_mode & 0o022:
raise RuntimeError("Untrusted camera lifecycle lock")
with path.open("rb") as handle:
try:
fcntl.flock(handle, fcntl.LOCK_SH | (0 if wait else fcntl.LOCK_NB))
except BlockingIOError:
raise RuntimeError("Подготовка драйвера ещё выполняется.") from None
if (ROOT / "maintenance").exists():
raise RuntimeError("Драйвер X4 обновляется. Повторите после подготовки.")
yield
+394
View File
@@ -0,0 +1,394 @@
"""Private WebRTC operator view outside the vendor SDK network namespace.
Opening/closing a peer never sends a camera command. Each source has its own
worker socket, session, decoder and bounded latest-frame slot. No stitching.
"""
import asyncio
import ipaddress
import json
import logging
import threading
import time
import uuid
from concurrent.futures import TimeoutError as FutureTimeout
from fractions import Fraction
import aioice.ice
import av
from aiortc import (
RTCConfiguration,
RTCPeerConnection,
RTCRtpSender,
RTCSessionDescription,
VideoStreamTrack,
)
from aiortc.mediastreams import MediaStreamError
from .errors import failure
from .frames import decode
from .http import request
NETWORKS = tuple(
ipaddress.ip_network(value)
for value in (
"127.0.0.0/8",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"100.64.0.0/10",
)
)
def private(value):
try:
address = ipaddress.ip_address(value)
return address.version == 4 and any(address in network for network in NETWORKS)
except ValueError:
return False
_host_addresses = aioice.ice.get_host_addresses
aioice.ice.get_host_addresses = lambda use_ipv4, use_ipv6: [
value for value in _host_addresses(use_ipv4=True, use_ipv6=False) if private(value)
]
def admit_sdp(sdp):
if not isinstance(sdp, str) or not 0 < len(sdp) <= 32768:
raise ValueError("Invalid camera offer")
for line in sdp.splitlines():
if line.startswith("a=candidate:"):
fields = line.split()
if len(fields) < 8 or not (private(fields[4]) or fields[4].endswith(".local")):
raise ValueError("Camera preview requires a private network")
class Source:
def __init__(self, path, session):
self.path, self.session = path, session
self.lock = threading.Lock()
self.frame = None
self.sequence = 0
self.observed = 0.0
self.started = time.monotonic()
self.closed = threading.Event()
self.thread = threading.Thread(target=self.pump, daemon=True)
def current(self):
with self.lock:
return self.sequence, self.frame, self.observed
def pump(self):
cursor, key, decoder, resized_at = 0, None, None, 0.0
try:
while not self.closed.is_set():
raw = request(
self.path,
"/video",
{"session_id": self.session, "cursor": cursor},
timeout=2,
binary=True,
)
value = decode(raw)
if value is None:
continue
header, data = value
cursor = header["cursor"]
if header["stream_index"] != 0:
continue
current = (header["generation"], header["codec"])
if decoder is None or key != current or header["gap"]:
decoder = av.CodecContext.create(
"h264" if header["codec"] == 0 else "hevc", "r"
)
decoder.thread_count = 1
key = current
try:
for packet in decoder.parse(data):
for frame in decoder.decode(packet):
if not (0 < frame.width <= 4096 and 0 < frame.height <= 2160):
raise RuntimeError("Camera frame exceeds the admitted view profile")
now = time.monotonic()
if now - resized_at < 1 / 15:
continue
width = min(1280, frame.width) // 2 * 2
height = max(2, round(frame.height * width / frame.width) // 2 * 2)
output = frame.reformat(width=width, height=height, format="yuv420p")
output.pts = round((now - self.started) * 90000)
output.time_base = Fraction(1, 90000)
with self.lock:
self.frame, self.observed = output, now
self.sequence += 1
resized_at = now
except av.error.FFmpegError:
decoder = None
except (OSError, ValueError, RuntimeError) as error:
logging.getLogger(__name__).warning(
"X4 preview source stopped (%s)", type(error).__name__
)
finally:
self.closed.set()
with self.lock:
self.frame = None
def close(self):
self.closed.set()
class Track(VideoStreamTrack):
def __init__(self, source):
super().__init__()
self.source, self.sequence, self.next_at = source, 0, 0.0
async def recv(self):
while self.readyState == "live" and not self.source.closed.is_set():
await asyncio.sleep(max(0, self.next_at - time.monotonic()))
sequence, frame, observed = self.source.current()
if frame is not None and sequence > self.sequence and time.monotonic() - observed < 2:
self.sequence = sequence
self.next_at = time.monotonic() + 1 / 15
# Distinct AV frames per encoder: force-keyframe decisions in
# one browser must not mutate another browser's frame object.
copy = av.VideoFrame(frame.width, frame.height, "yuv420p")
for target, source in zip(copy.planes, frame.planes, strict=True):
if target.line_size == source.line_size:
target.update(source)
else:
raw = bytes(source)
padded = bytearray(target.buffer_size)
for row in range(target.height):
padded[
row * target.line_size : row * target.line_size + target.width
] = raw[row * source.line_size : row * source.line_size + target.width]
target.update(padded)
copy.pts, copy.time_base = frame.pts, frame.time_base
return copy
await asyncio.sleep(0.01)
raise MediaStreamError
class Peers:
def __init__(self, source_factory=Source):
self.items, self.sources = {}, {}
self.primed = {}
self.source_factory = source_factory
def source(self, key, path):
source = self.sources.get(key)
if source is not None and source.closed.is_set():
raise RuntimeError("Camera video session ended")
if source is None:
if len(self.sources) >= 4:
raise ValueError("Остановите лишний просмотр камеры.")
source = self.source_factory(path, key[1])
self.sources[key] = source
source.thread.start()
return source
async def prime(self, key, path):
# Start reading before the explicit camera START. Some previews expose
# an independently decodable beginning only once per capture session.
source = self.source(key, path)
if key in self.primed:
return {"ok": True}
async def watch():
started, active, fresh = time.monotonic(), False, time.monotonic()
while key in self.primed:
try:
status = await asyncio.to_thread(request, path, "/snapshot", timeout=2)
preview = status.get("status", {}).get("preview")
ended = status.get("session_id") != key[1] or source.closed.is_set()
if status.get("online"):
fresh = time.monotonic()
ended |= active and preview == 0
active |= preview == 1
ended |= active and time.monotonic() - fresh > 15
except (OSError, RuntimeError, ValueError):
ended = True
if ended or (not active and time.monotonic() - started > 45):
await self.release(key)
return
await asyncio.sleep(1)
self.primed[key] = asyncio.create_task(watch())
return {"ok": True}
async def release(self, key):
task = self.primed.pop(key, None)
if task is not None and task is not asyncio.current_task():
task.cancel()
for identifier, entry in tuple(self.items.items()):
if entry["key"] == key:
await self.close(key, identifier)
source = self.sources.pop(key, None)
if source is not None:
source.close()
await asyncio.to_thread(source.thread.join, 3)
return {"ok": True}
async def verify(self, key, timeout=8):
# An active preview may have emitted its only keyframe minutes ago.
# Verify two NEW decoded frames from its existing source, never open a
# competing decoder or accept a retained frame as fresh evidence.
source = self.sources.get(key)
if source is None or source.closed.is_set():
return failure("preview_restart_required")
initial = source.current()[0]
started = time.monotonic()
while time.monotonic() - started < timeout:
if self.sources.get(key) is not source or source.closed.is_set():
break
sequence, frame, observed = source.current()
if frame is not None and sequence >= initial + 2 and time.monotonic() - observed < 2:
return {
"state": "complete",
"result": {
"verified": True,
"streams": [
{
"stream_index": 0,
"width": frame.width,
"height": frame.height,
"frames": sequence - initial,
}
],
"duration_ms": round((time.monotonic() - started) * 1000),
},
}
await asyncio.sleep(0.02)
return failure("preview_restart_required")
async def offer(self, key, path, params):
admit_sdp(params["sdp"])
# Inventory remains independent; these limits bound active media only.
if len(self.items) >= 4 or sum(item["key"] == key for item in self.items.values()) >= 2:
raise ValueError("Закройте лишний просмотр камеры.")
source = self.source(key, path)
pc = RTCPeerConnection(RTCConfiguration(iceServers=[]))
identifier = "peer_" + uuid.uuid4().hex
entry = {"key": key, "pc": pc, "source": source, "seen": time.monotonic(), "tasks": set()}
self.items[identifier] = entry
async def telemetry(channel):
while identifier in self.items:
sequence, frame, observed = source.current()
if channel.readyState == "open" and channel.bufferedAmount < 65536:
channel.send(
json.dumps(
{
"frame_sequence": sequence,
"frame_age_ms": round((time.monotonic() - observed) * 1000)
if frame
else None,
"fresh": frame is not None and time.monotonic() - observed < 2,
}
)
)
await asyncio.sleep(0.5)
def task(coroutine):
value = asyncio.create_task(coroutine)
entry["tasks"].add(value)
value.add_done_callback(entry["tasks"].discard)
@pc.on("datachannel")
def datachannel(channel):
if channel.label != "sensor":
channel.close()
return
@channel.on("message")
def message(value):
if value == "keepalive":
entry["seen"] = time.monotonic()
task(telemetry(channel))
@pc.on("connectionstatechange")
async def changed():
if pc.connectionState in ("failed", "closed"):
await self.close(key, identifier)
async def watchdog():
while identifier in self.items:
if source.closed.is_set() or time.monotonic() - entry["seen"] > 30:
await self.close(key, identifier)
return
await asyncio.sleep(1)
try:
sender = pc.addTrack(Track(source))
transceiver = next(item for item in pc.getTransceivers() if item.sender == sender)
transceiver.setCodecPreferences(
[
codec
for codec in RTCRtpSender.getCapabilities("video").codecs
if codec.mimeType.lower() == "video/h264"
]
)
await pc.setRemoteDescription(RTCSessionDescription(sdp=params["sdp"], type="offer"))
await pc.setLocalDescription(await pc.createAnswer())
task(watchdog())
return {"peer_id": identifier, "sdp": pc.localDescription.sdp, "type": "answer"}
except BaseException:
await self.close(key, identifier)
raise
async def close(self, key, identifier):
entry = self.items.get(identifier)
if entry is None:
return {"ok": True}
if entry["key"] != key:
raise ValueError("Preview belongs to a different camera session")
self.items.pop(identifier)
current = asyncio.current_task()
for task in tuple(entry["tasks"]):
if task is not current:
task.cancel()
await entry["pc"].close()
if key not in self.primed and not any(item["key"] == key for item in self.items.values()):
source = self.sources.pop(key)
source.close()
await asyncio.to_thread(source.thread.join, 3)
return {"ok": True}
class Engine:
def __init__(self):
self.loop = asyncio.new_event_loop()
self.peers = Peers()
self.thread = threading.Thread(target=self.loop.run_forever, daemon=True)
self.thread.start()
def capture(self, key, path, start):
work = self.peers.prime(key, path) if start else self.peers.release(key)
future = asyncio.run_coroutine_threadsafe(work, self.loop)
try:
return future.result(timeout=10)
except FutureTimeout as error:
future.cancel()
raise RuntimeError("Camera decoder setup timed out") from error
def call(self, key, path, action, params):
work = (
self.peers.offer(key, path, params)
if action == "offer"
else self.peers.close(key, params["peer_id"])
)
future = asyncio.run_coroutine_threadsafe(work, self.loop)
try:
return {"state": "complete", "result": future.result(timeout=20)}
except FutureTimeout as error:
future.cancel()
raise RuntimeError("Camera preview timed out") from error
def verify(self, key):
future = asyncio.run_coroutine_threadsafe(self.peers.verify(key), self.loop)
try:
return future.result(timeout=10)
except FutureTimeout as error:
future.cancel()
raise RuntimeError("Camera image verification timed out") from error
+179
View File
@@ -0,0 +1,179 @@
"""Typed private C ABI. Imported safely; vendor code loads only after admission."""
import ctypes
import json
import math
import re
import threading
from .identity import verify_isolation
ACTIONS = {
"details": 1,
"settings.read": 2,
"settings.apply": 3,
"preview.start": 4,
"preview.stop": 5,
"record.start": 6,
"record.stop": 7,
"photo.capture": 8,
"files.list": 9,
}
MAX_PACKET = 4 * 1024 * 1024
class VideoHeader(ctypes.Structure):
_fields_ = [
("sequence", ctypes.c_uint64),
("generation", ctypes.c_uint64),
("timestamp", ctypes.c_int64),
("stream_index", ctypes.c_int32),
("codec", ctypes.c_int32),
("bytes", ctypes.c_uint32),
]
def parameters(action, value):
"""Validate before any SDK call; parameters never select a file or library."""
if action == "offer":
if (
not isinstance(value, dict)
or set(value) != {"sdp", "layer"}
or value["layer"] != "preview"
or not isinstance(value["sdp"], str)
or not 0 < len(value["sdp"]) <= 32768
):
raise ValueError("Invalid camera preview offer")
return 0, "", 0.0
if action == "close-peer":
if (
not isinstance(value, dict)
or set(value) != {"peer_id"}
or not isinstance(value["peer_id"], str)
or not re.fullmatch(r"peer_[0-9a-f]{32}", value["peer_id"])
):
raise ValueError("Invalid camera preview identity")
return 0, "", 0.0
if action == "verify":
if not isinstance(value, dict) or value:
raise ValueError("Image verification uses the installed fixed profile")
return 0, "", 0.0
if action not in ACTIONS or not isinstance(value, dict):
raise ValueError("Unsupported camera command")
if action in ("settings.read", "settings.apply"):
required = {"mode"} if action == "settings.read" else {"mode", "key", "value"}
if (
set(value) != required
or type(value["mode"]) is not int
or not 0 <= value["mode"] <= 255
):
raise ValueError("Invalid camera mode")
if action == "settings.read":
return value["mode"], "", 0.0
if value["key"] not in (
"function_mode",
"video_resolution",
"photo_size",
"white_balance",
"iso",
"exposure_mode",
):
raise ValueError("Unsupported camera setting")
number = value["value"]
if (
type(number) not in (int, float)
or not math.isfinite(number)
or not 0 <= number <= 65535
):
raise ValueError("Invalid camera setting")
return value["mode"], value["key"], float(number)
if action == "files.list":
if (
set(value) - {"offset"}
or type(value.get("offset", 0)) is not int
or not 0 <= value.get("offset", 0) <= 100000
):
raise ValueError("Invalid camera file page")
return 0, "", float(value.get("offset", 0))
if value:
raise ValueError("Unexpected camera parameters")
return 0, "", 0.0
class NativeCamera:
def __init__(self, binding, library_path, log_directory):
# No ctypes.CDLL anywhere above this check: loading a .so can execute
# constructors even before the first explicit SDK function call.
verify_isolation(binding)
self.lock = threading.RLock()
self.video_lock = threading.Lock()
self.api = ctypes.CDLL(str(library_path))
self.api.mc_x4_abi.restype = ctypes.c_int
if self.api.mc_x4_abi() != 1:
raise RuntimeError("Incompatible camera adapter")
self.api.mc_x4_open.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
self.api.mc_x4_open.restype = ctypes.c_void_p
self.api.mc_x4_call.argtypes = [
ctypes.c_void_p,
ctypes.c_int,
ctypes.c_int,
ctypes.c_char_p,
ctypes.c_double,
]
self.api.mc_x4_call.restype = ctypes.c_char_p
self.api.mc_x4_read_video.argtypes = [
ctypes.c_void_p,
ctypes.POINTER(VideoHeader),
ctypes.POINTER(ctypes.c_uint8),
ctypes.c_size_t,
]
self.api.mc_x4_read_video.restype = ctypes.c_int
self.api.mc_x4_close.argtypes = [ctypes.c_void_p]
self.api.mc_x4_close.restype = None
self.handle = self.api.mc_x4_open(binding.serial.encode(), str(log_directory).encode())
if not self.handle:
raise RuntimeError("SDK could not open the selected X4")
self.buffer = (ctypes.c_uint8 * MAX_PACKET)()
def call(self, action, values):
mode, key, number = parameters(action, values)
with self.lock:
if not self.handle:
raise RuntimeError("Camera session is closed")
raw = self.api.mc_x4_call(self.handle, ACTIONS[action], mode, key.encode(), number)
if not raw or len(raw) > 65536:
raise RuntimeError("Camera returned an invalid response")
result = json.loads(raw)
if not isinstance(result, dict) or result.get("state") not in (
"complete",
"error",
"unknown",
):
raise RuntimeError("Camera returned an invalid response")
return result
def video(self):
# SDK control calls may block for seconds. The callback queue has its
# own native mutex and can be drained without taking the command lock.
with self.video_lock:
if not self.handle:
return None
header = VideoHeader()
state = self.api.mc_x4_read_video(
self.handle, ctypes.byref(header), self.buffer, MAX_PACKET
)
if state < 0 or header.bytes > MAX_PACKET:
raise RuntimeError("Invalid video packet")
if state == 0:
return None
if header.stream_index not in (0, 1) or header.codec not in (0, 1) or not header.bytes:
raise RuntimeError("Unsupported video packet")
return {name: getattr(header, name) for name, _ in VideoHeader._fields_}, bytes(
self.buffer[: header.bytes]
)
def close(self):
with self.lock, self.video_lock:
if self.handle:
self.api.mc_x4_close(self.handle)
self.handle = None
+105
View File
@@ -0,0 +1,105 @@
"""Per-camera durable effects. A retry retrieves a receipt, never replays START."""
import hashlib
import json
import os
import re
import tempfile
import threading
from datetime import UTC, datetime
from pathlib import Path
from missioncore_plugin_sdk.v0alpha2.operations import OperationRequest
from .errors import MESSAGES
from .native import parameters
UNKNOWN = {
"state": "unknown",
"error": "Результат команды не подтверждён. Обновите состояние камеры.",
}
INVALID = {"state": "error", "error": "Параметр или действие недоступны в текущем режиме камеры."}
def atomic(path, value):
fd, temporary = tempfile.mkstemp(prefix=".receipt-", dir=path.parent)
try:
with os.fdopen(fd, "w") as stream:
os.fchmod(stream.fileno(), 0o600)
json.dump(value, stream, ensure_ascii=False, allow_nan=False)
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, path)
directory = os.open(path.parent, os.O_RDONLY)
try:
os.fsync(directory)
finally:
os.close(directory)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
class Operations:
def __init__(self, device_id, session_id, root, camera):
self.device_id, self.session_id = device_id, session_id
self.root, self.camera = Path(root), camera
self.lock = threading.Lock()
self.root.mkdir(mode=0o700, parents=True, exist_ok=True)
def execute(self, value):
command = OperationRequest.model_validate(value)
if not re.fullmatch(r"op_[0-9a-f]{32}", command.operation_id):
raise ValueError("Invalid operation identity")
if command.session.device_id != self.device_id:
raise ValueError("Command belongs to another camera")
digest = hashlib.sha256(
json.dumps(value, sort_keys=True, allow_nan=False).encode()
).hexdigest()
path = self.root / (command.operation_id + ".json")
with self.lock:
if path.exists():
previous = json.loads(path.read_text())
if previous["digest"] != digest:
raise ValueError("Operation identity is already in use")
return previous["result"]
if command.session.session_id != self.session_id:
raise ValueError("Camera session changed")
if command.deadline_at <= datetime.now(UTC):
raise ValueError("Command deadline expired")
params = dict(command.parameters)
parameters(command.action_id, params)
receipt = {"digest": digest, "result": dict(UNKNOWN)}
# This fsync must complete before the SDK receives the command.
# A crash between dispatch and acknowledgement remains UNKNOWN.
atomic(path, receipt)
try:
result = self.camera.call(command.action_id, params)
if result["state"] == "error":
error = result.get("error")
if not isinstance(error, str):
error = ""
result = {
"state": "error",
"error": error
if error in MESSAGES.values()
else MESSAGES.get(error, "Не удалось выполнить команду камеры."),
}
elif result["state"] == "unknown":
result = dict(UNKNOWN)
# settings.apply must verify the readback, not merely accept
# the SDK's SetXXX acknowledgement or its old cached value.
if command.action_id == "settings.apply" and result["state"] == "complete":
observed = result.get("result", {})
actual = (
observed.get("mode")
if params["key"] == "function_mode"
else observed.get("values", {}).get(params["key"])
)
if actual != params["value"]:
result = dict(UNKNOWN)
receipt["result"] = result
except (RuntimeError, ValueError, OSError, KeyError, TypeError):
receipt["result"] = dict(UNKNOWN)
atomic(path, receipt)
return receipt["result"]
+114
View File
@@ -0,0 +1,114 @@
"""Bounded image verification; packets alone never count as a working camera."""
import time
from .errors import failure
from .operations import UNKNOWN
class Control:
def __init__(self, camera, feed=None):
self.camera = camera
self.feed = feed
self.verified = False
def call(self, action, values):
if action == "verify":
return self.verify()
if self.feed and action in ("preview.start", "preview.stop"):
# The native adapter treats an already reached state as a no-op.
# Preserve the same feed generation: clearing it would discard the
# only initial keyframe even though the camera sends no new START.
current = self.camera.call("details", {})
target = 1 if action == "preview.start" else 0
if (
current.get("state") == "complete"
and current.get("result", {}).get("connected") is True
and current["result"].get("preview") == target
):
return self.camera.call(action, values)
return self.feed.change(lambda: self.camera.call(action, values))
return self.camera.call(action, values)
def verify(self, timeout=8):
import av
self.verified = False
result = self.camera.call("details", {})
status = result.get("result", {})
if (
result.get("state") != "complete"
or status.get("connected") is not True
or status.get("recording") != 0
or status.get("preview") not in (0, 1)
):
return failure("verification_recording_active")
owned = status["preview"] == 0
decoders, observed = {}, {}
outcome = failure("preview_no_decodable_image")
start = time.monotonic()
read_video = self.feed.reader() if self.feed else self.camera.video
try:
if owned and self.call("preview.start", {}).get("state") != "complete":
return dict(UNKNOWN)
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
value = read_video()
if value is None:
time.sleep(0.01)
continue
header, data = value
index, codec, generation = (
header["stream_index"],
header["codec"],
header["generation"],
)
name = "h264" if codec == 0 else "hevc" if codec == 1 else None
if index not in (0, 1) or name is None:
return dict(UNKNOWN)
key = (index, codec, generation)
if header.get("gap") or index not in decoders or decoders[index][0] != key:
decoder = av.CodecContext.create(name, "r")
decoder.thread_count = 1
decoders[index] = (key, decoder)
observed.pop(index, None)
decoder = decoders[index][1]
try:
for packet in decoder.parse(data):
for frame in decoder.decode(packet):
if not (0 < frame.width <= 4096 and 0 < frame.height <= 2160):
return dict(UNKNOWN)
current = observed.setdefault(
index,
{
"stream_index": index,
"codec": name,
"width": frame.width,
"height": frame.height,
"frames": 0,
},
)
current["frames"] += 1
except av.error.FFmpegError:
decoders.pop(index, None)
observed.pop(index, None)
continue
if any(item["frames"] >= 2 for item in observed.values()):
outcome = {
"state": "complete",
"result": {
"verified": True,
"streams": list(observed.values()),
"duration_ms": round((time.monotonic() - start) * 1000),
},
}
break
finally:
if owned:
try:
if self.call("preview.stop", {}).get("state") != "complete":
outcome = dict(UNKNOWN)
except (RuntimeError, ValueError, OSError):
outcome = dict(UNKNOWN)
self.verified = outcome["state"] == "complete"
return outcome
+154
View File
@@ -0,0 +1,154 @@
"""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)