Add packaged per-camera X4 wake and preview recovery

This commit is contained in:
DCCONSTRUCTIONS
2026-09-10 14:20:52 +03:00
parent f164606828
commit d45b329de8
22 changed files with 1045 additions and 16 deletions
+34 -4
View File
@@ -30,14 +30,19 @@ class Broker:
self.media = None
self.media_operations = {}
self.capture_locks = {}
self.recovery = None
@staticmethod
def validate_identifier(identifier):
worker_socket(identifier)
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())
return self.capture_locks.setdefault(identifier, threading.RLock())
def capture_operation(self, identifier, value):
def capture_operation(self, identifier, value, restoring=False):
path = worker_socket(identifier)
lock = self.capture_lock(identifier)
with self.media_lock:
@@ -58,6 +63,10 @@ class Broker:
key = (identifier, session)
starting = value["action_id"] == "preview.start"
starting_idle = starting and current.get("status", {}).get("preview") != 1
if restoring and (
not current.get("online") or current.get("status", {}).get("recording") != 0
):
return dict(UNKNOWN)
if starting:
if not current.get("online"):
raise RuntimeError("Camera state is unavailable")
@@ -67,12 +76,16 @@ class Broker:
except (OSError, RuntimeError, ValueError):
if starting_idle:
self.media.capture(key, path, False)
if not starting and self.recovery and not restoring:
self.recovery.preview_intent(identifier, value, dict(UNKNOWN))
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)
if self.recovery and not restoring:
self.recovery.preview_intent(identifier, value, result)
return result
def verify_operation(self, identifier, value):
@@ -204,9 +217,21 @@ class Broker:
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()]}
snapshots = self.snapshots()
if self.recovery:
snapshots = self.recovery.augment(snapshots)
return {"items": [self.item(item, node) for item in snapshots]}
if method == "POST" and route == "/operation":
identifier = value["session"]["device_id"]
if value.get("action_id") == "recovery.configure" and self.recovery:
return self.recovery.operation(identifier, value)
if value.get("action_id") == "preview.stop" and self.recovery:
try:
current = request(worker_socket(identifier), "/snapshot", timeout=2)
except (OSError, RuntimeError, ValueError):
current = {}
if not current.get("online"):
return self.recovery.operation(identifier, value)
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"):
@@ -265,6 +290,11 @@ def main():
if path.exists():
path.unlink()
allowed = {0, pwd.getpwnam("mission-core-node").pw_uid}
with Server(path, Broker().dispatch, allowed) as server:
broker = Broker()
from .recovery import Recovery
broker.recovery = Recovery(broker, STATE / "recovery")
broker.recovery.start()
with Server(path, broker.dispatch, allowed) as server:
path.chmod(0o660)
server.serve_forever(poll_interval=0.5)