Allow test scripts to run on wired devices.

This is important when testing wireless network devices that are
connected over Ethernet. (Some evaluation devices are, since Ethernet is
easy to integrate with.)

Also start using structured logging. Paramiko (our SSH implementation)
already outputs important messages there, so we should be there too.

And add support for capturing information directly from skids.

Change-Id: I5e2a4ace0421a2d973fab188106c28cd7eee2e16
diff --git a/wifitables/ifstats.py b/wifitables/ifstats.py
index 4ad0059..169b52c 100644
--- a/wifitables/ifstats.py
+++ b/wifitables/ifstats.py
@@ -1,4 +1,4 @@
-"""ifstats: get information about a wireless interface."""
+"""ifstats: get information about a network interface."""
 
 import os
 import pipes
@@ -6,13 +6,22 @@
 
 
 class InterfaceStats(object):
-  """InterfaceStats collects statistics from a wireless interface."""
+  """InterfaceStats collects statistics from a network interface.
+
+  It's specialized for wireless interfaces on OS X and Linux, but will handle
+  wired interfaces too. This is useful when testing a router that you have
+  connected to over Ethernet.
+  """
 
   def __init__(self, system=None, runner=None, interface=None):
     self.system = system
     self.run = runner
     self.interface = interface
 
+  def IpAddr(self):
+    """Invoke `ip addr` in one-line mode and return output as a string."""
+    return self.run('ip -o -f inet addr')
+
   def IwLink(self, devname):
     """Invoke `iw dev <devname> link` and return its output as a string."""
     return self.run('iw dev {} link'.format(pipes.quote(devname)))
@@ -25,9 +34,9 @@
       text = self.run('iw dev {} scan'.format(devname_quoted))
     return text
 
-  def IpAddr(self):
-    """Invoke `ip addr` in one-line mode and return output as a string."""
-    return self.run('ip -o -f inet addr')
+  def Ifconfig(self):
+    """Gather information about known network interfaces from `ifconfig`."""
+    return self.run('ifconfig')
 
   def AirportI(self):
     """Gather information about the current wireless network from `airport`."""
@@ -42,20 +51,27 @@
 
     On a Mac with 802.11ac wireless, this will also contain a wireless network
     scan result with more information than `airport -s` provides.
