diff --git a/node/package.json b/node/package.json index 7d7885a..b04f980 100644 --- a/node/package.json +++ b/node/package.json @@ -3,8 +3,7 @@ "version": "0.0.9", "description": "", "main": "tessel.js", - "dependencies": { - }, + "dependencies": {}, "devDependencies": { "grunt": "^0.4.5", "grunt-cli": "^0.1.13", @@ -13,10 +12,11 @@ "grunt-contrib-watch": "^0.6.1", "grunt-jsbeautifier": "^0.2.10", "grunt-jscs": "^2.6.0", + "npm": "^3.10.5", "sinon": "^1.14.1" }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "grunt" }, "author": "Technical Machine ", "license": "MIT" diff --git a/node/tessel-export.js b/node/tessel-export.js index 8d0caf1..1e828a6 100644 --- a/node/tessel-export.js +++ b/node/tessel-export.js @@ -1223,19 +1223,10 @@ Tessel.LED.prototype.read = function(callback) { Tessel.Wifi = function() { var state = { - settings: {}, - connected: false + settings: {} }; Object.defineProperties(this, { - isConnected: { - get: () => state.connected - }, - connected: { - set: (value) => { - state.connected = value; - } - }, settings: { get: () => state.settings, set: (settings) => { @@ -1255,9 +1246,10 @@ Tessel.Wifi.prototype.enable = function(callback) { turnOnWifi() .then(commitWireless) .then(restartWifi) - .then(() => { + .then(getWifiInfo) + .then((network) => { + Object.assign(this.settings, network); this.emit('connect', this.settings); - this.connected = true; callback(); }) .catch((error) => { @@ -1276,7 +1268,6 @@ Tessel.Wifi.prototype.disable = function(callback) { .then(commitWireless) .then(restartWifi) .then(() => { - this.connected = false; this.emit('disconnect'); callback(); }) @@ -1291,11 +1282,11 @@ Tessel.Wifi.prototype.reset = function(callback) { callback = function() {}; } - this.connected = false; this.emit('disconnect', 'Resetting connection'); restartWifi() - .then(() => { - this.connected = true; + .then(getWifiInfo) + .then((network) => { + Object.assign(this.settings, network); this.emit('connect', this.settings); callback(); }) @@ -1305,12 +1296,34 @@ Tessel.Wifi.prototype.reset = function(callback) { }); }; -Tessel.Wifi.prototype.connection = function() { - if (this.isConnected) { - return this.settings; - } else { - return null; +Tessel.Wifi.prototype.connection = function(callback) { + if (typeof callback !== 'function') { + callback = function() {}; } + + isEnabled() + .then((enabled) => { + if (enabled) { + getWifiInfo() + .then((network) => { + delete network.password; + + this.settings = network; + + callback(null, network); + }) + .catch((error) => { + this.emit('error', error); + callback(error); + }); + } else { + callback(null, null); + } + }) + .catch((error) => { + this.emit('error', error); + callback(error); + }); }; Tessel.Wifi.prototype.connect = function(settings, callback) { @@ -1340,7 +1353,6 @@ Tessel.Wifi.prototype.connect = function(settings, callback) { delete settings.password; this.settings = Object.assign(network, settings); - this.connected = true; this.emit('connect', this.settings); callback(null, this.settings); @@ -1456,40 +1468,69 @@ function isEnabled() { function getWifiInfo() { return new Promise((resolve, reject) => { var checkCount = 0; + var rbcast = /(Bcast):([\w\.]+)/; function recursiveWifi() { - setImmediate(() => { - childProcess.exec(`ubus call iwinfo info '{"device":"wlan0"}'`, (error, results) => { - if (error) { - recursiveWifi(); - } else { - try { - var network = JSON.parse(results); - - if (network.ssid === undefined) { - // using 6 because it's the lowest count with accurate results after testing - if (checkCount < 6) { - checkCount++; - recursiveWifi(); - } else { - var msg = 'Tessel is unable to connect, please check your credentials or list of available networks (using tessel.network.wifi.findAvailableNetworks()) and try again.'; - throw msg; - } + childProcess.exec(`ubus call iwinfo info '{"device":"wlan0"}'`, (error, results) => { + if (error) { + recursiveWifi(); + } else { + try { + var network = JSON.parse(results); + + if (network.ssid === undefined) { + // using 6 because it's the lowest count with accurate results after testing + if (checkCount < 6) { + checkCount++; + recursiveWifi(); } else { - childProcess.exec('ifconfig wlan0', (error, ipResults) => { - if (error) { - reject(error); - } else { - network.ips = ipResults.split('\n'); - resolve(network); - } - }); + var msg = 'Tessel is unable to connect, please check your credentials or list of available networks (using tessel.network.wifi.findAvailableNetworks()) and try again.'; + throw msg; } - } catch (error) { - reject(error); + } else { + recursiveIP(network); } + } catch (error) { + reject(error); } - }); + } + }); + } + + // when immediately connecting and restarting the wifi chip, it takes a few moments before an IP address is broadcast to Tessel. + // This function keeps checking for that IP until it's available. + function recursiveIP(network) { + childProcess.exec('ifconfig wlan0', (error, ipResults) => { + if (error) { + reject(error); + } else { + var bcastMatches = ipResults.match(rbcast); + + if (bcastMatches === null) { + recursiveWifi(network); + } else { + // Successful matches will have a result that looks like: + // ["Bcast:0.0.0.0", "Bcast", "0.0.0.0"] + if (bcastMatches.length === 3) { + network.ip = bcastMatches[2]; + } else { + recursiveWifi(network); + } + } + + // attempt to parse out the security configuration from the returned network object + if (network.encryption.enabled) { + if (network.encryption.wep) { + network.security = 'wep'; + } else if (network.encryption.authentication && network.encryption.wpa) { + // sets "security" to either psk or psk2 + network.security = `${network.encryption.authentication[0]}${network.encryption.wpa[0] === 2 ? 2 : null}`; + } + } else { + network.security = 'none'; + } + resolve(network); + } }); } @@ -1522,6 +1563,16 @@ function scanWifi() { // Parse the security type - unused at the moment security: encryptionRegex.exec(entry)[1], }; + + // normalize security info to match configuration settings, i.e. none, wep, psk, psk2. "none" is already set correctly + if (networkInfo.security.includes('WEP')) { + networkInfo.security = 'wep'; + } else if (networkInfo.security.includes('WPA2')) { + networkInfo.security = 'psk2'; + } else if (networkInfo.security.includes('WPA')) { + networkInfo.security = 'psk'; + } + // Add this parsed network to our array networks.push(networkInfo); } catch (error) { diff --git a/node/test/unit/tessel.js b/node/test/unit/tessel.js index e5b9ca9..e3a980a 100644 --- a/node/test/unit/tessel.js +++ b/node/test/unit/tessel.js @@ -2453,32 +2453,43 @@ exports['Tessel.Wifi'] = { }, initialized: function(test) { - test.expect(2); + test.expect(1); - test.equal(this.tessel.network.wifi.isConnected, false, 'not connected by default'); test.deepEqual(this.tessel.network.wifi.settings, {}, 'no setings by default'); test.done(); }, connect: function(test) { - test.expect(5); + test.expect(4); var settings = { ssid: 'TestNetwork', password: 'TestPassword', - security: 'psk2' + security: 'wep' }; + var ipResult = `wlan0 Link encap:Ethernet HWaddr 02:A3:AA:A9:FB:02 + inet addr:10.0.1.11 Bcast:192.168.1.101 Mask:255.255.255.0 + inet6 addr: fe80::a3:aaff:fea9:fb02/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:2786 errors:0 dropped:0 overruns:0 frame:0 + TX packets:493 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:833626 (814.0 KiB) TX bytes:97959 (95.6 KiB)`; var ip = '192.168.1.101'; var network = { ssid: 'TestNetwork', - strength: '30/80' + strength: '30/80', + encryption: { + enabled: true, + wep: ['open'] + } }; this.exec.restore(); this.exec = sandbox.stub(childProcess, 'exec', (cmd, callback) => { if (cmd === 'ifconfig wlan0') { - callback(null, ip); + callback(null, ipResult); } else if (cmd === `ubus call iwinfo info '{"device":"wlan0"}'`) { callback(null, JSON.stringify(network)); } else { @@ -2487,7 +2498,7 @@ exports['Tessel.Wifi'] = { }); var results = Object.assign({ - ips: [ip] + ip: ip }, settings, network); delete results.password; @@ -2503,7 +2514,6 @@ exports['Tessel.Wifi'] = { test.deepEqual(networkSettings, results, 'correct settings'); test.deepEqual(this.tessel.network.wifi.settings, results, 'correct settings property'); - test.equal(this.tessel.network.wifi.isConnected, true, 'wifi is now connected'); test.equal(this.exec.callCount, 6, 'exec called correctly'); test.done(); @@ -2525,23 +2535,36 @@ exports['Tessel.Wifi'] = { }, connectWithoutCallback: function(test) { - test.expect(4); + test.expect(3); var settings = { ssid: 'TestNetwork', password: 'TestPassword', - security: 'psk2' + security: 'psk' }; + var ipResult = `wlan0 Link encap:Ethernet HWaddr 02:A3:AA:A9:FB:02 + inet addr:10.0.1.11 Bcast:192.168.1.101 Mask:255.255.255.0 + inet6 addr: fe80::a3:aaff:fea9:fb02/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:2786 errors:0 dropped:0 overruns:0 frame:0 + TX packets:493 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:833626 (814.0 KiB) TX bytes:97959 (95.6 KiB)`; var ip = '192.168.1.101'; var network = { ssid: 'TestNetwork', - strength: '30/80' + strength: '30/80', + encryption: { + enabled: true, + wpa: [1], + authentication: ['psk'] + } }; this.exec.restore(); this.exec = sandbox.stub(childProcess, 'exec', (cmd, callback) => { if (cmd === 'ifconfig wlan0') { - callback(null, ip); + callback(null, ipResult); } else if (cmd === `ubus call iwinfo info '{"device":"wlan0"}'`) { callback(null, JSON.stringify(network)); } else { @@ -2550,14 +2573,13 @@ exports['Tessel.Wifi'] = { }); var results = Object.assign({ - ips: [ip] + ip: ip }, settings, network); delete results.password; this.tessel.network.wifi.on('connect', (networkSettings) => { test.deepEqual(networkSettings, results, 'correct settings'); test.deepEqual(this.tessel.network.wifi.settings, results, 'correct settings property'); - test.equal(this.tessel.network.wifi.isConnected, true, 'wifi is now connected'); test.equal(this.exec.callCount, 6, 'exec called correctly'); test.done(); }); @@ -2571,22 +2593,35 @@ exports['Tessel.Wifi'] = { }, connectWithoutSecurity: function(test) { - test.expect(5); + test.expect(4); var settings = { ssid: 'TestNetwork', password: 'TestPassword' }; + var ipResult = `wlan0 Link encap:Ethernet HWaddr 02:A3:AA:A9:FB:02 + inet addr:10.0.1.11 Bcast:192.168.1.101 Mask:255.255.255.0 + inet6 addr: fe80::a3:aaff:fea9:fb02/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:2786 errors:0 dropped:0 overruns:0 frame:0 + TX packets:493 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:833626 (814.0 KiB) TX bytes:97959 (95.6 KiB)`; var ip = '192.168.1.101'; var network = { ssid: 'TestNetwork', - strength: '30/80' + strength: '30/80', + encryption: { + enabled: true, + wpa: [2], + authentication: ['psk'] + } }; this.exec.restore(); this.exec = sandbox.stub(childProcess, 'exec', (cmd, callback) => { if (cmd === 'ifconfig wlan0') { - callback(null, ip); + callback(null, ipResult); } else if (cmd === `ubus call iwinfo info '{"device":"wlan0"}'`) { callback(null, JSON.stringify(network)); } else { @@ -2595,7 +2630,7 @@ exports['Tessel.Wifi'] = { }); var results = Object.assign({ - ips: [ip], + ip: ip, security: 'psk2' }, settings, network); delete results.password; @@ -2612,7 +2647,6 @@ exports['Tessel.Wifi'] = { test.deepEqual(networkSettings, results, 'correct settings'); test.deepEqual(this.tessel.network.wifi.settings, results, 'correct settings property'); - test.equal(this.tessel.network.wifi.isConnected, true, 'wifi is now connected'); test.equal(this.exec.callCount, 6, 'exec called correctly'); test.done(); @@ -2620,21 +2654,32 @@ exports['Tessel.Wifi'] = { }, connectWithoutPassword: function(test) { - test.expect(5); + test.expect(4); var settings = { ssid: 'TestNetwork' }; + var ipResult = `wlan0 Link encap:Ethernet HWaddr 02:A3:AA:A9:FB:02 + inet addr:10.0.1.11 Bcast:192.168.1.101 Mask:255.255.255.0 + inet6 addr: fe80::a3:aaff:fea9:fb02/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:2786 errors:0 dropped:0 overruns:0 frame:0 + TX packets:493 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:833626 (814.0 KiB) TX bytes:97959 (95.6 KiB)`; var ip = '192.168.1.101'; var network = { ssid: 'TestNetwork', - strength: '30/80' + strength: '30/80', + encryption: { + enabled: false + } }; this.exec.restore(); this.exec = sandbox.stub(childProcess, 'exec', (cmd, callback) => { if (cmd === 'ifconfig wlan0') { - callback(null, ip); + callback(null, ipResult); } else if (cmd === `ubus call iwinfo info '{"device":"wlan0"}'`) { callback(null, JSON.stringify(network)); } else { @@ -2643,7 +2688,7 @@ exports['Tessel.Wifi'] = { }); var results = Object.assign({ - ips: [ip], + ip: ip, security: 'none' }, settings, network); @@ -2659,7 +2704,6 @@ exports['Tessel.Wifi'] = { test.deepEqual(networkSettings, results, 'correct settings'); test.deepEqual(this.tessel.network.wifi.settings, results, 'correct settings property'); - test.equal(this.tessel.network.wifi.isConnected, true, 'wifi is now connected'); test.equal(this.exec.callCount, 6, 'exec called correctly'); test.done(); @@ -2667,7 +2711,7 @@ exports['Tessel.Wifi'] = { }, connectThrowsError: function(test) { - test.expect(3); + test.expect(2); var settings = { ssid: 'TestNetwork' @@ -2691,7 +2735,6 @@ exports['Tessel.Wifi'] = { this.tessel.network.wifi.connect(settings, (error) => { if (error) { test.equal(error, testError, 'error should be passed into callback'); - test.equal(this.tessel.network.wifi.isConnected, false, 'wifi is not connected'); test.done(); } else { test.fail('should not connect'); @@ -2706,43 +2749,98 @@ exports['Tessel.Wifi'] = { var settings = { ssid: 'TestNetwork' }; + var ipResult = `wlan0 Link encap:Ethernet HWaddr 02:A3:AA:A9:FB:02 + inet addr:10.0.1.11 Bcast:192.168.1.101 Mask:255.255.255.0 + inet6 addr: fe80::a3:aaff:fea9:fb02/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:2786 errors:0 dropped:0 overruns:0 frame:0 + TX packets:493 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:833626 (814.0 KiB) TX bytes:97959 (95.6 KiB)`; var ip = '192.168.1.101'; var network = { ssid: 'TestNetwork', - strength: '30/80' + strength: '30/80', + encryption: { + enabled: false + } }; + var isFirstCheck = true; this.exec.restore(); this.exec = sandbox.stub(childProcess, 'exec', (cmd, callback) => { if (cmd === 'ifconfig wlan0') { - callback(null, ip); + callback(null, ipResult); } else if (cmd === `ubus call iwinfo info '{"device":"wlan0"}'`) { callback(null, JSON.stringify(network)); + } else if (cmd === `uci get wireless.@wifi-iface[0].disabled`) { + if (isFirstCheck) { + isFirstCheck = false; + callback(null, 1); + } else { + callback(null, 0); + } } else { callback(); } }); var results = Object.assign({ - ips: [ip], + ip: ip, security: 'none' }, settings, network); - test.equal(this.tessel.network.wifi.connection(), null, 'no settings yet'); - - this.tessel.network.wifi.connect(settings, (error) => { + this.tessel.network.wifi.connection((error, network) => { if (error) { test.fail(error); test.done(); } - test.deepEqual(this.tessel.network.wifi.connection(), results, 'correct settings'); - test.done(); + test.equal(network, null, 'no settings yet'); + + this.tessel.network.wifi.connect(settings, (error) => { + if (error) { + test.fail(error); + test.done(); + } + + this.tessel.network.wifi.connection((error, network) => { + test.deepEqual(network, results, 'correct settings'); + test.done(); + }); + }); }); }, reset: function(test) { - test.expect(3); + test.expect(2); + + var ipResult = `wlan0 Link encap:Ethernet HWaddr 02:A3:AA:A9:FB:02 + inet addr:10.0.1.11 Bcast:192.168.1.101 Mask:255.255.255.0 + inet6 addr: fe80::a3:aaff:fea9:fb02/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:2786 errors:0 dropped:0 overruns:0 frame:0 + TX packets:493 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:833626 (814.0 KiB) TX bytes:97959 (95.6 KiB)`; + var network = { + ssid: 'TestNetwork', + strength: '30/80', + encryption: { + enabled: false + } + }; + + this.exec.restore(); + this.exec = sandbox.stub(childProcess, 'exec', (cmd, callback) => { + if (cmd === 'ifconfig wlan0') { + callback(null, ipResult); + } else if (cmd === `ubus call iwinfo info '{"device":"wlan0"}'`) { + callback(null, JSON.stringify(network)); + } else { + callback(); + } + }); this.tessel.network.wifi.on('disconnect', () => { test.ok(true, 'disconnect event is fired'); @@ -2756,14 +2854,13 @@ exports['Tessel.Wifi'] = { test.fail(error); test.done(); } else { - test.equal(this.tessel.network.wifi.isConnected, true, 'wifi is not connected'); test.done(); } }); }, disable: function(test) { - test.expect(2); + test.expect(1); this.tessel.network.wifi.on('disconnect', () => { test.ok(true, 'disconnect event is fired'); @@ -2774,14 +2871,40 @@ exports['Tessel.Wifi'] = { test.fail(error); test.done(); } else { - test.equal(this.tessel.network.wifi.isConnected, false, 'wifi is not connected'); test.done(); } }); }, enable: function(test) { - test.expect(2); + test.expect(1); + + var ipResult = `wlan0 Link encap:Ethernet HWaddr 02:A3:AA:A9:FB:02 + inet addr:10.0.1.11 Bcast:192.168.1.101 Mask:255.255.255.0 + inet6 addr: fe80::a3:aaff:fea9:fb02/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:2786 errors:0 dropped:0 overruns:0 frame:0 + TX packets:493 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:833626 (814.0 KiB) TX bytes:97959 (95.6 KiB)`; + var network = { + ssid: 'TestNetwork', + strength: '30/80', + encryption: { + enabled: false + } + }; + + this.exec.restore(); + this.exec = sandbox.stub(childProcess, 'exec', (cmd, callback) => { + if (cmd === 'ifconfig wlan0') { + callback(null, ipResult); + } else if (cmd === `ubus call iwinfo info '{"device":"wlan0"}'`) { + callback(null, JSON.stringify(network)); + } else { + callback(); + } + }); this.tessel.network.wifi.on('connect', () => { test.ok(true, 'connect event is fired'); @@ -2792,21 +2915,20 @@ exports['Tessel.Wifi'] = { test.fail(error); test.done(); } else { - test.equal(this.tessel.network.wifi.isConnected, true, 'wifi is not connected'); test.done(); } }); }, findAvailableNetworks: function(test) { - test.expect(2); + test.expect(3); var networks = `Cell 01 - Address: 14:35:8B:11:30:F0 ESSID: "technicallyHome" Mode: Master Channel: 11 Signal: -55 dBm Quality: 55/70 - Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP) + Encryption: WPA PSK (TKIP, CCMP) Cell 02 - Address: 6C:70:9F:D9:7A:5C ESSID: "Fried Chicken Sandwich" @@ -2828,6 +2950,7 @@ exports['Tessel.Wifi'] = { this.tessel.network.wifi.findAvailableNetworks((error, found) => { test.equal(found.length, 2); test.equal(found[0].ssid, 'Fried Chicken Sandwich'); + test.equal(found[0].security, 'psk2'); test.done(); }); }, @@ -2859,7 +2982,7 @@ exports['Tessel.Wifi'] = { ESSID: "worst" Mode: Master Channel: 11 Signal: -55 dBm Quality: 30/ - Encryption: mixed WPA/WPA2 PSK (TKIP, CCMP) + Encryption: WPA PSK (TKIP, CCMP) Cell 02 - Address: 6C:70:9F:D9:7A:5C ESSID: "middle"