blob: f709a76f79ff3ec23ae8f366a5a94733d93a0ec2 [file] [log] [blame]
Qi Jiang73b25352017-08-02 19:56:31 +00001#!/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
17import logging
18import os
19import time
Qi Jiang8f9c7582017-09-06 21:00:14 +000020from acts import asserts
Qi Jiang73b25352017-08-02 19:56:31 +000021from acts import utils
22from acts.controllers import monsoon
Qi Jiangcaca55e2017-10-10 23:10:40 +000023from acts.libs.proc import job
Qi Jiang7f6dc982018-02-24 01:29:29 +000024from acts.controllers.ap_lib import bridge_interface as bi
Qi Jiang73b25352017-08-02 19:56:31 +000025from acts.test_utils.wifi import wifi_test_utils as wutils
Qi Jiang8f9c7582017-09-06 21:00:14 +000026from bokeh.layouts import layout
Qi Jiang73b25352017-08-02 19:56:31 +000027from bokeh.models import CustomJS, ColumnDataSource
Omar El Ayachf4f132b2018-01-26 03:37:08 +000028from bokeh.models import tools as bokeh_tools
Qi Jiang73b25352017-08-02 19:56:31 +000029from bokeh.models.widgets import DataTable, TableColumn
Qi Jiang8f9c7582017-09-06 21:00:14 +000030from bokeh.plotting import figure, output_file, save
Qi Jiang73b25352017-08-02 19:56:31 +000031from acts.controllers.ap_lib import hostapd_security
32from acts.controllers.ap_lib import hostapd_ap_preset
Daniel Barros43760302017-12-06 04:16:03 +000033from acts.test_utils.bt.bt_test_utils import enable_bluetooth
34from acts.test_utils.bt.bt_test_utils import disable_bluetooth
35
Daniel Barrosfefd8c32017-08-04 15:36:40 -070036# http://www.secdev.org/projects/scapy/
37# On ubuntu, sudo pip3 install scapy-python3
38import scapy.all as scapy
Qi Jiang73b25352017-08-02 19:56:31 +000039
40SETTINGS_PAGE = "am start -n com.android.settings/.Settings"
41SCROLL_BOTTOM = "input swipe 0 2000 0 0"
42UNLOCK_SCREEN = "input keyevent 82"
43SCREENON_USB_DISABLE = "dumpsys battery unplug"
44RESET_BATTERY_STATS = "dumpsys batterystats --reset"
45AOD_OFF = "settings put secure doze_always_on 0"
46MUSIC_IQ_OFF = "pm disable-user com.google.intelligence.sense"
47# Command to disable gestures
48LIFT = "settings put secure doze_pulse_on_pick_up 0"
49DOUBLE_TAP = "settings put secure doze_pulse_on_double_tap 0"
50JUMP_TO_CAMERA = "settings put secure camera_double_tap_power_gesture_disabled 1"
51RAISE_TO_CAMERA = "settings put secure camera_lift_trigger_enabled 0"
52FLIP_CAMERA = "settings put secure camera_double_twist_to_flip_enabled 0"
53ASSIST_GESTURE = "settings put secure assist_gesture_enabled 0"
54ASSIST_GESTURE_ALERT = "settings put secure assist_gesture_silence_alerts_enabled 0"
55ASSIST_GESTURE_WAKE = "settings put secure assist_gesture_wake_enabled 0"
56SYSTEM_NAVI = "settings put secure system_navigation_keys_enabled 0"
57# End of command to disable gestures
58AUTO_TIME_OFF = "settings put global auto_time 0"
59AUTO_TIMEZONE_OFF = "settings put global auto_time_zone 0"
Qi Jiang50733382017-08-22 01:11:58 +000060FORCE_YOUTUBE_STOP = "am force-stop com.google.android.youtube"
61FORCE_DIALER_STOP = "am force-stop com.google.android.dialer"
Qi Jiang73b25352017-08-02 19:56:31 +000062IPERF_TIMEOUT = 180
Daniel Barros7cc34e82017-09-22 02:41:32 +000063THRESHOLD_TOLERANCE = 0.2
Daniel Barrosebbf91a2017-10-04 01:08:40 +000064GET_FROM_PHONE = 'get_from_dut'
65GET_FROM_AP = 'get_from_ap'
Daniel Barros35c901a2017-10-25 13:30:57 +000066PHONE_BATTERY_VOLTAGE = 4.2
67MONSOON_MAX_CURRENT = 8.0
Qi Jiang36a7c732018-02-25 05:05:40 +000068MONSOON_RECOVER_TIME_MAX = 300
Qi Jiangce0b1272018-02-28 04:39:23 +000069MONSOON_RETRY_INTERVAL = 60
70MONSOON_WAIT = 60
Qi Jiang36a7c732018-02-25 05:05:40 +000071MEASUREMENT_RETRY_COUNT = 3
Qi Jiangce0b1272018-02-28 04:39:23 +000072MIN_PERCENT_SAMPLE = 95
Qi Jiang73b25352017-08-02 19:56:31 +000073
74
75def dut_rockbottom(ad):
76 """Set the phone into Rock-bottom state.
77
78 Args:
79 ad: the target android device, AndroidDevice object
80
81 """
82 ad.log.info("Now set the device to Rockbottom State")
83 utils.require_sl4a((ad, ))
Daniel Barros7cc34e82017-09-22 02:41:32 +000084 ad.droid.connectivityToggleAirplaneMode(False)
85 time.sleep(5)
86 ad.droid.connectivityToggleAirplaneMode(True)
Qi Jiang73b25352017-08-02 19:56:31 +000087 utils.set_ambient_display(ad, False)
88 utils.set_auto_rotate(ad, False)
89 utils.set_adaptive_brightness(ad, False)
90 utils.sync_device_time(ad)
91 utils.set_location_service(ad, False)
92 utils.set_mobile_data_always_on(ad, False)
93 utils.disable_doze_light(ad)
94 utils.disable_doze(ad)
95 wutils.reset_wifi(ad)
96 wutils.wifi_toggle_state(ad, False)
Qi Jiang73b25352017-08-02 19:56:31 +000097 ad.droid.nfcDisable()
98 ad.droid.setScreenBrightness(0)
99 ad.adb.shell(AOD_OFF)
100 ad.droid.setScreenTimeout(2200)
Qi Jiang73b25352017-08-02 19:56:31 +0000101 ad.droid.wakeUpNow()
102 ad.adb.shell(LIFT)
103 ad.adb.shell(DOUBLE_TAP)
104 ad.adb.shell(JUMP_TO_CAMERA)
105 ad.adb.shell(RAISE_TO_CAMERA)
106 ad.adb.shell(FLIP_CAMERA)
107 ad.adb.shell(ASSIST_GESTURE)
108 ad.adb.shell(ASSIST_GESTURE_ALERT)
109 ad.adb.shell(ASSIST_GESTURE_WAKE)
110 ad.adb.shell(SCREENON_USB_DISABLE)
111 ad.adb.shell(UNLOCK_SCREEN)
112 ad.adb.shell(SETTINGS_PAGE)
113 ad.adb.shell(SCROLL_BOTTOM)
114 ad.adb.shell(MUSIC_IQ_OFF)
115 ad.adb.shell(AUTO_TIME_OFF)
116 ad.adb.shell(AUTO_TIMEZONE_OFF)
Qi Jiang50733382017-08-22 01:11:58 +0000117 ad.adb.shell(FORCE_YOUTUBE_STOP)
118 ad.adb.shell(FORCE_DIALER_STOP)
119 ad.droid.wakeUpNow()
Qi Jiang73b25352017-08-02 19:56:31 +0000120 ad.log.info('Device has been set to Rockbottom state')
Daniel Barros35c901a2017-10-25 13:30:57 +0000121 ad.log.info('Screen is ON')
Qi Jiang73b25352017-08-02 19:56:31 +0000122
123
Qi Jiang8f9c7582017-09-06 21:00:14 +0000124def pass_fail_check(test_class, test_result):
125 """Check the test result and decide if it passed or failed.
126 The threshold is provided in the config file
127
128 Args:
129 test_class: the specific test class where test is running
Qi Jiang7f6dc982018-02-24 01:29:29 +0000130 test_result: the average current as the test result
Qi Jiang8f9c7582017-09-06 21:00:14 +0000131 """
132 test_name = test_class.current_test_name
133 current_threshold = test_class.threshold[test_name]
Qi Jiang7f6dc982018-02-24 01:29:29 +0000134 if test_result:
135 asserts.assert_true(
136 abs(test_result - current_threshold) / current_threshold <
137 THRESHOLD_TOLERANCE,
138 ("Measured average current in [%s]: %s, which is "
139 "more than %d percent off than acceptable threshold %.2fmA") %
140 (test_name, test_result, THRESHOLD_TOLERANCE * 100,
141 current_threshold))
142 asserts.explicit_pass("Measurement finished for %s." % test_name)
143 else:
144 asserts.fail(
145 "Something happened, measurement is not complete, test failed")
Qi Jiang8f9c7582017-09-06 21:00:14 +0000146
147
Qi Jiang36a7c732018-02-25 05:05:40 +0000148def monsoon_recover(mon, time_max, retry_interval):
149 """Test loop to wait for monsoon recover from unexpected error.
150
Qi Jiangce0b1272018-02-28 04:39:23 +0000151 Wait for a certain time duration, then quit.0
Qi Jiang36a7c732018-02-25 05:05:40 +0000152 Args:
153 mon: monsoon object
154 """
155 mon_status = False
156 time_start = time.time()
157 while time.time() < time_start + time_max and not mon_status:
158 try:
159 mon.usb("on")
160 mon_status = True
161 logging.info("Monsoon recovered from unexpected error")
Qi Jiangce0b1272018-02-28 04:39:23 +0000162 time.sleep(5)
Qi Jiang36a7c732018-02-25 05:05:40 +0000163 return mon_status
164 except monsoon.MonsoonError:
165 time.sleep(retry_interval)
166 continue
167 logging.warning("Couldn't recover monsoon from unexpected error")
168 return mon_status
169
170
Qi Jiangc64e51b2018-02-09 04:02:47 +0000171def monsoon_data_collect_save(ad, mon_info, test_name):
Qi Jiang73b25352017-08-02 19:56:31 +0000172 """Current measurement and save the log file.
173
174 Collect current data using Monsoon box and return the path of the
175 log file. Take bug report if requested.
176
177 Args:
178 ad: the android device under test
179 mon_info: dict with information of monsoon measurement, including
180 monsoon device object, measurement frequency, duration and
181 offset etc.
182 test_name: current test name, used to contruct the result file name
183 bug_report: indicator to take bug report or not, 0 or 1
184 Returns:
185 data_path: the absolute path to the log file of monsoon current
186 measurement
187 avg_current: the average current of the test
188 """
Qi Jiang36a7c732018-02-25 05:05:40 +0000189
190 log = logging.getLogger()
191 tag = (test_name + '_' + ad.model + '_' + ad.build_info['build_id'])
Qi Jiangce0b1272018-02-28 04:39:23 +0000192 data_path = os.path.join(mon_info['data_path'], "%s.txt" % tag)
193 total_expected_samples = mon_info['freq'] * (
194 mon_info['duration'] + mon_info['offset'])
195 min_required_samples = total_expected_samples * MIN_PERCENT_SAMPLE / 100
196 # Retry counter
Qi Jiang36a7c732018-02-25 05:05:40 +0000197 retry = 1
Qi Jiangce0b1272018-02-28 04:39:23 +0000198 # Indicator that need to recover monsoon from serial port errors
199 need_usb_on = 0
200 # Indicator that need to re-collect data
201 need_collect_data = 1
Qi Jiang94b289a2018-03-01 03:50:15 +0000202 result = None
Qi Jiangce0b1272018-02-28 04:39:23 +0000203 while retry <= MEASUREMENT_RETRY_COUNT:
Qi Jiang36a7c732018-02-25 05:05:40 +0000204 try:
Qi Jiangce0b1272018-02-28 04:39:23 +0000205 # If need to retake data
206 if need_collect_data == 1:
207 # If monsoon needs to be recovered
208 if need_usb_on == 1:
209 time.sleep(MONSOON_WAIT)
210 mon_info['dut'].usb('on')
211 #Resets the battery status right before the test started
212 ad.adb.shell(RESET_BATTERY_STATS)
213 log.info(
214 "Starting power measurement with monsoon box, try #{}".
215 format(retry))
216 #Start the power measurement using monsoon
Qi Jiang94b289a2018-03-01 03:50:15 +0000217 mon_info['dut'].disconnect_dut()
Qi Jiangce0b1272018-02-28 04:39:23 +0000218 result = mon_info['dut'].measure_power(
219 mon_info['freq'],
220 mon_info['duration'],
221 tag=tag,
222 offset=mon_info['offset'])
Qi Jiang94b289a2018-03-01 03:50:15 +0000223 mon_info['dut'].reconnect_dut()
Qi Jiangce0b1272018-02-28 04:39:23 +0000224 # If no need to retake data but monsoon needs to be recovered
225 elif need_usb_on == 1:
226 time.sleep(MONSOON_WAIT)
227 mon_info['dut'].usb('on')
228 # Return measurement results if no error happens
Qi Jiang36a7c732018-02-25 05:05:40 +0000229 avg_current = result.average_current
230 monsoon.MonsoonData.save_to_text_file([result], data_path)
231 log.info("Power measurement done within {} try".format(retry))
Qi Jiang36a7c732018-02-25 05:05:40 +0000232 return data_path, avg_current
Qi Jiangce0b1272018-02-28 04:39:23 +0000233 # Catch monsoon errors
Qi Jiang94b289a2018-03-01 03:50:15 +0000234 except monsoon.MonsoonError:
Qi Jiangce0b1272018-02-28 04:39:23 +0000235 # If captured samples are less than min required, re-take
Qi Jiang94b289a2018-03-01 03:50:15 +0000236 if not result or len(result.__data_points) <= min_required_samples:
Qi Jiangce0b1272018-02-28 04:39:23 +0000237 need_collect_data = 1
238 log.warning(
239 'More than {} percent of samples are missing due to monsoon error. Need to take one more measurement'.
240 format(100 - MIN_PERCENT_SAMPLE))
241 mon_status = monsoon_recover(mon_info['dut'],
242 MONSOON_RECOVER_TIME_MAX,
243 MONSOON_RETRY_INTERVAL)
244 if mon_status:
245 need_usb_on = 0
246 else:
247 need_usb_on = 1
Qi Jiang36a7c732018-02-25 05:05:40 +0000248 retry += 1
249 continue
Qi Jiangce0b1272018-02-28 04:39:23 +0000250 # No need to retake data, just recover monsoon
Qi Jiang36a7c732018-02-25 05:05:40 +0000251 else:
252 log.warning(
Qi Jiangce0b1272018-02-28 04:39:23 +0000253 'Error happened trying to reconnect to DUT, no need to re-collect data'
254 )
255 need_collect_data = 0
256 mon_status = monsoon_recover(mon_info['dut'],
257 MONSOON_RECOVER_TIME_MAX,
258 MONSOON_RETRY_INTERVAL)
259 if mon_status:
260 # Here should be all set, go back to return
261 need_usb_on = 0
262 else:
263 need_usb_on = 1
264 retry += 1
265 continue
266 if retry > MEASUREMENT_RETRY_COUNT:
267 log.error(
268 'Tried our best, test still failed due to Monsoon serial port error'
Qi Jiang7f6dc982018-02-24 01:29:29 +0000269 )
Qi Jiang73b25352017-08-02 19:56:31 +0000270
271
272def monsoon_data_plot(mon_info, file_path, tag=""):
273 """Plot the monsoon current data using bokeh interactive plotting tool.
274
275 Plotting power measurement data with bokeh to generate interactive plots.
276 You can do interactive data analysis on the plot after generating with the
277 provided widgets, which make the debugging much easier. To realize that,
278 bokeh callback java scripting is used. View a sample html output file:
279 https://drive.google.com/open?id=0Bwp8Cq841VnpT2dGUUxLYWZvVjA
280
281 Args:
282 mon_info: dict with information of monsoon measurement, including
283 monsoon device object, measurement frequency, duration and
284 offset etc.
285 file_path: the path to the monsoon log file with current data
286
287 Returns:
288 plot: the plotting object of bokeh, optional, will be needed if multiple
289 plots will be combined to one html file.
290 dt: the datatable object of bokeh, optional, will be needed if multiple
291 datatables will be combined to one html file.
292 """
293
294 log = logging.getLogger()
295 log.info("Plot the power measurement data")
296 #Get results as monsoon data object from the input file
297 results = monsoon.MonsoonData.from_text_file(file_path)
298 #Decouple current and timestamp data from the monsoon object
299 current_data = []
300 timestamps = []
301 voltage = results[0].voltage
302 [current_data.extend(x.data_points) for x in results]
303 [timestamps.extend(x.timestamps) for x in results]
304 period = 1 / float(mon_info['freq'])
305 time_relative = [x * period for x in range(len(current_data))]
306 #Calculate the average current for the test
307 current_data = [x * 1000 for x in current_data]
308 avg_current = sum(current_data) / len(current_data)
309 color = ['navy'] * len(current_data)
310
311 #Preparing the data and source link for bokehn java callback
Qi Jiang94b289a2018-03-01 03:50:15 +0000312 source = ColumnDataSource(
313 data=dict(x0=time_relative, y0=current_data, color=color))
314 s2 = ColumnDataSource(
315 data=dict(
316 z0=[mon_info['duration']],
317 y0=[round(avg_current, 2)],
318 x0=[round(avg_current * voltage, 2)],
319 z1=[round(avg_current * voltage * mon_info['duration'], 2)],
320 z2=[round(avg_current * mon_info['duration'], 2)]))
Qi Jiang73b25352017-08-02 19:56:31 +0000321 #Setting up data table for the output
322 columns = [
323 TableColumn(field='z0', title='Total Duration (s)'),
324 TableColumn(field='y0', title='Average Current (mA)'),
325 TableColumn(field='x0', title='Average Power (4.2v) (mW)'),
326 TableColumn(field='z1', title='Average Energy (mW*s)'),
327 TableColumn(field='z2', title='Normalized Average Energy (mA*s)')
328 ]
329 dt = DataTable(
330 source=s2, columns=columns, width=1300, height=60, editable=True)
331
332 plot_title = file_path[file_path.rfind('/') + 1:-4] + tag
333 output_file("%s/%s.html" % (mon_info['data_path'], plot_title))
Omar El Ayachf4f132b2018-01-26 03:37:08 +0000334 TOOLS = ('box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save')
Qi Jiang73b25352017-08-02 19:56:31 +0000335 # Create a new plot with the datatable above
336 plot = figure(
337 plot_width=1300,
338 plot_height=700,
339 title=plot_title,
340 tools=TOOLS,
Omar El Ayachf4f132b2018-01-26 03:37:08 +0000341 output_backend="webgl")
342 plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="width"))
343 plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="height"))
Qi Jiang73b25352017-08-02 19:56:31 +0000344 plot.line('x0', 'y0', source=source, line_width=2)
345 plot.circle('x0', 'y0', source=source, size=0.5, fill_color='color')
346 plot.xaxis.axis_label = 'Time (s)'
347 plot.yaxis.axis_label = 'Current (mA)'
348 plot.title.text_font_size = {'value': '15pt'}
349
350 #Callback Java scripting
351 source.callback = CustomJS(
352 args=dict(mytable=dt),
353 code="""
354 var inds = cb_obj.get('selected')['1d'].indices;
355 var d1 = cb_obj.get('data');
356 var d2 = mytable.get('source').get('data');
357 ym = 0
358 ts = 0
359 d2['x0'] = []
360 d2['y0'] = []
361 d2['z1'] = []
362 d2['z2'] = []
363 d2['z0'] = []
364 min=max=d1['x0'][inds[0]]
365 if (inds.length==0) {return;}
366 for (i = 0; i < inds.length; i++) {
367 ym += d1['y0'][inds[i]]
368 d1['color'][inds[i]] = "red"
369 if (d1['x0'][inds[i]] < min) {
370 min = d1['x0'][inds[i]]}
371 if (d1['x0'][inds[i]] > max) {
372 max = d1['x0'][inds[i]]}
373 }
374 ym /= inds.length
375 ts = max - min
376 dx0 = Math.round(ym*4.2*100.0)/100.0
377 dy0 = Math.round(ym*100.0)/100.0
378 dz1 = Math.round(ym*4.2*ts*100.0)/100.0
379 dz2 = Math.round(ym*ts*100.0)/100.0
380 dz0 = Math.round(ts*1000.0)/1000.0
381 d2['z0'].push(dz0)
382 d2['x0'].push(dx0)
383 d2['y0'].push(dy0)
384 d2['z1'].push(dz1)
385 d2['z2'].push(dz2)
386 mytable.trigger('change');
387 """)
388
389 #Layout the plot and the datatable bar
390 l = layout([[dt], [plot]])
391 save(l)
392 return [plot, dt]
393
394
Qi Jiang43c80e12017-11-04 03:05:21 +0000395def change_dtim(ad, gEnableModulatedDTIM, gMaxLIModulatedDTIM=10):
Qi Jiang73b25352017-08-02 19:56:31 +0000396 """Function to change the DTIM setting in the phone.
397
398 Args:
399 ad: the target android device, AndroidDevice object
400 gEnableModulatedDTIM: Modulated DTIM, int
401 gMaxLIModulatedDTIM: Maximum modulated DTIM, int
402 """
403 serial = ad.serial
404 ini_file_phone = 'vendor/firmware/wlan/qca_cld/WCNSS_qcom_cfg.ini'
405 ini_file_local = 'local_ini_file.ini'
406 ini_pull_cmd = 'adb -s %s pull %s %s' % (serial, ini_file_phone,
407 ini_file_local)
408 ini_push_cmd = 'adb -s %s push %s %s' % (serial, ini_file_local,
409 ini_file_phone)
410 utils.exe_cmd(ini_pull_cmd)
411
412 with open(ini_file_local, 'r') as fin:
413 for line in fin:
414 if 'gEnableModulatedDTIM=' in line:
415 gEDTIM_old = line.strip('gEnableModulatedDTIM=').strip('\n')
416 if 'gMaxLIModulatedDTIM=' in line:
417 gMDTIM_old = line.strip('gMaxLIModulatedDTIM=').strip('\n')
Qi Jiang43c80e12017-11-04 03:05:21 +0000418 if int(gEDTIM_old) == gEnableModulatedDTIM and int(
419 gMDTIM_old) == gMaxLIModulatedDTIM:
Qi Jiang73b25352017-08-02 19:56:31 +0000420 ad.log.info('Current DTIM is already the desired value,'
421 'no need to reset it')
422 return
423
424 gE_old = 'gEnableModulatedDTIM=' + gEDTIM_old
425 gM_old = 'gMaxLIModulatedDTIM=' + gMDTIM_old
426 gE_new = 'gEnableModulatedDTIM=' + str(gEnableModulatedDTIM)
427 gM_new = 'gMaxLIModulatedDTIM=' + str(gMaxLIModulatedDTIM)
428
429 sed_gE = 'sed -i \'s/%s/%s/g\' %s' % (gE_old, gE_new, ini_file_local)
430 sed_gM = 'sed -i \'s/%s/%s/g\' %s' % (gM_old, gM_new, ini_file_local)
431 utils.exe_cmd(sed_gE)
432 utils.exe_cmd(sed_gM)
433
434 utils.exe_cmd('adb -s {} root'.format(serial))
435 cmd_out = utils.exe_cmd('adb -s {} remount'.format(serial))
436 if ("Permission denied").encode() in cmd_out:
437 ad.log.info('Need to disable verity first and reboot')
438 utils.exe_cmd('adb -s {} disable-verity'.format(serial))
439 time.sleep(1)
440 ad.reboot()
441 ad.log.info('Verity disabled and device back from reboot')
442 utils.exe_cmd('adb -s {} root'.format(serial))
443 utils.exe_cmd('adb -s {} remount'.format(serial))
444 time.sleep(1)
445 utils.exe_cmd(ini_push_cmd)
446 ad.log.info('ini file changes checked in and rebooting...')
447 ad.reboot()
448 ad.log.info('DTIM updated and device back from reboot')
449
450
Qi Jiangdc073e52017-12-19 19:12:35 +0000451def ap_setup(ap, network, bandwidth=80):
Qi Jiang73b25352017-08-02 19:56:31 +0000452 """Set up the whirlwind AP with provided network info.
453
454 Args:
455 ap: access_point object of the AP
456 network: dict with information of the network, including ssid, password
457 bssid, channel etc.
Qi Jiangdc073e52017-12-19 19:12:35 +0000458 bandwidth: the operation bandwidth for the AP, default 80MHz
Qi Jiang7f6dc982018-02-24 01:29:29 +0000459 Returns:
460 brconfigs: the bridge interface configs
Qi Jiang73b25352017-08-02 19:56:31 +0000461 """
Qi Jiang73b25352017-08-02 19:56:31 +0000462 log = logging.getLogger()
463 bss_settings = []
464 ssid = network[wutils.WifiEnums.SSID_KEY]
Qi Jiangcaca55e2017-10-10 23:10:40 +0000465 if "password" in network.keys():
466 password = network["password"]
467 security = hostapd_security.Security(
468 security_mode="wpa", password=password)
469 else:
470 security = hostapd_security.Security(security_mode=None, password=None)
Qi Jiang73b25352017-08-02 19:56:31 +0000471 channel = network["channel"]
Qi Jiang73b25352017-08-02 19:56:31 +0000472 config = hostapd_ap_preset.create_ap_preset(
473 channel=channel,
474 ssid=ssid,
475 security=security,
476 bss_settings=bss_settings,
Qi Jiangdc073e52017-12-19 19:12:35 +0000477 vht_bandwidth=bandwidth,
478 profile_name='whirlwind',
479 iface_wlan_2g=ap.wlan_2g,
480 iface_wlan_5g=ap.wlan_5g)
Qi Jiang7f6dc982018-02-24 01:29:29 +0000481 config_bridge = ap.generate_bridge_configs(channel)
482 brconfigs = bi.BridgeInterfaceConfigs(config_bridge[0], config_bridge[1],
483 config_bridge[2])
484 ap.bridge.startup(brconfigs)
Qi Jiang73b25352017-08-02 19:56:31 +0000485 ap.start_ap(config)
486 log.info("AP started on channel {} with SSID {}".format(channel, ssid))
Qi Jiang7f6dc982018-02-24 01:29:29 +0000487 return brconfigs
Qi Jiang73b25352017-08-02 19:56:31 +0000488
489
Omar El Ayachc01e1ef2018-02-28 04:52:49 +0000490def bokeh_plot(data_sets,
491 legends,
492 fig_property,
493 shaded_region=None,
494 output_file_path=None):
Qi Jiang73b25352017-08-02 19:56:31 +0000495 """Plot bokeh figs.
496 Args:
497 data_sets: data sets including lists of x_data and lists of y_data
498 ex: [[[x_data1], [x_data2]], [[y_data1],[y_data2]]]
499 legends: list of legend for each curve
500 fig_property: dict containing the plot property, including title,
501 lables, linewidth, circle size, etc.
Omar El Ayachc01e1ef2018-02-28 04:52:49 +0000502 shaded_region: optional dict containing data for plot shading
503 output_file_path: optional path at which to save figure
Qi Jiang73b25352017-08-02 19:56:31 +0000504 Returns:
505 plot: bokeh plot figure object
506 """
Omar El Ayachf4f132b2018-01-26 03:37:08 +0000507 TOOLS = ('box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save')
Qi Jiang73b25352017-08-02 19:56:31 +0000508 plot = figure(
509 plot_width=1300,
510 plot_height=700,
511 title=fig_property['title'],
512 tools=TOOLS,
Omar El Ayachf4f132b2018-01-26 03:37:08 +0000513 output_backend="webgl")
514 plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="width"))
515 plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="height"))
Qi Jiang73b25352017-08-02 19:56:31 +0000516 colors = [
517 'red', 'green', 'blue', 'olive', 'orange', 'salmon', 'black', 'navy',
518 'yellow', 'darkred', 'goldenrod'
519 ]
Omar El Ayachc01e1ef2018-02-28 04:52:49 +0000520 if shaded_region:
521 band_x = shaded_region["x_vector"]
522 band_x.extend(shaded_region["x_vector"][::-1])
523 band_y = shaded_region["lower_limit"]
524 band_y.extend(shaded_region["upper_limit"][::-1])
525 plot.patch(
526 band_x, band_y, color='#7570B3', line_alpha=0.1, fill_alpha=0.1)
527
Qi Jiang73b25352017-08-02 19:56:31 +0000528 for x_data, y_data, legend in zip(data_sets[0], data_sets[1], legends):
529 index_now = legends.index(legend)
530 color = colors[index_now % len(colors)]
531 plot.line(
532 x_data, y_data, legend=str(legend), line_width=3, color=color)
533 plot.circle(
534 x_data, y_data, size=10, legend=str(legend), fill_color=color)
Omar El Ayachc01e1ef2018-02-28 04:52:49 +0000535
Qi Jiang73b25352017-08-02 19:56:31 +0000536 #Plot properties
537 plot.xaxis.axis_label = fig_property['x_label']
538 plot.yaxis.axis_label = fig_property['y_label']
539 plot.legend.location = "top_right"
540 plot.legend.click_policy = "hide"
541 plot.title.text_font_size = {'value': '15pt'}
Omar El Ayachded64232017-12-08 00:53:12 +0000542 if output_file_path is not None:
543 output_file(output_file_path)
544 save(plot)
Qi Jiang73b25352017-08-02 19:56:31 +0000545 return plot
546
547
548def run_iperf_client_nonblocking(ad, server_host, extra_args=""):
549 """Start iperf client on the device with nohup.
550
551 Return status as true if iperf client start successfully.
552 And data flow information as results.
553
554 Args:
555 ad: the android device under test
556 server_host: Address of the iperf server.
557 extra_args: A string representing extra arguments for iperf client,
558 e.g. "-i 1 -t 30".
559
560 """
561 log = logging.getLogger()
562 ad.adb.shell_nb("nohup iperf3 -c {} {} &".format(server_host, extra_args))
563 log.info("IPerf client started")
564
565
566def get_wifi_rssi(ad):
567 """Get the RSSI of the device.
568
569 Args:
570 ad: the android device under test
571 Returns:
572 RSSI: the rssi level of the device
573 """
574 RSSI = ad.droid.wifiGetConnectionInfo()['rssi']
575 return RSSI
Qi Jiang50733382017-08-22 01:11:58 +0000576
577
578def get_phone_ip(ad):
579 """Get the WiFi IP address of the phone.
580
581 Args:
582 ad: the android device under test
583 Returns:
584 IP: IP address of the phone for WiFi, as a string
585 """
586 IP = ad.droid.connectivityGetIPv4Addresses('wlan0')[0]
587
588 return IP
589
590
591def get_phone_mac(ad):
592 """Get the WiFi MAC address of the phone.
593
594 Args:
595 ad: the android device under test
596 Returns:
597 mac: MAC address of the phone for WiFi, as a string
598 """
599 mac = ad.droid.wifiGetConnectionInfo()["mac_address"]
600
601 return mac
602
603
604def get_phone_ipv6(ad):
605 """Get the WiFi IPV6 address of the phone.
606
607 Args:
608 ad: the android device under test
609 Returns:
610 IPv6: IPv6 address of the phone for WiFi, as a string
611 """
612 IPv6 = ad.droid.connectivityGetLinkLocalIpv6Address('wlan0')[:-6]
613
614 return IPv6
Daniel Barrosfefd8c32017-08-04 15:36:40 -0700615
616
617def get_if_addr6(intf, address_type):
618 """Returns the Ipv6 address from a given local interface.
619
620 Returns the desired IPv6 address from the interface 'intf' in human
621 readable form. The address type is indicated by the IPv6 constants like
622 IPV6_ADDR_LINKLOCAL, IPV6_ADDR_GLOBAL, etc. If no address is found,
623 None is returned.
624
625 Args:
626 intf: desired interface name
627 address_type: addrees typle like LINKLOCAL or GLOBAL
628
629 Returns:
630 Ipv6 address of the specified interface in human readable format
631 """
632 for if_list in scapy.in6_getifaddr():
633 if if_list[2] == intf and if_list[1] == address_type:
634 return if_list[0]
635
636 return None
Daniel Barrosebbf91a2017-10-04 01:08:40 +0000637
638
Qi Jiangcaca55e2017-10-10 23:10:40 +0000639@utils.timeout(60)
640def wait_for_dhcp(intf):
641 """Wait the DHCP address assigned to desired interface.
642
643 Getting DHCP address takes time and the wait time isn't constant. Utilizing
644 utils.timeout to keep trying until success
645
646 Args:
647 intf: desired interface name
648 Returns:
649 ip: ip address of the desired interface name
650 Raise:
651 TimeoutError: After timeout, if no DHCP assigned, raise
652 """
653 log = logging.getLogger()
654 reset_host_interface(intf)
655 ip = '0.0.0.0'
656 while ip == '0.0.0.0':
657 ip = scapy.get_if_addr(intf)
Qi Jiang0ded53a2018-02-03 06:23:11 +0000658 log.info('DHCP address assigned to {} as {}'.format(intf, ip))
Qi Jiangcaca55e2017-10-10 23:10:40 +0000659 return ip
660
661
662def reset_host_interface(intf):
663 """Reset the host interface.
664
665 Args:
666 intf: the desired interface to reset
667 """
668 log = logging.getLogger()
669 intf_down_cmd = 'ifconfig %s down' % intf
670 intf_up_cmd = 'ifconfig %s up' % intf
671 try:
672 job.run(intf_down_cmd)
Qi Jiang0ded53a2018-02-03 06:23:11 +0000673 time.sleep(10)
Qi Jiangcaca55e2017-10-10 23:10:40 +0000674 job.run(intf_up_cmd)
Qi Jiangcaca55e2017-10-10 23:10:40 +0000675 log.info('{} has been reset'.format(intf))
676 except job.Error:
677 raise Exception('No such interface')
678
679
Daniel Barrosebbf91a2017-10-04 01:08:40 +0000680def create_pkt_config(test_class):
681 """Creates the config for generating multicast packets
682
683 Args:
684 test_class: object with all networking paramters
685
686 Returns:
687 Dictionary with the multicast packet config
688 """
689 addr_type = (scapy.IPV6_ADDR_LINKLOCAL
690 if test_class.ipv6_src_type == 'LINK_LOCAL' else
691 scapy.IPV6_ADDR_GLOBAL)
692
693 mac_dst = test_class.mac_dst
694 if GET_FROM_PHONE in test_class.mac_dst:
695 mac_dst = get_phone_mac(test_class.dut)
696
697 ipv4_dst = test_class.ipv4_dst
698 if GET_FROM_PHONE in test_class.ipv4_dst:
699 ipv4_dst = get_phone_ip(test_class.dut)
700
701 ipv6_dst = test_class.ipv6_dst
702 if GET_FROM_PHONE in test_class.ipv6_dst:
703 ipv6_dst = get_phone_ipv6(test_class.dut)
704
705 ipv4_gw = test_class.ipv4_gwt
706 if GET_FROM_AP in test_class.ipv4_gwt:
707 ipv4_gw = test_class.access_point.ssh_settings.hostname
708
709 pkt_gen_config = {
710 'interf': test_class.pkt_sender.interface,
711 'subnet_mask': test_class.sub_mask,
712 'src_mac': test_class.mac_src,
713 'dst_mac': mac_dst,
714 'src_ipv4': test_class.ipv4_src,
715 'dst_ipv4': ipv4_dst,
716 'src_ipv6': test_class.ipv6_src,
717 'src_ipv6_type': addr_type,
718 'dst_ipv6': ipv6_dst,
719 'gw_ipv4': ipv4_gw
720 }
721 return pkt_gen_config
722
723
724def create_monsoon_info(test_class):
725 """Creates the config dictionary for monsoon
726
727 Args:
728 test_class: object with all the parameters
729
730 Returns:
731 Dictionary with the monsoon packet config
732 """
733 mon_info = {
734 'dut': test_class.mon,
735 'freq': test_class.mon_freq,
736 'duration': test_class.mon_duration,
737 'offset': test_class.mon_offset,
738 'data_path': test_class.mon_data_path
739 }
740 return mon_info
Daniel Barros43760302017-12-06 04:16:03 +0000741
742
743def setup_phone_wireless(test_class,
744 bt_on,
745 wifi_on,
746 screen_status,
747 network=None,
748 regular_mode=False):
749 """Sets the phone in rock-bottom and in the desired wireless mode
750
751 Args:
752 test_class: the specific test class where test is running
753 bt_on: Enable/Disable BT
754 wifi_on: Enable/Disable WiFi
755 screen_status: screen ON or OFF
756 network: a dict of information for the WiFi network to connect
757 regular_mode: enable cellular data (i.e., disable airplane mode)
758 """
759 # Initialize the dut to rock-bottom state
760 dut_rockbottom(test_class.dut)
Qi Jiang93b2d5b2018-02-25 03:30:46 +0000761 brconfigs = None
Daniel Barros43760302017-12-06 04:16:03 +0000762 time.sleep(1)
763
764 if regular_mode:
765 test_class.dut.droid.connectivityToggleAirplaneMode(False)
766 utils.set_mobile_data_always_on(test_class.dut, True)
767 time.sleep(2)
768
769 # Turn ON/OFF BT
770 if bt_on == 'ON':
771 enable_bluetooth(test_class.dut.droid, test_class.dut.ed)
772 test_class.dut.log.info('BT is ON')
773 else:
774 disable_bluetooth(test_class.dut.droid)
775 test_class.dut.droid.bluetoothDisableBLE()
776 test_class.dut.log.info('BT is OFF')
777 time.sleep(2)
778
779 # Turn ON/OFF Wifi
780 if wifi_on == 'ON':
781 wutils.wifi_toggle_state(test_class.dut, True)
782 test_class.dut.log.info('WiFi is ON')
783 if network:
784 # Set attenuation and connect to AP
785 for attn in range(test_class.num_atten):
786 test_class.attenuators[attn].set_atten(
787 test_class.atten_level['zero_atten'][attn])
788 test_class.log.info('Set attenuation level to all zero')
Qi Jiang93b2d5b2018-02-25 03:30:46 +0000789 brconfigs = ap_setup(test_class.access_point, network)
Daniel Barros43760302017-12-06 04:16:03 +0000790 wutils.wifi_connect(test_class.dut, network)
791 else:
792 wutils.wifi_toggle_state(test_class.dut, False)
793 test_class.dut.log.info('WiFi is OFF')
794 time.sleep(1)
795
796 # Set the desired screen status
797 if screen_status == 'OFF':
798 test_class.dut.droid.goToSleepNow()
799 test_class.dut.log.info('Screen is OFF')
800 time.sleep(1)
Qi Jiang93b2d5b2018-02-25 03:30:46 +0000801
802 if brconfigs:
803 return brconfigs
804 else:
805 return None