diff --git a/bin/tessel-2.js b/bin/tessel-2.js index 1a0f22d0..f2667340 100755 --- a/bin/tessel-2.js +++ b/bin/tessel-2.js @@ -503,6 +503,7 @@ makeCommand('version') makeCommand('ap') .callback(options => { + options.mode = 'ap'; log.level(options.loglevel); if (options.on || options.off) { @@ -529,6 +530,9 @@ makeCommand('ap') abbr: 's', help: 'Encryption to use on network (i.e. wep, psk, psk2, wpa, wpa2).' }) + .option('ip', { + help: 'Set the IP address of the network, i.e. "192.168.1.101"' + }) .option('off', { flag: true, help: 'Disable the access point' @@ -539,6 +543,48 @@ makeCommand('ap') }) .help('Configure the Tessel as an access point'); +makeCommand('adhoc') + .callback(options => { + options.mode = 'adhoc'; + log.level(options.loglevel); + + if (options.on || options.off) { + if (options.on) { + callControllerWith('enableAccessPoint', options); + } else { + callControllerWith('disableAccessPoint', options); + } + } else if (options.ssid) { + callControllerWith('createAccessPoint', options); + } else { + callControllerWith('getAccessPointInfo', options); + } + }) + .option('ssid', { + abbr: 'n', + help: 'Name of the network.' + }) + .option('password', { + abbr: 'p', + help: 'Password to access network.' + }) + .option('security', { + abbr: 's', + help: 'Encryption used on network (i.e. wep, psk, psk2, wpa, wpa2).' + }) + .option('ip', { + help: 'Set the IP address of the network, i.e. "192.168.1.101"' + }) + .option('off', { + flag: true, + help: 'Disable the adhoc network' + }) + .option('on', { + flag: true, + help: 'Enable the adhoc network' + }) + .help('Configure the Tessel as an adhoc network'); + makeCommand('root') .callback(options => { log.level(options.loglevel); diff --git a/lib/controller.js b/lib/controller.js index d3588389..7ee01e5a 100644 --- a/lib/controller.js +++ b/lib/controller.js @@ -720,9 +720,11 @@ controller.setWiFiState = function(opts) { controller.createAccessPoint = function(opts) { opts.authorized = true; var ssid = opts.ssid; + var ip = opts.ip; var password = opts.password; var security = opts.security; var securityOptions = ['none', 'wep', 'psk', 'psk2']; + var ipRegex = /^([0-1]?[0-9]?[0-9]|[2][0-4][0-9]|25[0-5])\.([0-1]?[0-9]?[0-9]|[2][0-4][0-9]|25[0-5])\.([0-1]?[0-9]?[0-9]|[2][0-4][0-9]|25[0-5])\.([0-1]?[0-9]?[0-9]|[2][0-4][0-9]|25[0-5])$/; return new Promise((resolve, reject) => { if (!ssid) { @@ -763,6 +765,11 @@ controller.createAccessPoint = function(opts) { return reject('Invalid passphrase: WPA/WPA2-PSK passkeys must be 8-63 ASCII characters, or 64 hexadecimal digits.'); } } + + if (ip && ipRegex.test(ip) === false) { + return reject(`Invalid IP address: the IP must 4 sets of numbers between 0 and 255, separate by a '.', like '192.168.1.101' or '255.255.255.255'`); + } + resolve(); }) .then(() => { @@ -774,12 +781,12 @@ controller.createAccessPoint = function(opts) { controller.enableAccessPoint = function(opts) { opts.authorized = true; - return controller.standardTesselCommand(opts, (tessel) => tessel.enableAccessPoint()); + return controller.standardTesselCommand(opts, (tessel) => tessel.enableAccessPoint(opts)); }; controller.disableAccessPoint = function(opts) { opts.authorized = true; - return controller.standardTesselCommand(opts, (tessel) => tessel.disableAccessPoint()); + return controller.standardTesselCommand(opts, (tessel) => tessel.disableAccessPoint(opts)); }; controller.getAccessPointInfo = function(opts) { diff --git a/lib/tessel/access-point.js b/lib/tessel/access-point.js index 4f1d93d8..60ebbc4c 100644 --- a/lib/tessel/access-point.js +++ b/lib/tessel/access-point.js @@ -9,6 +9,7 @@ var commands = require('./commands'); var log = require('../log'); var Tessel = require('./tessel'); +var defaultIP = '192.168.1.101'; function commitAndClose(tessel, status, resolve, reject) { @@ -27,8 +28,8 @@ function commitAndClose(tessel, status, resolve, reject) { .catch(reject); } -Tessel.prototype.enableAccessPoint = function() { - var status = 'Access Point successfully enabled.'; +Tessel.prototype.enableAccessPoint = function(opts) { + var status = `${opts.mode === 'ap' ? 'Access Point' : 'Adhoc Network'} successfully enabled.`; return new Promise((resolve, reject) => { return this.getAccessPointInfo() @@ -59,8 +60,8 @@ Tessel.prototype.enableAccessPoint = function() { }); }; -Tessel.prototype.disableAccessPoint = function() { - var status = 'Access Point successfully disabled.'; +Tessel.prototype.disableAccessPoint = function(opts) { + var status = `${opts.mode === 'ap' ? 'Access Point' : 'Adhoc Network'} successfully disabled.`; return new Promise((resolve, reject) => { return this.simpleExec(commands.turnAccessPointOff()) @@ -84,7 +85,7 @@ Tessel.prototype.getAccessPointInfo = function() { info = { ssid: values[0], - key: values[1], + key: values[2] !== 'none' ? values[1] : null, // the password for a previous configuration could still exist, so omit this info if the encryption is 'none' encryption: values[2], disabled: values[3] }; @@ -102,9 +103,11 @@ Tessel.prototype.getAccessPointInfo = function() { Tessel.prototype.createAccessPoint = function(opts) { var ssid = opts.ssid; - var password = opts.pass; + var password = opts.password; var security = opts.security; - var status = 'Created Access Point successfully. '; + var mode = opts.mode; + var ip = opts.ip; + var status = `Created ${mode === 'ap' ? 'Access Point' : 'Adhoc Network'} successfully. `; var setupAccessPoint = () => { @@ -112,11 +115,15 @@ Tessel.prototype.createAccessPoint = function(opts) { security = 'psk2'; } + if (!ip) { + ip = defaultIP; + } + if (password && security) { - status += `SSID: ${ssid}, password ${password}, security mode: ${security}`; + status += `SSID: ${ssid}, password ${password}, security mode: ${security}, IP address: ${ip}`; } else if (!password && !security) { security = 'none'; - status += `SSID: ${ssid}`; + status += `SSID: ${ssid}, IP address: ${ip}`; } var setSSID = () => this.simpleExec(commands.setAccessPointSSID(ssid)); @@ -125,6 +132,10 @@ Tessel.prototype.createAccessPoint = function(opts) { var setAccessPointSecurity = () => this.simpleExec(commands.setAccessPointSecurity(security)); + var setAccessPointMode = () => this.simpleExec(commands.setAccessPointMode(mode)); + + var setAccessPointIP = () => this.simpleExec(commands.setLanNetworkIP(ip)); + var turnAccessPointOn = () => this.simpleExec(commands.turnAccessPointOn()); var commitAndClosePromise = () => { @@ -143,32 +154,50 @@ Tessel.prototype.createAccessPoint = function(opts) { }; return setup() + .then(setAccessPointMode) + .then(setAccessPointIP) .then(turnAccessPointOn) .then(commitAndClosePromise); }; - return this.simpleExec(commands.getAccessPoint()) + return this.simpleExec(commands.getWifiSettings()) + .then((settings) => { + var regexDisabled = /disabled='(\d)'/; + var disabledMatcher = settings.match(regexDisabled); + + // because an adhoc network both connects and emits, + // wifi cannot be enabled to when creating an adhoc network + if (mode === 'adhoc' && disabledMatcher[1] === '0') { + throw new Error(`Tessel must have wifi disabled before creating an adhoc network. Please run 't2 wifi --off' and try again.`); + } + + return true; + }) + + .then(this.simpleExec(commands.getAccessPoint())) .then(() => { // When an AP exists, change the status to // reflect an "update" vs. "create": - status = 'Updated Access Point successfully. '; + status = `Updated ${mode === 'ap' ? 'Access Point' : 'Adhoc Network'} successfully. `; return setupAccessPoint(); }) - .catch(() => { + .catch((error) => { + // this error was thrown when checking on the wifi status above + if (error && error.message.includes('adhoc')) { + return Promise.reject(error); + } var setAccessPoint = () => { return this.simpleExec(commands.setAccessPoint()) .then(() => this.simpleExec(commands.setAccessPointDevice())) - .then(() => this.simpleExec(commands.setAccessPointNetwork())) - .then(() => this.simpleExec(commands.setAccessPointMode())); + .then(() => this.simpleExec(commands.setAccessPointNetwork())); }; var setLanNetwork = () => { return this.simpleExec(commands.setLanNetwork()) .then(() => this.simpleExec(commands.setLanNetworkIfname())) .then(() => this.simpleExec(commands.setLanNetworkProto())) - .then(() => this.simpleExec(commands.setLanNetworkIP())) .then(() => this.simpleExec(commands.setLanNetworkNetmask())); }; diff --git a/lib/tessel/commands.js b/lib/tessel/commands.js index 7920056f..174daccd 100644 --- a/lib/tessel/commands.js +++ b/lib/tessel/commands.js @@ -125,8 +125,8 @@ module.exports.setLanNetworkIfname = function() { module.exports.setLanNetworkProto = function() { return ['uci', 'set', 'network.lan.proto=static']; }; -module.exports.setLanNetworkIP = function() { - return ['uci', 'set', 'network.lan.ipaddr=192.168.1.101']; +module.exports.setLanNetworkIP = function(ip) { + return ['uci', 'set', `network.lan.ipaddr=${ip}`]; }; module.exports.setLanNetworkNetmask = function() { return ['uci', 'set', 'network.lan.netmask=255.255.255.0']; @@ -134,6 +134,9 @@ module.exports.setLanNetworkNetmask = function() { module.exports.commitNetwork = function() { return ['uci', 'commit', 'network']; }; +module.exports.getWifiSettings = function() { + return ['uci', 'show', 'wireless.@wifi-iface[0]']; +}; module.exports.getAccessPoint = function() { return ['uci', 'get', 'wireless.@wifi-iface[1]']; }; @@ -149,8 +152,8 @@ module.exports.setAccessPointDevice = function() { module.exports.setAccessPointNetwork = function() { return ['uci', 'set', 'wireless.@wifi-iface[1].network=lan']; }; -module.exports.setAccessPointMode = function() { - return ['uci', 'set', 'wireless.@wifi-iface[1].mode=ap']; +module.exports.setAccessPointMode = function(mode) { + return ['uci', 'set', `wireless.@wifi-iface[1].mode=${mode}`]; }; module.exports.setAccessPointSSID = function(ssid) { return ['uci', 'set', 'wireless.@wifi-iface[1].ssid=' + ssid]; diff --git a/lib/tessel/wifi.js b/lib/tessel/wifi.js index c425965e..298eda04 100644 --- a/lib/tessel/wifi.js +++ b/lib/tessel/wifi.js @@ -149,7 +149,22 @@ Tessel.prototype.connectToNetwork = function(opts) { }; - return setup() + return this.simpleExec(commands.getAccessPointConfig()) + .then((config) => { + var regexDisabled = /disabled='(\d)'/; + var regexMode = /mode='(.+)'/; + var disabledMatcher = config.match(regexDisabled); + var modeMatcher = config.match(regexMode); + + // because an adhoc network both connects and emits, + // adhoc cannot be enabled to when connecting to a wifi network + if (modeMatcher[1] === 'adhoc' && disabledMatcher[1] === '0') { + throw new Error(`Tessel must have adhoc disabled before connecting to a wifi network. Please run 't2 adhoc --off' and try again.`); + } + + return true; + }) + .then(setup) .then(turnWifiOn) .then(logStatus); }; diff --git a/test/unit/access-point.js b/test/unit/access-point.js index 99d6857b..7e4b3e4f 100644 --- a/test/unit/access-point.js +++ b/test/unit/access-point.js @@ -27,6 +27,7 @@ exports['Tessel.prototype.createAccessPoint'] = { this.reconnectWifi = this.sandbox.spy(commands, 'reconnectWifi'); this.reconnectDnsmasq = this.sandbox.spy(commands, 'reconnectDnsmasq'); this.reconnectDhcp = this.sandbox.spy(commands, 'reconnectDhcp'); + this.getWifiSettings = this.sandbox.spy(commands, 'getWifiSettings'); this.tessel = TesselSimulator(); // These are needed because the sheer number of commands run within @@ -46,11 +47,12 @@ exports['Tessel.prototype.createAccessPoint'] = { }, newAccessPoint: function(test) { - test.expect(20); + test.expect(23); var creds = { ssid: 'test', - pass: 'test-password', - security: 'psk2' + password: 'test-password', + security: 'psk2', + mode: 'ap' }; // Immediately close any opened connections @@ -93,9 +95,76 @@ exports['Tessel.prototype.createAccessPoint'] = { test.equal(this.reconnectWifi.callCount, 1); test.equal(this.reconnectDnsmasq.callCount, 1); test.equal(this.reconnectDhcp.callCount, 1); + test.equal(this.getWifiSettings.callCount, 1); test.ok(this.setAccessPointSSID.lastCall.calledWith(creds.ssid)); - test.ok(this.setAccessPointPassword.lastCall.calledWith(creds.pass)); + test.ok(this.setAccessPointPassword.lastCall.calledWith(creds.password)); test.ok(this.setAccessPointSecurity.lastCall.calledWith(creds.security)); + test.ok(this.setAccessPointMode.lastCall.calledWith(creds.mode)); + test.ok(this.setLanNetworkIP.lastCall.calledWith('192.168.1.101')); + + test.done(); + }) + .catch(error => { + test.ok(false, error.toString()); + test.done(); + }); + }, + + newAdhoc: function(test) { + test.expect(23); + var creds = { + ssid: 'test', + password: 'test-password', + security: 'psk2', + mode: 'adhoc' + }; + + // Immediately close any opened connections + this.tessel._rps.on('control', () => { + setImmediate(() => { + this.tessel._rps.emit('close'); + }); + }); + + // tessel.receive is called twice but needs to be rejected the first time + var count = 0; + this.sandbox.stub(this.tessel, 'receive', function(remoteProcess, callback) { + if (typeof callback !== 'function') { + callback = function() {}; + } + if (count === 0) { + count++; + return callback(new Error('uci: Entry not found')); + } else { + return callback(null, new Buffer(0)); + } + }); + + this.tessel.createAccessPoint(creds) + .then(() => { + test.equal(this.getAccessPoint.callCount, 1); + test.equal(this.setAccessPoint.callCount, 1); + test.equal(this.setAccessPointDevice.callCount, 1); + test.equal(this.setAccessPointNetwork.callCount, 1); + test.equal(this.setAccessPointMode.callCount, 1); + test.equal(this.setAccessPointSSID.callCount, 1); + test.equal(this.setAccessPointPassword.callCount, 1); + test.equal(this.setAccessPointSecurity.callCount, 1); + test.equal(this.setLanNetwork.callCount, 1); + test.equal(this.setLanNetworkIfname.callCount, 1); + test.equal(this.setLanNetworkProto.callCount, 1); + test.equal(this.setLanNetworkIP.callCount, 1); + test.equal(this.setLanNetworkNetmask.callCount, 1); + test.equal(this.commitNetwork.callCount, 1); + test.equal(this.reconnectWifi.callCount, 1); + test.equal(this.reconnectDnsmasq.callCount, 1); + test.equal(this.reconnectDhcp.callCount, 1); + test.equal(this.getWifiSettings.callCount, 1); + test.ok(this.setAccessPointSSID.lastCall.calledWith(creds.ssid)); + test.ok(this.setAccessPointPassword.lastCall.calledWith(creds.password)); + test.ok(this.setAccessPointSecurity.lastCall.calledWith(creds.security)); + test.ok(this.setAccessPointMode.lastCall.calledWith(creds.mode)); + test.ok(this.setLanNetworkIP.lastCall.calledWith('192.168.1.101')); test.done(); }) @@ -106,11 +175,12 @@ exports['Tessel.prototype.createAccessPoint'] = { }, noPasswordNoSecurity: function(test) { - test.expect(9); + test.expect(10); var creds = { ssid: 'test', - pass: undefined, - security: undefined + password: undefined, + security: undefined, + mode: 'ap' }; // Test is expecting two closes...; @@ -126,6 +196,7 @@ exports['Tessel.prototype.createAccessPoint'] = { test.equal(this.setAccessPointSSID.callCount, 1); test.equal(this.setAccessPointPassword.callCount, 0); test.equal(this.setAccessPointSecurity.callCount, 1); + test.equal(this.setAccessPointMode.callCount, 1); test.equal(this.reconnectWifi.callCount, 1); test.equal(this.reconnectDnsmasq.callCount, 1); test.equal(this.reconnectDhcp.callCount, 1); @@ -140,11 +211,13 @@ exports['Tessel.prototype.createAccessPoint'] = { }, properCredentials: function(test) { - test.expect(10); + test.expect(13); var creds = { ssid: 'test', - pass: 'test-password', - security: 'psk2' + password: 'test-password', + security: 'psk2', + mode: 'ap', + ip: '192.168.1.113' }; // Test is expecting two closes...; @@ -160,12 +233,15 @@ exports['Tessel.prototype.createAccessPoint'] = { test.equal(this.setAccessPointSSID.callCount, 1); test.equal(this.setAccessPointPassword.callCount, 1); test.equal(this.setAccessPointSecurity.callCount, 1); + test.equal(this.setAccessPointMode.callCount, 1); + test.equal(this.setLanNetworkIP.callCount, 1); test.equal(this.reconnectWifi.callCount, 1); test.equal(this.reconnectDnsmasq.callCount, 1); test.equal(this.reconnectDhcp.callCount, 1); test.ok(this.setAccessPointSSID.lastCall.calledWith(creds.ssid)); - test.ok(this.setAccessPointPassword.lastCall.calledWith(creds.pass)); + test.ok(this.setAccessPointPassword.lastCall.calledWith(creds.password)); test.ok(this.setAccessPointSecurity.lastCall.calledWith(creds.security)); + test.ok(this.setLanNetworkIP.lastCall.calledWith(creds.ip)); test.done(); }) .catch(error => { @@ -175,11 +251,12 @@ exports['Tessel.prototype.createAccessPoint'] = { }, passwordNoSecurity: function(test) { - test.expect(10); + test.expect(11); var creds = { ssid: 'test', - pass: 'test-password', - security: undefined + password: 'test-password', + security: undefined, + mode: 'ap' }; // Test is expecting two closes...; @@ -195,11 +272,12 @@ exports['Tessel.prototype.createAccessPoint'] = { test.equal(this.setAccessPointSSID.callCount, 1); test.equal(this.setAccessPointPassword.callCount, 1); test.equal(this.setAccessPointSecurity.callCount, 1); + test.equal(this.setAccessPointMode.callCount, 1); test.equal(this.reconnectWifi.callCount, 1); test.equal(this.reconnectDnsmasq.callCount, 1); test.equal(this.reconnectDhcp.callCount, 1); test.ok(this.setAccessPointSSID.lastCall.calledWith(creds.ssid)); - test.ok(this.setAccessPointPassword.lastCall.calledWith(creds.pass)); + test.ok(this.setAccessPointPassword.lastCall.calledWith(creds.password)); test.ok(this.setAccessPointSecurity.lastCall.calledWith('psk2')); test.done(); }) @@ -207,6 +285,42 @@ exports['Tessel.prototype.createAccessPoint'] = { test.ok(false, error.toString()); test.done(); }); + }, + + createAdhocWhenWifiEnabled: function(test) { + test.expect(2); + var creds = { + ssid: 'test', + mode: 'adhoc' + }; + + // Test is expecting two closes...; + this.tessel._rps.on('control', (command) => { + if (command.toString() === 'uci show wireless.@wifi-iface[0]') { + var info = new Buffer(tags.stripIndent ` + wireless.cfg053579.disabled='0'`); + + setImmediate(() => { + this.tessel._rps.stdout.emit('data', info); + this.tessel._rps.emit('close'); + }); + } else { + setImmediate(() => { + this.tessel._rps.emit('close'); + }); + } + }); + + this.tessel.createAccessPoint(creds) + .then(() => { + test.fail('Creating an adhoc network should fail while wifi is enabled.'); + test.done(); + }) + .catch(error => { + test.equal(this.getWifiSettings.callCount, 1); + test.ok(error.toString()); + test.done(); + }); } }; @@ -276,7 +390,7 @@ exports['Tessel.prototype.enableAccessPoint'] = { } }); - this.tessel.enableAccessPoint() + this.tessel.enableAccessPoint({mode: 'ap'}) .then(() => { test.equal(this.turnAccessPointOn.callCount, 1); test.equal(this.reconnectWifi.callCount, 1); @@ -328,7 +442,7 @@ exports['Tessel.prototype.enableAccessPoint'] = { } }); - this.tessel.enableAccessPoint() + this.tessel.enableAccessPoint({mode: 'ap'}) .then(() => { test.fail('Should not pass'); test.done(); @@ -374,7 +488,7 @@ exports['Tessel.prototype.disableAccessPoint'] = { }); }); - this.tessel.disableAccessPoint() + this.tessel.disableAccessPoint({mode: 'ap'}) .then(() => { test.equal(this.turnAccessPointOff.callCount, 1); test.equal(this.reconnectWifi.callCount, 1); diff --git a/test/unit/controller.js b/test/unit/controller.js index 707df0ab..d49c1f42 100644 --- a/test/unit/controller.js +++ b/test/unit/controller.js @@ -1156,6 +1156,36 @@ exports['controller.createAccessPoint'] = { test.done(); }); }, + + invalidAccessPointIPAddress: function(test) { + test.expect(1); + + controller.createAccessPoint({ + ssid: 'test', + ip: '999.9999.9' + }) + .catch(error => { + test.ok(error); + test.done(); + }); + }, + + validAccessPointIPAddress: function(test) { + test.expect(1); + + controller.createAccessPoint({ + ssid: 'test', + ip: '192.168.1.113' + }) + .then((settings) => { + test.ok(settings); + test.done(); + }) + .catch((error) => { + test.fail(error.toString()); + test.done(); + }); + }, }; exports['controller.root'] = { diff --git a/test/unit/wifi.js b/test/unit/wifi.js index 483c5087..1cf86227 100644 --- a/test/unit/wifi.js +++ b/test/unit/wifi.js @@ -232,6 +232,7 @@ module.exports['Tessel.prototype.connectToNetwork'] = { this.commitWirelessCredentials = this.sandbox.spy(commands, 'commitWirelessCredentials'); this.reconnectWifi = this.sandbox.spy(commands, 'reconnectWifi'); this.getWifiInfo = this.sandbox.spy(commands, 'getWifiInfo'); + this.getAccessPointConfig = this.sandbox.spy(commands, 'getAccessPointConfig'); this.tessel = TesselSimulator(); done(); @@ -242,7 +243,7 @@ module.exports['Tessel.prototype.connectToNetwork'] = { done(); }, noPassword: function(test) { - test.expect(8); + test.expect(9); var creds = { ssid: 'tank', password: undefined @@ -257,6 +258,15 @@ module.exports['Tessel.prototype.connectToNetwork'] = { this.tessel._rps.stdout.emit('data', new Buffer('signal')); this.tessel._rps.emit('close'); }); + } else if (command.toString() === 'uci show wireless.@wifi-iface[1]') { + var info = new Buffer(tags.stripIndent ` + wireless.cfg053579.mode='ap' + wireless.cfg053579.disabled='1'`); + + setImmediate(() => { + this.tessel._rps.stdout.emit('data', info); + this.tessel._rps.emit('close'); + }); } else { setImmediate(() => { // Remove any listeners on stdout so we don't break anything when we write to it @@ -273,9 +283,10 @@ module.exports['Tessel.prototype.connectToNetwork'] = { test.equal(this.setNetworkEncryption.callCount, 1); test.equal(this.commitWirelessCredentials.callCount, 1); test.equal(this.reconnectWifi.callCount, 1); + test.equal(this.getWifiInfo.callCount, 1); + test.equal(this.getAccessPointConfig.callCount, 1); test.ok(this.setNetworkSSID.lastCall.calledWith(creds.ssid)); test.ok(this.setNetworkEncryption.lastCall.calledWith('none')); - test.ok(this.getWifiInfo.callCount, 1); test.done(); }) .catch(error => { @@ -299,6 +310,15 @@ module.exports['Tessel.prototype.connectToNetwork'] = { this.tessel._rps.stdout.emit('data', new Buffer('signal')); this.tessel._rps.emit('close'); }); + } else if (command.toString() === 'uci show wireless.@wifi-iface[1]') { + var info = new Buffer(tags.stripIndent ` + wireless.cfg053579.mode='ap' + wireless.cfg053579.disabled='1'`); + + setImmediate(() => { + this.tessel._rps.stdout.emit('data', info); + this.tessel._rps.emit('close'); + }); } else { setImmediate(() => { // Remove any listeners on stdout so we don't break anything when we write to it @@ -344,6 +364,15 @@ module.exports['Tessel.prototype.connectToNetwork'] = { this.tessel._rps.stdout.emit('data', new Buffer('signal')); this.tessel._rps.emit('close'); }); + } else if (command.toString() === 'uci show wireless.@wifi-iface[1]') { + var info = new Buffer(tags.stripIndent ` + wireless.cfg053579.mode='ap' + wireless.cfg053579.disabled='1'`); + + setImmediate(() => { + this.tessel._rps.stdout.emit('data', info); + this.tessel._rps.emit('close'); + }); } else { setImmediate(() => { // Remove any listeners on stdout so we don't break anything when we write to it @@ -388,6 +417,15 @@ module.exports['Tessel.prototype.connectToNetwork'] = { this.tessel._rps.stderr.emit('data', new Buffer('Unable to connect to the network.')); this.tessel._rps.emit('close'); }); + } else if (command.toString() === 'uci show wireless.@wifi-iface[1]') { + var info = new Buffer(tags.stripIndent ` + wireless.cfg053579.mode='ap' + wireless.cfg053579.disabled='1'`); + + setImmediate(() => { + this.tessel._rps.stdout.emit('data', info); + this.tessel._rps.emit('close'); + }); } else { setImmediate(() => { // Remove any listeners on stderr so we don't break anything when we write to it @@ -435,6 +473,15 @@ module.exports['Tessel.prototype.connectToNetwork'] = { this.tessel._rps.stderr.removeAllListeners(); this.tessel._rps.emit('close'); }); + } else if (command.toString() === 'uci show wireless.@wifi-iface[1]') { + var info = new Buffer(tags.stripIndent ` + wireless.cfg053579.mode='ap' + wireless.cfg053579.disabled='1'`); + + setImmediate(() => { + this.tessel._rps.stdout.emit('data', info); + this.tessel._rps.emit('close'); + }); } else { setImmediate(() => { // Remove any listeners on stderr so we don't break anything when we write to it @@ -464,6 +511,51 @@ module.exports['Tessel.prototype.connectToNetwork'] = { test.done(); }); }, + connectFailsWhileAdhocEnabled: function(test) { + test.expect(2); + var creds = { + ssid: 'tank', + password: 'not_gonna_work' + }; + // Test is expecting several closes...; + this.tessel._rps.on('control', (command) => { + if (command.toString() === 'ubus call iwinfo info {"device":"wlan0"}') { + // Write to stdout so it completes as expected + // Wrap in setImmediate to make sure listener is set up before emitting + setImmediate(() => { + this.tessel._rps.stdout.emit('data', new Buffer('signal')); + this.tessel._rps.emit('close'); + }); + } else if (command.toString() === 'uci show wireless.@wifi-iface[1]') { + var info = new Buffer(tags.stripIndent ` + wireless.cfg053579.mode='adhoc' + wireless.cfg053579.disabled='0'`); + + setImmediate(() => { + this.tessel._rps.stdout.emit('data', info); + this.tessel._rps.emit('close'); + }); + } else { + setImmediate(() => { + // Remove any listeners on stderr so we don't break anything when we write to it + this.tessel._rps.stdout.removeAllListeners(); + this.tessel._rps.stderr.removeAllListeners(); + this.tessel._rps.emit('close'); + }); + } + }); + + this.tessel.connectToNetwork(creds) + .then(function() { + test.ok(false, 'Test should have rejected with an error.'); + test.done(); + }) + .catch((error) => { + test.equal(error.message.includes('Tessel must have adhoc disabled'), true); + test.equal(this.getAccessPointConfig.callCount, 1); + test.done(); + }); + }, // Sometimes the keyword for success (signal) is not in the first batch // of stdout data batchedResponse: function(test) { @@ -484,6 +576,15 @@ module.exports['Tessel.prototype.connectToNetwork'] = { this.tessel._rps.stdout.emit('data', new Buffer('signal')); this.tessel._rps.emit('close'); }); + } else if (command.toString() === 'uci show wireless.@wifi-iface[1]') { + var info = new Buffer(tags.stripIndent ` + wireless.cfg053579.mode='ap' + wireless.cfg053579.disabled='1'`); + + setImmediate(() => { + this.tessel._rps.stdout.emit('data', info); + this.tessel._rps.emit('close'); + }); } else { setImmediate(() => { // Remove any listeners on stderr so we don't break anything when we write to it