#!/usr/bin/python

### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU Library General Public License as published by
### the Free Software Foundation; version 2 only
###
### This program is distributed in the hope that it will be useful,
### but WITHOUT ANY WARRANTY; without even the implied warranty of
### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
### GNU Library General Public License for more details.
###
### You should have received a copy of the GNU Library General Public License
### along with this program; if not, write to the Free Software
### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
### Copyright 2004, 2005, 2006 Dag Wieers <dag@wieers.com>

import sys, signal, re, time, string
from pythonwifi import iwlibs

VERSION = '0.2'

#delay = 30
delay = 100

ansi = {
	'default': '\033[0;0m',

	'black': '\033[0;30m',
	'darkred': '\033[0;31m',
	'darkgreen': '\033[0;32m',
	'darkyellow': '\033[0;33m',
	'darkblue': '\033[0;34m',
	'darkmagenta': '\033[0;35m',
	'darkcyan': '\033[0;36m',
	'silver': '\033[0;37m',

	'gray': '\033[1;30m',
	'red': '\033[1;31m',
	'green': '\033[1;32m',
	'yellow': '\033[1;33m',
	'blue': '\033[1;34m',
	'magenta': '\033[1;35m',
	'cyan': '\033[1;36m',
	'white': '\033[1;37m',

	'blackbg': '\033[40m',
	'redbg': '\033[41m',
	'greenbg': '\033[42m',
	'yellowbg': '\033[43m',
	'bluebg': '\033[44m',
	'magentabg': '\033[45m', 
	'cyanbg': '\033[46m',
	'whitebg': '\033[47m',

	'reset': '\033[0;0m',
	'bold': '\033[1m',
	'reverse': '\033[2m',
	'underline': '\033[4m',

	'home': '\033[H',

	'up': '\033[1A',
	'down': '\033[1B',
	'right': '\033[1C',
	'left': '\033[1D',

	'clear': '\033[2J',
	'cleareol': '\033[K',
	'clearline': '\033[2K',
#	'save': '\033[s',
#	'restore': '\033[u',
	'save': '\0337',
	'restore': '\0338',
}

a = ''; b = ''
for i in range(0, 255):
	a = a + chr(i)
	if i < 32 or i > 127:
		b = b + '.'
	else:
		b = b + chr(i)
asciitrans = string.maketrans(a, b)

def asciify(str):
	return str.translate(asciitrans)

def sortfunc(a, b):
	if a['active'] and b['active']:
		return cmp(b['quality'], a['quality'])
	elif not a['active'] and b['active']:
		return 1
	elif a['active'] and not b['active']:
		return -1
	else:
		return cmp(b['timestamp'], a['timestamp'])

def sorted(aps):
	list = []
	for key in aps.keys():
		list.append(aps[key])
	list.sort(sortfunc)
	return list

def timestr(timestamp):
	interval = time.time() * 1000.0 - timestamp
	if interval / 1000 / 15 < 1:
		return ' %5dms ' % interval
	elif interval / 1000 / 60 < 1:
		return ' %6ds ' % (interval / 1000)
	elif interval / 1000 / 60 / 60 < 1:
		return ' %2dm %02ds ' % (interval / 1000 / 60, interval / 1000 % 60)
	elif interval / 1000 / 60 / 60 / 24 < 1:
		return ' %2dh %02dm ' % (interval / 1000 / 60 / 60, interval / 1000 / 60 % 60)
	else:
		return '%3dd %02dh ' % (interval / 1000 / 60 / 60 / 24, interval / 1000 / 60 / 60 % 24)

