kopia lustrzana https://github.com/OpenBuilds/OpenBuilds-CONTROL
3707 wiersze
115 KiB
JavaScript
3707 wiersze
115 KiB
JavaScript
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = '1';
|
|
|
|
process.on('uncaughtException', function(err) {
|
|
//showErrorDialog(err, attempts = 2) // make two attempts to show an uncaughtException in a dialog
|
|
if (DEBUG) {
|
|
debug_log(err)
|
|
} else {
|
|
console.log(err);
|
|
}
|
|
})
|
|
|
|
function showErrorDialog(err, attempts) {
|
|
console.error('Attempting to show an error dialog.')
|
|
if (!attempts) return;
|
|
try {
|
|
let options = {
|
|
type: 'error',
|
|
buttons: ['OK'],
|
|
title: 'Error',
|
|
message: `An error occured.`,
|
|
detail: `${err.message}\r\r\rIf you feel this shouldn't be happening, please report it at:\r\rhttps://github.com/OpenBuilds/OpenBuilds-CONTROL/issues`,
|
|
};
|
|
let window = BrowserWindow.getFocusedWindow()
|
|
dialog.showMessageBoxSync(window, options)
|
|
} catch (e) {
|
|
console.error(`An error occurred trying show an error, ho-boy. ${e}. We'll try again ${attempts} more time(s).`)
|
|
setTimeout(() => {
|
|
showErrorDialog(err, --attempts)
|
|
}, millisecondDelay = 2000);
|
|
}
|
|
}
|
|
|
|
// To see console.log output run with `DEBUGCONTROL=true electron .` or set environment variable for DEBUGCONTROL=true
|
|
// debug_log debug overhead
|
|
DEBUG = false;
|
|
if (process.env.DEBUGCONTROL) {
|
|
DEBUG = true;
|
|
console.log("Console Debugging Enabled")
|
|
}
|
|
|
|
function debug_log() {
|
|
if (DEBUG) {
|
|
console.log.apply(this, arguments);
|
|
}
|
|
} // end Debug Logger
|
|
|
|
debug_log("Starting OpenBuilds CONTROL v" + require('./package').version)
|
|
|
|
var config = {};
|
|
config.webPorts = [3000, 3020, 3200, 3220]
|
|
config.webPortIdx = 0;
|
|
config.nextWebPort = function() {
|
|
config.webPort = config.webPorts[config.webPortIdx]
|
|
config.webPortIdx++
|
|
if (config.webPortIdx == config.webPorts.length) {
|
|
throw new Error(`No ports were available to start the http server.\r\rWe tried ports ${config.webPorts.join(",")}.`);
|
|
}
|
|
return config.webPort;
|
|
}
|
|
config.webPort = process.env.WEB_PORT || config.nextWebPort();
|
|
config.posDecimals = process.env.DRO_DECIMALS || 3;
|
|
config.grblWaitTime = 0.5;
|
|
config.firmwareWaitTime = 4;
|
|
|
|
var express = require("express");
|
|
var app = express();
|
|
var http = require("http").Server(app);
|
|
var https = require('https');
|
|
|
|
//var ioServer = require('socket.io');
|
|
const {
|
|
Server: ioServer
|
|
} = require('socket.io');
|
|
|
|
var io = new ioServer();
|
|
|
|
var fs = require('fs');
|
|
var path = require("path");
|
|
const join = require('path').join;
|
|
const {
|
|
mkdirp
|
|
} = require('mkdirp')
|
|
|
|
|
|
//const drivelist = require('drivelist'); // removed in 1.0.350 due to Drivelist stability issues
|
|
|
|
// FluidNC test
|
|
var fluidncConfig = "";
|
|
// FluidNC end test
|
|
|
|
app.use(express.static(path.join(__dirname, "app")));
|
|
//app.use(express.limit('200M'));
|
|
|
|
app.use(function setCommonHeaders(req, res, next) {
|
|
res.set("Access-Control-Allow-Private-Network", "true");
|
|
next();
|
|
});
|
|
|
|
app.all('/*', function(req, res, next) {
|
|
res.header("Access-Control-Allow-Origin", "*");
|
|
res.header("Access-Control-Allow-Headers", "X-Requested-With");
|
|
res.header("Access-Control-Allow-Private-Network", "true");
|
|
next();
|
|
});
|
|
|
|
|
|
// Interface firmware flash
|
|
app.post('/uploadCustomFirmware', (req, res) => {
|
|
// 'firmwareBin' is the name of our file input field in the HTML form
|
|
let upload = multer({
|
|
storage: storage
|
|
}).single('firmwareBin');
|
|
|
|
upload(req, res, function(err) {
|
|
// req.file contains information of uploaded file
|
|
// req.body contains information of text fields, if there were any
|
|
|
|
if (err instanceof multer.MulterError) {
|
|
return res.send(err);
|
|
} else if (err) {
|
|
return res.send(err);
|
|
}
|
|
|
|
// Display uploaded image for user validation
|
|
firmwareImagePath = req.file.path;
|
|
res.send(req.file.path);
|
|
});
|
|
});
|
|
// end Interface Firmware flash
|
|
|
|
|
|
//Note when renewing Convert zerossl cert first `openssl.exe rsa -in domain-key.key -out domain-key.key`
|
|
// fix error: App threw an error during load
|
|
// Error: error:06000066:public key routines:OPENSSL_internal:DECODE_ERROR
|
|
|
|
var httpsOptions = {
|
|
key: fs.readFileSync(path.join(__dirname, 'privkey1.pem')),
|
|
cert: fs.readFileSync(path.join(__dirname, 'fullchain1.pem'))
|
|
};
|
|
|
|
const httpsserver = https.createServer(httpsOptions, app).listen(3001, function() {
|
|
debug_log('https: listening on:' + ip.address() + ":3001");
|
|
});
|
|
|
|
const httpserver = http.listen(config.webPort, '0.0.0.0', httpServerSuccess).on('error', httpServerError);
|
|
|
|
function httpServerSuccess() {
|
|
debug_log('http: listening on:' + ip.address() + ":" + config.webPort);
|
|
if (jogWindow) {
|
|
jogWindow.loadURL(`http://localhost:${config.webPort}/`);
|
|
}
|
|
}
|
|
|
|
function httpServerError(error) {
|
|
// If unable to start (port in use) - try next port in array from config.nextWebPort()
|
|
console.error(error.message);
|
|
httpserver.listen(config.nextWebPort());
|
|
}
|
|
|
|
io.attach(httpserver);
|
|
io.attach(httpsserver);
|
|
|
|
const grblStrings = require("./grblStrings.js");
|
|
|
|
// Serial
|
|
const {
|
|
SerialPort
|
|
} = require('serialport')
|
|
const {
|
|
ReadlineParser
|
|
} = require('@serialport/parser-readline')
|
|
|
|
|
|
|
|
// telnet
|
|
const net = require('net');
|
|
var ip = require("ip");
|
|
const Evilscan = require('evilscan');
|
|
|
|
var md5 = require('md5');
|
|
var _ = require('lodash');
|
|
var formidable = require('formidable')
|
|
var lastsentuploadprogress = 0;
|
|
|
|
// Electron app
|
|
const electron = require('electron');
|
|
const electronApp = electron.app;
|
|
|
|
electronApp.setAppUserModelId("openbuilds.control")
|
|
|
|
const {
|
|
dialog
|
|
} = require('electron')
|
|
electronApp.commandLine.appendSwitch('ignore-gpu-blacklist')
|
|
electronApp.commandLine.appendSwitch('enable-gpu-rasterization')
|
|
electronApp.commandLine.appendSwitch('enable-zero-copy')
|
|
|
|
if (isElectron()) {
|
|
debug_log("Local User Data: " + electronApp.getPath('userData'))
|
|
}
|
|
|
|
const BrowserWindow = electron.BrowserWindow;
|
|
const Tray = electron.Tray;
|
|
const nativeImage = require('electron').nativeImage
|
|
const Menu = require('electron').Menu
|
|
var forceQuit
|
|
|
|
var appIcon = null,
|
|
jogWindow = null,
|
|
mainWindow = null
|
|
var autoUpdater
|
|
|
|
|
|
var updateIsDownloading = false;
|
|
if (isElectron()) {
|
|
autoUpdater = require("electron-updater").autoUpdater
|
|
var availversion = '0.0.0'
|
|
|
|
autoUpdater.on('checking-for-update', () => {
|
|
var string = 'Starting update... Please wait';
|
|
var output = {
|
|
'command': 'autoupdate',
|
|
'response': string
|
|
}
|
|
io.sockets.emit('updatedata', output);
|
|
})
|
|
autoUpdater.on('update-available', (ev, info) => {
|
|
updateIsDownloading = true;
|
|
var string = "Starting Download: v" + ev.version;
|
|
availversion = ev.version
|
|
var output = {
|
|
'command': 'autoupdate',
|
|
'response': string
|
|
}
|
|
io.sockets.emit('updatedata', output);
|
|
debug_log(JSON.stringify(ev))
|
|
})
|
|
autoUpdater.on('update-not-available', (ev, info) => {
|
|
var string = 'Update not available. Installed version: ' + require('./package').version + " / Available version: " + ev.version + ".\n";
|
|
if (require('./package').version === ev.version) {
|
|
string += "You are already running the latest version!"
|
|
}
|
|
var output = {
|
|
'command': 'autoupdate',
|
|
'response': string
|
|
}
|
|
io.sockets.emit('updatedata', output);
|
|
debug_log(JSON.stringify(ev))
|
|
})
|
|
autoUpdater.on('error', (ev, err) => {
|
|
if (err) {
|
|
var string = 'Error in auto-updater: \n' + err.split('SyntaxError')[0];
|
|
} else {
|
|
var string = 'Error in auto-updater';
|
|
}
|
|
var output = {
|
|
'command': 'autoupdate',
|
|
'response': string
|
|
}
|
|
io.sockets.emit('updatedata', output);
|
|
})
|
|
autoUpdater.on('download-progress', (ev, progressObj) => {
|
|
updateIsDownloading = true;
|
|
var string = 'Download update ... ' + ev.percent.toFixed(1) + '%';
|
|
debug_log(string)
|
|
var output = {
|
|
'command': 'autoupdate',
|
|
'response': string
|
|
}
|
|
io.sockets.emit('updatedata', output);
|
|
io.sockets.emit('updateprogress', ev.percent.toFixed(0));
|
|
})
|
|
|
|
autoUpdater.on('update-downloaded', (info) => {
|
|
var string = "New update ready";
|
|
var output = {
|
|
'command': 'autoupdate',
|
|
'response': string
|
|
}
|
|
io.sockets.emit('updatedata', output);
|
|
io.sockets.emit('updateready', availversion);
|
|
// repeat every minute
|
|
setTimeout(function() {
|
|
io.sockets.emit('updateready', availversion);
|
|
}, 1000 * 60 * 60 * 8) // 8hrs before alerting again if it was snoozed
|
|
updateIsDownloading = false;
|
|
});
|
|
} else {
|
|
debug_log("Running outside Electron: Disabled AutoUpdater")
|
|
}
|
|
|
|
if (isElectron()) {
|
|
var uploadsDir = electronApp.getPath('userData') + '/upload/';
|
|
} else {
|
|
var uploadsDir = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + 'Library/Preferences' : '/var/local')
|
|
}
|
|
var jobStartTime = false;
|
|
var jobCompletedMsg = ""; // message sent when job is done
|
|
var uploadedgcode = ""; // var to store uploaded gcode
|
|
var uploadedworkspace = ""; // var to store uploaded OpenBuildsCAM Workspace
|
|
|
|
mkdirp(uploadsDir).then(made =>
|
|
debug_log('Created Uploads Temp Directory'))
|
|
|
|
// Check USB Selective Suspend Settings
|
|
function checkPowerSettings() {
|
|
if (process.platform == 'win32') {
|
|
debug_log("Checking Power Settings")
|
|
var powerplan = "",
|
|
usbselectiveAC = false,
|
|
usbselectiveDC = false;
|
|
const {
|
|
exec
|
|
} = require('child_process');
|
|
|
|
const cfg = exec('powercfg /GETACTIVESCHEME', function(error, stdout, stderr) {
|
|
if (error) {
|
|
debug_log(error.stack);
|
|
debug_log('Error code: ' + error.code);
|
|
debug_log('Signal received: ' + error.signal);
|
|
}
|
|
// console.log('Child Process STDOUT: ' + stdout);
|
|
// console.log('Child Process STDERR: ' + stderr);
|
|
powerplan = stdout.split(":")[1].split("()")[0].trim()
|
|
});
|
|
|
|
cfg.on('exit', function(code) {
|
|
debug_log('powercfg /GETACTIVESCHEME exited with exit code ' + code);
|
|
if (code == 0) {
|
|
const usbsetting = exec('powercfg /q ' + powerplan, function(error, stdout, stderr) {
|
|
if (error) {
|
|
debug_log(error.stack);
|
|
debug_log('Error code: ' + error.code);
|
|
debug_log('Signal received: ' + error.signal);
|
|
}
|
|
// console.log('Child Process STDOUT: ' + stdout);
|
|
// console.log('Child Process STDERR: ' + stderr);
|
|
usbselective = (stdout.slice(stdout.search("USB selective suspend setting") - 1)).split("\n")
|
|
usbselective.length = 7;
|
|
|
|
if (usbselective[5].indexOf("0x00000000") != -1) {
|
|
debug_log("USB Selective Suspend DISABLED on AC power ")
|
|
status.driver.powersettings.usbselectiveAC = false;
|
|
} else if (usbselective[5].indexOf("0x00000001") != -1) {
|
|
debug_log("USB Selective Suspend ENABLED on AC power ")
|
|
status.driver.powersettings.usbselectiveAC = true;
|
|
}
|
|
|
|
if (usbselective[6].indexOf("0x00000000") != -1) {
|
|
debug_log("USB Selective Suspend DISABLED on DC power ")
|
|
status.driver.powersettings.usbselectiveDC = false;
|
|
} else if (usbselective[6].indexOf("0x00000001") != -1) {
|
|
debug_log("USB Selective Suspend ENABLED on DC power ")
|
|
status.driver.powersettings.usbselectiveDC = true;
|
|
}
|
|
});
|
|
usbsetting.on('exit', function(code) {
|
|
debug_log('powercfg /q exited with exit code ' + code);
|
|
setTimeout(function() {
|
|
debug_log(status.driver.powersettings.usbselectiveDC, status.driver.powersettings.usbselectiveAC)
|
|
}, 100);
|
|
})
|
|
}
|
|
});
|
|
// end USB Selective Suspend
|
|
}
|
|
}
|
|
|
|
|
|
var oldportslist, oldiplist;
|
|
var oldpinslist;
|
|
const iconPath = path.join(__dirname, 'app/icon.png');
|
|
const iconNoComm = path.join(__dirname, 'app/icon-notconnected.png');
|
|
const iconPlay = path.join(__dirname, 'app/icon-play.png');
|
|
const iconStop = path.join(__dirname, 'app/icon-stop.png');
|
|
const iconPause = path.join(__dirname, 'app/icon-pause.png');
|
|
const iconAlarm = path.join(__dirname, 'app/icon-bell.png');
|
|
|
|
var iosocket;
|
|
var lastCommand = false
|
|
var gcodeQueue = [];
|
|
var queuePointer = 0;
|
|
var statusLoop;
|
|
var frontEndUpdateLoop
|
|
|
|
var queueCounter;
|
|
var listPortsLoop;
|
|
|
|
var GRBL_RX_BUFFER_SIZE = 127; // 128 characters
|
|
var GRBLHAL_RX_BUFFER_SIZE = 1023; // 128 characters
|
|
var sentBuffer = [];
|
|
|
|
var xPos = 0.00;
|
|
var yPos = 0.00;
|
|
var zPos = 0.00;
|
|
var aPos = 0.00;
|
|
var xOffset = 0.00;
|
|
var yOffset = 0.00;
|
|
var zOffset = 0.00;
|
|
var aOffset = 0.00;
|
|
|
|
|
|
var feedOverride = 100,
|
|
spindleOverride = 100;
|
|
|
|
|
|
//regex to identify MD5hash on sdupload later
|
|
var re = new RegExp("^[a-f0-9]{32}");
|
|
|
|
var status = {
|
|
login: false,
|
|
driver: {
|
|
version: require('./package').version,
|
|
ipaddress: ip.address(),
|
|
operatingsystem: false,
|
|
powersettings: {
|
|
usbselectiveAC: null,
|
|
usbselectiveDC: null
|
|
},
|
|
},
|
|
machine: {
|
|
name: '',
|
|
has4thAxis: false,
|
|
inputs: [],
|
|
overrides: {
|
|
feedOverride: 100, //
|
|
spindleOverride: 100, //
|
|
realFeed: 0, //
|
|
realSpindle: 0 //
|
|
},
|
|
//
|
|
tool: {
|
|
nexttool: {
|
|
number: 0,
|
|
line: ""
|
|
}
|
|
},
|
|
modals: {
|
|
//motionmode: "G0", // G0, G1, G2, G3, G38.2, G38.3, G38.4, G38.5, G80
|
|
coordinatesys: "G54", // G54, G55, G56, G57, G58, G59
|
|
plane: "G17", // G17, G18, G19
|
|
distancemode: "G90", // G90, G91
|
|
arcdistmode: "G91.1", // G91.1
|
|
feedratemode: "G94", // G93, G94
|
|
unitsmode: "G21", // G20, G21
|
|
radiuscomp: "G40", // G40
|
|
tlomode: "G49", // G43.1, G49
|
|
// programmode: "M0", // M0, M1, M2, M30
|
|
spindlestate: "M5", // M3, M4, M5
|
|
coolantstate: "M9", // M7, M8, M9
|
|
homedRecently: false
|
|
// tool: "0",
|
|
// spindle: "0",
|
|
// feedrate: "0"
|
|
},
|
|
probe: {
|
|
x: 0.00,
|
|
y: 0.00,
|
|
z: 0.00,
|
|
state: -1
|
|
},
|
|
position: {
|
|
work: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0,
|
|
a: 0,
|
|
e: 0
|
|
},
|
|
offset: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0,
|
|
a: 0,
|
|
e: 0
|
|
}
|
|
|
|
},
|
|
firmware: {
|
|
type: "",
|
|
platform: "",
|
|
version: "",
|
|
date: "",
|
|
buffer: [],
|
|
features: [],
|
|
blockBufferSize: 0,
|
|
rxBufferSize: 0,
|
|
},
|
|
},
|
|
comms: {
|
|
connectionStatus: 0, //0 = not connected, 1 = opening, 2 = connected, 3 = playing, 4 = paused, 5 = alarm, 6 = firmware upgrade
|
|
runStatus: "Pending", // 0 = init, 1 = idle, 2 = alarm, 3 = stop, 4 = run, etc?
|
|
queue: 0,
|
|
blocked: false,
|
|
paused: false,
|
|
controllerBuffer: 0, // Seems like you are tracking available buffer? Maybe nice to have in frontend?
|
|
interfaces: {
|
|
type: "",
|
|
ports: "",
|
|
networkDevices: [],
|
|
activePort: "" // or activeIP in the case of wifi/telnet?
|
|
},
|
|
alarm: ""
|
|
},
|
|
interface: {
|
|
diskdrive: false,
|
|
firmware: {
|
|
availVersion: "",
|
|
installedVersion: "",
|
|
},
|
|
connected: false
|
|
}
|
|
};
|
|
|
|
|
|
async function findPorts() {
|
|
const ports = await SerialPort.list()
|
|
// console.log(ports)
|
|
oldportslist = ports;
|
|
status.comms.interfaces.ports = ports;
|
|
}
|
|
findPorts()
|
|
|
|
async function findChangedPorts() {
|
|
const ports = await SerialPort.list()
|
|
// console.log(ports)
|
|
status.comms.interfaces.ports = ports;
|
|
if (!_.isEqual(ports, oldportslist)) {
|
|
var newPorts = _.differenceWith(ports, oldportslist, _.isEqual)
|
|
if (newPorts.length > 0) {
|
|
debug_log("Plugged " + newPorts[0].path);
|
|
}
|
|
var removedPorts = _.differenceWith(oldportslist, ports, _.isEqual)
|
|
if (removedPorts.length > 0) {
|
|
debug_log("Unplugged " + removedPorts[0].path);
|
|
}
|
|
}
|
|
oldportslist = ports;
|
|
// throw new Error('No ports found')
|
|
findPorts()
|
|
}
|
|
|
|
// async function findDisks() {
|
|
// const drives = await drivelist.list();
|
|
// status.interface.diskdrives = drives;
|
|
// } // removed in 1.0.350 due to Drivelist stability issues
|
|
|
|
var PortCheckinterval = setInterval(function() {
|
|
if (status.comms.connectionStatus == 0) {
|
|
findChangedPorts();
|
|
}
|
|
//findDisks(); // removed in 1.0.350 due to Drivelist stability issues
|
|
}, 1000);
|
|
|
|
// var telnetCheckinterval = setInterval(function() {
|
|
// if (status.comms.connectionStatus == 0) {
|
|
// scanForTelnetDevices();
|
|
// }
|
|
// }, 30000);
|
|
// scanForTelnetDevices();
|
|
|
|
checkPowerSettings()
|
|
|
|
// JSON API
|
|
app.get('/api/version', (req, res) => {
|
|
res.header("Access-Control-Allow-Origin", "*");
|
|
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
|
data = {
|
|
"application": "OMD",
|
|
"version": require('./package').version,
|
|
"ipaddress": ip.address() + ":" + config.webPort
|
|
}
|
|
res.send(JSON.stringify(data), null, 2);
|
|
})
|
|
|
|
app.get('/activate', (req, res) => {
|
|
debug_log(req.hostname)
|
|
res.header("Access-Control-Allow-Origin", "*");
|
|
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
|
res.send('Host: ' + req.hostname + ' asked to activate OpenBuilds CONTROL v' + require('./package').version);
|
|
showJogWindow()
|
|
setTimeout(function() {
|
|
io.sockets.emit('activate', req.hostname);
|
|
}, 500);
|
|
})
|
|
|
|
// Upload
|
|
app.get('/upload', (req, res) => {
|
|
res.header("Access-Control-Allow-Origin", "*");
|
|
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
|
res.sendFile(__dirname + '/app/upload.html');
|
|
})
|
|
|
|
app.get('/gcode', (req, res) => {
|
|
if (uploadedgcode.indexOf('$') != 0) { // Ignore grblSettings jobs
|
|
res.header("Access-Control-Allow-Origin", "*");
|
|
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
|
res.send(uploadedgcode);
|
|
}
|
|
})
|
|
|
|
app.get('/workspace', (req, res) => {
|
|
res.header("Access-Control-Allow-Origin", "*");
|
|
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
|
res.send(uploadedworkspace);
|
|
})
|
|
|
|
// http-post version of runJob
|
|
|
|
|
|
app.post('/runjob', (req, res) => {
|
|
// 'firmwareBin' is the name of our file input field in the HTML form
|
|
let upload = multer({
|
|
storage: storage
|
|
}).single('file');
|
|
|
|
upload(req, res, function(err) {
|
|
// req.file contains information of uploaded file
|
|
// req.body contains information of text fields, if there were any
|
|
if (err instanceof multer.MulterError) {
|
|
return res.send(err);
|
|
} else if (err) {
|
|
return res.send(err);
|
|
}
|
|
fs.readFile(req.file.path, 'utf8', function(err, data) {
|
|
if (err) {
|
|
return console.log(err);
|
|
}
|
|
var object = {
|
|
isJob: true,
|
|
//completedMsg: "",
|
|
data: data,
|
|
}
|
|
runJob(object)
|
|
});
|
|
res.send(`Running ` + req.file.path);
|
|
|
|
});
|
|
});
|
|
|
|
|
|
// File Post
|
|
app.post('/upload', function(req, res) {
|
|
res.header("Access-Control-Allow-Origin", "*");
|
|
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
|
//debug_log(req)
|
|
uploadprogress = 0
|
|
var form = new formidable.IncomingForm();
|
|
form.maxFileSize = 300 * 1024 * 1024;
|
|
form.parse(req, function(err, fields, files) {
|
|
// debug_log(files);
|
|
});
|
|
|
|
form.on('fileBegin', function(name, file) {
|
|
debug_log(JSON.stringify(name));
|
|
debug_log(JSON.stringify(file));
|
|
debug_log('Uploading ' + file.filepath);
|
|
});
|
|
|
|
form.on('progress', function(bytesReceived, bytesExpected) {
|
|
uploadprogress = parseInt(((bytesReceived * 100) / bytesExpected).toFixed(0));
|
|
if (uploadprogress != lastsentuploadprogress) {
|
|
lastsentuploadprogress = uploadprogress;
|
|
}
|
|
debug_log('Progress ' + uploadprogress + "% / " + bytesReceived + "b");
|
|
|
|
});
|
|
|
|
form.on('file', function(name, file) {
|
|
debug_log('Uploaded ' + file.filepath);
|
|
showJogWindow()
|
|
readFile(file.filepath)
|
|
});
|
|
|
|
form.on('aborted', function() {
|
|
// Emitted when the request was aborted by the user. Right now this can be due to a 'timeout' or 'close' event on the socket. After this event is emitted, an error event will follow. In the future there will be a separate 'timeout' event (needs a change in the node core).
|
|
});
|
|
|
|
form.on('end', function() {
|
|
//Emitted when the entire request has been received, and all contained files have finished flushing to disk. This is a great place for you to send your response.
|
|
res.end();
|
|
|
|
});
|
|
|
|
res.sendFile(__dirname + '/app/upload.html');
|
|
});
|
|
|
|
app.on('certificate-error', function(event, webContents, url, error,
|
|
certificate, callback) {
|
|
event.preventDefault();
|
|
callback(true);
|
|
});
|
|
|
|
io.on("connection", function(socket) {
|
|
|
|
debug_log("New IO Connection ");
|
|
|
|
|
|
iosocket = socket;
|
|
|
|
if (status.machine.firmware.type == 'grbl') {
|
|
|
|
debug_log("Is Grbl");
|
|
|
|
|
|
// // handle Grbl RESET external input
|
|
// if (status.machine.inputs.length > 0) {
|
|
// for (i = 0; i < status.machine.inputs.length; i++) {
|
|
// switch (status.machine.inputs[i]) {
|
|
// case 'R':
|
|
// // debug_log('PIN: SOFTRESET');
|
|
// safetosend = true;
|
|
// break;
|
|
// }
|
|
// }
|
|
// } else {
|
|
// setTimeout(function() {
|
|
debug_log("Emit Grbl: 1");
|
|
io.sockets.emit('grbl', status.machine.firmware)
|
|
// }, 10000);
|
|
// }
|
|
//
|
|
// if (safetosend != undefined && safetosend == true) {
|
|
// setTimeout(function() {
|
|
// debug_log("Emit Grbl: 2");
|
|
// io.sockets.emit('grbl', status.machine.firmware)
|
|
// }, 10000);
|
|
// }
|
|
|
|
}
|
|
|
|
|
|
// Global Update loop
|
|
clearInterval(frontEndUpdateLoop);
|
|
frontEndUpdateLoop = setInterval(function() {
|
|
io.sockets.emit("status", status);
|
|
}, 100);
|
|
|
|
socket.on("scannetwork", function(data) {
|
|
scanForTelnetDevices(data)
|
|
})
|
|
|
|
socket.on("openFile", function(data) {
|
|
dialog.showOpenDialog(jogWindow, {
|
|
properties: ['openFile']
|
|
}).then(result => {
|
|
console.log(result.canceled)
|
|
console.log(result.filePaths)
|
|
var openFilePath = result.filePaths[0];
|
|
if (openFilePath !== "") {
|
|
debug_log("path" + openFilePath);
|
|
readFile(openFilePath);
|
|
}
|
|
|
|
}).catch(err => {
|
|
console.log(err)
|
|
})
|
|
})
|
|
|
|
socket.on("openInterfaceDir", function(data) {
|
|
dialog.showOpenDialog(jogWindow, {
|
|
properties: ['openDirectory'],
|
|
title: "Select the USB Flashdrive you want to use with Interface"
|
|
}).then(result => {
|
|
console.log(result.canceled)
|
|
console.log(result.filePaths)
|
|
io.sockets.emit("interfaceDrive", result.filePaths[0]);
|
|
status.interface.diskdrive = result.filePaths[0]
|
|
}).catch(err => {
|
|
console.log(err)
|
|
})
|
|
})
|
|
|
|
socket.on("openbuilds", function(data) {
|
|
const {
|
|
shell
|
|
} = require('electron')
|
|
shell.openExternal('https://www.openbuilds.com')
|
|
});
|
|
|
|
socket.on("openbuildspartstore", function(data) {
|
|
const {
|
|
shell
|
|
} = require('electron')
|
|
shell.openExternal('https://www.openbuildspartstore.com')
|
|
});
|
|
|
|
socket.on("carveco", function(data) {
|
|
const {
|
|
shell
|
|
} = require('electron')
|
|
shell.openExternal('https://carveco.com/carveco-software-range/?ref=openbuilds')
|
|
});
|
|
|
|
socket.on("fabber", function(data) {
|
|
const {
|
|
shell
|
|
} = require('electron')
|
|
shell.openExternal('https://www.getfabber.com/openbuilds?ref=OpenBuilds')
|
|
});
|
|
|
|
socket.on("lightburn", function(data) {
|
|
const {
|
|
shell
|
|
} = require('electron')
|
|
shell.openExternal('https://openbuildspartstore.com/lightburn/')
|
|
});
|
|
|
|
socket.on("vectric", function(data) {
|
|
const {
|
|
shell
|
|
} = require('electron')
|
|
shell.openExternal('https://openbuildspartstore.com/vectric/')
|
|
});
|
|
|
|
socket.on("opencam", function(data) {
|
|
const {
|
|
shell
|
|
} = require('electron')
|
|
shell.openExternal('https://cam.openbuilds.com')
|
|
});
|
|
|
|
socket.on("opendocs", function(data) {
|
|
const {
|
|
shell
|
|
} = require('electron')
|
|
shell.openExternal('https://docs.openbuilds.com/')
|
|
});
|
|
|
|
socket.on("openforum", function(data) {
|
|
const {
|
|
shell
|
|
} = require('electron')
|
|
shell.openExternal('https://openbuilds.com/threads/openbuilds-control-software.13121/')
|
|
});
|
|
|
|
socket.on("gpuinfo", function(data) {
|
|
// GPU
|
|
var gpuInfoWindow = new BrowserWindow({
|
|
// 1366 * 768 == minimum to cater for
|
|
width: 800,
|
|
height: 800,
|
|
fullscreen: false,
|
|
center: true,
|
|
resizable: true,
|
|
maximizable: true,
|
|
title: "OpenBuilds CONTROL: Chromium's GPU Report",
|
|
frame: true,
|
|
autoHideMenuBar: true,
|
|
//icon: '/app/favicon.png',
|
|
icon: nativeImage.createFromPath(
|
|
path.join(__dirname, "/app/favicon.png")
|
|
),
|
|
webgl: true,
|
|
experimentalFeatures: true,
|
|
experimentalCanvasFeatures: true,
|
|
offscreen: true,
|
|
backgroundColor: "#fff"
|
|
});
|
|
gpuInfoWindow.loadURL("chrome://gpu");
|
|
|
|
gpuInfoWindow.once('ready-to-show', () => {
|
|
gpuInfoWindow.show()
|
|
gpuInfoWindow.setAlwaysOnTop(true);
|
|
gpuInfoWindow.focus();
|
|
gpuInfoWindow.setAlwaysOnTop(false);
|
|
})
|
|
});
|
|
|
|
socket.on("minimisetotray", function(data) {
|
|
jogWindow.hide();
|
|
});
|
|
|
|
socket.on("minimize", function(data) {
|
|
jogWindow.minimize();
|
|
});
|
|
|
|
socket.on("maximize", function(data) {
|
|
if (jogWindow.isFullScreen()) {
|
|
jogWindow.setFullScreen(false);
|
|
}
|
|
if (jogWindow.isMaximized()) {
|
|
jogWindow.unmaximize();
|
|
} else {
|
|
jogWindow.maximize();
|
|
}
|
|
});
|
|
|
|
socket.on("fullscreen", function(data) {
|
|
if (jogWindow.isFullScreen()) {
|
|
jogWindow.setFullScreen(false);
|
|
} else {
|
|
jogWindow.setFullScreen(true);
|
|
}
|
|
});
|
|
|
|
socket.on("quit", function(data) {
|
|
if (appIcon) {
|
|
appIcon.destroy();
|
|
}
|
|
electronApp.exit(0);
|
|
});
|
|
|
|
socket.on("applyUpdate", function(data) {
|
|
autoUpdater.quitAndInstall();
|
|
})
|
|
|
|
socket.on("downloadUpdate", function(data) {
|
|
if (!updateIsDownloading) {
|
|
if (typeof autoUpdater !== 'undefined') {
|
|
autoUpdater.checkForUpdates();
|
|
} else {
|
|
debug_log("autoUpdater not found")
|
|
}
|
|
}
|
|
})
|
|
|
|
socket.on("flashGrbl", function(data) {
|
|
|
|
var port = data.port;
|
|
var firmwareImagePath = data.file;
|
|
var board = data.board
|
|
var customImg = data.customImg
|
|
console.log(__dirname, file, data.file)
|
|
if (customImg) {
|
|
var firmwarePath = data.file
|
|
} else {
|
|
var firmwarePath = path.join(__dirname, data.file)
|
|
}
|
|
|
|
console.log("-------------------------------------------")
|
|
console.log(firmwarePath)
|
|
console.log("-------------------------------------------")
|
|
|
|
if (status.comms.connectionStatus > 0) {
|
|
debug_log('WARN: Closing Port ' + port);
|
|
stopPort();
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
|
|
function flashGrblCallback(debugString, port) {
|
|
debug_log(port, debugString);
|
|
var data = {
|
|
'port': port,
|
|
'string': debugString
|
|
}
|
|
io.sockets.emit("progStatus", data);
|
|
}
|
|
|
|
// setTimeout(function() {
|
|
// var avrgirl = new Avrgirl({
|
|
// board: board,
|
|
// port: port,
|
|
// debug: function(debugString) {
|
|
// var port = this.connection.options.port;
|
|
// flashGrblCallback(debugString, port)
|
|
// }
|
|
// });
|
|
//
|
|
// debug_log(JSON.stringify(avrgirl));
|
|
//
|
|
// status.comms.connectionStatus = 6;
|
|
// avrgirl.flash(firmwarePath, function(error) {
|
|
// if (error) {
|
|
// console.error(error);
|
|
// io.sockets.emit("progStatus", 'Flashing FAILED!');
|
|
// status.comms.connectionStatus = 0;
|
|
// } else {
|
|
// console.info('done.');
|
|
// io.sockets.emit("progStatus", 'Programmed Succesfully');
|
|
// io.sockets.emit("progStatus", 'Please Reconnect');
|
|
// status.comms.connectionStatus = 0;
|
|
// }
|
|
// status.comms.connectionStatus = 0;
|
|
// });
|
|
// }, 1000)
|
|
})
|
|
|
|
socket.on("flashGrblHal", function(data) {
|
|
if (status.comms.connectionStatus > 0) {
|
|
debug_log('WARN: Closing Port ' + port);
|
|
stopPort();
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
console.log(JSON.stringify(data), null, 4);
|
|
flashGrblHal(data)
|
|
})
|
|
|
|
socket.on("flashInterface", function(data) {
|
|
if (status.comms.connectionStatus > 0) {
|
|
debug_log('WARN: Closing Port ' + port);
|
|
stopPort();
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
flashInterface(data)
|
|
})
|
|
|
|
socket.on("writeInterfaceUsbDrive", function(data) {
|
|
|
|
debug_log(data)
|
|
//data.drive = mountpoint dest
|
|
//data.controller = type of controller
|
|
if (data.controller == "blackbox4x" || data.controller == "genericgrbl") {
|
|
var probesrc = path.join(__dirname, './app/wizards/interface/PROBE/');
|
|
var profilesrc = path.join(__dirname, './app/wizards/interface/PROFILESGRBL/');
|
|
} else if (data.controller == "blackboxx32" || data.controller == "genericgrblhal") {
|
|
var probesrc = path.join(__dirname, './app/wizards/interface/PROBE/');
|
|
var profilesrc = path.join(__dirname, './app/wizards/interface/PROFILESHAL/');
|
|
}
|
|
|
|
var probedest = path.join(data.drive, "/PROBE/");
|
|
var profiledest = path.join(data.drive, "/PROFILES/");
|
|
|
|
var ncp = require('ncp').ncp;
|
|
ncp.limit = 16;
|
|
|
|
var output = {
|
|
'command': 'Interface USB Drive',
|
|
'response': "Starting to copy data to " + data.drive,
|
|
'type': 'info'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
|
|
var errorCount = 0;
|
|
|
|
if (data.ssid && data.psk) {
|
|
|
|
|
|
const folderPath = path.join(data.drive, "CONFIG");
|
|
|
|
// Create the subfolder if it doesn't exist and then write the file
|
|
fs.mkdir(folderPath, {
|
|
recursive: true
|
|
}, (err) => {
|
|
if (err) {
|
|
var output = {
|
|
'command': 'Interface USB Drive',
|
|
'response': `Failed to create folder ${folderPath}! Error: ${err}`,
|
|
'type': 'error'
|
|
};
|
|
io.sockets.emit('data', output);
|
|
} else {
|
|
var fileContent = `${data.ssid}\n${data.psk}`;
|
|
|
|
fs.writeFile(path.join(folderPath, "wifi.cfg"), fileContent, (err) => {
|
|
if (err) {
|
|
errorCount++;
|
|
var output = {
|
|
'command': 'Interface USB Drive',
|
|
'response': `Failed to create Wifi Configuration file in ${folderPath}! Error: ${err}`,
|
|
'type': 'error'
|
|
};
|
|
io.sockets.emit('data', output);
|
|
} else {
|
|
var output = {
|
|
'command': 'Interface USB Drive',
|
|
'response': `Created Wifi Configuration file in ${folderPath} successfully!`,
|
|
'type': 'success'
|
|
};
|
|
io.sockets.emit('data', output);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
ncp(probesrc, probedest, function(err) {
|
|
if (err) {
|
|
var output = {
|
|
'command': 'Interface USB Drive',
|
|
'response': "Failed to copy PROBE macros to " + probedest + ": " + JSON.stringify(err),
|
|
'type': 'error'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
errorCount++
|
|
} else {
|
|
var output = {
|
|
'command': 'Interface USB Drive',
|
|
'response': "Copied PROBE macros to " + probedest + " succesfully!",
|
|
'type': 'success'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
}
|
|
});
|
|
|
|
|
|
ncp(profilesrc, profiledest, function(err) {
|
|
if (err) {
|
|
var output = {
|
|
'command': 'Interface USB Drive',
|
|
'response': "Failed to copy MACHINE PROFILES to " + profiledest + ": " + JSON.stringify(err),
|
|
'type': 'error'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
errorCount++
|
|
} else {
|
|
var output = {
|
|
'command': 'Interface USB Drive',
|
|
'response': "Copied MACHINE PROFILES to " + profiledest + " succesfully!",
|
|
'type': 'success'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
}
|
|
});
|
|
|
|
|
|
|
|
setTimeout(function() {
|
|
if (errorCount == 0) {
|
|
var output = {
|
|
'command': 'Interface USB Drive',
|
|
'response': "Finished copying supporting files to Drive " + data.drive,
|
|
'type': 'success'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
var output = {
|
|
'command': 'Interface USB Drive',
|
|
'response': "Please Eject the drive (Safely Remove) and insert it into your Interface's USB port",
|
|
'type': 'info'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
}
|
|
}, 500);
|
|
});
|
|
|
|
socket.on("connectTo", function(data) { // If a user picks a port to connect to, open a Node SerialPort Instance to it
|
|
|
|
if (status.comms.connectionStatus < 1) {
|
|
|
|
|
|
|
|
if (data.type == "usb") {
|
|
console.log("connect", "Connecting to " + data.port + " via " + data.type);
|
|
|
|
// Fix for autoreset getting stuck on MacOS with Silabs Chip
|
|
var allowRtsCts = false
|
|
var allowHupcl = false
|
|
if (process.platform == 'darwin') {
|
|
allowRtsCts = true
|
|
allowHupcl = true
|
|
}
|
|
|
|
port = new SerialPort({
|
|
path: data.port,
|
|
baudRate: parseInt(data.baud),
|
|
rtscts: allowRtsCts,
|
|
hupcl: allowHupcl // Don't set DTR - useful for X32 Reset
|
|
});
|
|
} else if (data.type == "telnet") {
|
|
console.log("connect", "Connecting to " + data.ip + " via " + data.type);
|
|
port = net.connect(23, data.ip);
|
|
port.isOpen = true;
|
|
}
|
|
|
|
const parser = port.pipe(new ReadlineParser({
|
|
delimiter: '\r\n'
|
|
}))
|
|
|
|
|
|
// port.on("data", function(data) {
|
|
// console.log(data)
|
|
// })
|
|
|
|
port.on("error", function(err) {
|
|
if (err.message != "Port is not open") {
|
|
debug_log("Error: ", err.message);
|
|
var output = {
|
|
'command': '',
|
|
'response': "PORT ERROR: " + err.message,
|
|
'type': 'error'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
|
|
if (status.comms.connectionStatus > 0) {
|
|
debug_log('WARN: Closing Port ' + port.path);
|
|
status.comms.connectionStatus = 0;
|
|
stopPort();
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
|
|
port.on("ready", function(e) {
|
|
portOpened(port, data)
|
|
});
|
|
|
|
port.on("open", function(e) {
|
|
portOpened(port, data)
|
|
});
|
|
|
|
port.on("close", function() { // open errors will be emitted as an error event
|
|
debug_log("PORT INFO: Port closed");
|
|
var output = {
|
|
'command': 'disconnect',
|
|
'response': "PORT INFO: Port closed",
|
|
'type': 'info'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
status.comms.connectionStatus = 0;
|
|
}); // end port.onclose
|
|
|
|
function portOpened(port, data) {
|
|
debug_log("PORT INFO: Connected to " + port.path + " at " + port.baudRate);
|
|
var output = {
|
|
'command': 'connect',
|
|
'response': "PORT INFO: Port is now open: " + port.path + " - Attempting to detect Controller...",
|
|
'type': 'info'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
|
|
status.comms.connectionStatus = 1;
|
|
|
|
addQRealtime("\n"); // this causes smoothie and grblHAL to send the welcome string
|
|
|
|
var output = {
|
|
'command': 'connect',
|
|
'response': "Attempting to detect Controller (1): (Autoreset)",
|
|
'type': 'info'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
|
|
setTimeout(function() { //wait for controller to be ready
|
|
if (status.machine.firmware.type.length < 1) {
|
|
debug_log("Didnt detect firmware after AutoReset. Lets see if we have Grbl instance with a board that doesnt have AutoReset");
|
|
var output = {
|
|
'command': 'connect',
|
|
'response': "Attempting to detect Controller (2): (Ctrl+X)",
|
|
'type': 'info'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
addQRealtime(String.fromCharCode(0x18)); // ctrl-x (needed for rx/tx connection)
|
|
debug_log("Sent: Ctrl+x");
|
|
}
|
|
}, config.grblWaitTime * 1000);
|
|
|
|
setTimeout(function() { //wait for controller to be ready
|
|
if (status.machine.firmware.type.length < 1) {
|
|
debug_log("No firmware yet, probably not Grbl then. lets see if we have Smoothie?");
|
|
var output = {
|
|
'command': 'connect',
|
|
'response': "Attempting to detect Controller (3): (others)",
|
|
'type': 'info'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
addQRealtime("version\n"); // Check if it's Smoothieware?
|
|
debug_log("Sent: version");
|
|
}
|
|
}, config.grblWaitTime * 2000);
|
|
|
|
setTimeout(function() {
|
|
|
|
if (status.machine.firmware.type.length > 1) {
|
|
if (status.machine.firmware.type === "grbl") {
|
|
debug_log("GRBL detected");
|
|
var output = {
|
|
'command': 'connect',
|
|
'response': "Detecting Firmware: Detected Grbl Succesfully",
|
|
'type': 'info'
|
|
}
|
|
|
|
setTimeout(function() {
|
|
io.sockets.emit('grbl', status.machine.firmware)
|
|
//v1.0.318 - commented out as a test - too many normal alarms clear prematurely
|
|
//io.sockets.emit('errorsCleared', true);
|
|
}, 600)
|
|
// Start interval for status queries
|
|
clearInterval(statusLoop);
|
|
statusLoop = setInterval(function() {
|
|
if (status.comms.connectionStatus > 0) {
|
|
addQRealtime("?");
|
|
}
|
|
}, 200);
|
|
status.machine.modals.homedRecently = false;
|
|
}
|
|
}
|
|
|
|
|
|
}, config.grblWaitTime * 3000)
|
|
|
|
if (config.firmwareWaitTime > 0) {
|
|
setTimeout(function() {
|
|
// Close port if we don't detect supported firmware after 2s.
|
|
if (status.machine.firmware.type.length < 1) {
|
|
debug_log("No supported firmware detected. Closing port " + port.path);
|
|
if (status.interface.connected) {
|
|
var output = {
|
|
'command': 'connect',
|
|
'response': `ERROR!: Connection established to INTERFACE, but no response from Grbl on the upstream controller. See https://docs.openbuilds.com/interface for more details. Closing port ` + port.path,
|
|
'type': 'error'
|
|
}
|
|
} else {
|
|
var output = {
|
|
'command': 'connect',
|
|
'response': `ERROR!: No Response from Controller - See https://docs.openbuilds.com/doku.php?id=docs:blackbox:faq-usb-connection-failed for troubleshooting information. Closing port ` + port.path,
|
|
'type': 'error'
|
|
}
|
|
}
|
|
io.sockets.emit('data', output);
|
|
stopPort();
|
|
} else {
|
|
if (data.type == "usb") {
|
|
var output = {
|
|
'command': 'connect',
|
|
'response': "Firmware Detected: " + status.machine.firmware.platform + " version " + status.machine.firmware.version + " dated " + status.machine.firmware.date + " on " + port.path,
|
|
'type': 'success'
|
|
}
|
|
} else if (data.type = "telnet") {
|
|
var output = {
|
|
'command': 'connect',
|
|
'response': "Firmware Detected: " + status.machine.firmware.platform + " version " + status.machine.firmware.version + " dated " + status.machine.firmware.date + " on " + data.ip,
|
|
'type': 'success'
|
|
}
|
|
}
|
|
io.sockets.emit('data', output);
|
|
}
|
|
}, config.firmwareWaitTime * 1000);
|
|
}
|
|
|
|
|
|
status.comms.connectionStatus = 2;
|
|
if (data.type == "usb") {
|
|
status.comms.interfaces.activePort = port.path;
|
|
status.comms.interfaces.type = data.type
|
|
status.comms.interfaces.activeBaud = port.baudRate;
|
|
} else if (data.type = "telnet") {
|
|
status.comms.interfaces.activePort = data.ip;
|
|
status.comms.interfaces.type = data.type
|
|
status.comms.interfaces.activeBaud = "net";
|
|
}
|
|
}
|
|
|
|
parser.on("data", function(data) {
|
|
//console.log(data)
|
|
var command = sentBuffer[0];
|
|
|
|
if (command == "$CD" && data != "ok") {
|
|
fluidncConfig = fluidncConfig += data + "\n"
|
|
}
|
|
|
|
if (data.indexOf("<") != 0) {
|
|
debug_log('data:', data)
|
|
}
|
|
|
|
// Grbl $I parser
|
|
if (data.indexOf("[VER:") === 0) {
|
|
status.machine.name = data.split(':')[2].split(']')[0].toLowerCase()
|
|
io.sockets.emit("status", status);
|
|
io.sockets.emit("machinename", data.split(':')[2].split(']')[0].toLowerCase());
|
|
status.machine.firmware.date = data.split(':')[1].split(".")[2];
|
|
}
|
|
|
|
if (data.indexOf("[OPT:") === 0) {
|
|
|
|
var startOpt = data.search(/opt:/i) + 4;
|
|
var grblOpt;
|
|
if (startOpt > 4) {
|
|
var grblOptLen = data.substr(startOpt).search(/]/);
|
|
grblOpts = data.substr(startOpt, grblOptLen).split(/,/);
|
|
|
|
status.machine.firmware.blockBufferSize = grblOpts[1];
|
|
status.machine.firmware.rxBufferSize = grblOpts[2];
|
|
|
|
var features = []
|
|
|
|
var i = grblOpts[0].length;
|
|
while (i--) {
|
|
features.push(grblOpts[0].charAt(i))
|
|
switch (grblOpts[0].charAt(i)) {
|
|
case 'Q':
|
|
debug_log('SPINDLE_IS_SERVO Enabled')
|
|
//
|
|
break;
|
|
case 'V': // Variable spindle enabled
|
|
debug_log('Variable spindle enabled')
|
|
//
|
|
break;
|
|
case 'N': // Line numbers enabled
|
|
debug_log('Line numbers enabled')
|
|
//
|
|
break;
|
|
case 'M': // Mist coolant enabled
|
|
debug_log('Mist coolant enabled')
|
|
//
|
|
break;
|
|
case 'C': // CoreXY enabled
|
|
debug_log('CoreXY enabled')
|
|
//
|
|
break;
|
|
case 'P': // Parking motion enabled
|
|
debug_log('Parking motion enabled')
|
|
//
|
|
break;
|
|
case 'Z': // Homing force origin enabled
|
|
debug_log('Homing force origin enabled')
|
|
//
|
|
break;
|
|
case 'H': // Homing single axis enabled
|
|
debug_log('Homing single axis enabled')
|
|
//
|
|
break;
|
|
case 'T': // Two limit switches on axis enabled
|
|
debug_log('Two limit switches on axis enabled')
|
|
//
|
|
break;
|
|
case 'A': // Allow feed rate overrides in probe cycles
|
|
debug_log('Allow feed rate overrides in probe cycles')
|
|
//
|
|
break;
|
|
case '$': // Restore EEPROM $ settings disabled
|
|
debug_log('Restore EEPROM $ settings disabled')
|
|
//
|
|
break;
|
|
case '#': // Restore EEPROM parameter data disabled
|
|
debug_log('Restore EEPROM parameter data disabled')
|
|
//
|
|
break;
|
|
case 'I': // Build info write user string disabled
|
|
debug_log('Build info write user string disabled')
|
|
//
|
|
break;
|
|
case 'E': // Force sync upon EEPROM write disabled
|
|
debug_log('Force sync upon EEPROM write disabled')
|
|
//
|
|
break;
|
|
case 'W': // Force sync upon work coordinate offset change disabled
|
|
debug_log('Force sync upon work coordinate offset change disabled')
|
|
//
|
|
break;
|
|
case 'L': // Homing init lock sets Grbl into an alarm state upon power up
|
|
debug_log('Homing init lock sets Grbl into an alarm state upon power up')
|
|
//
|
|
break;
|
|
}
|
|
}
|
|
status.machine.firmware.features = features;
|
|
io.sockets.emit("features", features);
|
|
}
|
|
}
|
|
|
|
// [PRB:0.000,0.000,0.000:0]
|
|
//if (data.indexOf("[PRB:") === 0 && command != "$#" && command != undefined) {
|
|
if (data.indexOf("[PRB:") === 0) {
|
|
debug_log(data)
|
|
var prbLen = data.substr(5).search(/\]/);
|
|
var prbData = data.substr(5, prbLen).split(/,/);
|
|
var success = data.split(':')[2].split(']')[0];
|
|
status.machine.probe.x = prbData[0];
|
|
status.machine.probe.y = prbData[1];
|
|
status.machine.probe.z = prbData[2].split(':')[0];
|
|
status.machine.probe.state = success;
|
|
if (success > 0) {
|
|
var output = {
|
|
'command': '[ PROBE ]',
|
|
'response': "Probe Completed.",
|
|
'type': 'success'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
} else {
|
|
var output = {
|
|
'command': '[ PROBE ]',
|
|
'response': "Probe move ERROR - probe did not make contact within specified distance",
|
|
'type': 'error'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
}
|
|
io.sockets.emit('prbResult', status.machine.probe);
|
|
};
|
|
|
|
if (data.indexOf("[GC:") === 0) {
|
|
gotModals(data)
|
|
}
|
|
|
|
if (data.indexOf("[INTF:") === 0) {
|
|
var output = {
|
|
'command': 'connect',
|
|
'response': "Detected an OpenBuilds Interface on port " + port.path,
|
|
'type': 'success'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
status.interface.connected = true;
|
|
if (data.split(":")[1].indexOf("ver") == 0) {
|
|
var installedVersion = parseFloat(data.split(":")[1].split("]")[0].split("-")[1])
|
|
status.interface.firmware.installedVersion = installedVersion
|
|
var output = {
|
|
'command': 'connect',
|
|
'response': "OpenBuilds Interface Firmware Version: v" + installedVersion,
|
|
'type': 'info'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
if (installedVersion < status.interface.firmware.availVersion) {
|
|
var output = {
|
|
'command': 'connect',
|
|
'response': "OpenBuilds Interface Firmware OUTDATED: v" + installedVersion + " can be upgraded to v" + status.interface.firmware.availVersion,
|
|
'type': 'error'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
io.sockets.emit('interfaceOutdated', status);
|
|
}
|
|
}
|
|
io.sockets.emit("status", status);
|
|
}
|
|
|
|
// Machine Identification
|
|
if (data.indexOf("Grbl") === 0 || data.indexOf("[FIRMWARE:grblHAL]") === 0) { // Check if it's Grbl
|
|
debug_log(data)
|
|
status.comms.blocked = false;
|
|
if (data.indexOf("GrblHAL") === 0) {
|
|
status.machine.firmware.type = "grbl";
|
|
status.machine.firmware.platform = "grblHAL"
|
|
status.machine.firmware.version = data.substr(8, 4); // get version
|
|
} else if (data.indexOf("[FIRMWARE:grblHAL]") === 0) {
|
|
status.machine.firmware.type = "grbl";
|
|
status.machine.firmware.platform = "grblHAL"
|
|
// Parse version from seperate [VER:...] line not here for this response
|
|
} else if (data.indexOf("FluidNC") != -1) { // Grbl 3.6 [FluidNC v3.6.5 (wifi) '$' for help]
|
|
status.machine.firmware.type = "grbl";
|
|
status.machine.firmware.platform = "FluidNC"
|
|
status.machine.firmware.version = data.substr(19, 5); // get version
|
|
} else {
|
|
status.machine.firmware.type = "grbl";
|
|
status.machine.firmware.platform = "gnea"
|
|
status.machine.firmware.version = data.substr(5, 4); // get version
|
|
}
|
|
if (parseFloat(status.machine.firmware.version) < 1.1) { // If version is too old
|
|
if (status.machine.firmware.version.length < 3) {
|
|
debug_log('invalid version string, stay connected')
|
|
} else {
|
|
if (status.comms.connectionStatus > 0) {
|
|
debug_log('WARN: Closing Port ' + port.path + " / v" + parseFloat(status.machine.firmware.version));
|
|
// stopPort();
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
var output = {
|
|
'command': command,
|
|
'response': "Detected an unsupported version: Grbl " + status.machine.firmware.version + ". This is sadly outdated. Please upgrade to Grbl 1.1 or newer to use this software. Go to http://github.com/gnea/grbl",
|
|
'type': 'error'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
}
|
|
}
|
|
status.machine.firmware.date = "";
|
|
// debug_log("GRBL detected");
|
|
// setTimeout(function() {
|
|
// io.sockets.emit('grbl', status.machine.firmware)
|
|
// //v1.0.318 - commented out as a test - too many normal alarms clear prematurely
|
|
// //io.sockets.emit('errorsCleared', true);
|
|
// }, 600)
|
|
// // Start interval for status queries
|
|
// clearInterval(statusLoop);
|
|
// statusLoop = setInterval(function() {
|
|
// if (status.comms.connectionStatus > 0) {
|
|
// addQRealtime("?");
|
|
// }
|
|
// }, 200);
|
|
status.machine.modals.homedRecently = false;
|
|
} else if (data.indexOf("LPC176") >= 0) { // LPC1768 or LPC1769 should be Smoothieware
|
|
status.comms.blocked = false;
|
|
debug_log("Smoothieware detected");
|
|
status.machine.firmware.type = "smoothie";
|
|
status.machine.firmware.version = data.substr(data.search(/version:/i) + 9).split(/,/);
|
|
status.machine.firmware.date = new Date(data.substr(data.search(/Build date:/i) + 12).split(/,/)).toDateString();
|
|
// Start interval for status queries
|
|
// statusLoop = setInterval(function() {
|
|
// if (status.comms.connectionStatus > 0) {
|
|
// addQRealtime("?");
|
|
// }
|
|
// }, 200);
|
|
var output = {
|
|
'command': "FIRMWARE ERROR",
|
|
'response': "Detected an unsupported version: Smoothieware " + status.machine.firmware.version + ". This software no longer support Smoothieware. \nLuckilly there is an alternative firmware you can install on your controller to make it work with this software. Check out Grbl-LPC at https://github.com/cprezzi/grbl-LPC - Grbl-LPC is a Grbl port for controllers using the NXP LPC176x chips, for example Smoothieboards",
|
|
'type': 'error'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
stopPort();
|
|
} // end of machine identification
|
|
|
|
// Machine Feedback: Position
|
|
if (data.indexOf("<") === 0) {
|
|
// debug_log(' Got statusReport (Grbl & Smoothieware)')
|
|
// statusfeedback func
|
|
parseFeedback(data)
|
|
if (command == "?") {
|
|
var output = {
|
|
'command': command,
|
|
'response': data,
|
|
'type': 'info'
|
|
}
|
|
// debug_log(output.response)
|
|
io.sockets.emit('data', output);
|
|
}
|
|
|
|
// debug_log(data)
|
|
} else if (data.indexOf("ok") === 0) { // Got an OK so we are clear to send
|
|
io.sockets.emit('ok', command); // added per #325
|
|
// debug_log("OK FOUND")
|
|
if (status.machine.firmware.type === "grbl") {
|
|
// debug_log('got OK from ' + command)
|
|
command = sentBuffer.shift();
|
|
}
|
|
if (command == "$CD") {
|
|
io.sockets.emit('fluidncConfig', fluidncConfig);
|
|
}
|
|
status.comms.blocked = false;
|
|
send1Q();
|
|
} else if (data.indexOf('ALARM') === 0) { //} || data.indexOf('HALTED') === 0) {
|
|
debug_log("ALARM: " + data)
|
|
status.comms.connectionStatus = 5;
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
// sentBuffer.shift();
|
|
var alarmCode = parseInt(data.split(':')[1]);
|
|
debug_log('ALARM: ' + alarmCode + ' - ' + grblStrings.alarms(alarmCode));
|
|
status.comms.alarm = alarmCode + ' - ' + grblStrings.alarms(alarmCode)
|
|
if (alarmCode != 5) {
|
|
io.sockets.emit("toastErrorAlarm", 'ALARM: ' + alarmCode + ' - ' + grblStrings.alarms(alarmCode) + " [ " + command + " ]")
|
|
}
|
|
var output = {
|
|
'command': '',
|
|
'response': 'ALARM: ' + alarmCode + ' - ' + grblStrings.alarms(alarmCode) + " [ " + command + " ]",
|
|
'type': 'error'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
break;
|
|
}
|
|
status.comms.connectionStatus = 5;
|
|
} else if (data.indexOf('WARNING: After HALT you should HOME as position is currently unknown') != -1) { //} || data.indexOf('HALTED') === 0) {
|
|
status.comms.connectionStatus = 2;
|
|
} else if (data.indexOf('Emergency Stop Requested') != -1) { //} || data.indexOf('HALTED') === 0) {
|
|
debug_log("Emergency Stop Requested")
|
|
status.comms.connectionStatus = 5;
|
|
} else if (data.indexOf('wait') === 0) { // Got wait from Repetier -> ignore
|
|
// do nothing
|
|
} else if (data.indexOf('error') === 0) { // Error received -> stay blocked stops queue
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
// sentBuffer.shift();
|
|
var errorCode = parseInt(data.split(':')[1]);
|
|
|
|
var lastAlarm = "";
|
|
if (errorCode == 9 && status.comms.connectionStatus == 5 && status.comms.alarm.length > 0) {
|
|
lastAlarm = "<hr>This error may just be a symptom of an earlier event:<br> ALARM: " + status.comms.alarm
|
|
}
|
|
debug_log('error: ' + errorCode + ' - ' + grblStrings.errors(errorCode) + " [ " + command + " ]");
|
|
var output = {
|
|
'command': '',
|
|
'response': 'error: ' + errorCode + ' - ' + grblStrings.errors(errorCode) + " [ " + command + " ]" + lastAlarm,
|
|
'type': 'error'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
io.sockets.emit("toastError", 'error: ' + errorCode + ' - ' + grblStrings.errors(errorCode) + " [ " + command + " ]" + lastAlarm)
|
|
break;
|
|
}
|
|
debug_log("error;")
|
|
sentBuffer.shift();
|
|
status.comms.connectionStatus = 5;
|
|
} else if (data === ' ') {
|
|
// nothing
|
|
} else {
|
|
// do nothing with +data
|
|
}
|
|
|
|
if (data.indexOf("[MSG:Reset to continue]") === 0) {
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
debug_log("[MSG:Reset to continue] -> Sending Reset")
|
|
addQRealtime(String.fromCharCode(0x18)); // ctrl-x
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if (command) {
|
|
command = command.replace(/(\r\n|\n|\r)/gm, "");
|
|
// debug_log("CMD: " + command + " / DATA RECV: " + data.replace(/(\r\n|\n|\r)/gm, ""));
|
|
|
|
if (command != "?" && command != "M105" && data.length > 0 && data.indexOf('<') == -1) {
|
|
var string = "";
|
|
if (status.comms.sduploading) {
|
|
string += "SD: "
|
|
}
|
|
string += data //+ " [ " + command + " ]"
|
|
var output = {
|
|
'command': command,
|
|
'response': string,
|
|
'type': 'info'
|
|
}
|
|
// debug_log(output.response)
|
|
io.sockets.emit('data', output);
|
|
}
|
|
} else {
|
|
if (data.indexOf("<") != 0) {
|
|
var output = {
|
|
'command': "",
|
|
'response': data,
|
|
'type': 'info'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
}
|
|
}
|
|
}); // end of parser.on(data)
|
|
}
|
|
});
|
|
|
|
socket.on('saveToSd', function(datapack) {
|
|
saveToSd(datapack);
|
|
});
|
|
|
|
|
|
socket.on('setqueuePointer', function(data) {
|
|
debug_log('Setting queuePointer to ' + data)
|
|
queuePointer = data
|
|
});
|
|
|
|
socket.on('runJob', function(object) {
|
|
// debug_log(data)
|
|
runJob(object);
|
|
});
|
|
|
|
socket.on('forceQueue', function(data) {
|
|
send1Q();
|
|
});
|
|
|
|
socket.on('serialInject', function(data) {
|
|
// Inject a live command into Serial stream in real-time (dev tool) even while a job is running, etc (straight Port.write from machineSend)
|
|
machineSend(data, true);
|
|
});
|
|
|
|
socket.on("dump", function(data) {
|
|
console.log(queuePointer);
|
|
console.log(gcodeQueue);
|
|
console.log(sentBuffer);
|
|
})
|
|
|
|
socket.on('runCommand', function(data) {
|
|
debug_log('Run Command (' + data.replace('\n', '|') + ')');
|
|
if (status.comms.connectionStatus > 0) {
|
|
if (data) {
|
|
data = data.split('\n');
|
|
for (var i = 0; i < data.length; i++) {
|
|
var line = data[i].split(';'); // Remove everything after ; = comment
|
|
var tosend = line[0].trim();
|
|
if (tosend.length > 0) {
|
|
addQToEnd(tosend);
|
|
}
|
|
}
|
|
status.comms.runStatus = 'Running'
|
|
// debug_log('sending ' + JSON.stringify(gcodeQueue))
|
|
send1Q();
|
|
}
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
});
|
|
|
|
socket.on('jog', function(data) {
|
|
debug_log('Jog ' + data);
|
|
if (status.comms.connectionStatus > 0) {
|
|
data = data.split(',');
|
|
var dir = data[0];
|
|
var dist = parseFloat(data[1]);
|
|
var feed;
|
|
if (data.length > 2) {
|
|
feed = parseInt(data[2]);
|
|
if (feed) {
|
|
feed = 'F' + feed;
|
|
}
|
|
}
|
|
if (dir && dist && feed) {
|
|
debug_log('Adding jog commands to queue. Firmw=' + status.machine.firmware.type + ', blocked=' + status.comms.blocked + ', paused=' + status.comms.paused + ', Q=' + gcodeQueue.length);
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
addQToEnd('$J=G91G21' + dir + dist + feed);
|
|
send1Q();
|
|
break;
|
|
default:
|
|
debug_log('ERROR: Unknown firmware!');
|
|
break;
|
|
}
|
|
} else {
|
|
debug_log('ERROR: Invalid params!');
|
|
}
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
});
|
|
|
|
socket.on('jogXY', function(data) {
|
|
debug_log('Jog XY' + data);
|
|
if (status.comms.connectionStatus > 0) {
|
|
// var data = {
|
|
// x: xincrement,
|
|
// y: yincrement,
|
|
// feed: feed
|
|
// }
|
|
var xincrement = parseFloat(data.x);
|
|
var yincrement = parseFloat(data.y);
|
|
var feed = parseFloat(data.feed)
|
|
if (feed) {
|
|
feed = 'F' + feed;
|
|
}
|
|
|
|
if (xincrement && yincrement && feed) {
|
|
debug_log('Adding jog commands to queue. blocked=' + status.comms.blocked + ', paused=' + status.comms.paused + ', Q=' + gcodeQueue.length);
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
addQToEnd('$J=G91G21X' + xincrement + " Y" + yincrement + " " + feed);
|
|
send1Q();
|
|
break;
|
|
default:
|
|
debug_log('ERROR: Unknown firmware!');
|
|
break;
|
|
}
|
|
} else {
|
|
debug_log('ERROR: Invalid params!');
|
|
}
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
});
|
|
|
|
socket.on('jogTo', function(data) { // data = {x:xVal, y:yVal, z:zVal, mode:0(absulute)|1(relative), feed:fVal}
|
|
debug_log('JogTo ' + JSON.stringify(data));
|
|
if (status.comms.connectionStatus > 0) {
|
|
if (data.x !== undefined || data.y !== undefined || data.z !== undefined) {
|
|
var xVal = (data.x !== undefined ? 'X' + parseFloat(data.x) : '');
|
|
var yVal = (data.y !== undefined ? 'Y' + parseFloat(data.y) : '');
|
|
var zVal = (data.z !== undefined ? 'Z' + parseFloat(data.z) : '');
|
|
var mode = ((data.mode == 0) ? 0 : 1);
|
|
var feed = (data.feed !== undefined ? 'F' + parseInt(data.feed) : '');
|
|
debug_log('Adding jog commands to queue. blocked=' + status.comms.blocked + ', paused=' + status.comms.paused + ', Q=' + gcodeQueue.length);
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
addQToEnd('$J=G91G21' + mode + xVal + yVal + zVal + feed);
|
|
break;
|
|
default:
|
|
debug_log('ERROR: Unknown firmware!');
|
|
break;
|
|
}
|
|
} else {
|
|
debug_log('error Invalid params!');
|
|
}
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
});
|
|
|
|
socket.on('setZero', function(data) {
|
|
debug_log('setZero(' + data + ')');
|
|
if (status.comms.connectionStatus > 0) {
|
|
switch (data) {
|
|
case 'x':
|
|
addQToEnd('G10 L20 P0 X0');
|
|
break;
|
|
case 'y':
|
|
addQToEnd('G10 L20 P0 Y0');
|
|
break;
|
|
case 'z':
|
|
addQToEnd('G10 L20 P0 Z0');
|
|
break;
|
|
case 'a':
|
|
addQToEnd('G10 L20 P0 A0');
|
|
break;
|
|
case 'all':
|
|
addQToEnd('G10 L20 P0 X0 Y0 Z0');
|
|
break;
|
|
case 'xyza':
|
|
addQToEnd('G10 L20 P0 X0 Y0 Z0 A0');
|
|
break;
|
|
}
|
|
send1Q();
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
});
|
|
|
|
socket.on('gotoZero', function(data) {
|
|
debug_log('gotoZero(' + data + ')');
|
|
if (status.comms.connectionStatus > 0) {
|
|
switch (data) {
|
|
case 'x':
|
|
addQToEnd('G0 X0');
|
|
break;
|
|
case 'y':
|
|
addQToEnd('G0 Y0');
|
|
break;
|
|
case 'z':
|
|
addQToEnd('G0 Z0');
|
|
break;
|
|
case 'a':
|
|
addQToEnd('G0 A0');
|
|
break;
|
|
case 'all':
|
|
addQToEnd('G0 X0 Y0 Z0');
|
|
break;
|
|
case 'xyza':
|
|
addQToEnd('G0 X0 Y0 Z0 A0');
|
|
break;
|
|
}
|
|
send1Q();
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
});
|
|
|
|
socket.on('setPosition', function(data) {
|
|
debug_log('setPosition(' + JSON.stringify(data) + ')');
|
|
if (status.comms.connectionStatus > 0) {
|
|
if (data.x !== undefined || data.y !== undefined || data.z !== undefined) {
|
|
var xVal = (data.x !== undefined ? 'X' + parseFloat(data.x) + ' ' : '');
|
|
var yVal = (data.y !== undefined ? 'Y' + parseFloat(data.y) + ' ' : '');
|
|
var zVal = (data.z !== undefined ? 'Z' + parseFloat(data.z) + ' ' : '');
|
|
var aVal = (data.a !== undefined ? 'A' + parseFloat(data.a) + ' ' : '');
|
|
addQToEnd('G10 L20 P0 ' + xVal + yVal + zVal + aVal);
|
|
send1Q();
|
|
}
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
});
|
|
|
|
socket.on('probe', function(data) {
|
|
debug_log('probe(' + JSON.stringify(data) + ')');
|
|
if (status.comms.connectionStatus > 0) {
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
addQToEnd('G38.2 ' + data.direction + '-5 F1');
|
|
addQToEnd('G92 ' + data.direction + ' ' + data.probeOffset);
|
|
send1Q();
|
|
break;
|
|
default:
|
|
//not supported
|
|
debug_log('Command not supported by firmware!');
|
|
break;
|
|
}
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
});
|
|
|
|
socket.on('feedOverride', function(data) {
|
|
debug_log(data)
|
|
if (status.comms.connectionStatus > 0) {
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
debug_log("current FRO = " + status.machine.overrides.feedOverride)
|
|
debug_log("requested FRO = " + data)
|
|
var curfro = parseInt(status.machine.overrides.feedOverride)
|
|
var reqfro = parseInt(data)
|
|
var delta;
|
|
|
|
if (reqfro == 100) {
|
|
addQRealtime(String.fromCharCode(0x90));
|
|
} else if (curfro < reqfro) {
|
|
// FRO Increase
|
|
delta = reqfro - curfro
|
|
debug_log("delta = " + delta)
|
|
var tens = Math.floor(delta / 10)
|
|
|
|
debug_log("need to send " + tens + " x10s increase")
|
|
// for (i = 0; i < tens; i++) {
|
|
// addQRealtime(String.fromCharCode(0x91));
|
|
// }
|
|
for (let i = 1; i < tens + 1; i++) {
|
|
setTimeout(function timer() {
|
|
addQRealtime(String.fromCharCode(0x91));
|
|
addQRealtime("?");
|
|
}, i * 50);
|
|
}
|
|
|
|
var ones = delta - (10 * tens);
|
|
debug_log("need to send " + ones + " x1s increase")
|
|
// for (i = 0; i < ones; i++) {
|
|
// addQRealtime(String.fromCharCode(0x93));
|
|
// }
|
|
for (let i = 1; i < ones + 1; i++) {
|
|
setTimeout(function timer() {
|
|
addQRealtime(String.fromCharCode(0x93));
|
|
addQRealtime("?");
|
|
}, i * 50);
|
|
}
|
|
} else if (curfro > reqfro) {
|
|
// FRO Decrease
|
|
delta = curfro - reqfro
|
|
debug_log("delta = " + delta)
|
|
|
|
var tens = Math.floor(delta / 10)
|
|
debug_log("need to send " + tens + " x10s decrease")
|
|
// for (i = 0; i < tens; i++) {
|
|
// addQRealtime(String.fromCharCode(0x92));
|
|
// }
|
|
for (let i = 1; i < tens + 1; i++) {
|
|
setTimeout(function timer() {
|
|
addQRealtime(String.fromCharCode(0x92));
|
|
addQRealtime("?");
|
|
}, i * 50);
|
|
}
|
|
|
|
var ones = delta - (10 * tens);
|
|
debug_log("need to send " + ones + " x1s decrease")
|
|
// for (i = 0; i < tens; i++) {
|
|
// addQRealtime(String.fromCharCode(0x94));
|
|
// }
|
|
for (let i = 1; i < ones + 1; i++) {
|
|
setTimeout(function timer() {
|
|
addQRealtime(String.fromCharCode(0x94));
|
|
addQRealtime("?");
|
|
}, i * 50);
|
|
}
|
|
}
|
|
addQRealtime("?");
|
|
status.machine.overrides.feedOverride = parseInt(reqfro); // Set now, but will be overriden from feedback from Grbl itself in next queryloop
|
|
break;
|
|
}
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
});
|
|
|
|
socket.on('spindleOverride', function(data) {
|
|
if (status.comms.connectionStatus > 0) {
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
debug_log("current SRO = " + status.machine.overrides.spindleOverride)
|
|
debug_log("requested SRO = " + data)
|
|
var cursro = parseInt(status.machine.overrides.spindleOverride)
|
|
var reqsro = parseInt(data)
|
|
var delta;
|
|
|
|
if (reqsro == 100) {
|
|
addQRealtime(String.fromCharCode(153));
|
|
} else if (cursro < reqsro) {
|
|
// FRO Increase
|
|
delta = reqsro - cursro
|
|
debug_log("delta = " + delta)
|
|
var tens = Math.floor(delta / 10)
|
|
|
|
debug_log("need to send " + tens + " x10s increase")
|
|
// for (i = 0; i < tens; i++) {
|
|
// addQRealtime(String.fromCharCode(154));
|
|
// }
|
|
for (let i = 1; i < tens + 1; i++) {
|
|
setTimeout(function timer() {
|
|
addQRealtime(String.fromCharCode(154));
|
|
addQRealtime("?");
|
|
}, i * 50);
|
|
}
|
|
|
|
var ones = delta - (10 * tens);
|
|
debug_log("need to send " + ones + " x1s increase")
|
|
// for (i = 0; i < ones; i++) {
|
|
// addQRealtime(String.fromCharCode(156));
|
|
// }
|
|
for (let i = 1; i < ones + 1; i++) {
|
|
setTimeout(function timer() {
|
|
addQRealtime(String.fromCharCode(156));
|
|
addQRealtime("?");
|
|
}, i * 50);
|
|
}
|
|
} else if (cursro > reqsro) {
|
|
// FRO Decrease
|
|
delta = cursro - reqsro
|
|
debug_log("delta = " + delta)
|
|
|
|
var tens = Math.floor(delta / 10)
|
|
debug_log("need to send " + tens + " x10s decrease")
|
|
// for (i = 0; i < tens; i++) {
|
|
// addQRealtime(String.fromCharCode(155));
|
|
// }
|
|
for (let i = 1; i < tens + 1; i++) {
|
|
setTimeout(function timer() {
|
|
addQRealtime(String.fromCharCode(155));
|
|
addQRealtime("?");
|
|
}, i * 50);
|
|
}
|
|
|
|
var ones = delta - (10 * tens);
|
|
debug_log("need to send " + ones + " x1s decrease")
|
|
// for (i = 0; i < tens; i++) {
|
|
// addQRealtime(String.fromCharCode(157));
|
|
// }
|
|
for (let i = 1; i < ones + 1; i++) {
|
|
setTimeout(function timer() {
|
|
addQRealtime(String.fromCharCode(157));
|
|
addQRealtime("?");
|
|
}, i * 50);
|
|
}
|
|
}
|
|
addQRealtime("?");
|
|
status.machine.overrides.spindleOverride = parseInt(reqsro); // Set now, but will be overriden from feedback from Grbl itself in next queryloop
|
|
break;
|
|
}
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
});
|
|
|
|
socket.on('laserTest', function(data) { // Laser Test Fire
|
|
laserTest(data);
|
|
});
|
|
|
|
socket.on('pause', function() {
|
|
pause();
|
|
});
|
|
|
|
socket.on('resume', function() {
|
|
unpause();
|
|
});
|
|
|
|
socket.on('stop', function(data) {
|
|
stop(data);
|
|
});
|
|
|
|
socket.on('clearAlarm', function(data) { // Clear Alarm
|
|
if (status.comms.connectionStatus > 0) {
|
|
data = parseInt(data);
|
|
debug_log('Clearing Queue: Method ' + data);
|
|
switch (data) {
|
|
case 1:
|
|
debug_log('Clearing Lockout');
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
addQRealtime('$X\n');
|
|
debug_log('Sent: $X');
|
|
break;
|
|
}
|
|
debug_log('Resuming Queue Lockout');
|
|
var output = {
|
|
'command': '[clear alarm]',
|
|
'response': "Operator clicked Clear Alarm: Cleared Lockout",
|
|
'type': 'info'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
break;
|
|
case 2:
|
|
debug_log('Emptying Queue');
|
|
status.comms.queue = 0
|
|
queuePointer = 0;
|
|
gcodeQueue.length = 0; // Dump the queue
|
|
sentBuffer.length = 0; // Dump bufferSizes
|
|
queuePointer = 0;
|
|
debug_log('Clearing Lockout');
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
clearInterval(queueCounter);
|
|
if (jogWindow) {
|
|
jogWindow.setProgressBar(0);
|
|
}
|
|
addQRealtime(String.fromCharCode(0x18)); // ctrl-x
|
|
setTimeout(function() {
|
|
addQRealtime('$X\n');
|
|
debug_log('Sent: $X');
|
|
}, 500);
|
|
status.comms.blocked = false;
|
|
status.comms.paused = false;
|
|
break;
|
|
}
|
|
var output = {
|
|
'command': '[clear alarm]',
|
|
'response': "Operator clicked Clear Alarm: Cleared Lockout and Emptied Queue",
|
|
'type': 'info'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
break;
|
|
}
|
|
status.comms.runStatus = 'Stopped'
|
|
status.comms.connectionStatus = 2;
|
|
status.comms.alarm = "";
|
|
io.sockets.emit('errorsCleared', true);
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
});
|
|
|
|
socket.on('resetMachine', function() {
|
|
if (status.comms.connectionStatus > 0) {
|
|
debug_log('Reset Machine');
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
addQRealtime(String.fromCharCode(0x18)); // ctrl-x
|
|
debug_log('Sent: Code(0x18)');
|
|
break;
|
|
}
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
});
|
|
|
|
socket.on('closePort', function(data) { // Close machine port and dump queue
|
|
if (status.comms.connectionStatus > 0) {
|
|
debug_log('WARN: Closing Port ' + port.path);
|
|
stopPort();
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
function readFile(filePath) {
|
|
if (filePath) {
|
|
if (filePath.length > 1) {
|
|
var filename = path.parse(filePath)
|
|
filename = filename.name + filename.ext
|
|
debug_log('readfile: ' + filePath)
|
|
fs.readFile(filePath, 'utf8',
|
|
function(err, data) {
|
|
if (err) {
|
|
debug_log(err);
|
|
var output = {
|
|
'command': '',
|
|
'response': "ERROR: File Upload Failed"
|
|
}
|
|
uploadedgcode = "";
|
|
}
|
|
if (data) {
|
|
if (filePath.endsWith('.obc')) { // OpenBuildsCAM Workspace
|
|
uploadedworkspace = data;
|
|
const {
|
|
shell
|
|
} = require('electron')
|
|
shell.openExternal('https://cam.openbuilds.com')
|
|
} else { // GCODE
|
|
var payload = {
|
|
gcode: data,
|
|
filename: filename
|
|
}
|
|
io.sockets.emit('gcodeupload', payload);
|
|
uploadedgcode = data;
|
|
return data
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function machineSend(gcode, realtime) {
|
|
debug_log("SENDING: " + gcode)
|
|
if (port.isOpen) {
|
|
if (realtime) {
|
|
// realtime commands doesnt count toward the queue, does not generate OK
|
|
port.write(gcode);
|
|
} else {
|
|
if (gcode.match(/T([\d.]+)/i)) {
|
|
var tool = parseFloat(RegExp.$1);
|
|
status.machine.tool.nexttool.number = tool
|
|
status.machine.tool.nexttool.line = gcode
|
|
}
|
|
var queueLeft = parseInt((gcodeQueue.length - queuePointer))
|
|
var queueTotal = parseInt(gcodeQueue.length)
|
|
// debug_log("Q: " + queueLeft)
|
|
var data = []
|
|
data.push(queueLeft);
|
|
data.push(queueTotal);
|
|
io.sockets.emit("queueCount", data);
|
|
// debug_log(gcode)
|
|
port.write(gcode);
|
|
debug_log("SENT: " + gcode)
|
|
}
|
|
} else {
|
|
debug_log("PORT NOT OPEN")
|
|
}
|
|
}
|
|
|
|
function runJob(object) {
|
|
|
|
// object = {
|
|
// isJob: true,
|
|
// completedMsg: "",
|
|
// data: "",
|
|
// }
|
|
|
|
jobStartTime = false;
|
|
var data = object.data
|
|
|
|
if (object.isJob) {
|
|
if (data.length < 20000) {
|
|
uploadedgcode = data;
|
|
}
|
|
jobStartTime = new Date().getTime();
|
|
}
|
|
|
|
if (object.completedMsg) {
|
|
jobCompletedMsg = object.completedMsg
|
|
}
|
|
|
|
|
|
// debug_log('Run Job (' + data.length + ')');
|
|
if (status.comms.connectionStatus > 0) {
|
|
if (data) {
|
|
data = data.split('\n');
|
|
for (var i = 0; i < data.length; i++) {
|
|
|
|
var line = data[i].replace("%", "").split(';'); // Remove everything after ; = comment
|
|
var tosend = line[0].trim();
|
|
if (tosend.length > 0) {
|
|
addQToEnd(tosend);
|
|
}
|
|
}
|
|
if (i > 0) {
|
|
// Start interval for qCount messages to socket clients
|
|
queueCounter = setInterval(function() {
|
|
status.comms.queue = gcodeQueue.length - queuePointer
|
|
if (jogWindow) {
|
|
jogWindow.setProgressBar(queuePointer / gcodeQueue.length)
|
|
}
|
|
}, 500);
|
|
send1Q(); // send first line
|
|
status.comms.connectionStatus = 3;
|
|
}
|
|
|
|
}
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
}
|
|
|
|
function stopPort() {
|
|
clearInterval(queueCounter);
|
|
clearInterval(statusLoop);
|
|
if (jogWindow) {
|
|
jogWindow.setProgressBar(0);
|
|
}
|
|
status.comms.interfaces.activePort = false;
|
|
status.comms.interfaces.activeBaud = false;
|
|
status.comms.connectionStatus = 0;
|
|
status.machine.firmware.type = "";
|
|
status.machine.firmware.version = ""; // get version
|
|
status.machine.firmware.date = "";
|
|
status.machine.firmware.buffer = "";
|
|
gcodeQueue.length = 0;
|
|
sentBuffer.length = 0; // dump bufferSizes
|
|
// port.drain(port.close());
|
|
|
|
if (status.comms.interfaces.type == "usb") {
|
|
port.drain(port.close());
|
|
} else if (status.comms.interfaces.type == "telnet") {
|
|
port.destroy();
|
|
}
|
|
}
|
|
|
|
function parseFeedback(data) {
|
|
//debug_log(data)
|
|
var state = data.substring(1, data.search(/(,|\|)/));
|
|
status.comms.runStatus = state
|
|
if (state == "Alarm") {
|
|
// debug_log("ALARM: " + data)
|
|
status.comms.connectionStatus = 5;
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
//var alarmCode = parseInt(data.split(':')[1]);
|
|
debug_log('ALARM: ' + data);
|
|
//status.comms.alarm = alarmCode + ' - ' + grblStrings.alarms(alarmCode)
|
|
break;
|
|
}
|
|
status.comms.connectionStatus = 5;
|
|
} else if (state == "Hold:0") {
|
|
pause();
|
|
}
|
|
if (status.machine.firmware.type == "grbl") {
|
|
// Extract work offset (for Grbl > 1.1 only!)
|
|
var startWCO = data.search(/wco:/i) + 4;
|
|
var wco;
|
|
if (startWCO > 4) {
|
|
wco = data.replace(">", "").substr(startWCO).split(/,|\|/, 4);
|
|
}
|
|
if (Array.isArray(wco)) {
|
|
xOffset = parseFloat(wco[0]).toFixed(config.posDecimals);
|
|
yOffset = parseFloat(wco[1]).toFixed(config.posDecimals);
|
|
zOffset = parseFloat(wco[2]).toFixed(config.posDecimals);
|
|
if (status.machine.has4thAxis) {
|
|
aOffset = parseFloat(wco[3]).toFixed(config.posDecimals);
|
|
status.machine.position.offset.x = parseFloat(xOffset);
|
|
status.machine.position.offset.y = parseFloat(yOffset);
|
|
status.machine.position.offset.z = parseFloat(zOffset);
|
|
status.machine.position.offset.a = parseFloat(aOffset);
|
|
} else {
|
|
status.machine.position.offset.x = parseFloat(xOffset);
|
|
status.machine.position.offset.y = parseFloat(yOffset);
|
|
status.machine.position.offset.z = parseFloat(zOffset);
|
|
}
|
|
}
|
|
// Extract wPos (for Grbl > 1.1 only!)
|
|
var startWPos = data.search(/wpos:/i) + 5;
|
|
var wPos;
|
|
if (startWPos > 5) {
|
|
var wPosLen = data.substr(startWPos).search(/>|\|/);
|
|
wPos = data.substr(startWPos, wPosLen).split(/,/);
|
|
}
|
|
var startMPos = data.search(/mpos:/i) + 5;
|
|
var mPos;
|
|
if (startMPos > 5) {
|
|
var mPosLen = data.substr(startMPos).search(/>|\|/);
|
|
mPos = data.substr(startMPos, mPosLen).split(/,/);
|
|
}
|
|
// If we got a WPOS
|
|
if (Array.isArray(wPos)) {
|
|
// debug_log('wpos')
|
|
if (xPos !== parseFloat(wPos[0]).toFixed(config.posDecimals)) {
|
|
xPos = parseFloat(wPos[0]).toFixed(config.posDecimals);
|
|
}
|
|
if (yPos !== parseFloat(wPos[1]).toFixed(config.posDecimals)) {
|
|
yPos = parseFloat(wPos[1]).toFixed(config.posDecimals);
|
|
}
|
|
if (zPos !== parseFloat(wPos[2]).toFixed(config.posDecimals)) {
|
|
zPos = parseFloat(wPos[2]).toFixed(config.posDecimals);
|
|
}
|
|
if (wPos.length > 3) {
|
|
if (aPos !== parseFloat(wPos[3]).toFixed(config.posDecimals)) {
|
|
aPos = parseFloat(wPos[3]).toFixed(config.posDecimals);
|
|
status.machine.has4thAxis = true;
|
|
}
|
|
}
|
|
if (status.machine.has4thAxis) {
|
|
status.machine.position.work.x = parseFloat(xPos);
|
|
status.machine.position.work.y = parseFloat(yPos);
|
|
status.machine.position.work.z = parseFloat(zPos);
|
|
status.machine.position.work.a = parseFloat(aPos);
|
|
} else {
|
|
status.machine.position.work.x = parseFloat(xPos);
|
|
status.machine.position.work.y = parseFloat(yPos);
|
|
status.machine.position.work.z = parseFloat(zPos);
|
|
}
|
|
// end is WPOS
|
|
} else if (Array.isArray(mPos)) {
|
|
// debug_log('mpos', mPos)
|
|
if (xPos !== parseFloat(mPos[0]).toFixed(config.posDecimals)) {
|
|
xPos = parseFloat(mPos[0]).toFixed(config.posDecimals);
|
|
}
|
|
if (yPos !== parseFloat(mPos[1]).toFixed(config.posDecimals)) {
|
|
yPos = parseFloat(mPos[1]).toFixed(config.posDecimals);
|
|
}
|
|
if (zPos !== parseFloat(mPos[2]).toFixed(config.posDecimals)) {
|
|
zPos = parseFloat(mPos[2]).toFixed(config.posDecimals);
|
|
}
|
|
if (mPos.length > 3) {
|
|
if (aPos !== parseFloat(mPos[3]).toFixed(config.posDecimals)) {
|
|
aPos = parseFloat(mPos[3]).toFixed(config.posDecimals);
|
|
status.machine.has4thAxis = true;
|
|
}
|
|
}
|
|
if (status.machine.has4thAxis) {
|
|
status.machine.position.work.x = parseFloat(parseFloat(xPos - status.machine.position.offset.x).toFixed(config.posDecimals));
|
|
status.machine.position.work.y = parseFloat(parseFloat(yPos - status.machine.position.offset.y).toFixed(config.posDecimals));
|
|
status.machine.position.work.z = parseFloat(parseFloat(zPos - status.machine.position.offset.z).toFixed(config.posDecimals));
|
|
status.machine.position.work.a = parseFloat(parseFloat(aPos - status.machine.position.offset.a).toFixed(config.posDecimals));
|
|
} else {
|
|
status.machine.position.work.x = parseFloat(parseFloat(xPos - status.machine.position.offset.x).toFixed(config.posDecimals));
|
|
status.machine.position.work.y = parseFloat(parseFloat(yPos - status.machine.position.offset.y).toFixed(config.posDecimals));
|
|
status.machine.position.work.z = parseFloat(parseFloat(zPos - status.machine.position.offset.z).toFixed(config.posDecimals));
|
|
}
|
|
// end if MPOS
|
|
}
|
|
|
|
}
|
|
// Extract override values (for Grbl > v1.1 only!)
|
|
var startOv = data.search(/ov:/i) + 3;
|
|
if (startOv > 3) {
|
|
var ov = data.replace(">", "").substr(startOv).split(/,|\|/, 3);
|
|
if (Array.isArray(ov)) {
|
|
if (ov[0]) {
|
|
status.machine.overrides.feedOverride = parseInt(ov[0]);
|
|
}
|
|
if (ov[1]) {
|
|
status.machine.overrides.rapidOverride = parseInt(ov[1]);
|
|
}
|
|
if (ov[2]) {
|
|
status.machine.overrides.spindleOverride = parseInt(ov[2]);
|
|
}
|
|
}
|
|
}
|
|
// Extract realtime Feed and Spindle (for Grbl > v1.1 only!)
|
|
var startFS = data.search(/\|FS:/i) + 4;
|
|
if (startFS > 4) {
|
|
var fs = data.replace(">", "").substr(startFS).split(/,|\|/);
|
|
if (Array.isArray(fs)) {
|
|
if (fs[0]) {
|
|
status.machine.overrides.realFeed = parseInt(fs[0]);
|
|
}
|
|
if (fs[1]) {
|
|
status.machine.overrides.realSpindle = parseInt(fs[1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// extras realtime feed (if variable spindle is disabled)
|
|
var startF = data.search(/\|F:/i) + 3;
|
|
if (startF > 3) {
|
|
var f = data.replace(">", "").substr(startF).split(/,|\|/);
|
|
console.log(JSON.stringify(f, null, 4))
|
|
if (Array.isArray(f)) {
|
|
if (f[0]) {
|
|
status.machine.overrides.realFeed = parseInt(f[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Extract Pin Data
|
|
var startPin = data.search(/Pn:/i) + 3;
|
|
if (startPin > 3) {
|
|
var pinsdata = data.replace(">", "").replace("\r", "").substr(startPin).split(/,|\|/, 1);
|
|
var pins = pinsdata[0].split('')
|
|
status.machine.inputs = pins;
|
|
if (!_.isEqual(pins, oldpinslist)) {
|
|
if (pins.includes('H') && !pins.includes('D')) {
|
|
// pause
|
|
pause();
|
|
var output = {
|
|
'command': '[external from hardware]',
|
|
'response': "OpenBuilds CONTROL received a FEEDHOLD notification from Grbl: This could be due to someone pressing the HOLD button (if connected)",
|
|
'type': 'info'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
} // end if HOLD
|
|
|
|
if (pins.includes('D')) {
|
|
// pause
|
|
pause();
|
|
}
|
|
|
|
if (pins.includes('R')) {
|
|
// abort
|
|
stop(true);
|
|
var output = {
|
|
'command': '[external from hardware]',
|
|
'response': "OpenBuilds CONTROL received a RESET/ABORT notification from Grbl: This could be due to someone pressing the RESET/ABORT button (if connected)",
|
|
'type': 'info'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
} // end if ABORT
|
|
|
|
if (pins.includes('S')) {
|
|
// abort
|
|
unpause();
|
|
var output = {
|
|
'command': '[external from hardware]',
|
|
'response': "OpenBuilds CONTROL received a CYCLESTART/RESUME notification from Grbl: This could be due to someone pressing the CYCLESTART/RESUME button (if connected)",
|
|
'type': 'info'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
} // end if RESUME/START
|
|
}
|
|
} else {
|
|
status.machine.inputs = [];
|
|
}
|
|
oldpinslist = pins;
|
|
// Extract Buffer Data
|
|
var startBuf = data.search(/Bf:/i) + 3;
|
|
if (startBuf > 3) {
|
|
var buffer = data.replace(">", "").replace("\r", "").substr(startBuf).split(/,|\|/, 2);
|
|
// debug_log("BUF: " + JSON.stringify(buffer, null, 2));
|
|
status.machine.firmware.buffer = buffer;
|
|
} else {
|
|
status.machine.firmware.buffer = [];
|
|
}
|
|
// end statusreport
|
|
}
|
|
|
|
function gotModals(data) {
|
|
// as per https://github.com/gnea/grbl/wiki/Grbl-v1.1-Commands#g---view-gcode-parser-state
|
|
// The shown g-code are the current modal states of Grbl's g-code parser.
|
|
// This may not correlate to what is executing since there are usually
|
|
// several motions queued in the planner buffer.
|
|
// [GC:G0 G54 G17 G21 G90 G94 M5 M9 T0 F0.0 S0]
|
|
|
|
// defaults
|
|
|
|
data = data.split(/:|\[|\]/)[2].split(" ")
|
|
|
|
for (i = 0; i < data.length; i++) {
|
|
// if (data[i] == "G0") {
|
|
// status.machine.modals.motionmode = "G0";
|
|
// }
|
|
// if (data[i] == "G1") {
|
|
// status.machine.modals.motionmode = "G1";
|
|
// }
|
|
// if (data[i] == "G2") {
|
|
// status.machine.modals.motionmode = "G2";
|
|
// }
|
|
// if (data[i] == "G3") {
|
|
// status.machine.modals.motionmode = "G3";
|
|
// }
|
|
// if (data[i] == "G38.2") {
|
|
// status.machine.modals.motionmode = "G38.2";
|
|
// }
|
|
// if (data[i] == "G38.3") {
|
|
// status.machine.modals.motionmode = "G38.3";
|
|
// }
|
|
// if (data[i] == "G38.4") {
|
|
// status.machine.modals.motionmode = "G38.4";
|
|
// }
|
|
// if (data[i] == "G38.5") {
|
|
// status.machine.modals.motionmode = "G38.5";
|
|
// }
|
|
// if (data[i] == "G80") {
|
|
// status.machine.modals.motionmode = "G80";
|
|
// }
|
|
|
|
// status.machine.modals.coordinatesys = "G54"; // G54, G55, G56, G57, G58, G59
|
|
if (data[i] == "G54") {
|
|
status.machine.modals.coordinatesys = "G54";
|
|
}
|
|
if (data[i] == "G55") {
|
|
status.machine.modals.coordinatesys = "G55";
|
|
}
|
|
if (data[i] == "G56") {
|
|
status.machine.modals.coordinatesys = "G56";
|
|
}
|
|
if (data[i] == "G57") {
|
|
status.machine.modals.coordinatesys = "G57";
|
|
}
|
|
if (data[i] == "G58") {
|
|
status.machine.modals.coordinatesys = "G58";
|
|
}
|
|
if (data[i] == "G59") {
|
|
status.machine.modals.coordinatesys = "G59";
|
|
}
|
|
|
|
// status.machine.modals.plane = "G17"; // G17, G18, G19
|
|
if (data[i] == "G17") {
|
|
status.machine.modals.plane = "G17";
|
|
}
|
|
if (data[i] == "G18") {
|
|
status.machine.modals.plane = "G18";
|
|
}
|
|
if (data[i] == "G19") {
|
|
status.machine.modals.plane = "G19";
|
|
}
|
|
|
|
// status.machine.modals.distancemode = "G90"; // G90, G91
|
|
if (data[i] == "G90") {
|
|
status.machine.modals.distancemode = "G90";
|
|
}
|
|
if (data[i] == "G91") {
|
|
status.machine.modals.distancemode = "G91";
|
|
}
|
|
|
|
// status.machine.modals.arcdistmode = "G91.1"; // G91.1
|
|
if (data[i] == "G91.1") {
|
|
status.machine.modals.arcdistmode = "G91.1";
|
|
}
|
|
|
|
// status.machine.modals.feedratemode = "G94"; // G93, G94
|
|
if (data[i] == "G93") {
|
|
status.machine.modals.feedratemode = "G93";
|
|
}
|
|
if (data[i] == "G94") {
|
|
status.machine.modals.feedratemode = "G94";
|
|
}
|
|
|
|
// status.machine.modals.unitsmode = "G21"; // G20, G21
|
|
if (data[i] == "G20") {
|
|
status.machine.modals.unitsmode = "G20";
|
|
}
|
|
if (data[i] == "G21") {
|
|
status.machine.modals.unitsmode = "G21";
|
|
}
|
|
|
|
// status.machine.modals.radiuscomp = "G40"; // G40
|
|
if (data[i] == "G40") {
|
|
status.machine.modals.radiuscomp = "G40";
|
|
}
|
|
|
|
// status.machine.modals.tlomode = "G49"; // G43.1, G49
|
|
if (data[i] == "G49") {
|
|
status.machine.modals.tlomode = "G49";
|
|
}
|
|
if (data[i] == "G43.1") {
|
|
status.machine.modals.tlomode = "G43.1";
|
|
}
|
|
|
|
// status.machine.modals.programmode = "M0"; // M0, M1, M2, M30
|
|
// if (data[i] == "M0") {
|
|
// status.machine.modals.programmode = "M0";
|
|
// }
|
|
// if (data[i] == "M1") {
|
|
// status.machine.modals.programmode = "M1";
|
|
// }
|
|
// if (data[i] == "M2") {
|
|
// status.machine.modals.programmode = "M2";
|
|
// }
|
|
// if (data[i] == "M30") {
|
|
// status.machine.modals.programmode = "M30";
|
|
// }
|
|
|
|
// status.machine.modals.spindlestate = "M5"; // M3, M4, M5
|
|
if (data[i] == "M3") {
|
|
status.machine.modals.spindlestate = "M3";
|
|
}
|
|
if (data[i] == "M4") {
|
|
status.machine.modals.spindlestate = "M4";
|
|
}
|
|
if (data[i] == "M5") {
|
|
status.machine.modals.spindlestate = "M5";
|
|
}
|
|
|
|
// status.machine.modals.coolantstate = "M9"; // M7, M8, M9
|
|
if (data[i] == "M7") {
|
|
status.machine.modals.coolantstate = "M7";
|
|
}
|
|
if (data[i] == "M8") {
|
|
status.machine.modals.coolantstate = "M8";
|
|
}
|
|
if (data[i] == "M9") {
|
|
status.machine.modals.coolantstate = "M9";
|
|
}
|
|
|
|
// // status.machine.modals.tool = "0",
|
|
// if (data[i].indexOf("T") === 0) {
|
|
// status.machine.modals.tool = parseFloat(data[i].substr(1))
|
|
// }
|
|
//
|
|
// // status.machine.modals.spindle = "0"
|
|
// if (data[i].indexOf("S") === 0) {
|
|
// status.machine.modals.spindle = parseFloat(data[i].substr(1))
|
|
// }
|
|
//
|
|
// // status.machine.modals.feedrate = "0"
|
|
// if (data[i].indexOf("F") === 0) {
|
|
// status.machine.modals.feedrate = parseFloat(data[i].substr(1))
|
|
// }
|
|
}
|
|
} // end gotModals
|
|
|
|
function laserTest(data) {
|
|
if (status.comms.connectionStatus > 0) {
|
|
data = data.split(',');
|
|
var power = parseFloat(data[0]);
|
|
var duration = parseInt(data[1]);
|
|
var maxS = parseFloat(data[2]);
|
|
if (power > 0) {
|
|
if (!laserTestOn) {
|
|
// laserTest is off
|
|
// debug_log('laserTest: ' + 'Power ' + power + ', Duration ' + duration + ', maxS ' + maxS);
|
|
if (duration >= 0) {
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
addQToEnd('G1F1');
|
|
addQToEnd('M3S' + parseInt(power * maxS / 100));
|
|
laserTestOn = true;
|
|
io.sockets.emit('laserTest', power);
|
|
if (duration > 0) {
|
|
addQToEnd('G4 P' + duration / 1000);
|
|
addQToEnd('M5S0');
|
|
laserTestOn = false;
|
|
}
|
|
send1Q();
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
// debug_log('laserTest: ' + 'Power off');
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
addQToEnd('M5S0');
|
|
send1Q();
|
|
break;
|
|
}
|
|
laserTestOn = false;
|
|
io.sockets.emit('laserTest', 0);
|
|
}
|
|
}
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
}
|
|
|
|
// queue
|
|
function BufferSpace(firmware) {
|
|
var total = 0;
|
|
var len = sentBuffer.length;
|
|
for (var i = 0; i < len; i++) {
|
|
total += sentBuffer[i].length;
|
|
}
|
|
if (firmware == "grbl") {
|
|
if (status.machine.firmware.rxBufferSize > 0) {
|
|
return (status.machine.firmware.rxBufferSize - 1) - total;
|
|
} else {
|
|
if (status.machine.firmware.platform == "grblHAL") {
|
|
return GRBLHAL_RX_BUFFER_SIZE - total;
|
|
} else {
|
|
return GRBL_RX_BUFFER_SIZE - total;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
function send1Q() {
|
|
// console.time('send1Q');
|
|
var gcode;
|
|
var gcodeLen = 0;
|
|
var spaceLeft = 0;
|
|
if (status.comms.connectionStatus > 0) {
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
if ((gcodeQueue.length - queuePointer) > 0 && !status.comms.blocked && !status.comms.paused) {
|
|
spaceLeft = BufferSpace('grbl');
|
|
|
|
// Do we have enough space in the buffer?
|
|
if (gcodeQueue[queuePointer].length < spaceLeft) {
|
|
gcode = gcodeQueue[queuePointer];
|
|
queuePointer++;
|
|
sentBuffer.push(gcode);
|
|
machineSend(gcode + '\n', false);
|
|
// debug_log('Sent: ' + gcode + ' Q: ' + (gcodeQueue.length - queuePointer) + ' Bspace: ' + (spaceLeft - gcode.length - 1));
|
|
} else {
|
|
status.comms.blocked = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if (queuePointer >= gcodeQueue.length) {
|
|
if (gcodeQueue.length > 1) {
|
|
var data = {
|
|
completed: true,
|
|
failed: false,
|
|
jobCompletedMsg: jobCompletedMsg,
|
|
jobStartTime: jobStartTime,
|
|
jobEndTime: new Date().getTime()
|
|
}
|
|
io.sockets.emit('jobComplete', data);
|
|
} else {
|
|
var data = {
|
|
completed: true,
|
|
failed: true,
|
|
jobCompletedMsg: jobCompletedMsg,
|
|
jobStartTime: jobStartTime,
|
|
jobEndTime: new Date().getTime()
|
|
}
|
|
io.sockets.emit('jobComplete', data);
|
|
}
|
|
status.comms.connectionStatus = 2; // finished
|
|
clearInterval(queueCounter);
|
|
if (jogWindow) {
|
|
jogWindow.setProgressBar(0);
|
|
}
|
|
gcodeQueue.length = 0; // Dump the Queye
|
|
queuePointer = 0;
|
|
status.comms.connectionStatus = 2; // finished
|
|
jobCompletedMsg = ""
|
|
jobStartTime = false;
|
|
}
|
|
} else {
|
|
debug_log('Not Connected')
|
|
}
|
|
// console.timeEnd('send1Q');
|
|
}
|
|
|
|
var modalCommands = ['G54', 'G55', 'G56', 'G57', 'G58', 'G59', 'G17', 'G18', 'G19', 'G90', 'G91', 'G91.1', 'G93', 'G94', 'G20', 'G21', 'G40', 'G43.1', 'G49', 'M0', 'M1', 'M2', 'M30', 'M3', 'M4', 'M5', 'M7', 'M8', 'M9']
|
|
var modalCommandsRegExp = new RegExp(modalCommands.join("|"));
|
|
|
|
function addQToEnd(gcode) {
|
|
// debug_log('added ' + gcode)
|
|
gcodeQueue.push(gcode);
|
|
// if (gcode.indexOf("G54") != -1 || gcode.indexOf("G55") != -1 || gcode.indexOf("G56") != -1 || gcode.indexOf("G57") != -1 || gcode.indexOf("G58") != -1 || gcode.indexOf("G59") != -1) {
|
|
// gcodeQueue.push("$G");
|
|
// }
|
|
var testGcode = gcode.toUpperCase()
|
|
if (testGcode.indexOf("$H") != -1) {
|
|
status.machine.modals.homedRecently = true;
|
|
}
|
|
if (testGcode == "$CD") {
|
|
fluidncConfig = ""; // empty string
|
|
}
|
|
if (!gcode.startsWith("$J=") && modalCommandsRegExp.test(testGcode)) {
|
|
gcodeQueue.push("$G");
|
|
}
|
|
if (gcode.match(/T([\d.]+)/i)) {
|
|
gcodeQueue.push("$G");
|
|
}
|
|
|
|
|
|
}
|
|
|
|
function addQToStart(gcode) {
|
|
gcodeQueue.unshift(gcode);
|
|
}
|
|
|
|
function addQRealtime(gcode) {
|
|
// realtime command skip the send1Q as it doesnt respond with an ok
|
|
machineSend(gcode, true);
|
|
}
|
|
|
|
function showJogWindow() {
|
|
if (jogWindow === null) {
|
|
createJogWindow();
|
|
}
|
|
jogWindow.show()
|
|
jogWindow.setAlwaysOnTop(true);
|
|
jogWindow.focus();
|
|
jogWindow.setAlwaysOnTop(false);
|
|
}
|
|
|
|
// Electron
|
|
function isElectron() {
|
|
if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') {
|
|
return true;
|
|
}
|
|
if (typeof process !== 'undefined' && process.versions && !!process.versions.electron) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (isElectron()) {
|
|
const gotTheLock = electronApp.requestSingleInstanceLock()
|
|
var lauchGUI = true;
|
|
if (!gotTheLock) {
|
|
debug_log("Already running! Check the System Tray")
|
|
electronApp.exit(0);
|
|
electronApp.quit();
|
|
} else {
|
|
electronApp.on('second-instance', (event, commandLine, workingDirectory) => {
|
|
//Someone tried to run a second instance, we should focus our window.
|
|
// debug_log('SingleInstance')
|
|
|
|
function checkFileType(fileName) {
|
|
var fileNameLC = fileName.toLowerCase();
|
|
if (fileNameLC.endsWith('.obc') || fileName.endsWith('.gcode') || fileName.endsWith('.gc') || fileName.endsWith('.tap') || fileName.endsWith('.nc') || fileName.endsWith('.cnc')) {
|
|
return fileName;
|
|
}
|
|
}
|
|
|
|
debug_log(commandLine)
|
|
lauchGUI = true;
|
|
|
|
var openFilePath = commandLine.find(checkFileType);
|
|
if (openFilePath !== "") {
|
|
readFile(openFilePath);
|
|
if (openFilePath !== undefined) {
|
|
if (openFilePath.endsWith('.obc')) {
|
|
lauchGUI = false;
|
|
} else {
|
|
lauchGUI = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (lauchGUI) {
|
|
showJogWindow()
|
|
}
|
|
})
|
|
// Create myWindow, load the rest of the app, etc...
|
|
electronApp.on('ready', () => {
|
|
if (process.platform == 'win32') {
|
|
// Don't show window - sit in Tray
|
|
} else {
|
|
showJogWindow() // Macos and Linux - launch GUI
|
|
}
|
|
})
|
|
}
|
|
|
|
if (electronApp) {
|
|
// Module to create native browser window.
|
|
|
|
function createApp() {
|
|
createTrayIcon();
|
|
if (process.platform == 'darwin') {
|
|
debug_log("Creating MacOS Menu");
|
|
createMenu();
|
|
status.driver.operatingsystem = 'macos';
|
|
}
|
|
if (process.platform == 'win32' && process.argv.length >= 2) {
|
|
var openFilePath = process.argv[1];
|
|
if (openFilePath !== "") {
|
|
debug_log("path" + openFilePath);
|
|
readFile(openFilePath);
|
|
}
|
|
status.driver.operatingsystem = 'windows';
|
|
}
|
|
|
|
if (process.platform == 'darwin' || uploadedgcode.length > 1) {
|
|
showJogWindow()
|
|
}
|
|
|
|
}
|
|
|
|
function createMenu() {
|
|
|
|
var template = [{
|
|
label: "Application",
|
|
submenu: [{
|
|
label: "Quit",
|
|
accelerator: "Command+Q",
|
|
click: function() {
|
|
if (appIcon) {
|
|
appIcon.destroy();
|
|
}
|
|
electronApp.exit(0);
|
|
}
|
|
}]
|
|
}, {
|
|
label: "Edit",
|
|
submenu: [{
|
|
label: "Cut",
|
|
accelerator: "CmdOrCtrl+X",
|
|
selector: "cut:"
|
|
},
|
|
{
|
|
label: "Copy",
|
|
accelerator: "CmdOrCtrl+C",
|
|
selector: "copy:"
|
|
},
|
|
{
|
|
label: "Paste",
|
|
accelerator: "CmdOrCtrl+V",
|
|
selector: "paste:"
|
|
},
|
|
{
|
|
label: "Select All",
|
|
accelerator: "CmdOrCtrl+A",
|
|
selector: "selectAll:"
|
|
}
|
|
]
|
|
}, {
|
|
label: "View",
|
|
submenu: [{
|
|
label: "Reload",
|
|
accelerator: "F5",
|
|
click: (item, focusedWindow) => {
|
|
if (focusedWindow) {
|
|
// on reload, start fresh and close any old
|
|
// open secondary windows
|
|
if (focusedWindow.id === 1) {
|
|
BrowserWindow.getAllWindows().forEach(win => {
|
|
if (win.id > 1) win.close();
|
|
});
|
|
}
|
|
focusedWindow.reload();
|
|
}
|
|
}
|
|
},
|
|
{
|
|
label: "Toggle Dev Tools",
|
|
accelerator: "F12",
|
|
click: () => {
|
|
jogWindow.webContents.toggleDevTools();
|
|
}
|
|
}
|
|
]
|
|
}];
|
|
|
|
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
|
|
}
|
|
|
|
function createTrayIcon() {
|
|
if (process.platform !== 'darwin') {
|
|
appIcon = new Tray(
|
|
nativeImage.createFromPath(iconPath)
|
|
)
|
|
const contextMenu = Menu.buildFromTemplate([{
|
|
label: 'Open User Interface (GUI)',
|
|
click() {
|
|
// debug_log("Clicked Systray")
|
|
showJogWindow()
|
|
}
|
|
}, {
|
|
label: 'Quit OpenBuilds CONTROL (Disables all integration until started again)',
|
|
click() {
|
|
if (appIcon) {
|
|
appIcon.destroy();
|
|
}
|
|
electronApp.exit(0);
|
|
}
|
|
}])
|
|
if (appIcon) {
|
|
appIcon.on('click', function() {
|
|
// debug_log("Clicked Systray")
|
|
showJogWindow()
|
|
})
|
|
}
|
|
|
|
if (appIcon) {
|
|
appIcon.on('balloon-click', function() {
|
|
// debug_log("Clicked Systray")
|
|
showJogWindow()
|
|
})
|
|
}
|
|
|
|
// Call this again for Linux because we modified the context menu
|
|
if (appIcon) {
|
|
appIcon.setContextMenu(contextMenu)
|
|
}
|
|
|
|
if (appIcon) {
|
|
appIcon.displayBalloon({
|
|
icon: nativeImage.createFromPath(iconPath),
|
|
title: "OpenBuilds CONTROL Started",
|
|
// content: "OpenBuilds CONTROL has started successfully: Active on " + ip.address() + ":" + config.webPort
|
|
content: "OpenBuilds CONTROL has started successfully"
|
|
})
|
|
}
|
|
} else {
|
|
const dockMenu = Menu.buildFromTemplate([{
|
|
label: 'Quit OpenBuilds CONTROL (Disables all integration until started again)',
|
|
click() {
|
|
// appIcon.destroy();
|
|
electronApp.exit(0);
|
|
}
|
|
}])
|
|
electronApp.dock.setMenu(dockMenu)
|
|
};
|
|
|
|
}
|
|
|
|
function createJogWindow() {
|
|
// Create the browser window.
|
|
jogWindow = new BrowserWindow({
|
|
// 1366 * 768 == minimum to cater for
|
|
width: 1000,
|
|
height: 850,
|
|
fullscreen: false,
|
|
center: true,
|
|
resizable: true,
|
|
maximizable: true,
|
|
title: "OpenBuilds CONTROL ",
|
|
frame: false,
|
|
autoHideMenuBar: true,
|
|
//icon: '/app/favicon.png',
|
|
icon: nativeImage.createFromPath(
|
|
path.join(__dirname, "/app/favicon.png")
|
|
),
|
|
webgl: true,
|
|
experimentalFeatures: true,
|
|
experimentalCanvasFeatures: true,
|
|
offscreen: true,
|
|
backgroundColor: "#fff",
|
|
webPreferences: {
|
|
nodeIntegration: true,
|
|
contextIsolation: false
|
|
}
|
|
});
|
|
|
|
jogWindow.setOverlayIcon(nativeImage.createFromPath(iconPath), 'Icon');
|
|
var ipaddr = ip.address();
|
|
// jogWindow.loadURL(`//` + ipaddr + `:3000/`)
|
|
jogWindow.loadURL(`http://localhost:${config.webPort}/`);
|
|
//jogWindow.webContents.openDevTools()
|
|
|
|
jogWindow.on('close', function(event) {
|
|
if (!forceQuit) {
|
|
jogWindow.hide();
|
|
return false;
|
|
}
|
|
});
|
|
|
|
// Emitted when the window is closed.
|
|
jogWindow.on('closed', function() {
|
|
// Dereference the window object, usually you would store windows
|
|
// in an array if your app supports multi windows, this is the time
|
|
// when you should delete the corresponding element.
|
|
jogWindow = null;
|
|
});
|
|
jogWindow.once('ready-to-show', () => {
|
|
showJogWindow()
|
|
})
|
|
}
|
|
|
|
// This method will be called when Electron has finished
|
|
// initialization and is ready to create browser windows.
|
|
// Some APIs can only be used after this event occurs.
|
|
electronApp.on('ready', createApp);
|
|
|
|
electronApp.on('before-quit', function() {
|
|
forceQuit = true;
|
|
})
|
|
|
|
electronApp.on('will-quit', function(event) {
|
|
// On OS X it is common for applications and their menu bar
|
|
// to stay active until the user quits explicitly with Cmd + Q
|
|
// We don't take that route, we close it completely
|
|
if (appIcon) {
|
|
appIcon.destroy();
|
|
}
|
|
electronApp.exit(0);
|
|
});
|
|
|
|
// Quit when all windows are closed.
|
|
electronApp.on('window-all-closed', function() {
|
|
// On OS X it is common for applications and their menu bar
|
|
// to stay active until the user quits explicitly with Cmd + Q
|
|
if (appIcon) {
|
|
appIcon.destroy();
|
|
}
|
|
electronApp.exit(0);
|
|
});
|
|
|
|
electronApp.on('activate', function() {
|
|
// On OS X it's common to re-create a window in the app when the
|
|
// dock icon is clicked and there are no other windows open.
|
|
if (mainWindow === null) {
|
|
createApp();
|
|
}
|
|
});
|
|
|
|
// Autostart on Login
|
|
if (process.platform == 'win32') {
|
|
electronApp.setLoginItemSettings({
|
|
openAtLogin: true,
|
|
args: []
|
|
})
|
|
}
|
|
}
|
|
} else { // if its not running under Electron, lets get Chrome up.
|
|
var isPi = require('detect-rpi');
|
|
if (isPi()) {
|
|
DEBUG = true;
|
|
debug_log('Running on Raspberry Pi!');
|
|
status.driver.operatingsystem = 'rpi'
|
|
startChrome();
|
|
} else {
|
|
debug_log("Running under NodeJS...");
|
|
}
|
|
}
|
|
|
|
|
|
function stop(data) {
|
|
//data = { stop: false, jog: false, abort: true}
|
|
if (status.comms.connectionStatus > 0) {
|
|
status.comms.paused = true;
|
|
debug_log('STOP');
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
|
|
if (data.jog) {
|
|
addQRealtime(String.fromCharCode(0x85)); // canceljog
|
|
debug_log('Sent: 0x85 Jog Cancel');
|
|
debug_log(queuePointer, gcodeQueue)
|
|
}
|
|
|
|
if (!data.abort && !data.jog) { // pause motion first.
|
|
addQRealtime('!'); // hold
|
|
debug_log('Sent: !');
|
|
}
|
|
|
|
if (status.machine.firmware.version === '1.1d') {
|
|
addQRealtime(String.fromCharCode(0x9E)); // Stop Spindle/Laser
|
|
debug_log('Sent: Code(0x9E)');
|
|
}
|
|
|
|
debug_log('Cleaning Queue');
|
|
if (!data.jog) {
|
|
setTimeout(function() {
|
|
addQRealtime(String.fromCharCode(0x18)); // ctrl-x
|
|
debug_log('Sent: Code(0x18)');
|
|
}, 200);
|
|
}
|
|
status.comms.connectionStatus = 2;
|
|
break;
|
|
}
|
|
clearInterval(queueCounter);
|
|
if (jogWindow) {
|
|
jogWindow.setProgressBar(0);
|
|
}
|
|
status.comms.queue = 0
|
|
queuePointer = 0;
|
|
gcodeQueue.length = 0; // Dump the queue
|
|
sentBuffer.length = 0; // Dump the queue
|
|
// sentBuffer.length = 0; // Dump bufferSizes
|
|
laserTestOn = false;
|
|
status.comms.blocked = false;
|
|
status.comms.paused = false;
|
|
status.comms.runStatus = 'Stopped';
|
|
status.comms.alarm = "";
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
}
|
|
|
|
function pause() {
|
|
if (status.comms.connectionStatus == 3) {
|
|
status.comms.paused = true;
|
|
debug_log('PAUSE');
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
addQRealtime('!'); // Send hold command
|
|
debug_log('Sent: !');
|
|
if (status.machine.firmware.version === '1.1d') {
|
|
addQRealtime(String.fromCharCode(0x9E)); // Stop Spindle/Laser
|
|
debug_log('Sent: Code(0x9E)');
|
|
}
|
|
break;
|
|
}
|
|
status.comms.runStatus = 'Paused';
|
|
status.comms.connectionStatus = 4;
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
}
|
|
|
|
function unpause() {
|
|
if (status.comms.connectionStatus > 0) {
|
|
debug_log('UNPAUSE');
|
|
switch (status.machine.firmware.type) {
|
|
case 'grbl':
|
|
addQRealtime('~'); // Send resume command
|
|
debug_log('Sent: ~');
|
|
break;
|
|
}
|
|
status.comms.paused = false;
|
|
status.comms.blocked = false;
|
|
setTimeout(function() {
|
|
send1Q(); // restart queue
|
|
}, 200);
|
|
status.comms.runStatus = 'Resuming';
|
|
status.comms.connectionStatus = 3;
|
|
} else {
|
|
debug_log('ERROR: Machine connection not open!');
|
|
}
|
|
}
|
|
|
|
function isJson(item) {
|
|
item = typeof item !== "string" ?
|
|
JSON.stringify(item) :
|
|
item;
|
|
|
|
try {
|
|
item = JSON.parse(item);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
|
|
if (typeof item === "object" && item !== null) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function startChrome() {
|
|
if (status.driver.operatingsystem == 'rpi') {
|
|
const {
|
|
spawn
|
|
} = require('child_process');
|
|
const chrome = spawn('chromium-browser', [`-app=http://127.0.0.1:${config.webPort}`]);
|
|
chrome.on('close', (code) => {
|
|
debug_log(`Chromium process exited with code ${code}`);
|
|
process.exit(0);
|
|
});
|
|
} else {
|
|
debug_log('Not a Raspberry Pi. Please use Electron Instead');
|
|
}
|
|
}
|
|
|
|
// Interface Programming
|
|
|
|
|
|
// grab latest firmware.bin for Interface on startup
|
|
|
|
var file = fs.createWriteStream(path.join(uploadsDir, "firmware.bin"));
|
|
https.get("https://raw.githubusercontent.com/OpenBuilds/firmware/main/interface/firmware.bin", function(response) {
|
|
response.pipe(file);
|
|
file.on('finish', function() {
|
|
file.close(function() {
|
|
|
|
const options = {
|
|
hostname: 'raw.githubusercontent.com',
|
|
port: 443,
|
|
path: '/OpenBuilds/firmware/main/interface/version.txt',
|
|
method: 'GET'
|
|
}
|
|
|
|
const req = https.request(options, res => {
|
|
console.log(`statusCode: ${res.statusCode}`)
|
|
|
|
res.on('data', d => {
|
|
status.interface.firmware.availVersion = parseFloat(d.toString())
|
|
|
|
var output = {
|
|
'command': 'interface firmware update tool',
|
|
'response': "Downloaded firmware.bin v" + status.interface.firmware.availVersion,
|
|
'type': 'info'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
debug_log(JSON.stringify(output));
|
|
|
|
})
|
|
})
|
|
|
|
req.on('error', error => {
|
|
var output = {
|
|
'command': 'interface firmware update tool',
|
|
'response': "Unable to download latest firmware.bin",
|
|
'type': 'error'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
})
|
|
|
|
req.end()
|
|
|
|
|
|
});
|
|
});
|
|
})
|
|
|
|
|
|
|
|
var firmwareImagePath = path.join(uploadsDir, './firmware.bin');
|
|
var spawn = require('child_process').spawn;
|
|
const multer = require('multer');
|
|
const storage = multer.diskStorage({
|
|
destination: function(req, file, cb) {
|
|
cb(null, uploadsDir);
|
|
},
|
|
// By default, multer removes file extensions so let's add them back
|
|
filename: function(req, file, cb) {
|
|
cb(null, file.fieldname + '-' + new Date().toJSON().replace(new RegExp(':', 'g'), '.') + path.extname(file.originalname));
|
|
}
|
|
});
|
|
|
|
function flashInterface(data) {
|
|
status.comms.connectionStatus = 6;
|
|
|
|
var port = data.port;
|
|
var file = data.file;
|
|
var board = data.board
|
|
|
|
|
|
console.log("Flashing Interface on " + port + " with file: " + file)
|
|
// var data = {
|
|
// 'port': port,
|
|
// 'string': debugString
|
|
// }
|
|
// io.sockets.emit("progStatus", data);
|
|
//
|
|
|
|
//for (let i = 0; i < ports.length; i++) {
|
|
|
|
var data = {
|
|
'port': port,
|
|
'string': "[Starting...]"
|
|
}
|
|
io.sockets.emit("progStatus", data);
|
|
|
|
var esptool_opts = [
|
|
'--chip', 'esp32',
|
|
'--port', port,
|
|
'--baud', '921600',
|
|
'--before', 'default_reset',
|
|
'--after', 'hard_reset',
|
|
'write_flash',
|
|
'-z',
|
|
'--flash_mode', 'dio',
|
|
'--flash_freq', '80m',
|
|
'--flash_size', 'detect',
|
|
'0xe000', path.join(__dirname, "./boot_app0.bin").replace('app.asar', 'app.asar.unpacked'),
|
|
'0x1000', path.join(__dirname, "./bootloader_qio_80m.bin").replace('app.asar', 'app.asar.unpacked'),
|
|
'0x10000', path.resolve(firmwareImagePath).replace('app.asar', 'app.asar.unpacked'),
|
|
'0x8000', path.join(__dirname, "./firmware.partitions.bin").replace('app.asar', 'app.asar.unpacked')
|
|
];
|
|
|
|
if (process.platform == 'linux') {
|
|
//path.join(__dirname, "..", "lib", "resources", "vad.onnx"),
|
|
fs.chmodSync(path.join(__dirname, "./esptool.py").replace('app.asar', 'app.asar.unpacked'), 0o755);
|
|
var child = spawn(path.join(__dirname, "./esptool.py").replace('app.asar', 'app.asar.unpacked'), esptool_opts);
|
|
} else if (process.platform == 'win32') {
|
|
var child = spawn(path.join(__dirname, "./esptool.exe").replace('app.asar', 'app.asar.unpacked'), esptool_opts);
|
|
} else if (process.platform == 'darwin') {
|
|
fs.chmodSync(path.join(__dirname, "./esptool.py").replace('app.asar', 'app.asar.unpacked'), 0o755);
|
|
var child = spawn(path.join(__dirname, "./esptool.py").replace('app.asar', 'app.asar.unpacked'), esptool_opts);
|
|
}
|
|
|
|
|
|
|
|
|
|
child.stdout.on('data', function(data) {
|
|
var debugString = data.toString();
|
|
console.log(debugString)
|
|
var data = {
|
|
'port': port,
|
|
'string': debugString
|
|
}
|
|
io.sockets.emit("progStatus", data);
|
|
status.comms.connectionStatus = 6;
|
|
|
|
});
|
|
|
|
child.stderr.on('data', function(data) {
|
|
var debugString = data.toString();
|
|
console.log(debugString)
|
|
var data = {
|
|
'port': port,
|
|
'string': debugString
|
|
}
|
|
io.sockets.emit("progStatus", data);
|
|
status.comms.connectionStatus = 6;
|
|
|
|
});
|
|
|
|
child.on('close', (code) => {
|
|
var data = {
|
|
'port': port,
|
|
'string': `[exit:` + code + `]`,
|
|
'code': code
|
|
}
|
|
io.sockets.emit("progStatus", data);
|
|
status.comms.connectionStatus = 0;
|
|
|
|
});
|
|
}
|
|
// end Interface Programming
|
|
|
|
function flashGrblHal(data) {
|
|
|
|
console.log(JSON.stringify(data))
|
|
|
|
status.comms.connectionStatus = 6;
|
|
|
|
var port = data.port;
|
|
var file = data.file;
|
|
var customImg = data.customImg
|
|
var erase = data.erase
|
|
|
|
if (customImg == true) {
|
|
var firmwarePath = firmwareImagePath
|
|
} else {
|
|
var firmwarePath = path.join(__dirname, file)
|
|
}
|
|
|
|
console.log("Flashing BlackBoxX32 on " + port + " with file: " + path.resolve(firmwarePath).replace('app.asar', 'app.asar.unpacked'))
|
|
var data = {
|
|
'port': port,
|
|
'string': "[Starting...]"
|
|
}
|
|
io.sockets.emit("progStatus", data);
|
|
|
|
var esptool_opts = [
|
|
'--port', port,
|
|
'--baud', '460800',
|
|
'--before', 'default_reset',
|
|
'--after', 'hard_reset',
|
|
'--chip', 'esp32',
|
|
'write_flash',
|
|
'--flash_mode', 'dio',
|
|
'--flash_size', 'detect',
|
|
'--flash_freq', '40m',
|
|
'0x1000', path.join(__dirname, "./grblhal-bootloader.bin").replace('app.asar', 'app.asar.unpacked'),
|
|
'0x8000', path.join(__dirname, "./grblhal-partition-table.bin").replace('app.asar', 'app.asar.unpacked'),
|
|
'0x10000', path.resolve(firmwarePath).replace('app.asar', 'app.asar.unpacked')
|
|
];
|
|
|
|
if (erase == true) {
|
|
esptool_opts.push('--erase-all');
|
|
}
|
|
|
|
if (process.platform == 'linux') {
|
|
fs.chmodSync(path.join(__dirname, "./esptool.py").replace('app.asar', 'app.asar.unpacked'), 0o755);
|
|
var child = spawn(path.join(__dirname, "./esptool.py").replace('app.asar', 'app.asar.unpacked'), esptool_opts);
|
|
} else if (process.platform == 'win32') {
|
|
var child = spawn(path.join(__dirname, "./esptool.exe").replace('app.asar', 'app.asar.unpacked'), esptool_opts);
|
|
} else if (process.platform == 'darwin') {
|
|
console.log("Running on MacOS")
|
|
fs.chmodSync(path.join(__dirname, "./esptool.py").replace('app.asar', 'app.asar.unpacked'), 0o755);
|
|
var child = spawn(path.join(__dirname, "./esptool.py").replace('app.asar', 'app.asar.unpacked'), esptool_opts);
|
|
}
|
|
|
|
|
|
child.stdout.on('data', function(data) {
|
|
var debugString = data.toString();
|
|
console.log(debugString)
|
|
var data = {
|
|
'port': port,
|
|
'string': debugString
|
|
}
|
|
io.sockets.emit("progStatus", data);
|
|
status.comms.connectionStatus = 6;
|
|
|
|
});
|
|
|
|
child.stderr.on('data', function(data) {
|
|
var debugString = data.toString();
|
|
console.log(debugString)
|
|
var data = {
|
|
'port': port,
|
|
'string': debugString
|
|
}
|
|
io.sockets.emit("progStatus", data);
|
|
status.comms.connectionStatus = 6;
|
|
|
|
});
|
|
|
|
child.on('close', (code) => {
|
|
var data = {
|
|
'port': port,
|
|
'string': `[exit:` + code + `]`,
|
|
'code': code
|
|
}
|
|
io.sockets.emit("progStatus", data);
|
|
status.comms.connectionStatus = 0;
|
|
|
|
});
|
|
}
|
|
// end BlackBoxX32 Programming
|
|
|
|
|
|
// LAN Scanner for BlackBox X32, Interface, SwitchBlox etc //
|
|
function scanForTelnetDevices(range) {
|
|
//var localNetwork = ip.address().split('.');
|
|
//var network = localNetwork[0] + '.' + localNetwork[1] + '.' + localNetwork[2];
|
|
//var range = network + ".1-" + network + ".254"
|
|
|
|
var networkDevices = []
|
|
oldiplist = status.comms.interfaces.networkDevices;
|
|
const telnetScanOptions = {
|
|
target: range,
|
|
port: '23',
|
|
status: 'TROU', // Timeout, Refused, Open, Unreachable
|
|
banner: true
|
|
};
|
|
|
|
var output = {
|
|
'command': 'network',
|
|
'response': "Starting network scan for: " + telnetScanOptions.target,
|
|
'type': 'success'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
|
|
new Evilscan(telnetScanOptions, (err, scan) => {
|
|
|
|
if (err) {
|
|
var output = {
|
|
'command': 'network',
|
|
'response': "Network Scan error: " + err,
|
|
'type': 'success'
|
|
}
|
|
io.sockets.emit('data', output);
|
|
//console.log(err);
|
|
return;
|
|
}
|
|
|
|
scan.on('result', data => {
|
|
// fired when item is matching options
|
|
//console.log(data);
|
|
if (data.status == "open") {
|
|
var type = false;
|
|
if (data.banner.indexOf("GrblHAL") != -1) {
|
|
type = "grblHAL"
|
|
} else if (data.banner.indexOf("Grbl") != -1) {
|
|
type = "grbl"
|
|
}
|
|
networkDevices.push({
|
|
ip: data.ip,
|
|
type: type,
|
|
banner: data.banner
|
|
})
|
|
}
|
|
|
|
});
|
|
|
|
scan.on('error', err => {
|
|
//throw new Error(data.toString());
|
|
});
|
|
|
|
scan.on('done', () => {
|
|
// finished !
|
|
networkDevices.sort((a, b) => {
|
|
return a.ip.split('.')[3] - b.ip.split('.')[3];
|
|
});
|
|
status.comms.interfaces.networkDevices = networkDevices;
|
|
if (!_.isEqual(status.comms.interfaces.networkDevices, oldiplist)) {
|
|
var newTelnetPorts = _.differenceWith(status.comms.interfaces.networkDevices, oldiplist, _.isEqual)
|
|
if (newTelnetPorts.length > 0) {
|
|
debug_log("Detected new device: " + newTelnetPorts[0].ip);
|
|
}
|
|
var removedTelnetPorts = _.differenceWith(oldiplist, status.comms.interfaces.networkDevices, _.isEqual)
|
|
if (removedTelnetPorts.length > 0) {
|
|
debug_log("No longer detecting device: " + removedTelnetPorts[0].ip);
|
|
}
|
|
}
|
|
oldiplist = status.comms.interfaces.networkDevices;
|
|
if (status.comms.interfaces.networkDevices.length > 0) {
|
|
var output = {
|
|
'command': 'network',
|
|
'response': "Network Scan completed. Found " + status.comms.interfaces.networkDevices.length + " devices. Network addresses added to the Port selection dropdown.",
|
|
'type': 'success'
|
|
}
|
|
} else {
|
|
var output = {
|
|
'command': 'network',
|
|
'response': "Network Scan completed. Found " + status.comms.interfaces.networkDevices.length + " devices",
|
|
'type': 'error'
|
|
}
|
|
}
|
|
|
|
io.sockets.emit('data', output);
|
|
});
|
|
|
|
scan.run();
|
|
});
|
|
}
|
|
// end LAN Scanner
|
|
|
|
|
|
process.on('exit', () => debug_log('exit')) |