[6320] | 1 | # -*- coding: utf-8 -*- |
---|
| 2 | """ |
---|
| 3 | ------------------------------------------------------------------------------ |
---|
| 4 | Mango 802.11 Reference Design Experiments Framework - Utilities |
---|
| 5 | ------------------------------------------------------------------------------ |
---|
| 6 | License: Copyright 2019 Mango Communications, Inc. All rights reserved. |
---|
| 7 | Use and distribution subject to terms in LICENSE.txt |
---|
| 8 | ------------------------------------------------------------------------------ |
---|
| 9 | """ |
---|
| 10 | |
---|
| 11 | import sys |
---|
| 12 | import time |
---|
| 13 | |
---|
| 14 | import wlan_exp.defaults as defaults |
---|
| 15 | |
---|
| 16 | # Fix to support Python 2.x and 3.x |
---|
| 17 | if 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 | |
---|
| 28 | class 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 |
---|
| 80 | uart_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 |
---|
| 100 | phy_modes = consts_dict({ |
---|
| 101 | 'DSSS' : 0, |
---|
| 102 | 'NONHT' : 1, |
---|
| 103 | 'HTMF' : 2}) |
---|
| 104 | |
---|
| 105 | |
---|
| 106 | def 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 | |
---|
| 185 | def 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. |
---|
| 219 | wlan_channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 36, 40, 44, 48] |
---|
| 220 | |
---|
| 221 | |
---|
| 222 | def 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 | |
---|
| 310 | def 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. |
---|
| 335 | wlan_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. |
---|
| 343 | wlan_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 | |
---|
| 361 | mac_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 |
---|
| 371 | mac_addr_mcast_mask = 0x010000000000 |
---|
| 372 | mac_addr_local_mask = 0x020000000000 |
---|
| 373 | mac_addr_broadcast = 0xFFFFFFFFFFFF |
---|
| 374 | |
---|
| 375 | |
---|
| 376 | |
---|
| 377 | # ----------------------------------------------------------------------------- |
---|
| 378 | # Node Utilities |
---|
| 379 | # ----------------------------------------------------------------------------- |
---|
| 380 | |
---|
| 381 | def 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 | |
---|
| 434 | def 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 | |
---|
| 459 | def 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 | |
---|
| 479 | def 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 | |
---|
| 505 | def 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 | |
---|
| 624 | def 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 | |
---|
| 794 | def 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 | |
---|
| 809 | def 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 | |
---|
| 824 | def 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 | |
---|
| 839 | def 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 | |
---|
| 854 | def 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 | |
---|
| 869 | def 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 | |
---|
| 884 | def 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 | |
---|
| 899 | def 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 | |
---|
| 911 | def 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 | |
---|
| 942 | def 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 | |
---|
| 971 | def 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 | |
---|
| 989 | def 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 | |
---|
| 1012 | def 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 |
---|
| 1057 | def 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 | # ----------------------------------------------------------------------------- |
---|
| 1087 | def _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 | |
---|
| 1110 | def _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 | |
---|
| 1126 | def _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 | |
---|
| 1136 | def _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 | |
---|
| 1192 | def _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 | |
---|