Compare commits

..

No commits in common. "2a1e0904e86f49de9240fefd081f29ece24c3cec" and "a1cea9c37d8f914dbcdf1b80101bb200469ac194" have entirely different histories.

11 changed files with 135 additions and 182 deletions

View file

@ -41,7 +41,7 @@ npm start
``` ```
Der Server startet mit `nodemon` und ist anschließend unter Port **3000** erreichbar. Der Server startet mit `nodemon` und ist anschließend unter Port **3000** erreichbar.
Die WebSocket-Verbindung läuft über denselben Port — es ist kein zweiter Port nötig. Der Socket.IO-Server läuft separat auf Port **3001**.
### Port ändern ### Port ändern
@ -65,7 +65,7 @@ scoreboard/
├── bin/www # HTTP-Server, startet die App ├── bin/www # HTTP-Server, startet die App
├── db.json # Persistente Liste bekannter Teamnamen ├── db.json # Persistente Liste bekannter Teamnamen
├── controllers/ ├── controllers/
│ ├── socketio.js # Socket.IO-Server (teilt sich den HTTP-Port) │ ├── socketio.js # Socket.IO-Server (Port 3001)
│ ├── timer.js # Timer-Logik (Start, Pause, Reset, IncDec) │ ├── timer.js # Timer-Logik (Start, Pause, Reset, IncDec)
│ ├── score.js # Score-Logik (Punkte, Teams, Seitenwechsel) │ ├── score.js # Score-Logik (Punkte, Teams, Seitenwechsel)
│ ├── db.js # Lesen/Schreiben der Teamnamen in db.json │ ├── db.js # Lesen/Schreiben der Teamnamen in db.json
@ -73,7 +73,7 @@ scoreboard/
│ └── cli.js # Start/Stop des Kiosk-Browsers auf dem Anzeigerechner │ └── cli.js # Start/Stop des Kiosk-Browsers auf dem Anzeigerechner
├── routes/ ├── routes/
│ ├── admin.js # REST-Endpunkte für das Admin-Panel │ ├── admin.js # REST-Endpunkte für das Admin-Panel
│ └── index.js # Monitor-Ansicht (waehlt das passende Template) │ └── index.js # Monitor-Ansicht + WebSocket-Verbindungshandler
├── views/ ├── views/
│ ├── admin.hbs # Admin-Panel (Handlebars-Template) │ ├── admin.hbs # Admin-Panel (Handlebars-Template)
│ ├── splashscreen.hbs # Monitor: Vereinslogo │ ├── splashscreen.hbs # Monitor: Vereinslogo
@ -183,7 +183,7 @@ Admin-Panel (Browser)
├── controllers/db.js ──┐ │ ├── controllers/db.js ──┐ │
└── controllers/cli.js │ │ io.sockets.emit(event, data) └── controllers/cli.js │ │ io.sockets.emit(event, data)
│ ▼ │ ▼
db.json Socket.IO (am selben Port) db.json Socket.IO-Server :3001
WebSocket-Verbindung WebSocket-Verbindung
@ -197,12 +197,6 @@ Der gesamte Spielzustand (Timer, Score, Teams, Schalter) liegt **im
Arbeitsspeicher** der Controller. Ein Serverneustart setzt ihn zurück; einzig die Arbeitsspeicher** der Controller. Ein Serverneustart setzt ihn zurück; einzig die
Teamnamen in `db.json` bleiben erhalten. Teamnamen in `db.json` bleiben erhalten.
Bricht die WebSocket-Verbindung ab — auf dem Pi, der das WLAN selbst aufspannt,
kommt das vor — verbindet sich der Client automatisch neu und holt sich beim
`connect`-Event den aktuellen Stand über die REST-Endpunkte. Ein **Wechsel der
Ansicht** während der Trennung (Splashscreen, Scoreboard ein/aus) wird dabei
nicht bemerkt; dafür ist "Monitor neu laden" gedacht.
### WebSocket-Events ### WebSocket-Events
| Event | Richtung | Beschreibung | | Event | Richtung | Beschreibung |

View file

@ -7,7 +7,6 @@
var app = require('../app'); var app = require('../app');
var debug = require('debug')('scoreboard:server'); var debug = require('debug')('scoreboard:server');
var http = require('http'); var http = require('http');
var io = require('../controllers/socketio');
/** /**
* Get port from environment and store in Express. * Get port from environment and store in Express.
@ -22,12 +21,6 @@ app.set('port', port);
var server = http.createServer(app); var server = http.createServer(app);
/**
* Attach Socket.IO to the same server, so it shares the HTTP port.
*/
io.attach(server);
/** /**
* Listen on provided port, on all network interfaces. * Listen on provided port, on all network interfaces.
*/ */

