├── .gitignore ├── README.md ├── bin └── cli.js ├── package.json └── src ├── DeviceManager.js └── SerialComms.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bin/port.txt 3 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # An ESP8266 command line interface. 2 | Administer the file system and more on an ESP8266 that is flashed with [NodeMcu firmware](https://github.com/nodemcu/nodemcu-firmware). 3 | 4 | ## Install 5 | ``` 6 | $ npm install esp8266 -g 7 | ``` 8 | 9 | ## Usage 10 | ``` 11 | $ esp command [subcommand] [data] 12 | ``` 13 | 14 | ## Commands 15 | #### port set 16 | Sets the name of the serial port to use in future commands. 17 | ``` 18 | $ esp port set /dev/tty.usbserial-A603UC7E 19 | ``` 20 | 21 | #### port get 22 | Displays the current port that is used. 23 | ``` 24 | $ esp port get 25 | Port: /dev/tty.usbserial-A603UC7E 26 | ``` 27 | 28 | #### file list 29 | Lists the sizes and names of all files on the module. 30 | ``` 31 | $ esp file list 32 | 1093 bytes init.lua 33 | 1321 bytes test.lua 34 | ``` 35 | 36 | #### file write <local_filename> [<remote_filename>] 37 | Writes a file from the local file system to the module. If a second filename is given, the local file will be renamed to this value on the device, else it will keep its local name. 38 | ``` 39 | $ esp file write ./webserver.lua init.lua 40 | ``` 41 | 42 | #### file push <local_filename> [<remote_filename>] 43 | Alternative to `esp file write` that compress the file if they are of any of the following types: Lua, HTML, JavaScript, CSS. 44 | ``` 45 | $ esp file push ./webserver.lua init.lua 46 | ``` 47 | 48 | #### file read <remote_filename> 49 | Displays the content of a file from the module. 50 | ``` 51 | $ esp file read hello-world.lua 52 | print 'Hello, world' 53 | ``` 54 | 55 | #### file execute <remote_filename> 56 | Executes the content of a Lua file on the module, returns the output. 57 | ``` 58 | $ esp file execute hello-world.lua 59 | Hello, world 60 | ``` 61 | 62 | #### file remove <remote_filename> 63 | Removes a file from the module. 64 | ``` 65 | $ esp file remove test.lua 66 | ``` 67 | 68 | #### restart 69 | Restarts the module. 70 | ``` 71 | $ esp restart 72 | ``` 73 | 74 | #### run <lua> 75 | Runs Lua code on the module, returns the output. 76 | ``` 77 | $ esp run "print 'Mechanisms, not policy.'" 78 | Mechanisms, not policy. 79 | ``` 80 | 81 | #### monitor 82 | Displays the data received from the serial port. 83 | ``` 84 | $ esp monitor 85 | Displaying output from port /dev/cu.wchusbserial1410. 86 | Press ^C to stop. 87 | ``` 88 | 89 | ## License 90 | MIT 91 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'), 4 | SerialComms = require('../src/SerialComms'), 5 | DeviceManager = require('../src/DeviceManager'), 6 | 7 | PORT_FILENAME = __dirname + '/port.txt'; 8 | 9 | 10 | 11 | 12 | var config = { 13 | 14 | 15 | port: { 16 | 17 | set: function (port) { 18 | fs.writeFileSync(PORT_FILENAME, port); 19 | }, 20 | 21 | 22 | get: function () { 23 | console.log ('Port:', port || '[not set]'); 24 | } 25 | 26 | }, 27 | 28 | 29 | file: { 30 | 31 | list: function () { 32 | new SerialComms(port).on('ready', function (comms) { 33 | new DeviceManager(comms).getFileList() 34 | .then(function (files) { 35 | for (var i = 0, file; file = files[i]; i++) { 36 | size = '' + file.size; 37 | size = ' '.substr(size.length) + size; 38 | console.log (size + ' bytes ' + file.filename); 39 | } 40 | }) 41 | .then(comms.close.bind(comms)); 42 | }); 43 | }, 44 | 45 | 46 | remove: function (filename) { 47 | new SerialComms(port).on('ready', function (comms) { 48 | new DeviceManager(comms).removeFile(filename) 49 | .then(comms.close.bind(comms)); 50 | }); 51 | }, 52 | 53 | 54 | write: function (filename, destination) { 55 | var data = '' + fs.readFileSync(filename), 56 | pathLib = require('path'), 57 | basename = pathLib.basename(destination || filename); 58 | 59 | new SerialComms(port).on('ready', function (comms) { 60 | new DeviceManager(comms).writeFile(basename, data) 61 | .then(comms.close.bind(comms)); 62 | }); 63 | }, 64 | 65 | 66 | push: function (filename, destination) { 67 | var data = '' + fs.readFileSync(filename), 68 | pathLib = require('path'), 69 | basename = pathLib.basename(destination || filename), 70 | match = filename.match(/\.([^\.]+)$/); 71 | 72 | if (match) { 73 | switch (match[1]) { 74 | case 'lua': 75 | data = require('luamin').minify(data); 76 | break; 77 | 78 | case 'html': 79 | data = require('html-minifier').minify(data, { 80 | removeComments: true, 81 | collapseWhitespace: true, 82 | removeRedundantAttributes: true, 83 | minifyJS: true, 84 | minifyCSS: true 85 | }); 86 | break; 87 | 88 | case 'js': 89 | data = require('uglify-js').minify(data, {fromString: true}).code; 90 | break; 91 | 92 | case 'css': 93 | data = (new (require('clean-css'))).minify(data).styles; 94 | break; 95 | } 96 | } 97 | 98 | new SerialComms(port).on('ready', function (comms) { 99 | new DeviceManager(comms).writeFile(basename, data) 100 | .then(comms.close.bind(comms)); 101 | }); 102 | }, 103 | 104 | 105 | read: function (filename) { 106 | new SerialComms(port).on('ready', function (comms) { 107 | new DeviceManager(comms).readFile(filename) 108 | .then(function (data) { return data.replace(/\r\n\r\n/g, '\n'); }) 109 | .then(console.log) 110 | .then(comms.close.bind(comms)); 111 | }); 112 | }, 113 | 114 | 115 | execute: function (filename) { 116 | new SerialComms(port).on('ready', function (comms) { 117 | new DeviceManager(comms).executeFile(filename) 118 | .then(console.log) 119 | .then(comms.close.bind(comms)); 120 | }); 121 | } 122 | 123 | }, 124 | 125 | 126 | restart: function () { 127 | new SerialComms(port).on('ready', function (comms) { 128 | new DeviceManager(comms).restart() 129 | .then(comms.close.bind(comms)); 130 | }); 131 | }, 132 | 133 | 134 | run: function (lua) { 135 | new SerialComms(port).on('ready', function (comms) { 136 | new DeviceManager(comms).executeLua(lua) 137 | .then(console.log) 138 | .then(comms.close.bind(comms)); 139 | }); 140 | }, 141 | 142 | monitor: function() { 143 | console.log("Displaying output from port " + port + "."); 144 | console.log("Press ^C to stop.\n"); 145 | 146 | new SerialComms(port).on('ready', function (comms) { 147 | comms.monitor(); 148 | }); 149 | } 150 | 151 | }; 152 | 153 | 154 | function execute (config, args) { 155 | var prop = config[args.shift()]; 156 | if (!prop) return false; 157 | 158 | if (typeof prop == 'function') { 159 | prop.apply(null, args); 160 | return true; 161 | } 162 | 163 | return execute(prop, args) 164 | } 165 | 166 | 167 | 168 | 169 | 170 | 171 | var args = process.argv.slice(2), 172 | port, 173 | success; 174 | 175 | 176 | if (args[0] == 'port' && args[1] == 'set') { 177 | // NOOP 178 | 179 | } else if (!fs.existsSync(PORT_FILENAME)) { 180 | console.log ('Serial port not configured.\nPlease use "esp port set " to configure.'); 181 | process.exit(); 182 | 183 | } else { 184 | port = '' + fs.readFileSync(PORT_FILENAME); 185 | } 186 | 187 | 188 | success = execute(config, args); 189 | 190 | if (!success) console.log ('Invalid command.'); 191 | 192 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esp8266", 3 | "description": "Command line interface for the ESP8266 module. Read and write files to/from the module and more.", 4 | "keywords": [ 5 | "ESP8266" 6 | ], 7 | "author": { 8 | "name": "Paul Cuthbertson", 9 | "url": "http://paulcuth.me.uk" 10 | }, 11 | "version": "0.0.3", 12 | "engines": { 13 | "node": ">=0.8.x" 14 | }, 15 | "bin": { 16 | "esp": "./bin/cli.js" 17 | }, 18 | "homepage": "http://esp8266.co.uk", 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/paulcuth/esp8266-cli" 22 | }, 23 | "license": { 24 | "type": "MIT" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/paulcuth/esp8266-cli/issues" 28 | }, 29 | "dependencies": { 30 | "clean-css": "^3.0.7", 31 | "es6-promise": "^2.0.0", 32 | "html-minifier": "^0.6.9", 33 | "luamin": "^0.2.8", 34 | "serialport": "^1.4.6", 35 | "uglify-js": "^2.4.16" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/DeviceManager.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | var Promise = require('es6-promise').Promise; 5 | 6 | 7 | 8 | var CHUNK_SIZE = 128; 9 | 10 | 11 | 12 | 13 | function DeviceManager (comms) { 14 | this._comms = comms; 15 | } 16 | 17 | 18 | 19 | 20 | DeviceManager.prototype.getFileList = function () { 21 | var command = 'for f,s in pairs(file.list()) do print(f,s) end'; 22 | 23 | return this._sendCommand(command) 24 | .then(function (data) { 25 | var files = data.split('\r\n'), 26 | result = [], 27 | i, file; 28 | 29 | for (i = 0; file = files[i]; i++) { 30 | file = file.split('\t'); 31 | result.push({ filename: file[0], size: file[1] }); 32 | } 33 | 34 | return result; 35 | }); 36 | } 37 | 38 | 39 | 40 | 41 | DeviceManager.prototype.removeFile = function (filename) { 42 | var command = 'file.remove"' + filename + '"'; 43 | 44 | return this._sendCommand(command) 45 | .then(function (data) { 46 | return '' + data != ''; 47 | }); 48 | }; 49 | 50 | 51 | 52 | 53 | DeviceManager.prototype.writeFile = function (filename, data) { 54 | return this.removeFile(filename) 55 | .then(this._writeFileHeader.bind(this, filename)) 56 | .then(this._writeFileData.bind(this, data)) 57 | .then(this._writeFileFooter.bind(this)); 58 | }; 59 | 60 | 61 | 62 | 63 | DeviceManager.prototype._writeFileHeader = function (filename, data) { 64 | var command = 'file.open("' + filename + '", "w")'; 65 | return this._sendCommand(command); 66 | }; 67 | 68 | 69 | 70 | 71 | DeviceManager.prototype._writeFileData = function (data) { 72 | var _this = this, 73 | chunked = [], 74 | chunk; 75 | 76 | data = '' + data; 77 | 78 | while (data.length) { 79 | chunk = data.substr(0, CHUNK_SIZE); 80 | data = data.substr(chunk.length); 81 | 82 | chunked.push(chunk); 83 | } 84 | 85 | return new Promise(function (resolve, reject) { 86 | function sendNextChunk () { 87 | if (!chunked.length) return resolve(); 88 | _this._writeFileChunk(chunked.shift()).then(sendNextChunk); 89 | } 90 | 91 | sendNextChunk(); 92 | }); 93 | }; 94 | 95 | 96 | 97 | 98 | DeviceManager.prototype._writeFileChunk = function (chunk) { 99 | var command, 100 | translate = { '\t': '\\t', '\n': '\\n', '\r': '\\r', '"': '\\"', '\\': '\\\\' }; 101 | 102 | chunk = chunk.replace(/[\t\n\r"\\]/g, function (x) { return translate[x]; }); 103 | command = 'file.write"' + chunk + '" file.flush()'; 104 | 105 | return this._sendCommand(command); 106 | }; 107 | 108 | 109 | 110 | 111 | DeviceManager.prototype._writeFileFooter = function () { 112 | var command = 'file.flush()file.close()'; 113 | return this._sendCommand(command); 114 | }; 115 | 116 | 117 | 118 | 119 | DeviceManager.prototype.readFile = function (filename) { 120 | var command = 'file.open("' + filename + '","r")for line in file.readline do print(line) end file.close()'; 121 | return this._sendCommand(command); 122 | }; 123 | 124 | 125 | 126 | 127 | DeviceManager.prototype.executeFile = function (filename) { 128 | var command = 'dofile"' + filename + '"'; 129 | return this._sendCommand(command); 130 | }; 131 | 132 | 133 | 134 | 135 | DeviceManager.prototype.executeLua = function (lua) { 136 | return this._sendCommand(lua); 137 | }; 138 | 139 | 140 | 141 | 142 | DeviceManager.prototype.restart = function (lua) { 143 | var command = 'node.restart()'; 144 | return this._sendCommand(command); 145 | }; 146 | 147 | 148 | 149 | 150 | DeviceManager.prototype._sendCommand = function (command) { 151 | var _this = this; 152 | 153 | return new Promise(function (resolve, reject) { 154 | _this._comms.once('response', resolve).send(command + '\r\n'); 155 | }); 156 | }; 157 | 158 | 159 | 160 | 161 | module.exports = DeviceManager; 162 | 163 | -------------------------------------------------------------------------------- /src/SerialComms.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | // Externals 4 | 5 | var SerialPort = require('serialport').SerialPort, 6 | EventEmitter = require('events').EventEmitter, 7 | util = require('util'); 8 | 9 | 10 | 11 | // Public 12 | 13 | function SerialComms (port) { 14 | this._echoBuffer = ''; 15 | this._responseBuffer = ''; 16 | this._port = new SerialPort(port, { 17 | baudrate: 9600, 18 | disconnectedCallback: process.exit 19 | }, false); 20 | 21 | this._initPort(); 22 | } 23 | 24 | 25 | util.inherits(SerialComms, EventEmitter); 26 | 27 | 28 | 29 | 30 | SerialComms.prototype._initPort = function () { 31 | var _this = this; 32 | 33 | this._port.on('data', function (data) { 34 | data = '' + data; 35 | var len = data.length, 36 | response; 37 | 38 | if (data == _this._echoBuffer.substr(0, len)) { 39 | _this._echoBuffer = _this._echoBuffer.substr(len); 40 | 41 | } else { 42 | _this._responseBuffer += data; 43 | 44 | if (_this._responseBuffer.substr(-2) == '> ') { 45 | response = _this._responseBuffer.substr(0, _this._responseBuffer.length - 4); 46 | _this._responseBuffer = ''; 47 | _this.emit('response', response); 48 | } 49 | } 50 | }); 51 | 52 | this._port.open(this._handlePortOpen.bind(this)); 53 | }; 54 | 55 | 56 | 57 | 58 | SerialComms.prototype._handlePortOpen = function (err) { 59 | if (err) throw new Error('Failed to open port: ' + err); 60 | this.emit('ready', this); 61 | } 62 | 63 | 64 | 65 | 66 | SerialComms.prototype.send = function (data) { 67 | if (!this._port) throw new Error('Port not open'); 68 | 69 | data = '' + data; 70 | this._echoBuffer += data; 71 | 72 | this._port.write(data, function(err) { 73 | if (err) throw err; 74 | }); 75 | }; 76 | 77 | 78 | 79 | 80 | SerialComms.prototype.close = function () { 81 | this._port.close(); 82 | }; 83 | 84 | 85 | SerialComms.prototype.monitor = function() { 86 | this._port.on('data', function(data) { 87 | process.stdout.write(data); 88 | }); 89 | } 90 | 91 | module.exports = SerialComms; 92 | --------------------------------------------------------------------------------