+
+    Returns:
+      A string containing the output of `system_profiler`, and attributes as
+      described in the Fabric documentation.
     """
     return self.run('system_profiler -xml SPAirPortDataType')
 
   def GatherDarwin(self):
     """Gather wireless network information on Darwin (Mac OS X)."""
-    return {
+    outputs = {
+        'ifconfig': self.Ifconfig(),
         'airport': self.AirportI(),
         'airportscan': self.AirportScan(),
         'systemprofiler': self.SystemProfiler()
     }
+    return outputs
 
   def GatherLinux(self):
     """Gather wireless network information on Linux."""
     outputs = {'ipaddr': self.IpAddr()}
+
     if self.interface is not None:
       outputs.update({
           'iwlink': self.IwLink(self.interface),
@@ -63,8 +79,8 @@
       })
       return outputs
 
-    # If no interface was supplied, use the first connected one (like `airport`
-    # does for you automatically.)
+    # If no interface was supplied, try to use the first connected one (like
+    # `airport` does for you automatically.)
     iw_dev_raw = self.run('iw dev')
     for line in iw_dev_raw.splitlines():
       tokens = line.split()
@@ -75,11 +91,11 @@
               'iwlink': self.IwLink(interface),
               'iwscan': self.IwScan(interface)
           })
-          return outputs
+          break
         except ValueError:
           pass
 
-    raise ValueError('No connected interfaces found.')
+    return outputs
 
   def Gather(self):
     """Gather all the wireless network information we know how to."""
@@ -93,16 +109,18 @@
 
 
 def ParseIwLink(text):
+  """Parse the text output by `iw dev <devname> link` into a dict."""
   ol = text.splitlines()
+  if not ol:
+    return {}
 
   # BSSID is in the first line, in an idiosyncratic format.
   # sample: Connected to d8:c7:c8:d7:72:30 (on wlan0)
   m = re.search(r'(\w{2}:){5}\w{2}', ol[0])
-  if m:
-    result = {'BSSID': m.group(0)}
-  else:
-    raise ValueError('dev was not connected.')
+  if not m:
+    return {}
 
+  result = {'BSSID': m.group(0)}
   for line in ol[1:]:
     try:
       key, value = line.split(':', 1)
@@ -112,6 +130,7 @@
 
   return result
 
+
 def ParseIpAddr(text):
   ol = text.splitlines()
   result = {}
@@ -123,6 +142,7 @@
 
 
 def ParseAirportI(text):
+  """Parse the text output by `airport -i` into a dict."""
   result = {}
   for line in text.splitlines():
     try:
@@ -136,7 +156,19 @@
 
   return result
 
+
 def ParseAirportScan(text):
+  """Parse the text output by `airport -S` into a table.
+
+  The table is similar to what would be returned by a csv.reader, and suitable
+  for output by a csv.writer.
+
+  Arguments:
+    text: a string containing the text output of `airport -s`
+  Returns:
+    a list of lists, where the first list is a header row (all strings)
+    and subsequent rows are mixed strings and integers.
+  """
   # This is a simple fixed-width format.
   header = ['SSID', 'BSSID', 'RSSI', 'CHANNEL', 'HT', 'CC',
             'SECURITY (auth/unicast/group)']
@@ -156,43 +188,42 @@
 
   return [header] + result
 
+
 def Parse(system, cache):
   if system == 'Darwin':
     return {
-        'link': ParseAirportI(cache.get('airport')),
-        'scan': ParseAirportScan(cache.get('airportscan'))
+        'link': ParseAirportI(cache.get('airport', '')),
+        'scan': ParseAirportScan(cache.get('airportscan', ''))
     }
   elif system == 'Linux':
     return {
-        'link': ParseIwLink(cache.get('iwlink')),
-        'addr': ParseIpAddr(cache.get('ipaddr'))
+        'link': ParseIwLink(cache.get('iwlink', '')),
+        'addr': ParseIpAddr(cache.get('ipaddr', ''))
     }
   else:
     raise OSError('Attempt to parse cache from '
                   'unrecognized system {}'.format(system))
 
+
 def Restore(report_dir):
   """Restore an InterfaceStats cache from data on the filesystem."""
   cache = {}
 
-  apath = os.path.join(report_dir, 'airport')
-  if os.path.exists(apath):
-    system = 'Darwin'
-    names = ['airport', 'airportscan']
-  else:
-    iwpath = os.path.join(report_dir, 'iwlink')
-    if os.path.exists(iwpath):
-      system = 'Linux'
-      names = ['ipaddr', 'iwlink', 'iwscan']
-    else:
-      raise IOError('Could not open report in {}'.format(report_dir))
+  for system, names in {
+      'Darwin': ['ifconfig', 'airport', 'airportscan', 'systemprofiler'],
+      'Linux': ['ipaddr', 'iwlink', 'iwscan'],
+      }:
+    read = 0
+    for name in names:
+      try:
+        with open(os.path.join(report_dir, name)) as infile:
+          cache[name] = infile.read()
+          read += 1
+      except IOError:
+        cache[name] = ''
 
-  for name in names:
-    try:
-      with open(os.path.join(report_dir, name)) as infile:
-        cache[name] = infile.read()
-    except IOError:
-      cache[name] = ''
+    if read:
+      return system, cache
 
-  return system, cache
+  raise IOError('Could not open report in {}'.format(report_dir))
 
diff --git a/wifitables/ifstats_skids.py b/wifitables/ifstats_skids.py
new file mode 100644
index 0000000..7ae4e58
--- /dev/null
+++ b/wifitables/ifstats_skids.py
@@ -0,0 +1,69 @@
+"""ifstats_skids: gather wireless status from a skid in STA mode."""
+# coding: utf-8
+import HTMLParser
+
+import requests
+
+
+def GetStatusWireless(bridge_ip):
+  resp = requests.get('http://{}/status_wireless.php'.format(bridge_ip))
+  return resp.text
+
+
+class MyHTMLParser(HTMLParser.HTMLParser):
+  """Extract connection information from the skid's status_wireless.php page."""
+
+  def __init__(self):
+    # HTMLParser is an old-style class (!!!, http://stackoverflow.com/a/9698750)
+    HTMLParser.HTMLParser.__init__(self)
+    self.in_tables = 0
+    self.in_td = False
+    self.data = ''
+    self.dlist = []
+
+  def handle_starttag(self, tag, attrs):
+    if not self.in_tables:
+      if tag == 'table' and ('class', 'tablemain') in attrs:
+        self.in_tables += 1
+    else:
+      if tag == 'table':
+        self.in_tables += 1
+      elif tag == 'td':
+        self.in_td = True
+
+  def handle_endtag(self, tag):
+    if self.in_tables:
+      if tag == 'table':
+        self.in_tables -= 1
+      elif tag == 'td':
+        self.in_td = False
+        self.dlist += [self.data]
+        self.data = ''
+
+  def handle_data(self, data):
+    if self.in_tables:
+      self.data += data
+
+
+def ParseStatusWireless(text):
+  """Parse a status_wireless.php page returned from a skid."""
+  parser = MyHTMLParser()
+  parser.feed(text)
+
+  results = {}
+  key = ''
+  for cell in [item.strip() for item in parser.dlist][3:-1]:
+    if not key:
+      key = cell[:-1]
+      continue
+
+    results[key] = cell
+    key = ''
+
+  return results
+
+
+if __name__ == '__main__':
+  out = GetStatusWireless('192.168.1.167')
+  print ParseStatusWireless(out)
+
diff --git a/wifitables/ifstats_skids_test.py b/wifitables/ifstats_skids_test.py
new file mode 100644
index 0000000..8b58ac4
--- /dev/null
+++ b/wifitables/ifstats_skids_test.py
@@ -0,0 +1,25 @@
+"""Tests for ifstats_skids."""
+
+import ifstats_skids
+from wvtest import *  # pylint: disable=wildcard-import
+
+
+@wvtest
+def ParseSavedStatusWireless():
+  with open('testdata/status_wireless.html') as status_wireless:
+    text = status_wireless.read()
+    res = ifstats_skids.ParseStatusWireless(text)
+    WVPASSEQ(res, {
+        'Wireless Band': '802.11ac',
+        'AP Mac Address (BSSID)': '00:26:86:F0:22:C9',
+        'Bytes Transmitted': '193033536',
+        'Device Mode': 'Station (STA)',
+        'Packets Received Successfully': '12351163',
+        'Association Status': 'Associated',
+        'Bandwidth': '80 MHz',
+        'RSSI': '-20',
+        'Packets Transmitted Successfully': '9372546',
+        'Bytes Received': '2814237224',
+        'Channel': '108'
+    })
+
diff --git a/wifitables/sample.py b/wifitables/sample.py
index 83b4e7c..6bebbdd 100755
--- a/wifitables/sample.py
+++ b/wifitables/sample.py
@@ -1,9 +1,10 @@
-#!/usr/bin/env python
-
+#!/usr/bin/python2.7
 """sample: measure wireless performance and write a report to the filesystem."""
 
 import atexit
