source: ReferenceDesigns/w3_802.11/python/wlan_exp/node.py

Last change on this file was 6321, checked in by murphpo, 2 years ago

Updated wlan-exp docs for 1.8.0

File size: 130.1 KB
Line 
1# -*- coding: utf-8 -*-
2"""
3------------------------------------------------------------------------------
4Mango 802.11 Reference Design Experiments Framework - Node Classes
5------------------------------------------------------------------------------
6License:   Copyright 2019 Mango Communications, Inc. All rights reserved.
7           Use and distribution subject to terms in LICENSE.txt
8------------------------------------------------------------------------------
9
10"""
11import sys
12
13import wlan_exp.transport.node as node
14
15import wlan_exp.version as version
16import wlan_exp.defaults as defaults
17import wlan_exp.cmds as cmds
18import wlan_exp.device as wlan_device
19
20__all__ = ['WlanExpNode', 'WlanExpNodeFactory']
21
22# Fix to support Python 2.x and 3.x
23if sys.version[0]=="3": long=None
24
25# ID/Name mapping for reference software applications
26# The ID values here match the <high,low>_sw_id values retrieved
27#  by the n.get_type() command during node init
28high_sw_apps = [(defaults.WLAN_EXP_HIGH_SW_ID_AP, 'AP'), 
29                (defaults.WLAN_EXP_HIGH_SW_ID_STA, 'STA'),
30                (defaults.WLAN_EXP_HIGH_SW_ID_IBSS, 'IBSS')]
31
32low_sw_apps  = [(defaults.WLAN_EXP_LOW_SW_ID_DCF, 'DCF'),
33                (defaults.WLAN_EXP_LOW_SW_ID_NOMAC, 'NoMAC')]
34
35def get_high_app_name(app_id):
36    for a in high_sw_apps:
37        if a[0] == app_id:
38            return a[1]
39   
40    return "Unknown High App ID {}".format(app_id)
41
42def get_low_app_name(app_id):
43    for a in low_sw_apps:
44        if a[0] == app_id:
45            return a[1]
46   
47    return "Unknown Low App ID {}".format(app_id)
48
49
50class WlanExpNode(node.WlanExpTransportNode, wlan_device.WlanDevice):
51    """Class for 802.11 Reference Design Experiments Framwork node.
52
53    Args:
54        network_config (transport.NetworkConfiguration) : Network configuration of the node
55
56
57    Attributes:
58        node_id (int): Unique identification for this node
59        name (str): User specified name for this node (supplied by user scripts)
60        description (str): String description of this node (auto-generated)
61        serial_number (int): Node's serial number, read from EEPROM on hardware
62        fpga_dna (int): Node's FPGA'a unique identification (on select hardware)
63        transport (transport.Transport): Node's transport object
64        transport_broadcast (transport.Transport): Node's broadcast transport object
65
66        wlan_mac_address (int): Wireless MAC address of the node (inherited from ``WlanDevice``)
67        ht_capable (bool): Indicates if device has PHY capable of HT (802.11n) rates
68            (inherited from ``WlanDevice``)
69
70        scheduler_interval (int): Minimum resolution (in usec) of the scheduler.  This
71            is also the minimum time between LTG events.
72        log_max_size (int): Maximum size of event log (in bytes)
73        log_total_bytes_read (int): Number of bytes read from the event log
74        log_num_wraps (int): Number of times the event log has wrapped
75        log_next_read_index (int): Index in to event log of next read
76        wlan_exp_ver_major (int): ``wlan_exp`` Major version running on this node
77        wlan_exp_ver_minor (int): ``wlan_exp`` Minor version running on this node
78        wlan_exp_ver_revision (int): ``wlan_exp`` Revision version running on this node
79        max_tx_power_dbm(int): Maximum transmit power of the node (in dBm)
80        min_tx_power_dbm(int): Minimum transmit power of the node (in dBm)
81
82    """
83   
84    wlan_exp_eth_mtu         = None
85    high_sw_id               = None
86    low_sw_id                = None
87
88    high_compilation_datetime = None
89    low_compilation_datetime  = None
90
91    scheduler_interval        = None
92
93    log_max_size             = None
94    log_total_bytes_read     = None
95    log_num_wraps            = None
96    log_next_read_index      = None
97
98    wlan_exp_ver_major       = None
99    wlan_exp_ver_minor       = None
100    wlan_exp_ver_revision    = None
101
102    max_tx_power_dbm         = None
103    min_tx_power_dbm         = None
104
105    def __init__(self, network_config=None):
106        super(WlanExpNode, self).__init__(network_config)
107
108        (self.wlan_exp_ver_major, self.wlan_exp_ver_minor,
109                self.wlan_exp_ver_revision) = version.wlan_exp_ver()
110
111        self.scheduler_interval           = 1
112
113        self.log_total_bytes_read           = 0
114        self.log_num_wraps                  = 0
115        self.log_next_read_index            = 0
116
117        # As of v1.5 all 802.11 Ref Design nodes are HT capable
118        self.ht_capable = True
119
120    def __str__(self):
121        msg = ""
122
123        msg += " Node ID   : {0}\n".format(self.node_id)
124        msg += " Serial #  : {0}\n".format(self.sn_str)
125        msg += " High App  : {0}\n".format(get_high_app_name(self.high_sw_id))
126        msg += " Low App   : {0}\n".format(get_low_app_name(self.low_sw_id))
127
128        if self.transport is not None:
129            msg += self.transport.__repr__()
130
131        return msg
132
133    def __repr__(self):
134        """Return node name and description"""
135        msg = ""
136        if self.serial_number is not None:
137            msg  = "Node {0} ".format(self.sn_str)
138            msg += "{0}/".format(get_high_app_name(self.high_sw_id))
139            msg += "{0}".format(get_low_app_name(self.low_sw_id))
140           
141            if self.name is not None:
142                msg += " ({0})".format(self.name)
143        else:
144            msg += "Node not initialized."
145       
146        return msg
147
148    #-------------------------------------------------------------------------
149    # Node Commands
150    #-------------------------------------------------------------------------
151    def update_node_info(self, hw_node_info):
152        """Applies parameters to the wlan_exp node instance from the node info
153        structs returned from hardware
154       
155       
156        Args:
157            hw_node_info (list): List of InfoStruct instances, parsed from the NODE_INFO
158                                 command response. First struct must be the platform-agnostic
159                                 NODE_INFO. Additional InfoStructs are platform-specific.
160        """
161
162        ni = hw_node_info[0]
163
164        # Verify the NodesConfiguration and NodeInfo serial numbers match
165        if self.serial_number != ni['serial_number']:
166            raise Exception('ERROR: serial number mismatch! Node object configured with {0}, hardware returned {1}'.format(self.serial_number, ni['serial_number']))
167
168        if self.platform_id != ni['platform_id']:
169            raise Exception('ERROR: platform ID mismatch! Node object configured with {0}, hardware returned {1}'.format(self.platform_id, ni['platform_id']))
170
171        # Verify the high/low software app IDs match the values set earlier in n.get_type()
172        if self.high_sw_id != ni['high_sw_id']:
173            raise Exception('ERROR: high_sw_id mismatch! Node object initialized with {0}, hardware returned {1}'.format(self.high_sw_id, ni['high_sw_id']))
174
175        if self.low_sw_id != ni['low_sw_id']:
176            raise Exception('ERROR: low_sw_id mismatch! Node object initialized with {0}, hardware returned {1}'.format(self.low_sw_id, ni['low_sw_id']))
177
178        # The node_info['<high,low>_sw_id'] values were already retrieved and adopted
179        #  by the n.get_type() command earlier in the node init process
180        # The self.<high,low>_sw_id parameters should be updated here if the node
181        #  init flow is updated to skip the n.get_type() step
182
183        self.node_id = ni['node_id']
184        self.wlan_mac_address = ni['wlan_mac_addr']
185        self.scheduler_interval = ni['scheduler_interval']
186
187        self.wlan_exp_ver_major = (ni['wlan_exp_version'] & 0xFF000000) >> 24
188        self.wlan_exp_ver_minor = (ni['wlan_exp_version'] & 0x00FF0000) >> 16
189        self.wlan_exp_ver_revision = (ni['wlan_exp_version'] & 0x0000FFFF)
190        self.check_wlan_exp_ver()
191
192        self.high_compilation_datetime = ni['high_compilation_date'] + ' ' + ni['high_compilation_time']
193        self.low_compilation_datetime = ni['low_compilation_date'] + ' ' + ni['low_compilation_time']
194       
195        # Test the MTU - this will raise an exception if the selected MTU is still too
196        #  high for this node-to-host link (for example, if the host and node both
197        #  support 9kB jumbo but the intermediate switch does not)
198        # The self.transport.mtu parameter was initialized to the host MTU via the NetworkConfig and
199        #  will be updated after the MTU test
200        # The self.wlan_exp_eth_mtu stores the value reported by the node hardware for future reference
201        self.wlan_exp_eth_mtu = ni['wlan_exp_eth_mtu']
202
203        if not self.test_mtu(min(self.transport.mtu, self.wlan_exp_eth_mtu)):
204            raise Exception('ERROR: MTU test failed for node {0} - actual MTU smaller than MTUs set in NetworkConfig {1} and NodeInfo {2}'.format(
205                    self, self.transport.mtu, self.wlan_exp_eth_mtu))
206
207        # Set the MTU for this host/node link
208        #  If the node hardware reports a smaller MTU, adopt the smaller value here
209        self.transport.mtu = min(self.transport.mtu, self.wlan_exp_eth_mtu)
210
211        # Re-configure the node's maximum response packet size using this known-valid MTU
212        # The current C code has a single global variable which controls the maximum size
213        #  of any wlan_exp response. In theory two hosts could re-set this response if
214        #  they use different MTUs. This presents an obvious race condition, something we
215        #  can address if a multi-host experiment is ever required
216       
217        # The max payload size is the MTU minus all the headers
218        import wlan_exp.transport.util as util
219        resp_hdr_len = util.get_total_header_len()
220        max_words = int((self.transport.mtu - resp_hdr_len)/4) # floor(bytes/4)
221
222        self.set_max_resp_words(max_words)
223
224        # Process platform-specific node info structs
225        #  This is placeholder code for a future update where platforms define
226        #  WlanExpNode subclasses which can implement more sophisticated
227        #  node info processing
228        # For now, copy every platform node info field to a node object property
229        for ni in hw_node_info[1:]:
230            flds = ni.keys()
231            for f in flds:
232                setattr(self, f, ni[f])
233
234    #--------------------------------------------
235    # Log Commands
236    #--------------------------------------------
237    def log_configure(self, log_enable=None, log_wrap_enable=None,
238                            log_full_payloads=None, log_txrx_mpdu=None, 
239                            log_txrx_ctrl=None):
240        """Configure log with the given flags.
241
242        By default all attributes are set to None.  Only attributes that
243        are given values will be updated on the node.  If an attribute is
244        not specified, then that attribute will retain the same value on
245        the node.
246
247        Args:
248            log_enable (bool):        Enable the event log
249                (Default value on Node: TRUE)
250            log_wrap_enable (bool):   Enable event log wrapping
251                (Default value on Node: FALSE)
252            log_full_payloads (bool): Record full Tx/Rx payloads in event log
253                (Default value on Node: FALSE)
254            log_txrx_mpdu (bool):     Enable Tx/Rx log entries for MPDU frames
255                (Default value on Node: TRUE)
256            log_txrx_ctrl (bool):     Enable Tx/Rx log entries for CTRL frames
257                (Default value on Node: TRUE)
258        """
259        self.send_cmd(cmds.LogConfigure(log_enable, log_wrap_enable,
260                                        log_full_payloads, log_txrx_mpdu, 
261                                        log_txrx_ctrl))
262
263
264    def log_get(self, size, offset=0, max_req_size=2**23):
265        """Low level method to retrieve log data from a wlan_exp node.
266
267        Args:
268            size (int):                   Number of bytes to read from the log
269            offset (int, optional):       Starting byte to read from the log
270            max_req_size (int, optional): Max request size that the transport
271                will fragment the request into.
272
273        Returns:
274            buffer (transport.Buffer): 
275                Data from the log corresponding to the input parameters
276
277        There is no guarentee that this will return data aligned to event
278        boundaries.  Use ``log_get_indexes()`` to get event aligned boundaries.
279
280        Log reads are not destructive. Log entries will only be destroyed by a
281        log reset or if the log wraps.
282
283        During a given ``log_get()`` command, the ETH_B Ethernet interface of
284        the node will not be able to respond to any other Ethernet packets
285        that are sent to the node.  This could cause the node to drop
286        incoming wlan_exp packets from other wlan_exp instances accessing
287        the node. Therefore, for large requests, having a smaller
288        ``max_req_size`` will allow the transport to fragement the command and
289        allow the node to be responsive to multiple hosts.
290
291        Some basic analysis shows that fragment sizes of 2**23 (8 MB)
292        add about 2% overhead to the receive time and each command takes less
293        than 1 second (~0.9 sec), which is the default transport timeout.
294        """
295        cmd_size = size
296        log_size = self.log_get_size()
297
298        # C code uses size=0xFFFFFFFF (CMD_PARAM_LOG_GET_ALL_ENTRIES) as magic value to return
299        #  all valid data from offset to end of log data array
300        if (cmd_size != cmds.CMD_PARAM_LOG_GET_ALL_ENTRIES) and ((size + offset) > log_size):
301            cmd_size = log_size - offset
302
303            print("WARNING:  Trying to get {0} bytes from the log at offset {1}".format(size, offset))
304            print("    while log only has {0} bytes.".format(log_size))
305            print("    Truncating command to {0} bytes at offset {1}.".format(cmd_size, offset))
306
307        return self.send_cmd(cmds.LogGetEvents(cmd_size, offset), max_req_size=max_req_size)
308
309
310    def log_get_all_new(self, log_tail_pad=0, max_req_size=2**23):
311        """Get all "new" entries in the log.
312       
313        The ``log_get_all_new()`` method simplifies retrieving a node's
314        log over the course of a long experiment. This method keeps track
315        of what log data has already been retrieved from the node, then
316        requests new data as the experiment progresses.
317       
318        The Python node object maintains internal state about what log data has
319        been read from the node to determine if there is "new" data in the log.
320        Since this state data is maintained in Python and not on the node
321        itself, it is possible for multiple host scripts to read log data using
322        ``log_get_all_new()``. The internal state maintained for
323        ``log_get_all_new()`` is only reset when using the node reset method
324        (i.e. ``node.reset(log=True)``).
325       
326        When a node is adding entries to its log, it will allocate the
327        memory for an entry from the log and then fill in the contents.  This
328        means at any given time, the last log entry may have incomplete
329        information.  In the current C code all log entries are allocated and
330        written in a single context, so it is impossible for Python to retreive
331        an incomplete log entry.
332       
333        In case future C code defers updating a previously-allocated log entry,
334        this method supports a log_tail_pad argument which will stop short of retrieving
335        the last bytes of the node's log data array.
336       
337        Unfortunately, this means that using a ``log_tail_pad`` other than
338        zero will result in return data that is not aligned to a log entry
339        boundary.  This should not be an issue if the goal is to periodically
340        read the log data from the node and store it in a file (as seen in
341        the ``log_capture_continuous.py`` example).  However, this can be an
342        issue if trying to periodically read the log and process the log data
343        directly.  In this case, ``log_tail_pad`` must be zero and the code
344        has to assume the last entry is invalid.  This has not come up very
345        much, but please let us know if this is an issue via the forums.       
346
347        Args:
348            log_tail_pad (int, optional): Number of bytes from the current end
349                of the "new" entries that will not be read during the call. 
350                This is to deal with the case where the node is in the process
351                of modifying the last log entry so it my contain incomplete
352                data and should not be read.
353
354        Returns:
355            buffer (transport.Buffer):
356                Data from the log that contains all entries since the last
357                time the log was read.
358        """
359       
360        # FIXME: there are a few subtle behaviors that should be discussed:
361        #  (1) This is a mostly a semantic issue, but this method will not actually
362        #      "get all new" log entries in the case of a wrap. It will not
363        #       retrieve around the boundary of a wrap. I think. I checked to
364        #       to see the surprisingly-specific send_cmd() method handled
365        #       the wrap case to issue multiple commands. I don't believe it
366        #       does. The extreme corner case is that the log_next_read_index
367        #       is near the end of the DRAM addresses and the log has since wrapped
368        #       and written ~1GB of entries. This method will just return the tiny
369        #       bit at the end and quit. You have to know to call it twice if you
370        #       actually want all your log.
371        #  (2) There's a nasty race that will happen if you wait too long
372        #      between calls to n.log_get_all_new(). If the log_next_read_index
373        #      starts to get encroached on by the node's wrapped write index, you're
374        #      sunk. There's no way to re-align to the log entries in DRAM. I
375        #      weakly believe this would manifest as the host trying to parse the
376        #      first log entry and seeing arbitrary bytes rather than a valid
377        #      log entry header. This could be improved -- it's detectable when
378        #      this error occurs. If num_wraps from the node exceeds self.log_num_wraps
379        #      and the next_index from the node is pretty close to our self.log_next_read_index,
380        #      then we know we are in the Danger Zone. The best thing we can do is
381        #      print a warning and punt on self.log_next_read_index and jump
382        #      "forward" to self.log_next_read_index=0 -- the only entry we know
383        #      we can align to.
384       
385        import wlan_exp.transport.message as message
386
387        return_val = message.Buffer()
388
389        # Check if the log is full to interpret the indexes correctly
390        if (self.log_is_full()):
391            log_size  = self.log_get_size()
392            read_size = log_size - self.log_next_read_index - log_tail_pad
393
394            # Read the data from the node
395            if (read_size > 0):
396                return_val = self.log_get(offset=self.log_next_read_index,
397                                          size=read_size,
398                                          max_req_size=max_req_size)
399
400                # Only increment index by how much was actually read
401                read_size  = return_val.get_buffer_size()
402
403                if (read_size > 0):
404                    self.log_next_read_index += read_size
405                else:
406                    print("WARNING:  Not able to read data from node.")
407            else:
408                pass
409                #print("WARNING:  No new data on node.")
410
411        # Log is not full
412        else:
413            (next_index, _, num_wraps) = self.log_get_indexes()
414
415            if ((self.log_next_read_index == 0) and (self.log_num_wraps == 0)):
416                # This is the first read of the log by this python object
417                if (num_wraps != 0):
418                    # Need to advance the num_wraps to the current num_wraps so
419                    # that the code does not get into a bad state with log reading.
420                    msg  = "\nWARNING:  On first read, the log on the node has already wrapped.\n"
421                    msg += "    Skipping the first {0} wraps of log data.\n".format(num_wraps)
422                    print(msg)
423                    self.log_num_wraps = num_wraps
424
425            # Check if log has wrapped
426            if (num_wraps == self.log_num_wraps):
427                # Since log has not wrapped, then read to the (next_index - log_tail_pad)
428                if (next_index > (self.log_next_read_index + log_tail_pad)):
429
430                    return_val = self.log_get(offset=self.log_next_read_index,
431                                              size=(next_index - self.log_next_read_index - log_tail_pad),
432                                              max_req_size=max_req_size)
433
434                    # Only increment index by how much was actually read
435                    read_size  = return_val.get_buffer_size()
436                    if (read_size > 0):
437                        self.log_next_read_index += read_size
438                    else:
439                        print("WARNING:  Not able to read data from node.")
440                else:
441                    pass
442                    #print("WARNING:  No new data on node.")
443            else:
444                # Log has wrapped.  Get all the entries on the old wrap
445                if (next_index != 0):
446
447                    #FIXME: I don't think CMD_PARAM_LOG_GET_ALL_ENTRIES will ever
448                    # actually make its way down to the node, even if max_req_size
449                    # is none. This is because send_cmd will always bind the maximum
450                    # size to self.transport.rx_buffer_size.
451
452                    return_val = self.log_get(offset=self.log_next_read_index,
453                                              size=cmds.CMD_PARAM_LOG_GET_ALL_ENTRIES,
454                                              max_req_size=max_req_size)
455
456                    # The amount of data returned from the node should not be zero
457                    read_size  = return_val.get_buffer_size()
458                    if (read_size > 0):
459                        self.log_next_read_index = 0
460                        self.log_num_wraps       = num_wraps
461                    else:
462                        print("WARNING:  Not able to read data from node.")
463                else:
464                    pass
465                    #print("WARNING:  No new data on node.")
466
467        return return_val
468
469
470    def log_get_size(self):
471        """Get the size of the node's current log (in bytes).
472
473        Returns:
474            num_bytes (int): Number of bytes in the log
475        """
476        (capacity, size)  = self.send_cmd(cmds.LogGetCapacity())
477
478        # Check the maximum size of the log and update the node state
479        if self.log_max_size is None:
480            self.log_max_size = capacity
481        else:
482            if (self.log_max_size != capacity):
483                msg  = "EVENT LOG WARNING:  Log capacity changed.\n"
484                msg += "    Went from {0} bytes to ".format(self.log_max_size)
485                msg += "{0} bytes.\n".format(capacity)
486                print(msg)
487                self.log_max_size = capacity
488
489        return size
490
491
492    def log_get_capacity(self):
493        """Get the total capacity of the node's log memory allocation (in bytes).
494
495        Returns:
496            capacity (int): Number of byte allocated for the log.
497        """
498        return self.log_max_size
499
500
501    def log_get_indexes(self):
502        """Get the indexes that describe the state of the event log.
503
504        Returns:
505            indexes (tuple):
506           
507                #. oldest_index (int): Log index of the oldest event in the log
508                #. next_index (int):   Log index where the next event will be recorded
509                #. num_wraps (int):    Number of times the log has wrapped
510       
511        """
512        (next_index, oldest_index, num_wraps, _) = self.send_cmd(cmds.LogGetStatus())
513
514        # Check that the log is in a good state
515        if ((num_wraps < self.log_num_wraps) or
516            ((num_wraps == self.log_num_wraps) and
517             (next_index < self.log_next_read_index))):
518            msg  = "\n!!! Event Log Corrupted.  Please reset the log. !!!\n"
519            print(msg)
520
521        return (next_index, oldest_index, num_wraps)
522
523
524    def log_get_flags(self):
525        """Get the flags that describe the event log configuration.
526
527        Returns:
528            flags (int): Integer describing the configuration of the event log.
529           
530                * ``0x0001`` - Logging enabled
531                * ``0x0002`` - Log wrapping enabled
532                * ``0x0004`` - Full payload logging enabled
533                * ``0x0008`` - Tx/Rx log entries for MPDU frames enabled
534                * ``0x0010`` - Tx/Rx log entries for CTRL frames enabled
535               
536        """
537        (_, _, _, flags) = self.send_cmd(cmds.LogGetStatus())
538
539        return flags
540
541
542    def log_is_full(self):
543        """Return whether the log is full or not.
544
545        Returns:
546            status (bool): True if the log is full; False if the log is not full.
547        """
548        (next_index, oldest_index, num_wraps, flags) = self.send_cmd(cmds.LogGetStatus())
549
550        if (((flags & cmds.CMD_PARAM_LOG_CONFIG_FLAG_WRAP) != cmds.CMD_PARAM_LOG_CONFIG_FLAG_WRAP) and
551            ((next_index == 0) and (oldest_index == 0) and (num_wraps == (self.log_num_wraps + 1)))):
552            return True
553        else:
554            return False
555
556
557    def log_write_exp_info(self, info_type, message=None):
558        """Write the experiment information provided to the log.
559
560        Args:
561            info_type (int): Type of the experiment info.  This is an arbitrary
562                16 bit number chosen by the experimentor
563            message (int, str, bytes, optional): Information to be placed in
564                the event log.
565
566        Message must be able to be converted to bytearray with 'UTF-8' format.
567        """
568        self.send_cmd(cmds.LogAddExpInfoEntry(info_type, message))
569
570
571    def log_write_time(self, time_id=None):
572        """Adds the current time in microseconds to the log.
573
574        Args:
575            time_id (int, optional):  User providied identifier to be used for
576                the TIME_INFO log entry.  If none is provided, a random number
577                will be inserted.
578        """
579        return self.send_cmd(cmds.NodeProcTime(cmds.CMD_PARAM_TIME_ADD_TO_LOG, cmds.CMD_PARAM_RSVD_TIME, time_id))
580
581
582
583    #--------------------------------------------
584    # Counts Commands
585    #--------------------------------------------
586    def get_txrx_counts(self, device_list=None):
587        """Get the Tx/Rx counts data structurs from the node.
588
589        Args:
590            device_list (list of WlanExpNode, WlanExpNode, WlanDevice, optional):
591                List of devices for which to get counts.  See note below for
592                more information.
593
594        Returns:
595            counts_dictionary (list of TxRxCounts, TxRxCounts):
596                TxRxCounts() for the device(s) specified.
597
598
599        The TxRxCounts() structure returned by this method can be accessed like
600        a dictionary and has the following fields:
601       
602            +-----------------------------+-----------------------------------------------------------------------------------------------------+
603            | Field                       | Description                                                                                         |
604            +=============================+=====================================================================================================+
605            | retrieval_timestamp         |  Value of System Time in microseconds when structure retrieved from the node                        |
606            +-----------------------------+-----------------------------------------------------------------------------------------------------+
607            | mac_addr                    |  MAC address of remote node whose statics are recorded here                                         |
608            +-----------------------------+-----------------------------------------------------------------------------------------------------+
609            | data_num_rx_bytes           |  Total number of bytes received in DATA packets from remote node (only non-duplicates)              |
610            +-----------------------------+-----------------------------------------------------------------------------------------------------+
611            | data_num_rx_bytes_total     |  Total number of bytes received in DATA packets from remote node (including duplicates)             |
612            +-----------------------------+-----------------------------------------------------------------------------------------------------+
613            | data_num_tx_bytes_success   |  Total number of bytes successfully transmitted in DATA packets to remote node                      |
614            +-----------------------------+-----------------------------------------------------------------------------------------------------+
615            | data_num_tx_bytes_total     |  Total number of bytes transmitted (successfully or not) in DATA packets to remote node             |
616            +-----------------------------+-----------------------------------------------------------------------------------------------------+
617            | data_num_rx_packets         |  Total number of DATA packets received from remote node                                             |
618            +-----------------------------+-----------------------------------------------------------------------------------------------------+
619            | data_num_tx_packets_success |  Total number of DATA packets successfully transmitted to remote node                               |
620            +-----------------------------+-----------------------------------------------------------------------------------------------------+
621            | data_num_tx_packets_total   |  Total number of DATA packets transmitted (successfully or not) to remote node                      |
622            +-----------------------------+-----------------------------------------------------------------------------------------------------+
623            | data_num_tx_attempts        |  Total number of low-level attempts of DATA packets to remote node (includes re-transmissions)      |
624            +-----------------------------+-----------------------------------------------------------------------------------------------------+
625            | mgmt_num_rx_bytes           |  Total number of bytes received in management packets from remote node (only non-duplicates)        |
626            +-----------------------------+-----------------------------------------------------------------------------------------------------+
627            | mgmt_num_rx_bytes_total     |  Total number of bytes received in management packets from remote node (including duplicates)       |           
628            +-----------------------------+-----------------------------------------------------------------------------------------------------+
629            | mgmt_num_tx_bytes_success   |  Total number of bytes successfully transmitted in management packets to remote node                |
630            +-----------------------------+-----------------------------------------------------------------------------------------------------+
631            | mgmt_num_tx_bytes_total     |  Total number of bytes transmitted (successfully or not) in management packets to remote node       |
632            +-----------------------------+-----------------------------------------------------------------------------------------------------+
633            | mgmt_num_rx_packets         |  Total number of management packets received from remote node (only non-duplicates)                 |
634            +-----------------------------+-----------------------------------------------------------------------------------------------------+
635            | mgmt_num_rx_packets_total   |  Total number of management packets received from remote node (including duplicates)                |
636            +-----------------------------+-----------------------------------------------------------------------------------------------------+
637            | mgmt_num_tx_packets_success |  Total number of management packets successfully transmitted to remote node                         |
638            +-----------------------------+-----------------------------------------------------------------------------------------------------+
639            | mgmt_num_tx_packets_total   |  Total number of management packets transmitted (successfully or not) to remote node                |
640            +-----------------------------+-----------------------------------------------------------------------------------------------------+
641            | mgmt_num_tx_attempts        |  Total number of low-level attempts of management packets to remote node (includes re-transmissions)|
642            +-----------------------------+-----------------------------------------------------------------------------------------------------+
643
644
645        If the ``device_list`` is a single device, then a single dictionary or
646        None is returned.  If the ``device_list`` is a list of devices, then a
647        list of dictionaries will be returned in the same order as the devices
648        in the list.  If any of the counts are not there, None will be inserted
649        in the list.  If the ``device_list`` is not specified, then all the
650        counts on the node will be returned.
651        """
652
653        # In wlan_exp versions <1.8.0 Tx/Rx counts were retrieved using the
654        #  dedicated CountsGetTxRx command, even though the C code has stored
655        #  counts inside the station_info structs since much earlier. As of
656        #  1.8.0 wlan_exp uses the existing station_info retrieval command to
657        #  retrieve and extract Tx/Rx counts
658        def station_info_to_counts(si):
659            import wlan_exp.info as info_structs
660            counts_fields = info_structs.info_field_defs['TXRX_COUNTS']
661           
662            # Construct a new dictionary with only the Tx/Rx counts fields
663            # Field name is [0] in each field definition tuple
664            c = {k:v for (k,v) in si.items() if k in [f[0] for f in counts_fields]}
665            return c
666
667        # Retrieve station_info for all requested devices
668        station_info_list = self.get_station_info_list(device_list=device_list)
669       
670        # Initialize the return list
671        counts_list = [None]*len(station_info_list)
672
673        for ii,station_info in enumerate(station_info_list):
674            # Ignore 'None' station_info entries, returned by node
675            #  for unknown devices in device_list
676            if(station_info):
677                counts_list[ii] = station_info_to_counts(station_info)
678           
679        # If caller supplied a single device, return a scalar instead of a length=1 list
680        if counts_list is not None and len(counts_list) == 1:
681            return counts_list[0]
682        else:
683            return counts_list
684
685
686
687    #--------------------------------------------
688    # Local Traffic Generation (LTG) Commands
689    #--------------------------------------------
690    def ltg_configure(self, traffic_flow, auto_start=False):
691        """Configure the node for the given traffic flow.
692
693        Args:
694            traffic_flow (ltg.FlowConfig):  FlowConfig (or subclass of
695                FlowConfig) that describes the parameters of the LTG.
696            auto_start (bool, optional): Automatically start the LTG or wait
697                until it is explictly started.
698
699        Returns:
700            ID (int):  Identifier for the LTG flow
701
702        The ``trafic_flow`` argument configures the behavior of the LTG,
703        including the traffic's destination, packet payloads, and packet
704        generation timing. The reference code implements three flow
705        configurations by default:
706
707         - ``FlowConfigCBR()``: a constant bit rate (CBR) source targeted to a
708           specifc destination address. Packet lengths and the packet
709           creation interval are constant.
710         - ``FlowConfigAllAssocCBR()``: a CBR source that targets traffic to
711           all nodes in the station info list.
712         - ``FlowConfigRandomRandom()``: a source that targets traffic to a
713           specific destination address, where each packet has a random
714           length and packets are created at random intervals.
715
716        Refer to the :doc:`ltg` documentation for details on these flow configs
717        and the underlying classes that can be used to build custom flow
718        configurations.
719
720        Examples:
721            The code below illustrates a few LTG configuration examples. ``n1``
722            and ``n2`` here are a wlan_exp node objects and the LTG traffic in
723            each flow will go from ``n1`` to ``n2``.
724            ::
725
726                import wlan_exp.ltg as ltg
727               
728                # Configure a CBR LTG addressed to a single node
729                ltg_id = n1.ltg_configure(ltg.FlowConfigCBR(dest_addr=n2.wlan_mac_address, payload_length=40, interval=0.01), auto_start=True)
730
731                # Configure a backlogged traffic source with constant packet size
732                ltg_id = n1.ltg_configure(ltg.FlowConfigCBR(dest_addr=n2.wlan_mac_address, payload_length=1000, interval=0), auto_start=True)
733
734                # Configure a random traffic source
735                ltg_id = n1.ltg_configure(ltg.FlowConfigRandomRandom(dest_addr=n2.wlan_mac_address,
736                                               min_payload_length=100,
737                                               max_payload_length=500,
738                                               min_interval=0,
739                                               max_interval=0.1),
740                                            auto_start=True)
741       
742        """
743        traffic_flow.enforce_min_resolution(self.scheduler_interval)
744        return self.send_cmd(cmds.LTGConfigure(traffic_flow, auto_start))
745
746
747    def ltg_get_status(self, ltg_id_list):
748        """Get the status of the LTG flows.
749
750        Args:
751            ltg_id_list (list of int, int): List of LTG flow identifiers or
752                single LTG flow identifier
753
754        If the ltg_id_list is a single ID, then a single status tuple is
755        returned.  If the ltg_id_list is a list of IDs, then a list of status
756        tuples will be returned in the same order as the IDs in the list.
757
758        Returns:
759            status (tuple):
760           
761                #. valid (bool): Is the LTG ID valid? (True/False)
762                #. running (bool): Is the LTG currently running? (True/False)
763                #. start_timestamp (int): Timestamp when the LTG started
764                #. stop_timestamp (int): Timestamp when the LTG stopped
765           
766        """
767        ret_val = []
768        if (type(ltg_id_list) is list):
769            for ltg_id in ltg_id_list:
770                result = self.send_cmd(cmds.LTGStatus(ltg_id))
771                if not result[0]:
772                    self._print_ltg_error(cmds.CMD_PARAM_LTG_ERROR, "get status for LTG {0}".format(ltg_id))
773                ret_val.append(result)
774        else:
775            result = self.send_cmd(cmds.LTGStatus(ltg_id_list))
776            if not result[0]:
777                self._print_ltg_error(cmds.CMD_PARAM_LTG_ERROR, "get status for LTG {0}".format(ltg_id_list))
778            ret_val.append(result)
779
780        return ret_val
781
782
783    def ltg_remove(self, ltg_id_list):
784        """Remove the LTG flows.
785
786        Args:
787            ltg_id_list (list of int, int): List of LTG flow identifiers or
788                single LTG flow identifier
789        """
790        if (type(ltg_id_list) is list):
791            for ltg_id in ltg_id_list:
792                status = self.send_cmd(cmds.LTGRemove(ltg_id))
793                self._print_ltg_error(status, "remove LTG {0}".format(ltg_id))
794        else:
795            status = self.send_cmd(cmds.LTGRemove(ltg_id_list))
796            self._print_ltg_error(status, "remove LTG {0}".format(ltg_id_list))
797
798
799    def ltg_start(self, ltg_id_list):
800        """Start the LTG flow.
801
802        Args:
803            ltg_id_list (list of int, int): List of LTG flow identifiers or
804                single LTG flow identifier
805        """
806        if (type(ltg_id_list) is list):
807            for ltg_id in ltg_id_list:
808                status = self.send_cmd(cmds.LTGStart(ltg_id))
809                self._print_ltg_error(status, "start LTG {0}".format(ltg_id))
810        else:
811            status = self.send_cmd(cmds.LTGStart(ltg_id_list))
812            self._print_ltg_error(status, "start LTG {0}".format(ltg_id_list))
813
814
815    def ltg_stop(self, ltg_id_list):
816        """Stop the LTG flow to the given nodes.
817
818        Args:
819            ltg_id_list (list of int, int): List of LTG flow identifiers or
820                single LTG flow identifier
821        """
822        if (type(ltg_id_list) is list):
823            for ltg_id in ltg_id_list:
824                status = self.send_cmd(cmds.LTGStop(ltg_id))
825                self._print_ltg_error(status, "stop LTG {0}".format(ltg_id))
826        else:
827            status = self.send_cmd(cmds.LTGStop(ltg_id_list))
828            self._print_ltg_error(status, "stop LTG {0}".format(ltg_id_list))
829
830
831    def ltg_remove_all(self):
832        """Stops and removes all LTG flows on the node."""
833        status = self.send_cmd(cmds.LTGRemove())
834        self._print_ltg_error(status, "remove all LTGs")
835
836
837    def ltg_start_all(self):
838        """Start all LTG flows on the node."""
839        status = self.send_cmd(cmds.LTGStart())
840        self._print_ltg_error(status, "start all LTGs")
841
842
843    def ltg_stop_all(self):
844        """Stop all LTG flows on the node."""
845        status = self.send_cmd(cmds.LTGStop())
846        self._print_ltg_error(status, "stop all LTGs")
847
848
849    def _print_ltg_error(self, status, msg):
850        """Print an LTG error message.
851
852        Args:
853            status (int): Status of LTG command
854            msg (str):    Message to print as part of LTG Error
855        """
856        if (status == cmds.CMD_PARAM_LTG_ERROR):
857            print("LTG ERROR: Could not {0} on '{1}'".format(msg, self.description))
858
859
860
861    #--------------------------------------------
862    # Configure Node Attribute Commands
863    #--------------------------------------------
864    def reset_all(self):
865        """Resets all portions of a node.
866
867        This includes:
868       
869          * Log
870          * Counts
871          * Local Traffic Generators (LTGs)
872          * Wireless Tx Queues
873          * BSS (i.e. all network state)
874          * Observed Networks (network_info_list)
875          * Observed Stations (station_info_list)
876
877        See the reset() command for a list of all portions of the node that
878        will be reset.
879        """
880        status = self.reset(log=True,
881                            txrx_counts=True,
882                            ltg=True,
883                            tx_queues=True,
884                            bss=True,
885                            network_list=True,
886                            station_info_list=True)
887
888        if (status == cmds.CMD_PARAM_LTG_ERROR):
889            print("LTG ERROR: Could not stop all LTGs on '{0}'".format(self.description))
890
891
892    def reset(self, log=False, txrx_counts=False, ltg=False, tx_queues=False,
893                    bss=False, network_list=False, station_info_list=False):
894        """Resets the state of node depending on the attributes.
895
896        Args:
897            log (bool): Reset the log data.  This will reset both the
898                log data on the node as well as the local variables used to
899                track log reads for ``log_get_all_new()``.  This will
900                not alter any log settings set by ``log_configure()``.
901            txrx_counts (bool): Reset the TX/RX Counts.  This will zero out
902                all of the packet / byte counts as well as the
903                ``latest_txrx_timestamp`` for all nodes in the station info
904                list.  It will also remove all promiscuous counts.
905            ltg (bool): Remove all LTGs.  This will stop and remove
906                any LTGs that are on the node.
907            tx_queues (bool): Purge all wireless Tx queues.  This will discard
908                all currently enqueued packets awaiting transmission at the
909                time the command is received.  This will not discard packets
910                already submitted to the lower-level MAC for transmission.
911            bss (bool): Reset network state.  This will nullify
912                the node's active BSS and removes all entries from the
913                station info list.  However, it will not remove the BSS from
914                the network list.  This will produce the following OTA
915                transmissions:
916               
917                  * For AP, a deauthentication frame to each associated station
918                  * For STA, a disassociation frame to its AP
919                  * For IBSS, nothing.
920                 
921            network_list (bool): Resets the list of observed wireless networks.
922                This will not remove the network_info for the node's current BSS.
923            station_info_list (bool): Resets the list of observed wireless clients.
924                This will not remove station_info entries for any associated nodes.
925        """
926        flags = 0
927
928        if log:
929            flags += cmds.CMD_PARAM_NODE_RESET_FLAG_LOG
930            self.log_total_bytes_read = 0
931            self.log_num_wraps        = 0
932            self.log_next_read_index  = 0
933
934        if txrx_counts:       flags += cmds.CMD_PARAM_NODE_RESET_FLAG_TXRX_COUNTS
935        if ltg:               flags += cmds.CMD_PARAM_NODE_RESET_FLAG_LTG
936        if tx_queues:         flags += cmds.CMD_PARAM_NODE_RESET_FLAG_TX_QUEUES
937        if bss:               flags += cmds.CMD_PARAM_NODE_RESET_FLAG_BSS
938        if network_list:      flags += cmds.CMD_PARAM_NODE_RESET_FLAG_NETWORK_LIST
939        if station_info_list: flags += cmds.CMD_PARAM_NODE_RESET_FLAG_STATION_INFO_LIST
940
941        # Send the reset command
942        return self.send_cmd(cmds.NodeResetState(flags))
943
944
945    def get_wlan_mac_address(self):
946        """Get the WLAN MAC Address of the node.
947
948        Returns:
949            MAC Address (int): 
950                Wireless Medium Access Control (MAC) address of the node.
951        """
952        addr = self.send_cmd(cmds.NodeProcWLANMACAddr(cmds.CMD_PARAM_READ))
953
954        if (addr != self.wlan_mac_address):
955            import wlan_exp.util as util
956
957            msg  = "WARNING:  WLAN MAC address mismatch.\n"
958            msg += "    Received MAC Address:  {0}".format(util.mac_addr_to_str(addr))
959            msg += "    Original MAC Address:  {0}".format(util.mac_addr_to_str(self.wlan_mac_address))
960            print(msg)
961
962        return addr
963
964
965    def set_mac_time(self, time, time_id=None):
966        """Sets the MAC time on the node.
967
968        Args:
969            time (int):              Time to which the node's MAC time will
970                be set (int in microseconds)
971            time_id (int, optional): Identifier used as part of the TIME_INFO
972                log entry created by this command.  If not specified, then a
973                random number will be used.
974        """
975        if type(time) not in [int, long]:
976            raise AttributeError("Time must be expressed in int microseconds")
977       
978        self.send_cmd(cmds.NodeProcTime(cmds.CMD_PARAM_WRITE, time, time_id))
979
980
981    def get_mac_time(self):
982        """Gets the MAC time from the node.
983
984        Returns:
985            time (int):  Timestamp of the MAC time of the node in int microseconds
986        """
987        node_time = self.send_cmd(cmds.NodeProcTime(cmds.CMD_PARAM_READ, cmds.CMD_PARAM_RSVD_TIME))
988
989        return node_time[0]
990
991
992    def get_system_time(self):
993        """Gets the system time from the node.
994
995        Returns:
996            Time (int):  Timestamp of the System time of the node in int microseconds
997        """
998        node_time = self.send_cmd(cmds.NodeProcTime(cmds.CMD_PARAM_READ, cmds.CMD_PARAM_RSVD_TIME))
999
1000        return node_time[1]
1001
1002
1003    def enable_beacon_mac_time_update(self, enable):
1004        """Enable / Disable MAC time update from received beacons.
1005       
1006        Args:
1007            enable (bool):  ``True`` enables and ``False`` disables MAC time
1008                updates from received beacons
1009        """
1010        self.send_cmd(cmds.NodeConfigure(beacon_mac_time_update=enable))
1011       
1012    def enable_ethernet_portal(self, enable):
1013        """Enable / Disable Ethernet Portal opertion of the node. When the portal is enabled the node
1014        bridges its ETH A Ethernet connection to the wireless medium. When the portal is disabled the node
1015        ignores all packets on ETH A and will not send any Ethernet transmissions on ETH A.
1016
1017        The Ethernet portal is enabled by default. Disabling the portal with this command is "sticky" - the
1018        portal will remain disabled until explicity re-enabled or until the node is rebooted. Changes to BSS
1019        state will not affect whether the Ethernet portal is enabled or disabled.
1020       
1021        Args:
1022            enable (bool):  ``True`` enables and ``False`` disables Ethernet
1023                portal functionality
1024        """
1025        self.send_cmd(cmds.NodeConfigure(portal_enable=enable))
1026
1027
1028    def set_radio_channel(self, channel):
1029        """Re-tune the node's RF interfaces to a new center frequency.
1030       
1031        This method directly controls the RF hardware. This can be disruptive
1032        to MAC operation if the node is currently associated with other nodes
1033        in a BSS which manages its own channel state.
1034       
1035        This method will immediately change the center frequency of the node's
1036        RF interfaces.  As a result this method can only be safely used on a
1037        node which is not currently a member of a BSS. To change the center
1038        frequency of nodes participating in a BSS use the
1039        ``node.configure_bss(channel=N)`` method on every node in the BSS.
1040       
1041        Args:
1042            channel (int):  Channel index for new center frequency. Must be a
1043                valid channel index.  See ``wlan_channels`` in util.py for the
1044                list of supported channel indexes.
1045        """
1046        import wlan_exp.util as util
1047       
1048        if channel in util.wlan_channels:
1049            if (self.is_scanning() is True):
1050                print('Warning: network scan is running and can overwrite the channel provided to set_radio_channel. Use stop_network_scan if needed.')           
1051               
1052            self.send_cmd(cmds.NodeProcChannel(cmds.CMD_PARAM_WRITE, channel))
1053        else:
1054            raise AttributeError("Channel must be in util.py wlan_channels")
1055
1056
1057    def set_low_to_high_rx_filter(self, mac_header=None, fcs=None):
1058        """Configures the filter that controls which received packets are
1059        passed from CPU Low to CPU High.
1060       
1061        The filter will always pass received packets where the destination
1062        address matches the node's MAC address. The filter can be configured
1063        to drop or pass other packets. This filter effectively controls which
1064        packets are written to the node's log.
1065
1066        Args:
1067            mac_header (str, optional): MAC header filter.  Values can be:
1068               
1069               - ``'MPDU_TO_ME'`` -- Pass all unicast-to-me or multicast data or management packet
1070               - ``'ALL_MPDU'``   -- Pass all data and management packets; no destination address filter
1071               - ``'ALL'``        -- Pass all packets; no packet type or address filters
1072           
1073            fcs (str, optional): FCS status filter.  Values can be:
1074
1075               - ``'GOOD'``       -- Pass only packets with good checksum result
1076               - ``'ALL'``        -- Pass packets with any checksum result
1077
1078        At boot the filter defaults to ``mac_header='ALL'`` and ``fcs='ALL'``.
1079        """
1080        self.send_cmd(cmds.NodeSetLowToHighFilter(cmds.CMD_PARAM_WRITE, mac_header, fcs))
1081
1082
1083    #------------------------
1084    # Tx Rate commands
1085
1086    def set_tx_rate_data(self, mcs, phy_mode, device_list=None, update_default_unicast=None, update_default_multicast=None):
1087        """Sets the packet transmit rate (mcs, phy_mode) for data frames.
1088
1089        Transmit parameters are maintained on a per-MAC address basis.
1090        The ``device_list`` argument can be used to select particular devices
1091        for which the Tx parameter update applies. The ``update_default_x``
1092        arguments can be used to specify that the provided power should be
1093        used for any future additions to the node's device list.
1094
1095        Args:
1096            mcs (int): Modulation and coding scheme (MCS) index (in [0 .. 7])
1097            phy_mode (str, int): PHY mode.  Must be one of:
1098
1099                * ``'NONHT'``: Use 802.11 (a/g) rates
1100                * ``'HTMF'``: Use 802.11 (n) rates
1101
1102            device_list (list of WlanExpNode / WlanDevice
1103             or 'ALL_UNICAST' or 'ALL_MULTICAST' or 'ALL', optional):
1104                List of 802.11 devices or single 802.11 device for which to set the
1105                unicast packet Tx power to 'power'. A value of 'ALL_UNICAST' will
1106                apply the specified power to all unicast receiver addresses. A value
1107                of `ALL_MULTICAST` will apply it to all multicast receiver addresses.
1108                A value of 'ALL' will apply the specified power to all addresses.
1109            update_default_unicast  (bool): set the default unicast Tx params to the
1110                provided 'power'.
1111            update_default_multicast  (bool): set the default multicast Tx params to the
1112                provided 'power'.               
1113
1114        One of ``device_list`` or ``update_default_unicast`` or ``update_default_multicast``
1115            must be set. 
1116        """
1117        if self._check_allowed_rate(mcs=mcs, phy_mode=phy_mode):
1118            self._node_set_tx_param(cmds.NodeProcTxRate, 'rate', (mcs, phy_mode), 
1119                                        'data', device_list, update_default_unicast, update_default_multicast)
1120        else:
1121            self._check_allowed_rate(mcs=mcs, phy_mode=phy_mode, verbose=True)
1122            raise AttributeError("Tx rate, (mcs, phy_mode) tuple, not supported by the design. See above error message.")
1123
1124    def set_tx_rate_mgmt(self, mcs, phy_mode, device_list=None, update_default_unicast=None, update_default_multicast=None):
1125        """Sets the packet transmit rate (mcs, phy_mode) for management frames.
1126
1127        Transmit parameters are maintained on a per-MAC address basis.
1128        The ``device_list`` argument can be used to select particular devices
1129        for which the Tx parameter update applies. The ``update_default_x``
1130        arguments can be used to specify that the provided power should be
1131        used for any future additions to the node's device list.
1132
1133        Args:
1134            mcs (int): Modulation and coding scheme (MCS) index (in [0 .. 7])
1135            phy_mode (str, int): PHY mode.  Must be one of:
1136
1137                * ``'NONHT'``: Use 802.11 (a/g) rates
1138                * ``'HTMF'``: Use 802.11 (n) rates
1139
1140            device_list (list of WlanExpNode / WlanDevice
1141             or 'ALL_UNICAST' or 'ALL_MULTICAST' or 'ALL', optional):
1142                List of 802.11 devices or single 802.11 device for which to set the
1143                unicast packet Tx power to 'power'. A value of 'ALL_UNICAST' will
1144                apply the specified power to all unicast receiver addresses. A value
1145                of `ALL_MULTICAST` will apply it to all multicast receiver addresses.
1146                A value of 'ALL' will apply the specified power to all addresses.
1147            update_default_unicast  (bool): set the default unicast Tx params to the
1148                provided 'power'.
1149            update_default_multicast  (bool): set the default multicast Tx params to the
1150                provided 'power'.               
1151
1152        One of ``device_list`` or ``update_default_unicast`` or ``update_default_multicast``
1153            must be set. 
1154        """
1155        if self._check_allowed_rate(mcs=mcs, phy_mode=phy_mode):
1156            self._node_set_tx_param(cmds.NodeProcTxRate, 'rate', (mcs, phy_mode), 
1157                                        'mgmt', device_list, update_default_unicast, update_default_multicast)
1158        else:
1159            self._check_allowed_rate(mcs=mcs, phy_mode=phy_mode, verbose=True)
1160            raise AttributeError("Tx rate, (mcs, phy_mode) tuple, not supported by the design. See above error message.")
1161
1162    #------------------------
1163    # Tx Antenna Mode commands
1164
1165    def set_tx_ant_mode_data(self, ant_mode, device_list=None, update_default_unicast=None, update_default_multicast=None):   
1166        """Sets the packet transmit antenna mode for data frames.
1167
1168        Transmit parameters are maintained on a per-MAC address basis.
1169        The ``device_list`` argument can be used to select particular devices
1170        for which the Tx parameter update applies. The ``update_default_x``
1171        arguments can be used to specify that the provided power should be
1172        used for any future additions to the node's device list.
1173
1174        Args:
1175            ant_mode (str):  Antenna mode; must be one of:
1176
1177                * ``'RF_A'``: transmit on RF A interface
1178                * ``'RF_B'``: transmit on RF B interface
1179
1180            device_list (list of WlanExpNode / WlanDevice
1181             or 'ALL_UNICAST' or 'ALL_MULTICAST' or 'ALL', optional):
1182                List of 802.11 devices or single 802.11 device for which to set the
1183                unicast packet Tx power to 'power'. A value of 'ALL_UNICAST' will
1184                apply the specified power to all unicast receiver addresses. A value
1185                of `ALL_MULTICAST` will apply it to all multicast receiver addresses.
1186                A value of 'ALL' will apply the specified power to all addresses.
1187            update_default_unicast  (bool): set the default unicast Tx params to the
1188                provided 'ant_mode'.
1189            update_default_multicast  (bool): set the default multicast Tx params to the
1190                provided 'ant_mode'.
1191
1192        One of ``device_list`` or ``update_default_unicast`` or ``update_default_multicast``
1193            must be set. 
1194        """
1195        if ant_mode is None:
1196            raise AttributeError("Invalid ant_mode: {0}".format(ant_mode))           
1197        self._node_set_tx_param(cmds.NodeProcTxAntMode, 'antenna_mode', 
1198                                        ant_mode, 'data', device_list, update_default_unicast, update_default_multicast)
1199                                       
1200    def set_tx_ant_mode_mgmt(self, ant_mode, device_list=None, update_default_unicast=None, update_default_multicast=None):   
1201        """Sets the packet transmit antenna mode for management frames.
1202
1203        Transmit parameters are maintained on a per-MAC address basis.
1204        The ``device_list`` argument can be used to select particular devices
1205        for which the Tx parameter update applies. The ``update_default_x``
1206        arguments can be used to specify that the provided power should be
1207        used for any future additions to the node's device list.
1208
1209        Args:
1210            ant_mode (str):  Antenna mode; must be one of:
1211
1212                * ``'RF_A'``: transmit on RF A interface
1213                * ``'RF_B'``: transmit on RF B interface
1214
1215            device_list (list of WlanExpNode / WlanDevice
1216             or 'ALL_UNICAST' or 'ALL_MULTICAST' or 'ALL', optional):
1217                List of 802.11 devices or single 802.11 device for which to set the
1218                unicast packet Tx power to 'power'. A value of 'ALL_UNICAST' will
1219                apply the specified power to all unicast receiver addresses. A value
1220                of `ALL_MULTICAST` will apply it to all multicast receiver addresses.
1221                A value of 'ALL' will apply the specified power to all addresses.
1222            update_default_unicast  (bool): set the default unicast Tx params to the
1223                provided 'ant_mode'.
1224            update_default_multicast  (bool): set the default multicast Tx params to the
1225                provided 'ant_mode'.               
1226
1227        One of ``device_list`` or ``update_default_unicast`` or ``update_default_multicast``
1228            must be set. 
1229        """
1230        if ant_mode is None:
1231            raise AttributeError("Invalid ant_mode: {0}".format(ant_mode))           
1232        self._node_set_tx_param(cmds.NodeProcTxAntMode, 'antenna_mode', 
1233                                        ant_mode, 'mgmt', device_list, update_default_unicast, update_default_multicast)
1234
1235    #------------------------
1236    # Rx Antenna Mode commands
1237
1238    def set_rx_ant_mode(self, ant_mode):
1239        """Sets the receive antenna mode for a node.
1240
1241        Args:
1242            ant_mode (str):  Antenna mode; must be one of:
1243
1244                * ``'RF_A'``: receive on RF A interface
1245                * ``'RF_B'``: receive on RF B interface
1246
1247        """
1248        if ant_mode is None:
1249            raise AttributeError("Invalid ant_mode: {0}".format(ant_mode))
1250           
1251        self.send_cmd(cmds.NodeProcRxAntMode(cmds.CMD_PARAM_WRITE, ant_mode))
1252
1253
1254    def get_rx_ant_mode(self):
1255        """Gets the current receive antenna mode for a node.
1256
1257        Returns:
1258            ant_mode (str): 
1259                Rx Antenna mode; must be one of:
1260
1261                  * ``'RF_A'``: receive on RF A interface
1262                  * ``'RF_B'``: receive on RF B interface
1263
1264        """
1265        return self.send_cmd(cmds.NodeProcRxAntMode(cmds.CMD_PARAM_READ))
1266
1267
1268    #------------------------
1269    # Tx Power commands
1270
1271    def set_tx_power_data(self, power, device_list=None, update_default_unicast=None, update_default_multicast=None):
1272        """Sets the transmit power for data frames.
1273
1274        This function is used to set the tranmsit power for frames of type
1275        data. Transmit parameters are maintained on a per-MAC address basis.
1276        The ``device_list`` argument can be used to select particular devices
1277        for which the Tx parameter update applies. The ``update_default_x``
1278        arguments can be used to specify that the provided power should be
1279        used for any future additions to the node's device list.
1280
1281        Args:
1282            power (int):  Transmit power in dBm (a value between
1283                ``node.max_tx_power_dbm`` and ``node.min_tx_power_dbm``)
1284            device_list (list of WlanExpNode / WlanDevice
1285             or 'ALL_UNICAST' or 'ALL_MULTICAST' or 'ALL', optional):
1286                List of 802.11 devices or single 802.11 device for which to set the
1287                unicast packet Tx power to 'power'. A value of 'ALL_UNICAST' will
1288                apply the specified power to all unicast receiver addresses. A value
1289                of `ALL_MULTICAST` will apply it to all multicast receiver addresses.
1290                A value of 'ALL' will apply the specified power to all addresses.
1291            update_default_unicast  (bool): set the default unicast Tx params to the
1292                provided 'power'.
1293            update_default_multicast  (bool): set the default multicast Tx params to the
1294                provided 'power'.               
1295
1296        One of ``device_list`` or ``update_default_unicast`` or ``update_default_multicast``
1297            must be set. 
1298        """
1299        self._node_set_tx_param(cmds.NodeProcTxPower, 'tx power', 
1300                                        (power, self.max_tx_power_dbm, self.min_tx_power_dbm), 
1301                                        'data', device_list, update_default_unicast, update_default_multicast)
1302
1303    def set_tx_power_mgmt(self, power, device_list=None, update_default_unicast=None, update_default_multicast=None):
1304        """Sets the transmit power for management frames.
1305
1306        This function is used to set the tranmsit power for frames of type
1307        management. Transmit parameters are maintained on a per-MAC address basis.
1308        The ``device_list`` argument can be used to select particular devices
1309        for which the Tx parameter update applies. The ``update_default_x``
1310        arguments can be used to specify that the provided power should be
1311        used for any future additions to the node's device list.
1312
1313        Args:
1314            power (int):  Transmit power in dBm (a value between
1315                ``node.max_tx_power_dbm`` and ``node.min_tx_power_dbm``)
1316            device_list (list of WlanExpNode / WlanDevice
1317             or 'ALL_UNICAST' or 'ALL_MULTICAST' or 'ALL', optional):
1318                List of 802.11 devices or single 802.11 device for which to set the
1319                unicast packet Tx power to 'power'. A value of 'ALL_UNICAST' will
1320                apply the specified power to all unicast receiver addresses. A value
1321                of `ALL_MULTICAST` will apply it to all multicast receiver addresses.
1322                A value of 'ALL' will apply the specified power to all addresses.
1323            update_default_unicast  (bool): set the default unicast Tx params to the
1324                provided 'power'.
1325            update_default_multicast  (bool): set the default multicast Tx params to the
1326                provided 'power'.         
1327               
1328        One of ``device_list`` or ``update_default_unicast`` or ``update_default_multicast``
1329            must be set. 
1330        """
1331        self._node_set_tx_param(cmds.NodeProcTxPower, 'tx power', 
1332                                        (power, self.max_tx_power_dbm, self.min_tx_power_dbm), 
1333                                        'mgmt', device_list, update_default_unicast, update_default_multicast)
1334
1335    def set_tx_power_ctrl(self, power):
1336        """Sets the control packet transmit power of the node.
1337
1338        Only the Tx power of the control packets can be set via wlan_exp.  The
1339        rate of control packets is determined by the 802.11 standard and
1340        control packets will be sent on whatever antenna that cause the control
1341        packet to be generated (ie an ack for a reception will go out on the
1342        same antenna on which the reception occurred).
1343
1344        Args:
1345            power (int):  Transmit power in dBm (a value between
1346                ``node.max_tx_power_dbm`` and ``node.min_tx_power_dbm``)
1347        """
1348        self.send_cmd(cmds.NodeProcTxPower(cmds.CMD_PARAM_WRITE, cmds.CMD_PARAM_TXPARAM_CTRL, 0, 0,
1349                                           (power, self.max_tx_power_dbm, self.min_tx_power_dbm), cmds.CMD_PARAM_TXPARAM_ADDR_NONE))
1350
1351
1352    def set_tx_power(self, power):
1353        """Sets the transmit power of the node.
1354
1355        This command will set all transmit power fields on the node to the same
1356        value:
1357       
1358            * Default Unicast Management Packet Tx Power for new station infos
1359            * Default Unicast Data Packet Tx Power for new station infos
1360            * Default Multicast Management Packet Tx Power for new station infos
1361            * Default Multicast Data Packet Tx Power for new station infos
1362            * Control Packet Tx Power
1363
1364        It will also update the transmit power for any frames destined for any
1365        known stations.
1366
1367        Args:
1368            power (int):  Transmit power in dBm (a value between
1369                ``node.max_tx_power_dbm`` and ``node.min_tx_power_dbm``)
1370        """
1371        self.send_cmd(cmds.NodeProcTxPower(cmds.CMD_PARAM_WRITE, cmds.CMD_PARAM_TXPARAM_ALL, 1, 1,
1372                                           (power, self.max_tx_power_dbm, self.min_tx_power_dbm), cmds.CMD_PARAM_TXPARAM_ADDR_ALL))
1373
1374
1375
1376    #------------------------
1377    # Other commands
1378
1379    def set_low_param(self, param_id, param_values):
1380        """Set a CPU Low parameter
1381
1382        This command provides a generic data pipe to set parameters in CPU Low. 
1383        Currently supported parameters are defined in cmds.py and use the
1384        naming convention:  CMD_PARAM_LOW_PARAM_*   In some cases, additional
1385        commands have been added to the node that utilize ``set_low_param()``
1386        in order to add error checking.
1387
1388        See http://warpproject.org/trac/wiki/802.11/wlan_exp/Extending
1389        for more information about how to extend ``set_low_param()`` to
1390        control additional parameters.
1391
1392        Args:
1393            param_id (int):              ID of parameter to be set
1394            param_values (list of int):  Value(s) to set the parameter
1395        """
1396        if(param_values is not None):
1397            try:
1398                v0 = param_values[0]
1399            except TypeError:
1400                v0 = param_values
1401
1402            if((type(v0) is not int) and (type(v0) is not long)) or (v0 >= 2**32):
1403                raise Exception('ERROR: parameter values must be scalar or iterable of ints in [0,2^32-1]!')
1404
1405        try:
1406            values = list(param_values)
1407        except TypeError:
1408            values = [param_values]
1409
1410        self.send_cmd(cmds.NodeLowParam(cmds.CMD_PARAM_WRITE, param_id=param_id, param_values=values))
1411
1412
1413    def configure_ofdm_pkt_det(self, corr_thresh, energy_thresh, min_dur = 4, post_wait = 0x3F, require_pkt_det = False):
1414        """Configures the OFDM auto-correlation packet detector thresholds. Set
1415        both thresholds to 0xFFFF to disable OFDM packet detections.
1416       
1417        Args:
1418            corr_thresh (int):  Auto-correlation threshold (in [1, 255])
1419            energy_thresh (int):  Energy threshold (in [1, 16383])
1420            min_dur (int): Minimum duration ([0, 15])
1421            post_wait (int): Post detection reset ([0, 63])
1422            require_pkt_det (bool): Require packet detection prior to PHY Rx
1423        """   
1424        if (corr_thresh < 1) or (corr_thresh > 255):
1425            raise AttributeError("'corr_thresh' must be in [1 .. 255].")
1426        if (energy_thresh < 1) or (energy_thresh > 16383):
1427            raise AttributeError("'energy_thresh' must be in [1 .. 16383].")         
1428        if (min_dur < 0) or (min_dur > 15):
1429            raise AttributeError("'min_dur' must be in [0 .. 15].")   
1430        if (post_wait < 0) or (post_wait > 63):
1431            raise AttributeError("'post_wait' must be in [0 .. 63].") 
1432        if type(require_pkt_det) is not bool:
1433            raise AttributeError("require_pkt_det must be a bool.  Provided {0}.".format(type(require_pkt_det)))       
1434        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))
1435   
1436    def configure_dsss_pkt_det(self, corr_thresh, energy_thresh, require_pkt_det = False):
1437        """Configures the DSSS auto-correlation packet detector thresholds.
1438       
1439        Args:
1440            corr_thresh (int):  Auto-correlation threshold (in [1, 255])
1441            energy_thresh (int):  Energy threshold (in [1, 16383])
1442            require_pkt_det (bool): Require packet detection prior to PHY Rx
1443        """   
1444        if (corr_thresh < 1) or (corr_thresh > 255):
1445            raise AttributeError("'corr_thresh' must be in [1 .. 255].")
1446        if (energy_thresh < 1) or (energy_thresh > 16383):
1447            raise AttributeError("'energy_thresh' must be in [1 .. 16383].")
1448        if type(require_pkt_det) is not bool:
1449            raise AttributeError("require_pkt_det must be a bool.  Provided {0}.".format(type(require_pkt_det)))       
1450        return self.set_low_param(cmds.CMD_PARAM_LOW_PARAM_DSSS_PKT_DET_THRESH, (corr_thresh, energy_thresh, require_pkt_det))
1451
1452
1453    def set_dcf_param(self, param_name, param_val):
1454        """Configures parameters of the DCF MAC in CPU Low.
1455       
1456        These parameters are write-only. These parameters only affect nodes
1457        running the DCF in CPU Low. Other MAC implementations should ignore
1458        these parameter updates.
1459
1460        Args:
1461            param_name (str): Name of the param to change (see table below)
1462            param_val (int): Value to set for param_name (see table below)
1463
1464        This method currently implements the following parameters:
1465
1466        .. list-table::
1467            :header-rows: 1
1468            :widths: 15 20 60
1469
1470            * - Name
1471              - Valid Values
1472              - Description
1473
1474            * - ``'rts_thresh'``
1475              - [0 .. 65535]
1476              - Threshold in bytes for maximum length
1477                packet which will not trigger RTS handshake
1478
1479            * - ``'short_retry_limit'`` and
1480                ``'long_retry_limit'``
1481              - [0 .. 10]
1482              - DCF retry limits, controls maximum number of retransmissions for short and long packets
1483                See `retransmissions <http://warpproject.org/trac/wiki/802.11/MAC/Lower/Retransmissions>`_.
1484                for more details.
1485
1486            * - ``'cw_exp_min'`` and
1487                ``'cw_exp_max'``
1488              - [0 .. 16]
1489              - Contention window exponent bounds. Contention window is set to random number in [0, (2^CW - 1)],
1490                where CW is bounded by [cw_exp_min, cw_exp_max]
1491
1492
1493        """
1494        if type(param_name) is not str:
1495            raise AttributeError("param_name must be a str.  Provided {0}.".format(type(param_name)))
1496       
1497        if type(param_val) is not int:
1498            raise AttributeError("param_val must be an int.  Provided {0}.".format(type(param_val)))
1499       
1500        # Process paramater
1501        if   (param_name == 'rts_thresh'):
1502            if ((param_val < 0) or (param_val > 65535)):
1503                raise AttributeError("'rts_thresh' must be in [0 .. 65535].")
1504           
1505            if self.low_sw_id != defaults.WLAN_EXP_LOW_SW_ID_DCF:
1506                raise Exception('ERROR: rts_thresh only valid with DCF application in CPU Low!')
1507
1508            self.set_low_param(param_id=cmds.CMD_PARAM_LOW_PARAM_DCF_RTS_THRESH, param_values=param_val)
1509           
1510        elif (param_name == 'short_retry_limit'):
1511            if ((param_val < 0) or (param_val > 65535)):
1512                raise AttributeError("'short_retry_limit' must be in [0 .. 65535].")
1513               
1514            if self.low_sw_id != defaults.WLAN_EXP_LOW_SW_ID_DCF:
1515                raise Exception('ERROR: short_retry_limit only valid with DCF application in CPU Low!')
1516   
1517            self.set_low_param(param_id=cmds.CMD_PARAM_LOW_PARAM_DCF_DOT11SHORTRETRY, param_values=param_val)
1518           
1519        elif (param_name == 'long_retry_limit'):
1520            if ((param_val < 0) or (param_val > 65535)):
1521                raise AttributeError("'long_retry_limit' must be in [0 .. 65535].")
1522               
1523            if self.low_sw_id != defaults.WLAN_EXP_LOW_SW_ID_DCF:
1524                raise Exception('ERROR: long_retry_limit only valid with DCF application in CPU Low!')
1525   
1526            self.set_low_param(param_id=cmds.CMD_PARAM_LOW_PARAM_DCF_DOT11LONGRETRY, param_values=param_val)       
1527           
1528        elif (param_name == 'cw_exp_min'):
1529            if ((param_val < 0) or (param_val > 16)):
1530                raise AttributeError("'cw_exp_min' must be in [0 .. 16].")
1531               
1532            if self.low_sw_id != defaults.WLAN_EXP_LOW_SW_ID_DCF:
1533                raise Exception('ERROR: cw_exp_min only valid with DCF application in CPU Low!')
1534   
1535            self.set_low_param(param_id=cmds.CMD_PARAM_LOW_PARAM_DCF_CW_EXP_MIN, param_values=param_val)
1536           
1537        elif (param_name == 'cw_exp_max'):
1538            if ((param_val < 0) or (param_val > 16)):
1539                raise AttributeError("'cw_exp_max' must be in [0 .. 16].")
1540               
1541            if self.low_sw_id != defaults.WLAN_EXP_LOW_SW_ID_DCF:
1542                raise Exception('ERROR: cw_exp_max only valid with DCF application in CPU Low!')
1543   
1544            self.set_low_param(param_id=cmds.CMD_PARAM_LOW_PARAM_DCF_CW_EXP_MAX, param_values=param_val)
1545           
1546        else:
1547            msg  = "param_name must be one of the following strings:\n"
1548            msg += "    'rts_thresh', 'short_retry_limit', 'long_retry_limit', \n"
1549            msg += "    'phy_cs_thresh', 'cw_exp_min', 'cw_exp_max' \n"
1550            msg += "Provided '{0}'".format(param_name)
1551            raise AttributeError(msg)
1552
1553
1554    def configure_pkt_det_min_power(self, enable, power_level=None):
1555        """Configure Minimum Power Requirement of Packet Detector.
1556
1557        Args:
1558            enable (bool):      True/False
1559            power_level (int):  [-90,-30] dBm
1560        """
1561        if enable is False:
1562            self.set_low_param(param_id=cmds.CMD_PARAM_LOW_PARAM_PKT_DET_MIN_POWER, param_values=0)
1563        else:
1564            if power_level is not None:
1565                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:
1566                    # C code expects a u32
1567                    #  Bit 24 is flag for en/disable of min_power logic
1568                    #  Bits [7:0] are u8 interpreted as dB above min threshold
1569                    param = (1 << 24) | ((power_level - cmds.CMD_PARAM_NODE_MIN_MIN_PKT_DET_POWER_DBM) & 0xFF)
1570                    self.set_low_param(param_id=cmds.CMD_PARAM_LOW_PARAM_PKT_DET_MIN_POWER, param_values=param)
1571                else:
1572                    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)
1573                    raise ValueError(msg)
1574            else:
1575                msg  = "\nPower level not specified\n"
1576                raise ValueError(msg)
1577
1578
1579    def set_phy_samp_rate(self, phy_samp_rate):
1580        """Sets the PHY sample rate (in MSps)
1581
1582        Args:
1583            phy_samp_rate (int):   
1584                PHY sample rate in MSps (10, 20, 40).  Default is 20 MSps.
1585        """
1586        if (phy_samp_rate not in [10, 20, 40]):
1587            raise AttributeError("'phy_samp_rate' must be in [10, 20, 40].")
1588           
1589        self.set_low_param(param_id=cmds.CMD_PARAM_LOW_PARAM_PHY_SAMPLE_RATE, param_values=phy_samp_rate)
1590
1591
1592    def set_random_seed(self, high_seed=None, low_seed=None, gen_random=False):
1593        """Sets the random number generator seed on the node.
1594
1595        Args:
1596            high_seed (int, optional):   Set the random number generator seed
1597                on CPU high
1598            low_seed (int, optional):    Set the random number generator seed
1599                on CPU low
1600            gen_random (bool, optional): If high_seed or low_seed is not
1601                provided, then generate a random seed for the generator.
1602        """
1603        import random
1604        max_seed = 2**32 - 1
1605        min_seed = 0
1606
1607        if (high_seed is None):
1608            if gen_random:
1609                high_seed = random.randint(min_seed, max_seed)
1610        else:
1611            if (high_seed > max_seed) or (high_seed < min_seed):
1612                msg  = "Seed must be an integer between [{0}, {1}]".format(min_seed, max_seed)
1613                raise AttributeError(msg)
1614
1615        if (low_seed is None):
1616            if gen_random:
1617                low_seed  = random.randint(min_seed, max_seed)
1618        else:
1619            if (low_seed > max_seed) or (low_seed < min_seed):
1620                msg  = "Seed must be an integer between [{0}, {1}]".format(min_seed, max_seed)
1621                raise AttributeError(msg)
1622
1623        self.send_cmd(cmds.NodeProcRandomSeed(cmds.CMD_PARAM_WRITE, high_seed, low_seed))
1624
1625
1626    def enable_dsss(self, enable):
1627        """Enable / Disable DSSS receptions on the node
1628
1629        By default DSSS receptions are enabled on the node when possible
1630        (ie PHY sample rate is 20 MHz and Channel is in 2.4 GHz band)
1631       
1632        Args:
1633            enable (bool): 
1634           
1635              * True - enable DSSS receptions on the node
1636              * False - disable DSSS receptions on the node
1637        """
1638        self.send_cmd(cmds.NodeConfigure(dsss_enable=enable))
1639
1640    def enable_ofdm_rx(self, enable):
1641        """Enable / Disable OFDM receptions on the node
1642
1643        OFDM receptions are enabled by default. The OFDM Rx pipeline can
1644        be disabled which will block all OFDM packet detections and OFDM
1645        Rx attempts. When OFDM Rx is disabled only the DSSS Rx pipeline
1646        will attempt to receive new packets.
1647       
1648        Args:
1649            enable (bool): 
1650              * True - enable OFDM receptions on the node
1651              * False - disable OFDM receptions on the node
1652        """
1653        return self.set_low_param(cmds.CMD_PARAM_LOW_PARAM_OFDM_RX_EN, int(enable))
1654       
1655    def set_print_level(self, level):
1656        """Set the verbosity of the wlan_exp output to the node's UART.
1657
1658        Args:
1659            level (int):  Printing level (defaults to ``WARNING``)
1660
1661        Valid print levels can be found in ``wlan_exp.util.uart_print_levels``:
1662       
1663          * ``NONE``    - Do not print messages
1664          * ``ERROR``   - Only print error messages
1665          * ``WARNING`` - Print error and warning messages
1666          * ``INFO``    - Print error, warning and info messages
1667          * ``DEBUG``   - Print error, warning, info and debug messages
1668        """
1669        import wlan_exp.util as util
1670       
1671        valid_levels = ['NONE', 'ERROR', 'WARNING', 'INFO', 'DEBUG',
1672                        util.uart_print_levels['NONE'], 
1673                        util.uart_print_levels['ERROR'],
1674                        util.uart_print_levels['WARNING'],
1675                        util.uart_print_levels['INFO'],
1676                        util.uart_print_levels['DEBUG']]
1677       
1678        if (level in valid_levels):
1679            self.send_cmd(cmds.NodeConfigure(print_level=level))
1680        else:
1681            msg  = "\nInvalid print level {0}.  Print level must be one of: \n".format(level)
1682            msg += "    ['NONE', 'ERROR', 'WARNING', 'INFO', 'DEBUG', \n"
1683            msg += "     uart_print_levels['NONE'], uart_print_levels['ERROR'],\n"
1684            msg += "     uart_print_levels['WARNING'], uart_print_levels['INFO'],\n"
1685            msg += "     uart_print_levels['DEBUG']]"
1686            raise ValueError(msg)
1687           
1688
1689
1690    #--------------------------------------------
1691    # Internal methods to view / configure node attributes
1692    #--------------------------------------------
1693    def _check_allowed_rate(self, mcs, phy_mode, verbose=False):
1694        """Check that rate parameters are allowed
1695
1696        Args:
1697            mcs (int):           Modulation and coding scheme (MCS) index
1698            phy_mode (str, int): PHY mode (from util.phy_modes)
1699
1700        Returns:
1701            valid (bool):  Are all parameters valid?
1702        """
1703        return self._check_supported_rate(mcs, phy_mode, verbose)
1704
1705
1706    def _check_supported_rate(self, mcs, phy_mode, verbose=False):
1707        """Checks that the selected rate parameters are supported by the
1708        current MAC and PHY implementation. This method only checks if a
1709        rate can be used in hardware. The application-specific method
1710        _check_allowed_rate() should be used to confirm a selected rate is
1711        both supported and allowed given the currnet MAC and network state.
1712
1713        Args:
1714            mcs (int): Modulation and coding scheme (MCS) index (in [0 .. 7])
1715            phy_mode (str, int): PHY mode.  Must be one of:
1716
1717                * ``'NONHT'``: Use 802.11 (a/g) rates
1718                * ``'HTMF'``: Use 802.11 (n) rates
1719
1720        Returns:
1721            rate_suppored (bool):  True if the specified rate is supported
1722        """
1723        import wlan_exp.util as util
1724
1725        rate_ok = True
1726
1727        if ((mcs < 0) or (mcs > 7)):
1728            if (verbose):
1729                print("Invalid MCS {0}. MCS must be integer in [0 .. 7]".format(mcs))
1730            rate_ok = False
1731
1732        if (phy_mode not in ['NONHT', 'HTMF', util.phy_modes['NONHT'], util.phy_modes['HTMF']]):
1733            if (verbose):
1734                print("Invalid PHY mode {0}. PHY mode must be one of ['NONHT', 'HTMF', phy_modes['NONHT'], phy_modes['HTMF']]".format(phy_mode))
1735            rate_ok = False
1736
1737        return rate_ok
1738
1739
1740    def _node_set_tx_param(self, cmd, param_name, param, frametype,
1741                               device_list=None, update_default_unicast=None, update_default_multicast=None):
1742        """Sets the data & management transmit parameters of the node.
1743
1744        Args:
1745            cmd (Cmd):          Command to be used to set param
1746            param_name (str):   Name of parameter for error messages
1747            param (int):        Parameter to be set
1748            frametype(str):     `data` or `mgmt`
1749            device_list (list of WlanExpNode / WlanDevice
1750             or 'ALL_UNICAST' or 'ALL_MULTICAST' or 'ALL', optional):
1751                List of 802.11 devices or single 802.11 device for which to set the
1752                unicast packet Tx power to 'power'. A value of 'ALL_UNICAST' will
1753                apply the specified power to all unicast receiver addresses. A value
1754                of `ALL_MULTICAST` will apply it to all multicast receiver addresses.
1755                A value of 'ALL' will apply the specified power to all addresses.
1756            update_default_unicast  (bool): set the default unicast Tx params to the
1757                provided 'power'.
1758            update_default_multicast  (bool): set the default multicast Tx params to the
1759                provided 'power'.         
1760
1761        One of ``device_list`` or ``default`` must be set.         
1762        """
1763        if (device_list is None) and (update_default_unicast is None) and (update_default_multicast is None):
1764            msg  = "\nCannot set the unicast transmit {0}:\n".format(param_name)
1765            msg += "    Must specify either a list of devices, 'ALL' current station infos,\n"
1766            msg += "    or update_default_unicast or update_default_multicast {0}.".format(param_name)
1767            raise ValueError(msg)
1768           
1769        if( (update_default_unicast is True) or (device_list == 'ALL_UNICAST') or (device_list == 'ALL') ):
1770            update_default_unicast = 1
1771        else:
1772            update_default_unicast = 0
1773
1774        if( (update_default_multicast is True) or (device_list == 'ALL_MULTICAST') or (device_list == 'ALL') ):
1775            update_default_multicast = 1
1776        else:
1777            update_default_multicast = 0
1778           
1779
1780        if(frametype == 'data'):
1781            if(device_list == 'ALL_UNICAST'):
1782                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))
1783            elif(device_list == 'ALL_MULTICAST'):
1784                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))
1785            elif(device_list == 'ALL'):
1786                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))               
1787            elif(device_list is not None):
1788                try:
1789                    for device in device_list:
1790                        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))
1791                except TypeError:
1792                    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))
1793            else:
1794                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))
1795        elif(frametype == 'mgmt'):
1796            if(device_list == 'ALL_UNICAST'):
1797                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))
1798            elif(device_list == 'ALL_MULTICAST'):
1799                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))
1800            elif(device_list == 'ALL'):
1801                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))               
1802            elif(device_list is not None):
1803                try:
1804                    for device in device_list:
1805                        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))
1806                except TypeError:
1807                    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))
1808            else:
1809                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))
1810
1811    #--------------------------------------------
1812    # Scan Commands
1813    #--------------------------------------------
1814    def set_scan_parameters(self, time_per_channel=None, num_probe_tx_per_channel=None, channel_list=None, ssid=None):
1815        """Set the paramters of the wireless network scan state machine.
1816       
1817        Args:
1818            time_per_channel (float, optional): Time (in float sec) to spend
1819                on each channel.  A value of None will not modify the current
1820                time per channel
1821            num_probe_tx_per_channel (int, optional):   Number of probe requests
1822                transmitted while on each channel.  A value of None will not
1823                modify the current number of probe requests per channel.
1824            channel_list (list of int optional): Channel(s) to scan; A value
1825                of None will not modify the current channel list.
1826            ssid (str, optional):  SSID to scan for (as part of probe request);
1827                A value of None will not modify the current SSID.
1828       
1829        Setting ``num_probe_tx_per_chan`` to a non-zero value will enable
1830        active scanning. The node will transmit broadcast Probe Request packets
1831        on every channel and will gather network information from received
1832        Probe Response and Beacon packets. Setting ``num_probe_tx_per_chan=0``
1833        will enable passive scanning. In this mode the node will not transmit
1834        any Probe Request packets and network information will be gathered only
1835        from received Beacon packets.
1836
1837        The blank SSID (``ssid=""``) is interpretted as a wildcard and will
1838        solicit Probe Response packets from networks with any SSID. "Closed"
1839        networks do not respond to the wildcard SSID. These networks will still
1840        be discovered via Beacon receptions.
1841
1842        If the channel list / SSID is not specified, then it will not be
1843        updated on the node (ie it will use the current channel list / SSID)
1844
1845        """
1846        # Check time_per_channel
1847        if time_per_channel is not None:
1848            try:
1849                time_per_channel = float(time_per_channel)
1850            except:
1851                raise AttributeError("time_per_channel must be expressable as a float.")
1852               
1853        # Check channel_list
1854        if channel_list is not None:
1855            tmp_chan_list = []
1856           
1857            if type(channel_list) is str:
1858                # Process pre-defined strings
1859                import wlan_exp.util as util
1860
1861                if   (channel_list == 'ALL'):
1862                    tmp_chan_list = util.wlan_channels
1863                elif (channel_list == 'ALL_2.4GHZ'):
1864                    tmp_chan_list = [x for x in util.wlan_channels if (x <= 14)]
1865                elif (channel_list == 'ALL_5GHZ'):
1866                    tmp_chan_list = [x for x in util.wlan_channels if (x > 14)]
1867                else:
1868                    msg  = "\n    String '{0}' not recognized.".format(channel_list)
1869                    msg += "\n    Please use 'ALL', 'ALL_2.4GHZ', 'ALL_5GHZ' or either an int or list of channels"
1870                    raise AttributeError(msg)
1871
1872            elif type(channel_list) is int:
1873                # Proess scalar integer
1874                tmp_chan_list.append(channel_list)
1875               
1876            else:
1877                # Proess interables
1878                try:
1879                    for channel in channel_list:
1880                        tmp_chan_list.append(channel)
1881                except:
1882                    msg  = "\n    Unable to process channel_list."
1883                    msg += "\n    Please use 'ALL', 'ALL_2.4GHZ', 'ALL_5GHZ' or either an int or list of channels"
1884                    raise AttributeError(msg)
1885
1886            if tmp_chan_list:
1887                channel_list = tmp_chan_list
1888            else:
1889                msg  = "\n    channel_list is empty."
1890                msg += "\n    Please use 'ALL', 'ALL_2.4GHZ', 'ALL_5GHZ' or either an int or list of channels"
1891                raise AttributeError(msg)
1892               
1893        self.send_cmd(cmds.NodeProcScanParam(cmds.CMD_PARAM_WRITE, time_per_channel, num_probe_tx_per_channel, channel_list, ssid))
1894   
1895   
1896    def start_network_scan(self):
1897        """Starts the wireless network scan state machine at the node.
1898       
1899        During a scan, the node cycles through a set of channels and transmits
1900        periodic Probe Request frames on each channel.  Information about
1901        available wireless networks is extracted from received Probe Responses
1902        and Beacon frames. The network scan results can be queried any time
1903        using the ``node.get_network_list()`` method.
1904
1905        Network scans can only be run by unassociated nodes. An associated
1906        node must first reset its BSS state before starting a scan.
1907
1908        The network scan state machine can be stopped with
1909        ``node.stop_network_scan()``. The scan state machine will also be
1910        stopped automatically if the node is configured with a new non-null
1911        BSS state.
1912           
1913        Example:
1914        ::
1915
1916            # Ensure node has null BSS state
1917            n.configure_bss(None)
1918
1919            # Start the scan state machine; scan will use default scan params
1920            #  Use n.set_scan_parameters() to customize scan behavior
1921            n.start_network_scan()
1922
1923            # Wait 5 seconds, retrieve the list of discovered networks
1924            time.sleep(5)
1925            networks = n.get_network_list()
1926
1927            # Stop the scan state machine
1928            n.stop_network_scan()
1929
1930        """
1931        self.send_cmd(cmds.NodeProcScan(enable=True))
1932   
1933   
1934    def stop_network_scan(self):
1935        """Stops the wireless network scan state machine."""
1936        self.send_cmd(cmds.NodeProcScan(enable=False))
1937
1938
1939    def is_scanning(self):
1940        """Queries if the node's wireless network scanning state machine is
1941        currently running.
1942       
1943        Returns:
1944            status (bool):
1945
1946                * True      -- Scan state machine is running
1947                * False     -- Scan state machine is not running
1948        """
1949        return self.send_cmd(cmds.NodeProcScan())
1950
1951
1952
1953    #--------------------------------------------
1954    # Association Commands
1955    #--------------------------------------------
1956    def configure_bss(self):
1957        """Configure the Basic Service Set (BSS) information of the node
1958       
1959        Each node is either a member of no BSS (colloquially "unassociated")
1960        or a member of one BSS.  A node requires a minimum valid bss_info to
1961        be a member of a BSS.  Based on the node type, there is a minimum
1962        set of fields needed for a valid bss_info. 
1963       
1964        This method must be overloaded by sub-classes.
1965       
1966        See http://warpproject.org/trac/wiki/802.11/wlan_exp/bss for more
1967        information about BSSes.
1968        """
1969        raise NotImplementedError()
1970
1971    def get_station_info(self, device_list=None):
1972       
1973        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.')
1974       
1975    def get_bss_members(self):
1976        """Get the BSS members from the node.
1977
1978        The StationInfo() returned by this method can be accessed like a
1979        dictionary and has the following fields:
1980       
1981            +-----------------------------+----------------------------------------------------------------------------------------------------+
1982            | Field                       | Description                                                                                        |
1983            +=============================+====================================================================================================+
1984            | mac_addr                    |  MAC address of station                                                                            |
1985            +-----------------------------+----------------------------------------------------------------------------------------------------+
1986            | id                          |  Identification Index for this station                                                             |
1987            +-----------------------------+----------------------------------------------------------------------------------------------------+
1988            | host_name                   |  String hostname (19 chars max), taken from DHCP DISCOVER packets                                  |
1989            +-----------------------------+----------------------------------------------------------------------------------------------------+
1990            | flags                       |  Station flags.  Value containts 1 bit fields:                                                     |
1991            |                             |      * 0x0001 - 'KEEP'                                                                             |
1992            |                             |      * 0x0002 - 'DISABLE_ASSOC_CHECK'                                                              |
1993            |                             |      * 0x0004 - 'DOZE'                                                                             |
1994            |                             |      * 0x0008 - 'HT_CAPABLE'                                                                       |
1995            |                             |                                                                                                    |
1996            +-----------------------------+----------------------------------------------------------------------------------------------------+
1997            | latest_rx_timestamp         |  Value of System Time in microseconds of last successful Rx from device                            |
1998            +-----------------------------+----------------------------------------------------------------------------------------------------+
1999            | latest_txrx_timestamp       |  Value of System Time in microseconds of last Tx or successful Rx from device                      |           
2000            +-----------------------------+----------------------------------------------------------------------------------------------------+
2001            | latest_rx_seq               |  Sequence number of last packet received from device                                               |
2002            +-----------------------------+----------------------------------------------------------------------------------------------------+
2003            | tx_phy_mcs                  |  Current PHY MCS index in [0:7] for new transmissions to device                                    |
2004            +-----------------------------+----------------------------------------------------------------------------------------------------+
2005            | tx_phy_mode                 |  Current PHY mode for new transmissions to deivce                                                  |
2006            +-----------------------------+----------------------------------------------------------------------------------------------------+
2007            | tx_phy_antenna_mode         |  Current PHY antenna mode in [1:4] for new transmissions to device                                 |
2008            +-----------------------------+----------------------------------------------------------------------------------------------------+
2009            | tx_phy_power                |  Current Tx power in dBm for new transmissions to device                                           |
2010            +-----------------------------+----------------------------------------------------------------------------------------------------+
2011            | tx_mac_flags                |  Flags for Tx MAC config for new transmissions to device.  Value contains 1 bit flags:             |
2012            |                             |                                                                                                    |
2013            |                             |      * None defined                                                                                |
2014            |                             |                                                                                                    |
2015            +-----------------------------+----------------------------------------------------------------------------------------------------+
2016       
2017        Returns:
2018            station_infos (list of StationInfo): 
2019                List of StationInfo() BSS members known to the node
2020
2021        """
2022        ret_val = self.send_cmd(cmds.NodeGetBSSMembers())
2023
2024        return ret_val
2025
2026
2027    def get_station_info_list(self, device_list=None):
2028        """Get the all Station Infos from node.
2029       
2030        Args:
2031            device_list (optional): single or iterable of WlanExpNode or WlanDevice
2032                List of devices for which to get counts. Omit this argument to retrieve
2033                all station_info entries from node
2034
2035        The StationInfo() returned by this method can be accessed like a
2036        dictionary and has the following fields:
2037       
2038            +-----------------------------+----------------------------------------------------------------------------------------------------+
2039            | Field                       | Description                                                                                        |
2040            +=============================+====================================================================================================+
2041            | mac_addr                    |  MAC address of station                                                                            |
2042            +-----------------------------+----------------------------------------------------------------------------------------------------+
2043            | id                          |  Identification Index for this station                                                             |
2044            +-----------------------------+----------------------------------------------------------------------------------------------------+
2045            | host_name                   |  String hostname (19 chars max), taken from DHCP DISCOVER packets                                  |
2046            +-----------------------------+----------------------------------------------------------------------------------------------------+
2047            | flags                       |  Station flags.  Value containts 1 bit fields:                                                     |
2048            |                             |      * 0x0001 - 'KEEP'                                                                             |
2049            |                             |      * 0x0002 - 'DISABLE_ASSOC_CHECK'                                                              |
2050            |                             |      * 0x0004 - 'DOZE'                                                                             |
2051            |                             |      * 0x0008 - 'HT_CAPABLE'                                                                       |
2052            |                             |                                                                                                    |
2053            +-----------------------------+----------------------------------------------------------------------------------------------------+
2054            | latest_rx_timestamp         |  Value of System Time in microseconds of last successful Rx from device                            |
2055            +-----------------------------+----------------------------------------------------------------------------------------------------+
2056            | latest_txrx_timestamp       |  Value of System Time in microseconds of last Tx or successful Rx from device                      |           
2057            +-----------------------------+----------------------------------------------------------------------------------------------------+
2058            | latest_rx_seq               |  Sequence number of last packet received from device                                               |
2059            +-----------------------------+----------------------------------------------------------------------------------------------------+
2060            | tx_phy_mcs                  |  Current PHY MCS index in [0:7] for new transmissions to device                                    |
2061            +-----------------------------+----------------------------------------------------------------------------------------------------+
2062            | tx_phy_mode                 |  Current PHY mode for new transmissions to deivce                                                  |
2063            +-----------------------------+----------------------------------------------------------------------------------------------------+
2064            | tx_phy_antenna_mode         |  Current PHY antenna mode in [1:4] for new transmissions to device                                 |
2065            +-----------------------------+----------------------------------------------------------------------------------------------------+
2066            | tx_phy_power                |  Current Tx power in dBm for new transmissions to device                                           |
2067            +-----------------------------+----------------------------------------------------------------------------------------------------+
2068            | tx_mac_flags                |  Flags for Tx MAC config for new transmissions to device.  Value contains 1 bit flags:             |
2069            |                             |                                                                                                    |
2070            |                             |      * None defined                                                                                |
2071            |                             |                                                                                                    |
2072            +-----------------------------+----------------------------------------------------------------------------------------------------+
2073       
2074        Returns:
2075            station_infos (list of StationInfo): 
2076                List of StationInfo() dictionary-like objects. Length of returned list depends on device_list argument.
2077               
2078                If device_list is not provided, the length of the return list will equal the length of the station_info
2079                list on the node. If the node's station_info list is empty this method will return an empty list ([])
2080               
2081                If device_list is provided the length of the return list will equal the list of device_list. Each entry
2082                in the returned list will either be None (if the node had no matching station_info) or a StationInfo()
2083                dictionary-like object (if the node had a matching station_info entry).
2084        """
2085        ret = []
2086
2087        if device_list is None:
2088            # Retrieve all station_info from node
2089            ret = self.send_cmd(cmds.NodeGetStationInfoList())
2090        else:
2091            # Covert scalar device_list to iterable
2092            try:
2093                devs = iter(device_list)
2094            except TypeError:
2095                devs = [device_list]
2096
2097            for device in devs:
2098                station_info = self.send_cmd(cmds.NodeGetStationInfoList(device.wlan_mac_address))
2099                if (len(station_info) == 1):
2100                    # Node returned list with one valid station_info for this device
2101                    ret.append(station_info[0])
2102                else:
2103                    # Node had no station_info for this device
2104                    ret.append(None)
2105
2106        return ret
2107
2108    def get_bss_info(self):
2109       print('WARNING: get_bss_info() is deprecated and will be removed in a future version. Please use get_network_info()')
2110       return self.get_network_info()
2111
2112    def get_bss_config(self):
2113        """Get BSS configuration of the network the node is a member of
2114
2115        Returns a dictionary with the following fields:
2116       
2117            +-----------------------------+----------------------------------------------------------------------------------------------------+
2118            | Field                       | Description                                                                                        |
2119            +=============================+====================================================================================================+
2120            | bssid                       |  BSS ID: 48-bit MAC address                                                                        |
2121            +-----------------------------+----------------------------------------------------------------------------------------------------+
2122            | channel                     |  Primary channel.  In util.wlan_channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 36, 40, 44, 48]     |
2123            +-----------------------------+----------------------------------------------------------------------------------------------------+
2124            | channel_type                |  Channel Type.  Value is one of:                                                                   |
2125            |                             |                                                                                                    |
2126            |                             |      * 0x00 - 'BW20'                                                                               |
2127            |                             |      * 0x01 - 'BW40_SEC_BELOW'                                                                     |
2128            |                             |      * 0x02 - 'BW40_SEC_ABOVE'                                                                     |
2129            |                             |                                                                                                    |
2130            +-----------------------------+----------------------------------------------------------------------------------------------------+
2131            | ssid                        |  SSID (32 chars max)                                                                               |
2132            +-----------------------------+----------------------------------------------------------------------------------------------------+
2133            | ht_capable                  |  1 - Network is capable of HT PHY mode                                                             |
2134            |                             |  0 - Netowrk is not capable of NHT PHY mode                                                        |
2135            +-----------------------------+----------------------------------------------------------------------------------------------------+
2136            | beacon_interval             |  Beacon interval - In time units of 1024 us'                                                       |
2137            +-----------------------------+----------------------------------------------------------------------------------------------------+           
2138            | dtim_period                 |                                                                                                    | 
2139            +-----------------------------+----------------------------------------------------------------------------------------------------+       
2140           
2141        Returns:
2142            bss_config : 
2143                BSS configuration of the network that the node is a member of (can be None)
2144        """
2145        network_info = self.get_network_info()
2146       
2147        if(network_info is None):
2148            # Node has NULL active_network_info - return None
2149            return None
2150       
2151        # Use the field names of the BSSConfig InfoStruct to transform the network_info
2152        #  into a bss_config dictionary
2153        from wlan_exp.info import BSSConfig
2154       
2155        bss_config_fields = BSSConfig().get_field_names()
2156       
2157        # Construct a dictionary with only BSSConfig fields
2158        bss_config = {}
2159        for f in bss_config_fields:
2160            bss_config[f] = network_info[f]
2161       
2162        return bss_config
2163
2164    def get_network_info(self):
2165        """Get information about the network the node is a member of
2166
2167        The NetworkInfo() returned by this method can be accessed like a
2168        dictionary and has the following fields:
2169       
2170            +-----------------------------+----------------------------------------------------------------------------------------------------+
2171            | Field                       | Description                                                                                        |
2172            +=============================+====================================================================================================+
2173            | bssid                       |  BSS ID: 48-bit MAC address                                                                        |
2174            +-----------------------------+----------------------------------------------------------------------------------------------------+
2175            | channel                     |  Primary channel.  In util.wlan_channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 36, 40, 44, 48]     |
2176            +-----------------------------+----------------------------------------------------------------------------------------------------+
2177            | channel_type                |  Channel Type.  Value is one of:                                                                   |
2178            |                             |                                                                                                    |
2179            |                             |      * 0x00 - 'BW20'                                                                               |
2180            |                             |      * 0x01 - 'BW40_SEC_BELOW'                                                                     |
2181            |                             |      * 0x02 - 'BW40_SEC_ABOVE'                                                                     |
2182            |                             |                                                                                                    |
2183            +-----------------------------+----------------------------------------------------------------------------------------------------+
2184            | ssid                        |  SSID (32 chars max)                                                                               |
2185            +-----------------------------+----------------------------------------------------------------------------------------------------+
2186            | ht_capable                  |  1 - Network is capable of HT PHY mode                                                             |
2187            |                             |  0 - Netowrk is not capable of NHT PHY mode                                                        |
2188            +-----------------------------+----------------------------------------------------------------------------------------------------+
2189            | beacon_interval             |  Beacon interval - In time units of 1024 us'                                                       |
2190            +-----------------------------+----------------------------------------------------------------------------------------------------+           
2191            | dtim_period                 |                                                                                                    |
2192            +-----------------------------+----------------------------------------------------------------------------------------------------+
2193            | flags                       |  Value contains 1 bit fields:                                                                      |
2194            |                             |                                                                                                    |
2195            |                             |      * 0x0001 - 'KEEP'                                                                             |
2196            +-----------------------------+----------------------------------------------------------------------------------------------------+
2197            | capabilities                |  Supported capabilities of the BSS.  Value contains 1 bit fields:                                  |
2198            |                             |                                                                                                    |
2199            |                             |      * 0x0001 - 'ESS'                                                                              |
2200            |                             |      * 0x0002 - 'IBSS'                                                                             |
2201            |                             |      * 0x0010 - 'PRIVACY'                                                                          |
2202            |                             |                                                                                                    |
2203            +-----------------------------+----------------------------------------------------------------------------------------------------+
2204            | latest_beacon_rx_time       |  Value of System Time in microseconds of last beacon Rx                                            |
2205            +-----------------------------+----------------------------------------------------------------------------------------------------+
2206            | latest_beacon_rx_power      |  Last observed beacon Rx Power (dBm)                                                               |
2207            +-----------------------------+----------------------------------------------------------------------------------------------------+           
2208
2209           
2210        Returns:
2211            network_info (NetworkInfo): 
2212                Information about network that the node is a member of (can be None)
2213        """
2214        ret_val = self.send_cmd(cmds.NodeGetNetworkInfo())
2215       
2216        if (len(ret_val) == 1):
2217            ret_val = ret_val[0]
2218        else:
2219            ret_val = None
2220
2221        return ret_val
2222
2223
2224    def get_network_list(self):
2225        """Get a list of known networks (NetworkInfo()s) on the node
2226
2227        Returns:
2228            networks (list of NetworkInfo): 
2229                List of NetworkInfo() that are known to the node
2230        """
2231        return self.send_cmd(cmds.NodeGetNetworkInfo("ALL"))
2232
2233
2234
2235    #--------------------------------------------
2236    # Queue Commands
2237    #--------------------------------------------
2238    def queue_tx_data_purge_all(self):
2239        print('WARNING: queue_tx_data_purge_all() has been renamed purge_tx_queues(). Please update your script.')
2240        self.purge_tx_queues()
2241
2242    def purge_tx_queues(self):
2243        """Purges all wireless transmit queues on the node.
2244       
2245        This will discard all currently enqueued packets awaiting transmission
2246        at the time the command is received.  This will not discard packets
2247        already submitted to the lower-level MAC for transmission.  Also, this
2248        will not stop additional packets from sources such as LTGs from being
2249        enqueued.
2250       
2251        This command is equivalent to ``reset(queue_data=True)``.
2252        """
2253        self.send_cmd(cmds.PurgeAllTxQueues())
2254
2255    #--------------------------------------------
2256    # Node User Commands
2257    #--------------------------------------------
2258    def send_user_command(self, cmd_id, args=None):
2259        """Send User defined command to the node
2260
2261        See documentation on how-to extend wlan_exp:
2262        http://warpproject.org/trac/wiki/802.11/wlan_exp/Extending
2263
2264        Args:
2265            cmd_id (u32):  User-defined Command ID
2266            args (u32, list of u32):  Scalar or list of u32 command arguments
2267                to send to the node
2268
2269        Returns:
2270            resp_args (list of u32): 
2271                List of u32 response arguments received from the node
2272        """
2273        if cmd_id is None:
2274            raise AttributeError("Command ID must be defined for a user command")
2275
2276        ret_val = self.send_cmd(cmds.UserSendCmd(cmd_id=cmd_id, args=args))
2277
2278        if ret_val is not None:
2279            return ret_val
2280        else:
2281            return []
2282
2283
2284
2285    #--------------------------------------------
2286    # Memory Access Commands - For developer use only
2287    #--------------------------------------------
2288    def _mem_write_high(self, address, values):
2289        """Writes 'values' to CPU High memory starting at 'address'
2290
2291        Args:
2292            address (int):         Address must be in [0 .. (2^32 - 1)]
2293            values (list of int):  Each value must be in [0 .. (2^32 - 1)]
2294        """
2295        # Code below assumes values is iterable - if caller supplies scalar, wrap it in a list
2296        if '__iter__' not in dir(values):
2297            values = [values]
2298
2299        if (self._check_mem_access_args(address, values)):
2300            return self.send_cmd(cmds.NodeMemAccess(cmd=cmds.CMD_PARAM_WRITE, high=True, address=address, length=len(values), values=values))
2301
2302
2303    def _mem_read_high(self, address, length):
2304        """Reads 'length' values from CPU High memory starting at 'address'
2305
2306        Args:
2307            address (int):  Address must be in [0 .. (2^32 - 1)]
2308            length (int):   Length must be in [1 .. 320] (ie fit in a 1400 byte packet)
2309
2310        Returns:
2311            values (list of u32):  List of u32 values received from the node
2312        """
2313        if (self._check_mem_access_args(address, values=None)):
2314            return self.send_cmd(cmds.NodeMemAccess(cmd=cmds.CMD_PARAM_READ, high=True, address=address, length=length))
2315
2316
2317    def _mem_write_low(self, address, values):
2318        """Writes 'values' to CPU Low memory starting at 'address'
2319
2320        Args:
2321            address (int):         Address must be in [0 .. (2^32 - 1)]
2322            values (list of int):  Each value must be in [0 .. (2^32 - 1)]
2323        """
2324        # Code below assumes values is iterable - if caller supplies scalar, wrap it in a list
2325        if '__iter__' not in dir(values):
2326            values = [values]
2327
2328        if (self._check_mem_access_args(address, values)):
2329            return self.send_cmd(cmds.NodeMemAccess(cmd=cmds.CMD_PARAM_WRITE, high=False, address=address, length=len(values), values=values))
2330
2331
2332    def _mem_read_low(self, address, length):
2333        """Reads 'length' values from CPU Low memory starting at 'address'
2334
2335        Args:
2336            address (int):  Address must be in [0 .. (2^32 - 1)]
2337            length (int):   Length must be in [1 .. 320] (ie fit in a 1400 byte packet)
2338
2339        Returns:
2340            values (list of u32):  List of u32 values received from the node
2341        """
2342        if (self._check_mem_access_args(address, values=None)):
2343            return self.send_cmd(cmds.NodeMemAccess(cmd=cmds.CMD_PARAM_READ, high=False, address=address, length=length))
2344
2345
2346    def _check_mem_access_args(self, address, values=None, length=None):
2347        """Check memory access variables
2348
2349        Args:
2350            address (int):         Address must be in [0 .. (2^32 - 1)]
2351            values (list of int):  Each value must be in [0 .. (2^32 - 1)]
2352            length (int):          Length must be in [1 .. 320] (ie fit in a 1400 byte packet)
2353
2354        Returns:
2355            valid (bool):  Are all arguments valid?
2356        """
2357        if ((int(address) != address) or (address >= 2**32) or (address < 0)):
2358            raise Exception('ERROR: address must be integer value in [0 .. (2^32 - 1)]!')
2359
2360        # Caller must pass iterable for values
2361        if (values is not None):
2362            error = False
2363
2364            for v in values:
2365                if (int(v) >= 2**32) or (int(v) < 0):
2366                    error = True
2367
2368            if (error):
2369                raise Exception('ERROR: values must be scalar or iterable of ints in [0 .. (2^32 - 1)]! {0}'.format(values))
2370
2371        if length is not None:
2372            if ((int(length) != length) or (length > 50) or (length <= 0)):
2373                raise Exception('ERROR: length must be an integer [1 .. 50] words (ie, 4 to 200 bytes)!')
2374
2375        return True
2376
2377
2378    def _eeprom_write(self, address, values):
2379        """Writes 'values' to EEPROM starting at 'address'
2380
2381        Args:
2382            address (int):         Address must be in [0 .. 15999]
2383            values (list of int):  Each value must be in [0 .. 255]
2384        """
2385        # Convert scalar values to a list for processing
2386        if (type(values) is not list):
2387            values = [values]
2388
2389        if (self._check_eeprom_access_args(address=address, values=values, length=len(values))):
2390            if(address >= 16000):
2391                raise Exception('ERROR: EEPROM addresses [16000 .. 16383] are read only!')
2392            else:
2393                return self.send_cmd(cmds.NodeEEPROMAccess(cmd=cmds.CMD_PARAM_WRITE, address=address, length=len(values), values=values))
2394
2395
2396    def _eeprom_read(self, address, length):
2397        """Reads 'length' values from EEPROM starting at 'address'
2398
2399        Args:
2400            address (int):  Address must be in [0 .. 16383]
2401            length (int):   Length must be in [1 .. 320] (ie fit in a 1400 byte packet)
2402
2403        Returns:
2404            values (list of u8):  List of u8 values received from the node
2405        """
2406        if (self._check_eeprom_access_args(address=address, length=length)):
2407            return self.send_cmd(cmds.NodeEEPROMAccess(cmd=cmds.CMD_PARAM_READ, address=address, length=length))
2408
2409
2410    def _check_eeprom_access_args(self, address, values=None, length=None):
2411        """Check EEPROM access variables
2412
2413        Args:
2414            address (int):         Address must be in [0 .. 16383]
2415            values (list of int):  Each value must be in [0 .. 255]
2416            length (int):          Length must be in [1 .. 320] (ie fit in a 1400 byte packet)
2417
2418        Returns:
2419            valid (bool):  Are all arguments valid?
2420        """
2421        if ((int(address) != address) or (address >= 16384) or (address < 0)):
2422            raise Exception('ERROR: address must be integer value in [0 .. 16383]!')
2423
2424        if (values is not None):
2425            if (type(values) is not list):
2426                values = [values]
2427
2428            error = False
2429
2430            for value in values:
2431                if (((type(value) is not int) and (type(value) is not long)) or
2432                    (value >= 2**8) or (value < 0)):
2433                    error = True
2434
2435            if (error):
2436                raise Exception('ERROR: values must be scalar or iterable of ints in [0 .. 255]!')
2437
2438        if length is not None:
2439            if ((int(length) != length) or (length > 320) or (length <= 0)):
2440                raise Exception('ERROR: length must be an integer [1 .. 320] words (ie, 4 to 1400 bytes)!')
2441
2442        return True
2443
2444    def check_wlan_exp_ver(self):
2445        """Check the wlan_exp version of the node against the current wlan_exp
2446        version.
2447        """
2448        ver_str     = version.wlan_exp_ver_str(self.wlan_exp_ver_major,
2449                                               self.wlan_exp_ver_minor,
2450                                               self.wlan_exp_ver_revision)
2451
2452        caller_desc = "During initialization '{0}' returned version {1}".format(self.sn_str, ver_str)
2453
2454        status = version.wlan_exp_ver_check(major=self.wlan_exp_ver_major,
2455                                            minor=self.wlan_exp_ver_minor,
2456                                            revision=self.wlan_exp_ver_revision,
2457                                            caller_desc=caller_desc)
2458
2459        if (status == version.WLAN_EXP_VERSION_NEWER):
2460            print("Please update the C code on the node to the proper wlan_exp version.")
2461
2462        if (status == version.WLAN_EXP_VERSION_OLDER):
2463            print("Please update the wlan_exp installation to match the version on the node.")
2464
2465
2466# End Class WlanExpNode
2467
2468
2469class WlanExpNodeFactory(node.WlanExpTransportNodeFactory):
2470    """Factory class to create WlanExpNode objects. This factory defines the
2471    Python node classes and application IDs used to map each hardware node
2472    to a Python class during init.
2473    """
2474
2475    def __init__(self, network_config=None):
2476        super(WlanExpNodeFactory, self).__init__(network_config)
2477
2478        # Define the default node types
2479
2480        # Add default classes to the factory
2481        import wlan_exp.node_ap
2482        import wlan_exp.node_sta
2483        import wlan_exp.node_ibss
2484
2485        self.add_node_type_class({'high_sw_id': defaults.WLAN_EXP_HIGH_SW_ID_AP, 'node_class': wlan_exp.node_ap.WlanExpNodeAp})
2486        self.add_node_type_class({'high_sw_id': defaults.WLAN_EXP_HIGH_SW_ID_STA, 'node_class': wlan_exp.node_sta.WlanExpNodeSta})
2487        self.add_node_type_class({'high_sw_id': defaults.WLAN_EXP_HIGH_SW_ID_IBSS, 'node_class': wlan_exp.node_ibss.WlanExpNodeIBSS})
2488
2489# End Class WlanExpNodeFactory
Note: See TracBrowser for help on using the repository browser.