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

Last change on this file was 6320, checked in by chunter, 5 years ago

1.8.0 release wlan-exp

File size: 44.5 KB
Line 
1# -*- coding: utf-8 -*-
2"""
3------------------------------------------------------------------------------
4Mango 802.11 Reference Design Experiments Framework - Utilities
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
12import time
13
14import wlan_exp.defaults as defaults
15
16# Fix to support Python 2.x and 3.x
17if sys.version[0]=="3": long=None
18
19__all__ = ['consts_dict', 'init_nodes', 'broadcast_cmd_set_mac_time', 'broadcast_cmd_write_time_to_logs',
20           'filter_nodes']
21
22
23
24# -----------------------------------------------------------------------------
25# Constants Dictionary Class
26# -----------------------------------------------------------------------------
27
28class consts_dict(dict):
29    """Contants Dictionary
30
31    Sub-class of dictionary, with fields accessible as immutable properties.
32    """
33    def copy(self):
34        return consts_dict(self)
35
36    # Allow attribute (ie ".") notation to access contents of dictionary
37    def __getattr__(self, name):
38        if name in self:
39            return self[name]
40        else:
41            raise AttributeError("No such attribute: " + name)
42
43    # Do not allow existing attributes or items to be modified or deleted
44    def __setattr__(self, name, value):
45        if name in self:
46            raise AttributeError("Cannot change existing entries in {0}".format(self.__class__.__name__))
47        else:
48            super(consts_dict, self).__setitem__(name, value)
49
50    def __delattr__(self, name):
51        pass
52
53    def __setitem__(self, key, value):
54        if key in self:
55            raise AttributeError("Cannot change existing entries in {0}".format(self.__class__.__name__))
56        else:
57            super(consts_dict, self).__setitem__(key, value)
58
59    def __delitem__(self, key):
60        pass
61
62# End class
63
64
65
66# -----------------------------------------------------------------------------
67# UART Print Levels
68# -----------------------------------------------------------------------------
69
70#: wlan_exp UART Print Levels:
71#:
72#:  * ``NONE``    - Do not print messages
73#:  * ``ERROR``   - Only print error messages
74#:  * ``WARNING`` - Print error and warning messages
75#:  * ``INFO``    - Print error, warning and info messages
76#:  * ``DEBUG``   - Print error, warning, info and debug messages
77#:   
78#: Use this dictionary for the ``set_print_level()`` command
79# The C counterparts are found in wlan_exp_common.h
80uart_print_levels = consts_dict({
81       'NONE'      :  0,
82       'ERROR'     :  1,
83       'WARNING'   :  2,
84       'INFO'      :  3,
85       'DEBUG'     :  4})
86
87
88
89# -----------------------------------------------------------------------------
90# Rate definitions
91# -----------------------------------------------------------------------------
92
93#: PHY Modes
94#:
95#:   * 'DSSS'  - DSSS (Rx only)
96#:   * 'NONHT' - NONHT OFDM (11a/g)
97#:   * 'HTMF'  - HTMF (11n)
98#:
99#: Use this dictionary to interpret ``phy_mode`` values encoded in Tx/Rx log entries
100phy_modes = consts_dict({
101       'DSSS'      :  0,
102       'NONHT'     :  1,
103       'HTMF'      :  2})
104
105
106def get_rate_info(mcs, phy_mode, phy_samp_rate=20, short_GI=False):
107    """Generate dictionary with details about a PHY rate. The returned dictionary
108    has fields:
109   
110      * ``mcs``: the MCS index passed in the ``mcs`` argument, integer in 0 to 7
111      * ``phy_mode``: the PHY mode passed in the ``phy_mode`` argument, either ``'NONHT'`` or ``'HTMF'``
112      * ``desc``: string describing the rate
113      * ``NDBPS``: integer number of data bits per OFDM symbol for the rate
114      * ``phy_rate``: float data rate in Mbps
115
116    Args:
117        mcs (int):           Modulation and coding scheme (MCS) index
118        phy_mode (str, int): PHY mode ('NONHT', 'HTMF')
119        phy_samp_rate (int): PHY sampling rate (10, 20, 40)
120        short_GI (bool):     Short Guard Interval (GI) (True/False)
121
122    Returns:
123        rate_info (dict):  Rate info dictionary
124    """
125    ret_val = dict()
126
127    # 802.11 a/g rates - IEEE 802.11-2012 Table 18-4
128    #     Clause 18 doesn't use the term "MCS", but it's a convenient
129    #     way to refer to these rates.
130    mod_orders_nonht        = ['BPSK', 'BPSK', 'QPSK', 'QPSK', '16-QAM', '16-QAM', '64-QAM', '64-QAM']
131    code_rates_nonht        = [ '1/2',  '3/4',  '1/2',  '3/4',    '1/2',    '3/4',    '2/3',    '3/4']
132    ndbps_nonht             = [    24,     36,     48,     72,       96,      144,      192,      216]
133    phy_rates_nonht         = [   6.0,    9.0,   12.0,   18.0,     24.0,     36.0,     48.0,     54.0]
134
135    # 802.11n rates - IEEE 802.11-2012 Tables 20-30 to 30-37
136    mod_orders_htmf         = ['BPSK', 'QPSK', 'QPSK', '16-QAM', '16-QAM', '64-QAM', '64-QAM', '64-QAM']
137    code_rates_htmf         = [ '1/2',  '1/2',  '3/4',    '1/2',    '3/4',    '2/3',    '3/4',    '5/6']
138    ndbps_htmf_bw20         = [    26,     52,     78,      104,      156,      208,      234,      260]
139    phy_rates_htmf_bw20_lgi = [   6.5,   13.0,   19.5,     26.0,     39.0,     52.0,     58.5,     65.0]
140    phy_rates_htmf_bw20_sgi = [   7.2,   14.4,   21.7,     28.9,     43.3,     57.8,     65.0,     72.2]
141    # ndbps_htmf_bw40         = [    54,    108,    162,      216,      324,      432,      486,      540]
142    # phy_rates_htmf_bw40_lgi = [  13.5,   27.0,   40.5,     54.0,     81.0,    108.0,    121.5,    135.0]
143    # phy_rates_htmf_bw40_sgi = [  15.0,   30.0,   45.0,     60.0,     90.0,    120.0,    135.0,    150.0]
144
145
146    # Check input arguments
147    if ((mcs < 0) or (mcs > 7)):
148        raise AttributeError("MCS must be in [0 .. 7]")
149
150    if (phy_mode not in ['NONHT', 'HTMF', phy_modes['NONHT'], phy_modes['HTMF']]):
151        raise AttributeError("PHY mode must be in ['NONHT', 'HTMF', phy_modes['NONHT'], phy_modes['HTMF']]")
152   
153    if (phy_samp_rate not in [10, 20, 40]):
154        raise AttributeError("PHY sample rate must be in [10, 20, 40]")
155       
156    # Set common values
157    ret_val['mcs'] = mcs
158
159    # Set 'NONHT' values
160    if ((phy_mode == 'NONHT') or (phy_mode == phy_modes['NONHT'])):
161        ret_val['phy_mode'] = 'NONHT'
162        ret_val['desc']     = 'NONHT {0} {1}'.format(mod_orders_nonht[mcs], code_rates_nonht[mcs])
163        ret_val['NDBPS']    = ndbps_nonht[mcs]
164        ret_val['phy_rate'] = phy_rates_nonht[mcs]
165
166    # Set 'HTMF' values
167    elif ((phy_mode == 'HTMF') or (phy_mode == phy_modes['HTMF'])):
168        ret_val['phy_mode'] = 'HTMF'
169        ret_val['desc']     = 'HTMF {0} {1}'.format(mod_orders_htmf[mcs], code_rates_htmf[mcs])
170        ret_val['NDBPS']    = ndbps_htmf_bw20[mcs]
171       
172        if (short_GI):
173            ret_val['phy_rate'] = phy_rates_htmf_bw20_sgi[mcs]
174        else:
175            ret_val['phy_rate'] = phy_rates_htmf_bw20_lgi[mcs]
176
177    # Update PHY rate for other PHY sampling rates
178    ret_val['phy_rate'] = ret_val['phy_rate'] * (phy_samp_rate / 20)
179
180    return ret_val
181
182# End def
183
184
185def rate_info_to_str(rate_info):
186    """Convert dictionary returned by ``get_rate_info()`` into a printable string.
187
188    Args:
189        rate_info (dict):  Dictionary returned by ``get_rate_info()``
190
191    Returns:
192        output (str):  String representation of the rate
193
194    Example:
195        >>> import wlan_exp.util as util
196        >>> r = util.get_rate_info(mcs=3, phy_mode='HTMF')
197        >>> print(util.rate_info_to_str(r))
198        26.0 Mbps (HTMF 16-QAM 1/2)
199   
200    """
201    msg = ""
202    if type(rate_info) is dict:
203        msg += "{0:>4.1f} Mbps ({1})".format(rate_info['phy_rate'], rate_info['desc'])
204    else:
205        print("Invalid rate info type.  Needed dict, provided {0}.".format(type(rate_info)))
206    return msg
207
208# End def
209
210
211
212# -----------------------------------------------------------------------------
213# Channel definitions
214# -----------------------------------------------------------------------------
215
216#: List of supported channels. Each value represents a 20MHz channel in the 2.4GHz
217#: or 5GHz bands. Use the ``get_channel_info`` method to lookup the actual center
218#: frequency for a given channel index.
219wlan_channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 36, 40, 44, 48]
220
221
222def get_channel_info(channel):
223    """Get channel info dictionary based on channel number
224
225    Args:
226        channel (int):  Number of 802.11 channel
227            (https://en.wikipedia.org/wiki/List_of_WLAN_channels)
228
229    Returns:
230        channel_info (dict):  Channel info dictionary
231
232    The returned dictionary has fields:
233
234     * ``channel``: Integer channel index
235     * ``freq``: Integer channel center frequency, in MHz
236
237    Examples:
238        >>> import wlan_exp.util as util
239        >>> util.get_channel_info(5)
240        {'freq': 2432, 'channel': 5}
241   
242    """
243   
244    channel_info = {
245         1 : {'channel' :   1, 'freq': 2412},
246         2 : {'channel' :   2, 'freq': 2417},
247         3 : {'channel' :   3, 'freq': 2422},
248         4 : {'channel' :   4, 'freq': 2427},
249         5 : {'channel' :   5, 'freq': 2432},
250         6 : {'channel' :   6, 'freq': 2437},
251         7 : {'channel' :   7, 'freq': 2442},
252         8 : {'channel' :   8, 'freq': 2447},
253         9 : {'channel' :   9, 'freq': 2452},
254        10 : {'channel' :  10, 'freq': 2457},
255        11 : {'channel' :  11, 'freq': 2462},
256        36 : {'channel' :   36, 'freq': 5180},
257        38 : {'channel' :   38, 'freq': 5190},
258        40 : {'channel' :   40, 'freq': 5200},
259        44 : {'channel' :   44, 'freq': 5220},
260        46 : {'channel' :   46, 'freq': 5230},
261        48 : {'channel' :   48, 'freq': 5240},
262        52 : {'channel' :   52, 'freq': 5260},
263        54 : {'channel' :   54, 'freq': 5270},
264        56 : {'channel' :   56, 'freq': 5280},
265        60 : {'channel' :   60, 'freq': 5300},
266        62 : {'channel' :   62, 'freq': 5310},
267        64 : {'channel' :   64, 'freq': 5320},
268        100 : {'channel' :   100, 'freq': 5500},
269        102 : {'channel' :   102, 'freq': 5510},
270        104 : {'channel' :   104, 'freq': 5520},
271        108 : {'channel' :   108, 'freq': 5540},
272        110 : {'channel' :   110, 'freq': 5550},
273        112 : {'channel' :   112, 'freq': 5560},
274        116 : {'channel' :   116, 'freq': 5580},
275        118 : {'channel' :   118, 'freq': 5590},
276        120 : {'channel' :   120, 'freq': 5600},
277        124 : {'channel' :   124, 'freq': 5620},
278        126 : {'channel' :   126, 'freq': 5630},
279        128 : {'channel' :   128, 'freq': 5640},
280        132 : {'channel' :   132, 'freq': 5660},
281        134 : {'channel' :   134, 'freq': 5670},
282        136 : {'channel' :   136, 'freq': 5680},
283        140 : {'channel' :   140, 'freq': 5700},
284        142 : {'channel' :   142, 'freq': 5710},
285        144 : {'channel' :   144, 'freq': 5720},
286        149 : {'channel' :   149, 'freq': 5745},
287        151 : {'channel' :   151, 'freq': 5755},
288        153 : {'channel' :   153, 'freq': 5765},
289        157 : {'channel' :   157, 'freq': 5785},
290        159 : {'channel' :   159, 'freq': 5795},
291        161 : {'channel' :   161, 'freq': 5805},
292        165 : {'channel' :   165, 'freq': 5825},   
293        172 : {'channel' :   172, 'freq': 5860},
294        173 : {'channel' :   173, 'freq': 5865},
295        174 : {'channel' :   174, 'freq': 5870},
296        175 : {'channel' :   175, 'freq': 5875},
297        176 : {'channel' :   176, 'freq': 5880},
298        177 : {'channel' :   177, 'freq': 5885},
299        178 : {'channel' :   178, 'freq': 5890}}
300
301    # Check input arguments
302    if (channel not in wlan_channels):
303        raise AttributeError("Channel must be list of supported channels.")
304
305    return channel_info[channel]
306
307# End def
308
309
310def channel_info_to_str(channel_info):
311    """Convert a channel info dictionary to a string.
312
313    Args:
314        channel_info (dict):  Dictionary returned by ``get_channel_info()``
315
316    Returns:
317        output (str):  String representation of the 'channel'
318    """
319    msg = ""
320    if type(channel_info) is dict:
321        msg += "{0:4d} ({1} MHz)".format(channel_info['channel'], channel_info['freq'])
322    else:
323        print("Invalid channel info type.  Needed dict, provided {0}.".format(type(channel_info)))
324    return msg
325
326# End def
327
328
329
330# -----------------------------------------------------------------------------
331# Antenna Mode definitions
332# -----------------------------------------------------------------------------
333
334#: Dictionary of supported receive interfaces.
335wlan_rx_ant_modes = consts_dict({
336       'RF_A'           : 0x0,
337       'RF_B'           : 0x1,
338       'RF_C'           : 0x2,
339       'RF_D'           : 0x3,
340       'RF_SELDIV_AB'   : 0x4})
341       
342#: Dictionary of supported transmit interfaces.
343wlan_tx_ant_modes = consts_dict({
344       'RF_A'           : 0x0,
345       'RF_B'           : 0x1})
346
347
348
349# -----------------------------------------------------------------------------
350# MAC Address definitions
351# -----------------------------------------------------------------------------
352
353# MAC Description Map
354#   List of tuples:  (MAC value, mask, description) describe various MAC addresses
355#
356# IP -> MAC multicast references:
357#     http://technet.microsoft.com/en-us/library/cc957928.aspx
358#     http://en.wikipedia.org/wiki/Multicast_address#Ethernet
359#     http://www.cavebear.com/archive/cavebear/Ethernet/multicast.html
360
361mac_addr_desc_map = [(0xFFFFFFFFFFFF, 0xFFFFFFFFFFFF, 'Broadcast'),
362                     (0x01005E000000, 0xFFFFFF800000, 'IP v4 Multicast'),
363                     (0x333300000000, 0xFFFF00000000, 'IP v6 Multicast'),
364                     (0xFEFFFF000000, 0xFFFFFF000000, 'Anonymized Device'),
365                     (0xFFFFFFFF0000, 0xFFFFFFFF0000, 'Anonymized Device'),
366                     (0x40D855042000, 0xFFFFFFFFF000, 'Mango MAC addresses')]
367
368
369# MAC bit definitions
370#   - Reference: http://standards.ieee.org/develop/regauth/tut/macgrp.pdf
371mac_addr_mcast_mask = 0x010000000000
372mac_addr_local_mask = 0x020000000000
373mac_addr_broadcast  = 0xFFFFFFFFFFFF
374
375
376
377# -----------------------------------------------------------------------------
378# Node Utilities
379# -----------------------------------------------------------------------------
380
381def init_nodes(nodes_config, network_config=None, node_factory=None,
382               network_reset=True, output=False):
383    """Initalize wlan_exp nodes.
384
385    The init_nodes function serves two purposes:  1) To initialize the node for
386    participation in the experiment and 2) To retrieve all necessary information
387    from the node to provide a valid python WlanExpNode object to be used in
388    the experiment script.
389
390    When a node is first configured from a bitstream, its network interface is
391    set to a default value such that it is part of the defalt subnet 10.0.0 but does not
392    have a valid IP address for communication with the host.  As part of the init_nodes
393    process, if network_reset is True, the host will reset the network configuration
394    on the node and configure the node with a valid IP address.  If the network settings
395    of a node have already been configured and are known to the python experiment script
396    a priori, then it is not necessary to issue a network reset to reset the network
397    settings on the node.  This can be extremely useful when trying to interact with a
398    node via multiple python experiment scripts at the same time.
399
400    Args:
401        nodes_config (NodesConfiguration):  A NodesConfiguration describing the nodes
402            in the network.
403        network_config (NetworkConfiguration, optional): A NetworkConfiguration object
404            describing the network configuration
405        node_factory (WlanExpNodeFactory, optional):  A WlanExpNodeFactory or subclass
406            to create nodes of a given node type
407        network_reset (bool, optional):  Issue a network reset command to the nodes to
408            initialize / re-initialize their network interface.
409        output (bool, optional):         Print output about the nodes
410
411    Returns:
412        nodes (list of WlanExpNode):
413            Initialized list of WlanExpNode / sub-classes of WlanExpNode depending on the
414            hardware configuration of the nodes.
415    """
416
417    # Create a Host Configuration if there is none provided
418    if network_config is None:
419        import wlan_exp.transport.config as config
420        network_config = config.NetworkConfiguration()
421
422    # If node_factory is not defined, create a default WlanExpNodeFactory
423    if node_factory is None:
424        import wlan_exp.node as node
425        node_factory = node.WlanExpNodeFactory(network_config)
426
427    # Use the utility, init_nodes, to initialize the nodes
428    import wlan_exp.transport.util as util
429    return util.init_nodes(nodes_config, network_config, node_factory, network_reset, output)
430
431# End def
432
433
434def broadcast_cmd_set_mac_time(time, network_config, time_id=None):
435    """Initialize the MAC time on all of the wlan_exp nodes.
436
437    This method will iterate through all network configurations and issue a broadcast
438    packet on each network that will set the MAC time on the node to 'time'.  The
439    method keeps track of how long it takes to send each packet so that the time on all
440    nodes is as close as possible even across networks.
441
442    Args:
443        network_config (NetworkConfiguration): One or more NetworkConfiguration objects
444           that define the networks on which the set_time command will be broadcast
445        time (int):              Time to which the node's MAC timestamp will be set (int microseconds)
446        time_id (int, optional): Identifier used as part of the TIME_INFO log entry created by this command.
447            If not specified, then a random number will be used.
448    """
449    import wlan_exp.cmds as cmds
450   
451    if type(time) not in [int, long]:
452        raise AttributeError("Time must be expressed in int microseconds")
453   
454    _broadcast_time_to_nodes(time_cmd=cmds.CMD_PARAM_WRITE, network_config=network_config, time=time, time_id=time_id)
455
456# End def
457
458
459def broadcast_cmd_write_time_to_logs(network_config, time_id=None):
460    """Add the current host time to the log on each node.
461
462    This method will iterate through all network configurations and issue a broadcast
463    packet on each network that will add the current time to the log. The method
464    keeps track of how long it takes to send each packet so that the time on all
465    nodes is as close as possible even across networks.
466
467    Args:
468        network_config (NetworkConfiguration): One or more NetworkConfiguration objects
469           that define the networks on which the log_write_time command will be broadcast
470        time_id (int, optional): Identifier used as part of the TIME_INFO log entry created by this command.
471            If not specified, then a random number will be used.
472    """
473    import wlan_exp.cmds as cmds
474    _broadcast_time_to_nodes(time_cmd=cmds.CMD_PARAM_TIME_ADD_TO_LOG, network_config=network_config, time_id=time_id)
475
476# End def
477
478
479def broadcast_cmd_write_exp_info_to_logs(network_config, info_type, message=None):
480    """Add the EXP INFO log entry to the log on each node.
481
482    This method will iterate through all network configurations and issue a broadcast
483    packet on each network that will add the EXP_INFO log entry to the log
484
485    Args:
486        network_config (NetworkConfiguration): One or more NetworkConfiguration objects
487           that define the networks on which the log_write_exp_info command will be broadcast
488        info_type (int): Type of the experiment info.  This is an arbitrary 16 bit number
489            chosen by the experimentor
490        message (int, str, bytes, optional): Information to be placed in the event log.
491    """
492    import wlan_exp.cmds as cmds
493
494    if type(network_config) is list:
495        configs = network_config
496    else:
497        configs = [network_config]
498
499    for config in configs:
500        _broadcast_cmd_to_nodes_helper(cmds.LogAddExpInfoEntry(info_type, message), config)
501
502# End def
503
504
505def filter_nodes(nodes, mac_high=None, mac_low=None, serial_number=None, warn=True):
506    """Return a list of nodes that match all the values for the given filter parameters.
507
508    Each of these filter parameters can be a single value or a list of values.
509   
510    Args:
511        nodes (list of WlanExpNode):  List of WlanExpNode / sub-classes of WlanExpNode
512        mac_high (str, int, optional):  Filter for CPU High functionality.  This value must be either
513            an integer corresponding to a node type (see wlan_exp/defaults.py for node types)
514            or the following strings:
515           
516                * **'AP'**   (equivalent to WLAN_EXP_HIGH_AP);
517                * **'STA'**  (equivalent to WLAN_EXP_HIGH_STA);
518                * **'IBSS'** (equivalent to WLAN_EXP_HIGH_IBSS).
519               
520            A value of None means that no filtering will occur for CPU High Functionality
521        mac_low (str, int, optional): Filter for CPU Low functionality.  This value must be either
522            an integer corresponding to a node type (see wlan_exp/defaults.py for node types)
523            or the following strings:
524           
525                * **'DCF'**   (equivalent to WLAN_EXP_LOW_DCF);
526                * **'NOMAC'** (equivalent to WLAN_EXP_LOW_NOMAC).
527               
528            A value of None means that no filtering will occur for CPU Low Functionality
529        serial_number (str, optional):  Filters nodes by serial number.
530        warn (bool, optional):          Print warnings (default value is True)
531
532    Returns:
533        nodes (list of WlanExpNode):  Filtered list of WlanExpNode / sub-classes of WlanExpNode
534
535    If the return list of nodes is empty, then this method will issue a warning
536    if the parameter warn is True.
537
538    Examples:
539        >>> filter_nodes(nodes, mac_high='AP', mac_low='DCF')
540        >>> filter_nodes(nodes, mac_high='AP')
541        >>> filter_nodes(nodes, mac_high='AP', mac_low='DCF', serial_numbers=['w3-a-00001','w3-a-00002'])
542   
543    """
544    ret_nodes         = []
545    tmp_mac_high      = None
546    tmp_mac_low       = None
547    tmp_serial_number = None
548
549    # Create MAC High Filter
550    if mac_high is not None:
551        if type(mac_high) is not list:
552            mac_high = [mac_high]
553
554        tmp_mac_high = []
555
556        for value in mac_high:
557            if type(value) is str:
558                if (value.lower() == 'ap'):
559                    tmp_mac_high.append(defaults.WLAN_EXP_HIGH_SW_ID_AP)
560                elif (value.lower() == 'sta'):
561                    tmp_mac_high.append(defaults.WLAN_EXP_HIGH_SW_ID_STA)
562                elif (value.lower() == 'ibss'):
563                    tmp_mac_high.append(defaults.WLAN_EXP_HIGH_SW_ID_IBSS)
564                else:
565                    msg  = "Unknown mac_high filter value: {0}\n".format(value)
566                    msg += "    Must be either 'AP', 'STA', or 'IBSS'"
567                    print(msg)
568
569            if type(value) is int:
570                tmp_mac_high.append(value)
571
572    # Create MAC Low Filter
573    if mac_low is not None:
574        if type(mac_low) is not list:
575            mac_low = [mac_low]
576
577        tmp_mac_low = []
578
579        for value in mac_low:
580            if type(value) is str:
581                if (value.lower() == 'dcf'):
582                    tmp_mac_low.append(defaults.WLAN_EXP_LOW_SW_ID_DCF)
583                elif (value.lower() == 'nomac'):
584                    tmp_mac_low.append(defaults.WLAN_EXP_LOW_SW_ID_NOMAC)
585                else:
586                    msg  = "Unknown mac_low filter value: {0}\n".format(value)
587                    msg += "    Must be either 'DCF' or 'NOMAC'"
588                    print(msg)
589
590            if type(value) is int:
591                tmp_mac_low.append(value)
592
593    # Create Serial Number Filter
594    if serial_number is not None:
595        import wlan_exp.transport.util as util
596
597        if type(serial_number) is not list:
598            serial_number = [serial_number]
599
600        tmp_serial_number = []
601
602        for value in serial_number:
603            try:
604                (sn, _) = util.get_serial_number(value)
605                tmp_serial_number.append(sn)
606            except TypeError as err:
607                print(err)
608
609    ret_nodes = _get_nodes_by_type(nodes, tmp_mac_high, tmp_mac_low)
610    ret_nodes = _get_nodes_by_sn(ret_nodes, tmp_serial_number)
611
612    if ((len(ret_nodes) == 0) and warn):
613        import warnings
614        msg  = "\nNo nodes match filter: \n"
615        msg += "    mac_high = {0}\n".format(mac_high)
616        msg += "    mac_high = {0}\n".format(mac_high)
617        warnings.warn(msg)
618
619    return ret_nodes
620
621# End def
622
623
624def check_bss_membership(nodes, verbose=False):
625    """Check that each of the nodes in the input list are members of the same
626    BSS.  For a BSS to match, the 'bssid', 'ssid' and 'channel' must match.
627   
628    There are two acceptable patterns for the nodes argument
629   
630        #. 1 AP and 1+ STA and 0  IBSS ("infrastructure" network)
631        #. 0 AP and 0  STA and 2+ IBSS ("ad hoc" network)
632   
633    In the case that nodes is 1 AP and 1+ STA and 0 IBSS, then the following
634    conditions must be met in order to return True
635   
636        #. AP BSS must be non-null
637        #. AP must have each STA in its station_info list
638        #. Each STA BSS must match AP BSS
639        #. Each STA must have AP as its only station_info. In the current STA
640           implementation this condition is guaranteed if the STA BSS matches
641           the AP BSS (previous condition)
642   
643    In the case that nodes is 0 AP and 0 STA and 2+ IBSS, then the following
644    conditions must be met in order to return True
645   
646        #. BSS must match at all nodes and be non-null
647   
648    Args:
649        nodes (list of WlanExpNode):  List of WlanExpNode / sub-classes of
650            WlanExpNode
651        verbose (bool):  Print details on which nodes fail BSS membership checks
652   
653    Returns:
654        members (bool):  Boolean indicating whether the nodes were members of the same BSS
655    """
656    import wlan_exp.node_ap   as node_ap
657    import wlan_exp.node_sta  as node_sta
658    import wlan_exp.node_ibss as node_ibss
659
660    # Define filter methods
661    is_ap   = lambda x: type(x) is node_ap.WlanExpNodeAp
662    is_sta  = lambda x: type(x) is node_sta.WlanExpNodeSta
663    is_ibss = lambda x: type(x) is node_ibss.WlanExpNodeIBSS
664
665    # Define BSS info fields used by this method
666    bss_info_fields = ['bssid', 'ssid', 'channel']
667
668    # Define BSS info equality check based on bss_info_fields
669    bss_cfg_eq = lambda x,y: all([x[f] == y[f] for f in bss_info_fields])
670
671    # Define BSS info print method that only prints bss_info_fields   
672    def print_bss_info(bss_info):
673        msg = "BSS Info\n"
674        for f in bss_info_fields:
675            msg += "    {0:10s} = {1}\n".format(f, bss_info[f])
676        return msg
677
678    # Convert argument to list if it is not
679    if type(nodes) is not list:
680        nodes = [nodes]
681
682    # Filter nodes by type
683    ap    = list(filter(is_ap, nodes))
684    stas  = list(filter(is_sta, nodes))
685    ibsss = list(filter(is_ibss, nodes))
686
687
688    # Check provided nodes argument
689    #   (1) 1 AP and 1+ STA and 0  IBSS
690    #   (2) 0 AP and 0  STA and 2+ IBSS
691    if ((len(ap) == 0) and (len(stas) == 0) and (len(ibsss) == 0)):
692        raise AttributeError('No wlan_exp nodes in list')
693
694    if ((len(ibsss) > 0) and ((len(ap) > 0) or (len(stas) > 0))):
695        raise AttributeError('Network cannot contain mix of IBSS and other node types')
696
697    if (len(ap) > 1):
698        raise AttributeError('{0} AP nodes in list - only 0 or 1 AP supported'.format(len(ap)))
699
700    if ((len(ap) == 1) and (len(stas) == 0)):
701        raise AttributeError('Network with 1 AP must include at least 1 STA')
702
703    if (len(ibsss) == 1):
704        raise AttributeError('Network with IBSS nodes must have 2 or more nodes')
705
706    if ((len(ap) == 0) and (len(stas) == 1)):
707        raise AttributeError('Network with STA must include 1 AP')
708
709    msg = ''
710    network_good = True
711
712    ###################################
713    # Infrastructure network (1 AP and 1+ STA and 0 IBSS)
714    if (len(ap) == 1):
715        # Check AP network info
716        ap     = ap[0]
717        ap_network = ap.get_network_info()
718       
719        if (ap_network is None):
720            msg += 'AP network is None - No BSS membership possible\n'
721            network_good = False
722       
723        # Check AP station_infos
724        if (network_good):
725            for s in stas:
726                if (not ap.is_associated(s)):
727                    msg += '"{0}" not in AP association table\n'.format(repr(s))
728                    network_good = False
729
730        # Check STA BSS info
731        if (network_good):
732            for s in stas:
733                sta_network = s.get_network_info()
734
735                if (sta_network is None):
736                    msg += '"{0}" network is None - No BSS membership possible\n'.format(repr(s))
737                    network_good = False
738                else:
739                    if (not bss_cfg_eq(ap_network, sta_network)):
740                        msg += 'Mismatch between Network Info:\n\n'
741                        msg += '"{0}":\n{1}\n\n'.format(repr(ap), print_bss_info(ap_network))
742                        msg += '"{0}":\n{1}\n'.format(repr(s), print_bss_info(sta_network))
743                        network_good = False
744       
745    ###################################
746    # Ad hoc network (0 AP and 0 STA and 2+ IBSS)
747    elif (len(ibsss) > 1):
748        # Check that all BSS infos match and are non-null
749        #     - Use first node as arbitrary 'golden' config
750        golden_network = ibsss[0].get_network_info()
751
752        if (golden_network is None):
753            msg += '"{0}" network is None - No BSS membership possible\n'.format(repr(ibsss[0]))
754            network_good = False
755           
756        if (network_good):
757            for n in ibsss[1:]:
758                n_network = n.get_network_info()
759
760                if (n_network is None):
761                    msg += '"{0}" network is None - No BSS membership possible\n'.format(repr(n))
762                    network_good = False
763                else:
764                    if (not bss_cfg_eq(golden_network, n_network)):
765                        msg += 'Mismatch between Network Info:\n\n'
766                        msg += '"{0}":\n{1}\n\n'.format(repr(ibsss[0]), print_bss_info(golden_network))
767                        msg += '"{0}":\n{1}\n'.format(repr(n), print_bss_info(n_network))
768                        network_good = False
769    else:
770        # Other combination of nodes that somehow passed the nodes type checking above
771        #
772        # This should be impossible with the reference AP/STA/IBSS node implementations
773        # but will catch the case of a new WlanExpNode subclass that has not been added
774        # to the nodes type checking
775        raise AttributeError('Unreognized or invalid combination of node types')
776
777    # Print message
778    if verbose and msg:
779        print(msg)
780
781    return network_good
782
783
784
785# -----------------------------------------------------------------------------
786# Replicated Misc Utilities
787# -----------------------------------------------------------------------------
788
789#
790# These utilities are replicated versions of other functions in wlan_exp.
791# They are consolidated in util to ease import of wlan_exp for scripts.
792#
793
794def int_to_ip(ip_address):
795    """Convert an integer to IP address string (dotted notation).
796
797    Args:
798        ip_address (int):  Unsigned 32-bit integer representation of the IP address
799
800    Returns:
801        ip_address (str):  String version of an IP address of the form W.X.Y.Z
802    """
803    import wlan_exp.transport.transport_eth_ip_udp as transport
804    return transport.int_to_ip(ip_address)
805
806# End def
807
808
809def ip_to_int(ip_address):
810    """Convert IP address string (dotted notation) to an integer.
811
812    Args:
813        ip_address (str):  String version of an IP address of the form W.X.Y.Z
814
815    Returns:
816        ip_address (int):  Unsigned 32-bit integer representation of the IP address
817    """
818    import wlan_exp.transport.transport_eth_ip_udp as transport
819    return transport.ip_to_int(ip_address)
820
821# End def
822
823
824def mac_addr_to_str(mac_address):
825    """Convert an integer to a colon separated MAC address string.
826
827    Args:
828        mac_address (int):  Unsigned 48-bit integer representation of the MAC address
829
830    Returns:
831        mac_address (str):  String version of an MAC address of the form XX:XX:XX:XX:XX:XX
832    """
833    import wlan_exp.transport.transport_eth_ip_udp as transport
834    return transport.mac_addr_to_str(mac_address)
835
836# End def
837
838
839def str_to_mac_addr(mac_address):
840    """Convert a colon separated MAC address string to an integer.
841
842    Args:
843        mac_address (str):  String version of an MAC address of the form XX:XX:XX:XX:XX:XX
844
845    Returns:
846        mac_address (int):  Unsigned 48-bit integer representation of the MAC address
847    """
848    import wlan_exp.transport.transport_eth_ip_udp as transport
849    return transport.str_to_mac_addr(mac_address)
850
851# End def
852
853
854def mac_addr_to_byte_str(mac_address):
855    """Convert an integer to a MAC address byte string.
856
857    Args:
858        mac_address (int):  Unsigned 48-bit integer representation of the MAC address
859
860    Returns:
861        mac_address (str):  Byte string version of an MAC address
862    """
863    import wlan_exp.transport.transport_eth_ip_udp as transport
864    return transport.mac_addr_to_byte_str(mac_address)
865
866# End def
867
868
869def byte_str_to_mac_addr(mac_address):
870    """Convert a MAC address byte string to an integer.
871
872    Args:
873        mac_address (str):  Byte string version of an MAC address
874
875    Returns:
876        mac_address (int):  Unsigned 48-bit integer representation of the MAC address
877    """
878    import wlan_exp.transport.transport_eth_ip_udp as transport
879    return transport.byte_str_to_mac_addr(mac_address)
880
881# End def
882
883
884def buffer_to_str(buffer):
885    """Convert a buffer of bytes to a formatted string.
886
887    Args:
888        buffer (bytes):  Buffer of bytes
889
890    Returns:
891        output (str):  Formatted string of the buffer byte values
892    """
893    import wlan_exp.transport.transport_eth_ip_udp as transport
894    return transport.buffer_to_str(buffer)
895
896# End def
897
898
899def ver_code_to_str(ver_code):
900    """Convert a wlan_exp version code to a string."""
901    import wlan_exp.version as version
902    return version.wlan_exp_ver_code_to_str(ver_code)
903
904# End def
905
906
907# -----------------------------------------------------------------------------
908# Misc Utilities
909# -----------------------------------------------------------------------------
910
911def create_locally_administered_bssid(mac_address):
912    """Create a locally administered BSSID.
913
914    Set "locally administered" bit to '1' and "multicast" bit to '0'
915
916    Args:
917        mac_address (int, str):  MAC address to be used as the base for the BSSID
918            either as a 48-bit integer or a colon delimited string of the form:
919            XX:XX:XX:XX:XX:XX
920
921    Returns:
922        bssid (int):
923            BSSID with the "locally administerd" bit set to '1' and the "multicast" bit set to '0'
924    """
925    if type(mac_address) is str:
926        type_is_str     = True
927        tmp_mac_address = str_to_mac_addr(mac_address)
928    else:
929        type_is_str     = False
930        tmp_mac_address = mac_address
931
932    tmp_mac_address = (tmp_mac_address | mac_addr_local_mask) & (mac_addr_broadcast - mac_addr_mcast_mask)
933
934    if type_is_str:
935        return mac_addr_to_str(tmp_mac_address)
936    else:
937        return tmp_mac_address
938
939# End def
940
941
942def is_locally_administered_bssid(bssid):
943    """Is the BSSID a locally administered BSSID?
944
945    Is "locally administered" bit to '1' and "multicast" bit to '0' of the BSSID?
946
947    Args:
948        bssid (int, str):  BSSID either as a 48-bit integer or a colon
949            delimited string of the form: XX:XX:XX:XX:XX:XX
950
951    Returns:
952        status (bool):
953
954            * True      -- BSSID is locally administered BSSID
955            * False     -- BSSID is not locally administered BSSID
956    """
957    if type(bssid) is str:
958        tmp_bssid = str_to_mac_addr(bssid)
959    else:
960        tmp_bssid = bssid
961
962    if (((tmp_bssid & mac_addr_local_mask) == mac_addr_local_mask) and 
963        ((tmp_bssid & mac_addr_mcast_mask) == 0)):
964        return True
965    else:
966        return False
967
968# End def
969
970
971def sn_to_str(platform_id, serial_number):
972    """Convert serial number to a string for a given hardware generation.
973
974    Args:
975        platform_id   (int):  Platform ID (currently only '3' is supported)
976        serial_number (int):  Integer part of the node's serial number
977
978    Returns:
979        serial_number (str):  String representation of the node's serial number
980    """
981    if(platform_id == 3):
982        return ('W3-a-{0:05d}'.format(int(serial_number)))
983    else:
984        print("ERROR:  Not a valid Platform ID: {0}".format(platform_id))
985
986# End def
987
988
989def node_type_to_str(node_type, node_factory=None):
990    """Convert the Node Type to a string description.
991
992    Args:
993        node_type (int):  node type ID (u32)
994        node_factory (WlanExpNodeFactory): A WlanExpNodeFactory or subclass to
995            create nodes of a given type
996
997    Returns:
998        node_type (str):  String representation of the node type
999
1000    By default, a dictionary of node types is built dynamically during init_nodes().
1001    If init_nodes() has not been run, then the method will try to create a node
1002    type dictionary.  If a node_factory is not provided then a default WlanExpNodeFactory
1003    will be used to determine the node type.  If a default WlanExpNodeFactory is used,
1004    then only framework node types will be known and custom node types will return:
1005    "Unknown node type: <value>"
1006    """
1007    return 'node_type_to_str: TODO'
1008
1009# End def
1010
1011
1012def mac_addr_desc(mac_addr, desc_map=None):
1013    """Returns a string description of a MAC address.
1014
1015    This is useful when printing a table of addresses.  Custom MAC address
1016    descriptions can be provided via the desc_map argument.  In addition
1017    to the provided desc_map, the global mac_addr_desc_map that describes
1018    mappings of different MAC addresses will also be used.
1019
1020    Args:
1021        mac_address (int):  64-bit integer representing 48-bit MAC address
1022        desc_map (list of tuple, optional): list of tuple or tuple of the form
1023            (addr_mask, addr_value, descritpion)
1024
1025    Returns:
1026        description (str):  Description of the MAC address or '' if address
1027        does not match any descriptions
1028
1029    The mac_address argument will be bitwise AND'd with each addr_mask, then
1030    compared to addr_value. If the result is non-zero the corresponding
1031    descprition will be returned.  This will only return the first description
1032    in the [desc_map, mac_addr_desc_map] list.
1033
1034    """
1035    # Cast to python int in case input is still numpy uint64
1036    mac_addr = int(mac_addr)
1037
1038    desc_out = ''
1039
1040    if(desc_map is None):
1041        desc_map = mac_addr_desc_map
1042    else:
1043        desc_map = list(desc_map) + mac_addr_desc_map
1044
1045    for (req, mask, desc) in desc_map:
1046        if( (mac_addr & mask) == req):
1047            desc_out += desc
1048            break
1049
1050    return desc_out
1051
1052# End def
1053
1054
1055# Excellent util function for dropping into interactive Python shell
1056#   From http://vjethava.blogspot.com/2010/11/matlabs-keyboard-command-in-python.html
1057def debug_here(banner=None):
1058    """Function that mimics the matlab keyboard command for interactive debbug.
1059
1060    Args:
1061        banner (str):  Banner message to be displayed before the interactive prompt
1062    """
1063    import code
1064    # Use exception trick to pick up the current frame
1065    try:
1066        raise None
1067    except TypeError:
1068        frame = sys.exc_info()[2].tb_frame.f_back
1069
1070    print("# Use quit() or Ctrl-D to exit")
1071
1072    # evaluate commands in current namespace
1073    namespace = frame.f_globals.copy()
1074    namespace.update(frame.f_locals)
1075
1076    try:
1077        code.interact(banner=banner, local=namespace)
1078    except SystemExit:
1079        return
1080
1081# End def
1082
1083
1084# -----------------------------------------------------------------------------
1085# Internal Methods
1086# -----------------------------------------------------------------------------
1087def _get_nodes_by_type(nodes, mac_high=None, mac_low=None):
1088    """Returns all nodes in the list that have the given node_type."""
1089
1090    # Initialize the return list to all nodes; non-matching nodes will be removed below
1091    ret_nodes = nodes
1092   
1093    # Filter on the high software application ID
1094    if mac_high is not None:
1095        if type(mac_high) is not list:
1096            mac_high = [mac_high]
1097        ret_nodes = [n for n in ret_nodes if n.high_sw_id in mac_high]
1098
1099    # Filter on the low software application ID
1100    if mac_low is not None:
1101        if type(mac_low) is not list:
1102            mac_low = [mac_low]
1103        ret_nodes = [n for n in ret_nodes if n.low_sw_id in mac_low]
1104
1105    return ret_nodes
1106
1107# End def
1108
1109
1110def _get_nodes_by_sn(nodes, serial_number=None):
1111    """Returns all nodes in the list that have the given serial number."""
1112
1113    # Initialize the return list to all nodes; non-matching nodes will be removed below
1114    ret_nodes = nodes
1115
1116    if serial_number is not None:
1117        if type(serial_number) is not list:
1118            serial_number = [serial_number]
1119        ret_nodes = [n for n in nodes if (n.serial_number in serial_number)]
1120
1121    return ret_nodes
1122
1123# End def
1124
1125
1126def _time():
1127    """Time function to handle differences between Python 2.7 and 3.3"""
1128    try:
1129        return time.perf_counter()
1130    except AttributeError:
1131        return time.clock()
1132
1133# End def
1134
1135
1136def _broadcast_time_to_nodes(time_cmd, network_config, time=0, time_id=None):
1137    """Internal method to issue broadcast time commands
1138
1139    This method will iterate through all host interfaces and issue a
1140    broadcast packet on each interface that will set the time to the
1141    timebase.  The method keeps track of how long it takes to send
1142    each packet so that the time on all nodes is as close as possible
1143    even across interface.
1144
1145    Attributes:
1146        time_cmd       -- NodeProcTime command to issue
1147        network_config -- A NetworkConfiguration object
1148        time           -- Optional time to broadcast to the nodes (defaults to 0)
1149                          as an integer number of microseconds
1150        time_id        -- Optional value to identify broadcast time commands across nodes
1151    """
1152    import wlan_exp.cmds as cmds
1153
1154    # NodeProcTime requires integer time value, interpretted as integer
1155    #  microseconds for setting node's MAC time
1156    try:
1157        node_time = int(time)
1158    except ValueError:
1159        raise Exception("ERROR: time argument must be integer - value {0} as {1} invalid".format(time, type(time)))
1160
1161    # Determine if data is being sent to multiple networks
1162    if type(network_config) is list:
1163        configs = network_config
1164    else:
1165        configs = [network_config]
1166
1167    # Send command to each network
1168    for idx, config in enumerate(configs):
1169        network_addr    = config.get_param('network')
1170
1171        if (idx == 0):
1172            node_time   = node_time
1173            start_time  = _time()
1174        else:
1175            node_time   = node_time + (_time() - start_time)
1176
1177        cmd             = cmds.NodeProcTime(time_cmd, node_time, time_id)
1178
1179        _broadcast_cmd_to_nodes_helper(cmd, network_config)
1180
1181        msg = ""
1182        if (time_cmd == cmds.CMD_PARAM_WRITE):
1183            msg += "Initializing the time of all nodes on network "
1184            msg += "{0} to: {1}".format(network_addr, node_time)
1185        elif (time_cmd == cmds.CMD_PARAM_TIME_ADD_TO_LOG):
1186            msg += "Adding current time to log for nodes on network {0}".format(network_addr)
1187        print(msg)
1188
1189# End def
1190
1191
1192def _broadcast_cmd_to_nodes_helper(cmd, network_config):
1193    """Internal method to issue broadcast commands
1194
1195    Attributes:
1196        network_config -- A NetworkConfiguration object
1197        cmd            -- A message.Cmd object describing the command
1198    """
1199    import wlan_exp.transport.transport_eth_ip_udp_py_broadcast as broadcast
1200
1201    # Get information out of the NetworkConfiguration
1202    tx_buf_size  = network_config.get_param('tx_buffer_size')
1203    rx_buf_size  = network_config.get_param('rx_buffer_size')
1204
1205    # Create broadcast transport and send message
1206    transport_broadcast = broadcast.TransportEthIpUdpPyBroadcast(network_config)
1207
1208    transport_broadcast.transport_open(tx_buf_size, rx_buf_size)
1209
1210    transport_broadcast.send(payload=cmd.serialize())
1211
1212    transport_broadcast.transport_close()
1213
1214# End def
1215
1216
Note: See TracBrowser for help on using the repository browser.