diff --git a/app.py b/app.py index 4dcf2cc..16b1678 100644 --- a/app.py +++ b/app.py @@ -3,10 +3,16 @@ import os import json import logging import shutil +import threading from logging.handlers import TimedRotatingFileHandler from datetime import datetime, timedelta from flask import Flask, render_template, redirect, url_for, send_from_directory, request, jsonify +import pygame +# pygame einmalig initialisieren (am besten global) +pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=512) +pygame.mixer.music.set_volume(0.8) # Standard 80 % + # Logging-Verzeichnis (muss VOR cleanup_old_log_dirs definiert werden!) LOG_DIR = 'logs' os.makedirs(LOG_DIR, exist_ok=True) @@ -540,12 +546,6 @@ def api_reconnect(): return jsonify({"success": False, "message": str(e)}), 500 -import pygame -import threading - -# pygame einmalig initialisieren (am besten global) -pygame.mixer.init() - @app.route('/api/play_sound', methods=['POST']) def api_play_sound(): try: @@ -588,6 +588,31 @@ def api_play_sound(): return jsonify({"success": False, "message": str(e)}), 500 +@app.route('/api/set_volume', methods=['POST']) +def api_set_volume(): + try: + data = request.get_json() + vol = float(data.get('volume', 0.8)) + vol = max(0.0, min(1.0, vol)) + pygame.mixer.music.set_volume(vol) + logger.info(f"Volume auf {vol} gesetzt") + return jsonify({"success": True}) + except Exception as e: + logger.error(f"Volume-Fehler: {e}") + return jsonify({"success": False, "message": str(e)}), 500 + + +@app.route('/api/stop_sound', methods=['POST']) +def api_stop_sound(): + try: + pygame.mixer.music.stop() + logger.info("Aktueller Sound gestoppt") + return jsonify({"success": True}) + except Exception as e: + logger.error(f"Stop-Sound-Fehler: {e}") + return jsonify({"success": False, "message": str(e)}), 500 + + if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True) diff --git a/configs/sounds.json b/configs/sounds.json index 782bb5d..1412eef 100644 --- a/configs/sounds.json +++ b/configs/sounds.json @@ -1,22 +1,16 @@ { "global_sounds": [ { - "id": "pfeife_lang", - "file": "pfeife_lang.wav", - "name": "Pfeife lang", - "description": "Langgezogene Dampflok-Pfeife" + "id": "dampflok-fahrt", + "file": "Dampflok-mit-Waggons-schnauft-heran-und-rollt-vorüber.mp3", + "name": "Dampflok Ankunft und Vorbeifahrt", + "description": "Dampflok mit Waggons schnauft heran und rollt vorüber" }, { - "id": "dampf_ausstoß", - "file": "dampf_ausstoß.mp3", - "name": "Dampfausstoß", - "description": "Kurzes Zischen" - }, - { - "id": "bahnhof_ankunft", - "file": "bahnhof_ankunft.mp3", - "name": "Bahnhofsankunft", - "description": "Stationsansage + Bremsquietschen" + "id": "wind_western1", + "file": "freesound_community-wind-western-64661.mp3", + "name": "Wind Western", + "description": "einfache Windgeräusche" } ] } \ No newline at end of file diff --git a/sounds/ Dampflok mit Waggons schnauft heran und rollt vorüber.mp3 b/sounds/Dampflok-mit-Waggons-schnauft-heran-und-rollt-vorüber.mp3 similarity index 100% rename from sounds/ Dampflok mit Waggons schnauft heran und rollt vorüber.mp3 rename to sounds/Dampflok-mit-Waggons-schnauft-heran-und-rollt-vorüber.mp3 diff --git a/sounds/freesound_community-wind-western-64661.mp3 b/sounds/freesound_community-wind-western-64661.mp3 new file mode 100644 index 0000000..93cf338 Binary files /dev/null and b/sounds/freesound_community-wind-western-64661.mp3 differ diff --git a/static/js/app.js b/static/js/app.js index 262620e..34242df 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -11,6 +11,9 @@ document.addEventListener('DOMContentLoaded', () => { const stopAllBtn = document.getElementById('stop-all-btn'); const reconnectSection = document.getElementById('reconnect-section'); const reconnectBtn = document.getElementById('reconnect-btn'); + const statusBadge = document.getElementById('connection-status'); + const volumeSlider = document.getElementById('volume-slider'); + const volumeDisplay = document.getElementById('volume-display'); if (!connectBtn) { console.warn('Nicht auf der Steuerseite – Connect-Button nicht gefunden'); @@ -18,8 +21,68 @@ document.addEventListener('DOMContentLoaded', () => { } // ── Config aus Template ──────────────────────────────────────────────────── - const config = window.mkConfig || {}; - console.log("app.js verwendet config:", config); + const config = {{ config | tojson | safe }}; + const globalSounds = {{ global_sounds | tojson | safe }}; + window.mkConfig = config; // global für Konsistenz + + console.log("Config im JS:", config); + console.log("Globale Sounds:", globalSounds); + + // ── Status-Anzeige ───────────────────────────────────────────────────────── + function updateStatus(connected, message = '') { + if (!statusBadge) return; + + if (connected) { + statusBadge.className = 'badge bg-success px-3 py-2 fs-6'; + statusBadge.innerHTML = ' Verbunden' + (message ? ` – ${message}` : ''); + if (reconnectSection) reconnectSection.style.display = 'none'; + } else { + statusBadge.className = 'badge bg-danger px-3 py-2 fs-6'; + statusBadge.innerHTML = ' Getrennt' + (message ? ` – ${message}` : ''); + if (reconnectSection) reconnectSection.style.display = 'block'; + } + } + + // Initialer Status + updateStatus(false); + + // ── Automatische Verbindungsprüfung ──────────────────────────────────────── + let connectionCheckInterval = null; + let failedChecks = 0; + const MAX_FAILED_CHECKS = 3; + + function startConnectionCheck() { + if (connectionCheckInterval) clearInterval(connectionCheckInterval); + + connectionCheckInterval = setInterval(async () => { + try { + console.log("→ Status-Check ..."); + const res = await fetch('/api/status'); + const data = await res.json(); + + if (data.connected) { + failedChecks = 0; + updateStatus(true); + } else { + failedChecks++; + console.warn(`Status-Check fehlgeschlagen (${failedChecks}/${MAX_FAILED_CHECKS}):`, data.message); + if (failedChecks >= MAX_FAILED_CHECKS) { + updateStatus(false, data.message || 'Mehrere Checks fehlgeschlagen'); + } + } + } catch (err) { + failedChecks++; + console.warn(`Status-Check Netzwerkfehler (${failedChecks}/${MAX_FAILED_CHECKS}):`, err); + if (failedChecks >= MAX_FAILED_CHECKS) { + updateStatus(false, 'Keine Antwort vom Hub/Server'); + } + } + }, 6000); // 6 Sekunden + } + + window.addEventListener('beforeunload', () => { + if (connectionCheckInterval) clearInterval(connectionCheckInterval); + }); // ── Connect-Button ──────────────────────────────────────────────────────── connectBtn.addEventListener('click', async () => { @@ -43,16 +106,13 @@ document.addEventListener('DOMContentLoaded', () => { controlSection.style.display = 'block'; reconnectSection.style.display = 'none'; - // Status aktualisieren updateStatus(true); + startConnectionCheck(); console.log("→ Rufe renderChannels() auf"); renderChannels(); console.log("→ renderChannels() abgeschlossen"); - - // Automatische Prüfung starten - startConnectionCheck(); } else { console.warn("→ Connect fehlgeschlagen:", result.message); alert('Verbindung fehlgeschlagen:\n' + (result.message || 'Unbekannter Fehler')); @@ -179,7 +239,6 @@ document.addEventListener('DOMContentLoaded', () => { `; } else { - // Licht, Sound, Fogger etc. → Toggle controlHTML = ` - {% endfor %} + +
+ + +
+ 80 %
- {% endif %} +
- - {% if global_sounds and global_sounds|length > 0 %} -
Standard-Sounds
-
- {% for sound in global_sounds %} - - {% endfor %} + + {% endif %} + + + +
+ + {% if config.sounds and config.sounds|length > 0 %} +
+
+ {% for sound in config.sounds %} +
+ + +
+ {% endfor %} +
+
+ {% endif %} + + +
+
+ {% for sound in global_sounds %} +
+ + +
+ {% endfor %} +
- {% endif %} +
{% if (not config.sounds or config.sounds|length == 0) and (not global_sounds or global_sounds|length == 0) %}

Keine Sounds konfiguriert

@@ -134,5 +171,8 @@ const config = {{ config | tojson | safe }}; const globalSounds = {{ global_sounds | tojson | safe }}; window.mkConfig = config; // falls du es global brauchst + + console.log("Config im JS:", config); + console.log("Globale Sounds:", globalSounds); {% endblock %} \ No newline at end of file