Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python3.4 |
| 2 | # |
| 3 | # Copyright 2017 Google, Inc. |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | |
| 17 | import logging |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 18 | import time |
| 19 | from acts import utils |
| 20 | from acts.controllers import monsoon |
Qi Jiang | caca55e | 2017-10-10 23:10:40 +0000 | [diff] [blame] | 21 | from acts.libs.proc import job |
Qi Jiang | 7f6dc98 | 2018-02-24 01:29:29 +0000 | [diff] [blame] | 22 | from acts.controllers.ap_lib import bridge_interface as bi |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 23 | from acts.test_utils.wifi import wifi_test_utils as wutils |
Qi Jiang | 8f9c758 | 2017-09-06 21:00:14 +0000 | [diff] [blame] | 24 | from bokeh.layouts import layout |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 25 | from bokeh.models import CustomJS, ColumnDataSource |
Omar El Ayach | f4f132b | 2018-01-26 03:37:08 +0000 | [diff] [blame] | 26 | from bokeh.models import tools as bokeh_tools |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 27 | from bokeh.models.widgets import DataTable, TableColumn |
Qi Jiang | 8f9c758 | 2017-09-06 21:00:14 +0000 | [diff] [blame] | 28 | from bokeh.plotting import figure, output_file, save |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 29 | from acts.controllers.ap_lib import hostapd_security |
| 30 | from acts.controllers.ap_lib import hostapd_ap_preset |
Daniel Barros | 4376030 | 2017-12-06 04:16:03 +0000 | [diff] [blame] | 31 | |
Daniel Barros | fefd8c3 | 2017-08-04 15:36:40 -0700 | [diff] [blame] | 32 | # http://www.secdev.org/projects/scapy/ |
| 33 | # On ubuntu, sudo pip3 install scapy-python3 |
| 34 | import scapy.all as scapy |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 35 | |
Daniel Barros | ebbf91a | 2017-10-04 01:08:40 +0000 | [diff] [blame] | 36 | GET_FROM_PHONE = 'get_from_dut' |
| 37 | GET_FROM_AP = 'get_from_ap' |
Qi Jiang | 487e758 | 2018-04-24 08:10:12 -0700 | [diff] [blame] | 38 | ENABLED_MODULATED_DTIM = 'gEnableModulatedDTIM=' |
| 39 | MAX_MODULATED_DTIM = 'gMaxLIModulatedDTIM=' |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 40 | |
| 41 | |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 42 | def monsoon_data_plot(mon_info, file_path, tag=""): |
| 43 | """Plot the monsoon current data using bokeh interactive plotting tool. |
| 44 | |
| 45 | Plotting power measurement data with bokeh to generate interactive plots. |
| 46 | You can do interactive data analysis on the plot after generating with the |
| 47 | provided widgets, which make the debugging much easier. To realize that, |
| 48 | bokeh callback java scripting is used. View a sample html output file: |
| 49 | https://drive.google.com/open?id=0Bwp8Cq841VnpT2dGUUxLYWZvVjA |
| 50 | |
| 51 | Args: |
Tom Turney | 35d297e | 2018-05-11 13:37:41 -0700 | [diff] [blame] | 52 | mon_info: obj with information of monsoon measurement, including |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 53 | monsoon device object, measurement frequency, duration and |
| 54 | offset etc. |
| 55 | file_path: the path to the monsoon log file with current data |
| 56 | |
| 57 | Returns: |
| 58 | plot: the plotting object of bokeh, optional, will be needed if multiple |
| 59 | plots will be combined to one html file. |
| 60 | dt: the datatable object of bokeh, optional, will be needed if multiple |
| 61 | datatables will be combined to one html file. |
| 62 | """ |
| 63 | |
| 64 | log = logging.getLogger() |
| 65 | log.info("Plot the power measurement data") |
| 66 | #Get results as monsoon data object from the input file |
| 67 | results = monsoon.MonsoonData.from_text_file(file_path) |
| 68 | #Decouple current and timestamp data from the monsoon object |
| 69 | current_data = [] |
| 70 | timestamps = [] |
| 71 | voltage = results[0].voltage |
| 72 | [current_data.extend(x.data_points) for x in results] |
| 73 | [timestamps.extend(x.timestamps) for x in results] |
Tom Turney | 35d297e | 2018-05-11 13:37:41 -0700 | [diff] [blame] | 74 | period = 1 / float(mon_info.freq) |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 75 | time_relative = [x * period for x in range(len(current_data))] |
| 76 | #Calculate the average current for the test |
| 77 | current_data = [x * 1000 for x in current_data] |
| 78 | avg_current = sum(current_data) / len(current_data) |
| 79 | color = ['navy'] * len(current_data) |
| 80 | |
| 81 | #Preparing the data and source link for bokehn java callback |
Qi Jiang | 94b289a | 2018-03-01 03:50:15 +0000 | [diff] [blame] | 82 | source = ColumnDataSource( |
| 83 | data=dict(x0=time_relative, y0=current_data, color=color)) |
| 84 | s2 = ColumnDataSource( |
| 85 | data=dict( |
Tom Turney | 35d297e | 2018-05-11 13:37:41 -0700 | [diff] [blame] | 86 | z0=[mon_info.duration], |
Qi Jiang | 94b289a | 2018-03-01 03:50:15 +0000 | [diff] [blame] | 87 | y0=[round(avg_current, 2)], |
| 88 | x0=[round(avg_current * voltage, 2)], |
Tom Turney | 35d297e | 2018-05-11 13:37:41 -0700 | [diff] [blame] | 89 | z1=[round(avg_current * voltage * mon_info.duration, 2)], |
| 90 | z2=[round(avg_current * mon_info.duration, 2)])) |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 91 | #Setting up data table for the output |
| 92 | columns = [ |
| 93 | TableColumn(field='z0', title='Total Duration (s)'), |
| 94 | TableColumn(field='y0', title='Average Current (mA)'), |
| 95 | TableColumn(field='x0', title='Average Power (4.2v) (mW)'), |
| 96 | TableColumn(field='z1', title='Average Energy (mW*s)'), |
| 97 | TableColumn(field='z2', title='Normalized Average Energy (mA*s)') |
| 98 | ] |
| 99 | dt = DataTable( |
| 100 | source=s2, columns=columns, width=1300, height=60, editable=True) |
| 101 | |
| 102 | plot_title = file_path[file_path.rfind('/') + 1:-4] + tag |
Tom Turney | 35d297e | 2018-05-11 13:37:41 -0700 | [diff] [blame] | 103 | output_file("%s/%s.html" % (mon_info.data_path, plot_title)) |
Omar El Ayach | f4f132b | 2018-01-26 03:37:08 +0000 | [diff] [blame] | 104 | TOOLS = ('box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save') |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 105 | # Create a new plot with the datatable above |
| 106 | plot = figure( |
| 107 | plot_width=1300, |
| 108 | plot_height=700, |
| 109 | title=plot_title, |
| 110 | tools=TOOLS, |
Omar El Ayach | f4f132b | 2018-01-26 03:37:08 +0000 | [diff] [blame] | 111 | output_backend="webgl") |
| 112 | plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="width")) |
| 113 | plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="height")) |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 114 | plot.line('x0', 'y0', source=source, line_width=2) |
| 115 | plot.circle('x0', 'y0', source=source, size=0.5, fill_color='color') |
| 116 | plot.xaxis.axis_label = 'Time (s)' |
| 117 | plot.yaxis.axis_label = 'Current (mA)' |
| 118 | plot.title.text_font_size = {'value': '15pt'} |
| 119 | |
| 120 | #Callback Java scripting |
| 121 | source.callback = CustomJS( |
| 122 | args=dict(mytable=dt), |
| 123 | code=""" |
| 124 | var inds = cb_obj.get('selected')['1d'].indices; |
| 125 | var d1 = cb_obj.get('data'); |
| 126 | var d2 = mytable.get('source').get('data'); |
| 127 | ym = 0 |
| 128 | ts = 0 |
| 129 | d2['x0'] = [] |
| 130 | d2['y0'] = [] |
| 131 | d2['z1'] = [] |
| 132 | d2['z2'] = [] |
| 133 | d2['z0'] = [] |
| 134 | min=max=d1['x0'][inds[0]] |
| 135 | if (inds.length==0) {return;} |
| 136 | for (i = 0; i < inds.length; i++) { |
| 137 | ym += d1['y0'][inds[i]] |
| 138 | d1['color'][inds[i]] = "red" |
| 139 | if (d1['x0'][inds[i]] < min) { |
| 140 | min = d1['x0'][inds[i]]} |
| 141 | if (d1['x0'][inds[i]] > max) { |
| 142 | max = d1['x0'][inds[i]]} |
| 143 | } |
| 144 | ym /= inds.length |
| 145 | ts = max - min |
| 146 | dx0 = Math.round(ym*4.2*100.0)/100.0 |
| 147 | dy0 = Math.round(ym*100.0)/100.0 |
| 148 | dz1 = Math.round(ym*4.2*ts*100.0)/100.0 |
| 149 | dz2 = Math.round(ym*ts*100.0)/100.0 |
| 150 | dz0 = Math.round(ts*1000.0)/1000.0 |
| 151 | d2['z0'].push(dz0) |
| 152 | d2['x0'].push(dx0) |
| 153 | d2['y0'].push(dy0) |
| 154 | d2['z1'].push(dz1) |
| 155 | d2['z2'].push(dz2) |
| 156 | mytable.trigger('change'); |
| 157 | """) |
| 158 | |
| 159 | #Layout the plot and the datatable bar |
| 160 | l = layout([[dt], [plot]]) |
| 161 | save(l) |
| 162 | return [plot, dt] |
| 163 | |
| 164 | |
Qi Jiang | 43c80e1 | 2017-11-04 03:05:21 +0000 | [diff] [blame] | 165 | def change_dtim(ad, gEnableModulatedDTIM, gMaxLIModulatedDTIM=10): |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 166 | """Function to change the DTIM setting in the phone. |
| 167 | |
| 168 | Args: |
| 169 | ad: the target android device, AndroidDevice object |
| 170 | gEnableModulatedDTIM: Modulated DTIM, int |
| 171 | gMaxLIModulatedDTIM: Maximum modulated DTIM, int |
| 172 | """ |
Qi Jiang | 487e758 | 2018-04-24 08:10:12 -0700 | [diff] [blame] | 173 | # First trying to find the ini file with DTIM settings |
| 174 | ini_file_phone = ad.adb.shell('ls /vendor/firmware/wlan/*/*.ini') |
| 175 | ini_file_local = ini_file_phone.split('/')[-1] |
| 176 | |
| 177 | # Pull the file and change the DTIM to desired value |
| 178 | ad.adb.pull('{} {}'.format(ini_file_phone, ini_file_local)) |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 179 | |
| 180 | with open(ini_file_local, 'r') as fin: |
| 181 | for line in fin: |
Qi Jiang | 487e758 | 2018-04-24 08:10:12 -0700 | [diff] [blame] | 182 | if ENABLED_MODULATED_DTIM in line: |
| 183 | gE_old = line.strip('\n') |
| 184 | gEDTIM_old = line.strip(ENABLED_MODULATED_DTIM).strip('\n') |
| 185 | if MAX_MODULATED_DTIM in line: |
| 186 | gM_old = line.strip('\n') |
| 187 | gMDTIM_old = line.strip(MAX_MODULATED_DTIM).strip('\n') |
| 188 | fin.close() |
Qi Jiang | 43c80e1 | 2017-11-04 03:05:21 +0000 | [diff] [blame] | 189 | if int(gEDTIM_old) == gEnableModulatedDTIM and int( |
| 190 | gMDTIM_old) == gMaxLIModulatedDTIM: |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 191 | ad.log.info('Current DTIM is already the desired value,' |
| 192 | 'no need to reset it') |
Qi Jiang | 119d8b8 | 2018-05-21 16:53:52 -0700 | [diff] [blame] | 193 | return 0 |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 194 | |
Qi Jiang | 487e758 | 2018-04-24 08:10:12 -0700 | [diff] [blame] | 195 | gE_new = ENABLED_MODULATED_DTIM + str(gEnableModulatedDTIM) |
| 196 | gM_new = MAX_MODULATED_DTIM + str(gMaxLIModulatedDTIM) |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 197 | |
Qi Jiang | 487e758 | 2018-04-24 08:10:12 -0700 | [diff] [blame] | 198 | sed_gE = 'sed -i \'s/{}/{}/g\' {}'.format(gE_old, gE_new, ini_file_local) |
| 199 | sed_gM = 'sed -i \'s/{}/{}/g\' {}'.format(gM_old, gM_new, ini_file_local) |
| 200 | job.run(sed_gE) |
| 201 | job.run(sed_gM) |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 202 | |
Qi Jiang | 487e758 | 2018-04-24 08:10:12 -0700 | [diff] [blame] | 203 | # Push the file to the phone |
| 204 | push_file_to_phone(ad, ini_file_local, ini_file_phone) |
| 205 | ad.log.info('DTIM changes checked in and rebooting...') |
| 206 | ad.reboot() |
Qi Jiang | 119d8b8 | 2018-05-21 16:53:52 -0700 | [diff] [blame] | 207 | # Wait for auto-wifi feature to start |
| 208 | time.sleep(20) |
Qi Jiang | 487e758 | 2018-04-24 08:10:12 -0700 | [diff] [blame] | 209 | ad.log.info('DTIM updated and device back from reboot') |
Qi Jiang | 119d8b8 | 2018-05-21 16:53:52 -0700 | [diff] [blame] | 210 | return 1 |
Qi Jiang | 487e758 | 2018-04-24 08:10:12 -0700 | [diff] [blame] | 211 | |
| 212 | |
| 213 | def push_file_to_phone(ad, file_local, file_phone): |
| 214 | """Function to push local file to android phone. |
| 215 | |
| 216 | Args: |
| 217 | ad: the target android device |
| 218 | file_local: the locla file to push |
| 219 | file_phone: the file/directory on the phone to be pushed |
| 220 | """ |
| 221 | ad.adb.root() |
| 222 | cmd_out = ad.adb.remount() |
| 223 | if 'Permission denied' in cmd_out: |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 224 | ad.log.info('Need to disable verity first and reboot') |
Qi Jiang | 487e758 | 2018-04-24 08:10:12 -0700 | [diff] [blame] | 225 | ad.adb.disable_verity() |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 226 | time.sleep(1) |
| 227 | ad.reboot() |
| 228 | ad.log.info('Verity disabled and device back from reboot') |
Qi Jiang | 487e758 | 2018-04-24 08:10:12 -0700 | [diff] [blame] | 229 | ad.adb.root() |
| 230 | ad.adb.remount() |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 231 | time.sleep(1) |
Qi Jiang | 487e758 | 2018-04-24 08:10:12 -0700 | [diff] [blame] | 232 | ad.adb.push('{} {}'.format(file_local, file_phone)) |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 233 | |
| 234 | |
Qi Jiang | dc073e5 | 2017-12-19 19:12:35 +0000 | [diff] [blame] | 235 | def ap_setup(ap, network, bandwidth=80): |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 236 | """Set up the whirlwind AP with provided network info. |
| 237 | |
| 238 | Args: |
| 239 | ap: access_point object of the AP |
| 240 | network: dict with information of the network, including ssid, password |
| 241 | bssid, channel etc. |
Qi Jiang | dc073e5 | 2017-12-19 19:12:35 +0000 | [diff] [blame] | 242 | bandwidth: the operation bandwidth for the AP, default 80MHz |
Qi Jiang | 7f6dc98 | 2018-02-24 01:29:29 +0000 | [diff] [blame] | 243 | Returns: |
| 244 | brconfigs: the bridge interface configs |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 245 | """ |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 246 | log = logging.getLogger() |
| 247 | bss_settings = [] |
| 248 | ssid = network[wutils.WifiEnums.SSID_KEY] |
Qi Jiang | caca55e | 2017-10-10 23:10:40 +0000 | [diff] [blame] | 249 | if "password" in network.keys(): |
| 250 | password = network["password"] |
| 251 | security = hostapd_security.Security( |
| 252 | security_mode="wpa", password=password) |
| 253 | else: |
| 254 | security = hostapd_security.Security(security_mode=None, password=None) |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 255 | channel = network["channel"] |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 256 | config = hostapd_ap_preset.create_ap_preset( |
| 257 | channel=channel, |
| 258 | ssid=ssid, |
| 259 | security=security, |
| 260 | bss_settings=bss_settings, |
Qi Jiang | dc073e5 | 2017-12-19 19:12:35 +0000 | [diff] [blame] | 261 | vht_bandwidth=bandwidth, |
| 262 | profile_name='whirlwind', |
| 263 | iface_wlan_2g=ap.wlan_2g, |
| 264 | iface_wlan_5g=ap.wlan_5g) |
Qi Jiang | 7f6dc98 | 2018-02-24 01:29:29 +0000 | [diff] [blame] | 265 | config_bridge = ap.generate_bridge_configs(channel) |
| 266 | brconfigs = bi.BridgeInterfaceConfigs(config_bridge[0], config_bridge[1], |
| 267 | config_bridge[2]) |
| 268 | ap.bridge.startup(brconfigs) |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 269 | ap.start_ap(config) |
| 270 | log.info("AP started on channel {} with SSID {}".format(channel, ssid)) |
Qi Jiang | 7f6dc98 | 2018-02-24 01:29:29 +0000 | [diff] [blame] | 271 | return brconfigs |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 272 | |
| 273 | |
Omar El Ayach | c01e1ef | 2018-02-28 04:52:49 +0000 | [diff] [blame] | 274 | def bokeh_plot(data_sets, |
| 275 | legends, |
| 276 | fig_property, |
| 277 | shaded_region=None, |
| 278 | output_file_path=None): |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 279 | """Plot bokeh figs. |
| 280 | Args: |
| 281 | data_sets: data sets including lists of x_data and lists of y_data |
| 282 | ex: [[[x_data1], [x_data2]], [[y_data1],[y_data2]]] |
| 283 | legends: list of legend for each curve |
| 284 | fig_property: dict containing the plot property, including title, |
| 285 | lables, linewidth, circle size, etc. |
Omar El Ayach | c01e1ef | 2018-02-28 04:52:49 +0000 | [diff] [blame] | 286 | shaded_region: optional dict containing data for plot shading |
| 287 | output_file_path: optional path at which to save figure |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 288 | Returns: |
| 289 | plot: bokeh plot figure object |
| 290 | """ |
Omar El Ayach | f4f132b | 2018-01-26 03:37:08 +0000 | [diff] [blame] | 291 | TOOLS = ('box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save') |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 292 | plot = figure( |
| 293 | plot_width=1300, |
| 294 | plot_height=700, |
| 295 | title=fig_property['title'], |
| 296 | tools=TOOLS, |
Omar El Ayach | f4f132b | 2018-01-26 03:37:08 +0000 | [diff] [blame] | 297 | output_backend="webgl") |
| 298 | plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="width")) |
| 299 | plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="height")) |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 300 | colors = [ |
| 301 | 'red', 'green', 'blue', 'olive', 'orange', 'salmon', 'black', 'navy', |
| 302 | 'yellow', 'darkred', 'goldenrod' |
| 303 | ] |
Omar El Ayach | c01e1ef | 2018-02-28 04:52:49 +0000 | [diff] [blame] | 304 | if shaded_region: |
| 305 | band_x = shaded_region["x_vector"] |
| 306 | band_x.extend(shaded_region["x_vector"][::-1]) |
| 307 | band_y = shaded_region["lower_limit"] |
| 308 | band_y.extend(shaded_region["upper_limit"][::-1]) |
| 309 | plot.patch( |
| 310 | band_x, band_y, color='#7570B3', line_alpha=0.1, fill_alpha=0.1) |
| 311 | |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 312 | for x_data, y_data, legend in zip(data_sets[0], data_sets[1], legends): |
| 313 | index_now = legends.index(legend) |
| 314 | color = colors[index_now % len(colors)] |
| 315 | plot.line( |
| 316 | x_data, y_data, legend=str(legend), line_width=3, color=color) |
| 317 | plot.circle( |
| 318 | x_data, y_data, size=10, legend=str(legend), fill_color=color) |
Omar El Ayach | c01e1ef | 2018-02-28 04:52:49 +0000 | [diff] [blame] | 319 | |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 320 | #Plot properties |
| 321 | plot.xaxis.axis_label = fig_property['x_label'] |
| 322 | plot.yaxis.axis_label = fig_property['y_label'] |
| 323 | plot.legend.location = "top_right" |
| 324 | plot.legend.click_policy = "hide" |
| 325 | plot.title.text_font_size = {'value': '15pt'} |
Omar El Ayach | ded6423 | 2017-12-08 00:53:12 +0000 | [diff] [blame] | 326 | if output_file_path is not None: |
| 327 | output_file(output_file_path) |
| 328 | save(plot) |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 329 | return plot |
| 330 | |
| 331 | |
| 332 | def run_iperf_client_nonblocking(ad, server_host, extra_args=""): |
| 333 | """Start iperf client on the device with nohup. |
| 334 | |
| 335 | Return status as true if iperf client start successfully. |
| 336 | And data flow information as results. |
| 337 | |
| 338 | Args: |
| 339 | ad: the android device under test |
| 340 | server_host: Address of the iperf server. |
| 341 | extra_args: A string representing extra arguments for iperf client, |
| 342 | e.g. "-i 1 -t 30". |
| 343 | |
| 344 | """ |
| 345 | log = logging.getLogger() |
| 346 | ad.adb.shell_nb("nohup iperf3 -c {} {} &".format(server_host, extra_args)) |
| 347 | log.info("IPerf client started") |
| 348 | |
| 349 | |
| 350 | def get_wifi_rssi(ad): |
| 351 | """Get the RSSI of the device. |
| 352 | |
| 353 | Args: |
| 354 | ad: the android device under test |
| 355 | Returns: |
| 356 | RSSI: the rssi level of the device |
| 357 | """ |
| 358 | RSSI = ad.droid.wifiGetConnectionInfo()['rssi'] |
| 359 | return RSSI |
Qi Jiang | 5073338 | 2017-08-22 01:11:58 +0000 | [diff] [blame] | 360 | |
| 361 | |
| 362 | def get_phone_ip(ad): |
| 363 | """Get the WiFi IP address of the phone. |
| 364 | |
| 365 | Args: |
| 366 | ad: the android device under test |
| 367 | Returns: |
| 368 | IP: IP address of the phone for WiFi, as a string |
| 369 | """ |
| 370 | IP = ad.droid.connectivityGetIPv4Addresses('wlan0')[0] |
| 371 | |
| 372 | return IP |
| 373 | |
| 374 | |
| 375 | def get_phone_mac(ad): |
| 376 | """Get the WiFi MAC address of the phone. |
| 377 | |
| 378 | Args: |
| 379 | ad: the android device under test |
| 380 | Returns: |
| 381 | mac: MAC address of the phone for WiFi, as a string |
| 382 | """ |
| 383 | mac = ad.droid.wifiGetConnectionInfo()["mac_address"] |
| 384 | |
| 385 | return mac |
| 386 | |
| 387 | |
| 388 | def get_phone_ipv6(ad): |
| 389 | """Get the WiFi IPV6 address of the phone. |
| 390 | |
| 391 | Args: |
| 392 | ad: the android device under test |
| 393 | Returns: |
| 394 | IPv6: IPv6 address of the phone for WiFi, as a string |
| 395 | """ |
| 396 | IPv6 = ad.droid.connectivityGetLinkLocalIpv6Address('wlan0')[:-6] |
| 397 | |
| 398 | return IPv6 |
Daniel Barros | fefd8c3 | 2017-08-04 15:36:40 -0700 | [diff] [blame] | 399 | |
| 400 | |
| 401 | def get_if_addr6(intf, address_type): |
| 402 | """Returns the Ipv6 address from a given local interface. |
| 403 | |
| 404 | Returns the desired IPv6 address from the interface 'intf' in human |
| 405 | readable form. The address type is indicated by the IPv6 constants like |
| 406 | IPV6_ADDR_LINKLOCAL, IPV6_ADDR_GLOBAL, etc. If no address is found, |
| 407 | None is returned. |
| 408 | |
| 409 | Args: |
| 410 | intf: desired interface name |
| 411 | address_type: addrees typle like LINKLOCAL or GLOBAL |
| 412 | |
| 413 | Returns: |
| 414 | Ipv6 address of the specified interface in human readable format |
| 415 | """ |
| 416 | for if_list in scapy.in6_getifaddr(): |
| 417 | if if_list[2] == intf and if_list[1] == address_type: |
| 418 | return if_list[0] |
| 419 | |
| 420 | return None |
Daniel Barros | ebbf91a | 2017-10-04 01:08:40 +0000 | [diff] [blame] | 421 | |
| 422 | |
Qi Jiang | caca55e | 2017-10-10 23:10:40 +0000 | [diff] [blame] | 423 | @utils.timeout(60) |
| 424 | def wait_for_dhcp(intf): |
| 425 | """Wait the DHCP address assigned to desired interface. |
| 426 | |
| 427 | Getting DHCP address takes time and the wait time isn't constant. Utilizing |
| 428 | utils.timeout to keep trying until success |
| 429 | |
| 430 | Args: |
| 431 | intf: desired interface name |
| 432 | Returns: |
| 433 | ip: ip address of the desired interface name |
| 434 | Raise: |
| 435 | TimeoutError: After timeout, if no DHCP assigned, raise |
| 436 | """ |
| 437 | log = logging.getLogger() |
| 438 | reset_host_interface(intf) |
| 439 | ip = '0.0.0.0' |
| 440 | while ip == '0.0.0.0': |
| 441 | ip = scapy.get_if_addr(intf) |
Qi Jiang | 0ded53a | 2018-02-03 06:23:11 +0000 | [diff] [blame] | 442 | log.info('DHCP address assigned to {} as {}'.format(intf, ip)) |
Qi Jiang | caca55e | 2017-10-10 23:10:40 +0000 | [diff] [blame] | 443 | return ip |
| 444 | |
| 445 | |
| 446 | def reset_host_interface(intf): |
| 447 | """Reset the host interface. |
| 448 | |
| 449 | Args: |
| 450 | intf: the desired interface to reset |
| 451 | """ |
| 452 | log = logging.getLogger() |
| 453 | intf_down_cmd = 'ifconfig %s down' % intf |
| 454 | intf_up_cmd = 'ifconfig %s up' % intf |
| 455 | try: |
| 456 | job.run(intf_down_cmd) |
Qi Jiang | 0ded53a | 2018-02-03 06:23:11 +0000 | [diff] [blame] | 457 | time.sleep(10) |
Qi Jiang | caca55e | 2017-10-10 23:10:40 +0000 | [diff] [blame] | 458 | job.run(intf_up_cmd) |
Qi Jiang | caca55e | 2017-10-10 23:10:40 +0000 | [diff] [blame] | 459 | log.info('{} has been reset'.format(intf)) |
| 460 | except job.Error: |
| 461 | raise Exception('No such interface') |
| 462 | |
| 463 | |
Daniel Barros | ebbf91a | 2017-10-04 01:08:40 +0000 | [diff] [blame] | 464 | def create_pkt_config(test_class): |
| 465 | """Creates the config for generating multicast packets |
| 466 | |
| 467 | Args: |
| 468 | test_class: object with all networking paramters |
| 469 | |
| 470 | Returns: |
| 471 | Dictionary with the multicast packet config |
| 472 | """ |
| 473 | addr_type = (scapy.IPV6_ADDR_LINKLOCAL |
| 474 | if test_class.ipv6_src_type == 'LINK_LOCAL' else |
| 475 | scapy.IPV6_ADDR_GLOBAL) |
| 476 | |
| 477 | mac_dst = test_class.mac_dst |
| 478 | if GET_FROM_PHONE in test_class.mac_dst: |
| 479 | mac_dst = get_phone_mac(test_class.dut) |
| 480 | |
| 481 | ipv4_dst = test_class.ipv4_dst |
| 482 | if GET_FROM_PHONE in test_class.ipv4_dst: |
| 483 | ipv4_dst = get_phone_ip(test_class.dut) |
| 484 | |
| 485 | ipv6_dst = test_class.ipv6_dst |
| 486 | if GET_FROM_PHONE in test_class.ipv6_dst: |
| 487 | ipv6_dst = get_phone_ipv6(test_class.dut) |
| 488 | |
| 489 | ipv4_gw = test_class.ipv4_gwt |
| 490 | if GET_FROM_AP in test_class.ipv4_gwt: |
| 491 | ipv4_gw = test_class.access_point.ssh_settings.hostname |
| 492 | |
| 493 | pkt_gen_config = { |
| 494 | 'interf': test_class.pkt_sender.interface, |
| 495 | 'subnet_mask': test_class.sub_mask, |
| 496 | 'src_mac': test_class.mac_src, |
| 497 | 'dst_mac': mac_dst, |
| 498 | 'src_ipv4': test_class.ipv4_src, |
| 499 | 'dst_ipv4': ipv4_dst, |
| 500 | 'src_ipv6': test_class.ipv6_src, |
| 501 | 'src_ipv6_type': addr_type, |
| 502 | 'dst_ipv6': ipv6_dst, |
| 503 | 'gw_ipv4': ipv4_gw |
| 504 | } |
| 505 | return pkt_gen_config |