console.log("Starting OpenBuilds Machine Driver v" + require('./package').version) var config = {}; config.webPort = process.env.WEB_PORT || 3000; config.posDecimals = process.env.DRO_DECIMALS || 2; config.grblWaitTime = 1; config.firmwareWaitTime = 4; var express = require("express"); var app = express(); var http = require("http").Server(app); var https = require('https'); // var io = require("socket.io")(https); var ioServer = require('socket.io'); var io = new ioServer(); // var oneIo = io.listen(https); // var anotherIo = io.listen(https); var fs = require('fs'); var httpsOptions = { key: fs.readFileSync('ssl/localhost.key'), cert: fs.readFileSync('ssl/localhost.cer') }; const httpsserver = https.createServer(httpsOptions, app).listen(3001, function() { console.log('https: listening on:' + ip.address() + ":3001"); }); const httpserver = http.listen(config.webPort, '0.0.0.0', function() { console.log('http: listening on:' + ip.address() + ":" + config.webPort); // Now refresh library refreshGcodeLibrary(); }); io.attach(httpserver); io.attach(httpsserver); const grblStrings = require("./grblStrings.js"); var path = require("path"); const join = require('path').join; const serialport = require('serialport'); var SerialPort = serialport; var md5 = require('md5'); var ip = require("ip"); var _ = require('lodash'); var fs = require("fs"); var rimraf = require("rimraf") var formidable = require('formidable') var util = require('util'); var lastsentuploadprogress = 0; var gcodethumbnail = require("gcodethumbnail"); var colors = { G0: '#00CC00', G1: '#CC0000', G2G3: "#0000CC" }; var width = 250; var height = 200; // Electron app const electron = require('electron'); // Module to control application life. const electronApp = electron.app; console.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 // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. var appIcon = null, jogWindow = null, mainWindow = null var uploadsDir = electronApp.getPath('userData') + '/upload/'; fs.existsSync(uploadsDir) || fs.mkdirSync(uploadsDir) // fs.mkdir(uploadsDir, err => { // if (err && err.code != 'EEXIST') throw 'up' // // already exists // }) var oldportslist; 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 isAlarmed = false; var lastmd5sum = '00000000000000000000000000000000' var lastGcode = [] var lastCommand = false var gcodeQueue = []; var queuePointer = 0; var startTime; var statusLoop; var queueCounter; var listPortsLoop; var GRBL_RX_BUFFER_SIZE = 128; // 128 characters var grblBufferSize = []; var new_grbl_buffer = false; var SMOOTHIE_RX_BUFFER_SIZE = 64; // max. length of one command line var smoothie_buffer = false; var lastMode; 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 has4thAxis = false; var feedOverride = 100, spindleOverride = 100; //regex to identify MD5hash on sdupload later var re = new RegExp("^[a-f0-9]{32}"); var status = { driver: { version: require('./package').version }, machine: { tools: { hotend1: false, hotend2: false, heatbed: false, laser: false, spindle: false }, overrides: { feedOverride: 100, // spindleOverride: 100, // realFeed: 0, // realSpindle: 0 // }, position: { work: { x: 0, y: 0, z: 0, a: 0, e: 0 }, offset: { x: 0, y: 0, z: 0, a: 0, e: 0 } }, temperature: { setpoint: { t0: 0, t1: 0, b: 0 }, actual: { t0: 0, t1: 0, b: 0 } }, firmware: { type: "", version: "", date: "", config: [] }, sdcard: { list: [] }, drivers: { x: { type: "", axis: "", microstep: 0, currentSetting: 0, enabled: 0, stallGuard: { stallGuardReading: 1023, stallGuardThreshold: 0, stallGuardFilter: 0 }, coolStep: { coolStepEnabled: 0, coolStepCurrent: 0, coolStepLowerThreshold: 0, coolStepUpperThreshold: 0, coolStepNumberOfReadings: 0, coolStepCurrentIncrement: 0, coolStepLowerCurrentLimit: 0 }, troubleshooting: { shortGndA: 0, shortGndB: 0, openLoadA: 0, openLoadB: 0, overTemp: 0 } }, y: { type: "", axis: "", microstep: 0, currentSetting: 0, enabled: 0, stallGuard: { stallGuardReading: 1023, stallGuardThreshold: 0, stallGuardFilter: 0 }, coolStep: { coolStepEnabled: 0, coolStepCurrent: 0, coolStepLowerThreshold: 0, coolStepUpperThreshold: 0, coolStepNumberOfReadings: 0, coolStepCurrentIncrement: 0, coolStepLowerCurrentLimit: 0 }, troubleshooting: { shortGndA: 0, shortGndB: 0, openLoadA: 0, openLoadB: 0, overTemp: 0 } }, z: { type: "", axis: "", microstep: 0, currentSetting: 0, enabled: 0, stallGuard: { stallGuardReading: 1023, stallGuardThreshold: 0, stallGuardFilter: 0 }, coolStep: { coolStepEnabled: 0, coolStepCurrent: 0, coolStepLowerThreshold: 0, coolStepUpperThreshold: 0, coolStepNumberOfReadings: 0, coolStepCurrentIncrement: 0, coolStepLowerCurrentLimit: 0 }, troubleshooting: { shortGndA: 0, shortGndB: 0, openLoadA: 0, openLoadB: 0, overTemp: 0 } }, a: { type: "", axis: "", microstep: 0, currentSetting: 0, enabled: 0, stallGuard: { stallGuardReading: 1023, stallGuardThreshold: 0, stallGuardFilter: 0 }, coolStep: { coolStepEnabled: 0, coolStepCurrent: 0, coolStepLowerThreshold: 0, coolStepUpperThreshold: 0, coolStepNumberOfReadings: 0, coolStepCurrentIncrement: 0, coolStepLowerCurrentLimit: 0 }, troubleshooting: { shortGndA: 0, shortGndB: 0, openLoadA: 0, openLoadB: 0, overTemp: 0 } }, } }, comms: { connectionStatus: 0, //0 = not connected, 1 = opening, 2 = connected, 3 = playing, 4 = paused connectedTo: "none", 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: { ports: "", activePort: "" // or activeIP in the case of wifi/telnet? }, alarm: "" } }; function refreshGcodeLibrary() { if (fs.existsSync(uploadsDir)) { const dirTree = require('directory-tree'); var tree = dirTree(uploadsDir, { extensions: /\.gcode|\.nc|\.tap|\.cnc|\.gc|\.g-code$/ }, (item, PATH) => { // if a gcode is found, then // console.log(item); ConvertGCODEtoPNG(item.path, item.path + ".png") }); // console.log("---------------") var tree = dirTree(uploadsDir, { extensions: /\.gcode|\.png/ }); var treeData = JSON.stringify(tree, null, 2) // console.log(treeData); fs.writeFileSync(join(uploadsDir + '/data.json'), treeData, 'utf-8') } } function ConvertGCODEtoPNG(file, out) { var path = out; fs.readFile(file, 'utf8', function(err, data) { if (err) { console.log(err); process.exit(1); } gcodethumbnail.generatePNG(path, data, colors, width, height); }); } SerialPort.list(function(err, ports) { oldportslist = ports; status.comms.interfaces.ports = ports; }); var PortCheckinterval = setInterval(function() { if (status.comms.connectionStatus == 0) { SerialPort.list(function(err, ports) { status.comms.interfaces.ports = ports; if (!_.isEqual(ports, oldportslist)) { var newPorts = _.differenceWith(ports, oldportslist, _.isEqual) if (newPorts.length > 0) { console.log("Plugged " + newPorts[0].comName); appIcon.displayBalloon({ icon: nativeImage.createFromPath(iconPath), title: "Driver Detected a new Port", content: "OpenBuilds Machine Driver detected a new port: " + newPorts[0].comName }) } var removedPorts = _.differenceWith(oldportslist, ports, _.isEqual) if (removedPorts.length > 0) { console.log("Unplugged " + removedPorts[0].comName); appIcon.displayBalloon({ icon: nativeImage.createFromPath(iconPath), title: "Driver Detected a disconnected Port", content: "OpenBuilds Machine Driver detected that port: " + removedPorts[0].comName + " was removed" }) } } oldportslist = ports; }); } }, 500); // Static Webserver app.use(express.static(path.join(__dirname, "app"))); // 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); }) // 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'); }) // 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"); // console.log(req) uploadprogress = 0 var form = new formidable.IncomingForm(); // Cleanup old files - later // fs.readdir(uploadsDir, function(err, files) { // files.forEach(function(file, index) { // fs.stat(path.join(uploadsDir, file), function(err, stat) { // var endTime, now; // if (err) { // return console.error(err); // } // now = new Date().getTime(); // // older than an hour // endTime = new Date(stat.ctime).getTime() + 3600000; // if (now > endTime) { // return rimraf(path.join(uploadsDir, file), function(err) { // if (err) { // return console.error(err); // } // console.log('successfully deleted' + file); // }); // } // }); // }); // }); // form.parse(req); form.parse(req, function(err, fields, files) { // console.log(util.inspect({ // fields: fields, // files: files // })); }); form.on('fileBegin', function(name, file) { // Emitted whenever a new file is detected in the upload stream. Use this event if you want to stream the file to somewhere else while buffering the upload on the file system. console.log('Uploading ' + file.name); file.path = uploadsDir + file.name; // io.sockets.in('sessionId').emit('startupload', 'STARTING'); }); form.on('progress', function(bytesReceived, bytesExpected) { uploadprogress = parseInt(((bytesReceived * 100) / bytesExpected).toFixed(0)); if (uploadprogress != lastsentuploadprogress) { // io.sockets.in('sessionId').emit('uploadprogress', uploadprogress); lastsentuploadprogress = uploadprogress; } }); form.on('file', function(name, file) { // Emitted whenever a field / file pair has been received. file is an instance of File. console.log('Uploaded ' + file.path); // io.sockets.in('sessionId').emit('doneupload', 'COMPLETE'); refreshGcodeLibrary(); if (jogWindow === null) { createJogWindow(); jogWindow.show() // workaround from https://github.com/electron/electron/issues/2867#issuecomment-261067169 to make window pop over for focus jogWindow.setAlwaysOnTop(true); jogWindow.focus(); jogWindow.setAlwaysOnTop(false); } else { jogWindow.show() jogWindow.setAlwaysOnTop(true); jogWindow.focus(); jogWindow.setAlwaysOnTop(false); } setTimeout(function() { fs.readFile(file.path, 'utf8', function(err, data) { if (err) { console.log(err); io.sockets.emit('data', "ERROR: File Upload Failed"); appIcon.displayBalloon({ icon: nativeImage.createFromPath(iconPath), title: "ERROR: File Upload Failed", content: "OpenBuilds Machine Driver ERROR: File Upload Failed" }) // process.exit(1); } // console.log(data) if (data) { io.sockets.emit('gcodeupload', data); appIcon.displayBalloon({ icon: nativeImage.createFromPath(iconPath), title: "GCODE Received", content: "OpenBuilds Machine Driver received new GCODE" }) } }); }, 1500); // console.log("Done, now lets work with " + file.path) }); 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.sendFile(__dirname + '/app/upload.html'); }); app.on('certificate-error', function(event, webContents, url, error, certificate, callback) { event.preventDefault(); callback(true); }); function stopPort() { clearInterval(queueCounter); clearInterval(statusLoop); 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 = ""; gcodeQueue.length = 0; lastGcode.length = 0; grblBufferSize.length = 0; // dump bufferSizes port.drain(port.close()); } io.on("connection", function(socket) { iosocket = socket; if (status.machine.firmware.type == 'grbl') { socket.emit('grbl') } var interval = setInterval(function() { io.sockets.emit("status", status); if (jogWindow) { if (status.comms.connectionStatus == 0) { jogWindow.setOverlayIcon(nativeImage.createFromPath(iconNoComm), 'Not Connected'); } else if (status.comms.connectionStatus == 1) { jogWindow.setOverlayIcon(nativeImage.createFromPath(iconStop), 'Port Connected'); } else if (status.comms.connectionStatus == 2) { jogWindow.setOverlayIcon(nativeImage.createFromPath(iconStop), 'Connected, and Firmware'); } else if (status.comms.connectionStatus == 3) { jogWindow.setOverlayIcon(nativeImage.createFromPath(iconPlay), 'Playing'); } else if (status.comms.connectionStatus == 4) { jogWindow.setOverlayIcon(nativeImage.createFromPath(iconPause), 'Paused'); } else if (status.comms.connectionStatus == 5) { jogWindow.setOverlayIcon(nativeImage.createFromPath(iconAlarm), 'Alarm'); } } }, 400); socket.on("minimise", function(data) { jogWindow.hide(); }); socket.on("maximise", function(data) {}); socket.on("quit", function(data) { appIcon.destroy(); electronApp.exit(0); }); 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) { data = data.split(","); console.log("Connecting via " + data[0] + " to " + data[1] + " at baud " + data[2]); port = new SerialPort(data[1], { parser: serialport.parsers.readline('\n'), baudRate: parseInt(data[2]) }); port.on("error", function(err) { console.log("Error: ", err.message); appIcon.displayBalloon({ icon: nativeImage.createFromPath(iconPath), title: "Driver encountered a Port error", content: "OpenBuilds Machine Driver received the following error: " + err.message }) // stopPort(); // status.comms.connectionStatus = 0; }); port.on("open", function() { io.sockets.emit('data', "PORT INFO: Opening USB Port"); status.comms.connectionStatus = 1; if (config.resetOnConnect == 1) { machineSend(String.fromCharCode(0x18)); // ctrl-x (needed for rx/tx connection) console.log("Sent: ctrl-x"); } else { machineSend("\n"); // this causes smoothie to send the welcome string } setTimeout(function() { //wait for controller to be ready if (status.machine.firmware.type.length < 1) { console.log("No GRBL, lets see if we have Smoothie?"); machineSend("version\n"); // Check if it's Smoothieware? console.log("Sent: version"); } }, config.grblWaitTime * 1000); if (config.firmwareWaitTime > 0) { setTimeout(function() { // Close port if we don't detect supported firmware after 2s. if (status.machine.firmware.type.length < 1) { console.log("No supported firmware detected. Closing port " + port.path); stopPort(); } }, config.firmwareWaitTime * 1000); } console.log("PORT INFO: Connected to " + port.path + " at " + port.options.baudRate); io.sockets.emit('data', "PORT INFO: Port is now open: " + port.path + " - Attempting to detect Firmware"); status.comms.connectionStatus = 2; status.comms.interfaces.activePort = port.path; status.comms.interfaces.activeBaud = port.options.baudRate; }); // end port .onopen port.on("close", function() { // open errors will be emitted as an error event console.log("PORT INFO: Port closed"); io.sockets.emit('data', "PORT INFO: Port closed"); }); // end port.onclose port.on("data", function(data) { // console.log("DATA RECV: " + data.replace(/(\r\n|\n|\r)/gm, "")); // console.log() // Command Tracking if (lastGcode.length == 0) { command = lastCommand; } else { command = lastGcode.shift(); if (lastGcode.length == 0) { lastCommand = command; } } if (!command) { command = "" }; command = command.replace(/(\r\n|\n|\r)/gm, ""); if (command != "?" && command != "M105" && data.length > 0) { var string = ""; if (status.comms.sduploading) { string += "SD: " } string += data //+ " [ " + command + " ]" var output = { 'command': command, 'response': string } io.sockets.emit('data', output); } // Machine Identification if (data.indexOf("Grbl") === 0) { // Check if it's Grbl status.comms.blocked = false; status.machine.firmware.type = "grbl"; status.machine.firmware.version = data.substr(5, 4); // get version status.machine.firmware.date = ""; console.log("GRBL detected"); socket.emit('grbl') appIcon.displayBalloon({ icon: nativeImage.createFromPath(iconPath), title: "Driver has established a Connection", content: "OpenBuilds Machine Driver is now connected to " + status.comms.interfaces.activePort + " running " + status.machine.firmware.type + " " + status.machine.firmware.version }) // Start interval for status queries statusLoop = setInterval(function() { if (status.comms.connectionStatus > 0) { if (!status.comms.sduploading && !status.comms.blocked) { machineSend("?"); } } }, 1000); } else if (data.indexOf("LPC176") >= 0) { // LPC1768 or LPC1769 should be Smoothieware status.comms.blocked = false; console.log("Smoothieware detected"); appIcon.displayBalloon({ icon: nativeImage.createFromPath(iconPath), title: "Driver has established a Connection", content: "OpenBuilds Machine Driver is now connected to " + status.comms.interfaces.activePort + " running " + status.machine.firmware.type + " " + status.machine.firmware.version }) 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) { if (!status.comms.sduploading) { machineSend("?"); if (status.machine.tools.hotend1 || status.machine.tools.hotend2 || status.machine.tools.heatbed) { machineSend("M105\n"); } // machineSend("M911 J0\n"); } } }, 200); // Lets see what we have to deal with // setTimeout(function() { // machineSend("config-get extruder.hotend.enable\n"); //cached: extruder.hotend.enable is set to true // }, 100); // setTimeout(function() { // machineSend("config-get extruder.hotend2.enable\n"); //cached: extruder.hotend2.enable is not in config // }, 200); // setTimeout(function() { // machineSend("config-get temperature_control.bed.enable\n"); //cached: temperature_control.bed.enable is set to true // }, 300); // setTimeout(function() { // machineSend("config-get laser_module_enable\n"); //cached: laser_module_enable is set to false // }, 400); // setTimeout(function() { // machineSend("config-get spindle.enable\n"); //cached: spindle.enable is not in config // }, 500); } // end of machine identification // sdcard listing and upload verification if (command == "M20") { if (data.indexOf("Begin file list") === 0) { status.machine.sdcard.list.length = 0; } else if (data.indexOf("End file list") === 0) { // ignore } else if (data.indexOf("ok") === 0) { // ignore } else if (data.indexOf("Done saving file") != -1) { status.comms.sduploading = false; } else { status.machine.sdcard.list.push(data) } } else if (re.test(data)) { var md5sum = data.split(/[ ,]+/)[0] if (lastmd5sum === md5sum) { console.log("SD UPLOAD VERIFIED! OK") io.sockets.emit('data', "SD UPLOAD COMPLETED, AND MD5 VERIFIED! OK!"); } else { // console.log("SD UPLOAD VERIFIED! FAILED: Original file: " + lastmd5sum +", SD file: " + md5sum ) // Due to firmware changing the content of the file, sometimes a valid upload still fails. A pass is definately a pass. But a fail could just be cosmetic. } } // end sdcard // config identification: generate Array of Config Entries for Smoothie if (command == "cat /sd/config" || command == "cat /sd/config.txt") { // console.log("CONF: " + data) status.machine.firmware.config.push(data) } if (data.indexOf('\"type\": \"TMC26x\"') != -1) { try { var object = JSON.parse(data) } catch (e) { console.log(e); // error in the above string (in this case, yes)! } if (object) { if (object.axis == "X") { status.machine.drivers.x = object; } if (object.axis == "Y") { status.machine.drivers.y = object; } if (object.axis == "Z") { status.machine.drivers.z = object; } if (object.axis == "A") { status.machine.drivers.a = object; } } } // config identification: trinamic driver status if (command.indexOf("M911.1 PX") != -1) { if (data.indexOf("Chip type") != -1) { status.machine.drivers.x.type = data.split(" ")[4]; } if (data.indexOf("Stall Guard value") === 0) { status.machine.drivers.x.stallGuard = data.split(" ")[3]; } if (data.indexOf("Current setting") === 0) { status.machine.drivers.x.current = data.split(" ")[2]; } if (data.indexOf("Microsteps") === 0) { status.machine.drivers.x.microstep = data.split(" ")[1]; } } if (command.indexOf("M911.1 PY") != -1) { if (data.indexOf("Chip type") != -1) { status.machine.drivers.y.type = data.split(" ")[4]; } if (data.indexOf("Stall Guard value") === 0) { status.machine.drivers.y.stallGuard = data.split(" ")[3]; } if (data.indexOf("Current setting") === 0) { status.machine.drivers.y.current = data.split(" ")[2]; } if (data.indexOf("Microsteps") === 0) { status.machine.drivers.y.microstep = data.split(" ")[1]; } } if (command.indexOf("M911.1 PZ") != -1) { if (data.indexOf("Chip type") != -1) { status.machine.drivers.z.type = data.split(" ")[4]; } if (data.indexOf("Stall Guard value") === 0) { status.machine.drivers.z.stallGuard = data.split(" ")[3]; } if (data.indexOf("Current setting") === 0) { status.machine.drivers.z.current = data.split(" ")[2]; } if (data.indexOf("Microsteps") === 0) { status.machine.drivers.z.microstep = data.split(" ")[1]; } } if (command.indexOf("M911.1 PA") != -1) { if (data.indexOf("Chip type") != -1) { status.machine.drivers.a.type = data.split(" ")[4]; } if (data.indexOf("Stall Guard value") === 0) { status.machine.drivers.a.stallGuard = data.split(" ")[3]; } if (data.indexOf("Current setting") === 0) { status.machine.drivers.a.current = data.split(" ")[2]; } if (data.indexOf("Microsteps") === 0) { status.machine.drivers.a.microstep = data.split(" ")[1]; } } // config identification: Tools availability if (data.indexOf("extruder.hotend.enable") != -1) { if (data.indexOf("set to true") != -1) { console.log("Adding 1st Extruder to Tools table"); status.machine.tools.hotend1 = true; status.comms.blocked = false; } } else if (data.indexOf("extruder.hotend2.enabl") != -1) { if (data.indexOf("set to true") != -1) { console.log("Adding 2nd Extruder to Tools table"); status.machine.tools.hotend2 = true; status.comms.blocked = false; } } else if (data.indexOf("temperature_control.bed.enable") != -1) { if (data.indexOf("set to true") != -1) { console.log("Adding Heatbed to Tools table"); status.machine.tools.heatbed = true; status.comms.blocked = false; } } else if (data.indexOf("laser_module_enable") != -1) { if (data.indexOf("set to true") != -1) { console.log("Adding Laser to Tools table"); status.machine.tools.laser = true; status.comms.blocked = false; } } else if (data.indexOf("spindle.enable") != -1) { if (data.indexOf("set to true") != -1) { console.log("Adding Spindle to Tools table"); status.machine.tools.spindle = true; status.comms.blocked = false; } } // Machine Feedback: Temperature and Position if (data.indexOf("ok T:") == 0) { // Got an Temperature Feedback (Smoothie) parseTemp(data) } else if (data.indexOf("<") === 0) { // Got statusReport (Grbl & Smoothieware) // statusfeedback func parseFeedback(data) } else if (data.indexOf("ok") === 0) { // Got an OK so we are clear to send // console.log("OK FOUND") if (status.machine.firmware.type === "grbl") { grblBufferSize.shift(); } status.comms.blocked = false; send1Q(); } else if (data.indexOf('ALARM') === 0) { //} || data.indexOf('HALTED') === 0) { console.log("ALARM: " + data) status.comms.connectionStatus = 5; isAlarmed = true; switch (status.machine.firmware.type) { case 'grbl': grblBufferSize.shift(); var alarmCode = parseInt(data.split(':')[1]); console.log('ALARM: ' + alarmCode + ' - ' + grblStrings.alarms(alarmCode)); status.comms.alarm = alarmCode + ' - ' + grblStrings.alarms(alarmCode) break; case 'smoothie': status.comms.alarm = data; 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; isAlarmed = false; } else if (data.indexOf('Emergency Stop Requested ') != -1) { //} || data.indexOf('HALTED') === 0) { status.comms.connectionStatus = 5; isAlarmed = true; } 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 if (data.indexOf('error:Alarm lock') === 0) { isAlarmed = true; } switch (status.machine.firmware.type) { case 'grbl': grblBufferSize.shift(); var errorCode = parseInt(data.split(':')[1]); console.log('error: ' + errorCode + ' - ' + grblStrings.errors(errorCode) + " [ " + command + " ]"); io.sockets.emit('data', 'error: ' + errorCode + ' - ' + grblStrings.errors(errorCode) + " [ " + command + " ]"); socket.emit("toastError", 'error: ' + errorCode + ' - ' + grblStrings.errors(errorCode) + " [ " + command + " ]") break; case 'smoothie': io.sockets.emit('data', data); break; } } else if (data === ' ') { // nothing } else { // do nothing with +data } }); // end of parser.on(data) } }); socket.on('saveToSd', function(datapack) { saveToSd(datapack); }); socket.on('runJob', function(data) { console.log('Run Job (' + data.length + ')'); if (status.comms.connectionStatus > 0) { if (data) { runningJob = 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) { addQ(tosend); } } if (i > 0) { startTime = new Date(Date.now()); // Start interval for qCount messages to socket clients queueCounter = setInterval(function() { status.comms.queue = gcodeQueue.length - queuePointer }, 500); send1Q(); status.comms.connectionStatus = 3; } } appIcon.displayBalloon({ icon: nativeImage.createFromPath(iconPath), title: "Driver: Job Started", content: "OpenBuilds Machine Driver started a job: Job Size: " + data.length + " lines of GCODE" }) } else { console.log('ERROR: Machine connection not open!'); } }); socket.on('forceQueue', function(data) { send1Q(); }); socket.on('runCommand', function(data) { console.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) { addQ(tosend); } } if (i > 0) { status.comms.runStatus = 'Running' send1Q(); } } } else { console.log('ERROR: Machine connection not open!'); } }); socket.on('jog', function(data) { console.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) { console.log('Adding jog commands to queue. blocked=' + status.comms.blocked + ', paused=' + status.comms.paused + ', Q=' + gcodeQueue.length); switch (status.machine.firmware.type) { case 'grbl': addQ('$J=G91' + dir + dist + feed); send1Q(); break; case 'smoothie': addQ('G91'); addQ('G0' + feed + dir + dist); addQ('G90'); send1Q(); break; default: console.log('ERROR: Unknown firmware!'); break; } } else { console.log('ERROR: Invalid params!'); } } else { console.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} console.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) : ''); console.log('Adding jog commands to queue. blocked=' + status.comms.blocked + ', paused=' + status.comms.paused + ', Q=' + gcodeQueue.length); switch (status.machine.firmware.type) { case 'grbl': addQ('$J=G91' + mode + xVal + yVal + zVal + feed); send1Q(); break; case 'smoothie': addQ('G91' + mode); addQ('G0' + feed + xVal + yVal + zVal); addQ('G90'); send1Q(); break; default: console.log('ERROR: Unknown firmware!'); break; } } else { console.log('error Invalid params!'); } } else { console.log('ERROR: Machine connection not open!'); } }); socket.on('setZero', function(data) { console.log('setZero(' + data + ')'); if (status.comms.connectionStatus > 0) { switch (data) { case 'x': addQ('G10 L20 P0 X0'); break; case 'y': addQ('G10 L20 P0 Y0'); break; case 'z': addQ('G10 L20 P0 Z0'); break; case 'a': addQ('G10 L20 P0 A0'); break; case 'all': addQ('G10 L20 P0 X0 Y0 Z0'); break; case 'xyza': addQ('G10 L20 P0 X0 Y0 Z0 A0'); break; } send1Q(); appIcon.displayBalloon({ icon: nativeImage.createFromPath(iconPath), title: "Driver: Work Coordinate System Reset", content: "OpenBuilds Machine Driver has reset the WCS on the " + data + " axes." }) } else { console.log('ERROR: Machine connection not open!'); } }); socket.on('gotoZero', function(data) { console.log('gotoZero(' + data + ')'); if (status.comms.connectionStatus > 0) { switch (data) { case 'x': addQ('G0 X0'); break; case 'y': addQ('G0 Y0'); break; case 'z': addQ('G0 Z0'); break; case 'a': addQ('G0 A0'); break; case 'all': addQ('G0 X0 Y0 Z0'); break; case 'xyza': addQ('G0 X0 Y0 Z0 A0'); break; } send1Q(); } else { console.log('ERROR: Machine connection not open!'); } }); socket.on('setPosition', function(data) { console.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) + ' ' : ''); addQ('G10 L20 P0 ' + xVal + yVal + zVal + aVal); send1Q(); } } else { console.log('ERROR: Machine connection not open!'); } }); socket.on('probe', function(data) { console.log('probe(' + JSON.stringify(data) + ')'); if (status.comms.connectionStatus > 0) { switch (status.machine.firmware.type) { case 'smoothie': switch (data.direction) { case 'z': addQ('G30 Z' + data.probeOffset); break; default: addQ('G38.2 ' + data.direction); break; } case 'grbl': addQ('G38.2 ' + data.direction + '-5 F1'); addQ('G92 ' + data.direction + ' ' + data.probeOffset); break; default: //not supported console.log('Command not supported by firmware!'); break; } send1Q(); } else { console.log('ERROR: Machine connection not open!'); } }); socket.on('feedOverride', function(data) { console.log(data) if (status.comms.connectionStatus > 0) { switch (status.machine.firmware.type) { case 'grbl': console.log("current FRO = " + status.machine.overrides.feedOverride) console.log("requested FRO = " + data) var curfro = parseInt(status.machine.overrides.feedOverride) var reqfro = parseInt(data) var delta; if (reqfro == 100) { machineSend(String.fromCharCode(144)); } else if (curfro < reqfro) { // FRO Increase delta = reqfro - curfro console.log("delta = " + delta) var tens = Math.floor(delta / 10) console.log("need to send " + tens + " x10s increase") for (i = 0; i < tens; i++) { machineSend(String.fromCharCode(145)); } var ones = delta - (10 * tens); console.log("need to send " + ones + " x1s increase") for (i = 0; i < ones; i++) { machineSend(String.fromCharCode(147)); } } else if (curfro > reqfro) { // FRO Decrease delta = curfro - reqfro console.log("delta = " + delta) var tens = Math.floor(delta / 10) console.log("need to send " + tens + " x10s decrease") for (i = 0; i < tens; i++) { machineSend(String.fromCharCode(146)); } var ones = delta - (10 * tens); console.log("need to send " + ones + " x1s decrease") for (i = 0; i < tens; i++) { machineSend(String.fromCharCode(148)); } } status.machine.overrides.feedOverride = reqfro // Set now, but will be overriden from feedback from Grbl itself in next queryloop // var code; // switch (data) { // case 0: // code = 144; // set to 100% // data = '100'; // break; // case 10: // code = 145; // +10% // data = '+' + data; // break; // case -10: // code = 146; // -10% // break; // case 1: // code = 147; // +1% // data = '+' + data; // break; // case -1: // code = 148; // -1% // break; // } // console.log("Code:" + code) // if (code) { // //jumpQ(String.fromCharCode(parseInt(code))); // machineSend(String.fromCharCode(parseInt(code))); // console.log('Sent: Code(' + code + ')'); // console.log('Feed Override ' + data + '%'); // } break; case 'smoothie': if (data === 0) { feedOverride = 100; } else { if ((feedOverride + data <= 200) && (feedOverride + data >= 10)) { // valid range is 10..200, else ignore! feedOverride += data; } } //jumpQ('M220S' + feedOverride); machineSend('M220S' + feedOverride + '\n'); // console.log('Sent: M220S' + feedOverride); status.machine.overrides.feedOverride = feedOverride // console.log('Feed Override ' + feedOverride.toString() + '%'); //send1Q(); break; } } else { console.log('ERROR: Machine connection not open!'); } }); socket.on('spindleOverride', function(data) { if (status.comms.connectionStatus > 0) { switch (status.machine.firmware.type) { case 'grbl': console.log("current SRO = " + status.machine.overrides.spindleOverride) console.log("requested SRO = " + data) var cursro = parseInt(status.machine.overrides.spindleOverride) var reqsro = parseInt(data) var delta; if (reqsro == 100) { machineSend(String.fromCharCode(153)); } else if (cursro < reqsro) { // FRO Increase delta = reqsro - cursro console.log("delta = " + delta) var tens = Math.floor(delta / 10) console.log("need to send " + tens + " x10s increase") for (i = 0; i < tens; i++) { machineSend(String.fromCharCode(154)); } var ones = delta - (10 * tens); console.log("need to send " + ones + " x1s increase") for (i = 0; i < ones; i++) { machineSend(String.fromCharCode(156)); } } else if (cursro > reqsro) { // FRO Decrease delta = cursro - reqsro console.log("delta = " + delta) var tens = Math.floor(delta / 10) console.log("need to send " + tens + " x10s decrease") for (i = 0; i < tens; i++) { machineSend(String.fromCharCode(155)); } var ones = delta - (10 * tens); console.log("need to send " + ones + " x1s decrease") for (i = 0; i < tens; i++) { machineSend(String.fromCharCode(157)); } } status.machine.overrides.spindleOverride = reqsro // Set now, but will be overriden from feedback from Grbl itself in next queryloop // var code; // switch (data) { // case 0: // code = 153; // set to 100% // data = '100'; // break; // case 10: // code = 154; // +10% // data = '+' + data; // break; // case -10: // code = 155; // -10% // break; // case 1: // code = 156; // +1% // data = '+' + data; // break; // case -1: // code = 157; // -1% // break; // } // if (code) { // //jumpQ(String.fromCharCode(parseInt(code))); // machineSend(String.fromCharCode(parseInt(code))); // console.log('Sent: Code(' + code + ')'); // console.log('Spindle (Laser) Override ' + data + '%'); // } break; case 'smoothie': if (data === 0) { spindleOverride = 100; } else { if ((spindleOverride + data <= 200) && (spindleOverride + data >= 0)) { // valid range is 0..200, else ignore! spindleOverride += data; } } //jumpQ('M221S' + spindleOverride); machineSend('M221S' + spindleOverride + '\n'); // console.log('Sent: M221S' + spindleOverride); status.machine.overrides.spindleOverride = spindleOverride; // console.log('Spindle (Laser) Override ' + spindleOverride.toString() + '%'); //send1Q(); break; } } else { console.log('ERROR: Machine connection not open!'); } }); socket.on('laserTest', function(data) { // Laser Test Fire laserTest(data); }); socket.on('pause', function() { if (status.comms.connectionStatus > 0) { status.comms.paused = true; console.log('PAUSE'); switch (status.machine.firmware.type) { case 'grbl': machineSend('!'); // Send hold command console.log('Sent: !'); if (status.machine.firmware.version === '1.1d') { machineSend(String.fromCharCode(0x9E)); // Stop Spindle/Laser console.log('Sent: Code(0x9E)'); } break; case 'smoothie': machineSend('M600'); // Laser will be turned off by smoothie (in default config!) //machineSend('M600\n'); // Laser will be turned off by smoothie (in default config!) console.log('Sent: M600'); break; } status.comms.runStatus = 'Paused'; status.comms.connectionStatus = 4; appIcon.displayBalloon({ icon: nativeImage.createFromPath(iconPath), title: "Driver: Job Paused", content: "OpenBuilds Machine Driver paused the job" }) } else { console.log('ERROR: Machine connection not open!'); } }); socket.on('resume', function() { if (status.comms.connectionStatus > 0) { console.log('UNPAUSE'); switch (status.machine.firmware.type) { case 'grbl': machineSend('~'); // Send resume command console.log('Sent: ~'); break; case 'smoothie': machineSend('M601'); // Send resume command //machineSend('M601\n'); console.log('Sent: M601'); break; } status.comms.paused = false; status.comms.blocked = false; setTimeout(function() { send1Q(); // restart queue }, 200); status.comms.runStatus = 'Resuming'; status.comms.connectionStatus = 3; appIcon.displayBalloon({ icon: nativeImage.createFromPath(iconPath), title: "Driver: Job Resumed", content: "OpenBuilds Machine Driver resumed the job" }) } else { console.log('ERROR: Machine connection not open!'); } }); socket.on('stop', function() { if (status.comms.connectionStatus > 0) { status.comms.paused = true; console.log('STOP'); switch (status.machine.firmware.type) { case 'grbl': machineSend('!'); // hold console.log('Sent: !'); if (status.machine.firmware.version === '1.1d') { machineSend(String.fromCharCode(0x9E)); // Stop Spindle/Laser console.log('Sent: Code(0x9E)'); } console.log('Cleaning Queue'); machineSend(String.fromCharCode(0x18)); // ctrl-x console.log('Sent: Code(0x18)'); break; case 'smoothie': status.comms.paused = true; machineSend('M112'); // ctrl-x console.log('Sent: M112'); break; } clearInterval(queueCounter); status.comms.queue = 0 queuePointer = 0; gcodeQueue.length = 0; // Dump the queue grblBufferSize.length = 0; // Dump bufferSizes lastGcode.length = 0 // Dump Last Command Queue queueLen = 0; queuePos = 0; laserTestOn = false; startTime = null; runningJob = null; status.comms.blocked = false; status.comms.paused = false; status.comms.runStatus = 'Stopped'; status.comms.connectionStatus = 2; isAlarmed = false; appIcon.displayBalloon({ icon: nativeImage.createFromPath(iconPath), title: "Driver: Job Aborted", content: "OpenBuilds Machine Driver was asked to abort the running job." }) // status.comms.connectionStatus = 2; } else { console.log('ERROR: Machine connection not open!'); } }); socket.on('clearAlarm', function(data) { // Clear Alarm if (status.comms.connectionStatus > 0) { data = parseInt(data); console.log('Clearing Queue: Method ' + data); switch (data) { case 1: console.log('Clearing Lockout'); switch (status.machine.firmware.type) { case 'grbl': machineSend('$X\n'); console.log('Sent: $X'); break; case 'smoothie': machineSend('$X\n'); console.log('Sent: $X'); break; } console.log('Resuming Queue Lockout'); break; case 2: console.log('Emptying Queue'); gcodeQueue.length = 0; // Dump the queue grblBufferSize.length = 0; // Dump bufferSizes lastGcode.length = 0 // Dump Last Command Queue queueLen = 0; queuePointer = 0; queuePos = 0; startTime = null; console.log('Clearing Lockout'); switch (status.machine.firmware.type) { case 'grbl': machineSend('$X\n'); console.log('Sent: $X'); status.comms.blocked = false; status.comms.paused = false; break; case 'smoothie': machineSend('M999'); //M999 console.log('Sent: M999'); send1Q(); status.comms.blocked = false; status.comms.paused = false; break; } break; } status.comms.runStatus = 'Stopped' status.comms.connectionStatus = 2; isAlarmed = false; appIcon.displayBalloon({ icon: nativeImage.createFromPath(iconPath), title: "Driver: Alarm Cleared", content: "OpenBuilds Machine Driver has cleared the Alarm Condition, you may continue" }) } else { console.log('ERROR: Machine connection not open!'); } }); socket.on('resetMachine', function() { if (status.comms.connectionStatus > 0) { console.log('Reset Machine'); switch (status.machine.firmware.type) { case 'grbl': machineSend(String.fromCharCode(0x18)); // ctrl-x console.log('Sent: Code(0x18)'); break; case 'smoothie': machineSend(String.fromCharCode(0x18)); // ctrl-x console.log('Sent: Code(0x18)'); break; } } else { console.log('ERROR: Machine connection not open!'); } }); socket.on('closePort', function(data) { // Close machine port and dump queue if (status.comms.connectionStatus > 0) { console.log('WARN: Closing Port ' + port.path); stopPort(); } else { console.log('ERROR: Machine connection not open!'); } }); }); function machineSend(gcode) { // console.log("SENDING: " + gcode) if (port.isOpen) { var queueLeft = (gcodeQueue.length - queuePointer) var queueTotal = gcodeQueue.length // console.log("Q: " + queueLeft) var data = [] data.push(queueLeft); data.push(queueTotal); data.push(status.comms.sduploading) io.sockets.emit("queueCount", data); port.write(gcode); lastGcode.push(gcode); if (gcode == "cat /sd/config\n" || gcode == "cat /sd/config.txt\n") { // console.log("DUMPING CONFIG ARRAY") status.machine.firmware.config.length = 0; } if (gcode.indexOf("M20") != -1) { status.machine.sdcard.list.length = 0; } // console.log("SENT: " + gcode) } else { console.log("PORT NOT OPEN") } } function grblBufferSpace() { var total = 0; var len = grblBufferSize.length; for (var i = 0; i < len; i++) { total += grblBufferSize[i]; } return GRBL_RX_BUFFER_SIZE - total; } function send1Q() { var gcode; var gcodeLen = 0; var spaceLeft = 0; if (status.comms.connectionStatus > 0) { switch (status.machine.firmware.type) { case 'grbl': if (new_grbl_buffer) { if (grblBufferSize.length === 0) { spaceLeft = GRBL_RX_BUFFER_SIZE; while ((queueLen - queuePointer) > 0 && spaceLeft > 0 && !status.comms.blocked && !status.comms.paused) { gcodeLen = gcodeQueue[queuePointer].length; if (gcodeLen < spaceLeft) { // Add gcode to send buffer gcode = gcodeQueue[queuePointer]; queuePointer++; grblBufferSize.push(gcodeLen + 1); gcodeLine += gcode + '\n'; spaceLeft = GRBL_RX_BUFFER_SIZE - gcodeLine.length; } else { // Not enough space left in send buffer status.comms.blocked = true; } } if (gcodeLine.length > 0) { // Send the buffer status.comms.blocked = true; machineSend(gcodeLine); // console.log('Sent: ' + gcodeLine + ' Q: ' + (queueLen - queuePointer)); } } } else { while ((queueLen - queuePointer) > 0 && !status.comms.blocked && !status.comms.paused) { spaceLeft = grblBufferSpace(); gcodeLen = gcodeQueue[queuePointer].length; if (gcodeLen < spaceLeft) { gcode = gcodeQueue[queuePointer]; queuePointer++; grblBufferSize.push(gcodeLen + 1); machineSend(gcode + '\n'); // console.log('Sent: ' + gcode + ' Q: ' + (queueLen - queuePointer) + ' Bspace: ' + (spaceLeft - gcodeLen - 1)); } else { status.comms.blocked = true; } } } break; case 'smoothie': if ((gcodeQueue.length - queuePointer) > 0 && !status.comms.blocked && !status.comms.paused) { gcode = gcodeQueue[queuePointer]; queuePointer++; status.comms.blocked = true; machineSend(gcode + '\n'); // console.log('Sent: ' + gcode + ' Q: ' + (gcodeQueue.length - queuePointer)); } break; } if (queuePointer >= gcodeQueue.length) { if (!isAlarmed) { status.comms.connectionStatus = 2; } else if (isAlarmed) { status.comms.connectionStatus = 5; } clearInterval(queueCounter); if (startTime) { finishTime = new Date(Date.now()); elapsedTimeMS = finishTime.getTime() - startTime.getTime(); elapsedTime = Math.round(elapsedTimeMS / 1000); speed = (queuePointer / elapsedTime).toFixed(0); console.log("Job started at " + startTime.toString()); console.log("Job finished at " + finishTime.toString()); console.log("Elapsed time: " + elapsedTime + " seconds."); console.log('Ave. Speed: ' + speed + ' lines/s'); appIcon.displayBalloon({ icon: nativeImage.createFromPath(iconPath), title: "Driver: Job Completed!", content: "OpenBuilds Machine Driver completed a Job in " + elapsedTime + " seconds. We processed " + speed + " gcode lines/second on average." }) } gcodeQueue.length = 0; // Dump the Queye grblBufferSize.length = 0; // Dump bufferSizes queueLen = 0; queuePointer = 0; queuePos = 0; startTime = null; runningJob = null; // status.comms.runStatus = "Finished" } } } function addQ(gcode) { gcodeQueue.push(gcode); queueLen = gcodeQueue.length; } function parseFeedback(data) { // console.log(data) var state = data.substring(1, data.search(/(,|\|)/)); status.comms.runStatus = state if (state == "Alarm") { // console.log("ALARM: " + data) status.comms.connectionStatus = 5; isAlarmed = true; switch (status.machine.firmware.type) { case 'grbl': grblBufferSize.shift(); var alarmCode = parseInt(data.split(':')[1]); // console.log('ALARM: ' + alarmCode + ' - ' + grblStrings.alarms(alarmCode)); status.comms.alarm = alarmCode + ' - ' + grblStrings.alarms(alarmCode) // if (alarmCode == 10) { // io.sockets.emit("toastError", 'alarm: ' + alarmCode + ' - Locked. Please Unlock or Clear Alarm'); // } else { // io.sockets.emit("toastError", 'alarm: ' + alarmCode + ' - ' + grblStrings.alarms(alarmCode)); // } break; case 'smoothie': status.comms.alarm = data; break; } status.comms.connectionStatus = 5; } if (status.machine.firmware.type == "grbl") { // 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(/,/); } if (Array.isArray(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); has4thAxis = true; } } if (has4thAxis) { status.machine.position.work.x = xPos status.machine.position.work.y = yPos status.machine.position.work.z = zPos status.machine.position.work.a = aPos } else { status.machine.position.work.x = xPos status.machine.position.work.y = yPos status.machine.position.work.z = zPos } } // END IS WPOS // 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 (has4thAxis) { aOffset = parseFloat(wco[3]).toFixed(config.posDecimals); status.machine.position.offset.x = xOffset; status.machine.position.offset.y = yOffset; status.machine.position.offset.z = zOffset; status.machine.position.offset.a = aOffset; } else { status.machine.position.offset.x = xOffset; status.machine.position.offset.y = yOffset; status.machine.position.offset.z = zOffset; } } } if (status.machine.firmware.type == "smoothie") { // Extract wPos (for Smoothieware only!) var startWPos = data.search(/wpos:/i) + 5; var wPos; if (startWPos > 5) { wPos = data.replace('>', '').substr(startWPos).split(/,/, 4); } if (Array.isArray(wPos)) { if (xPos !== wPos[0]) { xPos = wPos[0]; } if (yPos !== wPos[1]) { yPos = wPos[1]; } if (zPos !== wPos[2]) { zPos = wPos[2]; } if (wPos.length > 3) { if (aPos !== wPos[3]) { aPos = wPos[3]; has4thAxis = true; } } if (has4thAxis) { status.machine.position.work.x = parseFloat(xPos).toFixed(config.posDecimals) status.machine.position.work.y = parseFloat(yPos).toFixed(config.posDecimals) status.machine.position.work.z = parseFloat(zPos).toFixed(config.posDecimals) status.machine.position.work.a = parseFloat(aPos).toFixed(config.posDecimals) } else { status.machine.position.work.x = parseFloat(xPos).toFixed(config.posDecimals) status.machine.position.work.y = parseFloat(yPos).toFixed(config.posDecimals) status.machine.position.work.z = parseFloat(zPos).toFixed(config.posDecimals) } } // Extract mPos (for Smoothieware only!) var startMPos = data.search(/mpos:/i) + 5; var mPos; if (startMPos > 5) { mPos = data.replace(">", "").substr(startMPos).split(/,|\|/, 4); } if (Array.isArray(mPos)) { if (xOffset != mPos[0] - xPos) { xOffset = mPos[0] - xPos; } if (yOffset != mPos[1] - yPos) { yOffset = mPos[1] - yPos; } if (zOffset != mPos[2] - zPos) { zOffset = mPos[2] - zPos; } if (has4thAxis) { if (aOffset != mPos[3] - aPos) { aOffset = mPos[3] - aPos; } } if (has4thAxis) { status.machine.position.offset.x = parseFloat(xOffset).toFixed(config.posDecimals); status.machine.position.offset.y = parseFloat(yOffset).toFixed(config.posDecimals); status.machine.position.offset.z = parseFloat(zOffset).toFixed(config.posDecimals); status.machine.position.offset.a = parseFloat(aOffset).toFixed(config.posDecimals); } else { status.machine.position.offset.x = parseFloat(xOffset).toFixed(config.posDecimals); status.machine.position.offset.y = parseFloat(yOffset).toFixed(config.posDecimals); status.machine.position.offset.z = parseFloat(zOffset).toFixed(config.posDecimals); } } } // 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 = ov[0]; } if (ov[1]) { status.machine.overrides.rapidOverride = ov[1]; } if (ov[2]) { status.machine.overrides.spindleOverride = ov[2]; } } } // Extract realtime Feed and Spindle (for Grbl > v1.1 only!) var startFS = data.search(/FS:/i) + 3; if (startFS > 3) { var fs = data.replace(">", "").substr(startFS).split(/,|\|/); if (Array.isArray(fs)) { if (fs[0]) { status.machine.overrides.realFeed = fs[0]; } if (fs[1]) { status.machine.overrides.realSpindle = fs[1]; } } } // end statusreport } function parseTemp(data) { var heaterT0ActualTemp, heaterT0DisplayTemp, heaterT1ActualTemp, heaterT1DisplayTemp, bedActualTemp, bedDisplayTemp; // console.log(response); for (var r, n = /(B|T(\d*)):\s*([+]?[0-9]*\.?[0-9]+)? (\/)([+]?[0-9]*\.?[0-9]+)?/gi; null !== (r = n.exec(data));) { var o = r[1], a = r[3] + "°C"; a += "/" + r[5] + "°C", "T" == o ? (heaterT0ActualTemp = r[3], heaterT0DisplayTemp = r[5]) : "T1" == o && (heaterT1ActualTemp = r[3], heaterT1DisplayTemp = r[5]), "B" == o && (bedActualTemp = Number(r[3]), bedDisplayTemp = r[5]); } if (heaterT0ActualTemp) { status.machine.temperature.setpoint.t0 = parseFloat(heaterT0DisplayTemp); status.machine.temperature.actual.t0 = parseFloat(heaterT0ActualTemp); } if (heaterT1ActualTemp) { status.machine.temperature.setpoint.t1 = parseFloat(heaterT1DisplayTemp); status.machine.temperature.actual.t1 = parseFloat(heaterT1ActualTemp); } if (bedActualTemp) { status.machine.temperature.setpoint.b = parseFloat(bedDisplayTemp); status.machine.temperature.actual.b = parseFloat(bedActualTemp); } } function saveToSd(datapack) { var filename = datapack[0] var data = datapack[1] status.comms.sduploading = true console.log('Saving Job (' + data.length + ' bytes) to SD as ' + filename); switch (status.machine.firmware.type) { case 'grbl': console.log('SD not supported by Grbl'); break; case 'smoothie': if (status.comms.connectionStatus > 0) { if (data) { data = data.split('\n'); var string = ""; addQ('M28 ' + filename); 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) { addQ(tosend); string += tosend + "\n" } } addQ('M29'); addQ('md5sum /sd/' + filename); addQ('M20'); send1Q(); lastmd5sum = md5(string); // console.log(string) } } else { console.log('ERROR: Machine connection not open!'); } break; } } 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 // console.log('laserTest: ' + 'Power ' + power + ', Duration ' + duration + ', maxS ' + maxS); if (duration >= 0) { switch (status.machine.firmware.type) { case 'grbl': addQ('G1F1'); addQ('M3S' + parseInt(power * maxS / 100)); laserTestOn = true; io.sockets.emit('laserTest', power); if (duration > 0) { addQ('G4 P' + duration / 1000); addQ('M5S0'); laserTestOn = false; } send1Q(); break; case 'smoothie': addQ('M3\n'); addQ('fire ' + power + '\n'); laserTestOn = true; io.sockets.emit('laserTest', power); if (duration > 0) { var divider = 1; if (fDate >= new Date('2017-01-02')) { divider = 1000; } addQ('G4P' + duration / divider + '\n'); addQ('fire off\n'); addQ('M5'); setTimeout(function() { laserTestOn = false; io.sockets.emit('laserTest', 0); }, duration); } send1Q(); break; } } } else { // console.log('laserTest: ' + 'Power off'); switch (status.machine.firmware.type) { case 'grbl': addQ('M5S0'); send1Q(); break; case 'smoothie': addQ('fire off\n'); addQ('M5\n'); send1Q(); break; } laserTestOn = false; io.sockets.emit('laserTest', 0); } } } else { console.log('ERROR: Machine connection not open!'); } } 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; } const shouldQuit = electronApp.makeSingleInstance((commandLine, workingDirectory) => { // Someone tried to run a second instance, we should focus our window. if (jogWindow === null) { createJogWindow(); jogWindow.show() jogWindow.setAlwaysOnTop(true); jogWindow.focus(); jogWindow.setAlwaysOnTop(false); } else { jogWindow.show() jogWindow.setAlwaysOnTop(true); jogWindow.focus(); jogWindow.setAlwaysOnTop(false); } }); if (shouldQuit) { console.log("Already running! Check the System Tray") electronApp.exit(0); electronApp.quit(); } if (electronApp) { // Module to create native browser window. function createApp() { createTrayIcon(); // createWindow(); // createJogWindow(); } function createTrayIcon() { appIcon = new Tray( nativeImage.createFromPath(iconPath) ) const contextMenu = Menu.buildFromTemplate([ // { // label: 'Launch Full Application', // click() { // createWindow(); // } // }, { label: 'Quit Machine Driver', click() { appIcon.destroy(); electronApp.exit(0); } } ]) appIcon.on('click', function() { // console.log("Clicked Systray") if (jogWindow === null) { createJogWindow(); jogWindow.show() jogWindow.setAlwaysOnTop(true); jogWindow.focus(); jogWindow.setAlwaysOnTop(false); } else { jogWindow.show() jogWindow.setAlwaysOnTop(true); jogWindow.focus(); jogWindow.setAlwaysOnTop(false); } }) appIcon.on('balloon-click', function() { // console.log("Clicked Systray") if (jogWindow === null) { createJogWindow(); jogWindow.show() jogWindow.setAlwaysOnTop(true); jogWindow.focus(); jogWindow.setAlwaysOnTop(false); } else { jogWindow.show() jogWindow.setAlwaysOnTop(true); jogWindow.focus(); jogWindow.setAlwaysOnTop(false); } }) // Call this again for Linux because we modified the context menu appIcon.setContextMenu(contextMenu) appIcon.displayBalloon({ icon: nativeImage.createFromPath(iconPath), title: "Driver Started", content: "OpenBuilds Machine Driver has started successfully: Active on " + ip.address() + ":" + config.webPort }) } function createJogWindow() { // Create the browser window. jogWindow = new BrowserWindow({ width: 660, height: 710, fullscreen: false, center: true, resizable: true, title: "OpenBuilds Machine Driver ", frame: false, autoHideMenuBar: true, icon: '/app/favicon.png' }); jogWindow.setOverlayIcon(nativeImage.createFromPath(iconPath), 'Icon'); var ipaddr = ip.address(); // jogWindow.loadURL(`//` + ipaddr + `:3000/`) jogWindow.loadURL("http://localhost:3000/"); jogWindow.on('minimize', function(event) { event.preventDefault(); jogWindow.hide(); }); jogWindow.on('close', function(event) { event.preventDefault(); 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', () => { jogWindow.show() jogWindow.setAlwaysOnTop(true); jogWindow.focus(); jogWindow.setAlwaysOnTop(false); }) // jogWindow.maximize() // jogWindow.webContents.openDevTools() } function createWindow() { // Create the browser window. mainWindow = new BrowserWindow({ width: 1200, height: 900, fullscreen: false, center: true, resizable: true, title: "OpenBuilds Machine Driver ", frame: true, autoHideMenuBar: true, icon: '/app/favicon.png' }); // and load the index.html of the app. // mainWindow.loadURL('file://' + __dirname + '/app/index.html'); // mainWindow.loadURL(`file://${__dirname}/app/index.html`) var ipaddr = ip.address(); // mainWindow.loadURL(`//` + ipaddr + `:3000/`) mainWindow.loadURL("http://localhost:3000"); mainWindow.on('minimize', function(event) { event.preventDefault(); mainWindow.hide(); }); mainWindow.on('close', function(event) { event.preventDefault(); mainWindow.hide(); return false; }); // Emitted when the window is closed. mainWindow.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. mainWindow = null; }); mainWindow.once('ready-to-show', () => { mainWindow.show() }) mainWindow.maximize() // mainWindow.webContents.openDevTools() }; electronApp.commandLine.appendSwitch("--ignore-gpu-blacklist"); // 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); // 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 (process.platform !== 'darwin') { electronApp.quit(); appIcon.destroy(); } }); 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 electronApp.setLoginItemSettings({ openAtLogin: true, args: [] }) } process.on('uncaughtException', function(error) { // console.log("Uncaught Error " + error) });