function NodeMcuConnector(devicename, baudrate){ // new terminal line sequence "\n\r>" this.device = new _virtualTerminal([13,10,62]); this.errorHandler = null; this.isConnected = false; this.name = devicename; this.baudrate = baudrate; this.isHexWriteHelperUploaded = false; // handle low level errors this.device.onError(function(err){ // proxy if (this.errorHandler){ this.errorHandler.apply(this, [err]); // default: std logger }else{ _logger.error(err); } }.bind(this)); }
n/a
function ScriptableSerialTerminal(delimiterSequence){ this.device = null; this.inputbuffer = []; this.waitingForInput = null; this.errorHandler = null; this.delimiterSequence = delimiterSequence; this.encoding = 'utf8'; }
n/a
function SerialTerminal(){ this.device = null; this.errorHandler = null; this.connectHandler = null; this.encoding = 'utf8'; }
n/a
devices = function (showAll, jsonOutput){ // create new connector var connector = new _nodeMcuConnector(_options.device, _options.baudrate); // retrieve the device list (not bound to an opened connection) connector.deviceInfo(showAll, function(err, devices){ if (err){ _mculogger.error('Cannot retrieve serial device list - ' + err); }else{ if (jsonOutput){ writeOutput(JSON.stringify(devices)); }else{ // just show complete message if (devices.length == 0){ _mculogger.error('No Connected Devices found | Total: ' + devices.length); }else{ _mculogger.log('Connected Devices | Total: ' + devices.length); // print fileinfo devices.forEach(function(device){ writeOutput('\t |- ' + device.comName + ' (' + device.manufacturer + ', ' + device.pnpId + ')'); }); } } } }); }
...
// json output mode
.option('--json', 'Display output JSON encoded', false)
.action(function(opt){
var options = cliPrepare(opt);
_nodemcutool.devices(options.all, options.json);
});
_cli
.command('reset')
.description('Execute a Hard-Reset of the Module using DTR/RTS reset circuit')
// softreset mode
...
download = function (remoteFile){ // strip path var localFilename = _path.basename(remoteFile); // local file with same name already available ? if (_fs.existsSync(remoteFile)){ // change filename localFilename += '.' + (new Date().getTime()); _logger.log('Local file "' + remoteFile + '" already exist - new file renamed to "' + localFilename + '"'); } // try to establish a connection to the module getConnection(function(connector){ _logger.log('Downloading "' + remoteFile + '" ...'); connector.download(remoteFile, // onComplete function(err, filedata){ // finished! connector.disconnect(); if (err){ _logger.error('Data Transfer FAILED!'); }else{ _logger.log('Data Transfer complete!'); // store local file _fs.writeFileSync(localFilename, filedata); _logger.log('File "' + localFilename + '" created'); } } ); }); }
...
_cli
.command('download <file>')
.description('Download files from NodeMCU (ESP8266) target')
.action(function(remoteFilename, opt){
var options = cliPrepare(opt);
_nodemcutool.download(remoteFilename);
});
_cli
.command('remove <file>')
.description('Removes a file from NodeMCU filesystem')
.action(function(filename, opt){
var options = cliPrepare(opt);
...
fsinfo = function (format){ // try to establish a connection to the module getConnection(function(connector){ connector.fsinfo(function(err, meta, files){ // finished! connector.disconnect(); if (err){ _logger.error(err); }else{ // json output - third party applications if (format == 'json') { writeOutput(JSON.stringify({ files: files, meta: meta })); // raw format - suitable for use in bash scripts }else if (format == 'raw'){ // print fileinfo files.forEach(function(file){ writeOutput(file.name); }); }else{ _mculogger.log('Free Disk Space: ' + meta.remaining + ' KB | Total: ' + meta.total + ' KB | ' + files.length + ' Files'); // files found ? if (files.length==0){ _mculogger.log('No Files found - have you created the file-system?'); }else{ _mculogger.log('Files stored into Flash (SPIFFS)'); // print fileinfo files.forEach(function(file){ writeOutput('\t |- ' + file.name + ' (' + file.size + ' Bytes)'); }); } } } }); }); }
...
}
// raw format (text)
if (options.raw){
format = 'raw';
}
_nodemcutool.fsinfo(format);
});
_cli
.command('run <file>')
.description('Executes an existing .lua or .lc file on NodeMCU')
.action(function(filename, opt){
var options = cliPrepare(opt);
...
mkfs = function (){ // try to establish a connection to the module getConnection(function(connector){ _mculogger.log('Formatting the file system...this will take around ~30s'); connector.format(function(err, response){ // finished! connector.disconnect(); if (err){ _mculogger.error('Formatting failed - ' + err); }else{ // just show complete message _mculogger.log('File System created | ' + response); } }); }); }
...
.action(function(opt){
var options = cliPrepare(opt);
// no prompt!
if (opt.noninteractive){
// format
_nodemcutool.mkfs();
return;
}
// user confirmation required!
_prompt.start();
_prompt.message = '';
...
onOutput = function (handler) { outputHandler = handler; }
...
// errors
}else{
console.log(_colors.red('[' + facility.trim() + ']'), args.join(' '));
}
});
// general content
_nodemcutool.onOutput(function(message){
console.log(message);
});
function cliPrepare(options){
// default
options = options || {};
...
remove = function (filename){ // try to establish a connection to the module getConnection(function(connector){ connector.removeFile(filename, function(err){ // finished! connector.disconnect(); if (err){ _mculogger.error(err); }else{ // just show complete message (no feedback from nodemcu) _mculogger.log('File "' + filename + '" removed!'); } }); }); }
...
_cli
.command('remove <file>')
.description('Removes a file from NodeMCU filesystem')
.action(function(filename, opt){
var options = cliPrepare(opt);
_nodemcutool.remove(filename);
});
_cli
.command('mkfs')
.description('Format the SPIFFS filesystem - ALL FILES ARE REMOVED')
// force fs creation without prompt
...
reset = function (){ // create new connector var connector = new _nodeMcuConnector(_options.device, _options.baudrate); // open connection without connection check connector.connect(function(error, response){ if (error) { _logger.error('Unable to establish connection - ' + error); }else{ // status message _logger.log('Connected'); // execute reset connector.reset(function(err){ // finished! connector.disconnect(); if (err){ _mculogger.error(err); }else{ _mculogger.log('Hard-Reset executed (100ms)'); } }) } }, false); }
...
// software reset
if (options.softreset){
_nodemcutool.softreset();
// hard-reset nRST
}else{
_nodemcutool.reset();
}
});
_cli
.command('*')
.action(function(c){
...
run = function (filename){ // try to establish a connection to the module getConnection(function(connector){ connector.run(filename, function(err, output){ // finished! connector.disconnect(); if (err){ _mculogger.error(err); }else{ // show command response _mculogger.log('Running "' + filename + '"'); _mculogger.log('>----------------------------->'); writeOutput(output); _mculogger.log('>----------------------------->'); } }); }); }
...
_cli
.command('run <file>')
.description('Executes an existing .lua or .lc file on NodeMCU')
.action(function(filename, opt){
var options = cliPrepare(opt);
_nodemcutool.run(filename);
});
_cli
.command('upload [files...]')
.description('Upload Files to NodeMCU (ESP8266) target')
// file cleanup
...
setOptions = function (opt){ // merge with default options Object.keys(_options).forEach(function(key){ _options[key] = opt[key] || _options[key]; }); }
...
defaultConfig.keeppath = (d.keeppath && d.keeppath === true);
_logger.log('Project based configuration loaded');
}
}catch (err){
}
// set port/baud options
_nodemcutool.setOptions({
device: defaultConfig.port,
baudrate: defaultConfig.baudrate,
connectionDelay: defaultConfig.connectionDelay
});
return defaultConfig;
}
...
softreset = function (){ // try to establish a connection to the module getConnection(function(connector){ connector.softreset(function(err){ // finished! connector.disconnect(); if (err){ _mculogger.error(err); }else{ _mculogger.log('Soft-Reset executed (node.restart())'); } }); }); }
...
.action(function(opt){
var options = cliPrepare(opt);
// software reset
if (options.softreset){
_nodemcutool.softreset();
// hard-reset nRST
}else{
_nodemcutool.reset();
}
});
...
terminal = function (initialCommand){ // create new connector var terminal = new _serialTerminal(); _terminallogger.log('Starting Terminal Mode - press ctrl+c to exit'); // run initial command before starting terminal session ? if (initialCommand){ terminal.onConnect(function(device){ device.write(initialCommand + '\n'); }); } // start terminal.passthrough(_options.device, _options.baudrate, function(err){ if (err){ _terminallogger.error(err); }else{ _terminallogger.log('Connection closed'); } }); }
...
// run a initial command on startup ?
var initialCommand = null;
if (options.run){
initialCommand = _luaCommandBuilder.prepare('run', [options.run]);
}
// start terminal session
_nodemcutool.terminal(initialCommand);
});
_cli
.command('init')
.description('Initialize a project-based Configuration (file) within current directory')
.action(function(opt){
var options = cliPrepare(opt);
...
upload = function (localFiles, options, onProgess){ // the index of the current uploaded file var fileUploadIndex = 0; var uploadFile = function(connector, localFile, remoteFilename, onComplete){ // increment upload index fileUploadIndex++; // get file stats try{ var fileInfo = _fs.statSync(localFile); // check if file is directory if (fileInfo.isDirectory()) { _mculogger.error('Path "' + localFile + '" is a directory.'); onComplete(); return; } // local file available } catch (err){ _logger.error('Local file not found "' + localFile + '" skipping...'); onComplete(); return; } // display filename _logger.log('Uploading "' + localFile + '" >> "' + remoteFilename + '"...'); // trigger a progress update onProgess(0, fileInfo.size, fileUploadIndex); // normalize the remote filename (strip relative parts) remoteFilename = remoteFilename.replace(/\.\.\//g, '').replace(/\.\./g, '').replace(/^\.\//, ''); // delete old file (may existent) connector.removeFile(remoteFilename, function(err){ // handle error if (err){ connector.disconnect(); _logger.error(err); return; } // start the file transfer connector.upload(localFile, remoteFilename, options, // onComplete function(err){ // handle error if (err){ connector.disconnect(); _logger.error(err); return; } // compile flag set ? and is a lua file ? if (options.compile && _path.extname(localFile).toLowerCase() == '.lua'){ _mculogger.log(' |- compiling lua file..'); connector.compile(remoteFilename, function(error){ // success ? empty line will return (null) if (error){ connector.disconnect(); _mculogger.error('Error: ' + error); }else{ _mculogger.log(' |- success'); // drop original lua file connector.removeFile(remoteFilename, function(error){ if (error){ connector.disconnect(); _mculogger.error('Error: ' + error); return; } _mculogger.log(' |- original Lua file removed'); onComplete(); }); } }); }else{ // directly call complete handler onComplete(); } }, // on progress handler function(current, total){ // proxy and append file-number onProgess(current, total, fileUploadIndex); } ); }); }; // try to establish a connection to the module getConnection(function(connector){ // single file upload ? if (localFiles.length == 1){ // extract first element var localFile = localFiles[0]; // filename defaults to original filename minus path. // this behaviour can be overridden by --keeppath and --remotename options var remoteFile = options.remotename ? options.remotename : (options.keeppath ? localFile : _path.basename(localFile)); // start single file upload ...
...
_logger.error('No files provided for upload (empty file-list)');
return;
}
// handle multiple uploads
var currentFileNumber = 0;
_nodemcutool.upload(localFiles, options, function(current, total, fileNumber){
// new file ?
if (currentFileNumber != fileNumber){
bar.stop();
currentFileNumber = fileNumber;
bar.start(total, 1);
}else{
...
function NodeMcuConnector(devicename, baudrate){ // new terminal line sequence "\n\r>" this.device = new _virtualTerminal([13,10,62]); this.errorHandler = null; this.isConnected = false; this.name = devicename; this.baudrate = baudrate; this.isHexWriteHelperUploaded = false; // handle low level errors this.device.onError(function(err){ // proxy if (this.errorHandler){ this.errorHandler.apply(this, [err]); // default: std logger }else{ _logger.error(err); } }.bind(this)); }
n/a
checkConnection = function (cb){ // 1.5s connection timeout var watchdog = setTimeout(function(){ // stop watchdog = null; // throw error cb('Timeout, no response detected - is NodeMCU online and the Lua interpreter ready ?'); }, 1500); // proxy function to stop timer var ready = function(err){ // watchdog active ? if (watchdog !== null){ // stop watchdog clearTimeout(watchdog); // run callback cb(err); } }; // send a simple print command to the lua engine this.device.executeCommand(_luaCommandBuilder.command.echo, function(err, echo, output){ if (err){ ready(err); }else{ // validate command echo and command output if (output == 'echo1337' && echo == 'print("echo1337")') { ready(null); } else { ready('No response detected - is NodeMCU online and the Lua interpreter ready ?') } } }); }
n/a
compile = function (remoteName, cb){ // check connect flag if (!this.isConnected){ cb('Cannot compile remote file - device offline', null); return; } // run the lua compiler/interpreter to cache the file as bytecode this.device.executeCommand(_luaCommandBuilder.prepare('compile', [remoteName]), function(err, echo, response){ if (err){ cb('IO Error - ' + err, null); }else{ cb(null, response); } }.bind(this)); }
...
// file system info
fsInfo: 'print(file.fsinfo())',
// format the file system
fsFormat: 'file.format()',
// compile a remote file
compile: 'node.compile("?")',
// run a file
run: 'dofile("?")',
// soft-reset
reset: 'node.restart()',
...
connect = function (cb, applyConnectionCheck, connectDelay){ // try to open the device this.device.open(this.name, this.baudrate, function(error){ if (error){ cb('Cannot open port "' + this.name + '"', null); }else{ // skip connection check ? if (applyConnectionCheck === false){ // set connect flag this.isConnected = true; // run callback cb(null, 'Skipping Connection Check..'); return; } var checkConnection = function(){ this.checkConnection(function(error){ if (error){ cb(error, null); }else{ this.isConnected = true; // print data this.fetchDeviceInfo(function(error, data){ if (error){ cb(error, null); }else{ // show nodemcu device info cb(null, 'Version: ' + data.version + ' | ChipID: 0x' + data.chipID + ' | FlashID: 0x' + data.flashID ); } }); } }.bind(this)); }.bind(this); // delay the connection process ? may fix issues related to rebooting modules if (connectDelay && connectDelay > 1){ setTimeout(function(){ // send dummy sequence this.device.write('\n\n\n' + _luaCommandBuilder.command.echo + '\n\n\n' , function(){ // wait 1/3 to get the dummy sequence processed setTimeout(function(){ // send dummy sequence this.device.write('\n\n\n' + _luaCommandBuilder.command.echo + '\n\n\n' , function(){ // wait 1/3 to get the dummy sequence processed setTimeout(function(){ // purge received data this.device.purge(function(){ // connection startup checkConnection(); }.bind(this)); }.bind(this), connectDelay); }.bind(this)); }.bind(this), connectDelay); }.bind(this)); // 2/3 timeout }.bind(this), connectDelay); }else{ checkConnection(); } } }.bind(this)); }
n/a
deviceInfo = function (showAll, cb){ // get all available serial ports _serialport.list(function (err, ports){ // error occurred ? if (err){ cb(err); return; } // default condition ports = ports || []; // just pass-through if (showAll){ cb(null, ports); // filter by vendorIDs // NodeMCU v1.0 - CH341 Adapter | 0x1a86 QinHeng Electronics // NodeMCU v1.1 - CP2102 Adapter | 0x10c4 Cygnal Integrated Products, Inc }else{ cb(null, ports.filter(function(item){ return (item.vendorId == '0x1a86' || item.vendorId == '0x10c4'); })); } }); }
n/a
disconnect = function (){ this.isConnected = false; this.device.close(); }
n/a
download = function (remoteName, cb){ // check connect flag if (!this.isConnected){ cb('Cannot download file - device offline', null); return; } // transfer helper function to encode hex data this.device.executeCommand(_luaCommandBuilder.command.hexReadHelper, function(err, echo, response) { // successful opened ? if (err) { cb('Cannot transfer hex.encode helper function - ' + err); return; } // open remote file for write this.device.executeCommand(_luaCommandBuilder.prepare('fileOpen', [remoteName, 'r']), function(err, echo, response){ // successful opened ? if (err || response == 'nil'){ cb('Cannot open remote file "' + remoteName + '" for read - ' + err); return; } // write first element to file this.device.executeCommand('__hexread()', function(err, echo, filecontent){ if (err){ cb('Cannot read remote file content - ' + err, null); return; } // decode file content var data = new Buffer(filecontent, 'hex'); // send file close command this.device.executeCommand(_luaCommandBuilder.command.fileClose, function(err, echo, response){ if (err){ cb('Cannot close remote file - ' + err, null); }else{ cb(null, data); } }); }.bind(this)); }.bind(this)); }.bind(this)); }
...
_cli
.command('download <file>')
.description('Download files from NodeMCU (ESP8266) target')
.action(function(remoteFilename, opt){
var options = cliPrepare(opt);
_nodemcutool.download(remoteFilename);
});
_cli
.command('remove <file>')
.description('Removes a file from NodeMCU filesystem')
.action(function(filename, opt){
var options = cliPrepare(opt);
...
executeCommand = function (cmd, cb){ // check connect flag if (!this.isConnected){ cb('Cannot execute remote file - device offline', null); return; } // run the lua interpreter this.device.executeCommand(cmd, cb); }
n/a
fetchDeviceInfo = function (cb){ // run the node.info() command this.device.executeCommand(_luaCommandBuilder.command.nodeInfo, function(err, echo, data){ if (err){ cb(err); }else { // replace whitespaces with single delimiter var p = data.replace(/\s+/gi, '-').split('-'); if (p.length != 8) { cb('Invalid node.info() Response: ' + data, null); } else { // process data cb(null, { version: p[0] + '.' + p[1] + '.' + p[2], chipID: parseInt(p[3]).toString(16), flashID: parseInt(p[4]).toString(16), flashsize: p[5] + 'kB', flashmode: p[6], flashspeed: parseInt(p[7]) / 1000000 + 'MHz' }); } } }); }
n/a
format = function (cb){ // check connect flag if (!this.isConnected){ cb('Cannot format file system - device offline', null); return; } // get file system info (size) this.device.executeCommand(_luaCommandBuilder.command.fsFormat, function(err, echo, response){ if (err){ cb('IO Error - ' + err, null); }else{ cb(null, response); } }.bind(this)); }
...
// info command (flash id)
nodeInfo: 'print(node.info());',
// file system info
fsInfo: 'print(file.fsinfo())',
// format the file system
fsFormat: 'file.format()',
// compile a remote file
compile: 'node.compile("?")',
// run a file
run: 'dofile("?")',
...
fsinfo = function (cb){ // check connect flag if (!this.isConnected){ cb('Cannot fetch file system info - device offline', null); return; } // get file system info (size) this.device.executeCommand(_luaCommandBuilder.command.fsInfo, function(err, echo, response){ if (err){ cb('Cannot fetch file system metadata - ' + err, null); return; } // extract size (remaining, used, total) response = response.replace(/\s+/gi, '-').split('-'); var toKB = function(s){ return parseInt((parseInt(s)/1024)); }; var meta = { remaining: toKB(response[0]), used: toKB(response[1]), total: toKB(response[2]) }; // print a full file-list including size this.device.executeCommand(_luaCommandBuilder.command.listFiles, function(err, echo, response){ if (err){ cb('Cannot obtain file-list - ' + err, null); return; } // file-list to return var files = []; // files available (list not empty) ? if (response.length > 0){ // split the file-list by ";" var entries = response.trim().split(';'); // process each entry entries.forEach(function(entry){ // entry format: <name>:<size> var matches = /^(.*):(\d+)$/gi.exec(entry); // valid format ? if (matches){ // append file entry to list files.push({ name: matches[1], size: parseInt(matches[2]) }); } }); } // run callback cb(null, meta, files); }); }.bind(this)); }
...
}
// raw format (text)
if (options.raw){
format = 'raw';
}
_nodemcutool.fsinfo(format);
});
_cli
.command('run <file>')
.description('Executes an existing .lua or .lc file on NodeMCU')
.action(function(filename, opt){
var options = cliPrepare(opt);
...
onError = function (cb){ this.errorHandler = cb; }
n/a
removeFile = function (remoteName, cb){ // check connect flag if (!this.isConnected){ cb('Cannot remove remote file - device offline', null); return; } // get file system info (size) this.device.executeCommand(_luaCommandBuilder.prepare('fileRemove', [remoteName]), function(err, echo, response){ if (err){ cb('IO Error - ' + err, null); }else{ cb(null, response); } }.bind(this)); }
n/a
reset = function (cb){ // check connect flag if (!this.isConnected){ cb('Cannot reset module - device offline', null); return; } var d = this.device; // pull down RST pin using the reset circuit d.flowcontrol({ dtr: false, rts: true }, function(err){ if (err){ cb(err); // restore previous state after 100ms }else{ setTimeout(function(){ d.flowcontrol({ dtr: false, rts: false }, cb); }, 100); } }); }
...
// software reset
if (options.softreset){
_nodemcutool.softreset();
// hard-reset nRST
}else{
_nodemcutool.reset();
}
});
_cli
.command('*')
.action(function(c){
...
run = function (remoteName, cb){ // check connect flag if (!this.isConnected){ cb('Cannot execute remote file - device offline', null); return; } // run the lua compiler/interpreter to cache the file as bytecode this.device.executeCommand(_luaCommandBuilder.prepare('run', [remoteName]), function(err, echo, response){ if (err){ cb('IO Error - ' + err, null); }else{ cb(null, response); } }.bind(this)); }
...
_cli
.command('run <file>')
.description('Executes an existing .lua or .lc file on NodeMCU')
.action(function(filename, opt){
var options = cliPrepare(opt);
_nodemcutool.run(filename);
});
_cli
.command('upload [files...]')
.description('Upload Files to NodeMCU (ESP8266) target')
// file cleanup
...
softreset = function (cb){ // check connect flag if (!this.isConnected){ cb('Cannot reset module - device offline', null); return; } // transfer helper function to encode hex data this.device.executeCommand(_luaCommandBuilder.command.reset, function(err, echo, response) { // successful opened ? if (err) { cb('Cannot execute soft-reset command - ' + err); return; } cb(null); }); }
...
.action(function(opt){
var options = cliPrepare(opt);
// software reset
if (options.softreset){
_nodemcutool.softreset();
// hard-reset nRST
}else{
_nodemcutool.reset();
}
});
...
upload = function (localName, remoteName, options, completeCb, progressCb){ // check connect flag if (!this.isConnected){ completeCb('Cannot upload file - device offline', null); return; } // get file content var rawContent = _fs.readFileSync(localName); // remove lua comments and empty lines ? if (options.optimize && _path.extname(localName).toLowerCase() == '.lua'){ // apply optimizations rawContent = _luaOptimizer.optimize(rawContent); } // convert buffer to hex var content = rawContent.toString('hex'); // get absolute filesize var absoluteFilesize = rawContent.length; // split file content into chunks var chunks = content.match(/.{1,232}/g); // wrapper to start the transfer var startTransfer = function(){ // open remote file for write this.device.executeCommand(_luaCommandBuilder.prepare('fileOpen', [remoteName, 'w+']), function(err, echo, response){ // successful opened ? if (err || response == 'nil'){ completeCb('Cannot open remote file "' + remoteName + '" for write - ' + err); return; } var currentUploadSize = 0; // initial progress update progressCb.apply(progressCb, [0, absoluteFilesize]); var writeChunk = function(){ if (chunks.length > 0){ // get first element var l = chunks.shift(); // increment size counter currentUploadSize += l.length/2 ; // write first element to file this.device.executeCommand('__hexwrite("' + l + '")', function(err, echo, response){ if (err){ completeCb('Cannot write chunk to remote file - ' + err, null); return; } // run progress callback progressCb.apply(progressCb, [currentUploadSize, absoluteFilesize]); // write next line writeChunk(); }); }else{ // send file close command this.device.executeCommand(_luaCommandBuilder.command.fileCloseFlush, function(err, echo, response){ if (err){ completeCb('Cannot flush/close remote file - ' + err, null); }else{ completeCb(null, absoluteFilesize); } }); } }.bind(this); // start transfer writeChunk(); }.bind(this)); }.bind(this); // hex write helper already uploaded within current session ? if (this.isHexWriteHelperUploaded){ // start transfer directly startTransfer(); // otherwise upload helper }else{ // transfer helper function to decode hex data this.device.executeCommand(_luaCommandBuilder.command.hexWriteHelper, function(err, echo, response) { // successful opened ? if (err) { completeCb('Cannot transfer hex.decode helper function - ' + err); return; } // set flag this.isHexWriteHelperUploaded = true; // start file transfer on upload complete startTransfer(); }); } }
...
_logger.error('No files provided for upload (empty file-list)');
return;
}
// handle multiple uploads
var currentFileNumber = 0;
_nodemcutool.upload(localFiles, options, function(current, total, fileNumber){
// new file ?
if (currentFileNumber != fileNumber){
bar.stop();
currentFileNumber = fileNumber;
bar.start(total, 1);
}else{
...
prepare = function (commandName, args){ // get command by name var command = lua_commands[commandName] || null; // valid command name provided ? if (command == null){ return null; } // replace all placeholders with given args args.forEach(function(arg){ // simple escaping quotes arg = arg.replace(/[^\\]"/g, '\"'); // apply arg command = command.replace(/\?/, arg); }); return command; }
...
.option('--run <filename>', 'Running a file on NodeMCU before starting the terminal session',
false)
.action(function(opt){
var options = cliPrepare(opt);
// run a initial command on startup ?
var initialCommand = null;
if (options.run){
initialCommand = _luaCommandBuilder.prepare('run', [options.run
]);
}
// start terminal session
_nodemcutool.terminal(initialCommand);
});
_cli
...
function optimizeLuaContent(rawContent){ // apply optimizations var t = rawContent.toString('utf-8') .replace(/--.*$/gim, '') .replace(/(\r\n|\n\r|\n|\r)+/g, '$1') .replace(/^\s+/gm, '') .trim(); // re-convert to buffer return new Buffer(t, 'utf-8'); }
n/a
function ScriptableSerialTerminal(delimiterSequence){ this.device = null; this.inputbuffer = []; this.waitingForInput = null; this.errorHandler = null; this.delimiterSequence = delimiterSequence; this.encoding = 'utf8'; }
n/a
close = function (){ if (this.device.isOpen()){ this.device.close(); } }
...
// list files on SPIFFS
listFiles: 'local l = file.list();for k,v in pairs(l) do uart.write(0,k..":"..v..";") end print("
x22;)',
// file open
fileOpen: 'print(file.open("?", "?"))',
// close a opened file
fileClose: 'file.close()',
// remove file
fileRemove: 'file.remove("?")',
// file close & flush
fileCloseFlush: 'file.flush() file.close()',
...
executeCommand = function (command, cb){ // send command this.write(command + '\n', function(error){ // write successful ? if (error) { cb('Cannot run command: ' + error, null); }else{ // get echo this.getNextResponse(function(data){ cb(null, data.echo, data.response); }.bind(this)); } }.bind(this)); }
n/a
flowcontrol = function (options, cb){ this.device.set(options, cb); }
n/a
getNextResponse = function (cb){ if (this.inputbuffer.length > 0){ cb(this.inputbuffer.shift()); }else{ // add as waiting instance (no concurrent!) this.waitingForInput = cb; } }
...
// send command
this.write(command + '\n', function(error){
// write successful ?
if (error) {
cb('Cannot run command: ' + error, null);
}else{
// get echo
this.getNextResponse(function(data){
cb(null, data.echo, data.response);
}.bind(this));
}
}.bind(this));
};
// remove unread input
...
onError = function (cb){ this.errorHandler = cb; }
n/a
open = function (devicename, baudrate, cb){ // try to open the serial port this.device = new _serialport(devicename, { baudrate: parseInt(baudrate), parser: _serialport.parsers.byteDelimiter(this.delimiterSequence), autoOpen: false }); // handle errors this.device.on('error', function(error){ // proxy if (this.errorHandler){ this.errorHandler.apply(this, [error]); }else{ _logger.error(error); } // device opened ? if (this.device.isOpen()){ this.device.close(); } }.bind(this)); // listen on incoming data this.device.on('data', function(input){ // strip delimiter sequence from array input.splice(input.length-this.delimiterSequence.length, this.delimiterSequence.length); // convert byte array UTF8 to string; input = (new Buffer(input)).toString(this.encoding); // response data object - default no response data var data = { echo: input, response: null }; // response found ? split echo and response var splitIndex = input.indexOf('\n'); if (splitIndex > 0){ data.echo = input.substr(0, splitIndex).trim(); data.response = input.substr(splitIndex + 1).trim(); } // process waiting for input ? if (this.waitingForInput){ var t = this.waitingForInput; this.waitingForInput = null; t.apply(t, [data]) }else{ this.inputbuffer.push(data); } }.bind(this)); // open connection this.device.open(cb); }
...
String: Lorem ipsum dolor sit amet, consetetur sadipscing elitr
```
Behind the Scene
----------------
Many beginners may ask how the tool is working because there is no binary interface documented like FTP to access files.
The answer is quite simple: **NodeMCU-Tool** implements a serial terminal connection to the Module and runs some command line based
lua commands like file.open(), file.write() to access the filesystem. That's it
!
Since Version 1.2 it's also possible to transfer **binary** files to your device. NodeMCU-Tool uses a hexadecimal encode/decoding
to transfer the files binary save!
The required encoding (file downloads) / decoding (file uploads) functions are automatically uploaded on each operation.
### Systems Architecture ###
The Tool is separated into the following components (ordered by its invocation)
...
purge = function (cb){ // flush input buffer this.device.flush(function(error){ // flush readline buffer this.inputbuffer = []; // run callback if (cb){ cb.apply(this, [error]); } }.bind(this)); }
n/a
write = function (data, cb){ this.device.write(data, function(error){ if (error){ cb(error, null); }else{ this.device.drain(cb); } }.bind(this)); }
...
String: Lorem ipsum dolor sit amet, consetetur sadipscing elitr
```
Behind the Scene
----------------
Many beginners may ask how the tool is working because there is no binary interface documented like FTP to access files.
The answer is quite simple: **NodeMCU-Tool** implements a serial terminal connection to the Module and runs some command line based
lua commands like file.open(), file.write() to access the filesystem. That's it
!
Since Version 1.2 it's also possible to transfer **binary** files to your device. NodeMCU-Tool uses a hexadecimal encode/decoding
to transfer the files binary save!
The required encoding (file downloads) / decoding (file uploads) functions are automatically uploaded on each operation.
### Systems Architecture ###
The Tool is separated into the following components (ordered by its invocation)
...
function SerialTerminal(){ this.device = null; this.errorHandler = null; this.connectHandler = null; this.encoding = 'utf8'; }
n/a
close = function (){ if (this.device.isOpen()){ this.device.close(); } }
...
// list files on SPIFFS
listFiles: 'local l = file.list();for k,v in pairs(l) do uart.write(0,k..":"..v..";") end print("
x22;)',
// file open
fileOpen: 'print(file.open("?", "?"))',
// close a opened file
fileClose: 'file.close()',
// remove file
fileRemove: 'file.remove("?")',
// file close & flush
fileCloseFlush: 'file.flush() file.close()',
...
getDevice = function (){ return this.device; }
n/a
onConnect = function (cb){ this.connectHandler = cb; }
n/a
passthrough = function (devicename, baudrate, cb){ // try to open the serial port this.device = new _serialport(devicename, { baudrate: parseInt(baudrate), parser: _serialport.parsers.byteLength(1), autoOpen: false }); // handle errors this.device.on('error', function(err){ // proxy _logger.error(err); // device opened ? if (this.device.isOpen()){ this.device.close(); } }.bind(this)); // listen on incoming data this.device.on('data', function(input){ process.stdout.write(input); }.bind(this)); // open connection this.device.open(function(error){ if (error){ cb(error); }else{ // prepare if (process.stdin.isTTY){ process.stdin.setRawMode(true); } process.stdin.setEncoding('utf8'); // pass-through process.stdin.on('data', function(data){ // ctrl-c if (data == '\u0003'){ this.device.close(); cb(null); process.exit(); } this.device.write(data); }.bind(this)); // run connect handler ? if (this.connectHandler){ this.connectHandler.apply(this.connectHandler, [this.device]); } } }.bind(this)); }
n/a