Lattice Grid Buy a licence

demo D262

Starter kit: fleet telemetry

One telemetry feed, a live grid per device class and a units-online chart, with no backend

createDataRouter · MockWebSocket · rng

Building…
Loading a live grid…

The configuration

'starter-fleet-telemetry': () => ({
  rows: [],
  config: {},
  foot: [
    'one telemetry feed, a live grid per device class',
    'each class has its own columns, split from the one feed by kind',
    'the mock socket runs it with no backend; one line points it at real telemetry',
  ],
  mount: (el: HTMLElement, LG: any) => {
    const strip = document.createElement('div');
    strip.style.cssText = 'display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px';
    el.append(strip);
    const trucksEl = panel(strip, 'Trucks');
    const dronesEl = panel(strip, 'Drones');
    const sensorsEl = panel(strip, 'Sensors');
    const chartEl = chartHost(el);
    const trucks = LG.createGrid(trucksEl, {
      rowKey: 'id', theme: 'light', highlightOnChange: { duration: 400 },
      columns: [
        { field: 'id', title: 'Unit', layout: { width: 92 } },
        { field: 'region', title: 'Region', filter: { type: 'set' } },
        { field: 'speed', title: 'Speed', type: 'number' },
        { field: 'fuel', title: 'Fuel %', type: 'number' },
        { field: 'status', title: 'Status' },
      ],
    });
    const drones = LG.createGrid(dronesEl, {
      rowKey: 'id', theme: 'light', highlightOnChange: { duration: 400 },
      columns: [
        { field: 'id', title: 'Unit', layout: { width: 92 } },
        { field: 'region', title: 'Region', filter: { type: 'set' } },
        { field: 'altitude', title: 'Alt (m)', type: 'number' },
        { field: 'battery', title: 'Battery %', type: 'number' },
        { field: 'status', title: 'Status' },
      ],
    });
    const sensors = LG.createGrid(sensorsEl, {
      rowKey: 'id', theme: 'light', highlightOnChange: { duration: 400 },
      columns: [
        { field: 'id', title: 'Unit', layout: { width: 92 } },
        { field: 'region', title: 'Region', filter: { type: 'set' } },
        { field: 'tempC', title: 'Temp C', type: 'number' },
        { field: 'battery', title: 'Battery %', type: 'number' },
        { field: 'status', title: 'Status' },
      ],
    });
    const metrics = LG.createHeadlessGrid({
      rowKey: 't', columns: [{ field: 't', type: 'number' }, { field: 'online', type: 'number' }],
    });
    let chart: any, socket: any;
    Promise.all([loadDataRouter(), loadCharts(), loadMockSocket()])
      .then(([dr, ch, ms]: any[]) => {
        const rng = ms.rng;
        function* fleetFeed(seed: number) {
          const rand = rng(seed);
          const REGIONS = ['North', 'South', 'East', 'West'];
          const pick = (list: string[]) => list[Math.floor(rand() * list.length)];
          const round = (n: number, p: number) => { const f = Math.pow(10, p); return Math.round(n * f) / f; };
          const trucksD: any[] = [], dronesD: any[] = [], sensorsD: any[] = [];
          for (let i = 0; i < 14; i++) trucksD.push({ id: 'TRK-' + (100 + i), kind: 'truck', region: pick(REGIONS), speed: Math.round(rand() * 70), fuel: 20 + Math.round(rand() * 80), status: 'en route' });
          for (let j = 0; j < 10; j++) dronesD.push({ id: 'DRN-' + (200 + j), kind: 'drone', region: pick(REGIONS), altitude: 40 + Math.round(rand() * 260), battery: 30 + Math.round(rand() * 70), status: 'in flight' });
          for (let k = 0; k < 18; k++) sensorsD.push({ id: 'SNS-' + (300 + k), kind: 'sensor', region: pick(REGIONS), tempC: round(4 + rand() * 30, 1), battery: 30 + Math.round(rand() * 70), status: 'ok' });
          const all = trucksD.concat(dronesD, sensorsD);
          let t = 0;
          const online = () => ({ id: 'M-' + t, kind: 'metric', t, online: all.filter((d) => d.status !== 'offline').length });
          yield { kind: 'snapshot', rows: all.concat([online()]) };
          for (;;) {
            const changes: any[] = [];
            const n = 2 + Math.floor(rand() * 4);
            for (let m = 0; m < n; m++) {
              const d = all[Math.floor(rand() * all.length)];
              if (d.kind === 'truck') { d.speed = Math.round(rand() * 70); d.fuel = Math.max(0, d.fuel - Math.round(rand() * 3)); d.status = d.fuel < 10 ? 'low fuel' : 'en route'; }
              else if (d.kind === 'drone') { d.altitude = 40 + Math.round(rand() * 260); d.battery = Math.max(0, d.battery - Math.round(rand() * 4)); d.status = d.battery < 15 ? 'returning' : 'in flight'; }
              else { d.tempC = round(4 + rand() * 30, 1); d.battery = Math.max(0, d.battery - Math.round(rand() * 2)); d.status = d.tempC > 30 ? 'alert' : 'ok'; }
              changes.push({ op: 'upsert', row: { ...d } });
            }
            t++;
            changes.push({ op: 'upsert', row: online() });
            yield { kind: 'delta', changes };
          }
        }
        const router = dr.createDataRouter({ key: 'kind', rowKey: 'id' });
        router.attach(trucks, 'truck');
        router.attach(drones, 'drone');
        router.attach(sensors, 'sensor');
        router.attach(metrics, 'metric', { rowKey: 't' });
        chart = ch.createChart({ grid: metrics, container: chartEl, type: 'line', x: 't', y: 'online', title: 'Units online per tick' });
        socket = new ms.MockWebSocket({ feed: fleetFeed(11), rate: 800, jitter: 200 });
        pipe(socket, router);
      })
      .catch((err) => console.error('[starter-fleet-telemetry]', err));
    return () => {
      socket?.close?.();
      chart?.destroy?.();
      trucks?.destroy?.(); drones?.destroy?.(); sensors?.destroy?.(); metrics?.destroy?.();
    };
  },
})