]> code.delx.au - monosys/blob - bin/wifi-scan
wifi-scan: improve formatting, now can operate on existing scan data
[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 }
47
48 line = line.trim()
49 if (line.startsWith('SSID: ')) {
50 partial.ssid = line.split(':')[1].trim();
51 }
52 if (line.startsWith('signal: ')) {
53 partial.signal = line.split(':')[1].trim();
54 }
55 if (line.startsWith('DS Parameter set: channel')) {
56 partial.channel = line.split(':')[1].trim();
57 }
58 if (line.startsWith('freq: ')) {
59 partial.freq = 'freq ' + line.split(':')[1].trim();
60 }
61 }
62
63 function finishPartial() {
64 if (!partial) {
65 return;
66 }
67
68 partial.ssid = partial.ssid || '';
69 partial.channel = partial.channel || partial.freq || '';
70
71 const sortKey = [
72 parseFloat(partial.signal),
73 parseInt(partial.channel.split(' ')[1])
74 ];
75
76 results.push([sortKey, partial]);
77 }
78
79 return results
80 .sort()
81 .map(([, {bssid, ssid, signal, channel}]) => {
82 ssid = ssid.padStart(40, ' ').substr(0, 40);
83 channel = channel.padEnd(12, ' ');
84 return `${signal} ${channel} ${ssid} ${bssid}`;
85 })
86 .join('\n') + '\n';
87 }
88
89 async function main() {
90 const iface = process.argv[2] || await findInterface();
91
92 if (iface === '-') {
93 const scanResult = await fs.readFile('/dev/stdin', 'utf-8');
94 const prettyScanResult = formatScanResult(scanResult);
95 process.stdout.write(prettyScanResult);
96 } else {
97 for (;;) {
98 const scanResult = await scanInterface(iface).catch((err) => err.toString());
99 const prettyScanResult = formatScanResult(scanResult);
100 process.stdout.write('\x1b[2J\x1b[0f');
101 process.stdout.write(prettyScanResult);
102 await sleep(3000);
103 }
104 }
105 }
106
107 main().catch((err) => {
108 console.log('Unhandled error!', err);
109 process.exit(1);
110 });