+import collections
 import functools
+import logging
 import os
 import pipes
 import platform
@@ -13,8 +14,10 @@
 
 from fabric import api
 from fabric import network
+from fabric import utils
 
 import ifstats
+import ifstats_skids
 import iperf
 import isostream
 import options
@@ -23,18 +26,32 @@
 optspec = """
 sample [options...]
 --
-B,bind=         interface IP to bind during iperf runs
-d,destination=  host to run tests against [192.168.1.143]
-s,steps=        number of steps test was run from [10]
-j,journal=      append to journal tracking a series of test runs
-i,interface=    wireless interface to use for outgoing connections
-m,monitor=      wireless monitor interface to use
-no-filter       don't attempt to filter packets seen on monitor interface
-r,remote=       remote host to run tests on
-t,time=         length of time in seconds to run isostream test for [50]
+B,bind=              interface IP to bind during iperf runs
+d,destination=       host to run tests against [192.168.1.143]
+s,steps=             number of steps test was run from [10]
+j,journal=           append to journal tracking a series of test runs
+i,interface=         interface to use for outgoing connections
+m,monitor            try to monitor wireless traffic using `tcpdump`
+M,monitor-interface= override default interface for monitoring traffic.
+no-filter            don't attempt to filter packets seen on monitor interface
+r,remote=            comma-separated list of remote hosts to run tests on
+S,skid=              IP address of the skid used to reach the network
+t,time=              length of time in seconds to run isostream test for [50]
 """
 
 
