# -*- coding: utf-8 -*-
"""
------------------------------------------------------------------------------
Mango 802.11 Reference Design Experiments Framework - Node Classes
------------------------------------------------------------------------------
License: Copyright 2019 Mango Communications, Inc. All rights reserved.
Use and distribution subject to terms in LICENSE.txt
------------------------------------------------------------------------------
"""
import sys
import wlan_exp.transport.node as node
import wlan_exp.version as version
import wlan_exp.defaults as defaults
import wlan_exp.cmds as cmds
import wlan_exp.device as wlan_device
__all__ = ['WlanExpNode', 'WlanExpNodeFactory']
# Fix to support Python 2.x and 3.x
if sys.version[0]=="3": long=None
# ID/Name mapping for reference software applications
# The ID values here match the <high,low>_sw_id values retrieved
# by the n.get_type() command during node init
high_sw_apps = [(defaults.WLAN_EXP_HIGH_SW_ID_AP, 'AP'),
(defaults.WLAN_EXP_HIGH_SW_ID_STA, 'STA'),
(defaults.WLAN_EXP_HIGH_SW_ID_IBSS, 'IBSS')]
low_sw_apps = [(defaults.WLAN_EXP_LOW_SW_ID_DCF, 'DCF'),
(defaults.WLAN_EXP_LOW_SW_ID_NOMAC, 'NoMAC')]
def get_high_app_name(app_id):
for a in high_sw_apps:
if a[0] == app_id:
return a[1]
return "Unknown High App ID {}".format(app_id)
def get_low_app_name(app_id):
for a in low_sw_apps:
if a[0] == app_id:
return a[1]
return "Unknown Low App ID {}".format(app_id)
[docs]class WlanExpNode(node.WlanExpTransportNode, wlan_device.WlanDevice):
"""Class for 802.11 Reference Design Experiments Framwork node.
Args:
network_config (transport.NetworkConfiguration) : Network configuration of the node
Attributes:
node_id (int): Unique identification for this node
name (str): User specified name for this node (supplied by user scripts)
description (str): String description of this node (auto-generated)
serial_number (int): Node's serial number, read from EEPROM on hardware
fpga_dna (int): Node's FPGA'a unique identification (on select hardware)
transport (transport.Transport): Node's transport object
transport_broadcast (transport.Transport): Node's broadcast transport object
wlan_mac_address (int): Wireless MAC address of the node (inherited from ``WlanDevice``)
ht_capable (bool): Indicates if device has PHY capable of HT (802.11n) rates
(inherited from ``WlanDevice``)
scheduler_interval (int): Minimum resolution (in usec) of the scheduler. This
is also the minimum time between LTG events.
log_max_size (int): Maximum size of event log (in bytes)
log_total_bytes_read (int): Number of bytes read from the event log
log_num_wraps (int): Number of times the event log has wrapped
log_next_read_index (int): Index in to event log of next read
wlan_exp_ver_major (int): ``wlan_exp`` Major version running on this node
wlan_exp_ver_minor (int): ``wlan_exp`` Minor version running on this node
wlan_exp_ver_revision (int): ``wlan_exp`` Revision version running on this node
max_tx_power_dbm(int): Maximum transmit power of the node (in dBm)
min_tx_power_dbm(int): Minimum transmit power of the node (in dBm)
"""
wlan_exp_eth_mtu = None
high_sw_id = None
low_sw_id = None
high_compilation_datetime = None
low_compilation_datetime = None
scheduler_interval = None
log_max_size = None
log_total_bytes_read = None
log_num_wraps = None
log_next_read_index = None
wlan_exp_ver_major = None
wlan_exp_ver_minor = None
wlan_exp_ver_revision = None
max_tx_power_dbm = None
min_tx_power_dbm = None
def __init__(self, network_config=None):
super(WlanExpNode, self).__init__(network_config)
(self.wlan_exp_ver_major, self.wlan_exp_ver_minor,
self.wlan_exp_ver_revision) = version.wlan_exp_ver()
self.scheduler_interval = 1
self.log_total_bytes_read = 0
self.log_num_wraps = 0
self.log_next_read_index = 0
# As of v1.5 all 802.11 Ref Design nodes are HT capable
self.ht_capable = True
def __str__(self):
msg = ""
msg += " Node ID : {0}\n".format(self.node_id)
msg += " Serial # : {0}\n".format(self.sn_str)
msg += " High App : {0}\n".format(get_high_app_name(self.high_sw_id))
msg += " Low App : {0}\n".format(get_low_app_name(self.low_sw_id))
if self.transport is not None:
msg += self.transport.__repr__()
return msg
def __repr__(self):
"""Return node name and description"""
msg = ""
if self.serial_number is not None:
msg = "Node {0} ".format(self.sn_str)
msg += "{0}/".format(get_high_app_name(self.high_sw_id))
msg += "{0}".format(get_low_app_name(self.low_sw_id))
if self.name is not None:
msg += " ({0})".format(self.name)
else:
msg += "Node not initialized."
return msg
#-------------------------------------------------------------------------
# Node Commands
#-------------------------------------------------------------------------
def update_node_info(self, hw_node_info):
"""Applies parameters to the wlan_exp node instance from the node info
structs returned from hardware
Args:
hw_node_info (list): List of InfoStruct instances, parsed from the NODE_INFO
command response. First struct must be the platform-agnostic
NODE_INFO. Additional InfoStructs are platform-specific.
"""
ni = hw_node_info[0]
# Verify the NodesConfiguration and NodeInfo serial numbers match
if self.serial_number != ni['serial_number']:
raise Exception('ERROR: serial number mismatch! Node object configured with {0}, hardware returned {1}'.format(self.serial_number, ni['serial_number']))
if self.platform_id != ni['platform_id']:
raise Exception('ERROR: platform ID mismatch! Node object configured with {0}, hardware returned {1}'.format(self.platform_id, ni['platform_id']))
# Verify the high/low software app IDs match the values set earlier in n.get_type()
if self.high_sw_id != ni['high_sw_id']:
raise Exception('ERROR: high_sw_id mismatch! Node object initialized with {0}, hardware returned {1}'.format(self.high_sw_id, ni['high_sw_id']))
if self.low_sw_id != ni['low_sw_id']:
raise Exception('ERROR: low_sw_id mismatch! Node object initialized with {0}, hardware returned {1}'.format(self.low_sw_id, ni['low_sw_id']))
# The node_info['<high,low>_sw_id'] values were already retrieved and adopted
# by the n.get_type() command earlier in the node init process
# The self.<high,low>_sw_id parameters should be updated here if the node
# init flow is updated to skip the n.get_type() step
self.node_id = ni['node_id']
self.wlan_mac_address = ni['wlan_mac_addr']
self.scheduler_interval = ni['scheduler_interval']
self.wlan_exp_ver_major = (ni['wlan_exp_version'] & 0xFF000000) >> 24
self.wlan_exp_ver_minor = (ni['wlan_exp_version'] & 0x00FF0000) >> 16
self.wlan_exp_ver_revision = (ni['wlan_exp_version'] & 0x0000FFFF)
self.check_wlan_exp_ver()
self.high_compilation_datetime = ni['high_compilation_date'] + ' ' + ni['high_compilation_time']
self.low_compilation_datetime = ni['low_compilation_date'] + ' ' + ni['low_compilation_time']
# Test the MTU - this will raise an exception if the selected MTU is still too
# high for this node-to-host link (for example, if the host and node both
# support 9kB jumbo but the intermediate switch does not)
# The self.transport.mtu parameter was initialized to the host MTU via the NetworkConfig and
# will be updated after the MTU test
# The self.wlan_exp_eth_mtu stores the value reported by the node hardware for future reference
self.wlan_exp_eth_mtu = ni['wlan_exp_eth_mtu']
if not self.test_mtu(min(self.transport.mtu, self.wlan_exp_eth_mtu)):
raise Exception('ERROR: MTU test failed for node {0} - actual MTU smaller than MTUs set in NetworkConfig {1} and NodeInfo {2}'.format(
self, self.transport.mtu, self.wlan_exp_eth_mtu))
# Set the MTU for this host/node link
# If the node hardware reports a smaller MTU, adopt the smaller value here
self.transport.mtu = min(self.transport.mtu, self.wlan_exp_eth_mtu)
# Re-configure the node's maximum response packet size using this known-valid MTU
# The current C code has a single global variable which controls the maximum size
# of any wlan_exp response. In theory two hosts could re-set this response if
# they use different MTUs. This presents an obvious race condition, something we
# can address if a multi-host experiment is ever required
# The max payload size is the MTU minus all the headers
import wlan_exp.transport.util as util
resp_hdr_len = util.get_total_header_len()
max_words = int((self.transport.mtu - resp_hdr_len)/4) # floor(bytes/4)
self.set_max_resp_words(max_words)
# Process platform-specific node info structs
# This is placeholder code for a future update where platforms define
# WlanExpNode subclasses which can implement more sophisticated
# node info processing
# For now, copy every platform node info field to a node object property
for ni in hw_node_info[1:]:
flds = ni.keys()
for f in flds:
setattr(self, f, ni[f])
#--------------------------------------------
# Log Commands
#--------------------------------------------
[docs] def log_get(self, size, offset=0, max_req_size=2**23):
"""Low level method to retrieve log data from a wlan_exp node.
Args:
size (int): Number of bytes to read from the log
offset (int, optional): Starting byte to read from the log
max_req_size (int, optional): Max request size that the transport
will fragment the request into.
Returns:
buffer (transport.Buffer):
Data from the log corresponding to the input parameters
There is no guarentee that this will return data aligned to event
boundaries. Use ``log_get_indexes()`` to get event aligned boundaries.
Log reads are not destructive. Log entries will only be destroyed by a
log reset or if the log wraps.
During a given ``log_get()`` command, the ETH_B Ethernet interface of
the node will not be able to respond to any other Ethernet packets
that are sent to the node. This could cause the node to drop
incoming wlan_exp packets from other wlan_exp instances accessing
the node. Therefore, for large requests, having a smaller
``max_req_size`` will allow the transport to fragement the command and
allow the node to be responsive to multiple hosts.
Some basic analysis shows that fragment sizes of 2**23 (8 MB)
add about 2% overhead to the receive time and each command takes less
than 1 second (~0.9 sec), which is the default transport timeout.
"""
cmd_size = size
log_size = self.log_get_size()
# C code uses size=0xFFFFFFFF (CMD_PARAM_LOG_GET_ALL_ENTRIES) as magic value to return
# all valid data from offset to end of log data array
if (cmd_size != cmds.CMD_PARAM_LOG_GET_ALL_ENTRIES) and ((size + offset) > log_size):
cmd_size = log_size - offset
print("WARNING: Trying to get {0} bytes from the log at offset {1}".format(size, offset))
print(" while log only has {0} bytes.".format(log_size))
print(" Truncating command to {0} bytes at offset {1}.".format(cmd_size, offset))
return self.send_cmd(cmds.LogGetEvents(cmd_size, offset), max_req_size=max_req_size)
[docs] def log_get_all_new(self, log_tail_pad=0, max_req_size=2**23):
"""Get all "new" entries in the log.
The ``log_get_all_new()`` method simplifies retrieving a node's
log over the course of a long experiment. This method keeps track
of what log data has already been retrieved from the node, then
requests new data as the experiment progresses.
The Python node object maintains internal state about what log data has
been read from the node to determine if there is "new" data in the log.
Since this state data is maintained in Python and not on the node
itself, it is possible for multiple host scripts to read log data using
``log_get_all_new()``. The internal state maintained for
``log_get_all_new()`` is only reset when using the node reset method
(i.e. ``node.reset(log=True)``).
When a node is adding entries to its log, it will allocate the
memory for an entry from the log and then fill in the contents. This
means at any given time, the last log entry may have incomplete
information. In the current C code all log entries are allocated and
written in a single context, so it is impossible for Python to retreive
an incomplete log entry.
In case future C code defers updating a previously-allocated log entry,
this method supports a log_tail_pad argument which will stop short of retrieving
the last bytes of the node's log data array.
Unfortunately, this means that using a ``log_tail_pad`` other than
zero will result in return data that is not aligned to a log entry
boundary. This should not be an issue if the goal is to periodically
read the log data from the node and store it in a file (as seen in
the ``log_capture_continuous.py`` example). However, this can be an
issue if trying to periodically read the log and process the log data
directly. In this case, ``log_tail_pad`` must be zero and the code
has to assume the last entry is invalid. This has not come up very
much, but please let us know if this is an issue via the forums.
Args:
log_tail_pad (int, optional): Number of bytes from the current end
of the "new" entries that will not be read during the call.
This is to deal with the case where the node is in the process
of modifying the last log entry so it my contain incomplete
data and should not be read.
Returns:
buffer (transport.Buffer):
Data from the log that contains all entries since the last
time the log was read.
"""
# FIXME: there are a few subtle behaviors that should be discussed:
# (1) This is a mostly a semantic issue, but this method will not actually
# "get all new" log entries in the case of a wrap. It will not
# retrieve around the boundary of a wrap. I think. I checked to
# to see the surprisingly-specific send_cmd() method handled
# the wrap case to issue multiple commands. I don't believe it
# does. The extreme corner case is that the log_next_read_index
# is near the end of the DRAM addresses and the log has since wrapped
# and written ~1GB of entries. This method will just return the tiny
# bit at the end and quit. You have to know to call it twice if you
# actually want all your log.
# (2) There's a nasty race that will happen if you wait too long
# between calls to n.log_get_all_new(). If the log_next_read_index
# starts to get encroached on by the node's wrapped write index, you're
# sunk. There's no way to re-align to the log entries in DRAM. I
# weakly believe this would manifest as the host trying to parse the
# first log entry and seeing arbitrary bytes rather than a valid
# log entry header. This could be improved -- it's detectable when
# this error occurs. If num_wraps from the node exceeds self.log_num_wraps
# and the next_index from the node is pretty close to our self.log_next_read_index,
# then we know we are in the Danger Zone. The best thing we can do is
# print a warning and punt on self.log_next_read_index and jump
# "forward" to self.log_next_read_index=0 -- the only entry we know
# we can align to.
import wlan_exp.transport.message as message
return_val = message.Buffer()
# Check if the log is full to interpret the indexes correctly
if (self.log_is_full()):
log_size = self.log_get_size()
read_size = log_size - self.log_next_read_index - log_tail_pad
# Read the data from the node
if (read_size > 0):
return_val = self.log_get(offset=self.log_next_read_index,
size=read_size,
max_req_size=max_req_size)
# Only increment index by how much was actually read
read_size = return_val.get_buffer_size()
if (read_size > 0):
self.log_next_read_index += read_size
else:
print("WARNING: Not able to read data from node.")
else:
pass
#print("WARNING: No new data on node.")
# Log is not full
else:
(next_index, _, num_wraps) = self.log_get_indexes()
if ((self.log_next_read_index == 0) and (self.log_num_wraps == 0)):
# This is the first read of the log by this python object
if (num_wraps != 0):
# Need to advance the num_wraps to the current num_wraps so
# that the code does not get into a bad state with log reading.
msg = "\nWARNING: On first read, the log on the node has already wrapped.\n"
msg += " Skipping the first {0} wraps of log data.\n".format(num_wraps)
print(msg)
self.log_num_wraps = num_wraps
# Check if log has wrapped
if (num_wraps == self.log_num_wraps):
# Since log has not wrapped, then read to the (next_index - log_tail_pad)
if (next_index > (self.log_next_read_index + log_tail_pad)):
return_val = self.log_get(offset=self.log_next_read_index,
size=(next_index - self.log_next_read_index - log_tail_pad),
max_req_size=max_req_size)
# Only increment index by how much was actually read
read_size = return_val.get_buffer_size()
if (read_size > 0):
self.log_next_read_index += read_size
else:
print("WARNING: Not able to read data from node.")
else:
pass
#print("WARNING: No new data on node.")
else:
# Log has wrapped. Get all the entries on the old wrap
if (next_index != 0):
#FIXME: I don't think CMD_PARAM_LOG_GET_ALL_ENTRIES will ever
# actually make its way down to the node, even if max_req_size
# is none. This is because send_cmd will always bind the maximum
# size to self.transport.rx_buffer_size.
return_val = self.log_get(offset=self.log_next_read_index,
size=cmds.CMD_PARAM_LOG_GET_ALL_ENTRIES,
max_req_size=max_req_size)
# The amount of data returned from the node should not be zero
read_size = return_val.get_buffer_size()
if (read_size > 0):
self.log_next_read_index = 0
self.log_num_wraps = num_wraps
else:
print("WARNING: Not able to read data from node.")
else:
pass
#print("WARNING: No new data on node.")
return return_val
[docs] def log_get_size(self):
"""Get the size of the node's current log (in bytes).
Returns:
num_bytes (int): Number of bytes in the log
"""
(capacity, size) = self.send_cmd(cmds.LogGetCapacity())
# Check the maximum size of the log and update the node state
if self.log_max_size is None:
self.log_max_size = capacity
else:
if (self.log_max_size != capacity):
msg = "EVENT LOG WARNING: Log capacity changed.\n"
msg += " Went from {0} bytes to ".format(self.log_max_size)
msg += "{0} bytes.\n".format(capacity)
print(msg)
self.log_max_size = capacity
return size
[docs] def log_get_capacity(self):
"""Get the total capacity of the node's log memory allocation (in bytes).
Returns:
capacity (int): Number of byte allocated for the log.
"""
return self.log_max_size
[docs] def log_get_indexes(self):
"""Get the indexes that describe the state of the event log.
Returns:
indexes (tuple):
#. oldest_index (int): Log index of the oldest event in the log
#. next_index (int): Log index where the next event will be recorded
#. num_wraps (int): Number of times the log has wrapped
"""
(next_index, oldest_index, num_wraps, _) = self.send_cmd(cmds.LogGetStatus())
# Check that the log is in a good state
if ((num_wraps < self.log_num_wraps) or
((num_wraps == self.log_num_wraps) and
(next_index < self.log_next_read_index))):
msg = "\n!!! Event Log Corrupted. Please reset the log. !!!\n"
print(msg)
return (next_index, oldest_index, num_wraps)
[docs] def log_get_flags(self):
"""Get the flags that describe the event log configuration.
Returns:
flags (int): Integer describing the configuration of the event log.
* ``0x0001`` - Logging enabled
* ``0x0002`` - Log wrapping enabled
* ``0x0004`` - Full payload logging enabled
* ``0x0008`` - Tx/Rx log entries for MPDU frames enabled
* ``0x0010`` - Tx/Rx log entries for CTRL frames enabled
"""
(_, _, _, flags) = self.send_cmd(cmds.LogGetStatus())
return flags
[docs] def log_is_full(self):
"""Return whether the log is full or not.
Returns:
status (bool): True if the log is full; False if the log is not full.
"""
(next_index, oldest_index, num_wraps, flags) = self.send_cmd(cmds.LogGetStatus())
if (((flags & cmds.CMD_PARAM_LOG_CONFIG_FLAG_WRAP) != cmds.CMD_PARAM_LOG_CONFIG_FLAG_WRAP) and
((next_index == 0) and (oldest_index == 0) and (num_wraps == (self.log_num_wraps + 1)))):
return True
else:
return False
[docs] def log_write_exp_info(self, info_type, message=None):
"""Write the experiment information provided to the log.
Args:
info_type (int): Type of the experiment info. This is an arbitrary
16 bit number chosen by the experimentor
message (int, str, bytes, optional): Information to be placed in
the event log.
Message must be able to be converted to bytearray with 'UTF-8' format.
"""
self.send_cmd(cmds.LogAddExpInfoEntry(info_type, message))
[docs] def log_write_time(self, time_id=None):
"""Adds the current time in microseconds to the log.
Args:
time_id (int, optional): User providied identifier to be used for
the TIME_INFO log entry. If none is provided, a random number
will be inserted.
"""
return self.send_cmd(cmds.NodeProcTime(cmds.CMD_PARAM_TIME_ADD_TO_LOG, cmds.CMD_PARAM_RSVD_TIME, time_id))
#--------------------------------------------
# Counts Commands
#--------------------------------------------
[docs] def get_txrx_counts(self, device_list=None):
"""Get the Tx/Rx counts data structurs from the node.
Args:
device_list (list of WlanExpNode, WlanExpNode, WlanDevice, optional):
List of devices for which to get counts. See note below for
more information.
Returns:
counts_dictionary (list of TxRxCounts, TxRxCounts):
TxRxCounts() for the device(s) specified.
The TxRxCounts() structure returned by this method can be accessed like
a dictionary and has the following fields:
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| Field | Description |
+=============================+=====================================================================================================+
| retrieval_timestamp | Value of System Time in microseconds when structure retrieved from the node |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| mac_addr | MAC address of remote node whose statics are recorded here |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| data_num_rx_bytes | Total number of bytes received in DATA packets from remote node (only non-duplicates) |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| data_num_rx_bytes_total | Total number of bytes received in DATA packets from remote node (including duplicates) |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| data_num_tx_bytes_success | Total number of bytes successfully transmitted in DATA packets to remote node |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| data_num_tx_bytes_total | Total number of bytes transmitted (successfully or not) in DATA packets to remote node |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| data_num_rx_packets | Total number of DATA packets received from remote node |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| data_num_tx_packets_success | Total number of DATA packets successfully transmitted to remote node |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| data_num_tx_packets_total | Total number of DATA packets transmitted (successfully or not) to remote node |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| data_num_tx_attempts | Total number of low-level attempts of DATA packets to remote node (includes re-transmissions) |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| mgmt_num_rx_bytes | Total number of bytes received in management packets from remote node (only non-duplicates) |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| mgmt_num_rx_bytes_total | Total number of bytes received in management packets from remote node (including duplicates) |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| mgmt_num_tx_bytes_success | Total number of bytes successfully transmitted in management packets to remote node |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| mgmt_num_tx_bytes_total | Total number of bytes transmitted (successfully or not) in management packets to remote node |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| mgmt_num_rx_packets | Total number of management packets received from remote node (only non-duplicates) |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| mgmt_num_rx_packets_total | Total number of management packets received from remote node (including duplicates) |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| mgmt_num_tx_packets_success | Total number of management packets successfully transmitted to remote node |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| mgmt_num_tx_packets_total | Total number of management packets transmitted (successfully or not) to remote node |
+-----------------------------+-----------------------------------------------------------------------------------------------------+
| mgmt_num_tx_attempts | Total number of low-level attempts of management packets to remote node (includes re-transmissions)|
+-----------------------------+-----------------------------------------------------------------------------------------------------+
If the ``device_list`` is a single device, then a single dictionary or
None is returned. If the ``device_list`` is a list of devices, then a
list of dictionaries will be returned in the same order as the devices
in the list. If any of the counts are not there, None will be inserted
in the list. If the ``device_list`` is not specified, then all the
counts on the node will be returned.
"""
# In wlan_exp versions <1.8.0 Tx/Rx counts were retrieved using the
# dedicated CountsGetTxRx command, even though the C code has stored
# counts inside the station_info structs since much earlier. As of
# 1.8.0 wlan_exp uses the existing station_info retrieval command to
# retrieve and extract Tx/Rx counts
def station_info_to_counts(si):
import wlan_exp.info as info_structs
counts_fields = info_structs.info_field_defs['TXRX_COUNTS']
# Construct a new dictionary with only the Tx/Rx counts fields
# Field name is [0] in each field definition tuple
c = {k:v for (k,v) in si.items() if k in [f[0] for f in counts_fields]}
return c
# Retrieve station_info for all requested devices
station_info_list = self.get_station_info_list(device_list=device_list)
# Initialize the return list
counts_list = [None]*len(station_info_list)
for ii,station_info in enumerate(station_info_list):
# Ignore 'None' station_info entries, returned by node
# for unknown devices in device_list
if(station_info):
counts_list[ii] = station_info_to_counts(station_info)
# If caller supplied a single device, return a scalar instead of a length=1 list
if counts_list is not None and len(counts_list) == 1:
return counts_list[0]
else:
return counts_list
#--------------------------------------------
# Local Traffic Generation (LTG) Commands
#--------------------------------------------
# Leave the old ltg_configure(flow) method for a few releases to assist users in upgrading
# scripts to use the new ltg_create(payload,schedule) syntax
def ltg_create(self, schedule, payload, start=False, start_delay=None, duration=None):
"""Create a new Local Traffic Generator (LTG) at the node. The LTG will create packets
for wireless transmission using the schedule and payload parameters specified.
Args:
schedule (ltg.Schedule): An LTG Schedule instance defining the timing of when
the node creates and queueus packets for this LTG.
payload (ltg.Payload): An LTG Payload instance defining the packet contents
which are used to construct new packets on each execution of this LTG.
start (bool): Controls whether/when this LTG is started immediately upon creation.
False: LTG is created in the stopped state; use n.ltg_start(ltg_id) to start
True: LTG is created and started immediately
start_delay (numeric): Time in seconds from when the LTG is started until it begins
creating and enqueuing packets. After the start_delay has elapsed the timing of packet
creation is defined by the schedule argument. The start_delay occurs every time this LTG
is started, whether the start occurs automatically (i.e. argument start=True) or explicitly
via `n.ltg_start()`.
duration (numeric): LTG duration in seconds, controls how long this LTG will run each
time it is started. After the duration lapses the LTG will be stopped and can be
restarted with `ltg_start()` or removed with `ltg_remove()`. Set to None for an LTG
which runs forever.
Returns:
ID (int): ID for the newly created LTG. This ID must be saved to use with other
LTG methods like `ltg_start()`, `ltg_stop()`, and `ltg_remove()`.
"""
# Need LTG module to check types of Payload/Schedule arguments
from . import ltg
# Convert user-specified times from float seconds to int microseconds, then check for valid values
if start_delay is not None:
start_delay_usec = int(float(start_delay) * 10**6)
if(start_delay_usec >= 2**32):
raise Exception('ERROR: start_delay {0} is larger than maximum of {1}'.format(start_delay, int(2**32/10.0**6)))
else:
# No start delay specified, pass 0 to have no delay in C
start_delay_usec = 0
if duration is not None:
duration_usec = int(float(duration) * 10**6)
if(duration_usec >= 2**32):
raise Exception('ERROR: duration {0} is larger than maximum of {1}'.format(duration, int(2**32/10.0**6)))
if(duration_usec == 0):
raise Exception('ERROR: duration must be non-zero; set to None for infinite duration')
else:
# No duration specified means infinite - C uses duration=0 as "run forever"
duration_usec = 0
# Verify the payload/schedule arguments are valid class instances
if(ltg.Payload not in type(payload).__bases__):
raise Exception('ERROR: payload argument of type {0} invalid - must be instance of an ltg.Payload subclass'.format(type(payload)))
if(ltg.Schedule not in type(schedule).__bases__):
raise Exception('ERROR: schedule argument of type {0} invalid - must be instance of an ltg.Schedule subclass'.format(type(schedule)))
# Create the LTGConfig struct
ltg_config = ltg.LTGConfig(bool(start), start_delay_usec, duration_usec)
# Construct and send the LTG Configure command to the node
return self.send_cmd(cmds.LTGConfigure(ltg_config, payload, schedule))
[docs] def ltg_get_status(self, ltg_id_list):
"""Get the status of the LTG flows.
Args:
ltg_id_list (list of int, int): List of LTG flow identifiers or
single LTG flow identifier
If the ltg_id_list is a single ID, then a single status tuple is
returned. If the ltg_id_list is a list of IDs, then a list of status
tuples will be returned in the same order as the IDs in the list.
Returns:
status (tuple):
#. valid (bool): Is the LTG ID valid? (True/False)
#. running (bool): Is the LTG currently running? (True/False)
#. start_timestamp (int): Timestamp when the LTG started
#. stop_timestamp (int): Timestamp when the LTG stopped
"""
ret_val = []
if (type(ltg_id_list) is list):
for ltg_id in ltg_id_list:
result = self.send_cmd(cmds.LTGStatus(ltg_id))
if not result[0]:
self._print_ltg_error(cmds.CMD_PARAM_LTG_ERROR, "get status for LTG {0}".format(ltg_id))
ret_val.append(result)
else:
result = self.send_cmd(cmds.LTGStatus(ltg_id_list))
if not result[0]:
self._print_ltg_error(cmds.CMD_PARAM_LTG_ERROR, "get status for LTG {0}".format(ltg_id_list))
ret_val.append(result)
return ret_val
[docs] def ltg_remove(self, ltg_id_list):
"""Remove the LTG flows.
Args:
ltg_id_list (list of int, int): List of LTG flow identifiers or
single LTG flow identifier
"""
if (type(ltg_id_list) is list):
for ltg_id in ltg_id_list:
status = self.send_cmd(cmds.LTGRemove(ltg_id))
self._print_ltg_error(status, "remove LTG {0}".format(ltg_id))
else:
status = self.send_cmd(cmds.LTGRemove(ltg_id_list))
self._print_ltg_error(status, "remove LTG {0}".format(ltg_id_list))
[docs] def ltg_start(self, ltg_id_list):
"""Start the LTG flow.
Args:
ltg_id_list (list of int, int): List of LTG flow identifiers or
single LTG flow identifier
"""
if (type(ltg_id_list) is list):
for ltg_id in ltg_id_list:
status = self.send_cmd(cmds.LTGStart(ltg_id))
self._print_ltg_error(status, "start LTG {0}".format(ltg_id))
else:
status = self.send_cmd(cmds.LTGStart(ltg_id_list))
self._print_ltg_error(status, "start LTG {0}".format(ltg_id_list))
[docs] def ltg_stop(self, ltg_id_list):
"""Stop the LTG flow to the given nodes.
Args:
ltg_id_list (list of int, int): List of LTG flow identifiers or
single LTG flow identifier
"""
if (type(ltg_id_list) is list):
for ltg_id in ltg_id_list:
status = self.send_cmd(cmds.LTGStop(ltg_id))
self._print_ltg_error(status, "stop LTG {0}".format(ltg_id))
else:
status = self.send_cmd(cmds.LTGStop(ltg_id_list))
self._print_ltg_error(status, "stop LTG {0}".format(ltg_id_list))
[docs] def ltg_remove_all(self):
"""Stops and removes all LTG flows on the node."""
status = self.send_cmd(cmds.LTGRemove())
self._print_ltg_error(status, "remove all LTGs")
[docs] def ltg_start_all(self):
"""Start all LTG flows on the node."""
status = self.send_cmd(cmds.LTGStart())
self._print_ltg_error(status, "start all LTGs")
[docs] def ltg_stop_all(self):
"""Stop all LTG flows on the node."""
status = self.send_cmd(cmds.LTGStop())
self._print_ltg_error(status, "stop all LTGs")
def _print_ltg_error(self, status, msg):
"""Print an LTG error message.
Args:
status (int): Status of LTG command
msg (str): Message to print as part of LTG Error
"""
if (status == cmds.CMD_PARAM_LTG_ERROR):
print("LTG ERROR: Could not {0} on '{1}'".format(msg, self.description))
#--------------------------------------------
# Configure Node Attribute Commands
#--------------------------------------------
[docs] def reset_all(self):
"""Resets all portions of a node.
This includes:
* Log
* Counts
* Local Traffic Generators (LTGs)
* Wireless Tx Queues
* BSS (i.e. all network state)
* Observed Networks (network_info_list)
* Observed Stations (station_info_list)
See the reset() command for a list of all portions of the node that
will be reset.
"""
status = self.reset(log=True,
txrx_counts=True,
ltg=True,
tx_queues=True,
bss=True,
network_list=True,
station_info_list=True)
if (status == cmds.CMD_PARAM_LTG_ERROR):
print("LTG ERROR: Could not stop all LTGs on '{0}'".format(self.description))
[docs] def reset(self, log=False, txrx_counts=False, ltg=False, tx_queues=False,
bss=False, network_list=False, station_info_list=False):
"""Resets the state of node depending on the attributes.
Args:
log (bool): Reset the log data. This will reset both the
log data on the node as well as the local variables used to
track log reads for ``log_get_all_new()``. This will
not alter any log settings set by ``log_configure()``.
txrx_counts (bool): Reset the TX/RX Counts. This will zero out
all of the packet / byte counts as well as the
``latest_txrx_timestamp`` for all nodes in the station info
list. It will also remove all promiscuous counts.
ltg (bool): Remove all LTGs. This will stop and remove
any LTGs that are on the node.
tx_queues (bool): Purge all wireless Tx queues. This will discard
all currently enqueued packets awaiting transmission at the
time the command is received. This will not discard packets
already submitted to the lower-level MAC for transmission.
bss (bool): Reset network state. This will nullify
the node's active BSS and removes all entries from the
station info list. However, it will not remove the BSS from
the network list. This will produce the following OTA
transmissions:
* For AP, a deauthentication frame to each associated station
* For STA, a disassociation frame to its AP
* For IBSS, nothing.
network_list (bool): Resets the list of observed wireless networks.
This will not remove the network_info for the node's current BSS.
station_info_list (bool): Resets the list of observed wireless clients.
This will not remove station_info entries for any associated nodes.
"""
flags = 0
if log:
flags += cmds.CMD_PARAM_NODE_RESET_FLAG_LOG
self.log_total_bytes_read = 0
self.log_num_wraps = 0
self.log_next_read_index = 0
if txrx_counts: flags += cmds.CMD_PARAM_NODE_RESET_FLAG_TXRX_COUNTS
if ltg: flags += cmds.CMD_PARAM_NODE_RESET_FLAG_LTG
if tx_queues: flags += cmds.CMD_PARAM_NODE_RESET_FLAG_TX_QUEUES
if bss: flags += cmds.CMD_PARAM_NODE_RESET_FLAG_BSS
if network_list: flags += cmds.CMD_PARAM_NODE_RESET_FLAG_NETWORK_LIST
if station_info_list: flags += cmds.CMD_PARAM_NODE_RESET_FLAG_STATION_INFO_LIST
# Send the reset command
return self.send_cmd(cmds.NodeResetState(flags))
[docs] def get_wlan_mac_address(self):
"""Get the WLAN MAC Address of the node.
Returns:
MAC Address (int):
Wireless Medium Access Control (MAC) address of the node.
"""
addr = self.send_cmd(cmds.NodeProcWLANMACAddr(cmds.CMD_PARAM_READ))
if (addr != self.wlan_mac_address):
import wlan_exp.util as util
msg = "WARNING: WLAN MAC address mismatch.\n"
msg += " Received MAC Address: {0}".format(util.mac_addr_to_str(addr))
msg += " Original MAC Address: {0}".format(util.mac_addr_to_str(self.wlan_mac_address))
print(msg)
return addr
[docs] def set_mac_time(self, time, time_id=None):
"""Sets the MAC time on the node.
Args:
time (int): Time to which the node's MAC time will
be set (int in microseconds)
time_id (int, optional): Identifier used as part of the TIME_INFO
log entry created by this command. If not specified, then a
random number will be used.
"""
if type(time) not in [int, long]:
raise AttributeError("Time must be expressed in int microseconds")
self.send_cmd(cmds.NodeProcTime(cmds.CMD_PARAM_WRITE, time, time_id))
[docs] def get_mac_time(self):
"""Gets the MAC time from the node.
Returns:
time (int): Timestamp of the MAC time of the node in int microseconds
"""
node_time = self.send_cmd(cmds.NodeProcTime(cmds.CMD_PARAM_READ, cmds.CMD_PARAM_RSVD_TIME))
return node_time[0]
[docs] def get_system_time(self):
"""Gets the system time from the node.
Returns:
Time (int): Timestamp of the System time of the node in int microseconds
"""
node_time = self.send_cmd(cmds.NodeProcTime(cmds.CMD_PARAM_READ, cmds.CMD_PARAM_RSVD_TIME))
return node_time[1]
[docs] def enable_beacon_mac_time_update(self, enable):
"""Enable / Disable MAC time update from received beacons.
Args:
enable (bool): ``True`` enables and ``False`` disables MAC time
updates from received beacons
"""
self.send_cmd(cmds.NodeConfigure(beacon_mac_time_update=enable))
[docs] def enable_ethernet_portal(self, enable):
"""Enable / Disable Ethernet Portal opertion of the node. When the portal is enabled the node
bridges its ETH A Ethernet connection to the wireless medium. When the portal is disabled the node
ignores all packets on ETH A and will not send any Ethernet transmissions on ETH A.
The Ethernet portal is enabled by default. Disabling the portal with this command is "sticky" - the
portal will remain disabled until explicity re-enabled or until the node is rebooted. Changes to BSS
state will not affect whether the Ethernet portal is enabled or disabled.
Args:
enable (bool): ``True`` enables and ``False`` disables Ethernet
portal functionality
"""
self.send_cmd(cmds.NodeConfigure(portal_enable=enable))
[docs] def set_radio_channel(self, channel):
"""Re-tune the node's RF interfaces to a new center frequency.
This method directly controls the RF hardware. This can be disruptive
to MAC operation if the node is currently associated with other nodes
in a BSS which manages its own channel state.
This method will immediately change the center frequency of the node's
RF interfaces. As a result this method can only be safely used on a
node which is not currently a member of a BSS. To change the center
frequency of nodes participating in a BSS use the
``node.configure_bss(channel=N)`` method on every node in the BSS.
Args:
channel (int): Channel index for new center frequency. Must be a
valid channel index. See ``wlan_channels`` in util.py for the
list of supported channel indexes.
"""
import wlan_exp.util as util
if channel in util.wlan_channels:
if (self.is_scanning() is True):
print('Warning: network scan is running and can overwrite the channel provided to set_radio_channel. Use stop_network_scan if needed.')
self.send_cmd(cmds.NodeProcChannel(cmds.CMD_PARAM_WRITE, channel))
else:
raise AttributeError("Channel must be in util.py wlan_channels")
[docs] def set_low_to_high_rx_filter(self, mac_header=None, fcs=None):
"""Configures the filter that controls which received packets are
passed from CPU Low to CPU High.
The filter will always pass received packets where the destination
address matches the node's MAC address. The filter can be configured
to drop or pass other packets. This filter effectively controls which
packets are written to the node's log.
Args:
mac_header (str, optional): MAC header filter. Values can be:
- ``'MPDU_TO_ME'`` -- Pass all unicast-to-me or multicast data or management packet
- ``'ALL_MPDU'`` -- Pass all data and management packets; no destination address filter
- ``'ALL'`` -- Pass all packets; no packet type or address filters
fcs (str, optional): FCS status filter. Values can be:
- ``'GOOD'`` -- Pass only packets with good checksum result
- ``'ALL'`` -- Pass packets with any checksum result
At boot the filter defaults to ``mac_header='ALL'`` and ``fcs='ALL'``.
"""
self.send_cmd(cmds.NodeSetLowToHighFilter(cmds.CMD_PARAM_WRITE, mac_header, fcs))
#------------------------
# Tx Rate commands
[docs] def set_tx_rate_data(self, mcs, phy_mode, device_list=None, update_default_unicast=None, update_default_multicast=None):
"""Sets the packet transmit rate (mcs, phy_mode) for data frames.
Transmit parameters are maintained on a per-MAC address basis.
The ``device_list`` argument can be used to select particular devices
for which the Tx parameter update applies. The ``update_default_x``
arguments can be used to specify that the provided power should be
used for any future additions to the node's device list.
Args:
mcs (int): Modulation and coding scheme (MCS) index (in [0 .. 7])
phy_mode (str, int): PHY mode. Must be one of:
* ``'NONHT'``: Use 802.11 (a/g) rates
* ``'HTMF'``: Use 802.11 (n) rates
device_list (list of WlanExpNode / WlanDevice
or 'ALL_UNICAST' or 'ALL_MULTICAST' or 'ALL', optional):
List of 802.11 devices or single 802.11 device for which to set the
unicast packet Tx power to 'power'. A value of 'ALL_UNICAST' will
apply the specified power to all unicast receiver addresses. A value
of `ALL_MULTICAST` will apply it to all multicast receiver addresses.
A value of 'ALL' will apply the specified power to all addresses.
update_default_unicast (bool): set the default unicast Tx params to the
provided 'power'.
update_default_multicast (bool): set the default multicast Tx params to the
provided 'power'.
One of ``device_list`` or ``update_default_unicast`` or ``update_default_multicast``
must be set.
"""
if self._check_allowed_rate(mcs=mcs, phy_mode=phy_mode):
self._node_set_tx_param(cmds.NodeProcTxRate, 'rate', (mcs, phy_mode),
'data', device_list, update_default_unicast, update_default_multicast)
else:
self._check_allowed_rate(mcs=mcs, phy_mode=phy_mode, verbose=True)
raise AttributeError("Tx rate, (mcs, phy_mode) tuple, not supported by the design. See above error message.")
[docs] def set_tx_rate_mgmt(self, mcs, phy_mode, device_list=None, update_default_unicast=None, update_default_multicast=None):
"""Sets the packet transmit rate (mcs, phy_mode) for management frames.
Transmit parameters are maintained on a per-MAC address basis.
The ``device_list`` argument can be used to select particular devices
for which the Tx parameter update applies. The ``update_default_x``
arguments can be used to specify that the provided power should be
used for any future additions to the node's device list.
Args:
mcs (int): Modulation and coding scheme (MCS) index (in [0 .. 7])
phy_mode (str, int): PHY mode. Must be one of:
* ``'NONHT'``: Use 802.11 (a/g) rates
* ``'HTMF'``: Use 802.11 (n) rates
device_list (list of WlanExpNode / WlanDevice
or 'ALL_UNICAST' or 'ALL_MULTICAST' or 'ALL', optional):
List of 802.11 devices or single 802.11 device for which to set the
unicast packet Tx power to 'power'. A value of 'ALL_UNICAST' will
apply the specified power to all unicast receiver addresses. A value
of `ALL_MULTICAST` will apply it to all multicast receiver addresses.
A value of 'ALL' will apply the specified power to all addresses.
update_default_unicast (bool): set the default unicast Tx params to the
provided 'power'.
update_default_multicast (bool): set the default multicast Tx params to the
provided 'power'.
One of ``device_list`` or ``update_default_unicast`` or ``update_default_multicast``
must be set.
"""
if self._check_allowed_rate(mcs=mcs, phy_mode=phy_mode):
self._node_set_tx_param(cmds.NodeProcTxRate, 'rate', (mcs, phy_mode),
'mgmt', device_list, update_default_unicast, update_default_multicast)
else:
self._check_allowed_rate(mcs=mcs, phy_mode=phy_mode, verbose=True)
raise AttributeError("Tx rate, (mcs, phy_mode) tuple, not supported by the design. See above error message.")
#------------------------
# Tx Antenna Mode commands
[docs] def set_tx_ant_mode_data(self, ant_mode, device_list=None, update_default_unicast=None, update_default_multicast=None):
"""Sets the packet transmit antenna mode for data frames.
Transmit parameters are maintained on a per-MAC address basis.
The ``device_list`` argument can be used to select particular devices
for which the Tx parameter update applies. The ``update_default_x``
arguments can be used to specify that the provided power should be
used for any future additions to the node's device list.
Args:
ant_mode (str): Antenna mode; must be one of:
* ``'RF_A'``: transmit on RF A interface
* ``'RF_B'``: transmit on RF B interface
device_list (list of WlanExpNode / WlanDevice
or 'ALL_UNICAST' or 'ALL_MULTICAST' or 'ALL', optional):
List of 802.11 devices or single 802.11 device for which to set the
unicast packet Tx power to 'power'. A value of 'ALL_UNICAST' will
apply the specified power to all unicast receiver addresses. A value
of `ALL_MULTICAST` will apply it to all multicast receiver addresses.
A value of 'ALL' will apply the specified power to all addresses.
update_default_unicast (bool): set the default unicast Tx params to the
provided 'ant_mode'.
update_default_multicast (bool): set the default multicast Tx params to the
provided 'ant_mode'.
One of ``device_list`` or ``update_default_unicast`` or ``update_default_multicast``
must be set.
"""
if ant_mode is None:
raise AttributeError("Invalid ant_mode: {0}".format(ant_mode))
self._node_set_tx_param(cmds.NodeProcTxAntMode, 'antenna_mode',
ant_mode, 'data', device_list, update_default_unicast, update_default_multicast)
[docs] def set_tx_ant_mode_mgmt(self, ant_mode, device_list=None, update_default_unicast=None, update_default_multicast=None):
"""Sets the packet transmit antenna mode for management frames.
Transmit parameters are maintained on a per-MAC address basis.
The ``device_list`` argument can be used to select particular devices
for which the Tx parameter update applies. The ``update_default_x``
arguments can be used to specify that the provided power should be
used for any future additions to the node's device list.
Args:
ant_mode (str): Antenna mode; must be one of:
* ``'RF_A'``: transmit on RF A interface
* ``'RF_B'``: transmit on RF B interface
device_list (list of WlanExpNode / WlanDevice
or 'ALL_UNICAST' or 'ALL_MULTICAST' or 'ALL', optional):
List of 802.11 devices or single 802.11 device for which to set the
unicast packet Tx power to 'power'. A value of 'ALL_UNICAST' will
apply the specified power to all unicast receiver addresses. A value
of `ALL_MULTICAST` will apply it to all multicast receiver addresses.
A value of 'ALL' will apply the specified power to all addresses.
update_default_unicast (bool): set the default unicast Tx params to the
provided 'ant_mode'.
update_default_multicast (bool): set the default multicast Tx params to the
provided 'ant_mode'.
One of ``device_list`` or ``update_default_unicast`` or ``update_default_multicast``
must be set.
"""
if ant_mode is None:
raise AttributeError("Invalid ant_mode: {0}".format(ant_mode))
self._node_set_tx_param(cmds.NodeProcTxAntMode, 'antenna_mode',
ant_mode, 'mgmt', device_list, update_default_unicast, update_default_multicast)
#------------------------
# Rx Antenna Mode commands
[docs] def set_rx_ant_mode(self, ant_mode):
"""Sets the receive antenna mode for a node.
Args:
ant_mode (str): Antenna mode; must be one of:
* ``'RF_A'``: receive on RF A interface
* ``'RF_B'``: receive on RF B interface
"""
if ant_mode is None:
raise AttributeError("Invalid ant_mode: {0}".format(ant_mode))
self.send_cmd(cmds.NodeProcRxAntMode(cmds.CMD_PARAM_WRITE, ant_mode))
[docs] def get_rx_ant_mode(self):
"""Gets the current receive antenna mode for a node.
Returns:
ant_mode (str):
Rx Antenna mode; must be one of:
* ``'RF_A'``: receive on RF A interface
* ``'RF_B'``: receive on RF B interface
"""
return self.send_cmd(cmds.NodeProcRxAntMode(cmds.CMD_PARAM_READ))
#------------------------
# Tx Power commands
def set_tx_power_data(self, power, device_list=None, update_default_unicast=None, update_default_multicast=None):
"""Sets the transmit power for data frames.
This function is used to set the tranmsit power for frames of type
data. Transmit parameters are maintained on a per-MAC address basis.
The ``device_list`` argument can be used to select particular devices
for which the Tx parameter update applies. The ``update_default_x``
arguments can be used to specify that the provided power should be
used for any future additions to the node's device list.
Args:
power (int): Transmit power in dBm (a value between
``node.max_tx_power_dbm`` and ``node.min_tx_power_dbm``)
device_list (list of WlanExpNode / WlanDevice
or 'ALL_UNICAST' or 'ALL_MULTICAST' or 'ALL', optional):
List of 802.11 devices or single 802.11 device for which to set the
unicast packet Tx power to 'power'. A value of 'ALL_UNICAST' will
apply the specified power to all unicast receiver addresses. A value
of `ALL_MULTICAST` will apply it to all multicast receiver addresses.
A value of 'ALL' will apply the specified power to all addresses.
update_default_unicast (bool): set the default unicast Tx params to the
provided 'power'.
update_default_multicast (bool): set the default multicast Tx params to the
provided 'power'.
One of ``device_list`` or ``update_default_unicast`` or ``update_default_multicast``
must be set.
"""
self._node_set_tx_param(cmds.NodeProcTxPower, 'tx power',
(power, self.max_tx_power_dbm, self.min_tx_power_dbm),
'data', device_list, update_default_unicast, update_default_multicast)
def set_tx_power_mgmt(self, power, device_list=None, update_default_unicast=None, update_default_multicast=None):
"""Sets the transmit power for management frames.
This function is used to set the tranmsit power for frames of type
management. Transmit parameters are maintained on a per-MAC address basis.
The ``device_list`` argument can be used to select particular devices
for which the Tx parameter update applies. The ``update_default_x``
arguments can be used to specify that the provided power should be
used for any future additions to the node's device list.
Args:
power (int): Transmit power in dBm (a value between
``node.max_tx_power_dbm`` and ``node.min_tx_power_dbm``)
device_list (list of WlanExpNode / WlanDevice
or 'ALL_UNICAST' or 'ALL_MULTICAST' or 'ALL', optional):
List of 802.11 devices or single 802.11 device for which to set the
unicast packet Tx power to 'power'. A value of 'ALL_UNICAST' will
apply the specified power to all unicast receiver addresses. A value
of `ALL_MULTICAST` will apply it to all multicast receiver addresses.
A value of 'ALL' will apply the specified power to all addresses.
update_default_unicast (bool): set the default unicast Tx params to the
provided 'power'.
update_default_multicast (bool): set the default multicast Tx params to the
provided 'power'.
One of ``device_list`` or ``update_default_unicast`` or ``update_default_multicast``
must be set.
"""
self._node_set_tx_param(cmds.NodeProcTxPower, 'tx power',
(power, self.max_tx_power_dbm, self.min_tx_power_dbm),
'mgmt', device_list, update_default_unicast, update_default_multicast)
[docs] def set_tx_power_ctrl(self, power):
"""Sets the control packet transmit power of the node.
Only the Tx power of the control packets can be set via wlan_exp. The
rate of control packets is determined by the 802.11 standard and
control packets will be sent on whatever antenna that cause the control
packet to be generated (ie an ack for a reception will go out on the
same antenna on which the reception occurred).
Args:
power (int): Transmit power in dBm (a value between
``node.max_tx_power_dbm`` and ``node.min_tx_power_dbm``)
"""
self.send_cmd(cmds.NodeProcTxPower(cmds.CMD_PARAM_WRITE, cmds.CMD_PARAM_TXPARAM_CTRL, 0, 0,
(power, self.max_tx_power_dbm, self.min_tx_power_dbm), cmds.CMD_PARAM_TXPARAM_ADDR_NONE))
[docs] def set_tx_power(self, power):
"""Sets the transmit power of the node.
This command will set all transmit power fields on the node to the same
value:
* Default Unicast Management Packet Tx Power for new station infos
* Default Unicast Data Packet Tx Power for new station infos
* Default Multicast Management Packet Tx Power for new station infos
* Default Multicast Data Packet Tx Power for new station infos
* Control Packet Tx Power
It will also update the transmit power for any frames destined for any
known stations.
Args:
power (int): Transmit power in dBm (a value between
``node.max_tx_power_dbm`` and ``node.min_tx_power_dbm``)
"""
self.send_cmd(cmds.NodeProcTxPower(cmds.CMD_PARAM_WRITE, cmds.CMD_PARAM_TXPARAM_ALL, 1, 1,
(power, self.max_tx_power_dbm, self.min_tx_power_dbm), cmds.CMD_PARAM_TXPARAM_ADDR_ALL))
#------------------------
# Other commands
[docs] def set_low_param(self, param_id, param_values):
"""Set a CPU Low parameter
This command provides a generic data pipe to set parameters in CPU Low.
Currently supported parameters are defined in cmds.py and use the
naming convention: CMD_PARAM_LOW_PARAM_* In some cases, additional
commands have been added to the node that utilize ``set_low_param()``
in order to add error checking.
See http://warpproject.org/trac/wiki/802.11/wlan_exp/Extending
for more information about how to extend ``set_low_param()`` to
control additional parameters.
Args:
param_id (int): ID of parameter to be set
param_values (list of int): Value(s) to set the parameter
"""
if(param_values is not None):
try:
v0 = param_values[0]
except TypeError:
v0 = param_values
if((type(v0) is not int) and (type(v0) is not long)) or (v0 >= 2**32):
raise Exception('ERROR: parameter values must be scalar or iterable of ints in [0,2^32-1]!')
try:
values = list(param_values)
except TypeError:
values = [param_values]
self.send_cmd(cmds.NodeLowParam(cmds.CMD_PARAM_WRITE, param_id=param_id, param_values=values))
def configure_ofdm_pkt_det(self, corr_thresh, energy_thresh, min_dur = 4, post_wait = 0x3F, require_pkt_det = False):
"""Configures the OFDM auto-correlation packet detector thresholds. Set
both thresholds to 0xFFFF to disable OFDM packet detections.
Args:
corr_thresh (int): Auto-correlation threshold (in [1, 255])
energy_thresh (int): Energy threshold (in [1, 16383])
min_dur (int): Minimum duration ([0, 15])
post_wait (int): Post detection reset ([0, 63])
require_pkt_det (bool): Require packet detection prior to PHY Rx
"""
if (corr_thresh < 1) or (corr_thresh > 255):
raise AttributeError("'corr_thresh' must be in [1 .. 255].")
if (energy_thresh < 1) or (energy_thresh > 16383):
raise AttributeError("'energy_thresh' must be in [1 .. 16383].")
if (min_dur < 0) or (min_dur > 15):
raise AttributeError("'min_dur' must be in [0 .. 15].")
if (post_wait < 0) or (post_wait > 63):
raise AttributeError("'post_wait' must be in [0 .. 63].")
if type(require_pkt_det) is not bool:
raise AttributeError("require_pkt_det must be a bool. Provided {0}.".format(type(require_pkt_det)))
return self.set_low_param(cmds.CMD_PARAM_LOW_PARAM_OFDM_PKT_DET_THRESH, (corr_thresh, energy_thresh, min_dur, post_wait, require_pkt_det))
def configure_dsss_pkt_det(self, corr_thresh, energy_thresh, require_pkt_det = False):
"""Configures the DSSS auto-correlation packet detector thresholds.
Args:
corr_thresh (int): Auto-correlation threshold (in [1, 255])
energy_thresh (int): Energy threshold (in [1, 16383])
require_pkt_det (bool): Require packet detection prior to PHY Rx
"""
if (corr_thresh < 1) or (corr_thresh > 255):
raise AttributeError("'corr_thresh' must be in [1 .. 255].")
if (energy_thresh < 1) or (energy_thresh > 16383):
raise AttributeError("'energy_thresh' must be in [1 .. 16383].")
if type(require_pkt_det) is not bool:
raise AttributeError("require_pkt_det must be a bool. Provided {0}.".format(type(require_pkt_det)))
return self.set_low_param(cmds.CMD_PARAM_LOW_PARAM_DSSS_PKT_DET_THRESH, (corr_thresh, energy_thresh, require_pkt_det))
[docs] def set_dcf_param(self, param_name, param_val):
"""Configures parameters of the DCF MAC in CPU Low.
These parameters are write-only. These parameters only affect nodes
running the DCF in CPU Low. Other MAC implementations should ignore
these parameter updates.
Args:
param_name (str): Name of the param to change (see table below)
param_val (int): Value to set for param_name (see table below)
This method currently implements the following parameters:
.. list-table::
:header-rows: 1
:widths: 15 20 60
* - Name
- Valid Values
- Description
* - ``'rts_thresh'``
- [0 .. 65535]
- Threshold in bytes for maximum length
packet which will not trigger RTS handshake
* - ``'short_retry_limit'`` and
``'long_retry_limit'``
- [0 .. 10]
- DCF retry limits, controls maximum number of retransmissions for short and long packets
See `retransmissions <http://warpproject.org/trac/wiki/802.11/MAC/Lower/Retransmissions>`_.
for more details.
* - ``'cw_exp_min'`` and
``'cw_exp_max'``
- [0 .. 16]
- Contention window exponent bounds. Contention window is set to random number in [0, (2^CW - 1)],
where CW is bounded by [cw_exp_min, cw_exp_max]
"""
if type(param_name) is not str:
raise AttributeError("param_name must be a str. Provided {0}.".format(type(param_name)))
if type(param_val) is not int:
raise AttributeError("param_val must be an int. Provided {0}.".format(type(param_val)))
# Process paramater
if (param_name == 'rts_thresh'):
if ((param_val < 0) or (param_val > 65535)):
raise AttributeError("'rts_thresh' must be in [0 .. 65535].")
if self.low_sw_id != defaults.WLAN_EXP_LOW_SW_ID_DCF:
raise Exception('ERROR: rts_thresh only valid with DCF application in CPU Low!')
self.set_low_param(param_id=cmds.CMD_PARAM_LOW_PARAM_DCF_RTS_THRESH, param_values=param_val)
elif (param_name == 'short_retry_limit'):
if ((param_val < 0) or (param_val > 65535)):
raise AttributeError("'short_retry_limit' must be in [0 .. 65535].")
if self.low_sw_id != defaults.WLAN_EXP_LOW_SW_ID_DCF:
raise Exception('ERROR: short_retry_limit only valid with DCF application in CPU Low!')
self.set_low_param(param_id=cmds.CMD_PARAM_LOW_PARAM_DCF_DOT11SHORTRETRY, param_values=param_val)
elif (param_name == 'long_retry_limit'):
if ((param_val < 0) or (param_val > 65535)):
raise AttributeError("'long_retry_limit' must be in [0 .. 65535].")
if self.low_sw_id != defaults.WLAN_EXP_LOW_SW_ID_DCF:
raise Exception('ERROR: long_retry_limit only valid with DCF application in CPU Low!')
self.set_low_param(param_id=cmds.CMD_PARAM_LOW_PARAM_DCF_DOT11LONGRETRY, param_values=param_val)
elif (param_name == 'cw_exp_min'):
if ((param_val < 0) or (param_val > 16)):
raise AttributeError("'cw_exp_min' must be in [0 .. 16].")
if self.low_sw_id != defaults.WLAN_EXP_LOW_SW_ID_DCF:
raise Exception('ERROR: cw_exp_min only valid with DCF application in CPU Low!')
self.set_low_param(param_id=cmds.CMD_PARAM_LOW_PARAM_DCF_CW_EXP_MIN, param_values=param_val)
elif (param_name == 'cw_exp_max'):
if ((param_val < 0) or (param_val > 16)):
raise AttributeError("'cw_exp_max' must be in [0 .. 16].")
if self.low_sw_id != defaults.WLAN_EXP_LOW_SW_ID_DCF:
raise Exception('ERROR: cw_exp_max only valid with DCF application in CPU Low!')
self.set_low_param(param_id=cmds.CMD_PARAM_LOW_PARAM_DCF_CW_EXP_MAX, param_values=param_val)
else:
msg = "param_name must be one of the following strings:\n"
msg += " 'rts_thresh', 'short_retry_limit', 'long_retry_limit', \n"
msg += " 'phy_cs_thresh', 'cw_exp_min', 'cw_exp_max' \n"
msg += "Provided '{0}'".format(param_name)
raise AttributeError(msg)
[docs] def set_phy_samp_rate(self, phy_samp_rate):
"""Sets the PHY sample rate (in MSps)
Args:
phy_samp_rate (int):
PHY sample rate in MSps (10, 20, 40). Default is 20 MSps.
"""
if (phy_samp_rate not in [10, 20, 40]):
raise AttributeError("'phy_samp_rate' must be in [10, 20, 40].")
self.set_low_param(param_id=cmds.CMD_PARAM_LOW_PARAM_PHY_SAMPLE_RATE, param_values=phy_samp_rate)
[docs] def set_random_seed(self, high_seed=None, low_seed=None, gen_random=False):
"""Sets the random number generator seed on the node.
Args:
high_seed (int, optional): Set the random number generator seed
on CPU high
low_seed (int, optional): Set the random number generator seed
on CPU low
gen_random (bool, optional): If high_seed or low_seed is not
provided, then generate a random seed for the generator.
"""
import random
max_seed = 2**32 - 1
min_seed = 0
if (high_seed is None):
if gen_random:
high_seed = random.randint(min_seed, max_seed)
else:
if (high_seed > max_seed) or (high_seed < min_seed):
msg = "Seed must be an integer between [{0}, {1}]".format(min_seed, max_seed)
raise AttributeError(msg)
if (low_seed is None):
if gen_random:
low_seed = random.randint(min_seed, max_seed)
else:
if (low_seed > max_seed) or (low_seed < min_seed):
msg = "Seed must be an integer between [{0}, {1}]".format(min_seed, max_seed)
raise AttributeError(msg)
self.send_cmd(cmds.NodeProcRandomSeed(cmds.CMD_PARAM_WRITE, high_seed, low_seed))
def set_max_amsdu_length(self, max_amsdu_len=None):
"""Sets the maximum A-MSDU length on the node
Args:
max_amsdu_len: the maximum length of any transmitted A-MSDU
"""
self.send_cmd(cmds.NodeProcMaxAMSDULength(cmds.CMD_PARAM_WRITE, max_amsdu_len))
[docs] def enable_dsss(self, enable):
"""Enable / Disable DSSS receptions on the node
By default DSSS receptions are enabled on the node when possible
(ie PHY sample rate is 20 MHz and Channel is in 2.4 GHz band)
Args:
enable (bool):
* True - enable DSSS receptions on the node
* False - disable DSSS receptions on the node
"""
self.send_cmd(cmds.NodeConfigure(dsss_enable=enable))
def enable_ofdm_rx(self, enable):
"""Enable / Disable OFDM receptions on the node
OFDM receptions are enabled by default. The OFDM Rx pipeline can
be disabled which will block all OFDM packet detections and OFDM
Rx attempts. When OFDM Rx is disabled only the DSSS Rx pipeline
will attempt to receive new packets.
Args:
enable (bool):
* True - enable OFDM receptions on the node
* False - disable OFDM receptions on the node
"""
return self.set_low_param(cmds.CMD_PARAM_LOW_PARAM_OFDM_RX_EN, int(enable))
[docs] def set_print_level(self, level):
"""Set the verbosity of the wlan_exp output to the node's UART.
Args:
level (int): Printing level (defaults to ``WARNING``)
Valid print levels can be found in ``wlan_exp.util.uart_print_levels``:
* ``NONE`` - Do not print messages
* ``ERROR`` - Only print error messages
* ``WARNING`` - Print error and warning messages
* ``INFO`` - Print error, warning and info messages
* ``DEBUG`` - Print error, warning, info and debug messages
"""
import wlan_exp.util as util
valid_levels = ['NONE', 'ERROR', 'WARNING', 'INFO', 'DEBUG',
util.uart_print_levels['NONE'],
util.uart_print_levels['ERROR'],
util.uart_print_levels['WARNING'],
util.uart_print_levels['INFO'],
util.uart_print_levels['DEBUG']]
if (level in valid_levels):
self.send_cmd(cmds.NodeConfigure(print_level=level))
else:
msg = "\nInvalid print level {0}. Print level must be one of: \n".format(level)
msg += " ['NONE', 'ERROR', 'WARNING', 'INFO', 'DEBUG', \n"
msg += " uart_print_levels['NONE'], uart_print_levels['ERROR'],\n"
msg += " uart_print_levels['WARNING'], uart_print_levels['INFO'],\n"
msg += " uart_print_levels['DEBUG']]"
raise ValueError(msg)
#--------------------------------------------
# Internal methods to view / configure node attributes
#--------------------------------------------
def _check_allowed_rate(self, mcs, phy_mode, verbose=False):
"""Check that rate parameters are allowed
Args:
mcs (int): Modulation and coding scheme (MCS) index
phy_mode (str, int): PHY mode (from util.phy_modes)
Returns:
valid (bool): Are all parameters valid?
"""
return self._check_supported_rate(mcs, phy_mode, verbose)
def _check_supported_rate(self, mcs, phy_mode, verbose=False):
"""Checks that the selected rate parameters are supported by the
current MAC and PHY implementation. This method only checks if a
rate can be used in hardware. The application-specific method
_check_allowed_rate() should be used to confirm a selected rate is
both supported and allowed given the currnet MAC and network state.
Args:
mcs (int): Modulation and coding scheme (MCS) index (in [0 .. 7])
phy_mode (str, int): PHY mode. Must be one of:
* ``'NONHT'``: Use 802.11 (a/g) rates
* ``'HTMF'``: Use 802.11 (n) rates
Returns:
rate_suppored (bool): True if the specified rate is supported
"""
import wlan_exp.util as util
rate_ok = True
if ((mcs < 0) or (mcs > 7)):
if (verbose):
print("Invalid MCS {0}. MCS must be integer in [0 .. 7]".format(mcs))
rate_ok = False
if (phy_mode not in ['NONHT', 'HTMF', util.phy_modes['NONHT'], util.phy_modes['HTMF']]):
if (verbose):
print("Invalid PHY mode {0}. PHY mode must be one of ['NONHT', 'HTMF', phy_modes['NONHT'], phy_modes['HTMF']]".format(phy_mode))
rate_ok = False
return rate_ok
def _node_set_tx_param(self, cmd, param_name, param, frametype,
device_list=None, update_default_unicast=None, update_default_multicast=None):
"""Sets the data & management transmit parameters of the node.
Args:
cmd (Cmd): Command to be used to set param
param_name (str): Name of parameter for error messages
param (int): Parameter to be set
frametype(str): `data` or `mgmt`
device_list (list of WlanExpNode / WlanDevice
or 'ALL_UNICAST' or 'ALL_MULTICAST' or 'ALL', optional):
List of 802.11 devices or single 802.11 device for which to set the
unicast packet Tx power to 'power'. A value of 'ALL_UNICAST' will
apply the specified power to all unicast receiver addresses. A value
of `ALL_MULTICAST` will apply it to all multicast receiver addresses.
A value of 'ALL' will apply the specified power to all addresses.
update_default_unicast (bool): set the default unicast Tx params to the
provided 'power'.
update_default_multicast (bool): set the default multicast Tx params to the
provided 'power'.
One of ``device_list`` or ``default`` must be set.
"""
if (device_list is None) and (update_default_unicast is None) and (update_default_multicast is None):
msg = "\nCannot set the unicast transmit {0}:\n".format(param_name)
msg += " Must specify either a list of devices, 'ALL' current station infos,\n"
msg += " or update_default_unicast or update_default_multicast {0}.".format(param_name)
raise ValueError(msg)
if( (update_default_unicast is True) or (device_list == 'ALL_UNICAST') or (device_list == 'ALL') ):
update_default_unicast = 1
else:
update_default_unicast = 0
if( (update_default_multicast is True) or (device_list == 'ALL_MULTICAST') or (device_list == 'ALL') ):
update_default_multicast = 1
else:
update_default_multicast = 0
if(frametype == 'data'):
if(device_list == 'ALL_UNICAST'):
self.send_cmd(cmd(cmds.CMD_PARAM_WRITE, cmds.CMD_PARAM_TXPARAM_DATA, update_default_unicast, update_default_multicast, param, cmds.CMD_PARAM_TXPARAM_ADDR_ALL_UNICAST))
elif(device_list == 'ALL_MULTICAST'):
self.send_cmd(cmd(cmds.CMD_PARAM_WRITE, cmds.CMD_PARAM_TXPARAM_DATA, update_default_unicast, update_default_multicast, param, cmds.CMD_PARAM_TXPARAM_ADDR_ALL_MULTICAST))
elif(device_list == 'ALL'):
self.send_cmd(cmd(cmds.CMD_PARAM_WRITE, cmds.CMD_PARAM_TXPARAM_DATA, update_default_unicast, update_default_multicast, param, cmds.CMD_PARAM_TXPARAM_ADDR_ALL))
elif(device_list is not None):
try:
for device in device_list:
self.send_cmd(cmd(cmds.CMD_PARAM_WRITE, cmds.CMD_PARAM_TXPARAM_DATA, update_default_unicast, update_default_multicast, param, cmds.CMD_PARAM_TXPARAM_ADDR_SINGLE, device))
except TypeError:
self.send_cmd(cmd(cmds.CMD_PARAM_WRITE, cmds.CMD_PARAM_TXPARAM_DATA, update_default_unicast, update_default_multicast, param, cmds.CMD_PARAM_TXPARAM_ADDR_SINGLE, device_list))
else:
self.send_cmd(cmd(cmds.CMD_PARAM_WRITE, cmds.CMD_PARAM_TXPARAM_DATA, update_default_unicast, update_default_multicast, param, cmds.CMD_PARAM_TXPARAM_ADDR_NONE))
elif(frametype == 'mgmt'):
if(device_list == 'ALL_UNICAST'):
self.send_cmd(cmd(cmds.CMD_PARAM_WRITE, cmds.CMD_PARAM_TXPARAM_MGMT, update_default_unicast, update_default_multicast, param, cmds.CMD_PARAM_TXPARAM_ADDR_ALL_UNICAST))
elif(device_list == 'ALL_MULTICAST'):
self.send_cmd(cmd(cmds.CMD_PARAM_WRITE, cmds.CMD_PARAM_TXPARAM_MGMT, update_default_unicast, update_default_multicast, param, cmds.CMD_PARAM_TXPARAM_ADDR_ALL_MULTICAST))
elif(device_list == 'ALL'):
self.send_cmd(cmd(cmds.CMD_PARAM_WRITE, cmds.CMD_PARAM_TXPARAM_MGMT, update_default_unicast, update_default_multicast, param, cmds.CMD_PARAM_TXPARAM_ADDR_ALL))
elif(device_list is not None):
try:
for device in device_list:
self.send_cmd(cmd(cmds.CMD_PARAM_WRITE, cmds.CMD_PARAM_TXPARAM_MGMT, update_default_unicast, update_default_multicast, param, cmds.CMD_PARAM_TXPARAM_ADDR_SINGLE, device))
except TypeError:
self.send_cmd(cmd(cmds.CMD_PARAM_WRITE, cmds.CMD_PARAM_TXPARAM_MGMT, update_default_unicast, update_default_multicast, param, cmds.CMD_PARAM_TXPARAM_ADDR_SINGLE, device_list))
else:
self.send_cmd(cmd(cmds.CMD_PARAM_WRITE, cmds.CMD_PARAM_TXPARAM_MGMT, update_default_unicast, update_default_multicast, param, cmds.CMD_PARAM_TXPARAM_ADDR_NONE))
#--------------------------------------------
# Scan Commands
#--------------------------------------------
[docs] def set_scan_parameters(self, time_per_channel=None, num_probe_tx_per_channel=None, channel_list=None, ssid=None):
"""Set the paramters of the wireless network scan state machine.
Args:
time_per_channel (float, optional): Time (in float sec) to spend
on each channel. A value of None will not modify the current
time per channel
num_probe_tx_per_channel (int, optional): Number of probe requests
transmitted while on each channel. A value of None will not
modify the current number of probe requests per channel.
channel_list (list of int optional): Channel(s) to scan; A value
of None will not modify the current channel list.
ssid (str, optional): SSID to scan for (as part of probe request);
A value of None will not modify the current SSID.
Setting ``num_probe_tx_per_chan`` to a non-zero value will enable
active scanning. The node will transmit broadcast Probe Request packets
on every channel and will gather network information from received
Probe Response and Beacon packets. Setting ``num_probe_tx_per_chan=0``
will enable passive scanning. In this mode the node will not transmit
any Probe Request packets and network information will be gathered only
from received Beacon packets.
The blank SSID (``ssid=""``) is interpretted as a wildcard and will
solicit Probe Response packets from networks with any SSID. "Closed"
networks do not respond to the wildcard SSID. These networks will still
be discovered via Beacon receptions.
If the channel list / SSID is not specified, then it will not be
updated on the node (ie it will use the current channel list / SSID)
"""
# Check time_per_channel
if time_per_channel is not None:
try:
time_per_channel = float(time_per_channel)
except:
raise AttributeError("time_per_channel must be expressable as a float.")
# Check channel_list
if channel_list is not None:
tmp_chan_list = []
if type(channel_list) is str:
# Process pre-defined strings
import wlan_exp.util as util
if (channel_list == 'ALL'):
tmp_chan_list = util.wlan_channels
elif (channel_list == 'ALL_2.4GHZ'):
tmp_chan_list = [x for x in util.wlan_channels if (x <= 14)]
elif (channel_list == 'ALL_5GHZ'):
tmp_chan_list = [x for x in util.wlan_channels if (x > 14)]
else:
msg = "\n String '{0}' not recognized.".format(channel_list)
msg += "\n Please use 'ALL', 'ALL_2.4GHZ', 'ALL_5GHZ' or either an int or list of channels"
raise AttributeError(msg)
elif type(channel_list) is int:
# Proess scalar integer
tmp_chan_list.append(channel_list)
else:
# Proess interables
try:
for channel in channel_list:
tmp_chan_list.append(channel)
except:
msg = "\n Unable to process channel_list."
msg += "\n Please use 'ALL', 'ALL_2.4GHZ', 'ALL_5GHZ' or either an int or list of channels"
raise AttributeError(msg)
if tmp_chan_list:
channel_list = tmp_chan_list
else:
msg = "\n channel_list is empty."
msg += "\n Please use 'ALL', 'ALL_2.4GHZ', 'ALL_5GHZ' or either an int or list of channels"
raise AttributeError(msg)
self.send_cmd(cmds.NodeProcScanParam(cmds.CMD_PARAM_WRITE, time_per_channel, num_probe_tx_per_channel, channel_list, ssid))
[docs] def start_network_scan(self):
"""Starts the wireless network scan state machine at the node.
During a scan, the node cycles through a set of channels and transmits
periodic Probe Request frames on each channel. Information about
available wireless networks is extracted from received Probe Responses
and Beacon frames. The network scan results can be queried any time
using the ``node.get_network_list()`` method.
Network scans can only be run by unassociated nodes. An associated
node must first reset its BSS state before starting a scan.
The network scan state machine can be stopped with
``node.stop_network_scan()``. The scan state machine will also be
stopped automatically if the node is configured with a new non-null
BSS state.
Example:
::
# Ensure node has null BSS state
n.configure_bss(None)
# Start the scan state machine; scan will use default scan params
# Use n.set_scan_parameters() to customize scan behavior
n.start_network_scan()
# Wait 5 seconds, retrieve the list of discovered networks
time.sleep(5)
networks = n.get_network_list()
# Stop the scan state machine
n.stop_network_scan()
"""
self.send_cmd(cmds.NodeProcScan(enable=True))
[docs] def stop_network_scan(self):
"""Stops the wireless network scan state machine."""
self.send_cmd(cmds.NodeProcScan(enable=False))
[docs] def is_scanning(self):
"""Queries if the node's wireless network scanning state machine is
currently running.
Returns:
status (bool):
* True -- Scan state machine is running
* False -- Scan state machine is not running
"""
return self.send_cmd(cmds.NodeProcScan())
#--------------------------------------------
# Association Commands
#--------------------------------------------
def get_station_info(self, device_list=None):
raise DeprecationWarning('Error: get_station_info() is deprecated. Use get_bss_members() to retrieve the list of associated stations or get_station_info_list() to retrieve the list of all known stations.')
[docs] def get_bss_members(self):
"""Get the BSS members from the node.
The StationInfo() returned by this method can be accessed like a
dictionary and has the following fields:
+-----------------------------+----------------------------------------------------------------------------------------------------+
| Field | Description |
+=============================+====================================================================================================+
| mac_addr | MAC address of station |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| id | Identification Index for this station |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| host_name | String hostname (19 chars max), taken from DHCP DISCOVER packets |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| flags | Station flags. Value containts 1 bit fields: |
| | * 0x0001 - 'KEEP' |
| | * 0x0002 - 'DISABLE_ASSOC_CHECK' |
| | * 0x0004 - 'DOZE' |
| | * 0x0008 - 'HT_CAPABLE' |
| | |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| latest_rx_timestamp | Value of System Time in microseconds of last successful Rx from device |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| latest_txrx_timestamp | Value of System Time in microseconds of last Tx or successful Rx from device |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| latest_rx_seq | Sequence number of last packet received from device |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| tx_phy_mcs | Current PHY MCS index in [0:7] for new transmissions to device |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| tx_phy_mode | Current PHY mode for new transmissions to deivce |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| tx_phy_antenna_mode | Current PHY antenna mode in [1:4] for new transmissions to device |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| tx_phy_power | Current Tx power in dBm for new transmissions to device |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| tx_mac_flags | Flags for Tx MAC config for new transmissions to device. Value contains 1 bit flags: |
| | |
| | * None defined |
| | |
+-----------------------------+----------------------------------------------------------------------------------------------------+
Returns:
station_infos (list of StationInfo):
List of StationInfo() BSS members known to the node
"""
ret_val = self.send_cmd(cmds.NodeGetBSSMembers())
return ret_val
[docs] def get_station_info_list(self, device_list=None):
"""Get the all Station Infos from node.
Args:
device_list (optional): single or iterable of WlanExpNode or WlanDevice
List of devices for which to get counts. Omit this argument to retrieve
all station_info entries from node
The StationInfo() returned by this method can be accessed like a
dictionary and has the following fields:
+-----------------------------+----------------------------------------------------------------------------------------------------+
| Field | Description |
+=============================+====================================================================================================+
| mac_addr | MAC address of station |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| id | Identification Index for this station |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| host_name | String hostname (19 chars max), taken from DHCP DISCOVER packets |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| flags | Station flags. Value containts 1 bit fields: |
| | * 0x0001 - 'KEEP' |
| | * 0x0002 - 'DISABLE_ASSOC_CHECK' |
| | * 0x0004 - 'DOZE' |
| | * 0x0008 - 'HT_CAPABLE' |
| | |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| latest_rx_timestamp | Value of System Time in microseconds of last successful Rx from device |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| latest_txrx_timestamp | Value of System Time in microseconds of last Tx or successful Rx from device |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| latest_rx_seq | Sequence number of last packet received from device |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| tx_phy_mcs | Current PHY MCS index in [0:7] for new transmissions to device |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| tx_phy_mode | Current PHY mode for new transmissions to deivce |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| tx_phy_antenna_mode | Current PHY antenna mode in [1:4] for new transmissions to device |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| tx_phy_power | Current Tx power in dBm for new transmissions to device |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| tx_mac_flags | Flags for Tx MAC config for new transmissions to device. Value contains 1 bit flags: |
| | |
| | * None defined |
| | |
+-----------------------------+----------------------------------------------------------------------------------------------------+
Returns:
station_infos (list of StationInfo):
List of StationInfo() dictionary-like objects. Length of returned list depends on device_list argument.
If device_list is not provided, the length of the return list will equal the length of the station_info
list on the node. If the node's station_info list is empty this method will return an empty list ([])
If device_list is provided the length of the return list will equal the list of device_list. Each entry
in the returned list will either be None (if the node had no matching station_info) or a StationInfo()
dictionary-like object (if the node had a matching station_info entry).
"""
ret = []
if device_list is None:
# Retrieve all station_info from node
ret = self.send_cmd(cmds.NodeGetStationInfoList())
else:
# Covert scalar device_list to iterable
try:
devs = iter(device_list)
except TypeError:
devs = [device_list]
for device in devs:
station_info = self.send_cmd(cmds.NodeGetStationInfoList(device.wlan_mac_address))
if (len(station_info) == 1):
# Node returned list with one valid station_info for this device
ret.append(station_info[0])
else:
# Node had no station_info for this device
ret.append(None)
return ret
def get_bss_info(self):
print('WARNING: get_bss_info() is deprecated and will be removed in a future version. Please use get_network_info()')
return self.get_network_info()
[docs] def get_bss_config(self):
"""Get BSS configuration of the network the node is a member of
Returns a dictionary with the following fields:
+-----------------------------+----------------------------------------------------------------------------------------------------+
| Field | Description |
+=============================+====================================================================================================+
| bssid | BSS ID: 48-bit MAC address |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| channel | Primary channel. In util.wlan_channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 36, 40, 44, 48] |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| channel_type | Channel Type. Value is one of: |
| | |
| | * 0x00 - 'BW20' |
| | * 0x01 - 'BW40_SEC_BELOW' |
| | * 0x02 - 'BW40_SEC_ABOVE' |
| | |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| ssid | SSID (32 chars max) |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| ht_capable | 1 - Network is capable of HT PHY mode |
| | 0 - Netowrk is not capable of NHT PHY mode |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| beacon_interval | Beacon interval - In time units of 1024 us' |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| dtim_period | |
+-----------------------------+----------------------------------------------------------------------------------------------------+
Returns:
bss_config :
BSS configuration of the network that the node is a member of (can be None)
"""
network_info = self.get_network_info()
if(network_info is None):
# Node has NULL active_network_info - return None
return None
# Use the field names of the BSSConfig InfoStruct to transform the network_info
# into a bss_config dictionary
from wlan_exp.info import BSSConfig
bss_config_fields = BSSConfig().get_field_names()
# Construct a dictionary with only BSSConfig fields
bss_config = {}
for f in bss_config_fields:
bss_config[f] = network_info[f]
return bss_config
[docs] def get_network_info(self):
"""Get information about the network the node is a member of
The NetworkInfo() returned by this method can be accessed like a
dictionary and has the following fields:
+-----------------------------+----------------------------------------------------------------------------------------------------+
| Field | Description |
+=============================+====================================================================================================+
| bssid | BSS ID: 48-bit MAC address |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| channel | Primary channel. In util.wlan_channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 36, 40, 44, 48] |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| channel_type | Channel Type. Value is one of: |
| | |
| | * 0x00 - 'BW20' |
| | * 0x01 - 'BW40_SEC_BELOW' |
| | * 0x02 - 'BW40_SEC_ABOVE' |
| | |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| ssid | SSID (32 chars max) |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| ht_capable | 1 - Network is capable of HT PHY mode |
| | 0 - Netowrk is not capable of NHT PHY mode |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| beacon_interval | Beacon interval - In time units of 1024 us' |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| dtim_period | |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| flags | Value contains 1 bit fields: |
| | |
| | * 0x0001 - 'KEEP' |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| capabilities | Supported capabilities of the BSS. Value contains 1 bit fields: |
| | |
| | * 0x0001 - 'ESS' |
| | * 0x0002 - 'IBSS' |
| | * 0x0010 - 'PRIVACY' |
| | |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| latest_beacon_rx_time | Value of System Time in microseconds of last beacon Rx |
+-----------------------------+----------------------------------------------------------------------------------------------------+
| latest_beacon_rx_power | Last observed beacon Rx Power (dBm) |
+-----------------------------+----------------------------------------------------------------------------------------------------+
Returns:
network_info (NetworkInfo):
Information about network that the node is a member of (can be None)
"""
ret_val = self.send_cmd(cmds.NodeGetNetworkInfo())
if (len(ret_val) == 1):
ret_val = ret_val[0]
else:
ret_val = None
return ret_val
[docs] def get_network_list(self):
"""Get a list of known networks (NetworkInfo()s) on the node
Returns:
networks (list of NetworkInfo):
List of NetworkInfo() that are known to the node
"""
return self.send_cmd(cmds.NodeGetNetworkInfo("ALL"))
#--------------------------------------------
# Queue Commands
#--------------------------------------------
[docs] def queue_tx_data_purge_all(self):
print('WARNING: queue_tx_data_purge_all() has been renamed purge_tx_queues(). Please update your script.')
self.purge_tx_queues()
def purge_tx_queues(self):
"""Purges all wireless transmit queues on the node.
This will discard all currently enqueued packets awaiting transmission
at the time the command is received. This will not discard packets
already submitted to the lower-level MAC for transmission. Also, this
will not stop additional packets from sources such as LTGs from being
enqueued.
This command is equivalent to ``reset(queue_data=True)``.
"""
self.send_cmd(cmds.PurgeAllTxQueues())
#--------------------------------------------
# Node User Commands
#--------------------------------------------
[docs] def send_user_command(self, cmd_id, args=None):
"""Send User defined command to the node
See documentation on how-to extend wlan_exp:
http://warpproject.org/trac/wiki/802.11/wlan_exp/Extending
Args:
cmd_id (u32): User-defined Command ID
args (u32, list of u32): Scalar or list of u32 command arguments
to send to the node
Returns:
resp_args (list of u32):
List of u32 response arguments received from the node
"""
if cmd_id is None:
raise AttributeError("Command ID must be defined for a user command")
ret_val = self.send_cmd(cmds.UserSendCmd(cmd_id=cmd_id, args=args))
if ret_val is not None:
return ret_val
else:
return []
#--------------------------------------------
# Memory Access Commands - For developer use only
#--------------------------------------------
def _mem_write_high(self, address, values):
"""Writes 'values' to CPU High memory starting at 'address'
Args:
address (int): Address must be in [0 .. (2^32 - 1)]
values (list of int): Each value must be in [0 .. (2^32 - 1)]
"""
# Code below assumes values is iterable - if caller supplies scalar, wrap it in a list
if '__iter__' not in dir(values):
values = [values]
if (self._check_mem_access_args(address, values)):
return self.send_cmd(cmds.NodeMemAccess(cmd=cmds.CMD_PARAM_WRITE, high=True, address=address, length=len(values), values=values))
def _mem_read_high(self, address, length):
"""Reads 'length' values from CPU High memory starting at 'address'
Args:
address (int): Address must be in [0 .. (2^32 - 1)]
length (int): Length must be in [1 .. 320] (ie fit in a 1400 byte packet)
Returns:
values (list of u32): List of u32 values received from the node
"""
if (self._check_mem_access_args(address, values=None)):
return self.send_cmd(cmds.NodeMemAccess(cmd=cmds.CMD_PARAM_READ, high=True, address=address, length=length))
def _mem_write_low(self, address, values):
"""Writes 'values' to CPU Low memory starting at 'address'
Args:
address (int): Address must be in [0 .. (2^32 - 1)]
values (list of int): Each value must be in [0 .. (2^32 - 1)]
"""
# Code below assumes values is iterable - if caller supplies scalar, wrap it in a list
if '__iter__' not in dir(values):
values = [values]
if (self._check_mem_access_args(address, values)):
return self.send_cmd(cmds.NodeMemAccess(cmd=cmds.CMD_PARAM_WRITE, high=False, address=address, length=len(values), values=values))
def _mem_read_low(self, address, length):
"""Reads 'length' values from CPU Low memory starting at 'address'
Args:
address (int): Address must be in [0 .. (2^32 - 1)]
length (int): Length must be in [1 .. 320] (ie fit in a 1400 byte packet)
Returns:
values (list of u32): List of u32 values received from the node
"""
if (self._check_mem_access_args(address, values=None)):
return self.send_cmd(cmds.NodeMemAccess(cmd=cmds.CMD_PARAM_READ, high=False, address=address, length=length))
def _check_mem_access_args(self, address, values=None, length=None):
"""Check memory access variables
Args:
address (int): Address must be in [0 .. (2^32 - 1)]
values (list of int): Each value must be in [0 .. (2^32 - 1)]
length (int): Length must be in [1 .. 320] (ie fit in a 1400 byte packet)
Returns:
valid (bool): Are all arguments valid?
"""
if ((int(address) != address) or (address >= 2**32) or (address < 0)):
raise Exception('ERROR: address must be integer value in [0 .. (2^32 - 1)]!')
# Caller must pass iterable for values
if (values is not None):
error = False
for v in values:
if (int(v) >= 2**32) or (int(v) < 0):
error = True
if (error):
raise Exception('ERROR: values must be scalar or iterable of ints in [0 .. (2^32 - 1)]! {0}'.format(values))
if length is not None:
if ((int(length) != length) or (length > 50) or (length <= 0)):
raise Exception('ERROR: length must be an integer [1 .. 50] words (ie, 4 to 200 bytes)!')
return True
def _eeprom_write(self, address, values):
"""Writes 'values' to EEPROM starting at 'address'
Args:
address (int): Address must be in [0 .. 15999]
values (list of int): Each value must be in [0 .. 255]
"""
# Convert scalar values to a list for processing
if (type(values) is not list):
values = [values]
if (self._check_eeprom_access_args(address=address, values=values, length=len(values))):
if(address >= 16000):
raise Exception('ERROR: EEPROM addresses [16000 .. 16383] are read only!')
else:
return self.send_cmd(cmds.NodeEEPROMAccess(cmd=cmds.CMD_PARAM_WRITE, address=address, length=len(values), values=values))
def _eeprom_read(self, address, length):
"""Reads 'length' values from EEPROM starting at 'address'
Args:
address (int): Address must be in [0 .. 16383]
length (int): Length must be in [1 .. 320] (ie fit in a 1400 byte packet)
Returns:
values (list of u8): List of u8 values received from the node
"""
if (self._check_eeprom_access_args(address=address, length=length)):
return self.send_cmd(cmds.NodeEEPROMAccess(cmd=cmds.CMD_PARAM_READ, address=address, length=length))
def _check_eeprom_access_args(self, address, values=None, length=None):
"""Check EEPROM access variables
Args:
address (int): Address must be in [0 .. 16383]
values (list of int): Each value must be in [0 .. 255]
length (int): Length must be in [1 .. 320] (ie fit in a 1400 byte packet)
Returns:
valid (bool): Are all arguments valid?
"""
if ((int(address) != address) or (address >= 16384) or (address < 0)):
raise Exception('ERROR: address must be integer value in [0 .. 16383]!')
if (values is not None):
if (type(values) is not list):
values = [values]
error = False
for value in values:
if (((type(value) is not int) and (type(value) is not long)) or
(value >= 2**8) or (value < 0)):
error = True
if (error):
raise Exception('ERROR: values must be scalar or iterable of ints in [0 .. 255]!')
if length is not None:
if ((int(length) != length) or (length > 320) or (length <= 0)):
raise Exception('ERROR: length must be an integer [1 .. 320] words (ie, 4 to 1400 bytes)!')
return True
def check_wlan_exp_ver(self):
"""Check the wlan_exp version of the node against the current wlan_exp
version.
"""
ver_str = version.wlan_exp_ver_str(self.wlan_exp_ver_major,
self.wlan_exp_ver_minor,
self.wlan_exp_ver_revision)
caller_desc = "During initialization '{0}' returned version {1}".format(self.sn_str, ver_str)
status = version.wlan_exp_ver_check(major=self.wlan_exp_ver_major,
minor=self.wlan_exp_ver_minor,
revision=self.wlan_exp_ver_revision,
caller_desc=caller_desc)
if (status == version.WLAN_EXP_VERSION_NEWER):
print("Please update the C code on the node to the proper wlan_exp version.")
if (status == version.WLAN_EXP_VERSION_OLDER):
print("Please update the wlan_exp installation to match the version on the node.")
# End Class WlanExpNode
class WlanExpNodeFactory(node.WlanExpTransportNodeFactory):
"""Factory class to create WlanExpNode objects. This factory defines the
Python node classes and application IDs used to map each hardware node
to a Python class during init.
"""
def __init__(self, network_config=None):
super(WlanExpNodeFactory, self).__init__(network_config)
# Define the default node types
# Add default classes to the factory
import wlan_exp.node_ap
import wlan_exp.node_sta
import wlan_exp.node_ibss
self.add_node_type_class({'high_sw_id': defaults.WLAN_EXP_HIGH_SW_ID_AP, 'node_class': wlan_exp.node_ap.WlanExpNodeAp})
self.add_node_type_class({'high_sw_id': defaults.WLAN_EXP_HIGH_SW_ID_STA, 'node_class': wlan_exp.node_sta.WlanExpNodeSta})
self.add_node_type_class({'high_sw_id': defaults.WLAN_EXP_HIGH_SW_ID_IBSS, 'node_class': wlan_exp.node_ibss.WlanExpNodeIBSS})
# End Class WlanExpNodeFactory