infinicode 2.8.92 → 2.8.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,8 +15,10 @@ import struct
15
15
  app = Flask(__name__)
16
16
  CORS(app)
17
17
 
18
- # Camera management
19
- current_camera_index = 0
18
+ # Camera management. Production installs create /dev/robopark-camera from
19
+ # the primary USB capture interface, avoiding /dev/videoN renumbering.
20
+ _configured_camera = os.getenv("ROBOPARK_CAMERA_DEVICE", "")
21
+ current_camera_index = _configured_camera or ("/dev/robopark-camera" if os.path.exists("/dev/robopark-camera") else 0)
20
22
  camera = None
21
23
  camera_lock = threading.Lock()
22
24
  frame_condition = threading.Condition()
@@ -625,8 +627,11 @@ if __name__ == '__main__':
625
627
  print("RoboVision - Raspberry Pi Vision Server (Minimal)")
626
628
  print("=" * 60)
627
629
  print(f"Starting Flask server on http://0.0.0.0:{args.port}")
628
- if webhook_url:
629
- print(f"Motion webhook: {webhook_url}")
630
- print(f"Motion detection: {'ARMED' if motion_detection_active else 'off (POST /api/motion/toggle to arm)'}")
631
- print("=" * 60)
632
- app.run(host='0.0.0.0', port=args.port, debug=False, threaded=True)
630
+ if webhook_url:
631
+ print(f"Motion webhook: {webhook_url}")
632
+ print(f"Motion detection: {'ARMED' if motion_detection_active else 'off (POST /api/motion/toggle to arm)'}")
633
+ print("=" * 60)
634
+ # Start the single camera owner at boot. Production motion and MJPEG
635
+ # readiness must not depend on an operator opening the dashboard first.
636
+ _ensure_camera_worker()
637
+ app.run(host='0.0.0.0', port=args.port, debug=False, threaded=True)
@@ -17,12 +17,29 @@ import os
17
17
  from datetime import datetime
18
18
  import logging
19
19
  import subprocess
20
- import base64
21
- from groq import Groq
20
+ import base64
21
+ import functools
22
+ import sys
23
+ from pathlib import Path
24
+ from groq import Groq
25
+
26
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scheduler"))
27
+ from media_lock import media_lock
22
28
 
23
29
  # Configure logging
24
30
  logging.basicConfig(level=logging.INFO, format='%(message)s')
25
- logger = logging.getLogger(__name__)
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ def exclusive_media(kind: str):
35
+ """Prevent diagnostics from stealing ALSA devices from production."""
36
+ def decorate(func):
37
+ @functools.wraps(func)
38
+ def wrapped(*args, **kwargs):
39
+ with media_lock(kind, timeout=3.0):
40
+ return func(*args, **kwargs)
41
+ return wrapped
42
+ return decorate
26
43
 
27
44
  @asynccontextmanager
28
45
  async def lifespan(app: FastAPI):
@@ -173,7 +190,8 @@ def find_bluetooth_devices():
173
190
  # Find Bluetooth devices on startup
174
191
  find_bluetooth_devices()
175
192
 
176
- def play_audio_file(file_path: str):
193
+ @exclusive_media("speaker")
194
+ def play_audio_file(file_path: str):
177
195
  """Play audio file using sounddevice (supports Bluetooth)"""
178
196
  try:
179
197
  logger.info(f"🔊 Playing audio: {file_path}")
@@ -231,7 +249,8 @@ def get_available_input_devices():
231
249
  input_devices.append(i)
232
250
  return input_devices
233
251
 
234
- def record_audio_with_vad():
252
+ @exclusive_media("microphone")
253
+ def record_audio_with_vad():
235
254
  """
236
255
  Record audio from Bluetooth microphone with Voice Activity Detection
237
256
  Rotates through available devices if no audio detected