infinicode 2.8.52 → 2.8.54

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.
@@ -2,6 +2,7 @@
2
2
  export declare const VISION_REQUIRED_MODULES: string[];
3
3
  export declare function findSchedulerPath(): Promise<string | null>;
4
4
  export declare function findVisionPath(): Promise<string | null>;
5
+ export declare function findVisionAudioPath(): Promise<string | null>;
5
6
  export declare function findPython(): string;
6
7
  /**
7
8
  * Ensure a robot-side script's Python deps are importable, installing them
@@ -39,6 +39,9 @@ export async function findSchedulerPath() {
39
39
  export async function findVisionPath() {
40
40
  return findPackageScript('vision/app_pi_clean.py');
41
41
  }
42
+ export async function findVisionAudioPath() {
43
+ return findPackageScript('vision/audio_server_pi.py');
44
+ }
42
45
  export function findPython() {
43
46
  const names = process.platform === 'win32'
44
47
  ? ['python', 'python3', 'py']
@@ -23,6 +23,7 @@ const PATTERNS = [
23
23
  'scheduler.{0,3}main\\.py',
24
24
  'preview_agent.py',
25
25
  'app_pi_clean.py',
26
+ 'audio_server_pi.py',
26
27
  ];
27
28
  export async function stopAll() {
28
29
  const platform = process.platform;
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { spawn } from 'node:child_process';
9
9
  import chalk from 'chalk';
10
- import { findPython, findVisionPath, prepareRobotPython, VISION_REQUIRED_MODULES } from './python-env.js';
10
+ import { findPython, findVisionPath, findVisionAudioPath, prepareRobotPython, VISION_REQUIRED_MODULES } from './python-env.js';
11
11
  const DEFAULT_VISION_PORT = 5000;
12
12
  const DEFAULT_MOTION_WEBHOOK_PORT = 5057; // must match preview-agent's --vision-webhook-port default
13
13
  function isPiArm() {
@@ -23,8 +23,9 @@ export async function roboparkVisionAgent(opts) {
23
23
  // Real Pi: opencv comes from `apt install python3-opencv`, not pip (see
24
24
  // requirements_pi_unified.txt) — pip-installing it here would fight the
25
25
  // ARM-optimized system build. Everywhere else: plain pip install works.
26
- const reqFile = isPiArm() ? 'requirements_pi_unified.txt' : 'requirements-demo.txt';
27
- const python = prepareRobotPython(basePython, script, VISION_REQUIRED_MODULES, reqFile);
26
+ const reqFile = isPiArm() ? 'requirements_vision_agent.txt' : 'requirements-demo.txt';
27
+ const requiredModules = isPiArm() ? [...VISION_REQUIRED_MODULES, 'fastapi', 'uvicorn', 'sounddevice', 'soundfile', 'groq'] : VISION_REQUIRED_MODULES;
28
+ const python = prepareRobotPython(basePython, script, requiredModules, reqFile);
28
29
  if (!python)
29
30
  process.exit(1);
30
31
  const port = opts.port ?? String(DEFAULT_VISION_PORT);
@@ -33,19 +34,37 @@ export async function roboparkVisionAgent(opts) {
33
34
  const args = [script, '--port', port, '--motion-webhook-url', webhookUrl];
34
35
  if (motionActive)
35
36
  args.push('--motion-active');
37
+ // The original RoboVision audio selector lives in audio_server_pi.py.
38
+ // Start it beside the camera service on real Pi robots so /devices and
39
+ // /set-device remain the authoritative source for Park dropdowns.
40
+ let audioScript = null;
41
+ if (isPiArm())
42
+ audioScript = await findVisionAudioPath();
36
43
  console.log(chalk.bold('\n robopark vision-agent'));
37
44
  console.log(chalk.dim(' ' + '─'.repeat(52)));
38
45
  console.log(` port: ${chalk.cyan(port)}`);
39
46
  console.log(` webhook: ${chalk.cyan(webhookUrl)}`);
40
47
  console.log(` motion: ${motionActive ? chalk.green('armed') : chalk.dim('off')}`);
48
+ if (audioScript)
49
+ console.log(` audio: ${chalk.green('RoboVision audio server :8000')}`);
41
50
  console.log();
42
51
  if (opts.foreground) {
52
+ if (audioScript) {
53
+ const audio = spawn(python, [audioScript], { stdio: 'inherit', env: { ...process.env, ROBOPARK_AUDIO_SERVER: '1' } });
54
+ audio.unref();
55
+ }
43
56
  const proc = spawn(python, args, { stdio: 'inherit' });
44
57
  await new Promise((resolve) => {
45
58
  proc.on('close', () => resolve());
46
59
  });
47
60
  return;
48
61
  }
62
+ if (audioScript) {
63
+ const audio = spawn(python, [audioScript], {
64
+ stdio: 'ignore', detached: true, env: { ...process.env, ROBOPARK_AUDIO_SERVER: '1' },
65
+ });
66
+ audio.unref();
67
+ }
49
68
  const proc = spawn(python, args, {
50
69
  stdio: 'ignore',
51
70
  detached: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infinicode",
3
- "version": "2.8.52",
3
+ "version": "2.8.54",
4
4
  "description": "OpenKernel — provider-agnostic AI execution kernel. Native coding agent + mission-driven execution runtime.",
5
5
  "type": "module",
6
6
  "main": "./dist/kernel/index.js",
@@ -19,6 +19,7 @@ current_camera_index = 0
19
19
  camera = None
20
20
  audio_input_device = "default"
21
21
  audio_output_device = "default"
22
+ ROBOVISION_AUDIO_URL = os.getenv("ROBOVISION_AUDIO_URL", "http://127.0.0.1:8000")
22
23
 
23
24
  def _open_camera(index):
24
25
  # On Windows, cv2.VideoCapture(index) with no explicit backend can
@@ -344,22 +345,30 @@ def switch_camera():
344
345
 
345
346
 
346
347
  def _audio_inventory():
347
- inputs = [{"id": "default", "name": "System default input"}]
348
- outputs = [{"id": "default", "name": "System default output"}]
348
+ """Adapt RoboVision's existing audio_server_pi /devices response."""
349
349
  try:
350
- import pyaudio
351
- pa = pyaudio.PyAudio()
352
- for index in range(pa.get_device_count()):
353
- info = pa.get_device_info_by_index(index)
354
- item = {"id": str(index), "name": str(info.get("name", f"Audio device {index}")), "host_api": str(info.get("hostApi", ""))}
355
- if info.get("maxInputChannels", 0) > 0:
350
+ response = requests.get(f"{ROBOVISION_AUDIO_URL.rstrip('/')}/devices", timeout=0.8)
351
+ response.raise_for_status()
352
+ payload = response.json()
353
+ inputs, outputs = [], []
354
+ for device in payload.get("devices", []):
355
+ item = {
356
+ "id": str(device["index"]),
357
+ "name": str(device.get("name", f"Audio device {device['index']}")),
358
+ "backend": "robovision_audio",
359
+ "sample_rate": device.get("default_samplerate"),
360
+ }
361
+ if device.get("max_input_channels", 0) > 0:
356
362
  inputs.append(item.copy())
357
- if info.get("maxOutputChannels", 0) > 0:
363
+ if device.get("max_output_channels", 0) > 0:
358
364
  outputs.append(item.copy())
359
- pa.terminate()
360
- except Exception:
361
- pass
362
- return inputs, outputs
365
+ return inputs, outputs, {
366
+ "input": payload.get("bluetooth_input"),
367
+ "output": payload.get("bluetooth_output"),
368
+ "online": True,
369
+ }
370
+ except Exception as exc:
371
+ return [], [], {"online": False, "error": str(exc)}
363
372
 
364
373
 
365
374
  @app.route('/api/media/inventory', methods=['GET'])
@@ -370,12 +379,17 @@ def media_inventory():
370
379
  # still a valid selectable camera and is safer than hiding it.
371
380
  cameras = [{"id": path, "index": path, "name": f"Camera {path}", "backend": "v4l2"}
372
381
  for path in sorted(glob.glob('/dev/video*'))]
373
- inputs, outputs = _audio_inventory()
382
+ inputs, outputs, audio_state = _audio_inventory()
374
383
  return jsonify({
375
384
  "video": [{"id": "auto", "name": "Auto detect"}, {"id": "none", "name": "Disable camera"}] + cameras,
376
385
  "audio_input": inputs,
377
386
  "audio_output": outputs,
378
- "selected": {"video_device": str(current_camera_index), "audio_device": audio_input_device, "audio_output_device": audio_output_device},
387
+ "selected": {
388
+ "video_device": str(current_camera_index),
389
+ "audio_device": str(audio_state.get("input") if audio_state.get("input") is not None else audio_input_device),
390
+ "audio_output_device": str(audio_state.get("output") if audio_state.get("output") is not None else audio_output_device),
391
+ },
392
+ "audio_server": audio_state,
379
393
  "source": "robovision_pi",
380
394
  })
381
395
 
@@ -397,6 +411,18 @@ def media_config():
397
411
  audio_input_device = str(data["audio_device"])
398
412
  if "audio_output_device" in data:
399
413
  audio_output_device = str(data["audio_output_device"])
414
+ if "audio_device" in data or "audio_output_device" in data:
415
+ # audio_server_pi.py is RoboVision's authoritative selector.
416
+ # It accepts its original sounddevice indices via input/output.
417
+ payload = {}
418
+ if "audio_device" in data:
419
+ payload["input"] = int(audio_input_device) if audio_input_device.isdigit() else audio_input_device
420
+ if "audio_output_device" in data:
421
+ payload["output"] = int(audio_output_device) if audio_output_device.isdigit() else audio_output_device
422
+ try:
423
+ requests.post(f"{ROBOVISION_AUDIO_URL.rstrip('/')}/set-device", json=payload, timeout=0.8).raise_for_status()
424
+ except Exception:
425
+ pass
400
426
  return jsonify({"video_device": str(current_camera_index), "audio_device": audio_input_device, "audio_output_device": audio_output_device, "source": "robovision_pi"})
401
427
 
402
428
  @app.route('/api/motion/status', methods=['GET'])
@@ -67,7 +67,8 @@ pydantic==2.5.0
67
67
  # NETWORKING & HTTP
68
68
  # ============================================================================
69
69
  # HTTP requests
70
- requests==2.31.0
70
+ requests==2.31.0
71
+ groq>=0.11
71
72
 
72
73
  # CORS support
73
74
  python-multipart==0.0.6
@@ -0,0 +1,18 @@
1
+ # Runtime requirements for the packaged RoboPark vision-agent.
2
+ #
3
+ # This intentionally excludes the legacy optional YOLO/GPIO stack from
4
+ # requirements_pi_unified.txt. The packaged agent launches app_pi_clean.py and
5
+ # audio_server_pi.py only. Raspberry Pi OpenCV/PyAudio/Picamera2 are supplied
6
+ # by apt and exposed to ~/.robopark/venv through --system-site-packages.
7
+ Flask==3.0.0
8
+ Flask-CORS==4.0.0
9
+ requests>=2.31
10
+ fastapi>=0.115
11
+ uvicorn[standard]>=0.30
12
+ python-multipart>=0.0.12
13
+ sounddevice>=0.4.6
14
+ soundfile>=0.12.1
15
+ groq>=0.11
16
+ # Python 3.13 has no compatible NumPy 1.24 wheel. On older Pi images, use the
17
+ # distro NumPy provided through system-site-packages instead.
18
+ numpy>=2.1; python_version >= '3.13'