feat(fleet): add board observation center with live sources and maps

Add adaptive per-vehicle layouts, full-panel dragging, source controls and automatic preview recovery for RealSense, Insta360 X4 and XGRIDS K1 observation views. Integrate cached Cesium map layers through the private NAS gateway link, retain bounded sensor command receipts, and document validation and operational handoff.
This commit is contained in:
DCCONSTRUCTIONS
2026-09-10 22:12:05 +03:00
parent 5f75afb720
commit be58d589e2
48 changed files with 1575 additions and 83 deletions
@@ -0,0 +1,43 @@
import assert from 'node:assert/strict';
import {after,before,test} from 'node:test';
import {createServer} from 'vite';
let server,layout,source,fanout;
before(async()=>{
server=await createServer({appType:'custom',logLevel:'silent',server:{middlewareMode:true}});
layout=await server.ssrLoadModule('/src/core/fleet/observationLayout.ts');
source=await server.ssrLoadModule('../../packages/sensor-ui/src/observation.ts');
({observationRerun:fanout}=await server.ssrLoadModule('@xgrids-k1/frontend/sensors/observationRerun.ts'));
});
after(async()=>{await server?.close();});
test('layout keeps independent identities and restores absent devices without confusing equal model names',()=>{
const a=source.observationKey('camera-1','left'),b=source.observationKey('camera-2','left'),c=source.observationKey('camera-1','right');
assert.notEqual(a,b);assert.notEqual(a,c);
const value={...layout.emptyObservationLayout(),order:[b,a],hidden:[a],layers:{[a]:'depth'}};
assert.deepEqual(layout.orderedObservationIDs(value,[a,c]),[a,c]);
assert.deepEqual(layout.orderedObservationIDs(value,[a,b,c]),[b,a,c]);
assert.deepEqual(layout.moveObservation(value,[a,b,c],c,b).order,[c,b,a]);
assert.deepEqual(value.hidden,[a]);
});
test('untrusted browser layout is bounded and validates revisions and split numbers',()=>{
const decoded=layout.decodeObservationLayout({version:1,order:['a','a',null,8],hidden:'all',layers:{a:'depth',b:[]},splits:{a:Infinity,b:-8,c:98},arrangement:'bad'});
assert.deepEqual(decoded.order,['a']);assert.deepEqual(decoded.hidden,[]);assert.deepEqual(decoded.splits,{b:20,c:80});assert.deepEqual(decoded.layers,{a:'depth'});assert.equal(decoded.arrangement,'auto');
assert.deepEqual(layout.decodeObservationLayout({version:2}),layout.emptyObservationLayout());
});
test('camera observation exposes only advertised layers with one viewport and RGB default',()=>{
const views=source.cameraObservationViews({name:'RealSense',layers:['depth','private-channel','color','points']});
assert.equal(views.length,1);assert.deepEqual(views[0].layers.map(v=>v.value),['color','depth','points']);
assert.deepEqual(source.cameraObservationViews({name:'unknown',layers:[]}),[]);
});
test('two Rerun views share bytes, apply independent blueprints once and dispose both hosts',async()=>{
const fetchBefore=globalThis.fetch;globalThis.fetch=async url=>new Response(Uint8Array.of(String(url).includes('plan')?2:1));
const sent=[[],[]],closed=[],started=[];let count=0;
const factory=mount=>{const index=count++;return {ready:Promise.resolve({mount,viewer:{start:async()=>started.push(index),open_channel:name=>({ready:true,send_rrd:bytes=>sent[index].push([name,[...bytes]]),close:()=>{}}),get_active_recording_id:()=> 'recording-1',override_panel_state:()=>{},set_active_timeline:()=>{}}}),dispose:()=>closed.push(index)};};
try{
const host=fanout(factory,{current:{id:'top'}})({id:'3d'});const {viewer,mount}=await host.ready;
await viewer.start(null,mount,{});const channel=viewer.open_channel('rrd');channel.send_rrd(Uint8Array.of(9,8));
assert.deepEqual(sent[0],[['rrd',[9,8]]]);assert.deepEqual(sent[1],[['rrd',[9,8]]]);
viewer.get_active_recording_id();viewer.get_active_recording_id();
assert.deepEqual(sent[0].at(-1),['observation-perspective',[1]]);assert.deepEqual(sent[1].at(-1),['observation-plan',[2]]);
assert.equal(sent[0].length,2);assert.deepEqual(started,[0,1]);host.dispose();assert.deepEqual(closed,[0,1]);
}finally{globalThis.fetch=fetchBefore;}
});
@@ -0,0 +1,52 @@
import assert from 'node:assert/strict';
import {after,before,test} from 'node:test';
import {createServer} from 'vite';
let server,activation,x4;
before(async()=>{
server=await createServer({appType:'custom',logLevel:'silent',server:{middlewareMode:true}});
activation=await server.ssrLoadModule('../../packages/sensor-ui/src/observationStart.ts');
x4=await server.ssrLoadModule('../../plugins/insta360-x4/frontend/src/observationStart.ts');
});
after(async()=>{await server?.close();});
const device=(id='camera-1')=>({id,online:true,prepared:true,layers:['color'],snapshot:{acquisition:'idle',context:{session_id:'session-1',execution:{node_id:'board-1'}}},camera_status:{connected:true,preview:0,recording:0}});
function transport(items,submit=()=>({state:'complete'})){
const commands=[];return {commands,inventory:async()=>({fresh:true,items}),submit:async c=>{commands.push(c);return submit(c);},operation:async()=>({state:'complete'})};
}
test('opening two views shares one start; another camera keeps its own lifecycle',async()=>{
const a=device(),b=device('camera-2'),t=transport([a,b]);
await Promise.all([activation.startCameraObservation(a,t),activation.startCameraObservation(a,t),activation.startCameraObservation(b,t)]);
assert.equal(t.commands.length,2);
assert.deepEqual(t.commands.map(c=>c.session.device_id).sort(),[a.id,b.id]);
for(const c of t.commands){assert.equal(c.action_id,'start');assert.deepEqual(c.parameters,{record:false});}
});
test('opening observer cannot replace active capture, recording or playback',async()=>{
for(const value of [{snapshot:{acquisition:'streaming'}},{snapshot:{acquisition:'starting'}},{snapshot:{acquisition:'stopping'}},{playback_id:'recorded-source'}]){
const a=device();Object.assign(a,{...value,snapshot:{...a.snapshot,...value.snapshot}});const t=transport([a]);
await activation.startCameraObservation(a,t);assert.equal(t.commands.length,0);
}
});
test('stale inventory and replaced sessions never receive starts',async()=>{
const original=device(),replacement=device();replacement.snapshot.context.session_id='new-session';
for(const t of [transport([replacement]),{...transport([original]),inventory:async()=>({fresh:false,items:[original]})}]){
await assert.rejects(activation.startCameraObservation(original,t));assert.equal(t.commands.length,0);
}
});
test('X4 auto start uses preview only and attaches without restarting an active camera',async()=>{
const a=device(),t=transport([a]);await x4.startX4Observation(a,t);assert.deepEqual(t.commands.map(c=>c.action_id),['preview.start']);
a.camera_status.preview=1;t.commands.length=0;await x4.startX4Observation(a,t);assert.equal(t.commands.length,0);
});
test('stalled X4 preview recovers once for both lenses without any recording action',async()=>{
const a=device();a.camera_status.preview=1;const t=transport([a]);
await Promise.all([x4.startX4Observation(a,t,true),x4.startX4Observation(a,t,true)]);
assert.deepEqual(t.commands.map(c=>c.action_id),['preview.stop','preview.start']);
});
test('X4 recovery requires confirmed stopped recording and successful preview stop',async()=>{
for(const recording of [1,null,-1]){const a=device();a.camera_status={connected:true,preview:1,recording};const t=transport([a]);await assert.rejects(x4.startX4Observation(a,t,true));assert.equal(t.commands.length,0);}
const a=device();a.camera_status.preview=1;const t=transport([a],()=>({state:'unknown'}));
await assert.rejects(x4.startX4Observation(a,t,true));assert.deepEqual(t.commands.map(c=>c.action_id),['preview.stop']);
});
test('recovery cannot start a replacement camera session after its stop',async()=>{
const a=device();a.camera_status.preview=1;const b=device();b.snapshot.context.session_id='replacement';
const items=[a],t=transport(items,()=>{items[0]=b;return {state:'complete'};});
await assert.rejects(x4.startX4Observation(a,t,true));assert.deepEqual(t.commands.map(c=>c.action_id),['preview.stop']);
});
@@ -0,0 +1,52 @@
import assert from 'node:assert/strict';
import {after,before,test} from 'node:test';
import {createServer} from 'vite';
let server,PreviewLiveness,previewPlayback;
before(async()=>{
server=await createServer({appType:'custom',logLevel:'silent',server:{middlewareMode:true}});
({PreviewLiveness}=await server.ssrLoadModule('../../packages/sensor-ui/src/previewLiveness.ts'));
({previewPlayback}=await server.ssrLoadModule('../../packages/sensor-ui/src/previewPlayback.ts'));
});
after(async()=>{await server?.close();});
test('offscreen RTP decode progress keeps a viewer live without DOM frame callbacks',()=>{
const h=new PreviewLiveness(0);
for(let now=2000;now<180000;now+=2000){h.frame(now);assert.equal(h.state(now),'live');}
});
test('one stale telemetry sample signals delay, never a camera restart',()=>{
const h=new PreviewLiveness(0);h.frame(1000);h.source(false,1100);
assert.equal(h.state(1100),'delayed');h.source(true,1500);h.frame(1500);assert.equal(h.state(1500),'live');
});
test('capture recovery requires sustained fresh reports that the source stopped',()=>{
const h=new PreviewLiveness(0);h.frame(1000);
for(let now=2000;now<=12000;now+=1000){h.source(false,now);assert.equal(h.state(now),'delayed');}
h.source(false,14000);assert.equal(h.state(14000),'source-stalled');
h.source(true,14500);h.frame(14500);assert.equal(h.state(14500),'live');
});
test('missing telemetry and a stalled peer cannot be treated as proof to restart capture',()=>{
const h=new PreviewLiveness(0);h.frame(100);h.source(false,200);
assert.equal(h.state(13000),'peer-stalled');
});
test('negotiation time does not consume the first-frame grace period',()=>{
const h=new PreviewLiveness(0);h.connected(45000);
assert.equal(h.state(45001),'waiting');assert.equal(h.state(53001),'peer-stalled');
});
test('a browser-paused panel resumes the same stream and coalesces pending playback',async()=>{
const stream={},attempts=[];
const video={srcObject:stream,muted:false,paused:true,play(){return new Promise(resolve=>attempts.push(resolve));}};
const playback=previewPlayback(video);
playback.resume();playback.resume();assert.equal(attempts.length,1);assert.equal(video.muted,true);
video.paused=false;attempts[0]();await new Promise(resolve=>setImmediate(resolve));
playback.resume();assert.equal(attempts.length,1);
video.paused=true;playback.resume();assert.equal(attempts.length,2);assert.equal(video.srcObject,stream);
playback.dispose();attempts[1]();await new Promise(resolve=>setImmediate(resolve));
playback.resume();assert.equal(attempts.length,2);
});
test('hidden-panel play rejection is retried on visibility without replacing the stream',async()=>{
const stream={};let attempts=0;
const video={srcObject:stream,muted:true,paused:true,play(){attempts++;return Promise.reject(new Error('offscreen'));}};
const playback=previewPlayback(video);
playback.resume();await new Promise(resolve=>setImmediate(resolve));
playback.resume();await new Promise(resolve=>setImmediate(resolve));
assert.equal(attempts,2);assert.equal(video.srcObject,stream);
video.srcObject=null;playback.resume();assert.equal(attempts,2);
});