├── .jshintrc ├── HISTORY.md ├── README.md ├── index.js └── package.json /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": false, 6 | "curly": false, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "maxdepth": 8, 13 | "maxparams": 4, 14 | "asi": true, 15 | "noarg": true, 16 | "quotmark": "single", 17 | "regexp": true, 18 | "undef": true, 19 | "unused": true, 20 | "strict": false, 21 | "trailing": true, 22 | "smarttabs": true, 23 | "laxcomma": true, 24 | "mocha": true, 25 | "node": true, 26 | "-W058": true, 27 | "-W003": true, 28 | "-W030": false, 29 | "-W072": false 30 | } -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | ## [1.2.0] 2 | ### Added 3 | - Readme 4 | - connectTo (A global connection function instead of one per security type) 5 | - connectToId (In case you know already know the networkId) 6 | - connect (Keeping this for backwards compatibility) DEPRECATED? 7 | - connectOpen (Keeping this for backwards compatibility) DEPRECATED? 8 | 9 | - connectEAP (Adding it for consistency...) DEPRECATED? 10 | - disconnect 11 | - detectSupplicant 12 | - interfaceDown: interfaceDown, 13 | - interfaceUp: interfaceUp, 14 | - killSupplicant: disableSupplicant, 15 | - listNetworks (Lists already known networks) 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wifi For Pi 2 | 3 | The purpose of this module is to provide tools to connect to a Wifi 4 | 5 | # Table of Contents 6 | 7 | - [Installation](#installation) - How to install the module 8 | - [API](#api) - Commands available 9 | - [check(ssid, callback)](#checkssid-callback) - State of the network with the specified ssid 10 | - [connectTo(details, callback)](#connecttodetails-callback) - State of the network with the specified ssid 11 | - [connectToId(networkId, callback)](#connecttoidnetworkid-callback) - Connects to a network with the parameters specified (This can connect to open and secure networks including 802.1x) 12 | - [connect(ssid, password, callback)](#connectssid-password-callback) - Connects to a network with the ssid specified using the password provided 13 | - [connectOpen(ssid, callback)](#connectopenssid-callback) - Connects to an open network with the ssid specified 14 | - [connectEAP(ssid, password, callback)](#connecteapssid-password-callback) - Connects to a network with the ssid specified using the password provided 15 | - [disconnect(callback)](#disconnectcallback) - Disconnects from the network on the current interface 16 | - [detectSupplicant(callback)](#detectsupplicantcallback) - Looks for a running wpa_supplicant process and if so returns the config file and interface used 17 | - [interfaceDown(callback)](#interfacedowncallback) - Drops the interface provided 18 | - [interfaceUp(callback)](#interfaceupcallback) - Raises the interface provided 19 | - [killSupplicant(callback)](#killsupplicantcallback) - Kills the supplicant process for the specified interface 20 | - [listInterfaces(callback)](#listinterfacescallback) - List the available interface names in an array 21 | - [listNetworks(callback)](#listnetworkscallback) - List the networks in an array, each network has Network ID, SSID, BSSID and FLAGS 22 | - [restartInterface(callback)](#restartinterfacecallback) - Restarts the interface provided 23 | - [scan(callback)](#scancallback) - Scan available wifi networks 24 | - [setCurrentInterface(iface)](#setcurrentinterfaceiface) - Specify the interface to use 25 | - [startSupplicant(callback)](#startsupplicantcallback) - Starts a wpa_supplicant instance 26 | - [status(iface, callback)](#statusiface-callback) - Show status parameters of the interface specified, if no interface is provided the selected one is used 27 | - [Notes](#notes) 28 | 29 | 30 | # Installation 31 | 32 | On your project root execute: 33 | 34 | ``` shell 35 | $ npm install --save pi-wifi 36 | ``` 37 | 38 | # API 39 | 40 | The following is the list of functions available: 41 | 42 | 43 | ## check(ssid, callback) 44 | The **check** function is used to return the state of the network with the specified ssid 45 | 46 | ``` javascript 47 | var piWifi = require('pi-wifi'); 48 | 49 | piWifi.check('myTestNetwork', function(err, result) { 50 | if (err) { 51 | return console.error(err.message); 52 | } 53 | console.log(result); 54 | }); 55 | 56 | // => If everything is working 57 | // { selected: true, connected: true, ip: '192.168.0.12' } 58 | // => Alternatively 59 | // { selected: false, connected: false } 60 | ``` 61 | 62 | ## connectTo(details, callback) 63 | The **connectTo** function is used to connect to a network with the parameters specified (This can connect to open and secure networks including 802.1x) 64 | 65 | The **details** object can contain the following: 66 | 67 | - ***key_mgmt*** You can specify the type of security to use. (Optional) 68 | - ***ssid*** (required for secure and enterprise networks) 69 | - ***username*** (required for enterprise networks) 70 | - ***password*** (required for enterprise networks) 71 | - ***eap*** 72 | - ***phase1*** 73 | - ***phase2*** 74 | 75 | ``` javascript 76 | var piWifi = require('pi-wifi'); 77 | 78 | var networkDetails = { 79 | ssid: 'MyNetwork', 80 | username: 'demo', 81 | password: 'swordfish' 82 | }; 83 | 84 | //A simple connection 85 | piWifi.connectTo(networkDetails, function(err) { 86 | if(!err) { 87 | console.log('Network created successfully!'); 88 | } else { 89 | console.log(err.message); //Failed to connect 90 | } 91 | }); 92 | 93 | //After creating a succesful connection, you could use the function check to verify 94 | var ssid = 'MyOpenNetwork'; 95 | piWifi.connectTo({ssid: ssid}}, function(err) { 96 | if (!err) { //Network created correctly 97 | setTimeout(function () { 98 | piwifi.check(ssid, function (err, status) { 99 | if (!err && status.connected) { 100 | console.log('Connected to the network ' + ssid + '!'); 101 | } else { 102 | console.log('Unable to connect to the network ' + ssid + '!'); 103 | } 104 | }); 105 | }, 2000); 106 | } else { 107 | console.log('Unable to create the network ' + ssid + '.'); 108 | } 109 | }); 110 | 111 | ``` 112 | 113 | ## connectToId(networkId, callback) 114 | The **connectToId** function is used enables the network, saves the configuration and then select the network with the id provided 115 | 116 | ``` javascript 117 | var piWifi = require('pi-wifi'); 118 | 119 | piWifi.connectToId(2, function(err) { 120 | if (err) { 121 | return console.error(err.message); 122 | } 123 | console.log('Successful connection!'); 124 | }); 125 | ``` 126 | 127 | ## connect(ssid, password, callback) 128 | The **connect** function is used to connect to a network with the ssid specified using the password provided 129 | ***(Avoid using this as it is only for backwards compatibility and may be deprecated in the future)*** 130 | 131 | ``` javascript 132 | var piWifi = require('pi-wifi'); 133 | 134 | piWifi.connect('myTestNetwork', 'MyTestPassword', function(err) { 135 | if (err) { 136 | return console.error(err.message); 137 | } 138 | console.log('Successful connection!'); 139 | }); 140 | ``` 141 | 142 | ## connectOpen(ssid, callback) 143 | The **connectOpen** function is used to connect to an open network with the ssid specified 144 | ***(Avoid using this as it is only for backwards compatibility and may be deprecated in the future)*** 145 | 146 | ``` javascript 147 | var piWifi = require('pi-wifi'); 148 | 149 | piWifi.connectOpen('myTestNetwork', function(err) { 150 | if (err) { 151 | return console.error(err.message); 152 | } 153 | console.log('Successful connection!'); 154 | }); 155 | ``` 156 | 157 | 158 | ## connectEAP(ssid, password, callback) 159 | The **connectEAP** function is used to connect to a network with the ssid specified using the password provided 160 | ***(Avoid using this as it is only here for consistency and may be deprecated in the future)*** 161 | 162 | ``` javascript 163 | var piWifi = require('pi-wifi'); 164 | 165 | piWifi.connectEAP('myTestNetwork', 'MyTestUsername', 'MyTestPassword', function(err) { 166 | if (err) { 167 | return console.error(err.message); 168 | } 169 | console.log('Successful connection!'); 170 | }); 171 | ``` 172 | 173 | ## disconnect(callback) 174 | The **disconnect** function is used to disconnect from the network on the current interface 175 | ``` javascript 176 | var piWifi = require('pi-wifi'); 177 | 178 | piWifi.disconnect(function(err) { 179 | if (err) { 180 | return console.error(err.message); 181 | } 182 | console.log('Disconnected from network!'); 183 | }); 184 | ``` 185 | 186 | ## detectSupplicant(callback) 187 | The **detectSupplicant** function is used to look for a running wpa_supplicant process and if found returns the config file and interface used 188 | 189 | ``` javascript 190 | var piWifi = require('pi-wifi'); 191 | 192 | piWifi.detectSupplicant(function(err, iface, configFile) { 193 | if (err) { 194 | return console.error(err.message); 195 | } 196 | console.log('Supplicant running in interface', iface, 'using the configuration file', configFile); 197 | }); 198 | ``` 199 | 200 | ## interfaceDown(callback) 201 | The **interfaceDown** function is used to drop the interface provided 202 | 203 | ``` javascript 204 | var piWifi = require('pi-wifi'); 205 | 206 | piWifi.interfaceDown('wlan0', function(err) { 207 | if (err) { 208 | return console.error(err.message); 209 | } 210 | console.log('Interface dropped succesfully!'); 211 | }); 212 | ``` 213 | 214 | ## interfaceUp(callback) 215 | The **interfaceUp** function is used to raise the interface provided 216 | 217 | ``` javascript 218 | var piWifi = require('pi-wifi'); 219 | 220 | piWifi.interfaceUp('wlan0', function(err) { 221 | if (err) { 222 | return console.error(err.message); 223 | } 224 | console.log('Interface raised succesfully!'); 225 | }); 226 | ``` 227 | 228 | ## killSupplicant(callback) 229 | The **killSupplicant** function is used to kill the supplicant process for the specified interface 230 | ``` javascript 231 | var piWifi = require('pi-wifi'); 232 | 233 | piWifi.killSupplicant('wlan0', function(err) { 234 | if (err) { 235 | return console.error(err.message); 236 | } 237 | console.log('Supplicant process terminated!'); 238 | }); 239 | ``` 240 | 241 | ## listInterfaces(callback) 242 | The **listInterfaces** function is used to list the available network interfaces' names 243 | 244 | ``` javascript 245 | var piWifi = require('pi-wifi'); 246 | 247 | piWifi.listInterfaces(function(err, interfacesArray) { 248 | if (err) { 249 | return console.error(err.message); 250 | } 251 | console.log(interfacesArray); 252 | }); 253 | 254 | // => 255 | // ['wlan0', 'wlan1'] 256 | ``` 257 | 258 | ## listNetworks(callback) 259 | The **listNetworks** function is used to list the networks in an array, each network has Network ID, SSID, BSSID and FLAGS 260 | 261 | ``` javascript 262 | var piWifi = require('pi-wifi'); 263 | 264 | piWifi.listNetworks(function(err, networksArray) { 265 | if (err) { 266 | return console.error(err.message); 267 | } 268 | console.log(networksArray); 269 | }); 270 | 271 | // => 272 | // [{ network_id: 0, ssid: 'MyNetwork', bssid: 'any', flags: '[DISABLED]' }, 273 | // { network_id: 1, ssid: 'Skynet', bssid: 'any', flags: '[CURRENT]' }] 274 | ``` 275 | 276 | ## restartInterface(callback) 277 | The **restartInterface** function is used to restart the interface provided 278 | 279 | ``` javascript 280 | var piWifi = require('pi-wifi'); 281 | 282 | piWifi.restartInterface('wlan0', function(err) { 283 | if (err) { 284 | return console.error(err.message); 285 | } 286 | console.log('Interface restarted succesfully!'); 287 | }); 288 | ``` 289 | 290 | ## scan(callback) 291 | The **scan** function is used to scan available wifi networks 292 | 293 | ``` javascript 294 | var piWifi = require('pi-wifi'); 295 | 296 | piWifi.scan(function(err, networks) { 297 | if (err) { 298 | return console.error(err.message); 299 | } 300 | console.log(networks); 301 | }); 302 | 303 | // => 304 | //[ 305 | // { bssid: 'aa:bb:cc:dd:ee:ff', 306 | // frequency: 2462, 307 | // signalLevel: -40, 308 | // flags: '[WPA2-PSK-CCMP][WPS][ESS]', 309 | // ssid: 'MyNetwork' }, 310 | // { bssid: '11:22:33:44:55:66', 311 | // frequency: 2462, 312 | // signalLevel: -28, 313 | // flags: '[WPA2-PSK-CCMP][ESS]', 314 | // ssid: 'AnotherNetwork' }, 315 | // { bssid: 'aa:11:bb:22:cc:33', 316 | // frequency: 2462, 317 | // signalLevel: -33, 318 | // flags: '[WPA2-EAP-CCMP-preauth][WPS][ESS]', 319 | // ssid: 'MyEnterpriseNetwork' }, 320 | // { bssid: 'c0:56:27:44:3b:9c', 321 | // frequency: 2412, 322 | // signalLevel: -59, 323 | // flags: '[WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][ESS]', 324 | // ssid: 'MyGuestsNetwork' 325 | // } 326 | //] 327 | ``` 328 | 329 | ## setCurrentInterface(iface) 330 | The **setCurrentInterface** function is used to specify the interface to use as default 331 | 332 | ``` javascript 333 | var piWifi = require('pi-wifi'); 334 | piWifi.setCurrentInterface('wlan1'); 335 | 336 | piWifi.status('wlan0', function(err, status) { 337 | if (err) { 338 | return console.error(err.message); 339 | } 340 | console.log(status); 341 | }); 342 | 343 | ``` 344 | 345 | ## startSupplicant(callback) 346 | The **startSupplicant** function is used to start a wpa_supplicant instance 347 | 348 | ``` javascript 349 | var piWifi = require('pi-wifi'); 350 | 351 | piWifi.startSupplicant({iface: 'wlan0', config: '/etc/wpa_supplicant/wpa_supplicant.conf', dns: '/etc/resolv.conf'}, function(err, networks) { 352 | if (err) { 353 | return console.error(err.message); 354 | } 355 | console.log('Supplicant instance successfully started!'); 356 | }); 357 | 358 | ``` 359 | 360 | ## status(iface, callback) 361 | The **status** function is used to show status parameters of the interface specified, if no interface is provided the selected one is used 362 | 363 | ``` javascript 364 | var piWifi = require('pi-wifi'); 365 | 366 | piWifi.status('wlan0', function(err, status) { 367 | if (err) { 368 | return console.error(err.message); 369 | } 370 | console.log(status); 371 | }); 372 | 373 | // => 374 | //{ 375 | // bssid: '2c:f5:d3:02:ea:d9', 376 | // frequency: 2412, 377 | // mode: 'station', 378 | // key_mgmt: 'wpa2-psk', 379 | // ssid: 'MyNetwork', 380 | // pairwise_cipher: 'CCMP', 381 | // group_cipher: 'CCMP', 382 | // p2p_device_address: 'aa:bb:cc:dd:ee:ff', 383 | // wpa_state: 'COMPLETED', 384 | // ip: '10.20.30.40', 385 | // mac: 'a1:b2:c3:d4:e5:f6', 386 | // uuid: 'e1cda789-8c88-53e8-ffff-31c304580c22', 387 | // id: 0 388 | //} 389 | ``` 390 | 391 | ## Notes 392 | Tested on Raspberry Pi 3 393 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* Intended to be run ON the Raspberry Pi */ 2 | 3 | var tools = require('wireless-tools'); 4 | var async = require('async'); 5 | 6 | const defaultOpenKey = 'NONE'; 7 | const defaultEnterpriseKey = 'WPA-EAP'; 8 | const defaultSupplicantConfigFile = '/etc/wpa_supplicant/wpa_supplicant.conf'; 9 | const defaultDNSFile = '/etc/resolv.conf'; 10 | const defaultInterface = 'wlan0'; 11 | 12 | var currentInterface = defaultInterface; 13 | 14 | const commands = { 15 | detectSupplicant: 'ps -fea | grep -v grep | grep wpa_supplicant', 16 | interfaceDown: 'ifdown --force :INTERFACE', 17 | interfaceUp: 'ifup :INTERFACE', 18 | scan: 'sudo iwlist wlan0 scan | grep ESSID | cut \'"\' -f2', 19 | startSupplicant: 'sudo wpa_supplicant -Dwext -c :CONFIG_FILE -B -i :INTERFACE && sudo chattr -i :DNS_FILE', 20 | wpaDisconnect: 'wpa_cli disconnect', 21 | wpaListInterfaces: 'wpa_cli interface', 22 | wpaInterface: 'wpa_cli interface :INTERFACE', 23 | wpaList: 'wpa_cli list_networks' 24 | }; 25 | 26 | 27 | var exec = require('child_process').exec; 28 | 29 | /** 30 | * @method scan 31 | * @description scan available wifi networks 32 | * @param {Function} callback 33 | * @return {Array[Object]} 34 | */ 35 | function scan(callback) { 36 | tools.wpa.scan(currentInterface, function (err, data) { 37 | if (err || !data.hasOwnProperty('result') || data.result !== 'OK') return callback(err, data); 38 | setTimeout(function () { 39 | //console.log('Scan results:'); 40 | tools.wpa.scan_results(currentInterface, function (err, networks) { 41 | callback(err, networks); 42 | }); 43 | }, 1000); 44 | }); 45 | } 46 | 47 | 48 | /** 49 | * @method findConnection 50 | * @description Search a network on the known networks list and return it's network id, fails if not found 51 | * @param {string} ssid Network ssid 52 | * @param {Function(err, networkId)} callback Returns error if the network isn't found, id of the network found 53 | */ 54 | function findConnection(ssid, callback) { 55 | 56 | var networkId; 57 | //console.log('Listing...'); 58 | listNetworks(function (err, networksArray) { 59 | if (!err) { 60 | //console.log('Looking for the network...'); 61 | for (var i = 0; i < networksArray.length; i++) { 62 | if (networksArray[i].ssid === ssid) { 63 | //console.log('Network ' + networksArray[i].network_id + ' found'); 64 | return callback(err, networksArray[i].network_id); 65 | } 66 | } 67 | } 68 | //console.log('Network not found...'); 69 | return callback(err, networkId); 70 | }); 71 | } 72 | 73 | 74 | /** 75 | * @method setCurrentInterface 76 | * @description Specify the interface to use 77 | * @param {string} iface Intertface to set 78 | * @param {function} callback Returns error if unable to set the interface 79 | */ 80 | function setCurrentInterface(iface, callback) { 81 | exec(replaceInCommand(commands.wpaInterface, { interface: iface }), function (err) { 82 | if (!err) currentInterface = iface; 83 | return callback(err); 84 | }); 85 | } 86 | 87 | /** 88 | * @method checkConnectionDetails 89 | * @description Verifies the parameters details and if it isn't properly formed it will return an error, undefined otherwise 90 | * @param {Object} params Json object with the network parameters 91 | * @param {Function(err)} callback Returns error if the connection isn't successful 92 | */ 93 | function checkConnectionDetails(/*params*/) { 94 | var err; 95 | //TODO implement this verification (e.g.: If a secure network is missing a password error out) 96 | return err; 97 | } 98 | 99 | /** 100 | * @method prepareConnectionDetails 101 | * @param {Object} details 102 | * @return {Object} Returns an adjusted json object with the proper format and default parameters required 103 | */ 104 | function prepareConnectionDetails(details) { 105 | 106 | var params = {}; 107 | if (details.hasOwnProperty('ssid')) params.ssid = '\'"' + details.ssid + '"\''; //Add ssid 108 | 109 | if (!details.hasOwnProperty('password')) { 110 | if (!details.hasOwnProperty('key_mgmt')) params.key_mgmt = defaultOpenKey; //Set key_mgmt for an open network 111 | } else if (!details.hasOwnProperty('username')) { //If no user then it is a regular secured network 112 | params.psk = '\'"' + details.password + '"\''; 113 | } else { //If it has password and user it must be an enterprise network 114 | params.key_mgmt = defaultEnterpriseKey; //Set key_mgmt for default enterprise network (802.1x) 115 | params.identity = '\'"' + details.username + '"\''; 116 | params.password = '\'"' + details.password + '"\''; 117 | if (!details.hasOwnProperty('eap')) params.eap = 'PEAP'; 118 | //if (!details.hasOwnProperty('phase2')) params.phase2 = '\'"autheap=MSCHAPV2"\''; 119 | } 120 | 121 | if (details.hasOwnProperty('eap')) params.eap = details.eap; 122 | if (details.hasOwnProperty('phase1')) params.phase1 = details.phase2; 123 | if (details.hasOwnProperty('phase2')) params.phase2 = details.phase2; 124 | if (details.hasOwnProperty('key_mgmt')) params.key_mgmt = details.key_mgmt; //Add Key management if found 125 | 126 | return params; 127 | } 128 | 129 | /** 130 | * @method connection 131 | * @description Connects to a network with the parameters specified (This can connect to open and secure networks including EAP 802.1x) 132 | * @param {Object} details Network details 133 | * - {string} key_mgmt You can specify the type of security to use. (Optional) 134 | * - {string} ssid (Optional, required for secure and enterprise networks) 135 | * - {string} username (Optional, required for enterprise networks) 136 | * - {string} password (Optional, required for secure or enterprise networks) 137 | * @param {Function(err)} callback Returns error if the network creation isn't successful 138 | */ 139 | function connection(details, callback) { 140 | var netId; 141 | 142 | var err = checkConnectionDetails(details); 143 | if (err) return callback(err); 144 | 145 | var params = prepareConnectionDetails(details); 146 | 147 | findConnection(details.ssid, function (err, networkId) { //Look for an existing connection with that SSID 148 | async.series({ 149 | remove: function(next) { //Remove the network if found 150 | if (networkId === undefined) return next(undefined); 151 | tools.wpa.remove_network(currentInterface, networkId, next); 152 | }, 153 | create: function(next) { //Create a new network 154 | createConnection(function (err, networkId) { 155 | if (!err) netId = networkId; 156 | //console.log('Created network:', netId, err); 157 | next(err); 158 | }); 159 | }, 160 | setup: function (next) { //Add provided parameters to network 161 | setupConnection(netId, params, next); 162 | }, //Can't use async.apply due to netId being shared 163 | start: function(next) { connectToId(netId, next); } //Actually connect to the network 164 | }, function (err, results) { 165 | if (err && results.create) tools.wpa.remove_network(currentInterface, netId, callback); 166 | else callback(err); 167 | }); 168 | }); 169 | } 170 | 171 | 172 | /** 173 | * @method openConnection 174 | * @description Connects to an open network with the ssid specified 175 | * @param {string} ssid Network ssid 176 | * @param {Function(err)} callback Returns error if the connection isn't successful 177 | */ 178 | function openConnection(ssid, callback) { 179 | 180 | var params = { 181 | ssid: ssid 182 | }; 183 | connection(params, callback); 184 | } 185 | 186 | 187 | /** 188 | * @method enterpriseConnection 189 | * @description Connects to a network with the ssid specified using the password provided 190 | * @param {string} ssid Network ssid 191 | * @param {string} username User/identity to use on authentication 192 | * @param {string} password Password to use on authentication 193 | * @param {Function(err)} callback Returns error if the connection isn't successful 194 | */ 195 | function enterpriseConnection(ssid, username, password, callback) { 196 | 197 | var params = { 198 | ssid: ssid, 199 | username: username, 200 | password: password 201 | }; 202 | connection(params, callback); 203 | } 204 | 205 | 206 | /** 207 | * @method secureConnection 208 | * @description Connects to a network with the ssid specified using the password provided 209 | * @param {string} ssid Network ssid 210 | * @param {string} ssid Network psk 211 | * @param {Function(err)} callback Returns error if the connection isn't successful 212 | */ 213 | function secureConnection(ssid, password, callback) { 214 | 215 | var params = { 216 | ssid: ssid, 217 | password: password 218 | }; 219 | connection(params, callback); 220 | 221 | } 222 | 223 | 224 | /** 225 | * @method createConnection 226 | * @description Creates a connection record and returns its network id if successful 227 | * @param {Function(err, networkId)} callback Returns error if the network creation fails, Network id 228 | */ 229 | function createConnection(callback) { 230 | tools.wpa.add_network(currentInterface, function (err, netId) { 231 | if (!err && netId.hasOwnProperty('result')) netId = netId.result; 232 | callback(err, netId); 233 | }); 234 | } 235 | 236 | 237 | /** 238 | * @method setupConnection 239 | * @description Sets the parameters for a network 240 | * @param {Integer} networkId Network id 241 | * @param {Object} params Json object with network parameters to set (Name, Value) 242 | * @param {Function(err)} callback Returns error if the process fails 243 | */ 244 | function setupConnection(netId, params, callback) { 245 | async.eachOf(params, function (value, key, next) { //For each one of the parameters listed 246 | console.log('>>> Setting', key, 'with', value); 247 | setNetworkParameter(currentInterface, netId, key, value, next); //Set it to the network 248 | }, callback); 249 | } 250 | 251 | 252 | /** 253 | * @method setNetworkParameter 254 | * @description Sets a network parameter 255 | * @param {string} interface Interface to use 256 | * @param {integer} networkId Network to set it to 257 | * @param {string} name Parameter key/name 258 | * @param {string} value Parameter value 259 | * @param {function} callback Returns an error if the parameter wasn't set 260 | */ 261 | function setNetworkParameter(interface, networkId, name, value, callback) { 262 | tools.wpa.set_network(interface, networkId, name, value, callback); 263 | } 264 | 265 | 266 | /** 267 | * @method status 268 | * @description Show status parameters of the interface specified, if no interface is provided the selected one is used 269 | * @param {string} iface Interface to get status from. (If not provided it defaults to the currently selected one) 270 | * @param {Function(err)} callback Returns error if the process fails, status JSON object with the interface status 271 | * @example 272 | * 273 | * status('wlan0', function(err, status){ 274 | * if(!err) console.log(status); 275 | * }); 276 | * // => 277 | * { 278 | * bssid: '2c:f5:d3:02:ea:d9', 279 | * frequency: 2412, 280 | * mode: 'station', 281 | * key_mgmt: 'wpa2-psk', 282 | * ssid: 'Fake-Wifi', 283 | * pairwise_cipher: 'CCMP', 284 | * group_cipher: 'CCMP', 285 | * p2p_device_address: 'e4:28:9c:a8:53:72', 286 | * wpa_state: 'COMPLETED', 287 | * ip: '10.34.141.168', 288 | * mac: 'e4:28:9c:a8:53:72', 289 | * uuid: 'e1cda789-8c88-53e8-ffff-31c304580c1e', 290 | * id: 0 291 | * } 292 | */ 293 | function status(iface, cb) { 294 | if (cb === undefined) { 295 | cb = iface; 296 | iface = currentInterface; 297 | } 298 | tools.wpa.status(iface, cb); 299 | } 300 | 301 | 302 | /** 303 | * @method checkConnection 304 | * @description Returns the state of the network with the specified ssid 305 | * @param {string} ssid Network ssid 306 | * @param {Function(err, result)} Error if unable to get network status, Object with connection details 307 | * { 308 | * selected: true | false, 309 | * connected: true | false, 310 | * ip: 192.168.0.2 311 | * } 312 | */ 313 | function checkConnection(ssid, cb) { 314 | var result; 315 | status(function (err, status) { 316 | if (!err) { 317 | result = { selected: false, connected: false }; 318 | if (status.hasOwnProperty('ssid') && status.hasOwnProperty('wpa_state')) { 319 | result.selected = (status.ssid === ssid); 320 | if (result.selected) result.connected = (status.wpa_state === 'COMPLETED'); 321 | if (result.connected && status.ip) result.ip = status.ip; 322 | } else { 323 | err = new Error('Incomplete status object'); 324 | } 325 | } 326 | cb(err, result); 327 | }); 328 | } 329 | 330 | 331 | /** 332 | * @method connectToId 333 | * @description Enables a network, saves the configuration and selects the network with the id provided 334 | * @param {Integer} networkId Network id 335 | * @param {Function(err)} callback Returns error if the process fails 336 | */ 337 | function connectToId(networkId, callback) { 338 | //console.log('Enabling network ' + networkId + '...'); 339 | tools.wpa.enable_network(currentInterface, networkId, function (err, data) { 340 | if (err || !data.hasOwnProperty('result') || data.result !== 'OK') return callback(err); 341 | //console.log('Saving config...'); 342 | tools.wpa.save_config(currentInterface, function (err) { 343 | if (err || !data.hasOwnProperty('result') || data.result !== 'OK') return callback(err); 344 | //console.log('Selecting network...'); 345 | tools.wpa.select_network(currentInterface, networkId, function (err, data) { 346 | if (err || !data.hasOwnProperty('result') || data.result !== 'OK') return callback(err); 347 | callback(); 348 | }); 349 | }); 350 | }); 351 | } 352 | 353 | 354 | /** 355 | * @method detectSupplicant 356 | * @description Looks for a running wpa_supplicant process and if so returns the config file and interface used 357 | * @param {function} callback (err, iface, configFile) Error if the process failed or no supplicant is running, Interface used, Config file used 358 | */ 359 | function detectSupplicant(callback) { 360 | exec(commands.detectSupplicant, function (err, stdout) { 361 | var iface, configFile; 362 | var result = false; 363 | if (!err) { 364 | var lines = stdout.split('\n'); 365 | for (var i = 0; i < lines.length; i++) { 366 | if (lines[i].indexOf('wlan') > -1) { 367 | var options = lines[i].split(' '); 368 | if (options.indexOf('-i') > -1) iface = options[options.indexOf('-i') + 1]; 369 | if (options.indexOf('-c') > -1) configFile = options[options.indexOf('-c') + 1]; 370 | result = true; 371 | } 372 | } 373 | } 374 | callback(err, iface, configFile); 375 | }); 376 | } 377 | 378 | 379 | /** 380 | * @method startSupplicant 381 | * @description Starts a wpa_supplicant instance 382 | * @param {object} options Json object where the interface, config file and dns file to use can be specified, otherwhise default values will be selected 383 | * - iface: Interface to use. Defaults to the currently selected one 384 | * - config: Configuration file for the supplicant file. Defaults to /etc/wpa_supplicant/wpa_supplicant.conf 385 | * - dns: DNS file to use. Defaults to /etc/resolv.conf 386 | * @param {function} callback (err) Error if the process fails 387 | */ 388 | function startSupplicant(options, callback) { 389 | if (callback === undefined) callback = options; //If no options is passed and just the callback is provided 390 | 391 | var iface = options.hasOwnProperty('iface') ? options.iface : currentInterface; 392 | var configFile = options.hasOwnProperty('config') ? options.config : defaultSupplicantConfigFile; 393 | var dnsFile = options.hasOwnProperty('dns') ? options.dns : defaultDNSFile; 394 | exec(replaceInCommand(commands.startSupplicant, { config_file: configFile, interface: iface, dns_file: dnsFile }), callback); 395 | } 396 | 397 | 398 | /** 399 | * @method listInterfaces 400 | * @description Returns an array of available interface names 401 | * @param {function} callback (err, interfaces) Returns the error or an array of interface names 402 | */ 403 | function listInterfaces(callback) { 404 | exec(commands.wpaListInterfaces, function (err, stdout) { 405 | if (err) callback(err, null) // send back errors from the process 406 | 407 | if (stdout) { 408 | const lines = stdout.split('\n') 409 | const interfaces = [] // to hold the interface names 410 | const marker = 'available interfaces' // available interfaces appear one-per-line after this marker 411 | var markerSeen = false // whether we've seen the marker yet 412 | 413 | for (var i = 0; i < lines.length; i++) { 414 | var line = lines[i] 415 | 416 | // if we've passed the marker line & the current line contains an interface name, add it to the 417 | // list to be returned 418 | if (markerSeen && line.length) { 419 | interfaces.push(line.trim()) 420 | } 421 | 422 | // if the current line is like the marker, record that we've seen it (for future loop iterations) 423 | if (line.toLowerCase().substr(0, marker.length) === marker) { 424 | markerSeen = true 425 | } 426 | } 427 | 428 | callback(null, interfaces) 429 | } 430 | }) 431 | } 432 | 433 | /** 434 | * @method interfaceUp 435 | * @description Raises the interface provided 436 | * @param {string} iface Interface to start 437 | * @param {function} callback (err) Returns an error if the process fails 438 | */ 439 | function interfaceUp(iface, callback) { 440 | iface = iface || currentInterface; 441 | exec(replaceInCommand(commands.interfaceUp, { interface: iface }), function (err) { 442 | callback(err); 443 | }); 444 | } 445 | 446 | 447 | /** 448 | * @method interfaceDown 449 | * @description Drops the interface provided 450 | * @param {string} iface Interface to stop 451 | * @param {function} callback (err) Returns an error if the process fails 452 | */ 453 | function interfaceDown(iface, callback) { 454 | iface = iface || currentInterface; 455 | exec(replaceInCommand(commands.interfaceDown, { interface: iface }), function (err) { 456 | callback(err); 457 | }); 458 | } 459 | 460 | /** 461 | * @method restartInterface 462 | * @description Restarts the interface provided 463 | * @param {string} iface Interface to restart 464 | * @param {function} callback (err) Returns an error if the process fails 465 | */ 466 | function restartInterface(iface, callback) { 467 | async.series([ 468 | async.apply(interfaceDown, iface), 469 | async.apply(interfaceUp, iface), 470 | ], callback); 471 | } 472 | 473 | 474 | /** 475 | * @method listNetworks 476 | * @description List the networks in an array, each network has Network ID, SSID, BSSID and FLAGS 477 | * @param {function} callback (err, networksArray) returns err if the process fails, each network is a Json object that contains network_id, ssid, bssid and flags 478 | */ 479 | function listNetworks(callback) { 480 | exec(commands.wpaList, function (err, stdout) { 481 | var tempNetworkJson, parameters, networksArray; 482 | 483 | if (!err) { 484 | var networksList = stdout.split('\n'); 485 | networksArray = []; 486 | networksList.splice(0, 2); //Remove headers 487 | networksList.splice(networksList.length - 1, 1); //Remove footer 488 | 489 | for (var networkIndex in networksList) { 490 | tempNetworkJson = {}; 491 | 492 | parameters = networksList[networkIndex].split('\t'); 493 | tempNetworkJson = { 494 | network_id: parameters[0], 495 | ssid: parameters[1], 496 | bssid: parameters[2], 497 | flags: parameters[3], 498 | }; 499 | networksArray.push(tempNetworkJson); 500 | } 501 | } 502 | 503 | callback(err, networksArray); 504 | }); 505 | } 506 | 507 | /** 508 | * @method disconnect 509 | * @description Disconnects from the network on the current interface 510 | * @param {function} callback (err) returns err if the process fails 511 | */ 512 | function disconnect(callback) { 513 | exec(commands.wpaDisconnect, callback); 514 | } 515 | 516 | 517 | /** 518 | * @method disableSupplicant 519 | * @description Kills the supplicant process for the specified interface 520 | * @param {string} iface Interface used by supplicant (If not iface is supplied the current one will be used) 521 | * @param {function} callback (err) returns err if unable to kill the process 522 | */ 523 | function disableSupplicant(iface, callback) { 524 | if (callback === undefined) { 525 | callback = iface; //If no options is passed and just the callback is provided 526 | iface = currentInterface; 527 | } 528 | tools.wpa_supplicant.disable(iface, callback); 529 | } 530 | 531 | /** 532 | * @method replaceInCommand 533 | * @description Used to replace preset variables strings (e.g.: This is a :VAR text) 534 | * @param {string} text Text containg the variables to be replaced 535 | * @param {Object} toReplace Json object that contains the string to find (key) and the replace string (value) 536 | * @return Returns the text after replacing the variables 537 | */ 538 | function replaceInCommand(text, toReplace) { 539 | for (var placeHolder in toReplace) { 540 | text = text.replace(new RegExp(':' + placeHolder.toUpperCase(), 'g'), toReplace[placeHolder]); 541 | } 542 | return text; 543 | } 544 | 545 | module.exports = { 546 | check: checkConnection, 547 | connectTo: connection, 548 | connectToId: connectToId, 549 | connect: secureConnection, 550 | connectOpen: openConnection, 551 | connectEAP: enterpriseConnection, 552 | disconnect: disconnect, 553 | detectSupplicant: detectSupplicant, 554 | interfaceDown: interfaceDown, 555 | interfaceUp: interfaceUp, 556 | killSupplicant: disableSupplicant, 557 | listInterfaces: listInterfaces, 558 | listNetworks: listNetworks, 559 | restartInterface: restartInterface, 560 | scan: scan, 561 | setCurrentInterface: setCurrentInterface, 562 | startSupplicant: startSupplicant, 563 | status: status 564 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pi-wifi", 3 | "version": "1.2.0", 4 | "description": "Module for accessing wpa_cli on Raspberry Pi 3.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git@github.com/matrix-io/pi-wifi" 12 | }, 13 | "keywords": [ 14 | "raspberry", 15 | "pi", 16 | "wifi" 17 | ], 18 | "author": { 19 | "name": "Sean Canton", 20 | "email": "sean.canton@admobilize.com" 21 | }, 22 | "license": "ISC", 23 | "_id": "pi-wifi@1.0.1", 24 | "_shasum": "953a178e80cab14f5168f1cf7c2c78053744e9f1", 25 | "_from": "pi-wifi@>=1.0.1 <2.0.0", 26 | "_npmVersion": "3.10.3", 27 | "_nodeVersion": "6.7.0", 28 | "_npmUser": { 29 | "name": "matrix-io", 30 | "email": "appdev@admobilize.com" 31 | }, 32 | "dist": { 33 | "shasum": "953a178e80cab14f5168f1cf7c2c78053744e9f1", 34 | "tarball": "https://registry.npmjs.org/pi-wifi/-/pi-wifi-1.0.1.tgz" 35 | }, 36 | "maintainers": [ 37 | { 38 | "name": "matrix-io", 39 | "email": "appdev@admobilize.com" 40 | } 41 | ], 42 | "_npmOperationalInternal": { 43 | "host": "packages-12-west.internal.npmjs.com", 44 | "tmp": "tmp/pi-wifi-1.0.1.tgz_1482261858348_0.6249220559839159" 45 | }, 46 | "directories": {}, 47 | "_resolved": "https://registry.npmjs.org/pi-wifi/-/pi-wifi-1.0.1.tgz", 48 | "dependencies": { 49 | "async": "^2.1.5", 50 | "wireless-tools": "^0.19.0" 51 | } 52 | } 53 | --------------------------------------------------------------------------------