Skip to content

Instantly share code, notes, and snippets.

@ukslim
Last active September 10, 2024 13:42
Show Gist options
  • Save ukslim/df3dae50ce163e0fc8284f6f5f376dc8 to your computer and use it in GitHub Desktop.
Save ukslim/df3dae50ce163e0fc8284f6f5f376dc8 to your computer and use it in GitHub Desktop.
#!/usr/bin/env node
/*
My problem is that I have two networks - one in my house and one in my garden office, and I move
between them. In the office, my Mac can still see the house network, so it doesn't automatically switch
to the office network. It's strong enough for many things, but not for video conferencing. So unless I
remember to manually switch networks, I end up noticing when my colleagues tell me I'm breaking up.
This script exits early if we're on a network that isn't part of that scenario.
Otherwise it reads the signal strength of the networks we care about, and switches to strongest.
Password is hardcoded; if you're precious about this, find your own way.
We also assume that both networks have the same password, simply because mine do!
It's disappointing that the CLI makes you supply the password, given that the OSX GUI does not.
I run it with cron every 5 minutes.
*/
const { exec } = require("child_process");
const util = require("util");
const execAsync = util.promisify(exec);
const INTERFACE = "en0"; // Change this if your WiFi interface is different
const MY_NETWORKS = ["house", "garden"]; // SSIDs you'd like to switch between
const MY_PASSWORD = "notmuchofasecret";
async function getCurrentNetworkName() {
// Fast and cheap unlike system_profiler
const { stdout } = await execAsync(
`/usr/sbin/networksetup -getairportnetwork ${INTERFACE}`
);
return stdout.replace("Current Wi-Fi Network: ", "").trim();
}
async function getWifiInfo() {
const { stdout } = await execAsync(
"/usr/sbin/system_profiler -json SPAirPortDataType"
);
const interfaces =
JSON.parse(stdout).SPAirPortDataType[0].spairport_airport_interfaces[0];
const currentNetwork = interfaces.spairport_current_network_information;
const localNetworks =
interfaces.spairport_airport_other_local_wireless_networks;
const inScopeNetworks = localNetworks.filter(({ _name }) =>
MY_NETWORKS.includes(_name)
);
return {
currentNetwork,
inScopeNetworks,
};
}
function calculateScore(snString) {
// e.g. "-54 dBm / -91 dBm"
const [signal, noise] = snString
.split(" / ")
.map((s) => s.replace(" dBm", ""));
return signal - noise;
}
async function switchToNetwork(name) {
const result = await execAsync(
`/usr/sbin/networksetup -setairportnetwork ${INTERFACE} "${name}" ${MY_PASSWORD}`
);
if (result.stderr) {
console.log(result.stdout);
console.error(result.stderr);
}
}
async function chooseAndSwitchNetwork() {
const currentNetworkName = await getCurrentNetworkName();
if (!MY_NETWORKS.includes(currentNetworkName)) {
// We are not in the scenario we're trying to fix
return;
}
const { inScopeNetworks, currentNetwork } = await getWifiInfo();
const scoredAndRanked = inScopeNetworks
.map((network) => {
return {
...network,
score: calculateScore(network.spairport_signal_noise),
};
})
.sort((a, b) => b.score - a.score);
const bestNetworkName = scoredAndRanked[0]._name;
if (bestNetworkName === currentNetwork._name) {
// Already on the best network
return;
}
await switchToNetwork(bestNetworkName);
}
// Run the main function
chooseAndSwitchNetwork().catch(console.error);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment