infinicode 2.8.13 β 2.8.16
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/kernel/federation/dashboard-html.d.ts +1 -1
- package/dist/kernel/federation/dashboard-html.js +32 -1
- package/dist/robopark/auto-start.d.ts +1 -1
- package/dist/robopark/preview-agent-launcher.d.ts +3 -0
- package/dist/robopark/preview-agent-launcher.js +8 -0
- package/dist/robopark/python-env.d.ts +10 -4
- package/dist/robopark/python-env.js +28 -16
- package/dist/robopark/setup.d.ts +1 -0
- package/dist/robopark/setup.js +22 -0
- package/dist/robopark/vision-agent-launcher.d.ts +7 -0
- package/dist/robopark/vision-agent-launcher.js +55 -0
- package/dist/robopark-cli.js +16 -0
- package/package.json +3 -1
- package/packages/robopark/pi-client/README.md +17 -0
- package/packages/robopark/pi-client/_install_steps.sh +29 -0
- package/packages/robopark/pi-client/client.py +305 -0
- package/packages/robopark/pi-client/install.sh +41 -0
- package/packages/robopark/pi-client/join_convo.sh +55 -0
- package/packages/robopark/pi-client/livekit_bridge.py +289 -0
- package/packages/robopark/pi-client/motor_bridge.py +82 -0
- package/packages/robopark/pi-client/pi_ui.py +375 -0
- package/packages/robopark/pi-client/requirements.txt +10 -0
- package/packages/robopark/pi-client/robopark-pi-client.service +26 -0
- package/packages/robopark/pi-client/robopark-pi-ui.service +24 -0
- package/packages/robopark/pi-client/smoke_bridge.py +15 -0
- package/packages/robopark/pi-client/stub_motor_server.py +46 -0
- package/packages/robopark/scheduler/main.py +45 -9
- package/packages/robopark/scheduler/preview_agent.py +132 -3
- package/packages/robopark/vision/.env.example +7 -0
- package/packages/robopark/vision/README.md +66 -0
- package/packages/robopark/vision/app_pi_clean.py +326 -0
- package/packages/robopark/vision/audio_server_pi.py +751 -0
- package/packages/robopark/vision/install.sh +34 -0
- package/packages/robopark/vision/motor_server.py +304 -0
- package/packages/robopark/vision/requirements-demo.txt +12 -0
- package/packages/robopark/vision/requirements_pi_unified.txt +101 -0
- package/packages/robopark/vision/run.sh +244 -0
- package/packages/robopark/vision/services/services.sh +12 -0
|
@@ -825,6 +825,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
825
825
|
var nodes=[], byId={}, orbs=[], pulses=[], t0=0, raf=null, running=false, es=null, animSeq=-1;
|
|
826
826
|
var drawerId=null, camRAF=null, meterRAF=null, camAbort=null, drawerTimer=null;
|
|
827
827
|
var previewOn=false, previewTimer=null; // on-demand operator camera preview
|
|
828
|
+
var visionOn=false; // RoboVisionAI_PI overlay feed (direct MJPEG, not LiveKit)
|
|
828
829
|
var lkRoom=null, micAnalyser=null, audioCtx=null;
|
|
829
830
|
var imgs={}; // preloaded robot concept avatars
|
|
830
831
|
var TW=44, TH=22, PARK_W=15, PARK_D=8;
|
|
@@ -1189,6 +1190,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1189
1190
|
// tell the scheduler to end any live preview so the robot stops its cam
|
|
1190
1191
|
if(previewOn && drawerId){ var pn=byId[drawerId]; if(pn){ try{ fetch('/robopark/api/robots/'+encodeURIComponent(schedId(pn))+'/preview/stop'+QS,{method:'POST',keepalive:true}); }catch(e){} } }
|
|
1191
1192
|
previewOn=false; if(previewTimer){ clearInterval(previewTimer); previewTimer=null; }
|
|
1193
|
+
stopVision();
|
|
1192
1194
|
$('pk-drawer').classList.remove('open'); drawerId=null;
|
|
1193
1195
|
if(camRAF)cancelAnimationFrame(camRAF); camRAF=null;
|
|
1194
1196
|
if(meterRAF)cancelAnimationFrame(meterRAF); meterRAF=null;
|
|
@@ -1225,10 +1227,34 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1225
1227
|
var note=$('pk-golivenote'); if(note) note.textContent='';
|
|
1226
1228
|
idleCam('idle');
|
|
1227
1229
|
}
|
|
1230
|
+
// RoboVisionAI_PI serves its own MJPEG stream directly (not via LiveKit),
|
|
1231
|
+
// so the browser hits the robot's LAN IP straight on, no scheduler proxy.
|
|
1232
|
+
// The scheduler learns that IP from the device's own heartbeats.
|
|
1233
|
+
function toggleVision(n){
|
|
1234
|
+
var img=$('pk-vision-img'), note=$('pk-vision-note'), btn=$('pk-vision-toggle');
|
|
1235
|
+
if(visionOn){
|
|
1236
|
+
visionOn=false; if(img){ img.src=''; img.style.display='none'; }
|
|
1237
|
+
if(btn) btn.textContent='π Show overlay';
|
|
1238
|
+
if(note) note.textContent="object/motion detection overlay from the robot's vision server (RoboVisionAI_PI)";
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
var row=schedRow(n);
|
|
1242
|
+
var ip=row&&row.lan_ip;
|
|
1243
|
+
if(!ip){ if(note) note.textContent='no known IP for this robot yet β waiting for a heartbeat with lan_ip'; return; }
|
|
1244
|
+
var port=(row&&row.vision_port)||5000;
|
|
1245
|
+
visionOn=true;
|
|
1246
|
+
if(btn) btn.textContent='β Hide overlay';
|
|
1247
|
+
if(note) note.textContent='streaming from http://'+ip+':'+port+'/video_feed';
|
|
1248
|
+
if(img){ img.src='http://'+ip+':'+port+'/video_feed'; img.style.display='block'; }
|
|
1249
|
+
}
|
|
1250
|
+
function stopVision(){
|
|
1251
|
+
visionOn=false;
|
|
1252
|
+
var img=$('pk-vision-img'); if(img){ img.src=''; img.style.display='none'; }
|
|
1253
|
+
}
|
|
1228
1254
|
function pollRoboparkQuiet(){
|
|
1229
1255
|
fetch('/robopark/api/telemetry'+QS,{cache:'no-store'}).then(function(r){return r.json();}).then(function(d){
|
|
1230
1256
|
var list=(Array.isArray(d)?d:(d&&d.robots)||[]); roboparkByName={};
|
|
1231
|
-
list.forEach(function(x){ roboparkByName[x.robot_id||x.id||x.name||'robot']=x; });
|
|
1257
|
+
list.forEach(function(x){ roboparkByName[x.robot_id||x.id||x.name||'robot']=x; if(x.name) roboparkByName[x.name]=x; });
|
|
1232
1258
|
refreshDrawer();
|
|
1233
1259
|
}).catch(function(){});
|
|
1234
1260
|
}
|
|
@@ -1247,6 +1273,10 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1247
1273
|
+'<div class="hud"><div class="rec"><i></i>REC Β· '+esc(n.name)+'</div><div class="tr" id="pk-camres">β Β· β</div><div class="br" id="pk-camts">--:--:--</div></div></div>'
|
|
1248
1274
|
+'<div class="pk-note" id="pk-camnote">seeking LiveKit trackβ¦</div>'
|
|
1249
1275
|
+'<div class="pk-ctl" style="margin-top:0.5rem"><button class="rp-btn" id="pk-golive" data-robot="'+esc(n.name)+'">βΆ Go live</button><span class="pk-note" id="pk-golivenote" style="margin:0"></span></div>')
|
|
1276
|
+
+ sect('π vision overlay',
|
|
1277
|
+
'<div class="pk-cam" id="pk-vision-wrap"><img id="pk-vision-img" style="width:100%;display:none;border-radius:inherit" alt="vision feed"/></div>'
|
|
1278
|
+
+'<div class="pk-note" id="pk-vision-note">object/motion detection overlay from the robot\'s vision server (RoboVisionAI_PI)</div>'
|
|
1279
|
+
+'<div class="pk-ctl" style="margin-top:0.5rem"><button class="rp-btn" id="pk-vision-toggle">π Show overlay</button></div>')
|
|
1250
1280
|
+ sect('π microphone','<div class="pk-meter"><span class="lab">input</span><div class="pk-bars" id="pk-micbars"></div><span class="pk-val" id="pk-micval">β</span></div><div class="pk-note" id="pk-micstate">awaiting robot audio plugin</div>')
|
|
1251
1281
|
+ sect('π speaker','<canvas class="pk-wave" id="pk-spkwave" width="360" height="32"></canvas><div class="pk-meter"><span class="lab">output</span><div class="pk-bars" id="pk-spkbars"></div><span class="pk-val" id="pk-spkval">β</span></div><div class="pk-note" id="pk-spkstate">awaiting robot audio plugin</div>')
|
|
1252
1282
|
+ sect('π hardware','<div class="pk-hwgrid"><div><div class="l">load</div><div class="track"><div class="fill" id="pk-hw-load"></div></div></div><div><div class="l">status</div><div class="track"><div class="fill" id="pk-hw-conn" style="background:'+(n.connected?'var(--ok)':'var(--muted)')+'"></div></div></div></div><div class="pk-note">remote node β full CPU/mem telemetry shows on the node itself</div>')
|
|
@@ -1254,6 +1284,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1254
1284
|
+ sect('β controls','<div class="pk-ctl"><button class="rp-btn danger" id="pk-end" data-robot="'+esc(n.name)+'">End session</button><button class="rp-btn" id="pk-follow">Follow run</button></div><div class="pk-note">end-session is live via the scheduler; cam/mic/speaker fill in as the robot plugin publishes them</div>');
|
|
1255
1285
|
startCam(n); startMeters(n);
|
|
1256
1286
|
var gl=$('pk-golive'); if(gl) gl.addEventListener('click', function(){ if(rpReadOnly){ showOut('read-only node'); return; } goLive(n); });
|
|
1287
|
+
var vt=$('pk-vision-toggle'); if(vt) vt.addEventListener('click', function(){ toggleVision(n); });
|
|
1257
1288
|
var eb=$('pk-end'); if(eb) eb.addEventListener('click', function(){ if(rpReadOnly){ showOut('read-only node'); return; } endSession(schedId(n)); });
|
|
1258
1289
|
var fb=$('pk-follow'); if(fb) fb.addEventListener('click', function(){ selectTab('fleet'); $('cmdInput').value='/follow'; selectNode(n.id,n.name); showOut('type a runId after /follow, or run /status on '+n.name); });
|
|
1259
1290
|
} else {
|
|
@@ -41,6 +41,12 @@ export async function roboparkPreviewAgent(opts) {
|
|
|
41
41
|
args.push('--device-token', opts.deviceToken);
|
|
42
42
|
if (opts.enrollmentToken)
|
|
43
43
|
args.push('--enrollment-token', opts.enrollmentToken);
|
|
44
|
+
if (opts.visionWebhookPort)
|
|
45
|
+
args.push('--vision-webhook-port', opts.visionWebhookPort);
|
|
46
|
+
if (opts.visionTriggerCooldown)
|
|
47
|
+
args.push('--vision-trigger-cooldown', opts.visionTriggerCooldown);
|
|
48
|
+
if (opts.visionSessionSeconds)
|
|
49
|
+
args.push('--vision-session-seconds', opts.visionSessionSeconds);
|
|
44
50
|
if (opts.saveConfig)
|
|
45
51
|
args.push('--save-config');
|
|
46
52
|
console.log(chalk.bold('\n robopark preview-agent'));
|
|
@@ -49,6 +55,8 @@ export async function roboparkPreviewAgent(opts) {
|
|
|
49
55
|
console.log(` scheduler: ${chalk.cyan(schedulerUrl)}`);
|
|
50
56
|
console.log(` video: ${chalk.cyan(opts.videoDevice)}`);
|
|
51
57
|
console.log(` audio: ${chalk.cyan(opts.audioDevice)}`);
|
|
58
|
+
if (opts.visionWebhookPort)
|
|
59
|
+
console.log(` vision webhook: ${chalk.cyan(':' + opts.visionWebhookPort)} (point RoboVisionAI_PI's motion webhook here)`);
|
|
52
60
|
console.log();
|
|
53
61
|
if (opts.foreground) {
|
|
54
62
|
const proc = spawn(python, args, { stdio: 'inherit' });
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
/** Modules app_pi_clean.py imports β kept in sync with requirements_pi_unified.txt. */
|
|
2
|
+
export declare const VISION_REQUIRED_MODULES: string[];
|
|
1
3
|
export declare function findSchedulerPath(): Promise<string | null>;
|
|
4
|
+
export declare function findVisionPath(): Promise<string | null>;
|
|
2
5
|
export declare function findPython(): string;
|
|
3
6
|
/**
|
|
4
|
-
* Ensure
|
|
5
|
-
* requirements
|
|
6
|
-
* message already printed β if deps are still missing after the install
|
|
7
|
+
* Ensure a robot-side script's Python deps are importable, installing them
|
|
8
|
+
* from a requirements file (next to the script) if not. Returns false β with
|
|
9
|
+
* a message already printed β if deps are still missing after the install
|
|
7
10
|
* attempt, so callers can bail out before hitting a raw traceback.
|
|
11
|
+
*
|
|
12
|
+
* Defaults match preview_agent.py; pass `modules`/`reqFile` for other scripts
|
|
13
|
+
* (e.g. app_pi_clean.py + requirements_pi_unified.txt).
|
|
8
14
|
*/
|
|
9
|
-
export declare function ensurePythonDeps(python: string, scriptPath: string): boolean;
|
|
15
|
+
export declare function ensurePythonDeps(python: string, scriptPath: string, modules?: string[], reqFile?: string): boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* RoboPark β shared Python environment helpers for `enroll`
|
|
3
|
-
* `
|
|
2
|
+
* RoboPark β shared Python environment helpers for `enroll`, `preview-agent`,
|
|
3
|
+
* and `vision-agent`, which spawn preview_agent.py / app_pi_clean.py.
|
|
4
4
|
*/
|
|
5
5
|
import { execSync, spawnSync } from 'node:child_process';
|
|
6
6
|
import { existsSync } from 'node:fs';
|
|
@@ -8,10 +8,13 @@ import { join } from 'node:path';
|
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
/** Modules preview_agent.py imports β kept in sync with requirements-robot.txt. */
|
|
10
10
|
const REQUIRED_MODULES = ['httpx', 'cv2', 'pyaudio', 'livekit'];
|
|
11
|
-
|
|
11
|
+
/** Modules app_pi_clean.py imports β kept in sync with requirements_pi_unified.txt. */
|
|
12
|
+
export const VISION_REQUIRED_MODULES = ['flask', 'flask_cors', 'cv2', 'numpy', 'requests'];
|
|
13
|
+
async function findPackageScript(relativePath) {
|
|
14
|
+
const parts = relativePath.split('/');
|
|
12
15
|
const candidates = [
|
|
13
|
-
join(process.cwd(), 'packages', 'robopark',
|
|
14
|
-
join(process.cwd(),
|
|
16
|
+
join(process.cwd(), 'packages', 'robopark', ...parts),
|
|
17
|
+
join(process.cwd(), ...parts),
|
|
15
18
|
];
|
|
16
19
|
for (const p of candidates) {
|
|
17
20
|
if (existsSync(p))
|
|
@@ -21,13 +24,19 @@ export async function findSchedulerPath() {
|
|
|
21
24
|
const { createRequire } = await import('node:module');
|
|
22
25
|
const require = createRequire(import.meta.url);
|
|
23
26
|
const pkgMain = require.resolve('infinicode/package.json');
|
|
24
|
-
const p = join(pkgMain, '..', 'packages', 'robopark', '
|
|
27
|
+
const p = join(pkgMain, '..', 'packages', 'robopark', ...relativePath.split('/'));
|
|
25
28
|
if (existsSync(p))
|
|
26
29
|
return p;
|
|
27
30
|
}
|
|
28
31
|
catch { /* ignore */ }
|
|
29
32
|
return null;
|
|
30
33
|
}
|
|
34
|
+
export async function findSchedulerPath() {
|
|
35
|
+
return findPackageScript('scheduler/preview_agent.py');
|
|
36
|
+
}
|
|
37
|
+
export async function findVisionPath() {
|
|
38
|
+
return findPackageScript('vision/app_pi_clean.py');
|
|
39
|
+
}
|
|
31
40
|
export function findPython() {
|
|
32
41
|
const names = process.platform === 'win32'
|
|
33
42
|
? ['python', 'python3', 'py']
|
|
@@ -43,25 +52,28 @@ export function findPython() {
|
|
|
43
52
|
}
|
|
44
53
|
return process.platform === 'win32' ? 'python' : 'python3';
|
|
45
54
|
}
|
|
46
|
-
function missingModules(python) {
|
|
47
|
-
return
|
|
55
|
+
function missingModules(python, modules) {
|
|
56
|
+
return modules.filter(mod => {
|
|
48
57
|
const res = spawnSync(python, ['-c', `import ${mod}`], { stdio: 'ignore' });
|
|
49
58
|
return res.status !== 0;
|
|
50
59
|
});
|
|
51
60
|
}
|
|
52
61
|
/**
|
|
53
|
-
* Ensure
|
|
54
|
-
* requirements
|
|
55
|
-
* message already printed β if deps are still missing after the install
|
|
62
|
+
* Ensure a robot-side script's Python deps are importable, installing them
|
|
63
|
+
* from a requirements file (next to the script) if not. Returns false β with
|
|
64
|
+
* a message already printed β if deps are still missing after the install
|
|
56
65
|
* attempt, so callers can bail out before hitting a raw traceback.
|
|
66
|
+
*
|
|
67
|
+
* Defaults match preview_agent.py; pass `modules`/`reqFile` for other scripts
|
|
68
|
+
* (e.g. app_pi_clean.py + requirements_pi_unified.txt).
|
|
57
69
|
*/
|
|
58
|
-
export function ensurePythonDeps(python, scriptPath) {
|
|
59
|
-
const missing = missingModules(python);
|
|
70
|
+
export function ensurePythonDeps(python, scriptPath, modules = REQUIRED_MODULES, reqFile = 'requirements-robot.txt') {
|
|
71
|
+
const missing = missingModules(python, modules);
|
|
60
72
|
if (missing.length === 0)
|
|
61
73
|
return true;
|
|
62
|
-
const reqPath = join(scriptPath, '..',
|
|
74
|
+
const reqPath = join(scriptPath, '..', reqFile);
|
|
63
75
|
if (!existsSync(reqPath)) {
|
|
64
|
-
console.log(chalk.red(` β missing Python modules: ${missing.join(', ')} (and
|
|
76
|
+
console.log(chalk.red(` β missing Python modules: ${missing.join(', ')} (and ${reqFile} not found next to ${scriptPath})`));
|
|
65
77
|
return false;
|
|
66
78
|
}
|
|
67
79
|
console.log(chalk.dim(` installing Python deps (${missing.join(', ')})β¦`));
|
|
@@ -70,7 +82,7 @@ export function ensurePythonDeps(python, scriptPath) {
|
|
|
70
82
|
console.log(chalk.red(` β pip install failed β install manually: ${python} -m pip install -r "${reqPath}"`));
|
|
71
83
|
return false;
|
|
72
84
|
}
|
|
73
|
-
const stillMissing = missingModules(python);
|
|
85
|
+
const stillMissing = missingModules(python, modules);
|
|
74
86
|
if (stillMissing.length > 0) {
|
|
75
87
|
console.log(chalk.red(` β still missing after install: ${stillMissing.join(', ')} β install manually: ${python} -m pip install -r "${reqPath}"`));
|
|
76
88
|
return false;
|
package/dist/robopark/setup.d.ts
CHANGED
package/dist/robopark/setup.js
CHANGED
|
@@ -171,9 +171,16 @@ async function setupRobot(config, opts, ctx, internal) {
|
|
|
171
171
|
];
|
|
172
172
|
if (opts.enrollmentToken)
|
|
173
173
|
previewAgentArgs.push('--enrollment-token', opts.enrollmentToken);
|
|
174
|
+
// Vision (camera/motion detection -> presence-triggered session) is the
|
|
175
|
+
// production trigger β on by default. --no-vision opts out (e.g. no camera
|
|
176
|
+
// on this box, or RoboVisionAI_PI isn't installed).
|
|
177
|
+
const visionEnabled = opts.vision !== false;
|
|
178
|
+
const visionAgentArgs = ['vision-agent'];
|
|
174
179
|
console.log(chalk.dim(' robot command:'));
|
|
175
180
|
console.log(' ' + chalk.cyan(`infinicode ${args.join(' ')}`));
|
|
176
181
|
console.log(' ' + chalk.cyan(`robopark ${previewAgentArgs.join(' ')}`));
|
|
182
|
+
if (visionEnabled)
|
|
183
|
+
console.log(' ' + chalk.cyan(`robopark ${visionAgentArgs.join(' ')}`));
|
|
177
184
|
console.log();
|
|
178
185
|
if (opts.start) {
|
|
179
186
|
const robotArgv = await infinicodeArgv(args);
|
|
@@ -191,6 +198,11 @@ async function setupRobot(config, opts, ctx, internal) {
|
|
|
191
198
|
console.log(chalk.dim(' starting preview agentβ¦'));
|
|
192
199
|
const previewArgv = [process.execPath, process.argv[1], ...previewAgentArgs];
|
|
193
200
|
runDetached(previewArgv[0], previewArgv.slice(1));
|
|
201
|
+
if (visionEnabled) {
|
|
202
|
+
console.log(chalk.dim(' starting vision agentβ¦'));
|
|
203
|
+
const visionArgv = [process.execPath, process.argv[1], ...visionAgentArgs];
|
|
204
|
+
runDetached(visionArgv[0], visionArgv.slice(1));
|
|
205
|
+
}
|
|
194
206
|
}
|
|
195
207
|
if (opts.autoStart) {
|
|
196
208
|
const robotArgv = await infinicodeArgv(args);
|
|
@@ -209,6 +221,16 @@ async function setupRobot(config, opts, ctx, internal) {
|
|
|
209
221
|
args: previewArgv.slice(1),
|
|
210
222
|
});
|
|
211
223
|
console.log(reg2.ok ? chalk.green(` β ${reg2.message}`) : chalk.yellow(` β ${reg2.message}`));
|
|
224
|
+
if (visionEnabled) {
|
|
225
|
+
const visionArgv = [process.execPath, process.argv[1], ...visionAgentArgs, '--foreground'];
|
|
226
|
+
const reg3 = await registerAutoStart({
|
|
227
|
+
role: 'robot-vision-agent',
|
|
228
|
+
name: internal.name,
|
|
229
|
+
command: visionArgv[0],
|
|
230
|
+
args: visionArgv.slice(1),
|
|
231
|
+
});
|
|
232
|
+
console.log(reg3.ok ? chalk.green(` β ${reg3.message}`) : chalk.yellow(` β ${reg3.message}`));
|
|
233
|
+
}
|
|
212
234
|
}
|
|
213
235
|
}
|
|
214
236
|
async function setupControl(config, opts, ctx, internal) {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RoboPark β `robopark vision-agent` launcher.
|
|
3
|
+
*
|
|
4
|
+
* Starts app_pi_clean.py (RoboVisionAI_PI's camera/motion detector) with the
|
|
5
|
+
* right Python interpreter, self-armed and pointed at the local
|
|
6
|
+
* preview-agent's motion-webhook listener by default β no manual curl calls.
|
|
7
|
+
*/
|
|
8
|
+
import { spawn } from 'node:child_process';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { findPython, findVisionPath, ensurePythonDeps, VISION_REQUIRED_MODULES } from './python-env.js';
|
|
11
|
+
const DEFAULT_VISION_PORT = 5000;
|
|
12
|
+
const DEFAULT_MOTION_WEBHOOK_PORT = 5057; // must match preview-agent's --vision-webhook-port default
|
|
13
|
+
function isPiArm() {
|
|
14
|
+
return process.platform === 'linux' && (process.arch === 'arm' || process.arch === 'arm64');
|
|
15
|
+
}
|
|
16
|
+
export async function roboparkVisionAgent(opts) {
|
|
17
|
+
const script = await findVisionPath();
|
|
18
|
+
if (!script) {
|
|
19
|
+
console.log(chalk.red(' β could not find app_pi_clean.py'));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const python = findPython();
|
|
23
|
+
// Real Pi: opencv comes from `apt install python3-opencv`, not pip (see
|
|
24
|
+
// requirements_pi_unified.txt) β pip-installing it here would fight the
|
|
25
|
+
// ARM-optimized system build. Everywhere else: plain pip install works.
|
|
26
|
+
const reqFile = isPiArm() ? 'requirements_pi_unified.txt' : 'requirements-demo.txt';
|
|
27
|
+
if (!ensurePythonDeps(python, script, VISION_REQUIRED_MODULES, reqFile))
|
|
28
|
+
process.exit(1);
|
|
29
|
+
const port = opts.port ?? String(DEFAULT_VISION_PORT);
|
|
30
|
+
const webhookUrl = opts.motionWebhookUrl ?? `http://127.0.0.1:${DEFAULT_MOTION_WEBHOOK_PORT}/`;
|
|
31
|
+
const motionActive = opts.motionActive ?? true; // armed by default β "vision trigger as production system"
|
|
32
|
+
const args = [script, '--port', port, '--motion-webhook-url', webhookUrl];
|
|
33
|
+
if (motionActive)
|
|
34
|
+
args.push('--motion-active');
|
|
35
|
+
console.log(chalk.bold('\n robopark vision-agent'));
|
|
36
|
+
console.log(chalk.dim(' ' + 'β'.repeat(52)));
|
|
37
|
+
console.log(` port: ${chalk.cyan(port)}`);
|
|
38
|
+
console.log(` webhook: ${chalk.cyan(webhookUrl)}`);
|
|
39
|
+
console.log(` motion: ${motionActive ? chalk.green('armed') : chalk.dim('off')}`);
|
|
40
|
+
console.log();
|
|
41
|
+
if (opts.foreground) {
|
|
42
|
+
const proc = spawn(python, args, { stdio: 'inherit' });
|
|
43
|
+
await new Promise((resolve) => {
|
|
44
|
+
proc.on('close', () => resolve());
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const proc = spawn(python, args, {
|
|
49
|
+
stdio: 'ignore',
|
|
50
|
+
detached: true,
|
|
51
|
+
env: { ...process.env, ROBOPARK_VISION_AGENT: '1' },
|
|
52
|
+
});
|
|
53
|
+
proc.unref();
|
|
54
|
+
console.log(chalk.green(' β vision agent started in background'));
|
|
55
|
+
}
|
package/dist/robopark-cli.js
CHANGED
|
@@ -99,12 +99,26 @@ program
|
|
|
99
99
|
.option('--width <px>', 'video width', '640')
|
|
100
100
|
.option('--height <px>', 'video height', '480')
|
|
101
101
|
.option('--fps <n>', 'video fps', '15')
|
|
102
|
+
.option('--vision-webhook-port <port>', 'local port for RoboVisionAI_PI\'s motion webhook (0 disables)', '5057')
|
|
103
|
+
.option('--vision-trigger-cooldown <sec>', 'min seconds between motion-triggered sessions', '20')
|
|
104
|
+
.option('--vision-session-seconds <sec>', 'how long a motion-triggered session holds the publisher', '90')
|
|
102
105
|
.option('--save-config', 'write settings to ~/.robopark/preview_agent.json')
|
|
103
106
|
.option('--foreground', 'stay in foreground; do not detach')
|
|
104
107
|
.action(async (opts) => {
|
|
105
108
|
const { roboparkPreviewAgent } = await import('./robopark/preview-agent-launcher.js');
|
|
106
109
|
await roboparkPreviewAgent(opts);
|
|
107
110
|
});
|
|
111
|
+
program
|
|
112
|
+
.command('vision-agent')
|
|
113
|
+
.description('Start the robot-side camera/motion detector (RoboVisionAI_PI) β self-arms and wires to preview-agent\'s vision webhook')
|
|
114
|
+
.option('--port <port>', 'vision server port', '5000')
|
|
115
|
+
.option('--motion-webhook-url <url>', 'where to POST on motion (defaults to the local preview-agent\'s vision webhook, http://127.0.0.1:5057/)')
|
|
116
|
+
.option('--no-motion-active', 'do not arm motion detection on startup (armed by default)')
|
|
117
|
+
.option('--foreground', 'stay in foreground; do not detach')
|
|
118
|
+
.action(async (opts) => {
|
|
119
|
+
const { roboparkVisionAgent } = await import('./robopark/vision-agent-launcher.js');
|
|
120
|
+
await roboparkVisionAgent(opts);
|
|
121
|
+
});
|
|
108
122
|
program
|
|
109
123
|
.command('setup')
|
|
110
124
|
.description('Set up this device in one pass')
|
|
@@ -125,6 +139,7 @@ program
|
|
|
125
139
|
.option('--enrollment-token <token>', 'one-time scheduler enrollment token for robots')
|
|
126
140
|
.option('--video-device <dev>', 'default camera for robot preview agent', 'auto')
|
|
127
141
|
.option('--audio-device <dev>', 'default microphone for robot preview agent', 'default')
|
|
142
|
+
.option('--no-vision', 'do not start the vision (camera/motion trigger) agent for a robot β armed by default')
|
|
128
143
|
.option('--start', 'launch the node now')
|
|
129
144
|
.option('--auto-start', 'register to start on boot')
|
|
130
145
|
.option('--yes', 'non-interactive mode')
|
|
@@ -149,6 +164,7 @@ program
|
|
|
149
164
|
enrollmentToken: opts.enrollmentToken,
|
|
150
165
|
videoDevice: opts.videoDevice,
|
|
151
166
|
audioDevice: opts.audioDevice,
|
|
167
|
+
vision: opts.vision,
|
|
152
168
|
start: opts.start,
|
|
153
169
|
autoStart: opts.autoStart,
|
|
154
170
|
yes: opts.yes,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infinicode",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.16",
|
|
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",
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
"bin/robopark.js",
|
|
28
28
|
"dist",
|
|
29
29
|
"packages/robopark/scheduler",
|
|
30
|
+
"packages/robopark/pi-client",
|
|
31
|
+
"packages/robopark/vision",
|
|
30
32
|
".opencode/plugins",
|
|
31
33
|
".opencode/themes",
|
|
32
34
|
".opencode/tui.json",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# robopark-pi-client β full robot client (reference)
|
|
2
|
+
|
|
3
|
+
The full on-robot client: enrollment, heartbeat, motor control, and a
|
|
4
|
+
LiveKit publish loop (`livekit_bridge.py`) that joins a standing per-device
|
|
5
|
+
room whenever `production_mode` is on. `pi_ui.py` serves a touchscreen UI
|
|
6
|
+
with a manual "Join Conversation" button β the human-in-the-loop trigger.
|
|
7
|
+
|
|
8
|
+
This is a reference implementation shipped for Pi deployment; it is not
|
|
9
|
+
currently wired into `robopark setup --robot` (which uses `../scheduler/
|
|
10
|
+
preview_agent.py` for the on-demand preview + vision-trigger flow β see
|
|
11
|
+
`../vision/README.md`). `client.py`'s standing-room model and the scheduler's
|
|
12
|
+
`request-session`-per-trigger model are two different session strategies;
|
|
13
|
+
reconciling them into one is follow-up work, not done here.
|
|
14
|
+
|
|
15
|
+
Install: `pip install -r requirements.txt` (`livekit`/`livekit-api` are
|
|
16
|
+
commented out β Pi-only, install separately with the `livekit` SDK).
|
|
17
|
+
`install.sh`/`*.service` are Linux/Pi systemd wrappers.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
echo "=== reset perms (cleanup from previous attempt) ==="
|
|
4
|
+
sudo rm -rf /opt/robopark-pi-client/.venv 2>/dev/null || true
|
|
5
|
+
sudo chown -R robopark:robopark /opt/robopark-pi-client
|
|
6
|
+
|
|
7
|
+
echo "=== apt deps (no signing strict) ==="
|
|
8
|
+
sudo apt-get -o Acquire::AllowInsecureRepositories=true update 2>&1 | tail -3
|
|
9
|
+
sudo apt-get install -y --no-install-recommends python3-venv python3-pip libatlas3-base libasound2t64 portaudio19-dev libjpeg-dev libpng-dev ffmpeg 2>&1 | tail -3
|
|
10
|
+
echo "apt-done"
|
|
11
|
+
|
|
12
|
+
echo "=== create venv as robopark ==="
|
|
13
|
+
sudo -u robopark python3 -m venv /opt/robopark-pi-client/.venv
|
|
14
|
+
sudo -u robopark /opt/robopark-pi-client/.venv/bin/pip install --quiet --upgrade pip
|
|
15
|
+
sudo -u robopark /opt/robopark-pi-client/.venv/bin/pip install --quiet httpx
|
|
16
|
+
echo "httpx-done"
|
|
17
|
+
|
|
18
|
+
echo "=== media stack (optional, may take a while) ==="
|
|
19
|
+
sudo -u robopark /opt/robopark-pi-client/.venv/bin/pip install --quiet livekit livekit-api numpy 2>&1 | tail -3
|
|
20
|
+
echo "lk-done"
|
|
21
|
+
sudo -u robopark /opt/robopark-pi-client/.venv/bin/pip install --quiet opencv-python-headless 2>&1 | tail -3
|
|
22
|
+
echo "cv-done"
|
|
23
|
+
sudo -u robopark /opt/robopark-pi-client/.venv/bin/pip install --quiet sounddevice 2>&1 | tail -3
|
|
24
|
+
echo "sd-done"
|
|
25
|
+
|
|
26
|
+
echo "=== final state ==="
|
|
27
|
+
ls -la /opt/robopark-pi-client
|
|
28
|
+
ls /opt/robopark-pi-client/.venv/bin | head -5
|
|
29
|
+
echo "all-done"
|