The old Scanmeter high gain WiFi antenna pointer written in Bash still works very well.
However, it depends on the output of iwlist command to read wireless APs. For some reason iwlist is not reliable on my new Ubuntu machine.
I wrote a new antenna pointing tool in Python called Scanmeternm. Instead of iwlist, I am reading the wireless router power using ncmli dev wifi command. It’s certainly easier to customize for casual coders than the old Bash Scanmeter. For example, you could probably use the script to control an Arduino stepper motor and automatically rotate antenna on top of a vehicle.
Usage
The usage is very straightforward, and it is more user friendly than the old Scanmeter.
If you download to your Downloads directory, you’d add executable privilege:
chmod +x ~/Downloads/scanmeternm.py
Run as:
./~/Downloads/scanmeternm.py
Caveats
- Requires Network-manager package
- Does not work in Windows, maybe not even in OS X. I don’t know if Network-manager is available for Mac.
- I don’t know what units are used for nmcli dev wifi output. They may be linear or logarithmic. The meter readout green/red threshold is entirely arbitrary.
- #! /usr/bin/env python
- # Measure wifi signal based on nmcli. Works only if Network-manager is installed.
- # Scanmeternm V1.0, skifactz.com/wifi
- from subprocess import Popen, PIPE, STDOUT
- import sys
- import os
- import time
- # User customizable variables
- # Customize meter readout here
- threshold = 52 # Red/green threshold
- char = u'\u2588' # Graphics character, default is a box
- # Menu scanned list variables
- ssidMaxLength = 17 # Truncate length to satisfy tab based layout fit within 80 columns.
- tab = '\t' # Tab character. Can use ' ' or '\t' * 2 to modify layout
- # Lookup tables for 2.4 GHz and 5 GHz frequency to channel conversion.
- lut24 = {
- 2412:'01 (2.4 GHz)',
- 2417:'02 (2.4 GHz)',
- 2422:'03 (2.4 GHz)',
- 2427:'04 (2.4 GHz)',
- 2432:'05 (2.4 GHz)',
- 2437:'06 (2.4 GHz)',
- 2442:'07 (2.4 GHz)',
- 2447:'08 (2.4 GHz)',
- 2452:'09 (2.4 GHz)',
- 2457:'10 (2.4 GHz)',
- 2462:'11 (2.4 GHz)',
- 2467:'12 (2.4 GHz)',
- 2472:'13 (2.4 GHz)',
- 2484:'14 (2.4 GHz)'
- };
- lut5 = {
- 5035:'07 (5 GHz)',
- 5040:'08 (5 GHz)',
- 5045:'09 (5 GHz)',
- 5055:'11 (5 GHz)',
- 5060:'12 (5 GHz)',
- 5080:'16 (5 GHz)',
- 5170:'34 (5 GHz)',
- 5180:'36 (5 GHz)',
- 5190:'38 (5 GHz)',
- 5200:'40 (5 GHz)',
- 5210:'42 (5 GHz)',
- 5220:'44 (5 GHz)',
- 5230:'46 (5 GHz)',
- 5240:'48 (5 GHz)',
- 5260:'52 (5 GHz)',
- 5280:'56 (5 GHz)',
- 5300:'60 (5 GHz)',
- 5320:'64 (5 GHz)',
- 5500:'100 (5 GHz)',
- 5520:'104 (5 GHz)',
- 5540:'108 (5 GHz)',
- 5560:'112 (5 GHz)',
- 5580:'116 (5 GHz)',
- 5600:'120 (5 GHz)',
- 5620:'124 (5 GHz)',
- 5640:'128 (5 GHz)',
- 5660:'132 (5 GHz)',
- 5680:'136 (5 GHz)',
- 5700:'140 (5 GHz)',
- 5720:'144 (5 GHz)',
- 5745:'149 (5 GHz)',
- 5765:'153 (5 GHz)',
- 5785:'157 (5 GHz)',
- 5805:'161 (5 GHz)',
- 5825:'165 (5 GHz)'
- };
- # Convert 2.4 GHz and 5 GHz to channel numbers.
- def getchannel(freq):
- if freq[0] == '2':
- return lut24[int(freq)]
- if freq[0] == '5':
- return lut5[int(freq)]
- return 'N/A'
- # Prints list of scanned APs for main menu and returns the list
- def aplist():
- # Clear Linux console
- Popen(['clear'])
- # Scan available APs and save in scannedMenu list.
- scan = Popen(['nmcli', 'dev', 'wifi'], stdout=PIPE, stderr=STDOUT)
- scannedMenu=[]
- for line in scan.stdout:
- scannedMenu.append(line)
- # Print menu header.
- print 'No. SSID',
- print ' ' * (ssidMaxLength - 4), # White spaces minus SSID length
- print 'BSSID CHANNEL POWER ENCRYPT.'
- print u'\u00af' * 80
- # SSID and SECURITY fields may contain spaces. We use the ':' marks as the parsing ref.
- for line in scannedMenu[1:]:
- ssid = line.split("'")[1]
- cells = line.split(':')
- bssid = ':'.join([cells[0][-2:],
- cells[1], cells[2],
- cells[3],
- cells[4],
- cells[5][:2]])
- cells = cells[5].split() # Split cells using space for the rest of the fields.
- chan = getchannel(cells[2])
- power = cells[6]
- encrypt = cells[7]
- lineNo = str(scannedMenu.index(line)) # Number APs
- print lineNo.zfill(2) + ' ',
- print ssid[:ssidMaxLength],
- print ' ' * (ssidMaxLength - len(ssid)), # Padding based on maximum SSID length
- print tab.join([bssid, chan, power, encrypt])
- return scannedMenu
- # Menu user input returns selected bssid and facilitates user commands like 'S' and 'Q'.
- def menuinput(scannedMenu):
- ap = False
- def enterap():
- ap = raw_input('Enter access point number. Enter S for new scan or Q to quit. \n')
- if ap == 's' or ap == 'S':
- aplist()
- if ap == 'q' or ap == 'Q':
- sys.exit()
- if ap.isdigit() == False:
- ap = False
- if int(ap) > len(scannedMenu) - 1: # -1 because of the nmcli header line
- ap = False
- if int(ap) == 0:
- print 'No such AP.'
- ap = False
- return ap
- # User input, loop until ap no longer False
- while ap == False:
- ap = enterap()
- # User input selection assignent
- for line in scannedMenu[1:]:
- lineNo = scannedMenu.index(line) # index number
- if int(ap) == lineNo:
- ssidSel = line.split("'")[1]
- cells = line.split(':')
- bssidSel = ':'.join([cells[0][-2:],
- cells[1],
- cells[2],
- cells[3],
- cells[4],
- cells[5][:2]])
- return ssidSel, bssidSel
- # Main menu control calls AP scanning, prints the list and calls user AP selection.
- def mainControl():
- # print ap list, get the list
- scannedMenu = aplist()
- # user menu input. get selected ssid and bssid
- global scanselection # Declaring global list so it is available to AP scan below
- scanselection = menuinput(scannedMenu)
- print 'You selected:', scanselection[0], scanselection[1] + '\n'
- print 'CTRL + C exits scan mode.' + '\n'
- time.sleep(.33)
- mainControl()
- def red(text):
- return ('\033[91m' + text + '\033[0m')
- def green(text):
- return ('\033[92m' + text + '\033[0m')
- # Here we print power reading to the scaled console in form of an dynamic histogram.
- def readout(power):
- if powerScaled == 0:
- print '%s is out of range or not transmitting.' %scanselection[0]
- if powerScaled < thresholdScaled:
- lPower = powerScaled
- else:
- lPower = thresholdScaled
- hPower = powerScaled - thresholdScaled
- sys.stdout.write(red(char) * lPower)
- sys.stdout.write(green(char) * hPower)
- print power
- #print '\r'
- while True:
- try:
- # We declare these two variables for the scenario when the radio signal drops
- powerScaled = 0
- thresholdScaled = 0
- scan = Popen(['nmcli', 'dev', 'wifi'], stdout=PIPE, stderr=STDOUT)
- # Is the selected bssid continuosly present in the scan? If so, proceed.
- for line in scan.stdout:
- if scanselection[1] in line:
- cells = line.split(':')
- cells = cells[5].split() # Split cells using white space for the rest.
- power = int(cells[6])
- # Dynamically scale max nmcli signal to maximum console width, non-dB units
- rows, columns = os.popen('stty size', 'r').read().split()
- scaleFactor = float(columns) / 100 # Max terminal width / max nmcli signal
- powerScaled = int(round(power * scaleFactor))
- thresholdScaled = int(round(threshold * scaleFactor))
- # Print readout
- readout(powerScaled)
- # On CTRL + C go to main menu
- except KeyboardInterrupt:
- mainControl()