E-Stop Log

This commit is contained in:
chk
2026-06-12 18:47:28 +02:00
parent e5ec78ae1e
commit 3e3023fa63
7 changed files with 79 additions and 16 deletions

View File

@@ -10399,3 +10399,13 @@
2026-06-12T16:12:57.932Z ::ffff:127.0.0.1: M114 2026-06-12T16:12:57.932Z ::ffff:127.0.0.1: M114
2026-06-12T16:12:58.165Z ::ffff:127.0.0.1: G1 X1 Y2 Z3 2026-06-12T16:12:58.165Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-12T16:12:58.438Z ::ffff:127.0.0.1: G1 X1 2026-06-12T16:12:58.438Z ::ffff:127.0.0.1: G1 X1
2026-06-12T16:34:08.466Z ::ffff:127.0.0.1: M114
2026-06-12T16:34:08.468Z ::ffff:127.0.0.1: M114
2026-06-12T16:34:08.483Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-12T16:34:08.701Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-12T16:34:08.935Z ::ffff:127.0.0.1: G1 X1
2026-06-12T16:46:45.327Z ::ffff:127.0.0.1: M114
2026-06-12T16:46:45.356Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-12T16:46:46.488Z ::ffff:127.0.0.1: M114
2026-06-12T16:46:46.701Z ::ffff:127.0.0.1: G1 X1 Y2 Z3
2026-06-12T16:46:46.923Z ::ffff:127.0.0.1: G1 X1

View File

@@ -14634,3 +14634,7 @@
2026-06-12T16:00:23.226Z ::ffff:127.0.0.1 : Ping 2026-06-12T16:00:23.226Z ::ffff:127.0.0.1 : Ping
2026-06-12T16:12:57.687Z ::ffff:127.0.0.1 : Ping 2026-06-12T16:12:57.687Z ::ffff:127.0.0.1 : Ping
2026-06-12T16:12:57.751Z ::ffff:127.0.0.1 : Ping 2026-06-12T16:12:57.751Z ::ffff:127.0.0.1 : Ping
2026-06-12T16:34:08.216Z ::ffff:127.0.0.1 : Ping
2026-06-12T16:34:08.427Z ::ffff:127.0.0.1 : Ping
2026-06-12T16:46:45.306Z ::ffff:127.0.0.1 : Ping
2026-06-12T16:46:46.265Z ::ffff:127.0.0.1 : Ping

View File

