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 |
| 18 | import os |
| 19 | import time |
Qi Jiang | 8f9c758 | 2017-09-06 21:00:14 +0000 | [diff] [blame] | 20 | from acts import asserts |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 21 | from acts import utils |
| 22 | from acts.controllers import monsoon |
| 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 |
| 26 | from bokeh.models.widgets import DataTable, TableColumn |
Qi Jiang | 8f9c758 | 2017-09-06 21:00:14 +0000 | [diff] [blame] | 27 | from bokeh.plotting import figure, output_file, save |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 28 | from acts.controllers.ap_lib import hostapd_security |
| 29 | from acts.controllers.ap_lib import hostapd_ap_preset |
Daniel Barros | fefd8c3 | 2017-08-04 15:36:40 -0700 | [diff] [blame^] | 30 | # http://www.secdev.org/projects/scapy/ |
| 31 | # On ubuntu, sudo pip3 install scapy-python3 |
| 32 | import scapy.all as scapy |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 33 | |
| 34 | SETTINGS_PAGE = "am start -n com.android.settings/.Settings" |
| 35 | SCROLL_BOTTOM = "input swipe 0 2000 0 0" |
| 36 | UNLOCK_SCREEN = "input keyevent 82" |
| 37 | SCREENON_USB_DISABLE = "dumpsys battery unplug" |
| 38 | RESET_BATTERY_STATS = "dumpsys batterystats --reset" |
| 39 | AOD_OFF = "settings put secure doze_always_on 0" |
| 40 | MUSIC_IQ_OFF = "pm disable-user com.google.intelligence.sense" |
| 41 | # Command to disable gestures |
| 42 | LIFT = "settings put secure doze_pulse_on_pick_up 0" |
| 43 | DOUBLE_TAP = "settings put secure doze_pulse_on_double_tap 0" |
| 44 | JUMP_TO_CAMERA = "settings put secure camera_double_tap_power_gesture_disabled 1" |
| 45 | RAISE_TO_CAMERA = "settings put secure camera_lift_trigger_enabled 0" |
| 46 | FLIP_CAMERA = "settings put secure camera_double_twist_to_flip_enabled 0" |
| 47 | ASSIST_GESTURE = "settings put secure assist_gesture_enabled 0" |
| 48 | ASSIST_GESTURE_ALERT = "settings put secure assist_gesture_silence_alerts_enabled 0" |
| 49 | ASSIST_GESTURE_WAKE = "settings put secure assist_gesture_wake_enabled 0" |
| 50 | SYSTEM_NAVI = "settings put secure system_navigation_keys_enabled 0" |
| 51 | # End of command to disable gestures |
| 52 | AUTO_TIME_OFF = "settings put global auto_time 0" |
| 53 | AUTO_TIMEZONE_OFF = "settings put global auto_time_zone 0" |
Qi Jiang | 5073338 | 2017-08-22 01:11:58 +0000 | [diff] [blame] | 54 | FORCE_YOUTUBE_STOP = "am force-stop com.google.android.youtube" |
| 55 | FORCE_DIALER_STOP = "am force-stop com.google.android.dialer" |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 56 | IPERF_TIMEOUT = 180 |
Daniel Barros | 7cc34e8 | 2017-09-22 02:41:32 +0000 | [diff] [blame] | 57 | THRESHOLD_TOLERANCE = 0.2 |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 58 | |
| 59 | |
| 60 | def dut_rockbottom(ad): |
| 61 | """Set the phone into Rock-bottom state. |
| 62 | |
| 63 | Args: |
| 64 | ad: the target android device, AndroidDevice object |
| 65 | |
| 66 | """ |
| 67 | ad.log.info("Now set the device to Rockbottom State") |
| 68 | utils.require_sl4a((ad, )) |
Daniel Barros | 7cc34e8 | 2017-09-22 02:41:32 +0000 | [diff] [blame] | 69 | ad.droid.connectivityToggleAirplaneMode(False) |
| 70 | time.sleep(5) |
| 71 | ad.droid.connectivityToggleAirplaneMode(True) |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 72 | utils.set_ambient_display(ad, False) |
| 73 | utils.set_auto_rotate(ad, False) |
| 74 | utils.set_adaptive_brightness(ad, False) |
| 75 | utils.sync_device_time(ad) |
| 76 | utils.set_location_service(ad, False) |
| 77 | utils.set_mobile_data_always_on(ad, False) |
| 78 | utils.disable_doze_light(ad) |
| 79 | utils.disable_doze(ad) |
| 80 | wutils.reset_wifi(ad) |
| 81 | wutils.wifi_toggle_state(ad, False) |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 82 | ad.droid.nfcDisable() |
| 83 | ad.droid.setScreenBrightness(0) |
| 84 | ad.adb.shell(AOD_OFF) |
| 85 | ad.droid.setScreenTimeout(2200) |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 86 | ad.droid.wakeUpNow() |
| 87 | ad.adb.shell(LIFT) |
| 88 | ad.adb.shell(DOUBLE_TAP) |
| 89 | ad.adb.shell(JUMP_TO_CAMERA) |
| 90 | ad.adb.shell(RAISE_TO_CAMERA) |
| 91 | ad.adb.shell(FLIP_CAMERA) |
| 92 | ad.adb.shell(ASSIST_GESTURE) |
| 93 | ad.adb.shell(ASSIST_GESTURE_ALERT) |
| 94 | ad.adb.shell(ASSIST_GESTURE_WAKE) |
| 95 | ad.adb.shell(SCREENON_USB_DISABLE) |
| 96 | ad.adb.shell(UNLOCK_SCREEN) |
| 97 | ad.adb.shell(SETTINGS_PAGE) |
| 98 | ad.adb.shell(SCROLL_BOTTOM) |
| 99 | ad.adb.shell(MUSIC_IQ_OFF) |
| 100 | ad.adb.shell(AUTO_TIME_OFF) |
| 101 | ad.adb.shell(AUTO_TIMEZONE_OFF) |
Qi Jiang | 5073338 | 2017-08-22 01:11:58 +0000 | [diff] [blame] | 102 | ad.adb.shell(FORCE_YOUTUBE_STOP) |
| 103 | ad.adb.shell(FORCE_DIALER_STOP) |
| 104 | ad.droid.wakeUpNow() |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 105 | ad.log.info('Device has been set to Rockbottom state') |
| 106 | |
| 107 | |
Qi Jiang | 8f9c758 | 2017-09-06 21:00:14 +0000 | [diff] [blame] | 108 | def pass_fail_check(test_class, test_result): |
| 109 | """Check the test result and decide if it passed or failed. |
| 110 | The threshold is provided in the config file |
| 111 | |
| 112 | Args: |
| 113 | test_class: the specific test class where test is running |
| 114 | avg_current: the average current as the test result |
| 115 | """ |
| 116 | test_name = test_class.current_test_name |
| 117 | current_threshold = test_class.threshold[test_name] |
| 118 | asserts.assert_true( |
| 119 | abs(test_result - current_threshold) / current_threshold < |
| 120 | THRESHOLD_TOLERANCE, |
| 121 | ("Measured average current in [%s]: %s, which is " |
| 122 | "more than %d percent off than acceptable threshold %.2fmA") % |
| 123 | (test_name, test_result, THRESHOLD_TOLERANCE * 100, current_threshold)) |
| 124 | asserts.explicit_pass("Measurement finished for %s." % test_name) |
| 125 | |
| 126 | |
Qi Jiang | 73b2535 | 2017-08-02 19:56:31 +0000 | [diff] [blame] | 127 | def monsoon_data_collect_save(ad, mon_info, test_name, bug_report): |
| 128 | """Current measurement and save the log file. |
| 129 | |
| 130 | Collect current data using Monsoon box and return the path of the |
| 131 | log file. Take bug report if requested. |
| 132 | |
| 133 | Args: |
| 134 | ad: the android device under test |
| 135 | mon_info: dict with information of monsoon measurement, including |
| 136 | monsoon device object, measurement frequency, duration and |
| 137 | offset etc. |
| 138 | test_name: current test name, used to contruct the result file name |
| 139 | bug_report: indicator to take bug report or not, 0 or 1 |
| 140 | Returns: |
| 141 | data_path: the absolute path to the log file of monsoon current |
| 142 | measurement |
| 143 | avg_current: the average current of the test |
| 144 | """ |
| 145 | log = logging.getLogger() |
| 146 | log.info("Starting power measurement with monsoon box") |
| 147 | tag = (test_name + '_' + ad.model + '_' + ad.build_info['build_id']) |
| 148 | #Resets the battery status right before the test started |
| 149 | ad.adb.shell(RESET_BATTERY_STATS) |
| 150 | begin_time = utils.get_current_human_time() |
| 151 | #Start the power measurement using monsoon |
| 152 | result = mon_info['dut'].measure_power( |
| 153 | mon_info['freq'], |
| 154 | mon_info['duration'], |
| 155 | tag=tag, |
| 156 | offset=mon_info['offset']) |
| 157 | data_path = os.path.join(mon_info['data_path'], "%s.txt" % tag) |
| 158 | avg_current = result.average_current |
| 159 | monsoon.MonsoonData.save_to_text_file([result], data_path) |
| 160 | log.info("Power measurement done") |
| 161 | if bool(bug_report) == True: |
| 162 | ad.take_bug_report(test_name, begin_time) |
| 163 | return data_path, avg_current |
| 164 | |
| 165 | |
| 166 | def monsoon_data_plot(mon_info, file_path, tag=""): |
| 167 | """Plot the monsoon current data using bokeh interactive plotting tool. |
| 168 | |
| 169 | Plotting power measurement data with bokeh to generate interactive plots. |
| 170 | You can do interactive data analysis on the plot after generating with the |
| 171 | provided widgets, which make the debugging much easier. To realize that, |
| 172 | bokeh callback java scripting is used. View a sample html output file: |
| 173 | https://drive.google.com/open?id=0Bwp8Cq841VnpT2dGUUxLYWZvVjA |
| 174 | |
| 175 | Args: |
| 176 | mon_info: dict with information of monsoon measurement, including |
| 177 | monsoon device object, measurement frequency, duration and |
| 178 | offset etc. |
| 179 | file_path: the path to the monsoon log file with current data |
| 180 | |
| 181 | Returns: |
| 182 | plot: the plotting object of bokeh, optional, will be needed if multiple |
| 183 | plots will be combined to one html file. |
| 184 | dt: the datatable object of bokeh, optional, will be needed if multiple |
| 185 | datatables will be combined to one html file. |
| 186 | """ |
| 187 | |
| 188 | log = logging.getLogger() |
| 189 | log.info("Plot the power measurement data") |
| 190 | #Get results as monsoon data object from the input file |
| 191 | results = monsoon.MonsoonData.from_text_file(file_path) |
| 192 | #Decouple current and timestamp data from the monsoon object |
| 193 | current_data = [] |
| 194 | timestamps = [] |
| 195 | voltage = results[0].voltage |
| 196 | [current_data.extend(x.data_points) for x in results] |
| 197 | [timestamps.extend(x.timestamps) for x in results] |
| 198 | period = 1 / float(mon_info['freq']) |
| 199 | time_relative = [x * period for x in range(len(current_data))] |
| 200 | #Calculate the average current for the test |
| 201 | current_data = [x * 1000 for x in current_data] |
| 202 | avg_current = sum(current_data) / len(current_data) |
| 203 | color = ['navy'] * len(current_data) |
| 204 | |
| 205 | #Preparing the data and source link for bokehn java callback |
| 206 | source = ColumnDataSource(data=dict( |
| 207 | x0=time_relative, y0=current_data, color=color)) |
| 208 | s2 = ColumnDataSource(data=dict( |
| 209 | z0=[mon_info['duration']], |
| 210 | y0=[round(avg_current, 2)], |
| 211 | x0=[round(avg_current * voltage, 2)], |
| 212 | z1=[round(avg_current * voltage * mon_info['duration'], 2)], |
| 213 | z2=[round(avg_current * mon_info['duration'], 2)])) |
| 214 | #Setting up data table for the output |
| 215 | columns = [ |
| 216 | TableColumn(field='z0', title='Total Duration (s)'), |
| 217 | TableColumn(field='y0', title='Average Current (mA)'), |
| 218 | TableColumn(field='x0', title='Average Power (4.2v) (mW)'), |
| 219 | TableColumn(field='z1', title='Average Energy (mW*s)'), |
| 220 | TableColumn(field='z2', title='Normalized Average Energy (mA*s)') |
| 221 | ] |
| 222 | dt = DataTable( |
| 223 | source=s2, columns=columns, width=1300, height=60, editable=True) |
| 224 | |
| 225 | plot_title = file_path[file_path.rfind('/') + 1:-4] + tag |
| 226 | output_file("%s/%s.html" % (mon_info['data_path'], plot_title)) |
| 227 | TOOLS = ('box_zoom,box_select,pan,crosshair,redo,undo,resize,reset,' |
| 228 | 'hover,xwheel_zoom,ywheel_zoom,save') |
| 229 | # Create a new plot with the datatable above |
| 230 | plot = figure( |
| 231 | plot_width=1300, |
| 232 | plot_height=700, |
| 233 | title=plot_title, |
| 234 | tools=TOOLS, |
| 235 | webgl=True) |
| 236 | plot.line('x0', 'y0', source=source, line_width=2) |
| 237 | plot.circle('x0', 'y0', source=source, size=0.5, fill_color='color') |
| 238 | plot.xaxis.axis_label = 'Time (s)' |
| 239 | plot.yaxis.axis_label = 'Current (mA)' |
| 240 | plot.title.text_font_size = {'value': '15pt'} |
| 241 | |
| 242 | #Callback Java scripting |
| 243 | source.callback = CustomJS( |
| 244 | args=dict(mytable=dt), |
| 245 | code=""" |
| 246 | var inds = cb_obj.get('selected')['1d'].indices; |
| 247 | var d1 = cb_obj.get('data'); |
| 248 | var d2 = mytable.get('source').get('data'); |
| 249 | ym = 0 |
| 250 | ts = 0 |
| 251 | d2['x0'] = [] |
| 252 | d2['y0'] = [] |
| 253 | d2['z1'] = [] |
| 254 | d2['z2'] = [] |
| 255 | d2['z0'] = [] |
| 256 | min=max=d1['x0'][inds[0]] |
| 257 | if (inds.length==0) {return;} |
| 258 | for (i = 0; i < inds.length; i++) { |
| 259 | ym += d1['y0'][inds[i]] |
| 260 | d1['color'][inds[i]] = "red" |
| 261 | if (d1['x0'][inds[i]] < min) { |
| 262 | min = d1['x0'][inds[i]]} |
| 263 | if (d1['x0'][inds[i]] > max) { |
| 264 | max = d1['x0'][inds[i]]} |
| 265 | } |
| 266 | ym /= inds.length |
| 267 | ts = max - min |
| 268 | dx0 = Math.round(ym*4.2*100.0)/100.0 |
| 269 | dy0 = Math.round(ym*100.0)/100.0 |
| 270 | dz1 = Math.round(ym*4.2*ts*100.0)/100.0 |
| 271 | dz2 = Math.round(ym*ts*100.0)/100.0 |
| 272 | dz0 = Math.round(ts*1000.0)/1000.0 |
| 273 | d2['z0'].push(dz0) |
| 274 | d2['x0'].push(dx0) |
| 275 | d2['y0'].push(dy0) |
| 276 | d2['z1'].push(dz1) |
| 277 | d2['z2'].push(dz2) |
| 278 | mytable.trigger('change'); |
| 279 | """) |
| 280 | |
| 281 | #Layout the plot and the datatable bar |
| 282 | l = layout([[dt], [plot]]) |
| 283 | save(l) |
| 284 | return [plot, dt] |
| 285 | |
| 286 | |
| 287 | def change_dtim(ad, gEnableModulatedDTIM, gMaxLIModulatedDTIM=6): |
| 288 | """Function to change the DTIM setting in the phone. |
| 289 | |
| 290 | Args: |
| 291 | ad: the target android device, AndroidDevice object |
| 292 | gEnableModulatedDTIM: Modulated DTIM, int |
| 293 | gMaxLIModulatedDTIM: Maximum modulated DTIM, int |
| 294 | """ |
| 295 | serial = ad.serial |
| 296 | ini_file_phone = 'vendor/firmware/wlan/qca_cld/WCNSS_qcom_cfg.ini' |
| 297 | ini_file_local = 'local_ini_file.ini' |
| 298 | ini_pull_cmd = 'adb -s %s pull %s %s' % (serial, ini_file_phone, |
| 299 | ini_file_local) |
| 300 | ini_push_cmd = 'adb -s %s push %s %s' % (serial, ini_file_local, |
| 301 | ini_file_phone) |
| 302 | utils.exe_cmd(ini_pull_cmd) |
| 303 | |
| 304 | with open(ini_file_local, 'r') as fin: |
| 305 | for line in fin: |
| 306 | if 'gEnableModulatedDTIM=' in line: |
| 307 | gEDTIM_old = line.strip('gEnableModulatedDTIM=').strip('\n') |
| 308 | if 'gMaxLIModulatedDTIM=' in line: |
| 309 | gMDTIM_old = line.strip('gMaxLIModulatedDTIM=').strip('\n') |
| 310 | if int(gEDTIM_old) == gEnableModulatedDTIM: |
| 311 | ad.log.info('Current DTIM is already the desired value,' |
| 312 | 'no need to reset it') |
| 313 | return |
| 314 | |
| 315 | gE_old = 'gEnableModulatedDTIM=' + gEDTIM_old |
| 316 | gM_old = 'gMaxLIModulatedDTIM=' + gMDTIM_old |
| 317 | gE_new = 'gEnableModulatedDTIM=' + str(gEnableModulatedDTIM) |
| 318 | gM_new = 'gMaxLIModulatedDTIM=' + str(gMaxLIModulatedDTIM) |
| 319 | |
| 320 | sed_gE = 'sed -i \'s/%s/%s/g\' %s' % (gE_old, gE_new, ini_file_local) |
| 321 | sed_gM = 'sed -i \'s/%s/%s/g\' %s' % (gM_old, gM_new, ini_file_local) |
| 322 | utils.exe_cmd(sed_gE) |
| 323 | utils.exe_cmd(sed_gM) |
| 324 | |
| 325 | utils.exe_cmd('adb -s {} root'.format(serial)) |
| 326 | cmd_out = utils.exe_cmd('adb -s {} remount'.format(serial)) |
| 327 | if ("Permission denied").encode() in cmd_out: |
| 328 | ad.log.info('Need to disable verity first and reboot') |
| 329 | utils.exe_cmd('adb -s {} disable-verity'.format(serial)) |
| 330 | time.sleep(1) |
| 331 | ad.reboot() |
| 332 | ad.log.info('Verity disabled and device back from reboot') |
| 333 | utils.exe_cmd('adb -s {} root'.format(serial)) |
| 334 | utils.exe_cmd('adb -s {} remount'.format(serial)) |
| 335 | time.sleep(1) |
| 336 | utils.exe_cmd(ini_push_cmd) |
| 337 | ad.log.info('ini file changes checked in and rebooting...') |
| 338 | ad.reboot() |
| 339 | ad.log.info('DTIM updated and device back from reboot') |
| 340 | |
| 341 | |
| 342 | def ap_setup(ap, network): |
| 343 | """Set up the whirlwind AP with provided network info. |
| 344 | |
| 345 | Args: |
| 346 | ap: access_point object of the AP |
| 347 | network: dict with information of the network, including ssid, password |
| 348 | bssid, channel etc. |
| 349 | """ |
| 350 | |
| 351 | log = logging.getLogger() |
| 352 | bss_settings = [] |
| 353 | ssid = network[wutils.WifiEnums.SSID_KEY] |
| 354 | password = network["password"] |
| 355 | channel = network["channel"] |
| 356 | security = hostapd_security.Security( |
| 357 | security_mode="wpa", password=password) |
| 358 | config = hostapd_ap_preset.create_ap_preset( |
| 359 | channel=channel, |
| 360 | ssid=ssid, |
| 361 | security=security, |
| 362 | bss_settings=bss_settings, |
| 363 | profile_name='whirlwind') |
| 364 | ap.start_ap(config) |
| 365 | log.info("AP started on channel {} with SSID {}".format(channel, ssid)) |
| 366 | |
| 367 | |
| 368 | def bokeh_plot(data_sets, legends, fig_property): |
| 369 | """Plot bokeh figs. |
| 370 | Args: |
| 371 | data_sets: data sets including lists of x_data and lists of y_data |
| 372 | ex: [[[x_data1], [x_data2]], [[y_data1],[y_data2]]] |
| 373 | legends: list of legend for each curve |
| 374 | fig_property: dict containing the plot property, including title, |
| 375 | lables, linewidth, circle size, etc. |
| 376 | Returns: |
| 377 | plot: bokeh plot figure object |
| 378 | """ |
| 379 | TOOLS = ('box_zoom,box_select,pan,crosshair,redo,undo,resize,reset,' |
| 380 | 'hover,xwheel_zoom,ywheel_zoom,save') |
| 381 | plot = figure( |
| 382 | plot_width=1300, |
| 383 | plot_height=700, |
| 384 | title=fig_property['title'], |
| 385 | tools=TOOLS, |
| 386 | webgl=True) |
| 387 | colors = [ |
| 388 | 'red', 'green', 'blue', 'olive', 'orange', 'salmon', 'black', 'navy', |
| 389 | 'yellow', 'darkred', 'goldenrod' |
| 390 | ] |
| 391 | for x_data, y_data, legend in zip(data_sets[0], data_sets[1], legends): |
| 392 | index_now = legends.index(legend) |
| 393 | color = colors[index_now % len(colors)] |
| 394 | plot.line( |
| 395 | x_data, y_data, legend=str(legend), line_width=3, color=color) |
| 396 | plot.circle( |
| 397 | x_data, y_data, size=10, legend=str(legend), fill_color=color) |
| 398 | #Plot properties |
| 399 | plot.xaxis.axis_label = fig_property['x_label'] |
| 400 | plot.yaxis.axis_label = fig_property['y_label'] |
| 401 | plot.legend.location = "top_right" |
| 402 | plot.legend.click_policy = "hide" |
| 403 | plot.title.text_font_size = {'value': '15pt'} |
| 404 | return plot |
| 405 | |
| 406 | |
| 407 | def run_iperf_client_nonblocking(ad, server_host, extra_args=""): |
| 408 | """Start iperf client on the device with nohup. |
| 409 | |
| 410 | Return status as true if iperf client start successfully. |
| 411 | And data flow information as results. |
| 412 | |
| 413 | Args: |
| 414 | ad: the android device under test |
| 415 | server_host: Address of the iperf server. |
| 416 | extra_args: A string representing extra arguments for iperf client, |
| 417 | e.g. "-i 1 -t 30". |
| 418 | |
| 419 | """ |
| 420 | log = logging.getLogger() |
| 421 | ad.adb.shell_nb("nohup iperf3 -c {} {} &".format(server_host, extra_args)) |
| 422 | log.info("IPerf client started") |
| 423 | |
| 424 | |
| 425 | def get_wifi_rssi(ad): |
| 426 | """Get the RSSI of the device. |
| 427 | |
| 428 | Args: |
| 429 | ad: the android device under test |
| 430 | Returns: |
| 431 | RSSI: the rssi level of the device |
| 432 | """ |
| 433 | RSSI = ad.droid.wifiGetConnectionInfo()['rssi'] |
| 434 | return RSSI |
Qi Jiang | 5073338 | 2017-08-22 01:11:58 +0000 | [diff] [blame] | 435 | |
| 436 | |
| 437 | def get_phone_ip(ad): |
| 438 | """Get the WiFi IP address of the phone. |
| 439 | |
| 440 | Args: |
| 441 | ad: the android device under test |
| 442 | Returns: |
| 443 | IP: IP address of the phone for WiFi, as a string |
| 444 | """ |
| 445 | IP = ad.droid.connectivityGetIPv4Addresses('wlan0')[0] |
| 446 | |
| 447 | return IP |
| 448 | |
| 449 | |
| 450 | def get_phone_mac(ad): |
| 451 | """Get the WiFi MAC address of the phone. |
| 452 | |
| 453 | Args: |
| 454 | ad: the android device under test |
| 455 | Returns: |
| 456 | mac: MAC address of the phone for WiFi, as a string |
| 457 | """ |
| 458 | mac = ad.droid.wifiGetConnectionInfo()["mac_address"] |
| 459 | |
| 460 | return mac |
| 461 | |
| 462 | |
| 463 | def get_phone_ipv6(ad): |
| 464 | """Get the WiFi IPV6 address of the phone. |
| 465 | |
| 466 | Args: |
| 467 | ad: the android device under test |
| 468 | Returns: |
| 469 | IPv6: IPv6 address of the phone for WiFi, as a string |
| 470 | """ |
| 471 | IPv6 = ad.droid.connectivityGetLinkLocalIpv6Address('wlan0')[:-6] |
| 472 | |
| 473 | return IPv6 |
Daniel Barros | fefd8c3 | 2017-08-04 15:36:40 -0700 | [diff] [blame^] | 474 | |
| 475 | |
| 476 | def get_if_addr6(intf, address_type): |
| 477 | """Returns the Ipv6 address from a given local interface. |
| 478 | |
| 479 | Returns the desired IPv6 address from the interface 'intf' in human |
| 480 | readable form. The address type is indicated by the IPv6 constants like |
| 481 | IPV6_ADDR_LINKLOCAL, IPV6_ADDR_GLOBAL, etc. If no address is found, |
| 482 | None is returned. |
| 483 | |
| 484 | Args: |
| 485 | intf: desired interface name |
| 486 | address_type: addrees typle like LINKLOCAL or GLOBAL |
| 487 | |
| 488 | Returns: |
| 489 | Ipv6 address of the specified interface in human readable format |
| 490 | """ |
| 491 | for if_list in scapy.in6_getifaddr(): |
| 492 | if if_list[2] == intf and if_list[1] == address_type: |
| 493 | return if_list[0] |
| 494 | |
| 495 | return None |