diff --git a/logs/gcode_commands.log b/logs/gcode_commands.log index f0270e0..6ec7abb 100644 --- a/logs/gcode_commands.log +++ b/logs/gcode_commands.log @@ -10414,3 +10414,18 @@ 2026-06-12T16:56:03.538Z ::ffff:127.0.0.1: M114 2026-06-12T16:56:03.753Z ::ffff:127.0.0.1: G1 X1 Y2 Z3 2026-06-12T16:56:03.985Z ::ffff:127.0.0.1: G1 X1 +2026-06-12T21:09:26.350Z ::ffff:127.0.0.1: M114 +2026-06-12T21:09:26.458Z ::ffff:127.0.0.1: M114 +2026-06-12T21:09:26.465Z ::ffff:127.0.0.1: G1 X1 Y2 Z3 +2026-06-12T21:09:26.573Z ::ffff:127.0.0.1: G1 X1 Y2 Z3 +2026-06-12T21:09:26.800Z ::ffff:127.0.0.1: G1 X1 +2026-06-12T21:13:04.270Z ::ffff:127.0.0.1: M114 +2026-06-12T21:13:04.283Z ::ffff:127.0.0.1: G1 X1 Y2 Z3 +2026-06-12T21:13:04.452Z ::ffff:127.0.0.1: M114 +2026-06-12T21:13:04.666Z ::ffff:127.0.0.1: G1 X1 Y2 Z3 +2026-06-12T21:13:04.888Z ::ffff:127.0.0.1: G1 X1 +2026-06-12T21:15:19.076Z ::ffff:127.0.0.1: M114 +2026-06-12T21:15:19.091Z ::ffff:127.0.0.1: G1 X1 Y2 Z3 +2026-06-12T21:15:19.320Z ::ffff:127.0.0.1: M114 +2026-06-12T21:15:19.533Z ::ffff:127.0.0.1: G1 X1 Y2 Z3 +2026-06-12T21:15:19.762Z ::ffff:127.0.0.1: G1 X1 diff --git a/logs/pings.log b/logs/pings.log index 9244124..656598c 100644 --- a/logs/pings.log +++ b/logs/pings.log @@ -14640,3 +14640,9 @@ 2026-06-12T16:46:46.265Z ::ffff:127.0.0.1 : Ping 2026-06-12T16:56:02.816Z ::ffff:127.0.0.1 : Ping 2026-06-12T16:56:03.315Z ::ffff:127.0.0.1 : Ping +2026-06-12T21:09:26.116Z ::ffff:127.0.0.1 : Ping +2026-06-12T21:09:26.447Z ::ffff:127.0.0.1 : Ping +2026-06-12T21:13:04.233Z ::ffff:127.0.0.1 : Ping +2026-06-12T21:13:04.257Z ::ffff:127.0.0.1 : Ping +2026-06-12T21:15:19.049Z ::ffff:127.0.0.1 : Ping +2026-06-12T21:15:19.098Z ::ffff:127.0.0.1 : Ping diff --git a/public/app.js b/public/app.js index 92fa8fc..1b7edac 100644 --- a/public/app.js +++ b/public/app.js @@ -56,10 +56,20 @@ document.addEventListener('DOMContentLoaded', function() { const sendersUl = document.getElementById('senderList'); sendersUl.innerHTML = ''; data.senders.forEach(sender => { - const li = document.createElement('li'); + const li = document.createElement('li'); const state = sender.state || 'disconnected'; - const label = sender.url ? `${sender.name} (${sender.url}): ${state}` : `${sender.name}: ${state}`; - li.textContent = label; + + if (sender.isGCodeReceiver === false) { + // Shelly / EmergencyStop: nur Name + Zustand, keine URL + li.textContent = `${sender.name}: ${state}`; + li.classList.add('shelly'); + } else { + // Telnet / FluidNC: URL anzeigen wenn vorhanden + li.textContent = sender.url + ? `${sender.name} (${sender.url}): ${state}` + : `${sender.name}: ${state}`; + } + li.classList.add(state.toLowerCase()); sendersUl.appendChild(li); }); @@ -186,25 +196,33 @@ document.addEventListener('DOMContentLoaded', function() { const stops = document.querySelectorAll('#estopGrad stop'); const textPath = document.querySelector('#emergency-stop textPath'); + const textEl = document.querySelector('#emergency-stop text'); const btnInner = document.querySelector('#emergency-stop circle:last-of-type'); + const btnOuter = document.querySelector('#emergency-stop circle:first-of-type'); const label = document.getElementById('armed-status'); if (armed) { - // Rot: Roboter bestromt → Klick = Emergency Stop + // ── E-STOP: Gelber Ring + roter Pilz — laut, hervorspringend ──────────── + if (btnOuter) { btnOuter.setAttribute('fill', '#FFD700'); btnOuter.setAttribute('stroke', '#C8960A'); } + if (btnInner) { btnInner.setAttribute('r', '21'); btnInner.setAttribute('stroke', '#660000'); } if (stops[0]) stops[0].setAttribute('stop-color', '#ff5555'); if (stops[1]) stops[1].setAttribute('stop-color', '#cc0000'); if (stops[2]) stops[2].setAttribute('stop-color', '#880000'); - if (btnInner) btnInner.setAttribute('stroke', '#660000'); + if (textEl) textEl.setAttribute('fill', '#1a1000'); if (textPath) textPath.textContent = 'EMERGENCY STOP'; - if (label) { label.textContent = '● Bestromt'; label.className = 'estop-armed-label armed'; } + if (label) { label.textContent = '● Bestromt'; label.className = 'estop-armed-label armed'; } } else { - // Grün: Strom AUS → Klick = Strom einschalten - if (stops[0]) stops[0].setAttribute('stop-color', '#88ff99'); - if (stops[1]) stops[1].setAttribute('stop-color', '#00aa44'); - if (stops[2]) stops[2].setAttribute('stop-color', '#005522'); - if (btnInner) btnInner.setAttribute('stroke', '#003311'); + // ── POWER ON: Dunkler Navy-Ring + blauer Knopf — ruhig, klar ──────────── + // Kein gelber Ring → kein E-Stop-Charakter. + // Kompakter Knopf (r 19 statt 21) → wirkt wie klassischer Power-Switch. + if (btnOuter) { btnOuter.setAttribute('fill', '#1a3050'); btnOuter.setAttribute('stroke', '#0d1e33'); } + if (btnInner) { btnInner.setAttribute('r', '19'); btnInner.setAttribute('stroke', '#091929'); } + if (stops[0]) stops[0].setAttribute('stop-color', '#5c9fcf'); + if (stops[1]) stops[1].setAttribute('stop-color', '#255f96'); + if (stops[2]) stops[2].setAttribute('stop-color', '#0c3660'); + if (textEl) textEl.setAttribute('fill', '#9dc8e8'); if (textPath) textPath.textContent = 'START ROBOT'; - if (label) { label.textContent = '○ Kein Strom'; label.className = 'estop-armed-label disarmed'; } + if (label) { label.textContent = '○ Kein Strom'; label.className = 'estop-armed-label disarmed'; } } } diff --git a/public/style.css b/public/style.css index 40b9acd..fdb4006 100644 --- a/public/style.css +++ b/public/style.css @@ -127,6 +127,40 @@ h1 { content: "🟡"; } +/* Shelly / EmergencyStop: CSS-Kreis statt Emoji (volle Farbkontrolle) */ +.section#senders li.shelly::before { + content: ""; + display: block; + position: absolute; + left: 1px; + top: 50%; + transform: translateY(-50%); + width: 11px; + height: 11px; + border-radius: 50%; + /* Default: hellblau — Strom vorhanden, Shelly bereit */ + background: #89bcde; + box-shadow: 0 0 0 1px rgba(0, 30, 60, 0.4); +} + +/* Gestoppt: weiß — Strom wurde abgeschaltet */ +.section#senders li.shelly.stopped::before { + background: #dce9f5; + box-shadow: 0 0 0 1px rgba(100, 160, 210, 0.5); +} + +/* Fehler: gedämpftes Rot */ +.section#senders li.shelly.error::before { + background: #9a3a3a; + box-shadow: 0 0 0 1px rgba(60, 0, 0, 0.5); +} + +/* Getrennt / unbekannt: Grau */ +.section#senders li.shelly.disconnected::before { + background: #4a5a6a; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3); +} + .state-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(25px, 1fr)); @@ -302,8 +336,8 @@ h1 { color: var(--muted); } -.estop-armed-label.armed { color: #e74c3c; } -.estop-armed-label.disarmed { color: #2ecc71; } +.estop-armed-label.armed { color: #e74c3c; } /* Rot: Gefahr, bewusst laut */ +.estop-armed-label.disarmed { color: #89bcde; } /* Hellblau: ruhig, stromlos */ .btn { display: inline-block; @@ -323,23 +357,23 @@ h1 { cursor: not-allowed; } -/* Alarm-Unlock: Bernstein / Orange — Recovery-Aktion */ +/* Alarm-Unlock: ruhiges Blau — passend zum Stromloszustand */ .btn-unlock { - background: #c87800; - color: #fff; + background: #2a6496; + color: #e8f2fa; } .btn-unlock:hover:not(:disabled) { - background: #a56200; + background: #1e4d73; } -/* Status-Zeile unterhalb des Buttons */ +/* Status-Zeile unterhalb der Buttons — immer weiß/hell, keine Signalfarben */ .estop-status { font-size: 13px; min-height: 18px; color: var(--text); - opacity: 0.7; + opacity: 0.65; } -.estop-status.ok { color: #2ecc71; opacity: 1; } -.estop-status.err { color: #e74c3c; opacity: 1; } \ No newline at end of file +.estop-status.ok { color: var(--text); opacity: 0.9; } +.estop-status.err { color: var(--text); opacity: 0.9; } \ No newline at end of file diff --git a/server/InfoServer.js b/server/InfoServer.js index 1467fc1..6072498 100644 --- a/server/InfoServer.js +++ b/server/InfoServer.js @@ -24,7 +24,7 @@ function createInfoServer(httpsOptions, sharedState, robot, GCode, senders, opti // ── API ────────────────────────────────────────────────────────────────── app.get('/api/status', (req, res) => { - const sendersStatus = senders.map(({ name, instance }) => { + const sendersStatus = senders.map(({ name, instance, isGCodeReceiver }) => { const status = instance?.getStatus ? instance.getStatus() : { state: instance?.isTestMode ? 'connected' : instance?.tSocket ? 'connected' : 'disconnected', url: instance?.url || null, @@ -44,6 +44,7 @@ function createInfoServer(httpsOptions, sharedState, robot, GCode, senders, opti return { name, + isGCodeReceiver: isGCodeReceiver !== false, // false nur für Shelly state, url: status.url || null, isTestMode: !!status.isTestMode, diff --git a/test/InfoServer.test.js b/test/InfoServer.test.js index abb1677..0ffdb28 100644 --- a/test/InfoServer.test.js +++ b/test/InfoServer.test.js @@ -109,6 +109,7 @@ describe('InfoServer', () => { expect(status.senders).toEqual([ { name: 'Base', + isGCodeReceiver: true, state: 'connected', url: null, isTestMode: false, @@ -120,6 +121,7 @@ describe('InfoServer', () => { }, { name: 'Hand', + isGCodeReceiver: true, state: 'disconnected', url: null, isTestMode: false, @@ -165,6 +167,7 @@ describe('InfoServer', () => { expect(status.senders).toEqual([ { name: 'Reconnect', + isGCodeReceiver: true, state: 'reconnecting', url: 'reconnect.test', isTestMode: false,