From f2a675a652d40a77e01abd480504c9132d6a0426 Mon Sep 17 00:00:00 2001 From: chk <79915315+ChKendel@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:15:18 +0100 Subject: [PATCH] WS statt Telnet --- .gitignore | 3 + network/TelnetConnection.js | 88 -------------- package-lock.json | 14 ++- package.json | 16 ++- server/config/config.js | 4 +- server/fluidnc/FluidNCClient.js | 125 +++++++------------ server/server.js | 92 ++++++-------- web/index.html | 206 +++++++++++++++++--------------- 8 files changed, 221 insertions(+), 327 deletions(-) delete mode 100644 network/TelnetConnection.js diff --git a/.gitignore b/.gitignore index 2309cc8..1c68a92 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,9 @@ build/Release node_modules/ jspm_packages/ +# SSH keys +*.pem + # Snowpack dependency directory (https://snowpack.dev/) web_modules/ diff --git a/network/TelnetConnection.js b/network/TelnetConnection.js deleted file mode 100644 index f46cf3c..0000000 --- a/network/TelnetConnection.js +++ /dev/null @@ -1,88 +0,0 @@ -const net = require("net"); - -class TelnetConnection { - - constructor(config){ - - this.host = config.host; - this.port = config.port; - this.reconnectDelay = config.reconnectDelay || 30000; - - this.socket = null; - - this.dataListeners = []; - this.connectionListeners = []; - - this.connect(); - } - - connect(){ - - console.log("Connecting to FluidNC:", this.host); - - this.socket = net.createConnection({ - host: this.host, - port: this.port - }); - - this.socket.on("connect",()=>{ - - console.log("FluidNC connected"); - - this.connectionListeners.forEach(fn=>fn(true)); - - }); - - this.socket.on("data",(data)=>{ - - const msg = data.toString(); - - this.dataListeners.forEach(fn=>fn(msg)); - - }); - - this.socket.on("close",()=>{ - - console.log("FluidNC disconnected"); - - this.connectionListeners.forEach(fn=>fn(false)); - - setTimeout(()=>{ - this.connect(); - }, this.reconnectDelay); - - }); - - this.socket.on("error",(err)=>{ - console.log("Telnet error:",err.message); - }); - - } - - send(cmd){ - - if(!this.socket) return; - - this.socket.write(cmd+"\n"); - - } - - realtime(cmd){ - - if(!this.socket) return; - - this.socket.write(cmd); - - } - - onData(fn){ - this.dataListeners.push(fn); - } - - onConnection(fn){ - this.connectionListeners.push(fn); - } - -} - -module.exports = TelnetConnection; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9c995f3..3b629f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,9 +7,11 @@ "": { "name": "fluidnc-webcontrol", "version": "1.0.0", + "license": "ISC", "dependencies": { - "express": "^4.18.2", - "ws": "^8.16.0" + "express": "^4.22.1", + "telnet-stream": "^1.1.0", + "ws": "^8.19.0" } }, "node_modules/accepts": { @@ -214,6 +216,7 @@ "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -712,6 +715,12 @@ "node": ">= 0.8" } }, + "node_modules/telnet-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/telnet-stream/-/telnet-stream-1.1.0.tgz", + "integrity": "sha512-saVuav/ScOFlrQXSB8xUqwwIi3sifjSfBiiywGMu7B8wJz60duqFCyG8IhcRjoPpl6VGGkBH+yH3Tnbxh36h1Q==", + "license": "AGPL-3.0" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -760,6 +769,7 @@ "version": "8.19.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index 38b6b0d..15169d8 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,17 @@ "start": "node server/server.js" }, "dependencies": { - "express": "^4.18.2", - "ws": "^8.16.0" - } + "express": "^4.22.1", + "telnet-stream": "^1.1.0", + "ws": "^8.19.0" + }, + "description": "WebPage to show X-Z-Options. The FluidNC is connected to this app.", + "repository": { + "type": "git", + "url": "http://thinkcentre.local:3000/ChK/appRobotControlScara.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs" } diff --git a/server/config/config.js b/server/config/config.js index 59d79f1..4e7a35d 100644 --- a/server/config/config.js +++ b/server/config/config.js @@ -1,8 +1,8 @@ module.exports = { fluidnc: { - host: "fluidncsilver.local", - port: 81, + host: "fluidncred.local", + port: 80, reconnectDelay: 30000 }, diff --git a/server/fluidnc/FluidNCClient.js b/server/fluidnc/FluidNCClient.js index f6d371b..b81f3ff 100644 --- a/server/fluidnc/FluidNCClient.js +++ b/server/fluidnc/FluidNCClient.js @@ -1,104 +1,69 @@ +const WebSocket = require("ws"); +const EventEmitter = require("events"); -const config = require("../config/config"); -const net = require("net"); -const { resolve } = require("path"); -const TelnetSocket = require("telnet-stream"); +class FluidNCClient extends EventEmitter { + constructor(cfg) { + super(); + this.host = cfg.host; + this.port = cfg.port || 81; + this.ws = null; + this.reconnectDelay = 2000; -class FluidNCClient { - constructor() { - console.log("[FluidNCClient] Initializing..."); - - this.url = config.fluidnc.host; - this.port = config.fluidnc.port || 23; - this.tSocket = null; - this.connected = false; - this.state = { x:0, z:0, state:"unknown" }; - this.listeners = []; - - this._connect(); + this.connect(); } - _connect() { - console.log(`[FluidNCClient] Connecting to FluidNC: ${this.url}`); + connect() { + const url = `ws://${this.host}:${this.port}`; + console.log("[FluidNC] Connecting to:", url); - let socket = null; + this.ws = new WebSocket(url); - new Promise((resolve, reject) => { - socket = net.createConnection({host:this.url, port:this.port}, () => { - resolve(socket); - }).on("error", reject); - }).then(connection => { - console.log("[FluidNCClient] Telnet socket connected"); - this.connected = true; + this.ws.on("open", () => { + console.log("[FluidNC] Connected (WS)"); + }); - connection.on("data", data => { - const msg = data.toString().replace(/\r/g,"").trim(); - if(!msg) return; - console.log("[FluidNCClient] Received:", msg); + this.ws.on("message", (msg) => { + this.emit("message", msg.toString()); + }); - if(msg.startsWith("<") && msg.includes("MPos:")){ - const match = msg.match(/MPos:([\d\.\-]+),[\d\.\-]+,([\d\.\-]+)/); - if(match){ - this.state.x = parseFloat(match[1]); - this.state.z = parseFloat(match[2]); - this.state.state = msg.split(",")[0].replace("<",""); - this._broadcast(this.state); - } - } - }); + this.ws.on("close", () => { + console.log("[FluidNC] Disconnected → retry"); + setTimeout(() => this.connect(), this.reconnectDelay); + }); - if(socket != null){ - this.tSocket = TelnetSocket(socket); - - this.tSocket.on("close", () => { - console.log("[FluidNCClient] Telnet Closed"); - this.tSocket = null; - this.connected = false; - setTimeout(()=>this._connect(), 30000); - }); - } - - }, error => { - console.log("[FluidNCClient] Telnet Connection Error:", error.toString()); - this.tSocket = null; - this.connected = false; - setTimeout(()=>this._connect(), 30000); + this.ws.on("error", (err) => { + console.log("[FluidNC] WS Error:", err.message); }); } - _broadcast(msg){ - this.listeners.forEach(fn => fn(msg)); - } - - onMessage(fn){ - this.listeners.push(fn); - } - - send(cmd){ - if(this.tSocket){ - console.log("[FluidNCClient] Sending:", cmd); - this.tSocket.write(cmd + "\r\n"); + // --- BASIC COMMANDS --- + sendLine(cmd) { + if (this.ws && this.ws.readyState === WebSocket.OPEN) { + this.ws.send(cmd + "\n"); } } - jog(axis, value){ - if(!this.connected) return; - this.send("G91"); - this.send(`G0 ${axis.toUpperCase()}${value}`); - this.send("G90"); + requestStatus() { + this.sendLine("?"); } - sendGcode(cmd){ - if(!this.connected) return; - this.send("G90"); - this.send(cmd); + jog(axis, value) { + const cmd = `$J=G91 ${axis}${value} F2000`; + this.sendLine(cmd); } - setZero(){ - if(!this.connected) return; - this.send("G10 L20 P1 X0 Z0"); + sendGcode(cmd) { + this.sendLine(cmd); + } + + setZero() { + this.sendLine("G92 X0 Y0 Z0"); + } + + onMessage(fn) { + this.on("message", fn); } } diff --git a/server/server.js b/server/server.js index b1f31d0..9c52353 100644 --- a/server/server.js +++ b/server/server.js @@ -9,88 +9,72 @@ const FluidNCClient = require("./fluidnc/FluidNCClient"); const app = express(); -app.use(express.static(path.join(__dirname,"../web"))); +// Serve frontend +app.use(express.static(path.join(__dirname, "../web"))); -const server = https.createServer({ - key: fs.readFileSync(path.join(__dirname,"../cert/key.pem")), - cert: fs.readFileSync(path.join(__dirname,"../cert/cert.pem")) -},app); +// HTTPS server +const server = https.createServer( +{ + key: fs.readFileSync(path.join(__dirname, "../cert/key.pem")), + cert: fs.readFileSync(path.join(__dirname, "../cert/cert.pem")) +}, +app); -const wss = new WebSocket.Server({server}); +// Websocket server (browser connections) +const wss = new WebSocket.Server({ server }); -const fluid = new FluidNCClient(); +// Create FluidNC WebSocket client +const fluid = new FluidNCClient(config.fluidnc); +// Connected browser clients let clients = []; -wss.on("connection",(ws)=>{ - - console.log("[WS] Client connected"); - +wss.on("connection", (ws) => { + console.log("[WS] Browser connected"); clients.push(ws); - ws.on("message",(msg)=>{ - - try{ - + ws.on("message", (msg) => { + try { const data = JSON.parse(msg); - console.log("[WS] Received message:", data); - - if(data.type==="jog"){ - - fluid.jog(data.axis,data.value); - + if (data.type === "jog") { + fluid.jog(data.axis, data.value); } - if(data.type==="gcode"){ - + if (data.type === "gcode") { fluid.sendGcode(data.cmd); - } - if(data.type==="zero"){ - + if (data.type === "zero") { fluid.setZero(); - } - }catch(e){ - - console.log("[WS] Error parsing message:", e); - + } catch (e) { + console.log("[WS] Error parsing:", e); } - }); - ws.on("close",()=>{ - - clients = clients.filter(c=>c!==ws); - - console.log("[WS] Client disconnected"); - + ws.on("close", () => { + clients = clients.filter(c => c !== ws); + console.log("[WS] Browser disconnected"); }); - }); -fluid.onMessage((msg)=>{ - - const json = JSON.stringify(msg); - - clients.forEach(c=>{ - - if(c.readyState===WebSocket.OPEN){ - - c.send(json); - +// FluidNC → Browser broadcasting +fluid.onMessage((msg) => { + clients.forEach(c => { + if (c.readyState === WebSocket.OPEN) { + c.send(msg.toString()); } - }); - }); - -server.listen(config.server.port,()=>{ +// Status polling ("?" every 200ms) +setInterval(() => { + fluid.requestStatus(); +}, 200); + +server.listen(config.server.port, () => { console.log("[Server] Running at:"); console.log(`https://localhost:${config.server.port}`); - }); \ No newline at end of file diff --git a/web/index.html b/web/index.html index fc78cf7..e532c5f 100644 --- a/web/index.html +++ b/web/index.html @@ -1,132 +1,142 @@ - + + + SCARA Robot Control + + button { + background: #444; + border: none; + padding: 15px; + font-size: 18px; + color: white; + border-radius: 8px; + cursor: pointer; + } + button:hover { + background: #666; + } + -

Trying to connect to FluidNC...

+

SCARA Robot Control

-
+
+
X: 0.000
+
Z: 0.000
+
-

X Axis

+

Jog Controls

- - +
+ + + -0 + + + +
- - + + document.getElementById("posX").textContent = x.toFixed(3); + document.getElementById("posZ").textContent = z.toFixed(3); + } + \ No newline at end of file