UNPKG

espruino-web-ide

Version:

A Terminal and Graphical code Editor for Espruino JavaScript Microcontrollers

843 lines (812 loc) 32 kB
#!/usr/bin/env node /* Entrypoint for node module command-line app. Not used for Web IDE */ var fs = require("fs"); function getHelp() { return [ "USAGE: espruino ...options... [file_to_upload.js]", "", " -h,--help : Show this message", " -j [job.json] : Load options from JSON job file - see configDefaults.json", " Calling without a job filename creates a new job file ", " named after the uploaded file", " -v,--verbose : Verbose", " -q,--quiet : Quiet - apart from Espruino output", " -m,--minify : Minify the code before sending it", " -t,--time : Set Espruino's time when uploading code", " -w,--watch : If uploading a JS file, continue to watch it for", " changes and upload again if it does.", " -e command : Evaluate the given expression on Espruino", " If no file to upload is specified but you use -e,", " Espruino will not be reset", " --sleep 10 : Sleep for the given number of seconds after uploading code", " -n : Do not connect to Espruino to upload code", " --board BRDNAME/BRD.json : Rather than checking on connect, use the given board name or file", " --ide [8080] : Serve up the Espruino Web IDE on the given port. If not specified, 8080 is the default.", "", " -p,--port /dev/ttyX : Connect to a serial port", " -p,--port aa:bb:cc:dd:ee : Connect to a Bluetooth device by addresses", " -p,--port tcp://192.168.1.50 : Connect to a network device (port 23 default)", " -d deviceName : Connect to the first device with a name containing deviceName", " --download fileName : Download a file with the name matching fileName to the current directory", " -b baudRate : Set the baud rate of the serial connection", " No effect when using USB, default: 9600", " --no-ble : Disables Bluetooth Low Energy (using the 'noble' module)", " --list : List all available devices and exit", "", " --listconfigs : Show all available config options and exit", " --config key=value : Set internal Espruino config option", "", " -o out.js : Write the actual JS code sent to Espruino to a file", " --ohex out.hex : Write the JS code to a hex file as if sent by E.setBootCode", " --storage fn:data.bin : Load 'data.bin' from disk and write it to Storage as 'fn'", " --storage .boot0:- : Store program code in the given Storage file (not .bootcde)", "", " -f firmware.bin[:N] : Update Espruino's firmware to the given file", " Must be a USB Espruino in bootloader mode", " (bluetooth is not currently supported).", " Optionally skip N first bytes of the bin file.", "", "If no file, command, or firmware update is specified, this will act", "as a terminal for communicating directly with Espruino. Press Ctrl-C", "twice to exit.", "", "Please report bugs via https://github.com/espruino/EspruinoTools/issues", ""] } //override default console.log var log = console.log; console.log = function() { if (args.verbose) log.apply(console, arguments); } //Parse Arguments var args = { ports: [], // ports to try and connect to config: {}, // Config defines to set when running Espruino tools storageContents : {} // Storage files to save when using ohex }; var isNextValidNumber = function(next) { return next && isFinite(parseFloat(next)); } var isNextValidPort = function(next) { return next && next[0]!=='-' && next.indexOf(".js") == -1; } var isNextValidFileType = function(next, fileType) { return next && next[0]!=='-' && next.indexOf(fileType) >= 0; } var isNextValidJSON = function(next) { return isNextValidFileType(next, ".json"); } var isNextValidHEX = function(next) { return isNextValidFileType(next, ".hex"); } var isNextValidJS = function(next) { return next && !isNextValidJSON(next) && next.indexOf(".js") >= 0; } var isNextValid = function(next) { return next && next[0]!=='-'; } for (var i=2;i<process.argv.length;i++) { var arg = process.argv[i]; var next = process.argv[i+1]; if (arg[0]=="-") { if (arg=="-h" || arg=="--help") args.help = true; else if (arg=="-v" || arg=="--verbose") args.verbose = true; else if (arg=="-q" || arg=="--quiet") args.quiet = true; else if (arg=="-c" || arg=="--color") args.color = true; else if (arg=="-m" || arg=="--minify") args.minify = true; else if (arg=="-t" || arg=="--time") args.setTime = true; else if (arg=="-w" || arg=="--watch") args.watchFile = true; else if (arg=="-n" || arg=="--nosend") args.nosend = true; else if (arg=="--no-ble") args.noBle = true; else if (arg=="--list") args.showDevices = true; else if (arg=="--listconfigs") args.showConfigs = true; else if (arg=="-p" || arg=="--port") { args.ports.push({type:"path",name:next}); var j = (++i) + 1; while (isNextValidPort(process.argv[j])) { args.ports.push({type:"path",name:process.argv[j++]}); i++; } if (!isNextValidPort(next)) throw new Error("Expecting a port argument to -p, --port"); } else if (arg=="-d") { i++; args.ports.push({type:"name",name:next}); if (!isNextValid(next)) throw new Error("Expecting a name argument to -d"); } else if (arg=="--download") { i++; args.fileToDownload = next; if (!isNextValid(next)) throw new Error("Expecting a file name argument to --download"); } else if (arg=="--config") { i++; if (!next || next.indexOf("=")==-1) throw new Error("Expecting a key=value argument to --config"); var kidx = next.indexOf("="); try { args.config[next.substr(0,kidx)] = JSON.parse(next.substr(kidx+1)); } catch (e) { // treat as a string args.config[next.substr(0,kidx)] = next.substr(kidx+1); } } else if (arg=="-e") { i++; args.expr = next; if (!isNextValid(next)) throw new Error("Expecting an expression argument to -e"); } else if (arg=="-b") { i++; args.baudRate = parseInt(next); if (!isNextValid(next) || isNaN(args.baudRate)) throw new Error("Expecting a numeric argument to -b"); } else if (arg=="-j") { args.job = ""; // will trigger makeJobFile if (isNextValidJSON(next)) { i++; args.job = next; }; // optional } else if (arg=="-o") { i++; args.outputJS = next; if (!isNextValidJS(next)) throw new Error("Expecting a JS filename argument to -o"); } else if (arg=="--ohex" || arg=="-ohex"/* backwards compat.*/) { i++; args.outputHEX = next; if (!isNextValidHEX(next)) throw new Error("Expecting a .hex file argument to --ohex"); } else if (arg=="--storage") { i++; var d = next.split(":"); if (!next || next.indexOf(":")<0) throw new Error("Expecting a fn:file.bin argument to --storage"); args.storageContents[d[0]] = d[1]; } else if (arg=="-f") { i++; var arg = next; if (!isNextValid(next)) throw new Error("Expecting a filename argument to -f"); arg = arg.split(':', 2); args.updateFirmware = arg[0]; args.firmwareFlashOffset = parseInt(arg[1] || '0'); if (isNaN(args.firmwareFlashOffset)) throw new Error("Expecting a numeric offset for -f"); } else if (arg=="--board") { i++; args.board = next; if (!isNextValid(next)) throw new Error("Expecting an argument to --board"); } else if (arg=="--ide") { args.ideServer = 8080; if (isFinite(parseInt(next))) { args.ideServer = parseInt(next); i++; } } else if (arg=="--sleep") { i++; args.sleepAfterUpload = parseFloat(next); if (!isNextValidNumber(next)) throw new Error("Expecting a number argument to --sleep"); } else throw new Error("Unknown Argument '"+arg+"', try --help"); } else { if ("file" in args) throw new Error("File already specified as '"+args.file+"'"); args.file = arg; } } // job file injection... if (args.job) { var job = fs.readFileSync(args.job, {encoding:"utf8"}); // read the job file var config = JSON.parse(job); for (var key in config) args[key] = config[key]; } //Extra argument stuff args.espruinoPrefix = args.quiet?"":"--] "; args.espruinoPostfix = ""; if (args.color) { args.espruinoPrefix = "\033[32m"; args.espruinoPostfix = "\033[0m"; } //this is called after Espruino tools are loaded, and //sets up configuration as requested by the command-line options function setupConfig(Espruino, callback) { if (args.minify) Espruino.Config.MINIFICATION_LEVEL = "ESPRIMA"; if (args.baudRate && !isNaN(args.baudRate)) Espruino.Config.BAUD_RATE = args.baudRate; if (args.noBle) Espruino.Config.BLUETOOTH_LOW_ENERGY = false; if (args.setTime) Espruino.Config.SET_TIME_ON_WRITE = true; if (args.watchFile && !args.file) throw new Error("--watch specified, with no file to watch!"); if (args.updateFirmware && (args.file || args.expr)) throw new Error("Can't update firmware *and* upload code right now."); if (args.espruino) { // job file injections for (var key in args.espruino) Espruino.Config[key] = args.espruino[key]; } if (args.outputHEX) { log("--ohex used - enabling MODULE_AS_FUNCTION"); Espruino.Config.MODULE_AS_FUNCTION = true; } if (Object.keys(args.storageContents).length) { for (var storageName in args.storageContents) { var fileName = args.storageContents[storageName]; if (fileName=="-") args.storageContents[storageName] = { code : true }; else { args.storageContents[storageName] = { data: fs.readFileSync(fileName, {encoding:"utf8"}).toString() }; } } } if (args.config) { for (var key in args.config) { console.log("Command-line option set Espruino.Config."+key+" to "+JSON.stringify(args.config[key])); Espruino.Config[key] = args.config[key]; } } if ( args.ports && args.ports.length ) { Espruino.Config.SERIAL_TCPIP=args.ports .filter(function(p) { return p.name.substr(0,6) === "tcp://"; }) .map(function(p) { return p.name.trim(); } ) } if (args.showConfigs) { Espruino.Core.Config.getSections().forEach(function(section) { log(" "+section.name); log("==================================".substr(0,section.name.length+2)); log(""); if (section.description) { log(section.description); log(""); } var configItems = Espruino.Core.Config.data; for (var configName in configItems) { var configItem = configItems[configName]; if (configItem.section == section.name) { var d = configItem.name+" ("+configName+")"; log(d); log("-------------------------------------------------------------------------------".substr(0,d.length+2)); if (configItem.description) log(configItem.description); log("Type: "+JSON.stringify(configItem.type,null,2)); log("Default: --config "+configName+"="+configItem.defaultValue); log("Current: --config "+configName+"="+Espruino.Config[configName]); log(""); } } log(""); }); process.exit(1); //Espruino.Core.Config.getSection(sectionName); } if (args.board) { log("Explicit board JSON supplied: "+JSON.stringify(args.board)); var jsonLoaded = function(json) { console.log("Manual board JSON load complete"); if (json && json.info && json.info.binary_version) Espruino.Core.Env.setFirmwareVersion(json.info.binary_version); callback(); } Espruino.Config.ENV_ON_CONNECT = false; var env = Espruino.Core.Env.getData(); env.BOARD = args.board; if (args.board.indexOf(".")>=0) { var data = JSON.parse(require("fs").readFileSync(args.board).toString()); for (var key in data) env[key] = data[key]; Espruino.callProcessor("boardJSONLoaded", env, jsonLoaded); } else { // download the JSON Espruino.Plugins.BoardJSON.loadJSON(env, Espruino.Config.BOARD_JSON_URL+"/"+env.BOARD+".json", jsonLoaded); } } else callback(); } /* Write a file into a Uint8Array in the form that Espruino expects. Return the address of the next file. NOTE: On platforms with only a 64 bit write (STM32L4) this won't work */ function setStorageBufferFile(buffer, addr, filename, data) { if (!typeof data=="string") throw "Expecting string"; if (addr&3) throw "Unaligned"; var fileSize = data.length; var nextAddr = addr+16+((fileSize+3)&~3); if (nextAddr>buffer.length) throw "File too big for buffer"; // https://github.com/espruino/Espruino/blob/master/src/jsflash.h#L30 // 'size' buffer.set([fileSize&255, (fileSize>>8)&255, (fileSize>>16)&255, (fileSize>>24)&255], addr); // 'replacement' - none, since this is the only file buffer.set([0xFF,0xFF,0xFF,0xFF], addr+4); // 'filename', 8 bytes if (filename.length>8) throw "Filename "+JSON.stringify(filename)+" too big"; buffer.set([0,0,0,0,0,0,0,0], addr+8); for (var i=0;i<filename.length;i++) buffer[addr+8+i] = filename.charCodeAt(i); // Write the data in for (var i=0;i<fileSize;i++) buffer[addr+16+i] = data.charCodeAt(i); // return next addr return nextAddr; } /* Convert the given files to the format used by the Storage module, and return the Intel Hex file for it all. */ function toIntelHex(files) { var saveAddress, saveSize; try { saveAddress = Espruino.Core.Env.getData().chip.saved_code.address; saveSize = Espruino.Core.Env.getData().chip.saved_code.page_size * Espruino.Core.Env.getData().chip.saved_code.pages; } catch (e) { throw new Error("Board JSON not found or doesn't contain the relevant saved_code section"); } var buffer = new Uint8Array(saveSize); buffer.fill(0xFF); // fill with 255 for emptiness var offset = 0; for (var filename in files) { console.log(`Storage: set ${JSON.stringify(filename)} at ${offset} (${files[filename].length} bytes)`); offset = setStorageBufferFile(buffer, offset, filename, files[filename]); } if (offset>saveSize) throw new Error(`Too much code to save (${offset} vs ${saveSize} bytes)`); // Now work out intel hex function h(d) { var n = "0123456789ABCDEF"; return n[(d>>4)&15]+n[d&15]; } function ihexline(bytes) { bytes.push(1+(~bytes.reduce((a,b)=>a+b)&255)); // checksum - yay JS! return ":"+bytes.map(h).join("")+"\r\n"; } var lastHighAddr = -1; var ihex = ""; for (var idx=0;idx<saveSize;idx+=16) { var addr = saveAddress+idx; var highAddr = addr>>16; if (highAddr != lastHighAddr) { lastHighAddr = highAddr; ihex += ihexline([2,0,0,4,(highAddr>>8)&255,highAddr&255]); } var bytes = [ 16/*bytes*/, (addr>>8)&0xFF, addr&0xFF, 0]; // record type for (var j=0;j<16;j++) bytes.push(buffer[idx+j]); ihex += ihexline(bytes); } // END OF FILE marker (so don't copy this if you're trying to manually merge files!) ihex += ":00000001FF\r\n"; return ihex; } // create a job file from commandline settings function makeJobFile(config) { var job = {"espruino":{}}; // assign commandline values for (var key in args) { switch (key) { case 'job': // remove job itself, and others set internally from the results case 'espruinoPrefix': case 'espruinoPostfix': break; default: job[key] = args[key]; // otherwise just output each key: value } // write fields of Espruino.Config passed as config for (var k in config) { if (typeof config[k]!=='function') job.espruino[k] = config[k]; }; } // name job file same as code file with json ending or default and save. var jobFile = isNextValidJS(args.file) ? args.file.slice(0,args.file.lastIndexOf('.'))+'.json' : "job.json"; if (!fs.existsSync(jobFile)) { log("Creating job file "+JSON.stringify(jobFile)); fs.writeFileSync(jobFile,JSON.stringify(job,null,2),{encoding:"utf8"}); } else log("WARNING: File "+JSON.stringify(jobFile)+" already exists - not overwriting."); } //header if (!args.quiet) { var pjson = require(__dirname+'/../package.json'); console.log(pjson.version); log( "Espruino Command-line Tool "+pjson.version+"\n"+ "-----------------------------------\n"+ ""); } //Help if (args.help) { getHelp().forEach(function(l) {log(l);}); process.exit(1); } function sendCode(callback) { var code = ""; if (args.file) { code = fs.readFileSync(args.file, {encoding:"utf8"}); } if (args.expr) { if (code) { if (code[code.length-1]!="\n") code += "\n"; } else Espruino.Config.RESET_BEFORE_SEND = false; code += args.expr+"\n"; } if (code) { var env = Espruino.Core.Env.getData(); if (!env.info || !env.info.builtin_modules) { log("********************************************************************"); log("* No list of built-in modules found. If you get 'Module not found' *"); log("* messages for built-in modules you may want to connect to a board *"); log("* with '-p devicePath' or use '--board BOARDNAME' *"); log("********************************************************************"); } if (!args.outputHEX) { // if we're supposed to upload code somewhere ensure we do that properly for (var storageName in args.storageContents) { var storageContent = args.storageContents[storageName]; if (storageContent.code) { Espruino.Config.SAVE_ON_SEND = 3; // to storage file Espruino.Config.SAVE_STORAGE_FILE = storageName; // the filename Espruino.Config.LOAD_STORAGE_FILE = 2; // Load the Storage File just written to } } } Espruino.callProcessor("transformForEspruino", code, function(code) { if (args.outputHEX) { log("Writing hex output to "+args.outputHEX); var storage = {} var hadCode = false; for (var storageName in args.storageContents) { var storageContent = args.storageContents[storageName]; if (storageContent.code) { storage[storageName] = code; hadCode = true; } else { storage[storageName] = storageContent.data; } } // add code in default place if (!hadCode) storage[".bootcde"]=code; require("fs").writeFileSync(args.outputHEX, toIntelHex(storage)); } else { // if not creating a hex, we just add the code needed to upload // files to the beginning of what we upload for (var storageName in args.storageContents) { var storageContent = args.storageContents[storageName]; if (!storageContent.code) { code = Espruino.Core.Utils.getUploadFileCode(storageName, storageContent.data)+"\n" + code; } } } if (args.outputJS) { log("Writing output to "+args.outputJS); require("fs").writeFileSync(args.outputJS, code); } if (!args.nosend) Espruino.Core.CodeWriter.writeToEspruino(code, function() { if (args.sleepAfterUpload) { log("Upload Complete. Sleeping for "+args.sleepAfterUpload+"s"); setTimeout(callback, args.sleepAfterUpload*1000); } else { log("Upload Complete"); callback(); } }); else callback(); }); } else { callback(); } } /* Download a file from Espruino */ function downloadFile(callback) { Espruino.Core.Utils.downloadFile(args.fileToDownload, function(contents) { if (contents===undefined) { log("Timed out receiving file") if (!args.file && !args.updateFirmware && !args.expr) return process.exit(0); if (callback) return callback(); } //Write file to current directory require("fs").writeFileSync(args.fileToDownload, contents); log(`"${args.fileToDownload}" successfully downloaded.`); if (!args.file && !args.updateFirmware && !args.expr) return process.exit(0); if (callback) return callback(); }); } /* Connect and send file/expression/etc */ function connect(devicePath, exitCallback) { if (args.ideServer) log("WARNING: --ide specified, but no terminal. Don't specify a file/expression to upload."); if (!args.quiet && !args.nosend) log("Connecting to '"+devicePath+"'"); var currentLine = ""; var exitTimeout; // Handle received data Espruino.Core.Serial.startListening(function(data) { data = String.fromCharCode.apply(null, new Uint8Array(data)); currentLine += data; while (currentLine.indexOf("\n")>=0) { var i = currentLine.indexOf("\n"); log(args.espruinoPrefix + currentLine.substr(0,i)+args.espruinoPostfix); currentLine = currentLine.substr(i+1); } // if we're waiting to exit, make sure we wait until nothing has been printed if (exitTimeout && exitCallback) { clearTimeout(exitTimeout); exitTimeout = setTimeout(exitCallback, 500); } }); if (!args.nosend) { Espruino.Core.Serial.open(devicePath, function(status) { if (status === undefined) { console.error("Unable to connect!"); return exitCallback(); } if (!args.quiet) log("Connected"); // Do we need to update firmware? if (args.updateFirmware) { var bin = fs.readFileSync(args.updateFirmware, {encoding:"binary"}); var flashOffset = args.firmwareFlashOffset || 0; Espruino.Core.Flasher.flashBinaryToDevice(bin, flashOffset, function(err) { log(err ? "Error!" : "Success!"); exitTimeout = setTimeout(exitCallback, 500); }); } else { // Is there a file we should download? if (args.fileToDownload) { // figure out what code we need to send (if any) and download the file sendCode(function() { downloadFile(function() { exitTimeout = setTimeout(exitCallback, 500); }); }); } else { // figure out what code we need to send (if any) sendCode(function() { exitTimeout = setTimeout(exitCallback, 500); }); } } // send code over here... // ---------------------- }, function() { log("Disconnected."); exitCallback(); }); } else { sendCode(function() { exitTimeout = setTimeout(exitCallback, 500); }); } } function sendOnFileChanged() { var busy = false; var watcher = require("fs").watch(args.file, { persistent : false }, function(eventType) { if (busy) return; /* stop watching - some apps delete & recreate, so continuing to watch would break */ if (watcher) watcher.close(); watcher = undefined; busy = true; console.log(args.file+" changed, reloading"); setTimeout(function() { sendCode(function() { console.log("File sent!"); busy = false; // start watching again sendOnFileChanged(); }); }, 500); }); } /* Start a webserver for the Web IDE */ function startWebIDEServer(writeCallback) { var httpPort = args.ideServer; var server = require("http").createServer(function(req, res) { res.end(`<html> <body style="margin:0px"> <iframe id="ideframe" src="https://www.espruino.com/ide/" style="width:100%;height:100%;border:0px;"></iframe> <script src="https://www.espruino.com/ide/embed.js"></script> <script> var ws = new WebSocket("ws://" + location.host + "/ws", "serial"); var Espruino = EspruinoIDE(document.getElementById('ideframe')); Espruino.onports = function() { return [{path:'local', description:'Connected Device', type : "net"}]; }; Espruino.onready = function(data) { Espruino.connect("local");}; Espruino.onwrite = function(data) { ws.send(data); } ws.onmessage = function (event) { Espruino.received(event.data); }; ws.onclose = function (event) { Espruino.disconnect(); }; </script> </body> </html> `); }); server.listen(httpPort); log("Web IDE is now available on http://localhost:"+httpPort); /* Start the WebSocket relay - allows standard Websocket MQTT communications */ var WebSocketServer = require('websocket').server; var wsServer = new WebSocketServer({ httpServer: server, autoAcceptConnections: false }); var ideConnections = []; wsServer.on('request', function(request) { // request.reject() based on request.origin? var connection = request.accept('serial', request.origin); ideConnections.push(connection); log('Web IDE Connection accepted.'); connection.on('message', function(message) { if (message.type === 'utf8') writeCallback(message.utf8Data); }); connection.on('close', function(reasonCode, description) { log('Web IDE Connection closed.'); var conIdx = ideConnections.indexOf(connection); if (conIdx>=0) ideConnections.splice(conIdx,1); }); }); return { write : function(data) { ideConnections.forEach(function(connection) { connection.sendUTF(data) }); } }; } /* Connect and enter terminal mode */ function terminal(devicePath, exitCallback) { if (!args.quiet) log("Connecting to '"+devicePath+"'"); var hadCtrlC = false; var hadCR = false; var ideServer = undefined; process.stdin.setRawMode(true); Espruino.Core.Serial.startListening(function(data) { data = new Uint8Array(data); var dataStr = String.fromCharCode.apply(null, data); if (ideServer) ideServer.write(dataStr); process.stdout.write(dataStr); /* If Espruino responds after a Ctrl-C with anything other than a blank prompt, make sure the next Ctrl-C will exit */ for (var i=0;i<data.length;i++) { var ch = data[i]; if (ch==8) hadCR = true; else { if (hadCtrlC && (ch!=62 /*>*/ || !hadCR)) { //process.stdout.write("\nCTRLC RESET BECAUSE OF "+JSON.stringify(String.fromCharCode.apply(null, data))+" "+hadCR+" "+ch+"\n"); hadCtrlC = false; } hadCR = false; } } }); Espruino.Core.Serial.open(devicePath, function(status) { if (status === undefined) { console.error("Unable to connect!"); return exitCallback(); } if (!args.quiet) log("Connected"); process.stdin.on('data', function(chunk) { if (chunk !== null) { chunk = chunk.toString(); Espruino.Core.Serial.write(chunk); // Check for two Ctrl-C in a row (without Espruino doing anything inbetween) for (var i=0;i<chunk.length;i++) { var ch = chunk.charCodeAt(i); if (ch==3) { if (hadCtrlC) { process.stdout.write("\r\n"); exitCallback(); } else { // if we had ctrl-c, but didn't receive anything setTimeout(function() { if (hadCtrlC) process.stdout.write("\nPress Ctrl-C again to exit\n>"); }, 200); } hadCtrlC = true; } } } }); process.stdin.on('end', function() { console.log("STDIN ended. exiting..."); exitCallback(); }); if (args.ideServer) ideServer = startWebIDEServer(function(data) { Espruino.Core.Serial.write(data); }); if (args.fileToDownload) { // figure out what code we need to send (if any) and download the file sendCode(function() { downloadFile(function() { if (args.watchFile) sendOnFileChanged(); }); }); } else { // figure out what code we need to send (if any) sendCode(function() { if (args.watchFile) sendOnFileChanged(); }); } }, function() { log("\nDisconnected."); exitCallback(); }); } /* If the user's asked us to find a device by name, list all devices and search */ function getPortPath(port, callback) { if (port.type=="path") callback(port.name); else if (port.type=="name") { log("Searching for device named "+JSON.stringify(port.name)); var searchString = port.name.toLowerCase(); var timeout = 5; Espruino.Core.Serial.getPorts(function cb(ports, shouldCallAgain) { //log(JSON.stringify(ports,null,2)); var found = ports.find(function(p) { return p.description && p.description.toLowerCase().indexOf(searchString)>=0; }); if (found) { log("Found "+JSON.stringify(found.description)+" ("+JSON.stringify(found.path)+")"); callback(found.path); } else { if (timeout-- > 0 && shouldCallAgain) // try again - sometimes BLE devices take a while setTimeout(function() { Espruino.Core.Serial.getPorts(cb); }, 500); else { log("Port named "+JSON.stringify(port.name)+" not found"); process.exit(1); } } }); } else throw new Error("Unknown port type! "+JSON.stringify(port)); } function tasksComplete() { console.log("Done"); process.exit(0); } function startConnect() { if ((!args.file && !args.updateFirmware && !args.expr) || (args.file && args.watchFile)) { if (args.ports.length != 1) throw new Error("Can only have one port when using terminal mode"); getPortPath(args.ports[0], function(path) { terminal(path, tasksComplete); }); } else { //closure for stepping through each port //and connect + upload (use timeout callback [iterate] for proceeding) (function (ports, connect) { this.ports = ports; this.idx = 0; this.connect = connect; this.iterate = function() { if (idx>=ports.length) tasksComplete(); else getPortPath(ports[idx++], function(path) { connect(path,iterate); }); }; iterate(); })(args.ports, connect); } } function main() { setupConfig(Espruino, function() { if (args.job==="") makeJobFile(Espruino.Config); if (args.ports.length == 0 && (args.outputJS || args.outputHEX)) { console.log("No port supplied, but output file listed - not connecting"); args.nosend = true; sendCode(tasksComplete); } else if (args.ports.length == 0 || args.showDevices) { console.log("Searching for serial ports..."); var timeout = 5; var allPorts = []; var outputHeader = false; Espruino.Core.Serial.getPorts(function cb(ports, shouldCallAgain) { var newPorts = ports.filter(port=> !allPorts.find(p=>p.path==port.path)); allPorts = allPorts.concat(newPorts); // if we're explictly asked for ports, output them // else just write it only if verbose if (newPorts.length) { if (args.showDevices) { if (!outputHeader) { log("PORTS:"); outputHeader = true; } newPorts.forEach(p=> log(` ${p.path} (${p.description})${p.rssi?` RSSI ${p.rssi}`:""}`)); } else { newPorts.forEach(p=> console.log("NEW PORT: "+p.path + " ("+p.description+")")); } } if (!args.showDevices && allPorts.length) { if (!args.nosend) log("Using first port, "+JSON.stringify(allPorts[0])); args.ports = [{type:"path",name:allPorts[0].path}]; startConnect(); } else if (timeout-- > 0 && shouldCallAgain) { // try again - sometimes BLE devices take a while setTimeout(function() { Espruino.Core.Serial.getPorts(cb); }, 500); } else { if (allPorts.length==0) { console.error("Error: No Ports Found"); process.exit(1); } else { process.exit(0); } } }); } else startConnect(); }); } // Start up require('../index.js').init(main);