+class FabricLoggingHandler(logging.Handler):
+
+  def emit(self, record):
+    msg = self.format(record)
+    if record.levelno >= logging.ERROR:
+      utils.error(msg)
+    elif record.levelno == logging.WARNING:
+      utils.warn(msg)
+    else:
+      utils.puts(msg)
+
+
 def _Run(cmd_or_args, dirname='.', local=False):
   if isinstance(cmd_or_args, list):
     cmd = ' '.join([pipes.quote(arg) for arg in cmd_or_args])
@@ -64,47 +81,51 @@
 
 
 def main():
-  system = platform.system()
-  defaults = {
-      # on a modern MacBook, en0 is the AirPort and can monitor and send at once
-      'Darwin': {
-          'monitor': 'en0'
-      },
-      # on Linux, separate client and monitor interfaces are needed; and the
-      # monitor interface must be manually created
-      'Linux': {
-          'monitor': 'moni0'  # from the `iw` documentation
-      },
-  }
+  # Structured logging. Helps to pick up Paramiko (SSH transport layer) errors.
+  root = logging.getLogger()
+  root.setLevel(logging.INFO)
+  root.addHandler(FabricLoggingHandler())
 
   o = options.Options(optspec)
   (opt, _, extra) = o.parse(sys.argv[1:])
   if extra:
     o.fatal('did not understand supplied extra arguments.')
 
-  if not defaults.get(system):
-    raise OSError('Running on unsupported system {0}; '
-                  'supported systems are {1}'.format(system,
-                                                     ' '.join(defaults.keys())))
-
-  # Pick the report name before we run it, so it can be consistent across
-  # multiple systems.
+  # Build a directory for the report to live in.
   report_name = 'wifi-{}-{:04}'.format(time.time(), opt.steps)
-
   if opt.journal:
     dest_dir = os.path.join(os.path.dirname(opt.journal), report_name)
   else:
     dest_dir = report_name
 
-  print 'Report being written to', dest_dir
   if not os.path.exists(dest_dir):
     os.makedirs(dest_dir)
 
+  logging.info('Report being written to %s', dest_dir)
+
   # we run diagnostics, write their output to files, and gather the files into
   # a report that we present at the end of the run.
-  #
-  # entries collects Entry objects that have not been written to disk yet.
-  execute_args = {}
+  cache = collections.defaultdict(dict)
+  local_run = functools.partial(_Run, local=True)
+
+  # always gather network statistics for the system we're on
+  system = platform.system()
+  lifs = ifstats.InterfaceStats(system=system, runner=local_run)
+
+  for host, output in api.execute(lifs.Gather).items():
+    cache[host].update(output)
+  lresults = ifstats.Parse(system, cache['<local-only>'])
+  bssid = lresults.get('link', {}).get('BSSID')
+
+  if opt.skid:
+    logging.info('Getting stats from skid %s', opt.skid)
+    try:
+      status_wireless = ifstats_skids.GetStatusWireless(opt.skid)
+      cache[opt.skid]['status_wireless'] = status_wireless
+    except IOError as e:
+      logging.warning('Got IOError while connecting to skid %s: %s',
+                      opt.skid, e)
+
   if opt.remote:
     # update Fabric env for embedded systems
     api.env.update({
@@ -113,28 +134,30 @@
         'user': 'root',
         'shell': 'sh -l -c',
     })
-    execute_args['host'] = opt.remote
+    hosts = opt.remote.split(',')
 
-    wd = api.execute(_SetupTestHost, **execute_args).values()[0]
-    atexit.register(api.execute, _CleanupTestHost, wd, **execute_args)
+    wd = api.execute(_SetupTestHost, hosts=hosts).values()[0]
+    atexit.register(api.execute, _CleanupTestHost, wd, hosts=hosts)
 
     ifsystem = 'Linux'
     run = functools.partial(_Run, dirname=wd, local=False)
