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:
todays 802.11 spectrogram