View file

@ -17,6 +17,16 @@ let teamB = {
let sideswitch = false; // Attribute to switch sides after halftime let sideswitch = false; // Attribute to switch sides after halftime
let referenceMirrored = false; // If sitting behind the scoreboard, switch sides for admin board let referenceMirrored = false; // If sitting behind the scoreboard, switch sides for admin board
// Funktionsübersicht (Prototypen ohne Implementierung — nur zur Übersicht)
function setEnabled(status) {};
function configTeam(team, name, name2, isSpielgemeinschaft) {};
function setScore(team, score) {};
function alterScore(team, dir) {};
function clearScore() {};
function toggleSideswitch() {};
function getEnabled() {};
function getValues() {};
// Enable or disable the scoreboard // Enable or disable the scoreboard
function setEnabled(status) { function setEnabled(status) {
enabled = status; enabled = status;
@ -42,7 +52,6 @@ function setScore(team, score) {
} else if(team == "teamB") { } else if(team == "teamB") {
teamB.score = score; teamB.score = score;
} }
io.sockets.emit('score', print()); // Wie alterScore und clearScore den Monitor mitziehen
} }
// Ändert den Score eines Teams um 1 — Richtung "inc" (erhöhen) oder "dec" (verringern) // Ändert den Score eines Teams um 1 — Richtung "inc" (erhöhen) oder "dec" (verringern)
@ -52,7 +61,7 @@ function alterScore(team, dir) {
teamA.score++; teamA.score++;
console.log("teamA inc"); console.log("teamA inc");
} }
else if(dir == "dec" && teamA.score > 0) { // Kein negativer Spielstand else if(dir == "dec") {
teamA.score--; teamA.score--;
console.log("teamA dec"); console.log("teamA dec");
} }
@ -61,7 +70,7 @@ function alterScore(team, dir) {
teamB.score++; teamB.score++;
console.log("teamB inc"); console.log("teamB inc");
} }
else if(dir == "dec" && teamB.score > 0) { // Kein negativer Spielstand else if(dir == "dec") {
teamB.score--; teamB.score--;
console.log("teamB dec"); console.log("teamB dec");
} }

View file

@ -1,25 +1,12 @@
// Erstellt den zentralen Socket.IO-Server. // Erstellt den zentralen Socket.IO-Server auf Port 3001.
// Wird von timer.js, score.js und den Routen importiert, // Wird von timer.js, score.js und den Routen importiert,
// um Events (Timer, Score, Refresh) an alle verbundenen Clients zu senden. // um Events (Timer, Score, Refresh) an alle verbundenen Clients zu senden.
const { Server } = require('socket.io'); const { Server } = require('socket.io');
// Der Server wird ohne eigenen Port erzeugt und in bin/www per io.attach() an den // CORS auf "*" gesetzt, da Admin und Monitor auf unterschiedlichen Ports laufen können
// HTTP-Server gehaengt. Dieses Modul wird beim Laden der Routen ausgewertet, also const io = new Server(3001, {
// bevor der HTTP-Server existiert — daher die Trennung von Erzeugen und Anhaengen. cors: { origin: "*" }
// Weil Socket.IO damit unter derselben Herkunft laeuft wie die Seite, entfaellt die
// CORS-Ausnahme.
const io = new Server();
// Verbindungs-Handling: der Client meldet sich beim Verbinden selbst mit dem
// Socket.IO-eigenen 'connect'-Event und holt sich den aktuellen Stand ueber die
// REST-Endpunkte. Serverseitig wird die Verbindung nur protokolliert.
io.on('connection', (socket) => {
console.log("A user connected");
socket.on('disconnect', (reason) => {
console.log("A user disconnected: ", reason);
});
}); });
module.exports = io module.exports = io

View file

