0eebcb3aec
Pipeline web data-only complet : Cote feeds, OSC target :57124 ajoute au profil data-only.toml pour que le bridge.py diffuse vers : SC (57121) + oF (57123) + Web (57124). Cote web (data_only_viz/web/) : - server.js : Express :3211 + WS broadcast + osc UDP :57124 parse les paths /data/<feed>/<sub> en JSON et diffuse - public/dashboard.html : grille de cards live avec sparklines SVG vanilla (USGS, SWPC Kp/wind/Bz/X-ray, Blitz, OpenSky, Bluesky, meteo, air, ISS, volcans, social_buzz, grid) - public/map.html : Leaflet dark fullscreen avec markers ephemeres (60s TTL) pour seismes/foudre/avions/volcans + marker ISS persistant qui suit la position Cote launcher : - ProcessManager : startDataWeb / stopDataWeb + openDataDashboard / openDataMap, port :3211 hardcode, script attendu a metalVizDir/web/server.js - MenuBarContent : nouvelle row 'Dashboard data-only' en mode .dataOnly et .bodyMesh + boutons Dashboard/Carte monde quand le serveur tourne - stopAll inclut dataWebProc
92 lines
2.7 KiB
JavaScript
92 lines
2.7 KiB
JavaScript
// =====================================================================
|
|
// data_only_viz / web / server.js
|
|
//
|
|
// Bridge OSC -> WebSocket pour le mode data-only d'AV-Live.
|
|
//
|
|
// data_feeds bridge.py
|
|
// | OSC UDP :57124
|
|
// v
|
|
// server.js (Express :3211 + WS)
|
|
// | JSON via WebSocket
|
|
// v
|
|
// browser : dashboard.html / map.html
|
|
//
|
|
// Conversion : chaque message OSC /data/<feed>/<sub> args... devient
|
|
// { feed, sub, args, t }
|
|
// diffuse a tous les clients WS connectes.
|
|
// =====================================================================
|
|
import express from "express";
|
|
import { fileURLToPath } from "node:url";
|
|
import { dirname, join } from "node:path";
|
|
import { createServer } from "node:http";
|
|
import { WebSocketServer } from "ws";
|
|
import osc from "osc";
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const HTTP_PORT = parseInt(process.env.HTTP_PORT ?? "3211", 10);
|
|
const OSC_PORT_IN = parseInt(process.env.OSC_PORT_IN ?? "57124", 10);
|
|
|
|
const app = express();
|
|
app.use(express.static(join(__dirname, "public")));
|
|
app.get("/", (_req, res) => res.redirect("/dashboard.html"));
|
|
|
|
const httpServer = createServer(app);
|
|
const wss = new WebSocketServer({ server: httpServer });
|
|
|
|
// Ring buffer for late-joiners : ~500 last messages
|
|
const HISTORY_MAX = 500;
|
|
const history = [];
|
|
function record(msg) {
|
|
history.push(msg);
|
|
if (history.length > HISTORY_MAX) history.shift();
|
|
}
|
|
|
|
wss.on("connection", (ws) => {
|
|
// Replay recent history so a new dashboard sees the latest values
|
|
for (const msg of history.slice(-100)) {
|
|
try { ws.send(JSON.stringify(msg)); } catch {}
|
|
}
|
|
});
|
|
|
|
function broadcast(msg) {
|
|
const payload = JSON.stringify(msg);
|
|
for (const c of wss.clients) {
|
|
if (c.readyState === 1) {
|
|
try { c.send(payload); } catch {}
|
|
}
|
|
}
|
|
}
|
|
|
|
// OSC UDP listener
|
|
const udp = new osc.UDPPort({
|
|
localAddress: "127.0.0.1",
|
|
localPort: OSC_PORT_IN,
|
|
metadata: false,
|
|
});
|
|
|
|
udp.on("message", (m) => {
|
|
// Path format : /data/<feed>/<sub>
|
|
const parts = (m.address || "").split("/").filter(Boolean);
|
|
if (parts.length < 2 || parts[0] !== "data") return;
|
|
const feed = parts[1];
|
|
const sub = parts.slice(2).join("/") || "msg";
|
|
const msg = {
|
|
t: Date.now(),
|
|
feed,
|
|
sub,
|
|
args: Array.isArray(m.args) ? m.args : [],
|
|
};
|
|
record(msg);
|
|
broadcast(msg);
|
|
});
|
|
|
|
udp.on("error", (e) => console.error("OSC error:", e));
|
|
udp.open();
|
|
console.log(`OSC listening on udp :${OSC_PORT_IN}`);
|
|
|
|
httpServer.listen(HTTP_PORT, () => {
|
|
console.log(`Data-only web on http://127.0.0.1:${HTTP_PORT}/`);
|
|
console.log(` Dashboard : http://127.0.0.1:${HTTP_PORT}/dashboard.html`);
|
|
console.log(` Map : http://127.0.0.1:${HTTP_PORT}/map.html`);
|
|
});
|