Files
appRobotDriver/robot/WSSenderGrbl.js
2026-06-08 17:28:43 +02:00

399 lines
14 KiB
JavaScript
Executable File

const WebSocket = require('ws');
const SenderInterface = require('./SenderInterface');
module.exports = class WSSenderGrbl extends SenderInterface {
constructor(urlGRBL = "grblesp.local", maxSpeedF = 5000, xAxisGrbl = "x", yAxisGrbl = "y", zAxisGrbl = "z", aAxisGrbl = null, bAxisGrbl = null, cAxisGrbl = null, eAxisGrbl = null, options = {}) {
super();
this.ws = null;
this.state = 'disconnected';
this.error = null;
this.isTestMode = false;
this.shouldReconnect = true;
this.reconnectTimer = null;
this.connectPromise = null;
this.connectResolver = null;
this.connectRejecter = null;
this.reconnectAttempt = 0;
this.urlGRBLstr = urlGRBL;
this.maxSpeedF = maxSpeedF;
this.xAxisGrbl = xAxisGrbl;
this.yAxisGrbl = yAxisGrbl;
this.zAxisGrbl = zAxisGrbl;
this.aAxisGrbl = aAxisGrbl;
this.bAxisGrbl = bAxisGrbl;
this.cAxisGrbl = cAxisGrbl;
this.eAxisGrbl = eAxisGrbl;
this.WebSocketClass = options.WebSocketClass || WebSocket;
this.setTimeoutFn = options.setTimeoutFn || setTimeout;
this.clearTimeoutFn = options.clearTimeoutFn || clearTimeout;
this.reconnectDelay = Number.isFinite(options.reconnectDelay) ? options.reconnectDelay : 2000;
this.maxReconnectDelay = Number.isFinite(options.maxReconnectDelay) ? options.maxReconnectDelay : 30000;
this.wsPort = options.wsPort || 81;
this.autoConnect = options.autoConnect !== false;
if (urlGRBL === "test.test") {
this.isTestMode = true;
this.state = 'connected';
this.shouldReconnect = false;
this.ws = { readyState: 1, written: "", send(txt) { this.written = txt; }, close() {} };
return;
}
if (this.autoConnect) {
this.connect();
console.log("🤖 WSSenderGrbl initialized: " + urlGRBL);
}
}
async connect() {
if (this.isTestMode) {
this.state = 'connected';
return Promise.resolve(this);
}
if (this.state === 'connected' && this.ws && this.ws.readyState === 1) {
return Promise.resolve(this);
}
if (this.connectPromise) {
return this.connectPromise;
}
if (this.reconnectTimer) {
this.clearTimeoutFn(this.reconnectTimer);
this.reconnectTimer = null;
}
this.state = 'connecting';
this.error = null;
this.shouldReconnect = true;
this.connectPromise = new Promise((resolve, reject) => {
this.connectResolver = resolve;
this.connectRejecter = reject;
this._tryConnect();
});
return this.connectPromise;
}
_tryConnect() {
if (!this.shouldReconnect) return;
this.state = 'connecting';
this.error = null;
const url = `ws://${this.urlGRBLstr}:${this.wsPort}`;
const ws = new this.WebSocketClass(url);
ws.on('open', () => {
if (!this.shouldReconnect) {
ws.close();
return;
}
this.ws = ws;
this.state = 'connected';
this.error = null;
this.reconnectAttempt = 0;
if (this.connectResolver) {
this.connectResolver(this);
this.connectResolver = null;
this.connectRejecter = null;
}
this.connectPromise = null;
});
ws.on('close', () => {
console.log("WS Closed " + this.urlGRBLstr);
this.ws = null;
if (this.shouldReconnect) {
this.state = 'reconnecting';
this._scheduleReconnect();
} else {
this.state = 'disconnected';
}
});
ws.on('error', (err) => {
console.log("WS Connection Error on " + this.urlGRBLstr + ": " + err.message);
this.error = err.message || String(err);
if (!this.shouldReconnect) {
this.state = 'disconnected';
if (this.connectRejecter) {
this.connectRejecter(err);
this.connectResolver = null;
this.connectRejecter = null;
this.connectPromise = null;
}
}
});
}
_scheduleReconnect() {
if (!this.shouldReconnect || this.reconnectTimer) return;
const delay = Math.min(this.reconnectDelay * 2 ** this.reconnectAttempt, this.maxReconnectDelay);
this.reconnectAttempt += 1;
console.log(`WS reconnect attempt ${this.reconnectAttempt} in ${delay}ms for ${this.urlGRBLstr}`);
this.reconnectTimer = this.setTimeoutFn(() => {
this.reconnectTimer = null;
this._tryConnect();
}, delay);
}
send(command) {
if (!this.ws || this.ws.readyState !== 1) return false;
const payload = typeof command === 'string' ? command : String(command);
if (!payload) return false;
this.ws.send(payload + "\n");
return true;
}
getStatus() {
return {
state: this.state,
url: this.urlGRBLstr,
error: this.error,
isTestMode: !!this.isTestMode,
reconnectAttempt: this.reconnectAttempt,
reconnectTimer: !!this.reconnectTimer
};
}
disconnect() {
this.shouldReconnect = false;
if (this.reconnectTimer) {
this.clearTimeoutFn(this.reconnectTimer);
this.reconnectTimer = null;
}
if (this.connectPromise && this.connectRejecter) {
this.connectRejecter(new Error('disconnect'));
this.connectPromise = null;
this.connectResolver = null;
this.connectRejecter = null;
}
if (this.ws && this.ws.readyState !== 3) {
this.ws.close();
}
this.ws = null;
this.state = 'disconnected';
this.error = null;
}
moveTo(mOld, mNew) {
this.execCommand("G1", mOld, mNew);
}
execCommand(strCommand = "G1", mOld, mNew) {
var factorTurnLift = 1.2;
var factorOpenTurn = 1.92;
var handOpenInMM = 1.0;
var data = strCommand.toString("utf-8");
if (this.xAxisGrbl == "x") {
if (Number.isFinite(mNew.x)) {
data += " x" + (mNew.x).toFixed(2).toString();
}
}
if (this.xAxisGrbl == "y") {
if (Number.isFinite(mNew.y)) {
data += " x" + (mNew.y * 180 / Math.PI).toFixed(2).toString();
}
}
if (this.xAxisGrbl == "z") {
if (Number.isFinite(mNew.z) && Number.isFinite(mNew.y)) {
data += " x" + ((mNew.z * 180 / Math.PI) - (mNew.y * 180 / Math.PI)).toFixed(2).toString();
}
}
if (this.xAxisGrbl == "a") {
if (Number.isFinite(mNew.a)) {
data += " x" + (mNew.a * 180 / Math.PI).toFixed(2).toString();
}
}
if (this.xAxisGrbl == "b") {
if (Number.isFinite(mNew.b) && Number.isFinite(mNew.z) && Number.isFinite(mNew.y)) {
data += " x" + (mNew.b * 180 / Math.PI + (mNew.z * 180 / Math.PI) - (mNew.y * 180 / Math.PI)).toFixed(2).toString();
}
}
if (this.xAxisGrbl == "c") {
if (Number.isFinite(mNew.b) && Number.isFinite(mNew.c)) {
data += " x" + ((-1) * mNew.b * 180 / Math.PI + (mNew.c * 180 / Math.PI)).toFixed(2).toString();
}
}
if (this.xAxisGrbl == "e") {
if (Number.isFinite(mNew.b) && Number.isFinite(mNew.c) && Number.isFinite(mNew.e)) {
var handUpDown = mNew.b * 180 * factorTurnLift / Math.PI;
var handTurn = mNew.c * 180 / Math.PI;
data += " x" + ((-1.0 * (-1.0 * (handUpDown) + handTurn) + mNew.e * handOpenInMM)).toFixed(2).toString();
}
}
if (this.yAxisGrbl == "x") {
if (Number.isFinite(mNew.x)) {
data += " y" + (mNew.x).toFixed(2).toString();
}
}
if (this.yAxisGrbl == "y") {
if (Number.isFinite(mNew.y)) {
data += " y" + (mNew.y * 180 / Math.PI).toFixed(2).toString();
}
}
if (this.yAxisGrbl == "z") {
if (Number.isFinite(mNew.z) && Number.isFinite(mNew.y)) {
data += " y" + ((mNew.z * 180 / Math.PI) - (mNew.y * 180 / Math.PI)).toFixed(2).toString();
}
}
if (this.yAxisGrbl == "a") {
if (Number.isFinite(mNew.a)) {
data += " y" + (mNew.a * 180 / Math.PI).toFixed(2).toString();
}
}
if (this.yAxisGrbl == "b") {
if (Number.isFinite(mNew.b) && Number.isFinite(mNew.z) && Number.isFinite(mNew.y)) {
data += " y" + (mNew.b * 180 / Math.PI + (mNew.z * 180 / Math.PI) - (mNew.y * 180 / Math.PI)).toFixed(2).toString();
}
}
if (this.yAxisGrbl == "c") {
if (Number.isFinite(mNew.b) && Number.isFinite(mNew.c)) {
var handUpDown = (mNew.b * 180 / Math.PI) * factorTurnLift;
var handTurn = (mNew.c * 180 / Math.PI);
data += " y" + (-1.0 * (handUpDown) + handTurn).toFixed(2).toString();
}
}
if (this.yAxisGrbl == "e") {
if (Number.isFinite(mNew.e)) {
data += " y" + (mNew.e * 180 / Math.PI).toFixed(2).toString();
}
}
if (this.zAxisGrbl == "x") {
if (Number.isFinite(mNew.x)) {
data += " z" + (mNew.x).toFixed(2).toString();
}
}
if (this.zAxisGrbl == "y") {
if (Number.isFinite(mNew.y)) {
data += " z" + (mNew.y * 180 / Math.PI).toFixed(2).toString();
}
}
if (this.zAxisGrbl == "z") {
if (Number.isFinite(mNew.z) && Number.isFinite(mNew.y)) {
data += " z" + ((mNew.z * 180 / Math.PI) - (mNew.y * 180 / Math.PI)).toFixed(2).toString();
}
}
if (this.zAxisGrbl == "a") {
if (Number.isFinite(mNew.a)) {
data += " z" + (mNew.a * 180 / Math.PI).toFixed(2).toString();
}
}
if (this.zAxisGrbl == "b") {
if (Number.isFinite(mNew.b)) {
data += " z" + (mNew.b * 180 / Math.PI).toFixed(2).toString();
}
}
if (this.zAxisGrbl == "c") {
if (Number.isFinite(mNew.c) && Number.isFinite(mNew.b) && Number.isFinite(mNew.z) && Number.isFinite(mNew.y)) {
data += " z" + (mNew.c * 180 / Math.PI + (mNew.b * 180 / Math.PI) + (mNew.z * 180 / Math.PI) - (mNew.y * 180 / Math.PI)).toFixed(2).toString();
}
}
if (this.zAxisGrbl == "e") {
if (Number.isFinite(mNew.e)) {
data += " z" + (mNew.e * 180 / Math.PI).toFixed(2).toString();
}
}
if (this.aAxisGrbl == "x") {
if (Number.isFinite(mNew.y)) {
data += " a" + (mNew.y * 180 / Math.PI).toFixed(2).toString();
}
}
if (this.aAxisGrbl == "y") {
if (Number.isFinite(mNew.y)) {
data += " a" + (mNew.y * 180 / Math.PI).toFixed(2).toString();
}
}
if (this.aAxisGrbl == "z") {
if (Number.isFinite(mNew.z) && Number.isFinite(mNew.y)) {
data += " a" + ((mNew.z * 180 / Math.PI) - (mNew.y * 180 / Math.PI)).toFixed(2).toString();
}
}
if (this.aAxisGrbl == "a") {
if (Number.isFinite(mNew.a)) {
data += " a" + (mNew.a * 180 / Math.PI).toFixed(2).toString();
}
}
if (this.aAxisGrbl == "b") {
if (Number.isFinite(mNew.b) && Number.isFinite(mNew.z) && Number.isFinite(mNew.y)) {
data += " a" + (mNew.b * 180 / Math.PI + (mNew.z * 180 / Math.PI) - (mNew.y * 180 / Math.PI)).toFixed(2).toString();
}
}
if (this.aAxisGrbl == "c") {
if (Number.isFinite(mNew.c) && Number.isFinite(mNew.b) && Number.isFinite(mNew.z) && Number.isFinite(mNew.y)) {
data += " a" + (mNew.c * 180 / Math.PI + (mNew.b * 180 / Math.PI) + (mNew.z * 180 / Math.PI) - (mNew.y * 180 / Math.PI)).toFixed(2).toString();
}
}
if (this.aAxisGrbl == "e") {
if (Number.isFinite(mNew.e)) {
data += " a" + (mNew.e * 180 / Math.PI).toFixed(2).toString();
}
}
if (this.bAxisGrbl == "x") {
if (Number.isFinite(mNew.x)) {
data += " b" + (mNew.x).toFixed(2).toString();
}
}
if (this.bAxisGrbl == "y") {
if (Number.isFinite(mNew.y)) {
data += " b" + (mNew.y * 180 / Math.PI).toFixed(2).toString();
}
}
if (this.bAxisGrbl == "z") {
if (Number.isFinite(mNew.z) && Number.isFinite(mNew.y)) {
data += " b" + ((mNew.z * 180 / Math.PI) - (mNew.y * 180 / Math.PI)).toFixed(2).toString();
}
}
if (this.bAxisGrbl == "a") {
if (Number.isFinite(mNew.a)) {
data += " b" + (mNew.a * 180 / Math.PI).toFixed(2).toString();
}
}
if (this.bAxisGrbl == "b") {
if (Number.isFinite(mNew.b)) {
data += " b" + (mNew.b * 180 / Math.PI).toFixed(2).toString();
}
}
if (this.bAxisGrbl == "c") {
if (Number.isFinite(mNew.c) && Number.isFinite(mNew.b) && Number.isFinite(mNew.z) && Number.isFinite(mNew.y)) {
data += " b" + (mNew.c * 180 / Math.PI + (mNew.b * 180 / Math.PI) + (mNew.z * 180 / Math.PI) - (mNew.y * 180 / Math.PI)).toFixed(2).toString();
}
}
if (this.bAxisGrbl == "e") {
if (Number.isFinite(mNew.e)) {
data += " b" + (mNew.e * 180 / Math.PI).toFixed(2).toString();
}
}
data += " f" + (this.maxSpeedF.toFixed(2).toString());
if (data.length > 3) {
if (data.indexOf("G90") == -1) {
data = "G90 " + data;
}
console.log("Driver send to 🤖 " + this.urlGRBLstr + " the message: " + data);
this.send(data);
}
}
};