@ -7,6 +7,15 @@ let durationLeft = moment.duration(7, 'minutes'); // durationLeft after the t
let isPaused = true; // Status of the timer let isPaused = true; // Status of the timer
// Function prototypes
function start() {};
function pause() {};
function reset() {};
function end() {};
function print() {};
function incDec() {};
function getValues() {};
// Start timer not paused and not already finished // Start timer not paused and not already finished
function start() { function start() {
if(isPaused == true && durationLeft.asSeconds() != 0) { // Only allow start if timer ist currently paused and durationLeft is not 0 seconds if(isPaused == true && durationLeft.asSeconds() != 0) { // Only allow start if timer ist currently paused and durationLeft is not 0 seconds
@ -36,9 +45,7 @@ function pause() {
return false return false
} }
// Reset timer to passed value, and send durationLeft to all clients. // Reset timer to passed value, and send durationLeft to all clients
// Ein laufender Timer wird bewusst nicht angehalten, sondern zaehlt von der
// neuen Zeit weiter herunter.
function reset(newDuration) { function reset(newDuration) {
console.log("Timer Zurückgesetzt"); console.log("Timer Zurückgesetzt");
duration = moment.duration(newDuration, 'seconds').clone(); // Set initial duration to received duration in seconds duration = moment.duration(newDuration, 'seconds').clone(); // Set initial duration to received duration in seconds
@ -62,8 +69,7 @@ function print() {
// Increase or decrease the timer depending on the passed value in seconds // Increase or decrease the timer depending on the passed value in seconds
function incDec(value) { function incDec(value) {
if(value < 0 && Math.abs(value) >= durationLeft.asSeconds()) { // Nur beim Verringern: faellt die Restzeit auf 0 oder darunter, Timer beenden if(Math.abs(value) >= durationLeft.asSeconds()) { // If abs from passed value is greater than seconds left on timer, end timer
durationLeft = moment.duration(0, 'seconds'); // Restzeit sauber auf 00:00, sonst bliebe der Timer trotz "ENDE" startbar
end(); end();
} else { } else {
durationLeft.add(value, 'second'); durationLeft.add(value, 'second');
@ -81,9 +87,6 @@ function getValues() {
}; };
} }
// duration, durationLeft und isPaused werden bewusst nicht exportiert: Exporte
// werden beim Laden des Moduls einmal ausgewertet und wuerden dauerhaft die
// Startwerte liefern. Der aktuelle Stand kommt ueber getValues().
module.exports = { module.exports = {
start, pause, reset, end, print, incDec, getValues duration, durationLeft, isPaused, start, pause, reset, end, print, incDec, getValues
} }

View file

@ -1,17 +1,12 @@
// Socket.IO laeuft am selben Host und Port wie die Seite, io() ohne Argument let wss = "ws://" + window.location.hostname + ":3001" // Build socketio endpoint from window.location
// verbindet sich dorthin zurueck. const socket = io(wss); // Connect to socketio server
const socket = io();
// ###################################################################################################################################################### // ######################################################################################################################################################
// Websockets event handler // Websockets event handler
// ###################################################################################################################################################### // ######################################################################################################################################################
// 'connect' feuert bei jeder Verbindung, auch nach einem Reconnect. Da der Pi das socket.on('connected', (message) => { // Once client is connected to websocket
// WLAN selbst aufspannt, sind kurze Abbrueche normal: danach werden die Werte neu console.log("Connected");
// geholt, damit das Panel nicht auf einem veralteten Stand stehen bleibt.
socket.on('connect', () => {
console.log("Verbunden");
initialUpdate();
}); });
socket.on('timerDurationLeft', (message) => { socket.on('timerDurationLeft', (message) => {
@ -33,22 +28,6 @@ socket.on('score', (message) => {
// General functions // General functions
// ###################################################################################################################################################### // ######################################################################################################################################################
// Holt JSON von einem Endpunkt. Liefert null statt zu werfen, wenn der Server nicht
// erreichbar ist oder mit einem Fehler antwortet — sonst bricht der Aufrufer still ab.
async function fetchJson(url, options) {
try {
const response = await fetch(url, options);
if(!response.ok) {
console.error("Anfrage fehlgeschlagen:", url, response.status);
return null;
}
return await response.json();
} catch (err) {
console.error("Server nicht erreichbar:", url, err);
return null;
}
}
// Initial update gets called whenever page has been loaded // Initial update gets called whenever page has been loaded
async function initialUpdate() { async function initialUpdate() {
etcGetValues(); // Request new values for etc stuff etcGetValues(); // Request new values for etc stuff
@ -125,19 +104,6 @@ function updateScoreFrontend(values) {
} }
// Erzeugt einen Dropdown-Eintrag, der den Teamnamen in das angegebene Feld uebernimmt.
// Der Name wird als Text gesetzt und per Closure an den Handler gegeben, statt ihn in
// einen onclick-String zu schreiben: Namen mit Apostroph zerlegten sonst den Handler.
function createTeamDropdownItem(field, team) {
const li = document.createElement("li");
const a = document.createElement("a");
a.className = "dropdown-item";
a.textContent = team;
a.addEventListener("click", () => scoreInsertChosenName(field, team));
li.appendChild(a);
return li;
}
// Update DOM for all db contents with the passed values // Update DOM for all db contents with the passed values
function updateDbFrontend(values) { function updateDbFrontend(values) {
console.log(values); console.log(values);
@ -156,17 +122,27 @@ function updateDbFrontend(values) {
let selectDelete = document.getElementById("dbTeamToDelete"); // Select for team deletion from db let selectDelete = document.getElementById("dbTeamToDelete"); // Select for team deletion from db
selectDelete.innerHTML = "" // Clears all existing options from select selectDelete.innerHTML = "" // Clears all existing options from select
for(const team of values.teams) { // <li><a class="dropdown-item" onclick="scoreInsertChosenName">Action</a></li>
for(team of values.teams) {
// Create custom Dropdowns with different function arguments for team configuration // Create custom Dropdowns with different function arguments for team configuration
teamAnameDropdown.appendChild(createTeamDropdownItem("teamAname", team)); var li = document.createElement("li");
teamAname2Dropdown.appendChild(createTeamDropdownItem("teamAname2", team)); li.innerHTML = `<a class="dropdown-item" onclick="scoreInsertChosenName('teamAname','` + team +`')"> `+ team +`</a>`;
teamBnameDropdown.appendChild(createTeamDropdownItem("teamBname", team)); teamAnameDropdown.appendChild(li);
teamBname2Dropdown.appendChild(createTeamDropdownItem("teamBname2", team)); var li = document.createElement("li");
li.innerHTML = `<a class="dropdown-item" onclick="scoreInsertChosenName('teamAname2','` + team +`')"> `+ team +`</a>`;
teamAname2Dropdown.appendChild(li);
var li = document.createElement("li");
li.innerHTML = `<a class="dropdown-item" onclick="scoreInsertChosenName('teamBname','` + team +`')"> `+ team +`</a>`;
teamBnameDropdown.appendChild(li);
var li = document.createElement("li");
li.innerHTML = `<a class="dropdown-item" onclick="scoreInsertChosenName('teamBname2','` + team +`')"> `+ team +`</a>`;
teamBname2Dropdown.appendChild(li);
// Create select options for deletion in debugwindow // Create select options for deletion in debugwindow
let opt = document.createElement("option"); let opt = document.createElement("option");
opt.value = team; opt.value = team;
opt.textContent = team; opt.innerHTML = team;
selectDelete.appendChild(opt); selectDelete.appendChild(opt);
} }
} }
@ -208,14 +184,16 @@ async function killBrowser() {
// Request important values for etc stuff // Request important values for etc stuff
async function etcGetValues() { async function etcGetValues() {
const data = await fetchJson("/admin/etcGetValues"); // Call API Endpoint /admin/etcGetValues const response = await fetch("/admin/etcGetValues"); // Call API Endpoint /admin/etcGetValues
if(data) updateEtcFrontend(data); // Update admin frontend const data = await response.json(); // Wait for asynchronous transfer to complete
updateEtcFrontend(data); // Update admin frontend
} }
// Toggle index page to also show scoreboard // Toggle index page to also show scoreboard
async function etcToggleSplashscreen() { async function etcToggleSplashscreen() {
const data = await fetchJson("/admin/etcToggleSplashscreen");// Call API Endpoint /admin/etcToggleSplashscreen const response = await fetch("/admin/etcToggleSplashscreen");// Call API Endpoint /admin/etcToggleSplashscreen
if(data) updateEtcFrontend(data); // Update admin frontend const data = await response.json(); // Wait for asyncronous transfer to complete
updateEtcFrontend(data); // Update admin frontend
refreshMonitor(); // Refresh main monitor to display new frontend refreshMonitor(); // Refresh main monitor to display new frontend
} }
@ -225,8 +203,9 @@ async function etcToggleSplashscreen() {
// Request important timer values // Request important timer values
async function timerGetValues() { async function timerGetValues() {
const data = await fetchJson("/admin/timerGetValues"); // Call API Endpoint /admin/timerGetValues const response = await fetch("/admin/timerGetValues"); // Call API Endpoint /admin/timerGetValues
if(data) updateTimerFrontend(data); // Update admin frontend const data = await response.json(); // Wait for asynchronous transfer to complete
updateTimerFrontend(data); // Update admin frontend
} }
// Request to start the timer // Request to start the timer
@ -256,22 +235,24 @@ async function timerPause() {
// Request to reset the timer with the specified time // Request to reset the timer with the specified time
async function timerReset() { async function timerReset() {
let newDuration = document.getElementById("timerSelectDuration").value; let newDuration = document.getElementById("timerSelectDuration").value;
const data = await fetchJson("/admin/timerReset", { // Call API Endpoint /admin/timerStart with selected new duration in seconds const response = await fetch("/admin/timerReset", { // Call API Endpoint /admin/timerStart with selected new duration in seconds
method: 'POST', method: 'POST',
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({duration: newDuration}) body: JSON.stringify({duration: newDuration})
}); });
if(data) updateTimerFrontend(data); // Update admin frontend const data = await response.json(); // Wait for asyncronous transfer to complete
updateTimerFrontend(data); // Update admin frontend
} }
// Request to increase or decrease the timer depending on passed value in seconds // Request to increase or decrease the timer depending on passed value in seconds
async function timerIncDec(value) { async function timerIncDec(value) {
const data = await fetchJson("/admin/timerIncDec", { // Call API Endpoint /admin/timerIncDec with value in seconds to increase or decrease the timer const response = await fetch("/admin/timerIncDec", { // Call API Endpoint /admin/timerIncDec with value in seconds to increase or decrease the timer
method: 'POST', method: 'POST',
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({value: value}) body: JSON.stringify({value: value})
}); });
if(data) updateTimerFrontend(data); // Update admin frontend const data = await response.json(); // Wait for asyncronous transfer to complete
updateTimerFrontend(data); // Update admin frontend
} }
// ###################################################################################################################################################### // ######################################################################################################################################################
@ -280,43 +261,49 @@ async function timerIncDec(value) {
// Request important values for score // Request important values for score
async function scoreGetValues() { async function scoreGetValues() {
const data = await fetchJson("/admin/scoreGetValues"); // Call API Endpoint /admin/scoreGetValues const response = await fetch("/admin/scoreGetValues"); // Call API Endpoint /admin/scoreGetValues
if(data) updateScoreFrontend(data); // Update admin frontend const data = await response.json(); // Wait for asynchronous transfer to complete
updateScoreFrontend(data); // Update admin frontend
} }
// Toggle index page to also show scoreboard // Toggle index page to also show scoreboard
async function scoreToggle() { async function scoreToggle() {
const data = await fetchJson("/admin/scoreToggle"); // Call API Endpoint /admin/scoreToggle const response = await fetch("/admin/scoreToggle"); // Call API Endpoint /admin/scoreToggle
if(data) updateScoreFrontend(data); // Update admin frontend const data = await response.json(); // Wait for asyncronous transfer to complete
updateScoreFrontend(data); // Update admin frontend
refreshMonitor(); // Refresh main monitor to display new frontend refreshMonitor(); // Refresh main monitor to display new frontend
} }
// Alter the score of passed team in the specified direction // Alter the score of passed team in the specified direction
async function scoreAlterScore(team, dir) { async function scoreAlterScore(team, dir) {
const data = await fetchJson("/admin/scoreAlterScore", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction const response = await fetch("/admin/scoreAlterScore", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction
method: 'POST', method: 'POST',
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({team: team, dir: dir}) body: JSON.stringify({team: team, dir: dir})
}); });
if(data) updateScoreFrontend(data); // Update admin frontend const data = await response.json(); // Wait for asyncronous transfer to complete
updateScoreFrontend(data); // Update admin frontend
} }
// Switch sides // Switch sides
async function scoreToggleSideswitch() { async function scoreToggleSideswitch() {
const data = await fetchJson("/admin/scoreToggleSideswitch");// Call API Endpoint /admin/scoreSwitch const response = await fetch("/admin/scoreToggleSideswitch");// Call API Endpoint /admin/scoreSwitch
if(data) updateScoreFrontend(data); // Update admin frontend const data = await response.json(); // Wait for asyncronous transfer to complete
updateScoreFrontend(data); // Update admin frontend
} }
// Switch sides to mirror admin panel when sitting behind main monitor // Switch sides to mirror admin panel when sitting behind main monitor
async function scoreToggleReferenceMirrored() { async function scoreToggleReferenceMirrored() {
const data = await fetchJson("/admin/scoreToggleReferenceMirrored");// Call API Endpoint /admin/scoreSwitch const response = await fetch("/admin/scoreToggleReferenceMirrored");// Call API Endpoint /admin/scoreSwitch
if(data) updateScoreFrontend(data); // Update admin frontend const data = await response.json(); // Wait for asyncronous transfer to complete
updateScoreFrontend(data); // Update admin frontend
} }
// Clear score // Clear score
async function scoreClearScore() { async function scoreClearScore() {
const data = await fetchJson("/admin/scoreClearScore"); // Call API Endpoint /admin/scoreClearScore const response = await fetch("/admin/scoreClearScore"); // Call API Endpoint /admin/scoreClearScore
if(data) updateScoreFrontend(data); // Update admin frontend const data = await response.json(); // Wait for asynchronous transfer to complete
updateScoreFrontend(data); // Update admin frontend
} }
// Configure Teams // Configure Teams
@ -333,12 +320,13 @@ async function scoreConfigTeams() {
} }
console.log(teamA, teamB) console.log(teamA, teamB)
const data = await fetchJson("/admin/scoreConfigTeams", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction const response = await fetch("/admin/scoreConfigTeams", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction
method: 'POST', method: 'POST',
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({teamA: teamA, teamB: teamB}) body: JSON.stringify({teamA: teamA, teamB: teamB})
}); });
if(data) updateScoreFrontend(data); // Update admin frontend const data = await response.json(); // Wait for asyncronous transfer to complete
updateScoreFrontend(data); // Update admin frontend
refreshMonitor(); refreshMonitor();
} }
@ -348,28 +336,31 @@ async function scoreConfigTeams() {
// Request important values for db contents // Request important values for db contents
async function dbGetValues() { async function dbGetValues() {
const data = await fetchJson("/admin/dbGetValues"); // Call API Endpoint /admin/dbGetValues const response = await fetch("/admin/dbGetValues"); // Call API Endpoint /admin/dbGetValues
if(data) updateDbFrontend(data); // Update admin frontend const data = await response.json(); // Wait for asynchronous transfer to complete
updateDbFrontend(data); // Update admin frontend
} }
// Add team to DB // Add team to DB
async function dbAddTeam() { async function dbAddTeam() {
const data = await fetchJson("/admin/dbAddTeam", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction const response = await fetch("/admin/dbAddTeam", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction
method: 'POST', method: 'POST',
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({teamName: document.getElementById("dbTeamToAdd").value}) body: JSON.stringify({teamName: document.getElementById("dbTeamToAdd").value})
}); });
document.getElementById("dbTeamToAdd").value = ""; // Empty the text field after sending document.getElementById("dbTeamToAdd").value = ""; // Empty the text field after sending
if(data) updateDbFrontend(data); const data = await response.json(); // Wait for asyncronous transfer to complete
updateDbFrontend(data);
} }
// Delete team from DB // Delete team from DB
async function dbDeleteTeam() { async function dbDeleteTeam() {
let teamName = document.getElementById("dbTeamToDelete").value; let teamName = document.getElementById("dbTeamToDelete").value;
const data = await fetchJson("/admin/dbDeleteTeam", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction const response = await fetch("/admin/dbDeleteTeam", { // Call API Endpoint /admin/scoreAlterScore with the teamname to alter the score in the specified direction
method: 'POST', method: 'POST',
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({teamName: teamName}) body: JSON.stringify({teamName: teamName})
}); });
if(data) updateDbFrontend(data); const data = await response.json(); // Wait for asyncronous transfer to complete
updateDbFrontend(data);
} }

View file

@ -1,17 +1,12 @@
// Socket.IO laeuft am selben Host und Port wie die Seite, io() ohne Argument let wss = "ws://" + window.location.hostname + ":3001" // Build socketio endpoint from window.location
// verbindet sich dorthin zurueck. const socket = io(wss); // Connect to socketio server
const socket = io();
// ###################################################################################################################################################### // ######################################################################################################################################################
// Websockets event handler // Websockets event handler
// ###################################################################################################################################################### // ######################################################################################################################################################
// 'connect' feuert bei jeder Verbindung, auch nach einem Reconnect. Da der Pi das WLAN socket.on('connected', (message) => {
// selbst aufspannt, sind kurze Abbrueche normal: danach werden Timer und Spielstand neu console.log("Connected");
// geholt, damit die Anzeige nicht auf einem veralteten Stand haengen bleibt.
socket.on('connect', () => {
console.log("Verbunden");
initialUpdate();
}); });
socket.on('timerDurationLeft', (message) => { socket.on('timerDurationLeft', (message) => {
@ -31,22 +26,6 @@ socket.on('refresh', (message) => {
// General functions // General functions
// ###################################################################################################################################################### // ######################################################################################################################################################
// Holt JSON von einem Endpunkt. Liefert null statt zu werfen, wenn der Server nicht
// erreichbar ist oder mit einem Fehler antwortet.
async function fetchJson(url) {
try {
const response = await fetch(url);
if(!response.ok) {
console.error("Anfrage fehlgeschlagen:", url, response.status);
return null;
}
return await response.json();
} catch (err) {
console.error("Server nicht erreichbar:", url, err);
return null;
}
}
// Update DOM timer elements from passed values // Update DOM timer elements from passed values
function updateTimerFrontend(values) { function updateTimerFrontend(values) {
document.getElementById("durationLeft").innerHTML = values.print; // Display durationLeft as prettyfied string document.getElementById("durationLeft").innerHTML = values.print; // Display durationLeft as prettyfied string
@ -63,6 +42,7 @@ async function initialUpdate() {
// Request important timer values // Request important timer values
async function timerGetValues() { async function timerGetValues() {
const data = await fetchJson("/admin/timerGetValues"); // Call API Endpoint /admin/timerGetValues const response = await fetch("/admin/timerGetValues"); // Call API Endpoint /admin/timerGetValues
if(data) updateTimerFrontend(data); // Update admin frontend const data = await response.json(); // Wait for asynchronous transfer to complete
updateTimerFrontend(data); // Update admin frontend
} }

View file

@ -1,17 +1,12 @@
// Socket.IO laeuft am selben Host und Port wie die Seite, io() ohne Argument let wss = "ws://" + window.location.hostname + ":3001" // Build socketio endpoint from window.location
// verbindet sich dorthin zurueck. const socket = io(wss); // Connect to socketio server
const socket = io();
// ###################################################################################################################################################### // ######################################################################################################################################################
// Websockets event handler // Websockets event handler
// ###################################################################################################################################################### // ######################################################################################################################################################
// 'connect' feuert bei jeder Verbindung, auch nach einem Reconnect. Da der Pi das WLAN socket.on('connected', (message) => {
// selbst aufspannt, sind kurze Abbrueche normal: danach werden Timer und Spielstand neu console.log("Connected");
// geholt, damit die Anzeige nicht auf einem veralteten Stand haengen bleibt.
socket.on('connect', () => {
console.log("Verbunden");
initialUpdate();
}); });
socket.on('timerDurationLeft', (message) => { socket.on('timerDurationLeft', (message) => {
@ -40,22 +35,6 @@ socket.on('scoreSideswitch', (message) => {
// General functions // General functions
// ###################################################################################################################################################### // ######################################################################################################################################################
// Holt JSON von einem Endpunkt. Liefert null statt zu werfen, wenn der Server nicht
// erreichbar ist oder mit einem Fehler antwortet.
async function fetchJson(url) {
try {
const response = await fetch(url);
if(!response.ok) {
console.error("Anfrage fehlgeschlagen:", url, response.status);
return null;
}
return await response.json();
} catch (err) {
console.error("Server nicht erreichbar:", url, err);
return null;
}
}
// Update DOM timer elements from passed values // Update DOM timer elements from passed values
function updateTimerFrontend(values) { function updateTimerFrontend(values) {
document.getElementById("durationLeft").innerHTML = values.print; // Display durationLeft as prettyfied string document.getElementById("durationLeft").innerHTML = values.print; // Display durationLeft as prettyfied string
@ -108,8 +87,9 @@ async function initialUpdate() {
// Request important timer values // Request important timer values
async function timerGetValues() { async function timerGetValues() {
const data = await fetchJson("/admin/timerGetValues"); // Call API Endpoint /admin/timerGetValues const response = await fetch("/admin/timerGetValues"); // Call API Endpoint /admin/timerGetValues
if(data) updateTimerFrontend(data); // Update admin frontend const data = await response.json(); // Wait for asynchronous transfer to complete
updateTimerFrontend(data); // Update admin frontend
} }
// ###################################################################################################################################################### // ######################################################################################################################################################
@ -118,7 +98,8 @@ async function timerGetValues() {
// Request important values for score // Request important values for score
async function scoreGetValues() { async function scoreGetValues() {
const data = await fetchJson("/admin/scoreGetValues"); // Call API Endpoint /admin/scoreGetValues const response = await fetch("/admin/scoreGetValues"); // Call API Endpoint /admin/scoreGetValues
const data = await response.json(); // Wait for asynchronous transfer to complete and parse json (which got received by backend)
console.log(data); // Print received data console.log(data); // Print received data
if(data) updateScoreFrontend(data); // Update admin frontend with received values for scoreboard updateScoreFrontend(data); // Update admin frontend with received values for scoreboard
} }

View file

@ -1,11 +1,14 @@
// Socket.IO laeuft am selben Host und Port wie die Seite, io() ohne Argument let wss = "ws://" + window.location.hostname + ":3001" // Build socketio endpoint from window.location
// verbindet sich dorthin zurueck. const socket = io(wss); // Connect to socketio server
const socket = io();
// ###################################################################################################################################################### // ######################################################################################################################################################
// Websockets event handler // Websockets event handler
// ###################################################################################################################################################### // ######################################################################################################################################################
socket.on('connected', (message) => {
console.log("Connected");
});
socket.on('refresh', (message) => { socket.on('refresh', (message) => {
document.location.reload() // Reload page on received 'refresh' messagen document.location.reload() // Reload page on received 'refresh' messagen
}); });

View file

@ -1,5 +1,6 @@
var express = require('express'); var express = require('express');
var router = express.Router(); var router = express.Router();
let io = require('../controllers/socketio');
const score = require('../controllers/score'); const score = require('../controllers/score');
const etc = require('../controllers/etc'); const etc = require('../controllers/etc');
@ -17,4 +18,15 @@ router.get('/', function(req, res, next) {
} }
}); });
// Websocket-Verbindungshandler: wird ausgelöst, sobald ein Client (Monitor) sich verbindet
io.on('connection', (socket) => {
console.log("A user connected");
socket.emit("Hello user from server"); // Begrüßungsnachricht an den neuen Client
// Eingehende Nachrichten vom Client loggen (aktuell nur für Debugging)
socket.on('message', (message) => {
console.log(message)
})
})
module.exports = router; module.exports = router;

View file

@ -298,7 +298,7 @@
<p>Achtung, eine Fehlbedienung kann unerwünschtes Verhalten hervorrufen.</p> <p>Achtung, eine Fehlbedienung kann unerwünschtes Verhalten hervorrufen.</p>
<div class="form-check form-switch"> <div class="form-check form-switch">
<input class="form-check-input" onclick="scoreToggleReferenceMirrored()" type="checkbox" role="switch" id="scoreSwitchReferenceMirrored"> <input class="form-check-input" onclick="scoreToggleReferenceMirrored()" type="checkbox" role="switch" id="scoreSwitchReferenceMirrored">
<label class="form-check-label" for="scoreSwitchReferenceMirrored">Teamanzeige im Adminpanel spiegeln?</label> <label class="form-check-label" for="scoreSwitchEnable">Teamanzeige im Adminpanel spiegeln?</label>
</div> </div>
<div class="form-check form-switch"> <div class="form-check form-switch">
<input class="form-check-input" onclick="scoreToggle()" type="checkbox" role="switch" id="scoreSwitchEnable"> <input class="form-check-input" onclick="scoreToggle()" type="checkbox" role="switch" id="scoreSwitchEnable">
@ -306,7 +306,7 @@
</div> </div>
<div class="form-check form-switch"> <div class="form-check form-switch">
<input class="form-check-input" onclick="etcToggleSplashscreen()" type="checkbox" role="switch" id="etcSwitchEnable"> <input class="form-check-input" onclick="etcToggleSplashscreen()" type="checkbox" role="switch" id="etcSwitchEnable">
<label class="form-check-label" for="etcSwitchEnable">SG-Arheilgen Splashscreen anzeigen?</label> <label class="form-check-label" for="scoreSwitchEnable">SG-Arheilgen Splashscreen anzeigen?</label>
</div> </div>
<button class="mb-2 btn btn-lg btn-warning" onclick="refreshMonitor()"><i class="fa-solid fa-rotate"></i> Monitor neu laden</button> <br> <button class="mb-2 btn btn-lg btn-warning" onclick="refreshMonitor()"><i class="fa-solid fa-rotate"></i> Monitor neu laden</button> <br>
<button class="mb-2 btn btn-lg btn-success" onclick="openBrowser()"><i class="fa-solid fa-rocket"></i> Browser öffnen</button> <br> <button class="mb-2 btn btn-lg btn-success" onclick="openBrowser()"><i class="fa-solid fa-rocket"></i> Browser öffnen</button> <br>
@ -316,7 +316,7 @@
<p class="fw-bold">Team Datenbank:</p> <p class="fw-bold">Team Datenbank:</p>
<div class="input-group mb-3"> <div class="input-group mb-3">
<span class="input-group-text"><i class="fa-solid fa-user-plus"></i></span> <span class="input-group-text"><i class="fa-solid fa-user-plus"></i></span>
<input type="text" class="form-control" id="dbTeamToAdd"> <input type="text" class="form-control" id="dbTeamToAdd" aria-describedby="teamBnameHelp">
<button class="btn btn-outline-success" type="button" onclick="dbAddTeam()"><i class="fa-solid fa-plus"></i></button> <button class="btn btn-outline-success" type="button" onclick="dbAddTeam()"><i class="fa-solid fa-plus"></i></button>
</div> </div>
<div class="input-group mb-3"> <div class="input-group mb-3">