802.11 Wifi Radio spectrograms
I've written a small program to plot the 802.11 (b/g) wifi radio spectrum from data collected from a Linksys WAP-54G access point running OpenWRT.
The WAP-54G acts as a wireless client to my normal access point, another WAP-54G that was doing the probes before. Because almost all processing is done on a remote host, each access point only needs to have an appropiate /etc/dropbear/authorized_keys and the "wl" command installed.
You can view the most recent measurement data captured at my home here.
Data is collected using the wl rm_req and rm_rep commands, exectued from a remote host running MRTG, data is stored per channel.
A small shell script sends the necessary commands to the access point:
#!/bin/sh
host=newwave.intra.isquared.nl
ssh root@$host /usr/sbin/wl rm_req cca -c $1 -d 512
sleep 1
ssh root@$host /usr/sbin/wl rm_rep | awk '/Carrier Fraction/ { print $3}'
echo 0
ssh root@$host uptime
echo $host
A bit of Perl reads the MRTG logfiles and uses libgd to graph the data in a spectrogram:
#!/usr/pkg/bin/perl
# 802dot11spectrum.pl - create 802.11b/g frequency spectrogram
# from data in individual mrtg channel log files
# Hessel Schut, hessel@isquared.nl, 2007-08-04
use GD;
use constant num_channels => 13; # 802.11b/g
# file paths, etc.
use constant img_path => '/var/www/isquared.nl/mrtg/802dot11/spectrum.png';
use constant log_path => '/var/www/isquared.nl/mrtg/802dot11/';
use constant log_prefix => log_path.'radio_measurement_chan_';
use constant log_suffix => '.old';
###
# create new GD image object and set it's attributes
my $graph = GD::Image->newTrueColor(620,132);
# setup color palette
my @palette;
$palette[0] = $graph->colorAllocate(0,0,0);
$palette[255] = $graph->colorAllocate(255,255,255);
# initialize spectrum color ramp palette
# stolen and adapted from a DLA program written by Nick Gessler
for (my $c_idx = 1; $c_idx <= 254; $c_idx++) {
if ($c_idx < 37) {
# Edge 1 from BLACK to BLUE
$palette[$c_idx] = $graph->colorAllocate(
0, 0, 6 * $c_idx);
};
if ($c_idx >= 37 && $c_idx < 73) {
# Edge 2 from BLUE to CYAN
$palette[$c_idx] = $graph->colorAllocate(
0, int(3.5 * $c_idx - 37), 255 );
}
if ($c_idx >= 73 && $c_idx < 110) {
# Edge 3 from CYAN to GREEN
$palette[$c_idx] = $graph->colorAllocate(
0, 255, 255 - int(2.3 * $c_idx - 73));
};
if ($c_idx >= 110 && $c_idx < 146) {
# Edge 4 from GREEN to YELLOW
$palette[$c_idx] = $graph->colorAllocate(
int(1.75 * $c_idx - 110), 255, 0);
};
if ($c_idx >= 146 && $c_idx < 183) {
# Edge 5 from YELLOW to RED
$palette[$c_idx] = $graph->colorAllocate(
255, 255 - int(1.39 * $c_idx - 146), 0);
};
if ($c_idx >= 183 && $c_idx < 219) {
# Edge 6 from RED to MAGENTA
$palette[$c_idx] = $graph->colorAllocate(
255, 0, int(1.16 * $c_idx - 183));
};
if ($c_idx >= 219) {
# Edge 7 from MAGENTA to WHITE
$palette[$c_idx] = $graph->colorAllocate(
255, $c_idx - 219, 255);
};
};
$graph->interlaced('true');
# draw channel labels
my $label;
for (my $marker=1; $marker<= num_channels; $marker++) {
if ($marker < 10) {
$label = " ".$marker;
} else {
$label = $marker;
};
$graph->string(
gdTinyFont,
4,
4 + gdTinyFont->height * $marker - gdTinyFont->height,
$label,
$palette[255]
);
};
# read data for each channel from its mrtg log file
CHANNEL: for(my $channel=1; $channel <= num_channels; $channel++) {
if (LOGFILE) { close LOGFILE }; # close previous handle if exists
open(LOGFILE, "<".log_prefix."$channel".log_suffix) or
die "can't open logfile for reading: $!";
my $line = 0;
# iterate over log entries
LINE: while () {
$line++;
next LINE if ($line == 1); # skip first line
# read values (only timestamp and inval are used)
my ($ts, $inval,,,) = split;
next CHANNEL if ($line >= 600;
# Draw spectrum data:
$graph->rectangle(
16 + $line,
4 + gdTinyFont->height * $channel,
16 + $line,
4 + gdTinyFont->height * $channel - gdTinyFont->height,
$palette[$inval + 1]
);
# Draw timestamps on first sample and every x minutes
if ($channel == 1 && ($line == 2 || $line%44 == 0)) {
# put on peril sensitive sunglasses, kludgy polishing
# of tm structure data ahead:
my $ts_label = (localtime($ts))[1];
# pesky localtime returns plain integers, make 'em presentable
if (length $ts_label == 1) { $ts_label = "0".$ts_label };
$ts_label = (localtime($ts))[2].":".$ts_label;
my $dm_label = (localtime($ts))[3]." ";
# expand month numbers to abbrev. names
$dm_label .= (Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec)[
(localtime($ts))[4]];
# print ticks, time- and datestamps
$graph->string(
gdTinyFont,
16 + ($line - 1),
132 - (2 * gdTinyFont->height + 3),
$ts_label,
$palette[255]
);
$graph->string(
gdTinyFont,
16 + ($line - 1),
132 - (gdTinyFont->height + 3),
$dm_label,
$palette[255]
);
$graph->rectangle(
16 + ($line - 1),
132 - (2 * gdTinyFont->height + 4),
16 + ($line - 1),
129 - (2 * gdTinyFont->height + 4),
$palette[255]
);
};
};
close(LOGFILE);
}
# write image
open (PLOT, ">".img_path) or
die "Can't open image file for writing: $!\n";
binmode PLOT;
print PLOT $graph->png;
close (PLOT);
You can download this Perl script here, mind you, this is a quick hack, so don't expect performance, stability, or actually any results at all, "It works on my computer." *smile*
Below you find a sample plot of the generated spectrograms:
isquared.nl rss (atom)