+
+    # Gather network statistics from remote system
+    ifs = ifstats.InterfaceStats(system=ifsystem, runner=run,
+                                 interface=opt.interface)
+    for host, output in api.execute(ifs.Gather, hosts=hosts).items():
+      cache[host].update(output)
+
+    # try to compute a bind address for iperf automatically.
+    results = ifstats.Parse(system, cache[opt.remote])
+    addr = results.get('addr', {}).get(opt.interface, '')
   else:
     ifsystem = system
-    run = functools.partial(_Run, local=True)
+    run = local_run
+    hosts = []
 
-  ifs = ifstats.InterfaceStats(system=ifsystem,
-                               runner=run,
-                               interface=opt.interface)
-
-  # since we're only executing over one host, ignore the return from `execute`
-  # that says which host it was for now.
-  cache = api.execute(ifs.Gather, **execute_args).values()[0]
-  results = ifstats.Parse(ifsystem, cache)
-
-  bssid = results['link']['BSSID']
-  addr = results.get('addr', {}).get(opt.interface, '')
+    # try to compute a bind address for iperf automatically.
+    addr = lresults.get('addr', {}).get(opt.interface, '')
 
   # because ip addr usually includes a subnet mask, which will prevent iperf
   # from binding to the address
@@ -143,9 +166,26 @@
     addr = addr[:mask]
 
   if opt.monitor:
