Quozl's OLPC XO-1 Signal Strength Meter |
| quozl@us.netrek.org
| up |
|
The levels are shown as circles of varying diameter, where the radius is determined by the level.
Figure 1 |
Figure 2 |
#!/usr/bin/python
"""
Signal Strength Meter
Copyright (C) 2010 James Cameron (quozl@laptop.org)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
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 General Public License for more details.
You should have received a copy of the GNU 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
"""
import sys, pygame
license = [
"Signal Strength Meter",
"Copyright (C) 2010 James Cameron <quozl@laptop.org>",
" ",
"This program comes with ABSOLUTELY NO WARRANTY;",
"for details see source.",
" ",
"This is free software, and you are welcome to ",
"redistribute it under certain conditions; see ",
"source for details.",
" "
]
version = '0.3'
pause = 100 # milliseconds between screen updates
license_show_time = 10 # number of seconds to show license for
thickness = 4 # thickness of circles in pixels
freeze = False
fonts = ('DejaVuSansMono.ttf', 'DejaVuLGCSansMono.ttf')
fontpaths = ('/usr/share/fonts/dejavu/', '/usr/share/fonts/truetype/ttf-dejavu/')
# colours
c_black = (0, 0, 0)
c_blue = (0, 0, 192)
c_green = (0, 192, 0)
c_red = (192, 0, 0)
c_license = c_black
c_link = c_blue
c_signal = c_green
c_noise = c_red
def get_rf_raw():
""" obtain wireless status from first network interface """
fp = open('/proc/net/wireless', 'r')
head = fp.readline()
head = fp.readline()
line = fp.readline()
fp.close()
(x, x, link, signal, noise, x, x, x, x, x, x) = line.split()
return (int(link.replace('.', ' ')),
int(signal.replace('.', ' ')),
int(noise.replace('.', ' ')))
samples = []
def get_rf():
global samples, min_l, min_s, min_n, max_l, max_s, max_n, avg_l, avg_s, avg_n
raw = get_rf_raw()
samples.append(raw)
q = len(samples)
if q > 100:
samples = samples[1:]
q -= 1
(l, s, n) = raw
(min_l, min_s, min_n) = (l, s, n)
(max_l, max_s, max_n) = (l, s, n)
(tot_l, tot_s, tot_n) = (0, 0, 0)
for sample in samples:
(l, s, n) = sample
(min_l, min_s, min_n) = (min(min_l, l), max(min_s, s), max(min_n, n))
(max_l, max_s, max_n) = (max(max_l, l), min(max_s, s), min(max_n, n))
tot_l += l
tot_s += s
tot_n += n
avg_l = tot_l / q
avg_s = tot_s / q
avg_n = tot_n / q
return raw
# return (avg_l, avg_s, avg_n)
# what to do when user wants to leave
def op_quit():
pygame.display.quit()
pygame.quit()
sys.exit()
# produce a screenshot for documentation purposes
def op_copy():
pygame.image.save(screen, "/tmp/ssm-snapshot.png")
print "snapshot taken"
# toggle pause of screen updates
def op_freeze():
global freeze
freeze = not freeze
def op_clear():
global samples
samples = []
# control keyboard table, relates keys to functions
kb_table_control = {
pygame.K_d: op_quit, # control/d to quit
pygame.K_c: op_copy, # control/c to make screen snapshot
}
# normal keyboard table, relates keys to functions
kb_table_normal = {
pygame.K_q: op_quit, # q to quit
pygame.K_f: op_freeze, # f to freeze
pygame.K_SPACE: op_freeze, # space to freeze
pygame.K_BACKSPACE: op_clear, # backspace to clear samples
}
def kb(event):
""" handle keyboard events from user """
# ignore the shift and control keys
if event.key == pygame.K_LSHIFT or event.key == pygame.K_RSHIFT: return
if event.key == pygame.K_LCTRL or event.key == pygame.K_RCTRL: return
# check for control key sequences pressed
if (event.mod & pygame.KMOD_CTRL):
if kb_table_control.has_key(event.key):
handler = kb_table_control[event.key]
handler()
return
# check for normal keys pressed
if kb_table_normal.has_key(event.key):
handler = kb_table_normal[event.key]
handler()
return
class FontCache:
def __init__(self):
self.cache = {}
def read(self, names, size):
if names == None:
return pygame.font.Font(None, size)
for name in names:
for path in fontpaths:
try:
return pygame.font.Font(path + name, size)
except:
continue
return pygame.font.Font(None, size)
def get(self, names, size):
key = (names, size)
if key not in self.cache:
self.cache[key] = self.read(names, size)
return self.cache[key]
def draw_license():
c = min(255 * (100 - license_show_count) / 100, 255)
fn = fc.get(fonts, 34)
x = 50
y = 394
for line in license:
ts = fn.render(line, 1, (c, c, c), bg)
tr = ts.get_rect(left=x, top=y)
y = tr.bottom
dirty.append(screen.blit(ts, tr))
def draw_labels():
fr = 40 # framing pad for data labels
fn = fc.get(fonts, 35)
ts = fn.render('ssm.py ' + version, 1, c_black, bg)
tr = ts.get_rect(left=0, top=0)
dirty.append(screen.blit(ts, tr))
ts = fn.render('LINK QUALITY', 1, c_link, bg)
tr = ts.get_rect(centerx=width/2, top=fr)
dirty.append(screen.blit(ts, tr))
ts = fn.render('SIGNAL LEVEL', 1, c_signal, bg)
tr = ts.get_rect(left=fr, bottom=height-fr)
dirty.append(screen.blit(ts, tr))
ts = fn.render('NOISE LEVEL', 1, c_noise, bg)
tr = ts.get_rect(right=width-fr, bottom=height-fr)
dirty.append(screen.blit(ts, tr))
fn = fc.get(fonts, 22)
ts = fn.render('press q to quit', 1, c_black, bg)
tr = ts.get_rect(centerx=width/2, bottom=height)
y = tr.top
dirty.append(screen.blit(ts, tr))
ts = fn.render('press space to pause', 1, c_black, bg)
tr = ts.get_rect(centerx=width/2, bottom=y)
dirty.append(screen.blit(ts, tr))
def draw_results(show):
colour = c_black
if not show:
colour = bg
fn = fc.get(fonts, 20)
ts = fn.render('min link %3.0f signal %3.0f noise %3.0f' % (min_l, min_s, min_n),
1, colour, bg)
tr = ts.get_rect(right=width-10, top=10)
x = tr.left
y = tr.bottom
dirty.append(screen.blit(ts, tr))
ts = fn.render('avg link %3.0f signal %3.0f noise %3.0f' % (avg_l, avg_s, avg_n),
1, colour, bg)
tr = ts.get_rect(left=x, top=y)
x = tr.left
y = tr.bottom
dirty.append(screen.blit(ts, tr))
ts = fn.render('max link %3.0f signal %3.0f noise %3.0f' % (max_l, max_s, max_n),
1, colour, bg)
tr = ts.get_rect(left=x, top=y)
x = tr.left
y = tr.bottom
dirty.append(screen.blit(ts, tr))
ts = fn.render(' last %d samples' % len(samples), 1, colour, bg)
tr = ts.get_rect(right=width-10, top=y+10)
x = tr.left
y = tr.bottom
dirty.append(screen.blit(ts, tr))
def draw_item(show, colour, x, y, radius, v):
if not show:
colour = bg
if radius < 0:
radius = 0
p = pygame.draw.circle(screen, colour, (x, y), radius+thickness, thickness)
fn = fc.get(fonts, 50)
ts = fn.render(v, 1, colour, bg)
tr = ts.get_rect(centerx=x, centery=y)
q = screen.blit(ts, tr)
dirty.append(pygame.Rect.union(p, q))
def draw_link(link, show):
draw_item(show, c_link, width/2, height/3, link*15/10, "%d %%" % link)
def draw_signal(signal, show):
if signal != -256: # scanning?
draw_item(show, c_signal, width/3, height*2/3, 156+signal, "%d dBm" % signal)
def draw_noise(noise, show):
draw_item(show, c_noise, width*2/3, height*2/3, 156+noise, "%d dBm" % noise)
from optparse import OptionParser
parser = OptionParser(usage="usage: %prog [options] message",
version="%prog " + version)
parser.add_option("--verbose",
action="store_true", dest="verbose", default=False,
help="generate verbose output")
parser.add_option("--no-license",
action="store_true", dest="no_license", default=False,
help="do not display license")
(opt, args) = parser.parse_args()
if not opt.no_license:
for line in license:
print line
pygame.init()
fc = FontCache()
screen = pygame.display.set_mode()
width, height = screen.get_size()
pygame.mouse.set_visible(False)
white = (255, 255, 255)
black = (0, 0, 0)
pygame.time.set_timer(pygame.USEREVENT, pause)
if opt.no_license: license_show_time = 0
license_show_count = (1000 / pause) * license_show_time
fg = black
bg = white
# prepare background
screen.fill(bg)
pygame.display.flip()
# initial drawing pass
(link, signal, noise) = get_rf()
dirty = []
draw_labels()
draw_link(link, True)
draw_signal(signal, True)
draw_noise(noise, True)
pygame.display.update(dirty)
# main event loop
while True:
event = pygame.event.wait()
if event.type == pygame.QUIT:
op_quit()
elif event.type == pygame.KEYDOWN:
kb(event)
elif event.type != pygame.USEREVENT:
continue
if freeze:
continue
dirty = []
if license_show_count > 0:
# slow screen redraw with license present
# everything is redrawn
license_show_count = license_show_count - 1
draw_link(link, False)
draw_signal(signal, False)
draw_noise(noise, False)
draw_results(False)
if license_show_count == 0:
dirty.append(screen.fill(bg))
else:
draw_license()
(link, signal, noise) = get_rf()
draw_labels()
draw_link(link, True)
draw_signal(signal, True)
draw_noise(noise, True)
draw_results(True)
else:
# fast screen redraw with license absent
# only changes are redrawn
(old_link, old_signal, old_noise) = (link, signal, noise)
(link, signal, noise) = get_rf()
if signal == -256: # scanning?
signal = old_signal
draw_results(False)
if link != old_link:
draw_link(old_link, False)
draw_link(link, True)
if signal != old_signal:
draw_signal(old_signal, False)
draw_signal(signal, True)
if noise != old_noise:
draw_noise(old_noise, False)
draw_noise(noise, True)
draw_results(True)
# update only the portions of screen that were changed
pygame.display.update(dirty)