From bfb84fab50cdcdc09be0d6bc5cda45fd652e8719 Mon Sep 17 00:00:00 2001 From: chk <79915315+ChKendel@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:59:56 +0200 Subject: [PATCH] E-Stop IP --- data/robot/robot.json | 7 ++++++- logs/gcode_commands.log | 5 +++++ logs/pings.log | 2 ++ robot/RobotConfig.js | 16 +++++++++++----- robot/ShellyEmergencyStop.js | 8 +++++--- startRobot.js | 5 ++++- test/RobotConfig.test.js | 30 ++++++++++++++++++++++++++++++ test/ShellyEmergencyStop.test.js | 17 +++++++++++++++-- 8 files changed, 78 insertions(+), 12 deletions(-) diff --git a/data/robot/robot.json b/data/robot/robot.json index dc4c9af..3408061 100644 --- a/data/robot/robot.json +++ b/data/robot/robot.json @@ -17,7 +17,12 @@ "base": { "ip": "fluidNcBase.local", "port": 2300, "protocol": "telnet", "axes": ["x", "y", "z"], "heartbeatInterval": 5000 }, "elbow": { "ip": "fluidNcEllbow.local", "port": 5000, "protocol": "telnet", "axes": ["a", null, null], "heartbeatInterval": 5000 }, "hand": { "ip": "fluidNcHand.local", "port": 5000, "protocol": "telnet", "axes": ["c", "e", "b"], "heartbeatInterval": 5000 }, - "emergencyStop": { "protocol": "shelly", "url": "http://shelly1pmminig4-acebe6f095f4.local/rpc/Switch.Set?id=0&on=false" } + "emergencyStop": { + "protocol": "shelly", + "url": "http://192.168.0.238/rpc/Switch.Set?id=0&on=false", + "urlOn": "http://192.168.0.238/rpc/Switch.Set?id=0&on=true", + "urlStatus": "http://192.168.0.238/rpc/Switch.GetStatus?id=0" + } }, "vision_config": {"MarkerType": "DICT_4X4_250", "MarkerSize": 0.025}, "renderingInfo": { diff --git a/logs/gcode_commands.log b/logs/gcode_commands.log index 46874c7..f0270e0 100644 --- a/logs/gcode_commands.log +++ b/logs/gcode_commands.log @@ -10409,3 +10409,8 @@ 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 +2026-06-12T16:56:02.840Z ::ffff:127.0.0.1: M114 +2026-06-12T16:56:02.863Z ::ffff:127.0.0.1: G1 X1 Y2 Z3 +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 diff --git a/logs/pings.log b/logs/pings.log index dfe0bd9..9244124 100644 --- a/logs/pings.log +++ b/logs/pings.log @@ -14638,3 +14638,5 @@ 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 +2026-06-12T16:56:02.816Z ::ffff:127.0.0.1 : Ping +2026-06-12T16:56:03.315Z ::ffff:127.0.0.1 : Ping diff --git a/robot/RobotConfig.js b/robot/RobotConfig.js index 53f0bef..1ba4473 100644 --- a/robot/RobotConfig.js +++ b/robot/RobotConfig.js @@ -17,8 +17,8 @@ const DEFAULTS = { elbow: { ip: 'fluidNcEllbow.local', port: 5000, protocol: 'telnet', axes: ['a', null, null], heartbeatInterval: 10000 }, hand: { ip: 'fluidNcHand.local', port: 5000, protocol: 'telnet', axes: ['c', 'e', 'b'], heartbeatInterval: 10000 }, // Shelly Smart Plug: schaltet Strom für Emergency Stop. - // url: null → deaktiviert, wenn nicht in robot.json konfiguriert. - emergencyStop: { protocol: 'shelly', url: null } + // url/urlOn/urlStatus: null → deaktiviert, wenn nicht in robot.json konfiguriert. + emergencyStop: { protocol: 'shelly', url: null, urlOn: null, urlStatus: null } } }; @@ -87,10 +87,16 @@ function load(fsModule, processEnv, consoleObj) { const cfg = jsonControllers[key] ?? {}; if (def.protocol === 'shelly') { - // Shelly Smart Plug: nur protocol + url nötig (kein IP/Port/Axes/Heartbeat) + // Shelly Smart Plug: protocol + drei URLs (off / on / status). + // SHELLY_URL überschreibt url aus robot.json (analog zu GRBL_BASE_IP). + // urlOn/urlStatus: explizit konfigurierbar; fehlen sie, leitet ShellyEmergencyStop + // sie aus url ab (url.replace('on=false','on=true') bzw. /rpc/Switch.GetStatus). + const envUrl = env_.SHELLY_URL; controllers[key] = { - protocol: cfg.protocol ?? def.protocol, - url: cfg.url ?? def.url, + protocol: cfg.protocol ?? def.protocol, + url: envUrl ?? cfg.url ?? def.url, + urlOn: cfg.urlOn ?? def.urlOn, + urlStatus: cfg.urlStatus ?? def.urlStatus, }; } else { // Telnet (FluidNC): IP + Port + Achsen + Heartbeat diff --git a/robot/ShellyEmergencyStop.js b/robot/ShellyEmergencyStop.js index eeffdf0..8e7f691 100644 --- a/robot/ShellyEmergencyStop.js +++ b/robot/ShellyEmergencyStop.js @@ -25,9 +25,11 @@ module.exports = class ShellyEmergencyStop extends SenderInterface { constructor(url, options = {}) { super(); this._offUrl = url || null; - this._onUrl = url ? url.replace('on=false', 'on=true') : null; - // Switch.GetStatus?id=0 — leitet Status-URL aus der Switch.Set-URL ab - this._statusUrl = url ? url.replace(/\/rpc\/.*$/, '/rpc/Switch.GetStatus?id=0') : null; + // Explizite URL-Felder haben Vorrang vor Ableitungslogik. + // Das erlaubt IP-Adressen statt .local-Hostnamen (nötig in Docker-Umgebungen, + // wo mDNS nicht verfügbar ist). + this._onUrl = options.urlOn || (url ? url.replace('on=false', 'on=true') : null); + this._statusUrl = options.urlStatus || (url ? url.replace(/\/rpc\/.*$/, '/rpc/Switch.GetStatus?id=0') : null); this.url = url || null; // für getStatus / InfoServer-Anzeige this.state = 'ready'; this.error = null; diff --git a/startRobot.js b/startRobot.js index 5a2f935..34e45bc 100755 --- a/startRobot.js +++ b/startRobot.js @@ -97,7 +97,10 @@ function createApp(options = {}) { if (ctrl.protocol === 'shelly') { // Shelly Smart Plug: kein GCode-Empfänger, nur Emergency-Stop-Aktor - const instance = new ShellyClass(ctrl.url); + const instance = new ShellyClass(ctrl.url, { + urlOn: ctrl.urlOn, + urlStatus: ctrl.urlStatus, + }); senders.push({ name, instance, isGCodeReceiver: false }); } else { // Telnet (FluidNC): Konstruktor erwartet 7 Achsen-Slots (x y z a b c e) vor dem diff --git a/test/RobotConfig.test.js b/test/RobotConfig.test.js index a8d56ea..92613fa 100644 --- a/test/RobotConfig.test.js +++ b/test/RobotConfig.test.js @@ -182,6 +182,8 @@ describe('RobotConfig.load — emergencyStop (Shelly)', () => { test('DEFAULTS.controllers.emergencyStop hat protocol=shelly und url=null', () => { expect(DEFAULTS.controllers.emergencyStop.protocol).toBe('shelly'); expect(DEFAULTS.controllers.emergencyStop.url).toBeNull(); + expect(DEFAULTS.controllers.emergencyStop.urlOn).toBeNull(); + expect(DEFAULTS.controllers.emergencyStop.urlStatus).toBeNull(); }); test('emergencyStop.url aus robot.json wird übernommen', () => { @@ -198,11 +200,39 @@ describe('RobotConfig.load — emergencyStop (Shelly)', () => { expect(cfg.controllers.emergencyStop.url).toBe(shellyUrl); }); + test('emergencyStop.urlOn + urlStatus aus robot.json werden übernommen (IP statt .local)', () => { + const base = 'http://192.168.0.99'; + const json = { + ...FULL_ROBOT_JSON, + controllers: { + ...FULL_ROBOT_JSON.controllers, + emergencyStop: { + protocol: 'shelly', + url: `${base}/rpc/Switch.Set?id=0&on=false`, + urlOn: `${base}/rpc/Switch.Set?id=0&on=true`, + urlStatus: `${base}/rpc/Switch.GetStatus?id=0`, + } + } + }; + const cfg = load(makeFs(JSON.stringify(json)), {}, log); + expect(cfg.controllers.emergencyStop.url).toBe(`${base}/rpc/Switch.Set?id=0&on=false`); + expect(cfg.controllers.emergencyStop.urlOn).toBe(`${base}/rpc/Switch.Set?id=0&on=true`); + expect(cfg.controllers.emergencyStop.urlStatus).toBe(`${base}/rpc/Switch.GetStatus?id=0`); + }); + + test('SHELLY_URL Env-Variable überschreibt url aus robot.json', () => { + const envUrl = 'http://192.168.0.99/rpc/Switch.Set?id=0&on=false'; + const cfg = load(makeFs(JSON.stringify(FULL_ROBOT_JSON)), { SHELLY_URL: envUrl }, log); + expect(cfg.controllers.emergencyStop.url).toBe(envUrl); + }); + test('fehlendes emergencyStop in robot.json → url=null (Default)', () => { // FULL_ROBOT_JSON hat kein emergencyStop → fällt auf Default zurück const cfg = load(makeFs(JSON.stringify(FULL_ROBOT_JSON)), {}, log); expect(cfg.controllers.emergencyStop.protocol).toBe('shelly'); expect(cfg.controllers.emergencyStop.url).toBeNull(); + expect(cfg.controllers.emergencyStop.urlOn).toBeNull(); + expect(cfg.controllers.emergencyStop.urlStatus).toBeNull(); }); test('emergencyStop hat keine ip/port/axes/heartbeatInterval Felder', () => { diff --git a/test/ShellyEmergencyStop.test.js b/test/ShellyEmergencyStop.test.js index fd3fd4c..c4d5dfd 100644 --- a/test/ShellyEmergencyStop.test.js +++ b/test/ShellyEmergencyStop.test.js @@ -27,12 +27,12 @@ describe('ShellyEmergencyStop — Konstruktor', () => { expect(s.url).toBe(OFF_URL); }); - test('_onUrl ersetzt on=false durch on=true', () => { + test('_onUrl ersetzt on=false durch on=true (Ableitung)', () => { const s = new ShellyEmergencyStop(OFF_URL); expect(s._onUrl).toBe(ON_URL); }); - test('_statusUrl zeigt auf Switch.GetStatus', () => { + test('_statusUrl zeigt auf Switch.GetStatus (Ableitung)', () => { const s = new ShellyEmergencyStop(OFF_URL); expect(s._statusUrl).toBe(STATUS_URL); }); @@ -49,6 +49,19 @@ describe('ShellyEmergencyStop — Konstruktor', () => { expect(s.state).toBe('ready'); expect(s.error).toBeNull(); }); + + test('urlOn/urlStatus aus options überschreiben Ableitungslogik (IP statt .local)', () => { + // Typischer Docker-Use-case: url hat noch den .local-Hostnamen, + // aber urlOn/urlStatus zeigen bereits auf die echte IP. + const ipBase = 'http://192.168.0.99'; + const s = new ShellyEmergencyStop(OFF_URL, { + urlOn: `${ipBase}/rpc/Switch.Set?id=0&on=true`, + urlStatus: `${ipBase}/rpc/Switch.GetStatus?id=0`, + }); + expect(s._offUrl).toBe(OFF_URL); // url bleibt + expect(s._onUrl).toBe(`${ipBase}/rpc/Switch.Set?id=0&on=true`); // explizit + expect(s._statusUrl).toBe(`${ipBase}/rpc/Switch.GetStatus?id=0`); // explizit + }); }); describe('ShellyEmergencyStop — SenderInterface', () => {