@@ -175,7 +175,7 @@ document.addEventListener('DOMContentLoaded', function() {
// ── Emergency Stop Panel ───────────────────────────────────────────── // ── Emergency Stop Panel ─────────────────────────────────────────────
// SVG-Button: Farbe + Text + Click-Handler je nach armed-Zustand wechseln. // SVG-Button: Farbe + Text je nach armed-Zustand.
// armed=true → rot "EMERGENCY STOP" → POST /api/emergency-stop // armed=true → rot "EMERGENCY STOP" → POST /api/emergency-stop
// armed=false → grün "START ROBOT" → POST /api/power-on // armed=false → grün "START ROBOT" → POST /api/power-on
let _lastArmed = null; let _lastArmed = null;
@@ -184,7 +184,7 @@ document.addEventListener('DOMContentLoaded', function() {
if (armed === _lastArmed) return; if (armed === _lastArmed) return;
_lastArmed = armed; _lastArmed = armed;
const stops = document.querySelectorAll('#estopGrad stop'); const stops = document.querySelectorAll('#estopGrad stop');
const textPath = document.querySelector('#emergency-stop textPath'); const textPath = document.querySelector('#emergency-stop textPath');
const btnInner = document.querySelector('#emergency-stop circle:last-of-type'); const btnInner = document.querySelector('#emergency-stop circle:last-of-type');
const label = document.getElementById('armed-status'); const label = document.getElementById('armed-status');
@@ -198,7 +198,7 @@ document.addEventListener('DOMContentLoaded', function() {
if (textPath) textPath.textContent = 'EMERGENCY STOP'; 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 { } else {
// Grün: Strom AUS → Klick = Strom einschalten (Start Robot) // Grün: Strom AUS → Klick = Strom einschalten
if (stops[0]) stops[0].setAttribute('stop-color', '#88ff99'); if (stops[0]) stops[0].setAttribute('stop-color', '#88ff99');
if (stops[1]) stops[1].setAttribute('stop-color', '#00aa44'); if (stops[1]) stops[1].setAttribute('stop-color', '#00aa44');
if (stops[2]) stops[2].setAttribute('stop-color', '#005522'); if (stops[2]) stops[2].setAttribute('stop-color', '#005522');
@@ -206,24 +206,59 @@ document.addEventListener('DOMContentLoaded', function() {
if (textPath) textPath.textContent = 'START ROBOT'; 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'; }
} }
}
const div = document.getElementById('emergency-stop'); // ── Click-Handler (einmalig registriert, liest _lastArmed dynamisch) ─────────
if (div) { //
div.onclick = armed // Zeigt Lade-/Erfolgs-/Fehlerstatus unter dem Button.
? () => fetch('/api/emergency-stop', { method: 'POST' }) // Schreibt console.warn ⚠️ für den Browser-Dev-Tools-Log.
: () => fetch('/api/power-on', { method: 'POST' }); async function handleEstopClick() {
const armed = _lastArmed;
const url = armed ? '/api/emergency-stop' : '/api/power-on';
const action = armed ? 'EmergencyStop' : 'PowerOn';
const statusEl = document.getElementById('estop-action-status');
if (statusEl) { statusEl.textContent = '⏳ …'; statusEl.className = 'estop-status'; }
console.warn(`⚠️ [${action}] wird ausgeführt …`);
try {
const res = await fetch(url, { method: 'POST' });
const data = await res.json();
const ok = data.ok || (data.results || []).every(r => r.ok || r.skipped);
if (ok) {
if (statusEl) { statusEl.textContent = `${action} OK`; statusEl.className = 'estop-status ok'; }
console.warn(`⚠️ [${action}] OK`);
} else {
const failed = (data.results || [])
.filter(r => !r.ok && !r.skipped)
.map(r => `${r.name}(${r.error || '?'})`)
.join(', ');
if (statusEl) { statusEl.textContent = `⚠️ Teilfehler: ${failed}`; statusEl.className = 'estop-status err'; }
console.warn(`⚠️ [${action}] Teilfehler — ${failed}`);
}
} catch (err) {
if (statusEl) { statusEl.textContent = `❌ Netzwerkfehler`; statusEl.className = 'estop-status err'; }
console.error(`❌ [${action}] Netzwerkfehler: ${err.message}`);
} }
} }
// onclick einmalig setzen — bleibt dauerhaft, Handler liest _lastArmed dynamisch.
const estopDiv = document.getElementById('emergency-stop');
if (estopDiv) estopDiv.onclick = handleEstopClick;
async function pollPowerStatus() { async function pollPowerStatus() {
try { try {
const res = await fetch('/api/power-status'); const res = await fetch('/api/power-status');
if (!res.ok) return; if (!res.ok) return;
const data = await res.json(); const data = await res.json();
if (data.ok) updateEmergencyStopButton(data.armed); // Immer aktualisieren (auch bei ok:false → disarmed als sicherer Fallback)
updateEmergencyStopButton(data.ok ? data.armed : false);
} catch { /* Netzwerkfehler → Button bleibt im letzten Zustand */ } } catch { /* Netzwerkfehler → Button bleibt im letzten Zustand */ }
} }
// Sofort sicheren Startzustand (grün "START ROBOT") setzen, damit der onclick
// sofort aktiv ist — noch bevor der erste Poll antwortet.
updateEmergencyStopButton(false);
pollPowerStatus(); pollPowerStatus();
setInterval(pollPowerStatus, 2000); setInterval(pollPowerStatus, 2000);

View File

@@ -105,6 +105,7 @@
</svg> </svg>
</div> </div>
<div id="armed-status" class="estop-armed-label"></div> <div id="armed-status" class="estop-armed-label"></div>
<div id="estop-action-status" class="estop-status"></div>
</div> </div>
<button id="btn-alarm-unlock" class="btn btn-unlock"> <button id="btn-alarm-unlock" class="btn btn-unlock">

View File

@@ -99,12 +99,12 @@ module.exports = class ShellyEmergencyStop extends SenderInterface {
const result = await this._httpGet(this._offUrl); const result = await this._httpGet(this._offUrl);
this.state = result.ok ? 'stopped' : 'error'; this.state = result.ok ? 'stopped' : 'error';
this.error = result.ok ? null : `HTTP ${result.status}`; this.error = result.ok ? null : `HTTP ${result.status}`;
console.log(`[Shelly] power OFF → ${result.ok ? 'OK' : `HTTP ${result.status}`}`); console.warn(`⚠️ [Shelly] power OFF → ${result.ok ? 'OK' : `HTTP ${result.status}`}`);
return result; return result;
} catch (err) { } catch (err) {
this.state = 'error'; this.state = 'error';
this.error = err.message; this.error = err.message;
console.error(`[Shelly] power OFF failed: ${err.message}`); console.error(`[Shelly] power OFF failed: ${err.message}`);
return { ok: false, error: err.message }; return { ok: false, error: err.message };
} }
} }
@@ -120,7 +120,7 @@ module.exports = class ShellyEmergencyStop extends SenderInterface {
const result = await this._httpGet(this._onUrl); const result = await this._httpGet(this._onUrl);
this.state = result.ok ? 'ready' : 'error'; this.state = result.ok ? 'ready' : 'error';
this.error = result.ok ? null : `HTTP ${result.status}`; this.error = result.ok ? null : `HTTP ${result.status}`;
console.log(`[Shelly] power ON → ${result.ok ? 'OK' : `HTTP ${result.status}`}`); console.warn(`⚠️ [Shelly] power ON → ${result.ok ? 'OK' : `HTTP ${result.status}`}`);
return result; return result;
} catch (err) { } catch (err) {
this.state = 'error'; this.state = 'error';

View File

@@ -314,6 +314,7 @@ module.exports = class TelnetSenderGRBL extends SenderInterface {
if (!this.tSocket) return { ok: false, error: 'not connected' }; if (!this.tSocket) return { ok: false, error: 'not connected' };
try { try {
this.tSocket.write('!'); this.tSocket.write('!');
console.warn(`⚠️ [EmergencyStop] Feed Hold '!' → ${this.urlGRBLstr}`);
return { ok: true }; return { ok: true };
} catch (err) { } catch (err) {
return { ok: false, error: err.message }; return { ok: false, error: err.message };

View File

@@ -120,8 +120,12 @@ function createInfoServer(httpsOptions, sharedState, robot, GCode, senders, opti
? r.value ? r.value
: { name: senders[i].name, ok: false, error: r.reason?.message } : { name: senders[i].name, ok: false, error: r.reason?.message }
); );
console.log(`[EmergencyStop] triggered at ${new Date().toISOString()}`); const ok = results.every(r => r.ok || r.skipped);
res.json({ ok: results.every(r => r.ok || r.skipped), at: new Date().toISOString(), results }); const summary = results
.map(r => `${r.name}:${r.skipped ? 'skip' : r.ok ? 'ok' : `FAIL(${r.error})`}`)
.join(', ');
console.warn(`⚠️ [EmergencyStop] ${new Date().toISOString()} — [${summary}]`);
res.json({ ok, at: new Date().toISOString(), results });
}); });
app.post('/api/power-on', async (req, res) => { app.post('/api/power-on', async (req, res) => {
@@ -134,7 +138,10 @@ function createInfoServer(httpsOptions, sharedState, robot, GCode, senders, opti
const results = settled.map(r => const results = settled.map(r =>
r.status === 'fulfilled' ? r.value : { ok: false, error: r.reason?.message } r.status === 'fulfilled' ? r.value : { ok: false, error: r.reason?.message }
); );
res.json({ ok: results.length > 0 && results.every(r => r.ok), at: new Date().toISOString(), results }); const ok = results.length > 0 && results.every(r => r.ok);
const summary = results.map(r => `${r.name}:${r.ok ? 'ok' : `FAIL(${r.error})`}`).join(', ');
console.warn(`⚠️ [PowerOn] ${new Date().toISOString()} — [${summary || 'keine Shelly konfiguriert'}]`);
res.json({ ok, at: new Date().toISOString(), results });
}); });
app.post('/api/alarm-unlock', async (req, res) => { app.post('/api/alarm-unlock', async (req, res) => {
@@ -148,7 +155,12 @@ function createInfoServer(httpsOptions, sharedState, robot, GCode, senders, opti
? r.value ? r.value
: { name: senders[i].name, ok: false, error: r.reason?.message } : { name: senders[i].name, ok: false, error: r.reason?.message }
); );
res.json({ ok: results.every(r => r.ok || r.skipped), at: new Date().toISOString(), results }); const ok = results.every(r => r.ok || r.skipped);
const summary = results
.map(r => `${r.name}:${r.skipped ? 'skip' : r.ok ? 'ok' : `FAIL(${r.error})`}`)
.join(', ');
console.warn(`⚠️ [AlarmUnlock] ${new Date().toISOString()} — [${summary}]`);
res.json({ ok, at: new Date().toISOString(), results });
}); });
// ── 404 ────────────────────────────────────────────────────────────────── // ── 404 ──────────────────────────────────────────────────────────────────