-    tcpdump_proc, tcpdump_stderr = tcpdump.tcpdump(bssid, opt.monitor, dest_dir,
+    defaults = {
+        # on a modern MacBook, en0 is the AirPort and can both monitor and send
+        'Darwin': 'en0',
+        # on Linux, separate client and monitor interfaces are needed; and the
+        # monitor interface must be manually created
+        'Linux': 'moni0',  # from the `iw` documentation
+    }
+
+    if opt.monitor_interface:
+      monif = opt.monitor_interface
+    else:
+      monif = defaults.get(system)
+
+    if not monif:
+      logging.error('Cannot gather tcpdump: no monitor interface specified '
+                    'and autodetect failed.')
+
+    tcpdump_proc, tcpdump_stderr = tcpdump.Tcpdump(monif, bssid, dest_dir,
                                                    opt.filter)
-    print 'Gathering tcpdump in background as', tcpdump_proc.pid
+    logging.info('Gathering tcpdump in background as %d', tcpdump_proc.pid)
 
   if opt.bind and not addr:
     addr = opt.bind
@@ -155,17 +195,17 @@
   # `iperf` returns 56 if it can't reach the server, or 57 if it doesn't receive
   # a final report from it on Linux; don't abort in these cases
   with api.settings(warn_only=True):
-    cache.update(
-        api.execute(ips.RunTestSeries, opt.destination,
-                    **execute_args).values()[0]
-    )
+    for host, output in api.execute(ips.RunTestSeries, opt.destination,
+                                    hosts=hosts).items():
+      cache[host].update(output)
 
   # `isostream` won't end on its own, so we wrap it with `timeout` and accept a
   # return code of 124 (timed out) as well.
   with api.settings(ok_ret_codes=[0, 124]):
-    cache['isostream'] = api.execute(isostream.RunIsostreamTest, run,
-                                     opt.destination, time=opt.time,
-                                     **execute_args).values()[0]
+    for host, output in api.execute(isostream.RunIsostreamTest, run,
+                                    opt.destination, time=opt.time,
+                                    hosts=hosts).items():
+      cache[host]['isostream'] = output
 
   if opt.monitor:
     try:
@@ -180,9 +220,11 @@
     with open(opt.journal, 'a') as journal:
       print >> journal, report_name
 
-  for name, value in cache.items():
-    with open(os.path.join(dest_dir, name), 'w+b') as outfile:
-      outfile.write(value)
+  # TODO(willangley): write out correctly if more than one host
+  for host, items in cache.items():
+    for name, value in items.items():
+      with open(os.path.join(dest_dir, name), 'w+b') as outfile:
+        outfile.write(value)
 
 
 if __name__ == '__main__':
diff --git a/wifitables/tcpdump.py b/wifitables/tcpdump.py
index 44226bb..f29157f 100644
--- a/wifitables/tcpdump.py
+++ b/wifitables/tcpdump.py
@@ -1,12 +1,16 @@
 """tcpdump: helper module that gathers wireless network MCS using tcpdump."""
 
+import logging
 import multiprocessing
 import os
 import re
 import subprocess
 
 
-def MCSBackground(tcpdump, out):
+logger = logging.getLogger('tcpdump')
+
+
+def MCSBackground(tcpdump_proc, out):
   """Continually extract wireless MCS from `tcpdump` output.
 
   If tcpdump is actively capturing packets, this function will not return as
@@ -14,14 +18,14 @@
   activity using multiprocessing.
 
   Args:
-    tcpdump: a tcpdump process that's writing text output with Radiotap headers
-             to its stdout stream.
-    out:     Python file-like object to write MCS information to.
+    tcpdump_proc: A tcpdump process that's writing text output with Radiotap
+                  headers to its stdout stream.
+    out:          A Python file-like object to write MCS information to.
   """
   mcsre = re.compile(r'MCS (\d+)')
   x = 0
 
-  for row in iter(tcpdump.stdout.readline, b''):
+  for row in iter(tcpdump_proc.stdout.readline, b''):
     x += 1
     match = mcsre.search(row)
     if match:
@@ -57,11 +61,11 @@
   return sudo_tcpdump, out, err
 
 
-def tcpdump(bssid, interface, report_dir='', filtered=True):
+def Tcpdump(interface, bssid='', report_dir='', filtered=True):
   """Runs tcpdump in the background to gather wireless MCS."""
 
   cap = os.path.join(report_dir, 'testnetwork.pcap')
-  tcpdump_args = ['tcpdump',  '-IUnei', interface, '-w', cap]
+  tcpdump_args = ['tcpdump', '-IUnei', interface, '-w', cap]
 
   login = os.getlogin()
   if login != 'root':
@@ -70,13 +74,16 @@
     tcpdump_args = ['sudo'] + tcpdump_args + ['-Z', login]
 
   if filtered:
-    filt = ('(not subtype beacon and not subtype ack) and '
-            '(wlan addr1 {0} or wlan addr2 {0} or wlan addr3 {0})'.format(
-                bssid, bssid, bssid))
+    filt = '(not subtype beacon and not subtype ack)'
+    if bssid:
+      # pylint: disable=line-too-long
+      filt += ' and (wlan addr1 {0} or wlan addr2 {0} or wlan addr3 {0})'.format(bssid)
+    else:
+      logger.warning('Requested filtered tcpdump, but network BSSID not known.')
     tcpdump_args += [filt]
 
   err = open(os.path.join(report_dir, 'mcserr'), 'w+b')
-  tcpdump = subprocess.Popen(tcpdump_args, stderr=err)
+  tcpdump_proc = subprocess.Popen(tcpdump_args, stderr=err)
 
-  return tcpdump, err
+  return tcpdump_proc, err
 
diff --git a/wifitables/testdata/skids/status_wireless.html b/wifitables/testdata/skids/status_wireless.html
new file mode 100644
index 0000000..a1bb23e
--- /dev/null
+++ b/wifitables/testdata/skids/status_wireless.html
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head>        <title>Quantenna Communications</title>        <link rel="stylesheet" type="text/css" href="./themes/style.css" media="screen" />        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />        <meta http-equiv="expires" content="0" />        <meta http-equiv="CACHE-CONTROL" content="no-cache" /></head><script language="javascript" type="text/javascript" src="./js/cookiecontrol.js"></script><script language="javascript" type="text/javascript" src="./js/menu.js"></script><script language="javascript" type="text/javascript" src="./js/webif.js"></script><script>location.href='login.php';</script><script type="text/javascript">var privilege="";function popnew(url){        newwindow=window.open(url,'name');        if (window.focus) {newwindow.focus();}}function validate(action_name){        popnew("assoc_table.php?id="+action_name);}</script><script language="javascript">var mode = "Station";function reload(){        var tmp_index= "0";        var interface_index=tmp_index.split(",");        var cmb_if = document.getElementById("cmb_interface");        if (mode == "Station")        {                window.location.href="status_wireless.php";        }        else        {                window.location.href="status_wireless.php?id="+interface_index[cmb_if.selectedIndex];        }}function populate_interface_list(){        var cmb_if = document.getElementById("cmb_interface");        var passed_id="";        cmb_if.options.length = "1";        var tmp_index= "0";        var interface_index=tmp_index.split(",");        var tmp_text=new Array();        tmp_text[0]="wifi0(00:26:86:F0:24:1A)";        tmp_text[1]="";        tmp_text[2]="";        tmp_text[3]="";        tmp_text[4]="";        tmp_text[5]="";        tmp_text[6]="";        tmp_text[7]="";        for (var i=0; i < cmb_if.options.length; i++)        {                cmb_if.options[i].text = tmp_text[i]; cmb_if.options[i].value = interface_index[i];                if (passed_id == cmb_if.options[i].value)                {                        cmb_if.options[i].selected=true;                }        }}function onload_event(){        init_menu();        if (mode == "Access point")        {                populate_interface_list();        }        else        {                set_visible('tr_if', false);                set_visible('tr_l', false);        }}</script><body class="body" onload="onload_event();">        <div class="top">                <a class="logo" href="./status_device.php">                        <img src="./images/logo.png"/>                </a>        </div><div class="container">        <div class="left">                <script type="text/javascript">                        createMenu('Station',privilege);                </script>        </div>        <div class="right">                <div class="righttop">STATUS - WIRELESS</div>                <div class="rightmain">                        <table class="tablemain">                                <tr id="tr_if">                                        <td width="40%">Wifi Interface:</td>                                        <td width="60%">                                                <select name="cmb_interface" class="combox" id="cmb_interface" onchange="reload()">                                                </select>                                        </td>                                </tr>                                <tr id="tr_l">                                        <td class="divline" colspan="2";></td>                                </tr>                                <tr>                                        <td>Device Mode:</td>                                        <td>Station (STA)</td>                                </tr>                                <tr>                                        <td>Wireless Band:</td>                                        <td>802.11ac</td>                                </tr>                                <tr>                                        <td>Bandwidth:</td>                                        <td>80 MHz</td>                                </tr>                                <tr>                                        <td>AP Mac Address (BSSID):</td>                                        <td>00:26:86:F0:22:C9</td>                                </tr>                                <tr>                                        <td>Channel:</td>                                        <td>108</td>                                </tr>                                <tr>                                        <td>                                        Association Status:                                     </td>                                        <td valign="middle">Associated                                          <input type="button" name="btn_assoc_table" id="btn_assoc_table" value="Association Table" class="button" style="width:150px; height:23px;" onclick="validate(0);"/>                                        </td>                                </tr>                                <tr id="tr_rssi" >                                        <td>RSSI:</td>                                        <td>-20</td>                                </tr>                                <tr>                                        <td>Packets Received Successfully:</td>                                        <td>12351163</td>                                </tr>                                <tr>                                        <td>Bytes Received:</td>                                        <td>2814237224</td>                                </tr>                                <tr>                                        <td>Packets Transmitted Successfully:</td>                                        <td>9372546</td>                                </tr>                                <tr>                                        <td>Bytes Transmitted:</td>                                        <td>193033536</td>                                </tr>                                <tr>                                        <td class="divline" colspan="2";></td>                                </tr>                        </table>                        <div class="rightbottom">                                <button name="btn_refresh" id="btn_refresh" type="button" onclick="reload();"  class="button">Refresh</button>                        </div>                </div>        </div></div><div class="bottom">        <a href="help/aboutus.php">About Quantenna</a> |  <a href="help/contactus.php">Contact Us</a> | <a href="help/privacypolicy.php">Privacy Policy</a> | <a href="help/terms.php">Terms of Use</a> <br />        <div>&copy; 2013 Quantenna Communications, Inc. All Rights Reserved.</div></div></body></html>
\ No newline at end of file