# -*- 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 _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) 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['_sw_id'] values were already retrieved and adopted # by the n.get_type() command earlier in the node init process # The self._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 #-------------------------------------------- def log_configure(self, log_enable=None, log_wrap_enable=None, log_full_payloads=None, log_txrx_mpdu=None, log_txrx_ctrl=None): """Configure log with the given flags. By default all attributes are set to None. Only attributes that are given values will be updated on the node. If an attribute is not specified, then that attribute will retain the same value on the node. Args: log_enable (bool): Enable the event log (Default value on Node: TRUE) log_wrap_enable (bool): Enable event log wrapping (Default value on Node: FALSE) log_full_payloads (bool): Record full Tx/Rx payloads in event log (Default value on Node: FALSE) log_txrx_mpdu (bool): Enable Tx/Rx log entries for MPDU frames (Default value on Node: TRUE) log_txrx_ctrl (bool): Enable Tx/Rx log entries for CTRL frames (Default value on Node: TRUE) """ self.send_cmd(cmds.LogConfigure(log_enable, log_wrap_enable, log_full_payloads, log_txrx_mpdu, log_txrx_ctrl)) 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) 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 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 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 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) 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 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 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)) 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 #-------------------------------------------- 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 #-------------------------------------------- def ltg_configure(self, traffic_flow, auto_start=False): """Configure the node for the given traffic flow. Args: traffic_flow (ltg.FlowConfig): FlowConfig (or subclass of FlowConfig) that describes the parameters of the LTG. auto_start (bool, optional): Automatically start the LTG or wait until it is explictly started. Returns: ID (int): Identifier for the LTG flow The ``trafic_flow`` argument configures the behavior of the LTG, including the traffic's destination, packet payloads, and packet generation timing. The reference code implements three flow configurations by default: - ``FlowConfigCBR()``: a constant bit rate (CBR) source targeted to a specifc destination address. Packet lengths and the packet creation interval are constant. - ``FlowConfigAllAssocCBR()``: a CBR source that targets traffic to all nodes in the station info list. - ``FlowConfigRandomRandom()``: a source that targets traffic to a specific destination address, where each packet has a random length and packets are created at random intervals. Refer to the :doc:`ltg` documentation for details on these flow configs and the underlying classes that can be used to build custom flow configurations. Examples: The code below illustrates a few LTG configuration examples. ``n1`` and ``n2`` here are a wlan_exp node objects and the LTG traffic in each flow will go from ``n1`` to ``n2``. :: import wlan_exp.ltg as ltg # Configure a CBR LTG addressed to a single node ltg_id = n1.ltg_configure(ltg.FlowConfigCBR(dest_addr=n2.wlan_mac_address, payload_length=40, interval=0.01), auto_start=True) # Configure a backlogged traffic source with constant packet size ltg_id = n1.ltg_configure(ltg.FlowConfigCBR(dest_addr=n2.wlan_mac_address, payload_length=1000, interval=0), auto_start=True) # Configure a random traffic source ltg_id = n1.ltg_configure(ltg.FlowConfigRandomRandom(dest_addr=n2.wlan_mac_address, min_payload_length=100, max_payload_length=500, min_interval=0, max_interval=0.1), auto_start=True) """ traffic_flow.enforce_min_resolution(self.scheduler_interval) return self.send_cmd(cmds.LTGConfigure(traffic_flow, auto_start)) 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 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)) 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)) 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)) 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") 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") 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 #-------------------------------------------- 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)) 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)) 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 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)) 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] 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] 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)) 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)) 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") 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 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.") 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 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) 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 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)) 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) 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)) 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 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)) 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 `_. 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) def configure_pkt_det_min_power(self, enable, power_level=None): """Configure Minimum Power Requirement of Packet Detector. Args: enable (bool): True/False power_level (int): [-90,-30] dBm """ if enable is False: self.set_low_param(param_id=cmds.CMD_PARAM_LOW_PARAM_PKT_DET_MIN_POWER, param_values=0) else: if power_level is not None: if power_level >= cmds.CMD_PARAM_NODE_MIN_MIN_PKT_DET_POWER_DBM and power_level <= cmds.CMD_PARAM_NODE_MAX_MIN_PKT_DET_POWER_DBM: # C code expects a u32 # Bit 24 is flag for en/disable of min_power logic # Bits [7:0] are u8 interpreted as dB above min threshold param = (1 << 24) | ((power_level - cmds.CMD_PARAM_NODE_MIN_MIN_PKT_DET_POWER_DBM) & 0xFF) self.set_low_param(param_id=cmds.CMD_PARAM_LOW_PARAM_PKT_DET_MIN_POWER, param_values=param) else: msg = "\nPower level must be in the range of [{0},{1}]\n".format(cmds.CMD_PARAM_NODE_MIN_MIN_PKT_DET_POWER_DBM, cmds.CMD_PARAM_NODE_MAX_MIN_PKT_DET_POWER_DBM) raise ValueError(msg) else: msg = "\nPower level not specified\n" raise ValueError(msg) 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) 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 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)) 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 #-------------------------------------------- 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)) 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)) def stop_network_scan(self): """Stops the wireless network scan state machine.""" self.send_cmd(cmds.NodeProcScan(enable=False)) 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 configure_bss(self): """Configure the Basic Service Set (BSS) information of the node Each node is either a member of no BSS (colloquially "unassociated") or a member of one BSS. A node requires a minimum valid bss_info to be a member of a BSS. Based on the node type, there is a minimum set of fields needed for a valid bss_info. This method must be overloaded by sub-classes. See http://warpproject.org/trac/wiki/802.11/wlan_exp/bss for more information about BSSes. """ raise NotImplementedError() 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.') 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 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() 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 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 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 #-------------------------------------------- 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 #-------------------------------------------- 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