├── .gitignore ├── README.md ├── index.js ├── package.json ├── src └── wifi-connection.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | .vscode 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (http://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # Typescript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rpi-wifi-connection 2 | 3 | Module to connect a Raspberry Pi to Wi-Fi 4 | 5 | ## Installation 6 | $ npm install rpi-wifi-connection --save 7 | 8 | ## Usage 9 | var Wifi = require('rpi-wifi-connection'); 10 | var wifi = new Wifi(); 11 | 12 | ## Methods 13 | 14 | ### constructor(iface) 15 | 16 | Constructs a new wifi connection object. 17 | 18 | - **iface** - Specifies the name of the interface (default is **wlan0**) 19 | 20 | ### connect(options) 21 | 22 | Connects to the specified network. 23 | 24 | - **options.ssid** - Specifies the network name. 25 | - **options.psk** - Specifies the password. 26 | - **options.timeout** - Specifies the number of milliseconds to wait for connection. Default is 60 seconds (60000). 27 | - **options.hidden** - Should be set to `true` if the network is hidden. 28 | 29 | ````javascript 30 | var Wifi = require('rpi-wifi-connection'); 31 | var wifi = new Wifi(); 32 | 33 | wifi.connect({ssid:'my-network', psk:'raspberry'}).then(() => { 34 | console.log('Connected to network.'); 35 | }) 36 | .catch((error) => { 37 | console.log(error); 38 | }); 39 | ```` 40 | 41 | ### scan() 42 | 43 | Return a promise containing the available networks 44 | 45 | ````javascript 46 | 47 | var Wifi = require('rpi-wifi-connection'); 48 | var wifi = new Wifi(); 49 | 50 | wifi.scan().then((ssids) => { 51 | console.log(ssids); 52 | }) 53 | .catch((error) => { 54 | console.log(error); 55 | }); 56 | 57 | // [ { bssid: 'f4:ca:e5:e7:de:58', signalLevel: -72, frequency: 2467, ssid: 'homo' }, 58 | { bssid: 'f4:ca:e5:e7:de:5a', signalLevel: -72, frequency: 2467, ssid: 'deus' } ] 59 | ```` 60 | 61 | ### getStatus() 62 | 63 | Returns a promise containing the network status. 64 | 65 | ````javascript 66 | 67 | var Wifi = require('rpi-wifi-connection'); 68 | var wifi = new Wifi(); 69 | 70 | wifi.getStatus().then((status) => { 71 | console.log(status); 72 | }) 73 | .catch((error) => { 74 | console.log(error); 75 | }); 76 | 77 | // { ssid: 'Julia', ip_address: '10.0.1.189' } 78 | ```` 79 | 80 | ### getState() 81 | 82 | Returns a promise containing the connection state. Please note that 83 | this only returns the connection state of your Raspberry Pi for any network. 84 | To see if you are connected to a specific network, use **getStatus()**. 85 | 86 | ````javascript 87 | var Wifi = require('rpi-wifi-connection'); 88 | var wifi = new Wifi(); 89 | 90 | wifi.getState().then((connected) => { 91 | if (connected) 92 | console.log('Connected to network.'); 93 | else 94 | console.log('Not connected to network.'); 95 | }) 96 | .catch((error) => { 97 | console.log(error); 98 | }); 99 | ```` 100 | 101 | ### getNetworks() 102 | 103 | Returns a promise containing a list of Wi-Fi networks. 104 | 105 | ````javascript 106 | var Wifi = require('rpi-wifi-connection'); 107 | var wifi = new Wifi(); 108 | 109 | wifi.getNetworks().then((networks) => { 110 | console.log(networks); 111 | }); 112 | 113 | // [ { id: 0, ssid: 'Julia' } ] 114 | ```` 115 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/wifi-connection.js'); 2 | 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rpi-wifi-connection", 3 | "version": "1.0.17", 4 | "description": "Module to connect a Raspberry Pi to Wi-Fi", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/meg768/rpi-wifi-connection.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/meg768/rpi-wifi-connection/issues" 17 | }, 18 | "homepage": "https://github.com/meg768/rpi-wifi-connection#readme", 19 | 20 | "dependencies": { 21 | "sprintf-js": "^1.0.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/wifi-connection.js: -------------------------------------------------------------------------------- 1 | 2 | var sprintf = require('sprintf-js').sprintf; 3 | var Events = require('events'); 4 | var ChildProcess = require('child_process'); 5 | 6 | function debug() { 7 | }; 8 | 9 | function isType(obj, type) { 10 | return Object.prototype.toString.call(obj) == '[object ' + type + ']'; 11 | }; 12 | 13 | function isString(obj) { 14 | return isType(obj, 'String'); 15 | }; 16 | 17 | function isFunction(obj) { 18 | return typeof obj === 'function'; 19 | }; 20 | 21 | 22 | module.exports = class WiFiConnection { 23 | 24 | constructor(options) { 25 | 26 | if (isString(options)) 27 | options = {iface:options}; 28 | 29 | options = Object.assign({iface:'wlan0'}, options); 30 | 31 | if (options.debug) { 32 | if (isFunction(options.debug)) { 33 | debug = options.debug; 34 | } 35 | else { 36 | debug = function() { 37 | console.log.apply(this, arguments); 38 | }; 39 | } 40 | } 41 | 42 | this.iface = options.iface; 43 | } 44 | 45 | wpa_cli(command, pattern) { 46 | 47 | return new Promise((resolve, reject) => { 48 | 49 | ChildProcess.exec(sprintf('wpa_cli -i %s %s', this.iface, command), (error, stdout, stderr) => { 50 | if (error) 51 | reject(error); 52 | else { 53 | var output = stdout.trim(); 54 | 55 | if (pattern) { 56 | var match = output.match(pattern); 57 | 58 | if (match) { 59 | if (match[1]) 60 | resolve(match[1]); 61 | else 62 | resolve(); 63 | } 64 | else { 65 | reject(new Error(sprintf('Could not parse reply from wpa_cli %s: "%s"', command, output))); 66 | 67 | } 68 | 69 | } 70 | else { 71 | resolve(output); 72 | } 73 | } 74 | }); 75 | }); 76 | } 77 | 78 | getState() { 79 | return new Promise((resolve, reject) => { 80 | 81 | this.getStatus().then((status) => { 82 | resolve(isString(status.ip_address)); 83 | }) 84 | 85 | .catch((error) => { 86 | reject(error); 87 | }) 88 | }); 89 | } 90 | 91 | getStatus() { 92 | return new Promise((resolve, reject) => { 93 | 94 | this.wpa_cli('status').then((output) => { 95 | 96 | var match; 97 | var status = {}; 98 | 99 | if ((match = output.match(/[^b]ssid=([^\n]+)/))) { 100 | status.ssid = match[1]; 101 | } 102 | 103 | if ((match = output.match(/ip_address=([^\n]+)/))) { 104 | status.ip_address = match[1]; 105 | } 106 | 107 | resolve(status); 108 | }) 109 | .catch((error) => { 110 | reject(error); 111 | }) 112 | }); 113 | 114 | } 115 | 116 | getNetworks() { 117 | return new Promise((resolve, reject) => { 118 | 119 | this.wpa_cli('list_networks').then((output) => { 120 | 121 | output = output.split('\n'); 122 | 123 | // Remove header 124 | output.splice(0, 1); 125 | 126 | var networks = []; 127 | 128 | output.forEach((line) => { 129 | var params = line.split('\t'); 130 | networks.push({ 131 | id : parseInt(params[0]), 132 | ssid : params[1] 133 | }); 134 | 135 | }); 136 | 137 | resolve(networks); 138 | }) 139 | .catch((error) => { 140 | reject(error); 141 | }) 142 | }); 143 | 144 | } 145 | 146 | connect(options) { 147 | 148 | options = Object.assign({timeout:60000}, options); 149 | 150 | var self = this; 151 | var ssid = options.ssid; 152 | var password = options.psk; 153 | var timeout = options.timeout; 154 | var hidden = options.hidden; 155 | 156 | function delay(ms) { 157 | return new Promise((resolve, reject) => { 158 | setTimeout(resolve, ms); 159 | }); 160 | } 161 | 162 | function addNetwork() { 163 | debug('Adding network...'); 164 | 165 | return new Promise((resolve, reject) => { 166 | self.wpa_cli('add_network', '^([0-9]+)').then((id) => { 167 | resolve(parseInt(id)); 168 | }) 169 | .catch((error) => { 170 | reject(error); 171 | }); 172 | 173 | }); 174 | } 175 | 176 | 177 | function setNetwork(id, name, value) { 178 | debug(sprintf('Setting variable %s=%s for network %d.', name, value, id)); 179 | if (typeof value === 'number') { 180 | return self.wpa_cli(sprintf('set_network %d %s %d', id, name, value), '^OK'); 181 | } else { 182 | return self.wpa_cli(sprintf('set_network %d %s \'"%s"\'', id, name, value), '^OK'); 183 | } 184 | } 185 | 186 | 187 | function selectNetwork(id) { 188 | debug(sprintf('Selecting network %d...', id)); 189 | return self.wpa_cli(sprintf('select_network %s', id), '^OK'); 190 | } 191 | 192 | function reconfigure() { 193 | debug(sprintf('Reconfiguring...')); 194 | return self.wpa_cli(sprintf('reconfigure'), '^OK'); 195 | } 196 | 197 | function saveConfiguration() { 198 | debug(sprintf('Saving configuration...')); 199 | return self.wpa_cli(sprintf('save_config'), '^OK'); 200 | } 201 | 202 | function removeNetwork(id) { 203 | debug(sprintf('Removing network %d...', id)); 204 | self.wpa_cli(sprintf('remove_network %d', id), '^OK'); 205 | } 206 | 207 | function waitForNetworkConnection(timeout, timestamp) { 208 | 209 | if (timestamp == undefined) 210 | timestamp = new Date(); 211 | 212 | return new Promise((resolve, reject) => { 213 | 214 | self.getStatus().then((status) => { 215 | 216 | debug(sprintf('Connection status ssid=%s ip_address=%s ...', status.ssid || '', status.ip_address || '')); 217 | 218 | if (isString(status.ip_address) && status.ssid == ssid) { 219 | return Promise.resolve(); 220 | } 221 | else { 222 | var now = new Date(); 223 | 224 | if (now.getTime() - timestamp.getTime() < timeout) { 225 | return delay(1000).then(() => { 226 | return waitForNetworkConnection(timeout, timestamp); 227 | }) 228 | } 229 | else { 230 | throw new Error('Unable to connect to network.'); 231 | } 232 | } 233 | }) 234 | 235 | .then(() => { 236 | resolve(); 237 | }) 238 | .catch((error) => { 239 | reject(error); 240 | }); 241 | 242 | }); 243 | 244 | } 245 | 246 | 247 | function removeExistingNetworks() { 248 | 249 | debug(sprintf('Removing previous networks with the same ssid "%s"...', ssid)); 250 | 251 | return new Promise((resolve, reject) => { 252 | self.getNetworks().then((networks) => { 253 | 254 | networks = networks.filter((network) => { 255 | return network.ssid == ssid; 256 | }); 257 | 258 | var promise = Promise.resolve(); 259 | 260 | networks.forEach((network) => { 261 | promise = promise.then(() => { 262 | return removeNetwork(network.id); 263 | }); 264 | }); 265 | 266 | promise.then(() => { 267 | resolve(); 268 | }) 269 | .catch((error) => { 270 | reject(error); 271 | }) 272 | }); 273 | }); 274 | 275 | } 276 | 277 | return new Promise((resolve, reject) => { 278 | 279 | var networkID = undefined; 280 | 281 | removeExistingNetworks().then(() => { 282 | return addNetwork(); 283 | }) 284 | .then((id) => { 285 | networkID = id; 286 | return setNetwork(networkID, 'ssid', ssid); 287 | }) 288 | .then(() => { 289 | return (isString(password) ? setNetwork(networkID, 'psk', password) : Promise.resolve()); 290 | }) 291 | .then(() => { 292 | return hidden ? setNetwork(networkID, 'scan_ssid', 1) : Promise.resolve(); 293 | }) 294 | .then(() => { 295 | return selectNetwork(networkID); 296 | }) 297 | .then(() => { 298 | return waitForNetworkConnection(timeout); 299 | }) 300 | .then(() => { 301 | return saveConfiguration(); 302 | }) 303 | .then(() => { 304 | resolve(); 305 | }) 306 | .catch((error) => { 307 | // Undo all changes 308 | reconfigure(); 309 | 310 | reject(error); 311 | }) 312 | }); 313 | 314 | } 315 | 316 | scan() { 317 | 318 | var self = this; 319 | 320 | function scan() { 321 | debug('Scan ssids'); 322 | return self.wpa_cli('scan'); 323 | } 324 | 325 | function scan_results() { 326 | debug('Ssids scan results'); 327 | return self.wpa_cli('scan_results'); 328 | } 329 | 330 | return new Promise((resolve, reject) => { 331 | 332 | scan().then((ssid) => { 333 | return scan_results(); 334 | }) 335 | .then((output) => { 336 | output = output.split('\n'); 337 | output.splice(0, 1); 338 | 339 | var ssids = []; 340 | 341 | output.forEach((line) => { 342 | var params = line.split('\t'); 343 | ssids.push({ 344 | bssid : params[0], 345 | frequency : parseInt(params[1]), 346 | signalLevel : parseInt(params[2]), 347 | ssid : params[4] 348 | }); 349 | 350 | }); 351 | resolve(ssids); 352 | }) 353 | .catch((error) => { 354 | reject(error); 355 | }) 356 | }); 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var Wifi = require('./src/wifi-connection.js'); 2 | var wifi = new Wifi({debug:true}); 3 | 4 | function switchNetworks() { 5 | 6 | Promise.resolve().then(() => { 7 | return wifi.connect({ssid:'Julia', psk:'potatismos'}); 8 | }) 9 | .then(() => { 10 | return wifi.getStatus(); 11 | }) 12 | .then((status) => { 13 | console.log('Current status:', status); 14 | console.log('Switching to another network...') 15 | return wifi.connect({ssid:'Magnus iPhone', psk:'potatismos'}); 16 | }) 17 | .then(() => { 18 | return wifi.getStatus(); 19 | }) 20 | .then((status) => { 21 | console.log('Current status:', status); 22 | }) 23 | .catch((error) => { 24 | console.log(error); 25 | }); 26 | 27 | } 28 | 29 | function switchToInvalidNetwork() { 30 | 31 | Promise.resolve().then(() => { 32 | return wifi.connect({ssid:'Julia', psk:'potatismos'}); 33 | }) 34 | .then(() => { 35 | return wifi.getStatus(); 36 | }) 37 | .then((status) => { 38 | console.log('Current status:', status); 39 | return wifi.connect({ssid:'Magnus iPhone', psk:'wrong-password'}); 40 | }) 41 | .then(() => { 42 | console.log('Should never get here!'); 43 | }) 44 | 45 | .catch((error) => { 46 | console.log(error); 47 | }) 48 | .then(() => { 49 | return wifi.getStatus(); 50 | }) 51 | .then((status) => { 52 | console.log('Current status:', status); 53 | }) 54 | 55 | } 56 | 57 | 58 | switchToInvalidNetwork(); 59 | --------------------------------------------------------------------------------