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. This makes shiny spectrograms like so:

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.

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:

1
2
3
4
5
6
7
8
9
10
#!/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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#!/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 (<LOGFILE>) {
      
      $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

Comments