def strength(snr):
	ecol = ansi['white']
	signalname = 'Excellent'
	if 20 < snr <= 25:
		ecol = ansi['green']
		signalname = 'Very Good'
	elif 15 < snr <= 20:
		ecol = ansi['darkgreen']
		signalname = 'Good'
	elif 10 < snr <= 15:
		ecol = ansi['yellow']
		signalname = 'Low'
	elif 8 < snr <= 10:
		ecol = ansi['darkyellow']
		signalname = 'Very Low'
	elif 6 < snr <= 8:
		ecol = ansi['red']
		signalname = 'Very Low'
	elif 0 < snr <= 6:
		ecol = ansi['darkred']
		signalname = 'Ultra Low'
	elif snr == 0:
		ecol = ansi['silver']
		signalname = 'No Signal'
	elif snr < 0:
		ecol = ansi['gray']
		signalname = 'Not Assoc'
	return ecol + signalname

if not iwlibs.getNICnames():
	print "No wireless interfaces found on the system."
	sys.exit(1)

print ansi['clear']

aps = {}
assaps = {}
try:
	while True:

		### Collect Access Point information and Association information
		for iface in iwlibs.getNICnames():
			ifobj = iwlibs.Wireless(iface)

			### Create Association mapping
			stats, quality, discard, missed_beacon = ifobj.getStatistics()
			snr = quality.signallevel - quality.noiselevel

			### Create 10 sec average
			if not assaps.has_key(iface) or snr == 0:
				ticks = 1.0
				avgsnr30 = snr
				avgsnr15 = snr
				avgsnr = snr
			else:
				ticks = assaps[iface]['ticks'] + 1.0
				avgsnr = (assaps[iface]['avgsnr'] * ticks + snr) / (ticks + 1)
				if ticks * delay >= 30 * 1000.0:
					ticks30 = (30 * 1000.0 / delay) - 1
					avgsnr30 = (assaps[iface]['avgsnr30'] * ticks30 + snr) / (ticks30 + 1)
				else:
					avgsnr30 = avgsnr

				if ticks * delay >= 15 * 1000.0:
					ticks15 = (15 * 1000.0 / delay) - 1
					avgsnr15 = (assaps[iface]['avgsnr15'] * ticks15 + snr) / (ticks15 + 1)
				else:
					avgsnr15 = avgsnr

#			print 'avg15: %4.2f' % avgsnr15
#			print 'avg30: %4.2f' % avgsnr30
#			print 'avg:   %4.2f' % avgsnr

#			print ifobj.getFrequency()

			assaps[iface] = {
				'avgsnr': avgsnr,
				'avgsnr15': avgsnr15,
				'avgsnr30': avgsnr30,
				'bitrate': ifobj.getBitrate(),
				'bssid': ifobj.getAPaddr(),
#				'channel': ifobj.getChannel(ifobj.getFrequency(), d),
				'discard': discard,
				'essid': ifobj.getEssid(),
#				'frequency': ifobj.getFrequency(),
				'iface': iface,
				'missed_beacon': missed_beacon,
				'noise': quality.noiselevel,
				'stats': stats,
				'signal': quality.signallevel,
				'snr': snr,
				'strength': strength(avgsnr15),
				'ticks': ticks,
			}

			### Create Access Point mapping
			scanresults = ifobj.scan()
			for result in scanresults:
				bssid = result.bssid
				try:
					beacon = int(re.match('.*Last beacon: ([0-9]+)ms ago', result.custom[1]).group(1))
				except:
					beacon = int(re.match('.*Last beacon: ([0-9]+)ms ago', result.custom[0]).group(1))

				if aps.has_key(bssid):
					ticks = aps[bssid]['ticks'] + 1.0
					avgquality = ( aps[bssid]['avgquality'] * ticks + result.quality.quality ) / (ticks + 1)
				else:
					ticks = 1.0
					avgquality = result.quality.quality

