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
+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)