167 lines
6.5 KiB
JavaScript
167 lines
6.5 KiB
JavaScript
'use strict';
|
|
|
|
const http = require('http');
|
|
const SenderInterface = require('./SenderInterface');
|
|
|
|
/**
|
|
* Steuert einen Shelly Smart Plug als Emergency-Stop-Aktor.
|
|
*
|
|
* emergencyStop() → Switch.Set?id=0&on=false (Strom abschalten)
|
|
* powerOn() → Switch.Set?id=0&on=true (Strom einschalten)
|
|
* alarmUnlock() → no-op / skipped (kein FluidNC-Alarm)
|
|
*
|
|
* Implementiert SenderInterface — empfängt aber keinen GCode (send() ist no-op).
|
|
* startRobot.js trägt diese Klasse NICHT in robot.cmdReceivers ein.
|
|
*/
|
|
module.exports = class ShellyEmergencyStop extends SenderInterface {
|
|
|
|
/**
|
|
* @param {string|null} url Shelly-RPC-URL für Switch.Set?on=false,
|
|
* z.B. "http://shelly.local/rpc/Switch.Set?id=0&on=false".
|
|
* null → kein Shelly konfiguriert (alle Methoden liefern {ok:false}).
|
|
* @param {object} options
|
|
* @param {function} [options.httpGetFn] DI für Tests; Standard: Node http.get
|
|
*/
|
|
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;
|
|
this.url = url || null; // für getStatus / InfoServer-Anzeige
|
|
this.state = 'ready';
|
|
this.error = null;
|
|
this._httpGet = options.httpGetFn || ShellyEmergencyStop._defaultHttpGet;
|
|
this._httpGetJson = options.httpGetJsonFn || ShellyEmergencyStop._defaultHttpGetJson;
|
|
}
|
|
|
|
/**
|
|
* Standard-HTTP-GET über Node's eingebautes http-Modul (kein npm-Paket nötig).
|
|
* Response-Body wird verworfen (nur Status-Code relevant).
|
|
* @private
|
|
*/
|
|
static _defaultHttpGet(url) {
|
|
return new Promise((resolve, reject) => {
|
|
http.get(url, res => {
|
|
resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, status: res.statusCode });
|
|
res.resume(); // Response-Body verwerfen
|
|
}).on('error', reject);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Standard-HTTP-GET mit JSON-Body-Parsing.
|
|
* Für Switch.GetStatus — Response-Body enthält { output, apower, voltage, ... }.
|
|
* @private
|
|
*/
|
|
static _defaultHttpGetJson(url) {
|
|
return new Promise((resolve, reject) => {
|
|
http.get(url, res => {
|
|
let body = '';
|
|
res.on('data', chunk => { body += chunk; });
|
|
res.on('end', () => {
|
|
try {
|
|
const data = JSON.parse(body);
|
|
resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, status: res.statusCode, data });
|
|
} catch {
|
|
resolve({ ok: false, status: res.statusCode, data: null, error: 'JSON parse error' });
|
|
}
|
|
});
|
|
}).on('error', reject);
|
|
});
|
|
}
|
|
|
|
// ── SenderInterface ──────────────────────────────────────────────────────
|
|
|
|
/** Shelly braucht kein persistentes Socket — sofort ready. */
|
|
async connect() { return this; }
|
|
|
|
/** Legt keine Ressourcen frei (kein Socket), aber aktualisiert den State. */
|
|
disconnect() { this.state = 'disconnected'; }
|
|
|
|
/** Kein GCode-Empfänger — wird immer ignoriert. */
|
|
send(/* cmd */) { return false; }
|
|
|
|
getStatus() {
|
|
return { state: this.state, url: this.url, error: this.error };
|
|
}
|
|
|
|
// ── Emergency Stop / Power ───────────────────────────────────────────────
|
|
|
|
/**
|
|
* Strom abschalten.
|
|
* Wird von POST /api/emergency-stop im InfoServer aufgerufen.
|
|
* @returns {{ ok: boolean, status?: number, error?: string }}
|
|
*/
|
|
async emergencyStop() {
|
|
if (!this._offUrl) return { ok: false, error: 'no shelly url configured' };
|
|
try {
|
|
const result = await this._httpGet(this._offUrl);
|
|
this.state = result.ok ? 'stopped' : 'error';
|
|
this.error = result.ok ? null : `HTTP ${result.status}`;
|
|
console.warn(`⚠️ [Shelly] power OFF → ${result.ok ? 'OK' : `HTTP ${result.status}`}`);
|
|
return result;
|
|
} catch (err) {
|
|
this.state = 'error';
|
|
this.error = err.message;
|
|
console.error(`❌ [Shelly] power OFF failed: ${err.message}`);
|
|
return { ok: false, error: err.message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Strom wieder einschalten.
|
|
* Wird von POST /api/power-on im InfoServer aufgerufen.
|
|
* @returns {{ ok: boolean, status?: number, error?: string }}
|
|
*/
|
|
async powerOn() {
|
|
if (!this._onUrl) return { ok: false, error: 'no shelly url configured' };
|
|
try {
|
|
const result = await this._httpGet(this._onUrl);
|
|
this.state = result.ok ? 'ready' : 'error';
|
|
this.error = result.ok ? null : `HTTP ${result.status}`;
|
|
console.warn(`⚠️ [Shelly] power ON → ${result.ok ? 'OK' : `HTTP ${result.status}`}`);
|
|
return result;
|
|
} catch (err) {
|
|
this.state = 'error';
|
|
this.error = err.message;
|
|
console.error(`[Shelly] power ON failed: ${err.message}`);
|
|
return { ok: false, error: err.message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shelly hat keine FluidNC-Alarme — no-op, damit InfoServer-Sammeldurchlauf
|
|
* den Shelly einfach überspringen kann.
|
|
*/
|
|
async alarmUnlock() { return { ok: true, skipped: true }; }
|
|
|
|
// ── Power Status ─────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Liest den aktuellen Schaltzustand vom Shelly (Switch.GetStatus?id=0).
|
|
* `armed = true` → output:true → Strom AN → Roboter bestromt
|
|
* `armed = false` → output:false → Strom AUS → Roboter stromlos
|
|
*
|
|
* Gibt zusätzlich Spannung und Wirkleistung zurück (für Diagnosezwecke).
|
|
* @returns {{ ok: boolean, armed: boolean, voltage?: number, power?: number, error?: string }}
|
|
*/
|
|
async getArmed() {
|
|
if (!this._statusUrl) return { ok: false, armed: false, error: 'no shelly url configured' };
|
|
try {
|
|
const result = await this._httpGetJson(this._statusUrl);
|
|
if (!result.ok || !result.data) {
|
|
return { ok: false, armed: false, error: result.error || `HTTP ${result.status}` };
|
|
}
|
|
return {
|
|
ok: true,
|
|
armed: result.data.output === true,
|
|
voltage: result.data.voltage,
|
|
power: result.data.apower,
|
|
};
|
|
} catch (err) {
|
|
return { ok: false, armed: false, error: err.message };
|
|
}
|
|
}
|
|
};
|