#				print dir(result.frequency)
#				print result.frequency.getBitrate()
#				print result.frequency.getChannel(result.frequency.getFrequency(), result.rate)
#				print result.frequency.getFrequency()

				aps[bssid] = {
					'active': True,
					'avgquality': avgquality,
					'bssid': result.bssid, 
					'channel': result.frequency.getChannel(result.frequency.getFrequency(), result.range), 
#					'frequency': result.frequency.getFrequency(), 
					'encode': result.encode, 
					'essid': asciify(result.essid),
					'iface': iface,
					'maxrate': result.rate[-1], 
					'noise': result.quality.getNoiselevel(), 
					'quality': result.quality.quality, 
					'signal': result.quality.getSignallevel(), 
					'ticks': ticks,
					'timestamp': time.time() * 1000.0 - beacon,
				}

		### Display Association information
		print ansi['home'] + ansi['blue'] + ' %(iface)-5s  %(essid)-20s  %(signal)4s  %(noise)4s  %(bitrate)8s  %(missed_beacon)3s  %(snr)3s  %(avgsnr)4s  %(avgsnr15)4s  %(signalname)-10s' % { 'iface': 'Iface', 'essid': 'ESSID/Name', 'signal': 'Sgnl', 'noise': 'Nois', 'snr': 'SNR', 'avgsnr': 'Avg', 'avgsnr15': 'Av15', 'bitrate': 'Cur rate', 'missed_beacon': 'Mis', 'signalname': 'Strength' }
#		print ansi['blue'] + "%-5s  %-20s  %4s  %4s  %3s  %8s  %3s  %-10s" % ('Iface', 'ESSID/Name', 'Sgnl', 'Nois', 'SNR', 'Cur rate', 'Mis', 'Strength')
		for iface in assaps.keys():
			obj = assaps[iface]
			print ansi['default'] + (' %(iface)-5s  %(essid)-20s  %(signal)4s  %(noise)4s  %(bitrate)8s  %(missed_beacon)3s  %(snr)3s  %(avgsnr)4.1f  %(avgsnr15)4.1f  %(strength)-10s' % obj) + ansi['silver'] + ansi['cleareol']

		print ansi['clearline']

		### Display Access Point information
		print ansi['blue'] + " %-20s  %2s  %-17s  %-5s  %-8s  %8s  %3s  %5s" % ('ESSID/Name', 'Ch', 'BSSID/HW address', 'Iface', 'Max rate', 'Last bcn', 'Qua', 'Avg Q')
		for obj in sorted(aps):
			if obj['bssid'] == assaps[obj['iface']]['bssid']:
				symbol = '+'
				if obj['active']:
					color = 'white'
				else:
					color = 'silver'
			else:
				if obj['active']:
					color = 'default'
					symbol = ' '
				else:
					color = 'gray'
					symbol = '!'

			if (map(lambda x: hex(ord(x)), obj['encode']) == ['0x0','0x0','0x0','0x8']):
				if obj['active']:
					ecolor = 'red'
				else:
					ecolor = 'darkred'
			else:
				if obj['active']:
					ecolor = 'green'
				else:
					ecolor = 'darkgreen'

			beacon = timestr(obj['timestamp'])

			print ansi[color] + symbol + ansi[ecolor] + ('%(essid)-20s ' % obj) + ansi[color] + (' %(channel)2s  %(bssid)-17s  %(iface)-5s  %(maxrate)8s ' % obj) + beacon + (' %(quality)3s  %(avgquality)5.1f ' % obj) + ansi['silver'] + ansi['cleareol']

#			if obj['bssid'] == ifobj.getAPaddr():
#				print '%4s  %4s  %3s  %8s  %3s' % (qual.signallevel, qual.noiselevel, qual.signallevel - qual.noiselevel, ifobj.getBitrate(), missed_beacon),
##			else:
##				print ansi['gray'] + '%4s  %4s  %3s  %8s  %3s' % (obj['signal'], obj['noise'], int(obj['signal']) - int(obj['noise']), '-', '-'),
				

			aps[obj['bssid']]['active'] = False

		print ansi['blue'] + ' %s access points (%s active and %s inactive)' % (len(aps), len(scanresults), len(aps) - len(scanresults)) + ansi['cleareol']
		print ansi['clearline'],

		time.sleep(delay / 1000.0)

except KeyboardInterrupt, e:
	print ansi['default']
except RuntimeError, e:
	print ansi['default'], e
	sys.exit(1)

# vim:ts=4:sw=4
