From a63499855541dbaa409d6dc3f135170aca3913a7 Mon Sep 17 00:00:00 2001 From: ChK Date: Thu, 5 Feb 2026 18:50:24 +0100 Subject: [PATCH] =?UTF-8?q?Wrapper=20f=C3=BCr=20WSS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/GamePad.js | 45 ++++--- public/KeyboardInput.js | 17 ++- public/WebService.js | 265 +++++++++++++++++++++++++++++++--------- public/index.html | 45 ++++--- 4 files changed, 259 insertions(+), 113 deletions(-) diff --git a/public/GamePad.js b/public/GamePad.js index 997080c..b56ffa7 100755 --- a/public/GamePad.js +++ b/public/GamePad.js @@ -24,8 +24,8 @@ function checkGamePad() { // Dreieck zum Dreieck-Setzen - if (buttons[3].pressed) { - socket.send(`G90 G1 X0 Y300 Z0 A${Math.PI/2} B${-1.0*Math.PI/2} C0 F${xyzSpeed}`); + if (buttons[3].pressed) { + socket.sendCommand(`G90 G1 X0 Y300 Z0 A${Math.PI/2} B${-1.0*Math.PI/2} C0 F${xyzSpeed}`); } if (buttons[4].pressed) { //console.log("x=" + robot.x + " y=" + robot.y + " z=" + robot.z); @@ -37,47 +37,46 @@ function checkGamePad() { if(buttons[0].pressed && (Date.now() - lastCheckTime > 500)){ lastCheckTime = Date.now() console.log('FPoint!'); - socket.send('FPoint'); - socket.send('FShow'); + socket.sendCommand('FPoint'); + socket.sendCommand('FShow'); } // L1 und R1 Button to forward-backward in Point-List if(gp.buttons[4].pressed && (Date.now() - lastCheckTime > 500)){ lastCheckTime = Date.now() - socket.send('FMinus'); - socket.send('FShow'); + socket.sendCommand('FMinus'); + socket.sendCommand('FShow'); } if(gp.buttons[5].pressed && (Date.now() - lastCheckTime > 500)){ lastCheckTime = Date.now() - socket.send('FPlus'); - socket.send('FShow'); + socket.sendCommand('FPlus'); + socket.sendCommand('FShow'); } - if (x < -0.2) { socket.send(`G91 G1 X+${stepSizeXYZ} F${xyzSpeed}`);} - if (x > 0.2) { socket.send(`G91 G1 X-${stepSizeXYZ} F${xyzSpeed}`);} + if (x < -0.2) { socket.sendCommand(`G91 G1 X+${stepSizeXYZ} F${xyzSpeed}`);} + if (x > 0.2) { socket.sendCommand(`G91 G1 X-${stepSizeXYZ} F${xyzSpeed}`);} - if (y < -0.2) { socket.send(`G91 G1 Y${stepSizeXYZ} F${xyzSpeed}`); } - if (y > 0.2) { socket.send(`G91 G1 Y-${stepSizeXYZ} F${xyzSpeed}`);} + if (y < -0.2) { socket.sendCommand(`G91 G1 Y${stepSizeXYZ} F${xyzSpeed}`); } + if (y > 0.2) { socket.sendCommand(`G91 G1 Y-${stepSizeXYZ} F${xyzSpeed}`);} - if (z < -0.2) { socket.send(`G91 G1 Z${stepSizeXYZ} F${xyzSpeed}`); } - if (z > 0.2) { socket.send(`G91 G1 Z-${stepSizeXYZ} F${xyzSpeed}`); } + if (z < -0.2) { socket.sendCommand(`G91 G1 Z${stepSizeXYZ} F${xyzSpeed}`); } + if (z > 0.2) { socket.sendCommand(`G91 G1 Z-${stepSizeXYZ} F${xyzSpeed}`); } // Greif-Richtung // LeftRight - if(buttons[14].pressed){ socket.send(`G91 G1 A${stepSize} F${xyzSpeed}`);} - if(buttons[15].pressed){ socket.send(`G91 G1 A-${stepSize} F${xyzSpeed}`);} + if(buttons[14].pressed){ socket.sendCommand(`G91 G1 A${stepSize} F${xyzSpeed}`);} + if(buttons[15].pressed){ socket.sendCommand(`G91 G1 A-${stepSize} F${xyzSpeed}`);} // Up - Down - if(buttons[12].pressed){ socket.send(`G91 G1 B${stepSize} F${xyzSpeed}`);} - if(buttons[13].pressed){ socket.send(`G91 G1 B-${stepSize} F${xyzSpeed}`);} + if(buttons[12].pressed){ socket.sendCommand(`G91 G1 B${stepSize} F${xyzSpeed}`);} + if(buttons[13].pressed){ socket.sendCommand(`G91 G1 B-${stepSize} F${xyzSpeed}`);} // Drehung - if (psi < -0.2) { socket.send(`G91 G1 C${stepSize} F${xyzSpeed}`); } - if (psi > 0.2) { socket.send(`G91 G1 C-${stepSize} F${xyzSpeed}`); } + if (psi < -0.2) { socket.sendCommand(`G91 G1 C${stepSize} F${xyzSpeed}`); } + if (psi > 0.2) { socket.sendCommand(`G91 G1 C-${stepSize} F${xyzSpeed}`); } // Trigger-Buttons für Öffnen und Schliessen - if(buttons[6].pressed){socket.send(`G91 G1 E-${stepSizeE} F${xyzSpeed}`);} - if(buttons[7].pressed){socket.send(`G91 G1 E${stepSizeE} F${xyzSpeed}`);} - + if(buttons[6].pressed){socket.sendCommand(`G91 G1 E-${stepSizeE} F${xyzSpeed}`);} + if(buttons[7].pressed){socket.sendCommand(`G91 G1 E${stepSizeE} F${xyzSpeed}`);} if (isRunning) { setTimeout(checkGamePad, 15);} } diff --git a/public/KeyboardInput.js b/public/KeyboardInput.js index fdf2838..14605f4 100755 --- a/public/KeyboardInput.js +++ b/public/KeyboardInput.js @@ -1,17 +1,16 @@ -document.onkeydown = checkKey; +document.addEventListener("keydown", (e)=>{ -function checkKey(e) { + if(!window.socket){ + console.warn("socket not ready"); + return; + } - e = e || window.event; - - if(e.key == 'a' || e.key == 'A') - { + if(e.key==="a"||e.key==="A"){ console.log("back to A position"); socket.send(`G90 G1 X0 Y300 Z0 A${Math.PI/2} B-${Math.PI/2} C0 F100`); } - // Hand-Winkel (Eulerwinkel) - else if(e.key == 'i' || e.key == 'I'){ + else if(e.key==="i"||e.key==="I"){ socket.send('G91 G1 B+0.1 F100'); } else if(e.key == 'k' || e.key == 'K'){ @@ -63,4 +62,4 @@ function checkKey(e) { } -} \ No newline at end of file +}); diff --git a/public/WebService.js b/public/WebService.js index cf5b345..c6580c7 100755 --- a/public/WebService.js +++ b/public/WebService.js @@ -1,75 +1,224 @@ -var startTime = Date.now() ; -console.log("Meine Document-Location: " + document.location); -var lastPingRequest; +// WebService.js - robust WebSocket wrapper (drop-in replacement) +(function () { + const startTime = Date.now(); + console.log("Meine Document-Location:", document.location); -// Use explicit '/echo' path and a small wrapper to guard sends from other modules -var socketUrl = (location.protocol === 'https:' ? 'wss://' : 'ws://') + window.location.host + '/echo'; + const socketUrl = + (location.protocol === "https:" ? "wss://" : "ws://") + + window.location.host + + "/echo"; -console.log("WebSocket URL: " + socketUrl); + console.log("WebSocket URL:", socketUrl); -// socket wrapper keeps the same global identifier but guards sends when underlying ws is not open -var socket = { + // Expose global socket object immediately so other scripts never see "socket is not defined" + window.socket = { + // internal _ws: null, - send: function(msg) { + _queue: [], + _retry: 0, + _pingIntervalId: null, + _lastPingRequest: null, + _connectedOnce: false, + + // user hooks (can be assigned by UI code) + onMessage: null, + onOpen: null, + onClose: null, + + // ------ API: send / sendCommand (legacy & recommended) ------ + // raw send (backwards compatible) + send(msg) { + this._enqueueOrSend(msg); + }, + + // preferred semantic method for commands + sendCommand(cmd) { + // you can add validation/normalization here + this._enqueueOrSend(cmd); + }, + + // convenience robot command + moveZ(value, feed = 100) { + // positive value moves up; negative moves down depending on your G-code semantics + this.sendCommand(`G91 G1 Z${value} F${feed}`); + }, + + // manual control to connect/disconnect + connect() { + _connect(this); + }, + + disconnect() { + _disconnect(this); + }, + + isOpen() { + return this._ws && this._ws.readyState === WebSocket.OPEN; + }, + + // internal helper: enqueue if not open, otherwise send immediately + _enqueueOrSend(msg) { + try { if (this._ws && this._ws.readyState === WebSocket.OPEN) { - this._ws.send(msg); - console.log('Sent message with WebSocket.OPEN:', msg); + this._ws.send(msg); + console.log("WS SEND:", msg); } else { - console.warn('Socket not open, dropping message:', msg); + // queue to be flushed on open + console.warn("WS not ready → queueing:", msg); + this._queue.push(msg); } + } catch (e) { + console.error("WS send failed:", e, " (queued )"); + this._queue.push(msg); + } } -}; + }; -let pingIntervalId = null; -let reconnectDelay = 1000; - -function connect() { - const ws = new WebSocket(socketUrl); - socket._ws = ws; - - ws.onopen = () => { - console.log('Connected to WS Server'); - reconnectDelay = 1000; - if (pingIntervalId) clearInterval(pingIntervalId); - pingIntervalId = setInterval(() => { - if (socket._ws && socket._ws.readyState === WebSocket.OPEN) { - lastPingRequest = Date.now(); - socket.send('Ping'); - } - }, 5000); - }; - - ws.onclose = (event) => { - console.log((event.wasClean) ? 'Disconnected' : 'Connection break: ' + (event.reason || event.code)); - if (pingIntervalId) { clearInterval(pingIntervalId); pingIntervalId = null; } - setTimeout(() => { - console.log('Reconnecting...'); - connect(); - }, reconnectDelay); - reconnectDelay = Math.min(30000, reconnectDelay * 2); - }; - - ws.onmessage = (event) => { - if(event.data == 'Ping'){ - console.log('Ping: ' + (Date.now() - lastPingRequest).toString() + ' ms'); + // ---------- internal functions ---------- + function _flushQueue(sock) { + while (sock._queue.length) { + const m = sock._queue.shift(); + try { + if (sock._ws && sock._ws.readyState === WebSocket.OPEN) { + sock._ws.send(m); + console.log("Flushed queued msg:", m); + } else { + // push back and stop flushing + sock._queue.unshift(m); + break; } - else if(event.data.toString().includes('position')){ - console.log('Position: ' + event.data); + } catch (e) { + console.warn("Failed flushing queued msg, requeueing", e); + sock._queue.unshift(m); + break; + } + } + } + + function _startPing(sock) { + if (sock._pingIntervalId) return; + sock._pingIntervalId = setInterval(() => { + if (sock._ws && sock._ws.readyState === WebSocket.OPEN) { + sock._lastPingRequest = Date.now(); + sock._enqueueOrSend("Ping"); + } + }, 5000); + } + + function _stopPing(sock) { + if (sock._pingIntervalId) { + clearInterval(sock._pingIntervalId); + sock._pingIntervalId = null; + } + } + + function _connect(sock) { + // prevent duplicate connects + if (sock._ws && (sock._ws.readyState === WebSocket.CONNECTING || sock._ws.readyState === WebSocket.OPEN)) { + console.log("WebSocket already connecting/open - skipping connect()"); + return; + } + + try { + console.log("WebSocket: connecting to", socketUrl); + const ws = new WebSocket(socketUrl); + sock._ws = ws; + + ws.onopen = () => { + console.log("Connected to WS Server"); + sock._connectedOnce = true; + sock._retry = 0; // reset retries + _flushQueue(sock); + _startPing(sock); + + if (typeof sock.onOpen === "function") { + try { sock.onOpen(); } catch (e) { console.error("onOpen handler error", e); } } - else if(event.data.toString().includes('XYZ__FShow__XYZ')){ - const content = event.data.toString().split('XYZ__FShow__XYZ')[1]; - console.log('File Content: ' + content); - const el = document.querySelector('textarea#GCodeWindow.editor-look'); - if (el) el.value = content; + }; + + ws.onmessage = (event) => { + const data = event.data ? event.data.toString() : ""; + // built-in handling (keeps backward compatible behaviour) + if (data === "Ping") { + if (sock._lastPingRequest) { + console.log("Ping:", Date.now() - sock._lastPingRequest, "ms"); + } else { + console.log("Ping (no timestamp)"); + } + } else if (data.includes("position")) { + console.log("Position:", data); + } else if (data.includes("XYZ__FShow__XYZ")) { + const content = data.split("XYZ__FShow__XYZ")[1] || ""; + console.log("FShow content received, length", content.length); + const el = document.querySelector("textarea#GCodeWindow.editor-look"); + if (el) el.value = content; + } else { + console.log("DATA:", Date.now() - startTime, data); } - else{ - console.log('DATA SinceStartup: ' + (Date.now() - startTime).toString() +': ', event.data); + + // call user onMessage hook if present + if (typeof sock.onMessage === "function") { + try { sock.onMessage(data); } catch (e) { console.error("onMessage handler error", e); } } - }; + }; - ws.onerror = (err) => console.error('WebSocket error:', err || err.message); -} + ws.onclose = (event) => { + const reason = event.reason || event.code || "closed"; + console.log(event.wasClean ? "Disconnected" : "Connection break: " + reason); + _stopPing(sock); -connect(); + if (typeof sock.onClose === "function") { + try { sock.onClose(event); } catch (e) { console.error("onClose handler error", e); } + } + // exponential backoff with small jitter + sock._retry = (sock._retry || 0) + 1; + const base = 1000; + const delay = Math.min(30000, base * Math.pow(2, sock._retry - 1)) + Math.floor(Math.random() * 300); + console.log("Reconnect in", delay, "ms (attempt", sock._retry, ")"); + setTimeout(() => _connect(sock), delay); + }; + ws.onerror = (err) => { + // some browsers call onerror right before onclose; log only + console.error("WebSocket error:", err && (err.message || err)); + }; + } catch (e) { + console.error("WebSocket create failed", e); + // schedule reconnect + sock._retry = (sock._retry || 0) + 1; + const delay = Math.min(30000, 1000 * Math.pow(2, sock._retry - 1)) + Math.floor(Math.random() * 300); + setTimeout(() => _connect(sock), delay); + } + } + + function _disconnect(sock) { + try { + _stopPing(sock); + if (sock._ws) { + try { sock._ws.close(); } catch (e) { /* ignore */ } + sock._ws = null; + } + } catch (e) { + console.error("Error during disconnect", e); + } + } + + // Kick off initial connection + window.socket.connect(); + + // Expose some helpful debug tools in the console + window.__socket_debug = { + getQueue: () => window.socket._queue.slice(), + getState: () => (window.socket._ws ? window.socket._ws.readyState : null), + flush: () => _flushQueue(window.socket), + reconnect: () => { + _disconnect(window.socket); + window.socket._retry = 0; + window.socket.connect(); + } + }; + + // Optional: if you want a simple rate limiter later, you can add code here. + // Keep GamePad traffic in mind — don't flood the queue on reconnect. +})(); diff --git a/public/index.html b/public/index.html index 3dc55a7..d5c9a61 100755 --- a/public/index.html +++ b/public/index.html @@ -7,8 +7,8 @@