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 | |
---|