]> code.delx.au - monosys/blob - bin/wifi-scan
notes: fix raspi install notes, also @home -> @username
[monosys] / bin / wifi-scan
1 #!/usr/bin/env node
2 'use strict'
3
4 const {exec} = require('child_process');
5 const fs = require('fs').promises;
6
7 function execAsync(command, opts) {
8 return new Promise((resolve, reject) => {
9 exec(command, opts, (error, stdout, stderr) => {
10 if (error) {
11 reject(error);
12 } else {
13 resolve({stdout, stderr});
14 }
15 });
16 });
17 }
18
19 function sleep(n) {
20 return new Promise((resolve) => setTimeout(resolve, n));
21 }
22
23 async function findInterface() {
24 const {stdout} = await execAsync('iw dev');
25 const lines = stdout.split('\n')
26 .map((line) => line.trim())
27 .filter((line) => line.startsWith('Interface '))
28 .map((line) => line.split(' ')[1]);
29 return lines[0];
30 }
31
32 async function scanInterface(iface) {
33 const {stdout} = await execAsync(`sudo iw dev ${iface} scan`);
34 return stdout;
35 }
36
37 function formatScanResult(scanResult) {
38 const results = [];
39 let partial = null;
40
41 for (let line of scanResult.split('\n')) {
42 if (line.startsWith('BSS ')) {
43 finishPartial();
44 partial = {};
45 partial.bssid = line.match(/[a-z0-9:]+/)[0];
46 partial.associated = line.indexOf('associated') >= 0 ? '**' : '';
47 }
48
49 line = line.trim()
50 if (line.startsWith('SSID: ')) {
51 partial.ssid = line.split(':')[1].trim();
52 }
53 if (line.startsWith('signal: ')) {
54 partial.signal = line.split(':')[1].trim();
55 }
56 if (line.startsWith('DS Parameter set: channel')) {
57 partial.channel = line.split(':')[1].trim();
58 }
59 if (line.startsWith('* primary channel:')) {
60 partial.channel = 'channel ' + line.split(':')[1].trim();
61 }
62 if (line.startsWith('freq: ')) {
63 partial.freq = 'freq ' + line.split(':')[1].trim();
64 }
65 }
66
67 finishPartial();
68
69 function finishPartial() {
70 if (!partial) {
71 return;
72 }
73
74 partial.ssid = partial.ssid || '';
75 partial.channel = partial.channel || partial.freq || '';
76
77 const sortKey = [
78 parseFloat(partial.signal),
79 parseInt(partial.channel.split(' ')[1])
80 ];
81
82 results.push([sortKey, partial]);
83 }
84
85 return results
86 .sort()
87 .map(([, {bssid, ssid, signal, channel, associated}]) => {
88 ssid = ssid.padStart(40, ' ').substr(0, 40);
89 channel = channel.padEnd(12, ' ');
90 return `${signal} ${channel} ${ssid} ${bssid} ${associated}`;
91 })
92 .join('\n') + '\n';
93 }
94
95 async function main() {
96 const iface = process.argv[2] || await findInterface();
97
98 if (iface === '-') {
99 const scanResult = await fs.readFile('/dev/stdin', 'utf-8');
100 const prettyScanResult = formatScanResult(scanResult);
101 process.stdout.write(prettyScanResult);
102 } else {
103 for (;;) {
104 const scanResult = await scanInterface(iface).catch((err) => err.toString());
105 const prettyScanResult = formatScanResult(scanResult);
106 process.stdout.write('\x1b[2J\x1b[0f');
107 process.stdout.write(prettyScanResult);
108 await sleep(3000);
109 }
110 }
111 }
112
113 main().catch((err) => {
114 console.log('Unhandled error!', err);
115 process.exit(1);
116 });