Tim Shepard's additions to wifimon.py.
diff --git a/wifimon.py b/wifimon.py
index 035e2ce..6f2d1ce 100755
--- a/wifimon.py
+++ b/wifimon.py
@@ -1,4 +1,8 @@
-#!/usr/bin/python
+#!/usr/bin/env python2
+
+from __future__ import print_function
+import gzip
+import bz2
 import csv
 import os
 import string
@@ -6,6 +10,17 @@
 import subprocess
 import sys
 
+class Struct(dict):
+    def __init__(self, *args, **kwargs):
+        dict.__init__(self, *args, **kwargs)
+        self.__dict__.update(**kwargs)
+    def __getattr__(self, name):
+        return self[name]
+    def __setattr__(self, name, value):
+        self[name] = value
+    def __delattr__(self, name):
+        del self[name]
+
 TCPDUMP_MAGIC = 0xa1b2c3d4
 TCPDUMP_VERSION = (2, 4)
 LINKTYPE_IEEE802_11_RADIOTAP = 127
@@ -116,6 +131,70 @@
   return out
 
 
+MCS_TABLE = [
+    (1, "BPSK", "1/2", (6.50, 7.20, 13.50, 15.00)),
+    (1, "QPSK", "1/2", (13.00, 14.40, 27.00, 30.00)),
+    (1, "QPSK", "3/4", (19.50, 21.70, 40.50, 45.00)),
+    (1, "16-QAM", "1/2", (26.00, 28.90, 54.00, 60.00)),
+    (1, "16-QAM", "3/4", (39.00, 43.30, 81.00, 90.00)),
+    (1, "64-QAM", "2/3", (52.00, 57.80, 108.00, 120.00)),
+    (1, "64-QAM", "3/4", (58.50, 65.00, 121.50, 135.00)),
+    (1, "64-QAM", "5/6", (65.00, 72.20, 135.00, 150.00)),
+    (2, "BPSK", "1/2", (13.00, 14.40, 27.00, 30.00)),
+    (2, "QPSK", "1/2", (26.00, 28.90, 54.00, 60.00)),
+    (2, "QPSK", "3/4", (39.00, 43.30, 81.00, 90.00)),
+    (2, "16-QAM", "1/2", (52.00, 57.80, 108.00, 120.00)),
+    (2, "16-QAM", "3/4", (78.00, 86.70, 162.00, 180.00)),
+    (2, "64-QAM", "2/3", (104.00, 115.60, 216.00, 240.00)),
+    (2, "64-QAM", "3/4", (117.00, 130.00, 243.00, 270.00)),
+    (2, "64-QAM", "5/6", (130.00, 144.40, 270.00, 300.00)),
+    (3, "BPSK", "1/2", (19.50, 21.70, 40.50, 45.00)),
+    (3, "QPSK", "1/2", (39.00, 43.30, 81.00, 90.00)),
+    (3, "QPSK", "3/4", (58.50, 65.00, 121.50, 135.00)),
+    (3, "16-QAM", "1/2", (78.00, 86.70, 162.00, 180.00)),
+    (3, "16-QAM", "3/4", (117.00, 130.00, 243.00, 270.00)),
+    (3, "64-QAM", "2/3", (156.00, 173.30, 324.00, 360.00)),
+    (3, "64-QAM", "3/4", (175.50, 195.00, 364.50, 405.00)),
+    (3, "64-QAM", "5/6", (195.00, 216.70, 405.00, 450.00)),
+    (4, "BPSK", "1/2", (26.00, 28.80, 54.00, 60.00)),
+    (4, "QPSK", "1/2", (52.00, 57.60, 108.00, 120.00)),
+    (4, "QPSK", "3/4", (78.00, 86.80, 162.00, 180.00)),
+    (4, "16-QAM", "1/2", (104.00, 115.60, 216.00, 240.00)),
+    (4, "16-QAM", "3/4", (156.00, 173.20, 324.00, 360.00)),
+    (4, "64-QAM", "2/3", (208.00, 231.20, 432.00, 480.00)),
+    (4, "64-QAM", "3/4", (234.00, 260.00, 486.00, 540.00)),
+    (4, "64-QAM", "5/6", (260.00, 288.80, 540.00, 600.00)),
+    (1, "BPSK", "1/2", (0, 0, 6.50, 7.20)),
+]
+
+def mcs2rate(tb):
+#    known=tb[0]
+#    flags=tb[1]
+# ********************************************
+# ************* currently broken code here ***
+# ************ FIXME FIXME FIXME FIXME *******
+# ********************************************
+    if known & (1 << 0):
+        bw=(20,40,20,20)[flags & 0x3]
+    else:
+        bw=20
+    if known & (1 << 2):
+        gi=((flags&0x4) >> 2)
+    else:
+        gi=0
+    if known & (1 << 1):
+        mcs=tb[2]
+    else:
+        mcs=0
+    if bw==20:
+        si=0
+    else:
+        si=2
+    if gi:
+        si += 1
+    return MCS_TABLE[mcs][3][si]
+
+
 def Packetize(stream):
   # pcap global header
   magicbytes = stream.read(4)
@@ -137,7 +216,7 @@
     raise ValueError('unexpected tcpdump network type %r' % network)
 
   while 1:
-    opt = {}
+    opt = Struct({})
 
     # pcap packet header
     bytes = stream.read(16)
@@ -151,11 +230,15 @@
       raise ValueError('packet incl_len(%d) > snaplen(%d): invalid'
                        % (incl_len, snaplen))
 
