diff --git a/game/server/package-lock.json b/game/server/package-lock.json index 9db677b2d484247131a18630e23e9ef3ca6a5572..2e06169a96c2773841a9606c6215e8ff75bc9d0d 100644 --- a/game/server/package-lock.json +++ b/game/server/package-lock.json @@ -4,9 +4,11 @@ "requires": true, "packages": { "": { + "name": "server", "dependencies": { "dotenv": "^17.2.3", "express": "^5.1.0", + "minimist": "^1.2.8", "pg": "^8.16.3", "serialport": "^13.0.0", "ws": "^8.18.3" @@ -265,23 +267,27 @@ } }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/bytes": { @@ -654,15 +660,19 @@ } }, "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/inherits": { @@ -741,6 +751,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -833,6 +852,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -1024,22 +1044,6 @@ "node": ">= 0.8" } }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/raw-body/node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", diff --git a/game/server/package.json b/game/server/package.json index 56c9042d25ac60ac6e012656f1eee1cccc4d4557..4450e1dc2ce36ffb3abd167424d7ebb7d6303b06 100644 --- a/game/server/package.json +++ b/game/server/package.json @@ -3,6 +3,7 @@ "dotenv": "^17.2.3", "express": "^5.1.0", "pg": "^8.16.3", + "minimist": "^1.2.8", "serialport": "^13.0.0", "ws": "^8.18.3" } diff --git a/game/server/server.js b/game/server/server.js index 97ebfc6b2ee8a152ec8a63c83b56f83711fda9db..532630e3e62202a80b7243c06c3b7f1bd66c5285 100644 --- a/game/server/server.js +++ b/game/server/server.js @@ -1,95 +1,110 @@ // Chargement de la configuration sécurisée (.env) require('dotenv').config({ path: '../../.env' }); +const minimist = require('minimist'); const express = require('express'); const { SerialPort, ReadlineParser } = require('serialport'); const WebSocket = require('ws'); -// Import du module de base de données const { initDB, bufferGameRun } = require('./db/db'); -const app = express(); -const port = 3000; +// --- Lecture des arguments CLI --- +const argv = minimist(process.argv.slice(2), { + default: { + httpPort: 3000, + monitorPort: 3001, + sensorId: "ttyACM0", + baud: 9600, + minScore: 2 + } +}); -// --- Configuration Métier --- -const SENSOR_ID = "ttyACM0"; // Identifiant du capteur pour la BDD -const MIN_SCORE_TO_SAVE = 2; // Seuil pour éviter le spam en BDD +// --- Configuration CLI --- +const HTTP_PORT = argv.httpPort; +const MONITOR_PORT = argv.monitorPort; +const SENSOR_ID = argv.sensorId; +const MIN_SCORE_TO_SAVE = argv.minScore; +const BAUD_RATE = argv.baud; + +console.log("Configuration utilisée :", { + HTTP_PORT, + MONITOR_PORT, + SENSOR_ID, + BAUD_RATE, + MIN_SCORE_TO_SAVE +}); // --- Variables de Session --- let currentSessionScore = 0; let currentSessionDeaths = 0; -// Initialisation BDD au démarrage +// Initialisation BDD initDB(); +const app = express(); + // --- Serveur HTTP --- -const server = app.listen(port, () => { - console.log(`Serveur démarré sur http://localhost:${port}`); +const server = app.listen(HTTP_PORT, () => { + console.log(`Serveur HTTP + WebSocket Jeu sur http://localhost:${HTTP_PORT}`); }); -// --- Serveur WebSocket --- +// --- Serveur WebSocket (JEU) --- const wss = new WebSocket.Server({ server }); -// --- Configuration Ports Série (Architecture Répéteur) --- - -// 1. Le Vrai Port (Pour le Jeu) -// Connexion directe au matériel pour zéro latence -const arduinoPort = new SerialPort({ - path: "/dev/ttyACM0", - baudRate: 9600, - autoOpen: true +// --- Serveur WebSocket (MONITORING) --- +const monitoringWSS = new WebSocket.Server({ port: MONITOR_PORT }, () => { + console.log(`Serveur WebSocket Monitoring sur ws://localhost:${MONITOR_PORT}`); }); -// 2. Le Pont Virtuel (Pour le Moniteur) -// Copie du trafic pour les graphiques -const bridgePort = new SerialPort({ - path: "/dev/ttyBRIDGE", - baudRate: 9600, +// Fonction utilitaire pour envoyer des événements au monitoring +function sendMonitorEvent(event) { + const payload = JSON.stringify(event); + + monitoringWSS.clients.forEach(client => { + if (client.readyState === WebSocket.OPEN) { + client.send(payload); + } + }); +} + +// --- Configuration Port Série --- +const arduinoPort = new SerialPort({ + path: `/dev/${SENSOR_ID}`, + baudRate: BAUD_RATE, autoOpen: true }); const parser = arduinoPort.pipe(new ReadlineParser()); -// --- Réception Données (Arduino -> PC) --- +// --- Réception Données Arduino --- parser.on("data", line => { const cleanLine = line.trim(); - - // 1. Traitement Jeu (Détection du saut) + if (cleanLine.includes("JUMP")) { + // Envoi au jeu wss.clients.forEach(client => { if (client.readyState === WebSocket.OPEN) { client.send(JSON.stringify({ action: "jump" })); } }); - } - // 2. Copie vers Moniteur (Miroir) - if (bridgePort.isOpen) { - bridgePort.write(cleanLine + "\n"); + sendMonitorEvent({ type: "input", action: "jump" }); } }); -// --- Émission Données (PC -> Arduino) --- +// --- Envoi Arduino --- function sendToArduino(message) { const msg = message + "\n"; - - // Envoi au matériel réel (Affichage) + if (arduinoPort.isOpen) { arduinoPort.write(msg, err => { if (err) console.error("Erreur écriture Arduino :", err); }); } - - // Envoi au pont virtuel (Logs Moniteur) - if (bridgePort.isOpen) { - bridgePort.write(msg, err => { - if (err) console.error("Erreur écriture Bridge :", err); - }); - } } -// --- WebSocket & Logique BDD --- +// --- WebSocket JEU --- wss.on("connection", ws => { - console.log("Client connecté"); + console.log("Client connecté au serveur JEU"); ws.on("close", () => { currentSessionScore = 0; @@ -99,29 +114,46 @@ wss.on("connection", ws => { try { const data = JSON.parse(msg); - if(data.type == "input") { - sendToArduino(String(data.action).toUpperCase()); + if (data.type === "input") { + sendMonitorEvent({ + type: "input", + action: data.action, + }); } + // --- SCORE --- if (data.type === "score") { - // Détection Fin de Partie (Signal Restart = 0) + + sendMonitorEvent({ + type: "score", + score: data.score, + }); + + // Fin de partie if (data.score === 0) { - // Sauvegarde si le score précédent était pertinent - if (currentSessionScore >= MIN_SCORE_TO_SAVE) { - console.log(`Fin de session. Score: ${currentSessionScore}. Ajout au buffer.`); - bufferGameRun(SENSOR_ID, currentSessionScore, currentSessionDeaths); - } - currentSessionScore = 0; - currentSessionDeaths = 0; + if (currentSessionScore >= MIN_SCORE_TO_SAVE) { + bufferGameRun(SENSOR_ID, currentSessionScore, currentSessionDeaths); + } + + currentSessionScore = 0; + currentSessionDeaths = 0; + } else { - currentSessionScore = data.score; + currentSessionScore = data.score; } sendToArduino(`S${data.score}`); } + // --- DEATHS --- if (data.type === "death") { currentSessionDeaths = data.deaths; + + sendMonitorEvent({ + type: "death", + deaths: data.deaths, + }); + sendToArduino(`D${data.deaths}`); } @@ -129,4 +161,4 @@ wss.on("connection", ws => { console.error("Erreur WS :", e); } }); -}); \ No newline at end of file +}); diff --git a/monitor/client/src/Dashboard.tsx b/monitor/client/src/Dashboard.tsx index 937085698ef8b30fe11f4df61c609efce259677e..962f29f369a1368a90deb1593399feb3ad04206f 100644 --- a/monitor/client/src/Dashboard.tsx +++ b/monitor/client/src/Dashboard.tsx @@ -5,7 +5,7 @@ import Charts from "./Charts"; // Define the shape of the messages coming from WebSocket type WSData = - | { action: "jump" } + | { type: "input"; action: "jump" } | { type: "score"; score: number } | { type: "death"; deaths: number }; @@ -24,13 +24,14 @@ export default function Dashboard() { const wsRef = useRef(null); useEffect(() => { - const ws = new WebSocket("ws://localhost:8000"); + const ws = new WebSocket("ws://localhost:8080"); wsRef.current = ws; ws.onopen = () => setConnected(true); ws.onclose = () => setConnected(false); ws.onmessage = (msg) => { + console.log(msg.data); const data: WSData = JSON.parse(msg.data); // raw log diff --git a/monitor/server/monitor-arduino.js b/monitor/server/monitor-arduino.js index 6ebeea00f2dd50c1a6d4599c8637dcc7bae3b386..26d08a59e4a24b4c904a16bf16d69277054e9be6 100644 --- a/monitor/server/monitor-arduino.js +++ b/monitor/server/monitor-arduino.js @@ -1,95 +1,56 @@ -#!/usr/bin/env node +const WebSocket = require('ws'); +const minimist = require('minimist'); -const { SerialPort, ReadlineParser } = require("serialport"); -const express = require("express"); -const http = require("http"); -const WebSocket = require("ws"); +// ------- CLI ARGUMENTS ------- +// Example: +// node monitor-arduino.js --remote=wss://remote.example.com --port=8080 -// --------------------------- -// COMMAND LINE ARGUMENTS -// --------------------------- -// -// Usage: -// node monitor-arduino.js --serial=/dev/ttyUSB0 --baud=9600 --http=8000 -// +const args = minimist(process.argv.slice(2)); -const args = require("minimist")(process.argv.slice(2), { - string: ["serial", "baud", "http"], -}); +const REMOTE_WS_URL = args.remote || "ws://localhost:9000"; +const LOCAL_PORT = args.port || 8080; + +console.log("Remote WebSocket URL:", REMOTE_WS_URL); +console.log("Local server port:", LOCAL_PORT); -const PORT_NAME = args.serial; -const BAUD = parseInt(args.baud, 10); -const HTTP_PORT = parseInt(args.http, 10); +// ------- CONNECT TO REMOTE SERVER ------- +const remoteSocket = new WebSocket(REMOTE_WS_URL); -// --------------------------- -// SERIAL PORT SETUP -// --------------------------- -const port = new SerialPort({ - path: PORT_NAME, - baudRate: BAUD, +remoteSocket.on('open', () => { + console.log("Connected to remote WebSocket server:", REMOTE_WS_URL); }); -const parser = port.pipe(new ReadlineParser({ delimiter: "\n" })); +remoteSocket.on('error', (err) => { + console.error("Remote WS error:", err); +}); -port.on("open", () => { - console.log(`Listening to Arduino on ${PORT_NAME} at ${BAUD} baud`); +// ------- CREATE LOCAL SERVER ------- +const localWSS = new WebSocket.Server({ port: LOCAL_PORT }, () => { + console.log(`Local WebSocket server running on ws://localhost:${LOCAL_PORT}`); }); -// --------------------------- -// WEBSERVER + WEBSOCKET -// --------------------------- -const app = express(); -const server = http.createServer(app); -const wss = new WebSocket.Server({ server }); +let clients = []; -wss.on("connection", (ws) => { - console.log("WebSocket client connected"); -}); +// Handle clients connecting to local proxy +localWSS.on('connection', (socket) => { + console.log("Client connected"); + clients.push(socket); -// broadcast helper -function broadcast(obj) { - const msg = JSON.stringify(obj); - wss.clients.forEach((client) => { - if (client.readyState === WebSocket.OPEN) { - client.send(msg); - } + socket.on('close', () => { + console.log("Client disconnected"); + clients = clients.filter(s => s !== socket); }); -} - -// --------------------------- -// PARSE SERIAL AND FORWARD -// --------------------------- -parser.on("data", (raw) => { - const line = raw.trim(); - console.log("Serial:", line); +}); - if (line === "JUMP") { - broadcast({ type: "input", action: "jump" }); - return; - } +// ------- FORWARD REMOTE → LOCAL CLIENTS ------- +remoteSocket.on('message', (msg) => { - if (line.startsWith("S")) { - const value = parseInt(line.substring(1), 10); - if (!isNaN(value)) { - broadcast({ type: "score", score: value }); - } - return; - } + const text = msg.toString(); // convert Buffer → UTF-8 string + console.log("Received from remote:", text); - if (line.startsWith("D")) { - const value = parseInt(line.substring(1), 10); - if (!isNaN(value)) { - broadcast({ type: "death", deaths: value }); + clients.forEach(client => { + if (client.readyState === WebSocket.OPEN) { + client.send(text); // send text, not binary } - return; - } - - broadcast({ type: "unknown", raw: line }); -}); - -// --------------------------- -// START SERVER -// --------------------------- -server.listen(HTTP_PORT, () => { - console.log(`HTTP/WebSocket server running at http://localhost:${HTTP_PORT}`); + }); });