From 337e12078b95adc8f5b93a0b764eec19a112fe3f Mon Sep 17 00:00:00 2001 From: Carsten Graf Date: Fri, 11 Jul 2025 22:23:23 +0200 Subject: [PATCH] =?UTF-8?q?Batteriebanner=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/index.css | 237 ++++++++++++---------- data/index.html | 481 ++++++++++++++++++++++++++++---------------- src/communication.h | 5 +- 3 files changed, 439 insertions(+), 284 deletions(-) diff --git a/data/index.css b/data/index.css index f93ad53..5f81f05 100644 --- a/data/index.css +++ b/data/index.css @@ -79,7 +79,7 @@ html { transform: scale(1.1); } - .heartbeat-indicators { + .heartbeat-indicators { position: fixed; top: 20px; right: 90px; @@ -119,6 +119,134 @@ html { box-shadow: 0 0 10px rgba(46, 204, 113, 0.5); } + /* Batterie-Banner Styling */ + .battery-banner { + position: fixed; + top: 0; + left: 0; + width: 100%; + background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%); + color: white; + padding: 15px 20px; + text-align: center; + z-index: 2000; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); + border-bottom: 3px solid rgba(255, 255, 255, 0.2); + animation: batteryPulse 2s infinite; + transform: translateY(-100%); + transition: transform 0.5s ease; + } + + .battery-banner.show { + transform: translateY(0); + } + + .battery-banner .banner-content { + display: flex; + align-items: center; + justify-content: center; + gap: 15px; + flex-wrap: wrap; + } + + .battery-banner .banner-icon { + font-size: 2rem; + animation: batteryBlink 1.5s infinite; + } + + .battery-banner .banner-text { + font-size: 1.1rem; + font-weight: bold; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); + } + + .battery-banner .banner-devices { + font-size: 0.9rem; + opacity: 0.9; + margin-top: 5px; + } + + .battery-banner .close-btn { + position: absolute; + top: 50%; + right: 20px; + transform: translateY(-50%); + background: rgba(255, 255, 255, 0.2); + border: 2px solid rgba(255, 255, 255, 0.3); + color: white; + width: 35px; + height: 35px; + border-radius: 50%; + cursor: pointer; + font-size: 1.2rem; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + } + + .battery-banner .close-btn:hover { + background: rgba(255, 255, 255, 0.3); + border-color: rgba(255, 255, 255, 0.5); + transform: translateY(-50%) scale(1.1); + } + + @keyframes batteryPulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.8; + } + } + + @keyframes batteryBlink { + 0%, 50% { + opacity: 1; + } + 51%, 100% { + opacity: 0.5; + } + } + + /* Anpassung für mobile Geräte */ + @media (max-width: 768px) { + .battery-banner { + padding: 12px 15px; + } + + .battery-banner .banner-content { + gap: 10px; + } + + .battery-banner .banner-icon { + font-size: 1.5rem; + } + + .battery-banner .banner-text { + font-size: 1rem; + } + + .battery-banner .close-btn { + width: 30px; + height: 30px; + font-size: 1rem; + right: 15px; + } + } + + /* Hauptcontent nach unten verschieben wenn Banner sichtbar */ + .content-shifted { + margin-top: 80px; + transition: margin-top 0.5s ease; + } + + @media (max-width: 768px) { + .content-shifted { + margin-top: 70px; + } + } + .header { text-align: center; margin-bottom: 2vh; @@ -154,20 +282,6 @@ html { } } - @media (max-width: 480px) { - .timer-container { - gap: 20px; - padding: 0 10px; - } - } - - @media (min-width: 1400px) { - .timer-container { - max-width: 1400px; - padding: 0 60px; - } - } - .lane { background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); @@ -319,97 +433,4 @@ html { .learning-mode p { font-size: clamp(0.9rem, 1.8vw, 1.1rem); - } - - /* Responsive Logo Anpassungen für kleine Bildschirme */ - @media (max-width: 768px) { - .logo { - position: fixed; - top: 10px; - left: 50%; - transform: translateX(-50%); - z-index: 1001; - } - - .header { - margin-top: 60px; /* Platz für das Logo schaffen */ - margin-bottom: 1.5vh; - } - - .settings-btn { - top: 10px; - right: 10px; - width: 40px; - height: 40px; - font-size: 1rem; - padding: 8px; - } - - .timer-container { - grid-template-columns: 1fr; - gap: clamp(10px, 2vh, 15px); - padding: 0 3vw; - max-height: 50vh; /* Reduziert wegen des zusätzlichen Platzes oben */ - } - - .header h1 { - font-size: clamp(1.5rem, 4vw, 2rem); - } - - .header p { - font-size: clamp(0.7rem, 2vw, 0.9rem); - } - - body { - padding: 10px; - } - - .swimmer-name { - font-size: clamp(1.2rem, 4vw, 1.8rem); - margin-bottom: clamp(10px, 1.5vh, 20px); - } - } - - @media (max-width: 480px) { - .logo { - top: 8px; - } - - .header { - margin-top: 65px; /* Etwas mehr Platz auf sehr kleinen Bildschirmen */ - } - - .settings-btn { - top: 8px; - right: 8px; - width: 35px; - height: 35px; - font-size: 0.9rem; - padding: 6px; - } - - body { - padding: 8px; - } - - .timer-container { - padding: 0 2vw; - max-height: 45vh; - } - - .swimmer-name { - font-size: clamp(1rem, 4vw, 1.5rem); - padding: clamp(6px, 1vh, 10px) clamp(8px, 1.5vw, 12px); - } - } - - /* Für sehr kleine Bildschirme (iPhone SE, etc.) */ - @media (max-width: 375px) { - .header { - margin-top: 70px; - } - - .timer-container { - max-height: 40vh; - } } \ No newline at end of file diff --git a/data/index.html b/data/index.html index 92c877b..37d254d 100644 --- a/data/index.html +++ b/data/index.html @@ -9,12 +9,24 @@ - - + +
+ + +
+ ⚙️ -
+
@@ -62,199 +74,322 @@
+ // Lernmodus + const learningDisplay = document.getElementById("learning-display"); + if (learningMode) { + document.getElementById("learning-button").textContent = learningButton; + learningDisplay.style.display = "block"; + } else { + learningDisplay.style.display = "none"; + } + } + + function syncFromBackend() { + fetch("/api/data") + .then((response) => response.json()) + .then((data) => { + timer1 = data.time1; + timer2 = data.time2; + status1 = data.status1; + status2 = data.status2; + best1 = data.best1; + best2 = data.best2; + learningMode = data.learningMode; + learningButton = data.learningButton || ""; + lastSync = Date.now(); + updateDisplay(); + }) + .catch((error) => + console.error("Fehler beim Laden der Daten:", error) + ); + } + + // Sync with backend every 1 second + setInterval(syncFromBackend, 1000); + + // Smooth update every 50ms + setInterval(updateDisplay, 50); + + // Heartbeat timeout check (every second) + setInterval(() => { + const now = Date.now(); + [ + {button: "start1", id: "heartbeat1"}, + {button: "stop1", id: "heartbeat2"}, + {button: "start2", id: "heartbeat3"}, + {button: "stop2", id: "heartbeat4"}, + ].forEach(({button, id}) => { + if (now - heartbeatTimeouts[button] > 10000) { + document.getElementById(id).classList.remove('active'); + } + }); + }, 1000); + + // Initial load + syncFromBackend(); + \ No newline at end of file diff --git a/src/communication.h b/src/communication.h index 7dfb314..ac82f61 100644 --- a/src/communication.h +++ b/src/communication.h @@ -155,18 +155,17 @@ void handleBatteryTopic(const char* topic, const char* payload) { } // Parse payload for timestamp (optional, falls im Payload enthalten) - uint64_t timestamp = millis(); StaticJsonDocument<128> payloadDoc; if (payload && strlen(payload) > 0 && deserializeJson(payloadDoc, payload) == DeserializationError::Ok) { if (payloadDoc.containsKey("voltage")) { - timestamp = payloadDoc["voltage"]; + batteryLevel = payloadDoc["voltage"]; } } //Berechne die Prozentzahl des Batteriestands für eine 1S LIPO batteryLevel sind Volts // Hier wird angenommen, dass 3.7V 100% entspricht und 3.0V 0% batteryLevel = batteryLevel * 1000; // Umwandlung von V in mV für genauere Berechnung - if (batteryLevel < 3000) { + if (batteryLevel < 3200) { batteryLevel = 0; // 0% bei 3.0V } else if (batteryLevel > 3700) { batteryLevel = 100; // 100% bei 3.7V