-    opt['pcap_secs'] = ts_sec + (ts_usec / 1e6)
+    opt.pcap_secs = ts_sec + (ts_usec / 1e6)
 
     # pcap packet data
     radiotap = stream.read(incl_len)
 
+    opt.incl_len = incl_len
+    opt.orig_len = orig_len
+
+
     # radiotap header (always little-endian)
     (it_version, unused_it_pad,
      it_len, it_present) = struct.unpack('<BBHI', radiotap[:8])
@@ -171,14 +254,14 @@
         sz = struct.calcsize(format)
         v = struct.unpack(format, optbytes[ofs:ofs+sz])
         if name == 'mac_usecs':
-          opt['mac_usecs'] = v[0]
-          opt['mac_secs'] = v[0] / 1e6
+          opt.mac_usecs = v[0]
+          # opt.mac_secs = v[0] / 1e6
         elif name == 'channel':
-          opt['freq'] = v[0]
-          opt['channel_flags'] = v[1]
+          opt.freq = v[0]
+          opt.channel_flags = v[1]
         elif name == 'ht':
-          opt['ht'] = v
-          opt['mcs'] = v[2]
+          opt.ht = v
+          opt.mcs = v[2]
         else:
           opt[name] = v if len(v) > 1 else v[0]
         ofs += sz
@@ -195,20 +278,21 @@
     dot11wep = (fctl & 0x4000) >> 14
     dot11order = (fctl & 0x8000) >> 15
     fulltype = (dot11type << 4) | dot11subtype
-    opt['type'] = fulltype
+    opt.type = fulltype
+    opt.duration = duration
     (typename, typefields) = DOT11_TYPES.get(fulltype, ('Unknown', ('ra',)))
-    opt['typestr'] = '%02X %s' % (fulltype, typename)
-    opt['dsmode'] = dot11dsmode
-    opt['retry'] = dot11retry
-    opt['powerman'] = dot11powerman
-    opt['order'] = dot11order
+    opt.typestr = '%02X %s' % (fulltype, typename)
+    opt.dsmode = dot11dsmode
+    opt.retry = dot11retry
+    opt.powerman = dot11powerman
+    opt.order = dot11order
 
     ofs = 4
     for i, fieldname in enumerate(typefields):
       if fieldname == 'seq':
         seq = struct.unpack('<H', frame[ofs:ofs+2])[0]
-        opt['seq'] = (seq & 0xfff0) >> 4
-        opt['frag'] = (seq & 0x000f)
+        opt.seq = (seq & 0xfff0) >> 4
+        opt.frag = (seq & 0x000f)
         ofs += 2
       else:
         opt[fieldname] = MacAddr(frame[ofs:ofs+6])
@@ -217,12 +301,19 @@
     yield opt, frame
 
 
-def main():
+def d(p):
+  basetime=0
   if 0:
-    for opt, frame in Packetize(sys.stdin):
-      print opt
-      print HexDump(frame)
-  else:
+    for opt, frame in Packetize(p):
+        ts=opt.pcap_secs
+        if basetime:
+            ts -= basetime
+        else:
+            basetime=ts
+            ts=0
+        print (ts, opt)
+#      print HexDump(frame)
+  elif 1:
     want_fields = [
         'ta',
         'ra',
@@ -242,15 +333,56 @@
     ]
     co = csv.writer(sys.stdout)
     co.writerow(['pcap_secs'] + want_fields)
-    sys.stdout.flush()
     tbase_pcap = 0
     tbase_mac = 0
-    for opt, frame in Packetize(sys.stdin):
+    for opt, frame in Packetize(p):
       t_pcap = opt.get('pcap_secs', 0)
       if not tbase_pcap: tbase_pcap = t_pcap
       co.writerow(['%.6f' % (t_pcap - tbase_pcap)] +
                   [opt.get(f, None) for f in want_fields])
-      sys.stdout.flush()
+#      sys.stdout.flush()
+  else:
+      try:
+          for opt, frame in Packetize(p):
+              ts=opt.pcap_secs
+#              if basetime:
+#                  ts -= basetime
+#              else:
+#                  basetime=ts
+#                  ts=0
+              ts="%.09f"%ts
+              if 'xa' in opt:
+                  src=opt.xa
+              else:
+                  src='no:xa:00:00:00:00'
+              if 'mac_usecs' in opt:
+#                  if opt.mac_usecs > 4294967296:
+#                      continue
+                  mac_usecs = opt.mac_usecs
+              else:
+                  mac_usecs = 0
+              if 'seq' in opt:
+                  seq = opt.seq
+              else:
+                  seq = 'noseq'
+              if 'flags' in opt:
+                  if opt.flags & 0x40:    # "Bad FCS" flag in radiotap header
+                      continue
+              if 'mcs' in opt:
+                  print (src, opt.dsmode, opt.typestr, ts, mcs2rate(opt.mcs), mac_usecs, opt.orig_len, seq, opt.flags)
+              else:
+                  print (src, opt.dsmode, opt.typestr, ts, opt.rate, mac_usecs, opt.orig_len, seq, opt.flags)
+
+      finally:
+          pass
+
+def zopen(fn):
+    if fn.endswith(".bz2"):
+        return bz2.BZ2File(fn)
+    if fn.endswith(".gz"):
+        return gzip.open(fn)
+    return open(fn)
 
 if __name__ == '__main__':
-  main()
+    p=zopen(sys.argv[1])
+    d(p)