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