infinicode 2.8.107 → 2.8.108
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.
|
@@ -825,6 +825,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
825
825
|
<div class="section"><h3>Live insights</h3><div class="pk-camera-insight" id="pk-camera-ops-insights"></div></div>
|
|
826
826
|
<div class="section"><h3>Conversation pipeline</h3><div class="pk-camera-pipeline" id="pk-camera-ops-pipeline"></div></div>
|
|
827
827
|
<div class="section"><h3>Hardware and media</h3><div class="pk-camera-insight" id="pk-camera-ops-hardware"></div></div>
|
|
828
|
+
<div class="section"><h3>Motor operations</h3><div class="rp-field"><label>Saved sequence</label><select id="pk-camera-ops-motor-sequence"></select></div><div class="pk-ctl"><button class="rp-btn" id="pk-camera-ops-motor-discover">Discover relays</button><button class="rp-btn primary" id="pk-camera-ops-motor-run">Run sequence</button><button class="rp-btn danger" id="pk-camera-ops-motor-test">Test registered GPIO</button></div><div class="pk-motor-status" id="pk-camera-ops-motor-status">Loading robot motor profile...</div></div>
|
|
828
829
|
<div class="section"><h3>Operator note</h3><div class="pk-note">Video is relayed directly from the robot's RoboVision MJPEG service through the authenticated mesh. Voice remains on LiveKit, avoiding camera ownership conflicts.</div></div>
|
|
829
830
|
</aside>
|
|
830
831
|
</div>
|
|
@@ -7640,10 +7641,11 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7640
7641
|
}
|
|
7641
7642
|
function motorEditorAddMotor(){captureMotorEditor();var number=motorProfile.registry.length+1;motorProfile.registry.push({id:'motor-'+number,name:'Motor '+number,gpio:Math.min(27,Math.max(2,number+4)),active_high:true,max_duration_ms:3000});renderMotorEditor();}
|
|
7642
7643
|
function motorEditorAddStep(){captureMotorEditor();if(!motorProfile.registry.length){motorStatus('Register a motor before adding a timed node','bad');return;}motorEditorSequence().steps.push({motor_id:motorProfile.registry[0].id,delay_ms:0,duration_ms:300});renderMotorEditor();}
|
|
7643
|
-
function motorStatus(message,kind){
|
|
7644
|
+
function motorStatus(message,kind){['pk-motor-status','pk-camera-ops-motor-status'].forEach(function(id){var host=$(id);if(host){host.textContent=message;host.className='pk-motor-status '+(kind||'');}});}
|
|
7645
|
+
function motorErrorMessage(error){var message=error&&error.message?error.message:String(error||'unknown error');return /(?:\s|^)404(?:\s|$)|not found/i.test(message)?'Motor API is not loaded on the running livekit-1 scheduler. Update and restart the scheduler, then retry.':message;}
|
|
7644
7646
|
function loadMotorProfile(n){
|
|
7645
7647
|
var robotId=schedId(n);motorStatus('Loading saved motor profile...');
|
|
7646
|
-
getJson('/robopark/api/robots/'+encodeURIComponent(robotId)+'/motor-profile').then(function(data){motorProfile={registry:data.registry||[],sequences:data.sequences||[],greeting_sequence_id:data.greeting_sequence_id||null,motor_server_url:(schedulerDevice(n)||{}).motor_server_url||data.motor_server_url||'http://127.0.0.1:8001'};if(!motorProfile.sequences.length)motorProfile.sequences=[{id:'greeting-motion',name:'Greeting motion',steps:[]}];motorSequenceId=motorProfile.greeting_sequence_id||motorProfile.sequences[0].id;renderMotorEditor();if(motorProfile.registry.length){motorStatus('Saved profile loaded | '+motorProfile.registry.length+' relay(s), '+motorProfile.sequences.length+' sequence(s)','ok');return;}motorStatus('No central registry; discovering robot-local relays...');return discoverMotorRelays(n,true);}).catch(function(error){motorStatus('Motor profile unavailable: '+error
|
|
7648
|
+
getJson('/robopark/api/robots/'+encodeURIComponent(robotId)+'/motor-profile').then(function(data){motorProfile={registry:data.registry||[],sequences:data.sequences||[],greeting_sequence_id:data.greeting_sequence_id||null,motor_server_url:(schedulerDevice(n)||{}).motor_server_url||data.motor_server_url||'http://127.0.0.1:8001'};if(!motorProfile.sequences.length)motorProfile.sequences=[{id:'greeting-motion',name:'Greeting motion',steps:[]}];motorSequenceId=motorProfile.greeting_sequence_id||motorProfile.sequences[0].id;renderMotorEditor();if(motorProfile.registry.length){motorStatus('Saved profile loaded | '+motorProfile.registry.length+' relay(s), '+motorProfile.sequences.length+' sequence(s)','ok');return;}motorStatus('No central registry; discovering robot-local relays...');return discoverMotorRelays(n,true);}).catch(function(error){motorStatus('Motor profile unavailable: '+motorErrorMessage(error),'bad');});
|
|
7647
7649
|
}
|
|
7648
7650
|
function saveMotorProfile(n){
|
|
7649
7651
|
captureMotorEditor();var robotId=schedId(n),url=motorProfile.motor_server_url;
|
|
@@ -7670,7 +7672,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7670
7672
|
}
|
|
7671
7673
|
function testAllMotorRelays(n){
|
|
7672
7674
|
captureMotorEditor();var ready=motorProfile.registry.length?Promise.resolve():discoverMotorRelays(n,true);
|
|
7673
|
-
ready.then(function(){if(!window.confirm('Pulse all '+motorProfile.registry.length+' registered relays one at a time? Ensure the motor area is clear.'))return null;var robotId=schedId(n);motorStatus('Saving profile and testing '+motorProfile.registry.length+' registered GPIO relay(s), one at a time...');return saveMotorProfile(n).then(function(){return rpAction('POST','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motors/test-all?duration_ms=300&pause_ms=200',{});}).then(function(q){return _shellWaitForResult(robotId,q.request_id,q.device_id||robotId,'','gpio-test-all');}).then(function(result){motorStatus(result.ok?'Full registered GPIO test completed: '+(result.completed_steps||[]).length+' relay(s).':'GPIO test failed: '+(result.error||'unknown error'),result.ok?'ok':'bad');});}).catch(function(error){motorStatus('GPIO test failed: '+error
|
|
7675
|
+
ready.then(function(){if(!window.confirm('Pulse all '+motorProfile.registry.length+' registered relays one at a time? Ensure the motor area is clear.'))return null;var robotId=schedId(n);motorStatus('Saving profile and testing '+motorProfile.registry.length+' registered GPIO relay(s), one at a time...');return saveMotorProfile(n).then(function(){return rpAction('POST','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motors/test-all?duration_ms=300&pause_ms=200',{});}).then(function(q){return _shellWaitForResult(robotId,q.request_id,q.device_id||robotId,'','gpio-test-all');}).then(function(result){motorStatus(result.ok?'Full registered GPIO test completed: '+(result.completed_steps||[]).length+' relay(s).':'GPIO test failed: '+(result.error||'unknown error'),result.ok?'ok':'bad');});}).catch(function(error){motorStatus('GPIO test failed: '+motorErrorMessage(error),'bad');});
|
|
7674
7676
|
}
|
|
7675
7677
|
|
|
7676
7678
|
var pipelineHistoryPage=1;
|
|
@@ -7862,20 +7864,22 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7862
7864
|
var stages=[[currentStageLabel,true,'ACTIVE'],['Robot online',!!n.connected,'ready'],['Camera streaming',cameraLive,'ready'],['Motion detector armed',!!device.production_mode,'armed'],['Scheduler session',active,'active']],pipe=$('pk-camera-ops-pipeline');if(pipe)pipe.innerHTML=stages.map(function(s,index){return '<div class="'+(index===0?'fleet-stage-focus '+esc(currentStageStatus):'')+'"><i class="'+(s[1]?'ok':'warn')+'"></i><span>'+esc(s[0])+'</span><b>'+(s[1]?s[2]:'waiting')+'</b></div>';}).join('');
|
|
7863
7865
|
var inv=device.device_inventory||{},mh=inv.media_health||{},hardware=$('pk-camera-ops-hardware');if(hardware)hardware.innerHTML='<span>CPU</span><b>'+(hw.cpu&&hw.cpu.percent!=null?Math.round(hw.cpu.percent)+'%':'--')+'</b><span>Memory</span><b>'+(hw.memory&&hw.memory.percent!=null?Math.round(hw.memory.percent)+'%':'--')+'</b><span>Temperature</span><b>'+(hw.temperatureC!=null?Math.round(hw.temperatureC)+'C':'--')+'</b><span>Camera inputs</span><b>'+((inv.video||[]).length||0)+'</b><span>Microphones</span><b>'+((inv.audio_input||[]).length||0)+'</b><span>Speaker outputs</span><b>'+((inv.audio_output||[]).length||0)+'</b><span>Camera permission</span><b class="'+(mh.camera_access===false?'bad':'ok')+'">'+(mh.camera_access===false?'DENIED':mh.camera_access===true?'granted':'--')+'</b><span>Audio permission</span><b class="'+(mh.audio_access===false?'bad':'ok')+'">'+(mh.audio_access===false?'DENIED':mh.audio_access===true?'granted':'--')+'</b><span>Camera worker</span><b class="'+(mh.camera_stalled?'bad':'ok')+'">'+(mh.camera_stalled?'STALLED':mh.camera_worker?'streaming':'--')+'</b><span>Frame age</span><b>'+(mh.camera_frame_age_seconds!=null?Number(mh.camera_frame_age_seconds).toFixed(1)+'s':'--')+'</b><span>Selected camera</span><b>'+esc(device.video_device||'auto')+'</b><span>Selected microphone</span><b>'+esc(device.audio_device||'default')+'</b>';
|
|
7864
7866
|
var prod=$('pk-camera-ops-production');if(prod){prod.textContent=device.production_mode?'Disable production':'Enable production';prod.classList.toggle('primary',!!device.production_mode);}
|
|
7867
|
+
var motorSelect=$('pk-camera-ops-motor-sequence');if(motorSelect){var selected=motorSelect.value||motorSequenceId;motorSelect.innerHTML=(motorProfile.sequences||[]).map(function(sequence){return '<option value="'+esc(sequence.id)+'">'+esc(sequence.name||sequence.id)+'</option>';}).join('')||'<option value="">No saved sequences</option>';if(selected)motorSelect.value=selected;}
|
|
7865
7868
|
}
|
|
7866
7869
|
function reloadCameraOperations(){
|
|
7867
7870
|
if(!cameraOpsNode)return;var img=$('pk-camera-ops-img'),empty=$('pk-camera-ops-empty');if(!img)return;img.dataset.live='false';if(empty){empty.style.display='block';empty.textContent='opening RoboVision camera...';}
|
|
7868
7871
|
img.onload=function(){img.dataset.live='true';if(cameraOpsRetryTimer){clearTimeout(cameraOpsRetryTimer);cameraOpsRetryTimer=null;}if(empty)empty.style.display='none';refreshCameraOperations();};img.onerror=function(){var failedNode=cameraOpsNode;img.dataset.live='false';if(empty){empty.style.display='block';empty.textContent='camera relay unavailable / retrying';}refreshCameraOperations();if(cameraOpsRetryTimer)clearTimeout(cameraOpsRetryTimer);cameraOpsRetryTimer=setTimeout(function(){if(cameraOpsNode&&cameraOpsNode===failedNode)reloadCameraOperations();},3000);};img.src=cameraOpsFeedUrl(cameraOpsNode);refreshCameraOperations();
|
|
7869
7872
|
}
|
|
7870
7873
|
function openCameraOperations(n){
|
|
7871
|
-
cameraOpsNode=n;var host=$('pk-camera-ops');if(!host)return;host.classList.add('open');document.body.classList.add('camera-ops-open');var name=n.displayName||n.name||'robot',avatar=rpAvatarFor(name),av=$('pk-camera-ops-avatar');$('pk-camera-ops-name').textContent=name+' / live operations';$('pk-camera-ops-sub').textContent='RoboVision mesh relay / '+(n.connected?'connected':'offline');if(av){if(avatar){av.src=avatar+QS;av.style.display='block';}else{av.removeAttribute('src');av.style.display='none';}}reloadCameraOperations();if(cameraOpsTimer)clearInterval(cameraOpsTimer);cameraOpsTimer=setInterval(refreshCameraOperations,1000);
|
|
7874
|
+
cameraOpsNode=n;var host=$('pk-camera-ops');if(!host)return;host.classList.add('open');document.body.classList.add('camera-ops-open');var name=n.displayName||n.name||'robot',avatar=rpAvatarFor(name),av=$('pk-camera-ops-avatar');$('pk-camera-ops-name').textContent=name+' / live operations';$('pk-camera-ops-sub').textContent='RoboVision mesh relay / '+(n.connected?'connected':'offline');if(av){if(avatar){av.src=avatar+QS;av.style.display='block';}else{av.removeAttribute('src');av.style.display='none';}}loadMotorProfile(n);reloadCameraOperations();if(cameraOpsTimer)clearInterval(cameraOpsTimer);cameraOpsTimer=setInterval(refreshCameraOperations,1000);
|
|
7872
7875
|
}
|
|
7873
7876
|
function closeCameraOperations(){
|
|
7874
7877
|
var host=$('pk-camera-ops'),img=$('pk-camera-ops-img');if(host)host.classList.remove('open');document.body.classList.remove('camera-ops-open');if(cameraOpsRetryTimer){clearTimeout(cameraOpsRetryTimer);cameraOpsRetryTimer=null;}if(img){img.src='';img.dataset.live='false';}if(cameraOpsTimer){clearInterval(cameraOpsTimer);cameraOpsTimer=null;}cameraOpsNode=null;if(document.fullscreenElement&&document.exitFullscreen)document.exitFullscreen().catch(function(){});
|
|
7875
7878
|
}
|
|
7876
7879
|
(function bindCameraOperations(){
|
|
7877
|
-
var close=$('pk-camera-ops-close'),refresh=$('pk-camera-ops-refresh'),nativeBtn=$('pk-camera-ops-native'),trigger=$('pk-camera-ops-trigger'),prod=$('pk-camera-ops-production'),end=$('pk-camera-ops-end');
|
|
7880
|
+
var close=$('pk-camera-ops-close'),refresh=$('pk-camera-ops-refresh'),nativeBtn=$('pk-camera-ops-native'),trigger=$('pk-camera-ops-trigger'),prod=$('pk-camera-ops-production'),end=$('pk-camera-ops-end'),motorDiscover=$('pk-camera-ops-motor-discover'),motorRun=$('pk-camera-ops-motor-run'),motorTest=$('pk-camera-ops-motor-test'),motorSelect=$('pk-camera-ops-motor-sequence');
|
|
7878
7881
|
if(close)close.addEventListener('click',closeCameraOperations);if(refresh)refresh.addEventListener('click',reloadCameraOperations);if(nativeBtn)nativeBtn.addEventListener('click',function(){var host=$('pk-camera-ops');if(host&&host.requestFullscreen)host.requestFullscreen().catch(function(e){showOut('fullscreen: '+e.message);});});if(trigger)trigger.addEventListener('click',function(){if(cameraOpsNode)triggerSession(schedId(cameraOpsNode));});if(prod)prod.addEventListener('click',function(){if(cameraOpsNode){var d=schedulerDevice(cameraOpsNode)||{};setProductionMode(cameraOpsNode,!d.production_mode);setTimeout(refreshCameraOperations,400);}});if(end)end.addEventListener('click',function(){if(cameraOpsNode)endSession(schedId(cameraOpsNode));});document.addEventListener('keydown',function(e){if(e.key==='Escape'&&cameraOpsNode)closeCameraOperations();});
|
|
7882
|
+
if(motorDiscover)motorDiscover.addEventListener('click',function(){if(cameraOpsNode)discoverMotorRelays(cameraOpsNode,false).catch(function(error){motorStatus('Discovery failed: '+error.message,'bad');});});if(motorRun)motorRun.addEventListener('click',function(){if(cameraOpsNode)runMotorSequence(cameraOpsNode);});if(motorTest)motorTest.addEventListener('click',function(){if(cameraOpsNode)testAllMotorRelays(cameraOpsNode);});if(motorSelect)motorSelect.addEventListener('change',function(){motorSequenceId=motorSelect.value;renderMotorEditor();refreshCameraOperations();});
|
|
7879
7883
|
})();
|
|
7880
7884
|
|
|
7881
7885
|
// ── cam: target a LiveKit track; fall back to a styled preview ──
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { spawn } from 'node:child_process';
|
|
11
11
|
import { existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
12
|
-
import { homedir } from 'node:os';
|
|
12
|
+
import { homedir, networkInterfaces } from 'node:os';
|
|
13
13
|
import { dirname, join } from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
15
|
import chalk from 'chalk';
|
|
@@ -18,6 +18,122 @@ const VISION_URL = 'http://127.0.0.1:5000/api/media/inventory';
|
|
|
18
18
|
const VISION_STATUS_URL = 'http://127.0.0.1:5000/api/camera/status';
|
|
19
19
|
const RESTART_DELAY_MS = 5_000;
|
|
20
20
|
const MEDIA_WATCHDOG_INTERVAL_MS = 10_000;
|
|
21
|
+
const SCHEDULER_HEARTBEAT_INTERVAL_MS = 5_000;
|
|
22
|
+
function currentLanIp() {
|
|
23
|
+
for (const addresses of Object.values(networkInterfaces())) {
|
|
24
|
+
for (const address of addresses ?? []) {
|
|
25
|
+
if (address.family === 'IPv4' && !address.internal)
|
|
26
|
+
return address.address;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
function readSchedulerIdentity() {
|
|
32
|
+
const configDir = join(homedir(), '.robopark');
|
|
33
|
+
try {
|
|
34
|
+
const config = JSON.parse(readFileSync(join(configDir, 'preview_agent.json'), 'utf8'));
|
|
35
|
+
const deviceId = String(config.device_id ?? '').trim();
|
|
36
|
+
const fileToken = existsSync(join(configDir, 'device_token'))
|
|
37
|
+
? readFileSync(join(configDir, 'device_token'), 'utf8').trim()
|
|
38
|
+
: '';
|
|
39
|
+
const deviceToken = fileToken || String(config.device_token ?? '').trim();
|
|
40
|
+
if (deviceId && deviceToken)
|
|
41
|
+
return { deviceId, deviceToken };
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// First boot has no identity yet.
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
function persistSchedulerIdentity(identity, schedulerUrl) {
|
|
49
|
+
const configDir = join(homedir(), '.robopark');
|
|
50
|
+
const configPath = join(configDir, 'preview_agent.json');
|
|
51
|
+
mkdirSync(configDir, { recursive: true });
|
|
52
|
+
let config = {};
|
|
53
|
+
try {
|
|
54
|
+
config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
55
|
+
}
|
|
56
|
+
catch { /* first boot */ }
|
|
57
|
+
config.device_id = identity.deviceId;
|
|
58
|
+
config.device_token = identity.deviceToken;
|
|
59
|
+
config.scheduler_url = schedulerUrl;
|
|
60
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
|
|
61
|
+
writeFileSync(join(configDir, 'device_token'), identity.deviceToken, { encoding: 'utf8', mode: 0o600 });
|
|
62
|
+
}
|
|
63
|
+
function startSchedulerHeartbeat(opts, livekitUrl) {
|
|
64
|
+
let identity = readSchedulerIdentity();
|
|
65
|
+
let stopped = false;
|
|
66
|
+
let inFlight = false;
|
|
67
|
+
const bootstrap = async () => {
|
|
68
|
+
const response = await fetch(`${opts.schedulerUrl.replace(/\/+$/, '')}/api/devices/bootstrap`, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: {
|
|
71
|
+
'content-type': 'application/json',
|
|
72
|
+
'x-robopark-mesh-token': opts.token,
|
|
73
|
+
},
|
|
74
|
+
body: JSON.stringify({ name: opts.name, lan_ip: currentLanIp(), livekit_url: livekitUrl }),
|
|
75
|
+
signal: AbortSignal.timeout(10_000),
|
|
76
|
+
});
|
|
77
|
+
if (!response.ok)
|
|
78
|
+
throw new Error(`bootstrap HTTP ${response.status}`);
|
|
79
|
+
const data = await response.json();
|
|
80
|
+
const next = {
|
|
81
|
+
deviceId: String(data.device_id ?? '').trim(),
|
|
82
|
+
deviceToken: String(data.device_token ?? '').trim(),
|
|
83
|
+
};
|
|
84
|
+
if (!next.deviceId || !next.deviceToken)
|
|
85
|
+
throw new Error('bootstrap returned no device credentials');
|
|
86
|
+
persistSchedulerIdentity(next, opts.schedulerUrl);
|
|
87
|
+
return next;
|
|
88
|
+
};
|
|
89
|
+
const beat = async () => {
|
|
90
|
+
if (stopped || inFlight)
|
|
91
|
+
return;
|
|
92
|
+
inFlight = true;
|
|
93
|
+
try {
|
|
94
|
+
identity = readSchedulerIdentity() ?? identity;
|
|
95
|
+
if (!identity)
|
|
96
|
+
identity = await bootstrap();
|
|
97
|
+
let response = await fetch(`${opts.schedulerUrl.replace(/\/+$/, '')}/api/devices/${encodeURIComponent(identity.deviceId)}/heartbeat`, {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers: {
|
|
100
|
+
authorization: `Bearer ${identity.deviceToken}`,
|
|
101
|
+
'content-type': 'application/json',
|
|
102
|
+
'x-robopark-mesh-token': opts.token,
|
|
103
|
+
},
|
|
104
|
+
body: JSON.stringify({ status: 'online', ip: currentLanIp(), livekit_url: livekitUrl }),
|
|
105
|
+
signal: AbortSignal.timeout(10_000),
|
|
106
|
+
});
|
|
107
|
+
if (response.status === 401 || response.status === 404) {
|
|
108
|
+
identity = await bootstrap();
|
|
109
|
+
response = await fetch(`${opts.schedulerUrl.replace(/\/+$/, '')}/api/devices/${encodeURIComponent(identity.deviceId)}/heartbeat`, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: {
|
|
112
|
+
authorization: `Bearer ${identity.deviceToken}`,
|
|
113
|
+
'content-type': 'application/json',
|
|
114
|
+
'x-robopark-mesh-token': opts.token,
|
|
115
|
+
},
|
|
116
|
+
body: JSON.stringify({ status: 'online', ip: currentLanIp(), livekit_url: livekitUrl }),
|
|
117
|
+
signal: AbortSignal.timeout(10_000),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (!response.ok)
|
|
121
|
+
throw new Error(`heartbeat HTTP ${response.status}`);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
console.error(chalk.yellow(` scheduler heartbeat watchdog: ${error instanceof Error ? error.message : String(error)}`));
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
inFlight = false;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
void beat();
|
|
131
|
+
const timer = setInterval(() => { void beat(); }, SCHEDULER_HEARTBEAT_INTERVAL_MS);
|
|
132
|
+
return () => {
|
|
133
|
+
stopped = true;
|
|
134
|
+
clearInterval(timer);
|
|
135
|
+
};
|
|
136
|
+
}
|
|
21
137
|
function resetStaleEnrollment() {
|
|
22
138
|
const configDir = join(homedir(), '.robopark');
|
|
23
139
|
const tokenPath = join(configDir, 'device_token');
|
|
@@ -288,6 +404,7 @@ export async function roboparkRobotRuntime(opts) {
|
|
|
288
404
|
}
|
|
289
405
|
// Identity and inventory reporting start immediately. RoboVision may still
|
|
290
406
|
// be installing or recovering without making the robot disappear.
|
|
407
|
+
const stopSchedulerHeartbeat = startSchedulerHeartbeat(opts, livekitUrl);
|
|
291
408
|
start(children[0]);
|
|
292
409
|
start(preview);
|
|
293
410
|
if (opts.vision !== false) {
|
|
@@ -303,6 +420,7 @@ export async function roboparkRobotRuntime(opts) {
|
|
|
303
420
|
const mediaWatchdog = setInterval(() => { void monitorVision(); }, MEDIA_WATCHDOG_INTERVAL_MS);
|
|
304
421
|
const shutdown = () => {
|
|
305
422
|
stopping = true;
|
|
423
|
+
stopSchedulerHeartbeat();
|
|
306
424
|
clearInterval(mediaWatchdog);
|
|
307
425
|
for (const entry of [...children, preview])
|
|
308
426
|
signalChild(entry, 'SIGTERM');
|
package/package.json
CHANGED