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.
- package/dist/robopark/python-env.d.ts +1 -0
- package/dist/robopark/python-env.js +3 -0
- package/dist/robopark/stop-all.js +1 -0
- package/dist/robopark/vision-agent-launcher.js +22 -3
- package/package.json +1 -1
- package/packages/robopark/vision/app_pi_clean.py +41 -15
- package/packages/robopark/vision/requirements_pi_unified.txt +2 -1
- package/packages/robopark/vision/requirements_vision_agent.txt +18 -0
|
@@ -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']
|
|
@@ -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() ? '
|
|
27
|
-
const
|
|
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
|
@@ -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
|
-
|
|
348
|
-
outputs = [{"id": "default", "name": "System default output"}]
|
|
348
|
+
"""Adapt RoboVision's existing audio_server_pi /devices response."""
|
|
349
349
|
try:
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
|
363
|
+
if device.get("max_output_channels", 0) > 0:
|
|
358
364
|
outputs.append(item.copy())
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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": {
|
|
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'])
|
|
@@ -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'
|