summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xacts/framework/acts/base_test.py33
-rwxr-xr-xacts/framework/acts/bin/monsoon.py131
-rwxr-xr-xacts/framework/acts/controllers/android_device.py12
-rw-r--r--acts/framework/acts/controllers/anritsu_lib/md8475_cellular_simulator.py11
-rw-r--r--acts/framework/acts/controllers/ap_lib/hostapd_ap_preset.py26
-rw-r--r--acts/framework/acts/controllers/ap_lib/hostapd_config.py28
-rwxr-xr-xacts/framework/acts/controllers/ap_lib/hostapd_constants.py1135
-rw-r--r--acts/framework/acts/controllers/ap_lib/third_party_ap_profiles/actiontec.py61
-rw-r--r--acts/framework/acts/controllers/ap_lib/third_party_ap_profiles/asus.py395
-rw-r--r--acts/framework/acts/controllers/ap_lib/third_party_ap_profiles/belkin.py108
-rw-r--r--acts/framework/acts/controllers/attenuator_lib/minicircuits/telnet.py11
-rw-r--r--acts/framework/acts/controllers/cellular_simulator.py9
-rw-r--r--acts/framework/acts/controllers/fuchsia_device.py2
-rw-r--r--acts/framework/acts/controllers/fuchsia_lib/utils_lib.py9
-rwxr-xr-xacts/framework/acts/controllers/iperf_server.py35
-rw-r--r--acts/framework/acts/controllers/monsoon.py1010
-rw-r--r--acts/framework/acts/controllers/monsoon_lib/api/common.py79
-rw-r--r--acts/framework/acts/controllers/monsoon_lib/api/hvpm/monsoon.py7
-rw-r--r--acts/framework/acts/controllers/monsoon_lib/api/lvpm_stock/monsoon.py7
-rw-r--r--acts/framework/acts/controllers/monsoon_lib/api/monsoon.py11
-rw-r--r--acts/framework/acts/controllers/rohdeschwarz_lib/cmw500.py169
-rw-r--r--acts/framework/acts/controllers/rohdeschwarz_lib/cmw500_cellular_simulator.py224
-rw-r--r--acts/framework/acts/test_utils/abstract_devices/bluetooth_handsfree_abstract_device.py44
-rw-r--r--acts/framework/acts/test_utils/bt/BtEnum.py8
-rw-r--r--acts/framework/acts/test_utils/bt/bt_power_test_utils.py205
-rw-r--r--acts/framework/acts/test_utils/bt/bt_test_utils.py248
-rw-r--r--acts/framework/acts/test_utils/bt/pts/fuchsia_pts_ics_lib.py50
-rw-r--r--acts/framework/acts/test_utils/bt/pts/pts_base_class.py2
-rw-r--r--acts/framework/acts/test_utils/gnss/gnss_test_utils.py22
-rw-r--r--acts/framework/acts/test_utils/gnss/gnss_testlog_utils.py306
-rw-r--r--acts/framework/acts/test_utils/power/PowerBTBaseTest.py135
-rw-r--r--acts/framework/acts/test_utils/power/PowerBaseTest.py247
-rw-r--r--acts/framework/acts/test_utils/power/PowerCellularLabBaseTest.py12
-rw-r--r--acts/framework/acts/test_utils/power/PowerGnssBaseTest.py85
-rw-r--r--acts/framework/acts/test_utils/power/PowerWiFiBaseTest.py12
-rw-r--r--acts/framework/acts/test_utils/power/tel_simulations/LteSimulation.py14
-rw-r--r--acts/framework/acts/test_utils/tel/tel_test_utils.py188
-rw-r--r--acts/framework/acts/test_utils/wifi/ota_chamber.py44
-rw-r--r--acts/framework/acts/test_utils/wifi/ota_sniffer.py578
-rw-r--r--acts/framework/acts/test_utils/wifi/wifi_constants.py18
-rw-r--r--acts/framework/acts/test_utils/wifi/wifi_power_test_utils.py95
-rwxr-xr-xacts/framework/acts/test_utils/wifi/wifi_test_utils.py49
-rwxr-xr-xacts/framework/setup.py1
-rwxr-xr-xacts/framework/tests/acts_import_unit_test.py7
-rwxr-xr-xacts/framework/tests/controllers/monsoon_lib/api/monsoon_test.py9
-rw-r--r--acts/tests/google/bt/pts/A2dpPtsTest.py7
-rw-r--r--acts/tests/google/bt/pts/GattPtsTest.py7
-rw-r--r--acts/tests/google/bt/pts/SdpPtsTest.py8
-rw-r--r--acts/tests/google/coex/hotspot_tests/HotspotWiFiChannelTest.py5
-rw-r--r--acts/tests/google/net/DhcpServerTest.py26
-rw-r--r--acts/tests/google/net/DnsOverTlsTest.py2
-rw-r--r--acts/tests/google/power/PowerBaselineTest.py4
-rw-r--r--acts/tests/google/power/bt/PowerBLEadvertiseTest.py63
-rw-r--r--acts/tests/google/power/bt/PowerBLEscanTest.py57
-rw-r--r--acts/tests/google/power/bt/PowerBTa2dpTest.py75
-rw-r--r--acts/tests/google/power/bt/PowerBTbaselineTest.py62
-rw-r--r--acts/tests/google/power/bt/PowerBTcalibrationTest.py65
-rw-r--r--acts/tests/google/power/bt/PowerBTidleTest.py59
-rw-r--r--acts/tests/google/power/bt/PowerBTscanTest.py85
-rw-r--r--acts/tests/google/power/gnss/PowerGnssDpoSimTest.py16
-rw-r--r--acts/tests/google/power/tel/lab/PowerTelHotspotTest.py4
-rw-r--r--acts/tests/google/power/tel/lab/PowerTelIdleTest.py4
-rw-r--r--acts/tests/google/power/tel/lab/PowerTelTrafficTest.py24
-rw-r--r--acts/tests/google/power/tel/lab/PowerTelVoiceCallTest.py4
-rw-r--r--acts/tests/google/power/wifi/PowerWiFiHotspotTest.py4
-rw-r--r--acts/tests/google/power/wifi/PowerWiFimulticastTest.py69
-rw-r--r--acts/tests/google/power/wifi/PowerWiFiroamingTest.py44
-rw-r--r--acts/tests/google/tel/live/TelLiveDataTest.py15
-rw-r--r--acts/tests/google/tel/live/TelLiveStressDataTest.py77
-rw-r--r--acts/tests/google/tel/live/TelLiveStressTest.py10
-rw-r--r--acts/tests/google/tel/live/TelLiveVoiceTest.py42
-rwxr-xr-xacts/tests/google/wifi/WifiChaosTest.py6
-rw-r--r--acts/tests/google/wifi/WifiPingTest.py27
-rw-r--r--acts/tests/google/wifi/WifiRvrTest.py31
-rw-r--r--acts/tests/google/wifi/WifiSensitivityTest.py4
-rw-r--r--acts/tests/google/wifi/WifiSoftApTest.py134
-rw-r--r--acts/tests/google/wifi/WifiStressTest.py4
77 files changed, 4290 insertions, 2646 deletions
diff --git a/acts/framework/acts/base_test.py b/acts/framework/acts/base_test.py
index c76d98a3ca..6fad51ea6c 100755
--- a/acts/framework/acts/base_test.py
+++ b/acts/framework/acts/base_test.py
@@ -277,14 +277,6 @@ class BaseTestClass(MoblyBaseTest):
A list of json serializable objects, each represents the
info of a controller object. The order of the info object
should follow that of the input objects.
- def get_post_job_info(controller_list):
- [Optional] Returns information about the controller after the
- test has run. This info is sent to test_run_summary.json's
- "Extras" key.
- Args:
- The list of controller objects created by the module
- Returns:
- A (name, data) tuple.
Registering a controller module declares a test class's dependency the
controller. If the module config exists and the module matches the
controller interface, controller objects will be instantiated with
@@ -338,28 +330,6 @@ class BaseTestClass(MoblyBaseTest):
setattr(self, module_ref_name, controllers)
return controllers
- def unregister_controllers(self):
- """Destroy controller objects and clear internal registry. Invokes
- Mobly's controller manager's unregister_controllers.
-
- This will be called upon test class teardown.
- """
- controller_modules = self._controller_manager._controller_modules
- controller_objects = self._controller_manager._controller_objects
- # Record post job info for the controller
- for name, controller_module in controller_modules.items():
- if hasattr(controller_module, 'get_post_job_info'):
- self.log.debug('Getting post job info for %s', name)
- try:
- name, value = controller_module.get_post_job_info(
- controller_objects[name])
- self.results.set_extra_data(name, value)
- self.summary_writer.dump(
- {name: value}, records.TestSummaryEntryType.USER_DATA)
- except:
- self.log.error("Fail to get post job info for %s", name)
- self._controller_manager.unregister_controllers()
-
def _record_controller_info(self):
"""Collect controller information and write to summary file."""
try:
@@ -387,8 +357,7 @@ class BaseTestClass(MoblyBaseTest):
"""Proxy function to guarantee the base implementation of teardown_class
is called.
"""
- self.teardown_class()
- self.unregister_controllers()
+ super()._teardown_class()
event_bus.post(TestClassEndEvent(self, self.results))
def _setup_test(self, test_name):
diff --git a/acts/framework/acts/bin/monsoon.py b/acts/framework/acts/bin/monsoon.py
index c43eca505e..a76b425dd9 100755
--- a/acts/framework/acts/bin/monsoon.py
+++ b/acts/framework/acts/bin/monsoon.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright 2016 - The Android Open Source Project
+# Copyright 2019 - The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -13,75 +13,100 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
"""Interface for a USB-connected Monsoon power meter
(http://msoon.com/LabEquipment/PowerMonitor/).
"""
import argparse
-import sys
-import time
-import collections
-from acts.controllers.monsoon import Monsoon
+import acts.controllers.monsoon as monsoon_controller
+
-def main(FLAGS):
+def main(args):
"""Simple command-line interface for Monsoon."""
- if FLAGS.avg and FLAGS.avg < 0:
- print("--avg must be greater than 0")
+ if args.avg and args.avg < 0:
+ print('--avg must be greater than 0')
return
- mon = Monsoon(serial=int(FLAGS.serialno[0]))
+ mon = monsoon_controller.create([int(args.serialno[0])])[0]
- if FLAGS.voltage is not None:
- mon.set_voltage(FLAGS.voltage)
+ if args.voltage is not None:
+ mon.set_voltage(args.voltage)
- if FLAGS.current is not None:
- mon.set_max_current(FLAGS.current)
+ if args.current is not None:
+ mon.set_max_current(args.current)
- if FLAGS.status:
+ if args.status:
items = sorted(mon.status.items())
- print("\n".join(["%s: %s" % item for item in items]))
+ print('\n'.join(['%s: %s' % item for item in items]))
- if FLAGS.usbpassthrough:
- mon.usb(FLAGS.usbpassthrough)
+ if args.usbpassthrough:
+ mon.usb(args.usbpassthrough)
- if FLAGS.startcurrent is not None:
- mon.set_max_init_current(FLAGS.startcurrent)
+ if args.startcurrent is not None:
+ mon.set_max_initial_current(args.startcurrent)
- if FLAGS.samples:
- # Have to sleep a bit here for monsoon to be ready to lower the rate of
- # socket read timeout.
- time.sleep(1)
- result = mon.take_samples(FLAGS.hz, FLAGS.samples,
- sample_offset=FLAGS.offset, live=True)
+ if args.samples:
+ result = mon.measure_power(
+ args.samples / args.hz,
+ measure_after_seconds=args.offset,
+ hz=args.hz,
+ output_path='monsoon_output.txt')
print(repr(result))
+
if __name__ == '__main__':
- parser = argparse.ArgumentParser(description=("This is a python utility "
- "tool to control monsoon power measurement boxes."))
- parser.add_argument("--status", action="store_true",
- help="Print power meter status.")
- parser.add_argument("-avg", "--avg", type=int, default=0,
- help="Also report average over last n data points.")
- parser.add_argument("-v", "--voltage", type=float,
- help="Set output voltage (0 for off)")
- parser.add_argument("-c", "--current", type=float,
- help="Set max output current.")
- parser.add_argument("-sc", "--startcurrent", type=float,
- help="Set max power-up/inital current.")
- parser.add_argument("-usb", "--usbpassthrough", choices=("on", "off",
- "auto"), help="USB control (on, off, auto).")
- parser.add_argument("-sp", "--samples", type=int,
- help="Collect and print this many samples")
- parser.add_argument("-hz", "--hz", type=int,
- help="Sample this many times per second.")
- parser.add_argument("-d", "--device", help="Use this /dev/ttyACM... file.")
- parser.add_argument("-sn", "--serialno", type=int, nargs=1, required=True,
- help="The serial number of the Monsoon to use.")
- parser.add_argument("--offset", type=int, nargs='?', default=0,
- help="The number of samples to discard when calculating average.")
- parser.add_argument("-r", "--ramp", action="store_true", help=("Gradually "
- "increase voltage to prevent tripping Monsoon overvoltage"))
- args = parser.parse_args()
- main(args)
+ parser = argparse.ArgumentParser(
+ description='This is a python utility tool to control monsoon power '
+ 'measurement boxes.')
+ parser.add_argument(
+ '--status', action='store_true', help='Print power meter status.')
+ parser.add_argument(
+ '-avg',
+ '--avg',
+ type=int,
+ default=0,
+ help='Also report average over last n data points.')
+ parser.add_argument(
+ '-v', '--voltage', type=float, help='Set output voltage (0 for off)')
+ parser.add_argument(
+ '-c', '--current', type=float, help='Set max output current.')
+ parser.add_argument(
+ '-sc',
+ '--startcurrent',
+ type=float,
+ help='Set max power-up/initial current.')
+ parser.add_argument(
+ '-usb',
+ '--usbpassthrough',
+ choices=('on', 'off', 'auto'),
+ help='USB control (on, off, auto).')
+ parser.add_argument(
+ '-sp',
+ '--samples',
+ type=int,
+ help='Collect and print this many samples')
+ parser.add_argument(
+ '-hz', '--hz', type=int, help='Sample this many times per second.')
+ parser.add_argument('-d', '--device', help='Use this /dev/ttyACM... file.')
+ parser.add_argument(
+ '-sn',
+ '--serialno',
+ type=int,
+ nargs=1,
+ required=True,
+ help='The serial number of the Monsoon to use.')
+ parser.add_argument(
+ '--offset',
+ type=int,
+ nargs='?',
+ default=0,
+ help='The number of samples to discard when calculating average.')
+ parser.add_argument(
+ '-r',
+ '--ramp',
+ action='store_true',
+ help='Gradually increase voltage to prevent tripping Monsoon '
+ 'overvoltage.')
+ arguments = parser.parse_args()
+ main(arguments)
diff --git a/acts/framework/acts/controllers/android_device.py b/acts/framework/acts/controllers/android_device.py
index 9f03c93e50..16671fa621 100755
--- a/acts/framework/acts/controllers/android_device.py
+++ b/acts/framework/acts/controllers/android_device.py
@@ -137,18 +137,6 @@ def get_info(ads):
return device_info
-def get_post_job_info(ads):
- """Returns the tracked build id to test_run_summary.json
-
- Args:
- ads: A list of AndroidDevice objects.
-
- Returns:
- A dict consisting of {'build_id': ads[0].build_info}
- """
- return 'Build Info', ads[0].build_info
-
-
def _start_services_on_ads(ads):
"""Starts long running services on multiple AndroidDevice objects.
diff --git a/acts/framework/acts/controllers/anritsu_lib/md8475_cellular_simulator.py b/acts/framework/acts/controllers/anritsu_lib/md8475_cellular_simulator.py
index 23fee412cb..08db20b52f 100644
--- a/acts/framework/acts/controllers/anritsu_lib/md8475_cellular_simulator.py
+++ b/acts/framework/acts/controllers/anritsu_lib/md8475_cellular_simulator.py
@@ -177,6 +177,17 @@ class MD8475CellularSimulator(cc.AbstractCellularSimulator):
else:
self.bts[bts_index].tbs_pattern = 'OFF'
+ def set_lte_rrc_state_change_timer(self, enabled, time=10):
+ """ Configures the LTE RRC state change timer.
+
+ Args:
+ enabled: a boolean indicating if the timer should be on or off.
+ time: time in seconds for the timer to expire
+ """
+ self.anritsu.set_lte_rrc_status_change(enabled)
+ if enabled:
+ self.anritsu.set_lte_rrc_status_change_timer(time)
+
def set_band(self, bts_index, band):
""" Sets the right duplex mode before switching to a new band.
diff --git a/acts/framework/acts/controllers/ap_lib/hostapd_ap_preset.py b/acts/framework/acts/controllers/ap_lib/hostapd_ap_preset.py
index 930d525574..a7d89a7e5b 100644
--- a/acts/framework/acts/controllers/ap_lib/hostapd_ap_preset.py
+++ b/acts/framework/acts/controllers/ap_lib/hostapd_ap_preset.py
@@ -13,6 +13,8 @@
# limitations under the License.
import acts.controllers.ap_lib.third_party_ap_profiles.actiontec as actiontec
+import acts.controllers.ap_lib.third_party_ap_profiles.asus as asus
+import acts.controllers.ap_lib.third_party_ap_profiles.belkin as belkin
from acts.controllers.ap_lib import hostapd_config
from acts.controllers.ap_lib import hostapd_constants
@@ -243,10 +245,32 @@ def create_ap_preset(profile_name='whirlwind',
security=security)
elif profile_name == 'actiontec_mi424wr':
config = actiontec.actiontec_mi424wr(iface_wlan_2g=iface_wlan_2g,
- iface_wlan_5g=iface_wlan_5g,
channel=channel,
ssid=ssid,
security=security)
+ elif profile_name == 'asus_rtac66u':
+ config = asus.asus_rtac66u(iface_wlan_2g=iface_wlan_2g,
+ iface_wlan_5g=iface_wlan_5g,
+ channel=channel,
+ ssid=ssid,
+ security=security)
+ elif profile_name == 'asus_rtac86u':
+ config = asus.asus_rtac86u(iface_wlan_2g=iface_wlan_2g,
+ iface_wlan_5g=iface_wlan_5g,
+ channel=channel,
+ ssid=ssid,
+ security=security)
+ elif profile_name == 'asus_rtac5300':
+ config = asus.asus_rtac5300(iface_wlan_2g=iface_wlan_2g,
+ iface_wlan_5g=iface_wlan_5g,
+ channel=channel,
+ ssid=ssid,
+ security=security)
+ elif profile_name == 'belkin_f9k1001v5':
+ config = belkin.belkin_f9k1001v5(iface_wlan_2g=iface_wlan_2g,
+ channel=channel,
+ ssid=ssid,
+ security=security)
else:
raise ValueError('Invalid ap model specified (%s)' % profile_name)
diff --git a/acts/framework/acts/controllers/ap_lib/hostapd_config.py b/acts/framework/acts/controllers/ap_lib/hostapd_config.py
index 2ee6f0dadf..64ea022641 100644
--- a/acts/framework/acts/controllers/ap_lib/hostapd_config.py
+++ b/acts/framework/acts/controllers/ap_lib/hostapd_config.py
@@ -71,7 +71,6 @@ class HostapdConfig(object):
All the settings for a router that are not part of an ssid.
"""
-
def _get_11ac_center_channel_from_channel(self, channel):
"""Returns the center channel of the selected channel band based
on the channel and channel bandwidth provided.
@@ -452,10 +451,9 @@ class HostapdConfig(object):
logging.warning(
'No channel bandwidth specified. Using 80MHz for 11ac.')
self._vht_oper_chwidth = 1
- if not vht_channel_width == 20:
- if not vht_center_channel:
- self._vht_oper_centr_freq_seg0_idx = self._get_11ac_center_channel_from_channel(
- self.channel)
+ if not vht_channel_width == 20 and not vht_center_channel:
+ self._vht_oper_centr_freq_seg0_idx = self._get_11ac_center_channel_from_channel(
+ self.channel)
else:
self._vht_oper_centr_freq_seg0_idx = vht_center_channel
self._ac_capabilities = set(ac_capabilities)
@@ -473,16 +471,16 @@ class HostapdConfig(object):
self._bss_lookup[bss.name] = bss
def __repr__(self):
- return ('%s(mode=%r, channel=%r, frequency=%r, '
- 'n_capabilities=%r, beacon_interval=%r, '
- 'dtim_period=%r, frag_threshold=%r, ssid=%r, bssid=%r, '
- 'wmm_enabled=%r, security_config=%r, '
- 'spectrum_mgmt_required=%r)' %
- (self.__class__.__name__, self._mode, self.channel,
- self.frequency, self._n_capabilities, self._beacon_interval,
- self._dtim_period, self._frag_threshold, self._ssid,
- self._bssid, self._wmm_enabled, self._security,
- self._spectrum_mgmt_required))
+ return (
+ '%s(mode=%r, channel=%r, frequency=%r, '
+ 'n_capabilities=%r, beacon_interval=%r, '
+ 'dtim_period=%r, frag_threshold=%r, ssid=%r, bssid=%r, '
+ 'wmm_enabled=%r, security_config=%r, '
+ 'spectrum_mgmt_required=%r)' %
+ (self.__class__.__name__, self._mode, self.channel, self.frequency,
+ self._n_capabilities, self._beacon_interval, self._dtim_period,
+ self._frag_threshold, self._ssid, self._bssid, self._wmm_enabled,
+ self._security, self._spectrum_mgmt_required))
def supports_channel(self, value):
"""Check whether channel is supported by the current hardware mode.
diff --git a/acts/framework/acts/controllers/ap_lib/hostapd_constants.py b/acts/framework/acts/controllers/ap_lib/hostapd_constants.py
index 6fe3530d4b..4139ed0595 100755
--- a/acts/framework/acts/controllers/ap_lib/hostapd_constants.py
+++ b/acts/framework/acts/controllers/ap_lib/hostapd_constants.py
@@ -153,7 +153,7 @@ N_CAPABILITY_LSIG_TXOP_PROT = object()
N_CAPABILITY_40_INTOLERANT = object()
N_CAPABILITY_MAX_AMSDU_7935 = object()
N_CAPABILITY_DELAY_BLOCK_ACK = object()
-N_CAPABILITY_SMPS_STATIC=object()
+N_CAPABILITY_SMPS_STATIC = object()
N_CAPABILITY_SMPS_DYNAMIC = object()
N_CAPABILITIES_MAPPING = {
N_CAPABILITY_LDPC: '[LDPC]',
@@ -262,12 +262,12 @@ VHT_CHANNEL = {
HT40_ALLOW_MAP = {
N_CAPABILITY_HT40_MINUS_CHANNELS:
tuple(
- itertools.chain(
- range(6, 14), range(40, 65, 8), range(104, 137, 8), [153, 161])),
+ itertools.chain(range(6, 14), range(40, 65, 8), range(104, 137, 8),
+ [153, 161])),
N_CAPABILITY_HT40_PLUS_CHANNELS:
tuple(
- itertools.chain(
- range(1, 8), range(36, 61, 8), range(100, 133, 8), [149, 157]))
+ itertools.chain(range(1, 8), range(36, 61, 8), range(100, 133, 8),
+ [149, 157]))
}
PMF_SUPPORT_DISABLED = 0
@@ -280,15 +280,17 @@ DRIVER_NAME = 'nl80211'
CENTER_CHANNEL_MAP = {
VHT_CHANNEL_WIDTH_40: {
- 'delta': 2,
+ 'delta':
+ 2,
'channels': ((36, 40), (44, 48), (52, 56), (60, 64), (100, 104),
(108, 112), (116, 120), (124, 128), (132, 136),
(140, 144), (149, 153), (147, 161))
},
VHT_CHANNEL_WIDTH_80: {
- 'delta': 6,
- 'channels': ((36, 48), (52, 64), (100, 112), (116, 128), (132, 144),
- (149, 161))
+ 'delta':
+ 6,
+ 'channels':
+ ((36, 48), (52, 64), (100, 112), (116, 128), (132, 144), (149, 161))
},
VHT_CHANNEL_WIDTH_160: {
'delta': 14,
@@ -296,26 +298,24 @@ CENTER_CHANNEL_MAP = {
}
}
-OFDM_DATA_RATES = {
- 'supported_rates': '60 90 120 180 240 360 480 540'
-}
+OFDM_DATA_RATES = {'supported_rates': '60 90 120 180 240 360 480 540'}
-CCK_DATA_RATES = {
- 'supported_rates': '10 20 55 11'
-}
+CCK_DATA_RATES = {'supported_rates': '10 20 55 11'}
-OFDM_ONLY_BASIC_RATES = {
- 'basic_rates': '60 120 240'
-}
+OFDM_ONLY_BASIC_RATES = {'basic_rates': '60 120 240'}
-CCK_AND_OFDM_BASIC_RATES = {
- 'basic_rates': '10 20 55 11'
-}
+CCK_AND_OFDM_BASIC_RATES = {'basic_rates': '10 20 55 11'}
WEP_AUTH = {
- 'open': {'auth_algs': 1},
- 'shared': {'auth_algs': 2},
- 'open_and_shared': {'auth_algs': 3}
+ 'open': {
+ 'auth_algs': 1
+ },
+ 'shared': {
+ 'auth_algs': 2
+ },
+ 'open_and_shared': {
+ 'auth_algs': 3
+ }
}
WMM_11B_DEFAULT_PARAMS = {
@@ -326,7 +326,7 @@ WMM_11B_DEFAULT_PARAMS = {
'wmm_ac_be_aifs': 3,
'wmm_ac_be_cwmin': 5,
'wmm_ac_be_cwmax': 7,
- 'wmm_ac_be_txop_limit':0,
+ 'wmm_ac_be_txop_limit': 0,
'wmm_ac_vi_aifs': 2,
'wmm_ac_vi_cwmin': 4,
'wmm_ac_vi_cwmax': 5,
@@ -345,7 +345,7 @@ WMM_PHYS_11A_11G_11N_11AC_DEFAULT_PARAMS = {
'wmm_ac_be_aifs': 3,
'wmm_ac_be_cwmin': 4,
'wmm_ac_be_cwmax': 10,
- 'wmm_ac_be_txop_limit':0,
+ 'wmm_ac_be_txop_limit': 0,
'wmm_ac_vi_aifs': 2,
'wmm_ac_vi_cwmin': 3,
'wmm_ac_vi_cwmax': 4,
@@ -364,7 +364,7 @@ WMM_NON_DEFAULT_PARAMS = {
'wmm_ac_be_aifs': 2,
'wmm_ac_be_cwmin': 2,
'wmm_ac_be_cwmax': 8,
- 'wmm_ac_be_txop_limit':0,
+ 'wmm_ac_be_txop_limit': 0,
'wmm_ac_vi_aifs': 1,
'wmm_ac_vi_cwmin': 7,
'wmm_ac_vi_cwmax': 10,
@@ -380,295 +380,812 @@ WMM_ACM_BE = {'wmm_ac_be_acm': 1}
WMM_ACM_VI = {'wmm_ac_vi_acm': 1}
WMM_ACM_VO = {'wmm_ac_vo_acm': 1}
-UTF_8_SSID = {
- 'utf8_ssid': 1
-}
+UAPSD_ENABLED = {'uapsd_advertisement_enabled': 1}
+
+UTF_8_SSID = {'utf8_ssid': 1}
VENDOR_IE = {
- 'correct_length_beacon':
- {'vendor_elements': 'dd0411223301'} ,
- 'too_short_length_beacon':
- {'vendor_elements': 'dd0311223301'},
- 'too_long_length_beacon':
- {'vendor_elements': 'dd0511223301'},
- 'zero_length_beacon_with_data':
- {'vendor_elements': 'dd0011223301'},
- 'zero_length_beacon_without_data':
- {'vendor_elements': 'dd00'},
- 'simliar_to_wpa':
- {'vendor_elements': 'dd040050f203'},
- 'correct_length_association_response':
- {'assocresp_elements=': 'dd0411223301'},
- 'too_short_length_association_response':
- {'assocresp_elements=': 'dd0311223301'},
- 'too_long_length_association_response':
- {'assocresp_elements=': 'dd0511223301'},
- 'zero_length_association_response_with_data':
- {'assocresp_elements': 'dd0011223301'},
- 'zero_length_association_response_without_data':
- {'assocresp_elements': 'dd00'}
+ 'correct_length_beacon': {
+ 'vendor_elements': 'dd0411223301'
+ },
+ 'too_short_length_beacon': {
+ 'vendor_elements': 'dd0311223301'
+ },
+ 'too_long_length_beacon': {
+ 'vendor_elements': 'dd0511223301'
+ },
+ 'zero_length_beacon_with_data': {
+ 'vendor_elements': 'dd0011223301'
+ },
+ 'zero_length_beacon_without_data': {
+ 'vendor_elements': 'dd00'
+ },
+ 'simliar_to_wpa': {
+ 'vendor_elements': 'dd040050f203'
+ },
+ 'correct_length_association_response': {
+ 'assocresp_elements=': 'dd0411223301'
+ },
+ 'too_short_length_association_response': {
+ 'assocresp_elements=': 'dd0311223301'
+ },
+ 'too_long_length_association_response': {
+ 'assocresp_elements=': 'dd0511223301'
+ },
+ 'zero_length_association_response_with_data': {
+ 'assocresp_elements': 'dd0011223301'
+ },
+ 'zero_length_association_response_without_data': {
+ 'assocresp_elements': 'dd00'
+ }
}
-ENABLE_IEEE80211D = {
- 'ieee80211d': 1
-}
+ENABLE_IEEE80211D = {'ieee80211d': 1}
COUNTRY_STRING = {
- 'ALL': {'country3': '0x20'},
- 'OUTDOOR': {'country3': '0x4f'},
- 'INDOOR': {'country3': '0x49'},
- 'NONCOUNTRY': {'country3': '0x58'},
- 'GLOBAL': {'country3': '0x04'}
+ 'ALL': {
+ 'country3': '0x20'
+ },
+ 'OUTDOOR': {
+ 'country3': '0x4f'
+ },
+ 'INDOOR': {
+ 'country3': '0x49'
+ },
+ 'NONCOUNTRY': {
+ 'country3': '0x58'
+ },
+ 'GLOBAL': {
+ 'country3': '0x04'
+ }
}
COUNTRY_CODE = {
- 'AFGHANISTAN': {'country_code': 'AF'},
- 'ALAND_ISLANDS': {'country_code': 'AX'},
- 'ALBANIA': {'country_code': 'AL'},
- 'ALGERIA': {'country_code': 'DZ'},
- 'AMERICAN_SAMOA': {'country_code': 'AS'},
- 'ANDORRA': {'country_code': 'AD'},
- 'ANGOLA': {'country_code': 'AO'},
- 'ANGUILLA': {'country_code': 'AI'},
- 'ANTARCTICA': {'country_code': 'AQ'},
- 'ANTIGUA_AND_BARBUDA': {'country_code': 'AG'},
- 'ARGENTINA': {'country_code': 'AR'},
- 'ARMENIA': {'country_code': 'AM'},
- 'ARUBA': {'country_code': 'AW'},
- 'AUSTRALIA': {'country_code': 'AU'},
- 'AUSTRIA': {'country_code': 'AT'},
- 'AZERBAIJAN': {'country_code': 'AZ'},
- 'BAHAMAS': {'country_code': 'BS'},
- 'BAHRAIN': {'country_code': 'BH'},
- 'BANGLADESH': {'country_code': 'BD'},
- 'BARBADOS': {'country_code': 'BB'},
- 'BELARUS': {'country_code': 'BY'},
- 'BELGIUM': {'country_code': 'BE'},
- 'BELIZE': {'country_code': 'BZ'},
- 'BENIN': {'country_code': 'BJ'},
- 'BERMUDA': {'country_code': 'BM'},
- 'BHUTAN': {'country_code': 'BT'},
- 'BOLIVIA': {'country_code': 'BO'},
- 'BONAIRE': {'country_code': 'BQ'},
- 'BOSNIA_AND_HERZEGOVINA': {'country_code': 'BA'},
- 'BOTSWANA': {'country_code': 'BW'},
- 'BOUVET_ISLAND': {'country_code': 'BV'},
- 'BRAZIL': {'country_code': 'BR'},
- 'BRITISH_INDIAN_OCEAN_TERRITORY': {'country_code': 'IO'},
- 'BRUNEI_DARUSSALAM': {'country_code': 'BN'},
- 'BULGARIA': {'country_code': 'BG'},
- 'BURKINA_FASO': {'country_code': 'BF'},
- 'BURUNDI': {'country_code': 'BI'},
- 'CAMBODIA': {'country_code': 'KH'},
- 'CAMEROON': {'country_code': 'CM'},
- 'CANADA': {'country_code': 'CA'},
- 'CAPE_VERDE': {'country_code': 'CV'},
- 'CAYMAN_ISLANDS': {'country_code': 'KY'},
- 'CENTRAL_AFRICAN_REPUBLIC': {'country_code': 'CF'},
- 'CHAD': {'country_code': 'TD'},
- 'CHILE': {'country_code': 'CL'},
- 'CHINA': {'country_code': 'CN'},
- 'CHRISTMAS_ISLAND': {'country_code': 'CX'},
- 'COCOS_ISLANDS': {'country_code': 'CC'},
- 'COLOMBIA': {'country_code': 'CO'},
- 'COMOROS': {'country_code': 'KM'},
- 'CONGO': {'country_code': 'CG'},
- 'DEMOCRATIC_REPUBLIC_CONGO': {'country_code': 'CD'},
- 'COOK_ISLANDS': {'country_code': 'CK'},
- 'COSTA_RICA': {'country_code': 'CR'},
- 'COTE_D_IVOIRE': {'country_code': 'CI'},
- 'CROATIA': {'country_code': 'HR'},
- 'CUBA': {'country_code': 'CU'},
- 'CURACAO': {'country_code': 'CW'},
- 'CYPRUS': {'country_code': 'CY'},
- 'CZECH_REPUBLIC': {'country_code': 'CZ'},
- 'DENMARK': {'country_code': 'DK'},
- 'DJIBOUTI': {'country_code': 'DJ'},
- 'DOMINICA': {'country_code': 'DM'},
- 'DOMINICAN_REPUBLIC': {'country_code': 'DO'},
- 'ECUADOR': {'country_code': 'EC'},
- 'EGYPT': {'country_code': 'EG'},
- 'EL_SALVADOR': {'country_code': 'SV'},
- 'EQUATORIAL_GUINEA': {'country_code': 'GQ'},
- 'ERITREA': {'country_code': 'ER'},
- 'ESTONIA': {'country_code': 'EE'},
- 'ETHIOPIA': {'country_code': 'ET'},
- 'FALKLAND_ISLANDS_(MALVINAS)': {'country_code': 'FK'},
- 'FAROE_ISLANDS': {'country_code': 'FO'},
- 'FIJI': {'country_code': 'FJ'},
- 'FINLAND': {'country_code': 'FI'},
- 'FRANCE': {'country_code': 'FR'},
- 'FRENCH_GUIANA': {'country_code': 'GF'},
- 'FRENCH_POLYNESIA': {'country_code': 'PF'},
- 'FRENCH_SOUTHERN_TERRITORIES': {'country_code': 'TF'},
- 'GABON': {'country_code': 'GA'},
- 'GAMBIA': {'country_code': 'GM'},
- 'GEORGIA': {'country_code': 'GE'},
- 'GERMANY': {'country_code': 'DE'},
- 'GHANA': {'country_code': 'GH'},
- 'GIBRALTAR': {'country_code': 'GI'},
- 'GREECE': {'country_code': 'GR'},
- 'GREENLAND': {'country_code': 'GL'},
- 'GRENADA': {'country_code': 'GD'},
- 'GUADELOUPE': {'country_code': 'GP'},
- 'GUAM': {'country_code': 'GU'},
- 'GUATEMALA': {'country_code': 'GT'},
- 'GUERNSEY': {'country_code': 'GG'},
- 'GUINEA': {'country_code': 'GN'},
- 'GUINEA-BISSAU': {'country_code': 'GW'},
- 'GUYANA': {'country_code': 'GY'},
- 'HAITI': {'country_code': 'HT'},
- 'HEARD_ISLAND_AND_MCDONALD_ISLANDS': {'country_code': 'HM'},
- 'VATICAN_CITY_STATE': {'country_code': 'VA'},
- 'HONDURAS': {'country_code': 'HN'},
- 'HONG_KONG': {'country_code': 'HK'},
- 'HUNGARY': {'country_code': 'HU'},
- 'ICELAND': {'country_code': 'IS'},
- 'INDIA': {'country_code': 'IN'},
- 'INDONESIA': {'country_code': 'ID'},
- 'IRAN': {'country_code': 'IR'},
- 'IRAQ': {'country_code': 'IQ'},
- 'IRELAND': {'country_code': 'IE'},
- 'ISLE_OF_MAN': {'country_code': 'IM'},
- 'ISRAEL': {'country_code': 'IL'},
- 'ITALY': {'country_code': 'IT'},
- 'JAMAICA': {'country_code': 'JM'},
- 'JAPAN': {'country_code': 'JP'},
- 'JERSEY': {'country_code': 'JE'},
- 'JORDAN': {'country_code': 'JO'},
- 'KAZAKHSTAN': {'country_code': 'KZ'},
- 'KENYA': {'country_code': 'KE'},
- 'KIRIBATI': {'country_code': 'KI'},
- 'DEMOCRATIC_PEOPLE_S_REPUBLIC_OF_KOREA': {'country_code': 'KP'},
- 'REPUBLIC_OF_KOREA': {'country_code': 'KR'},
- 'KUWAIT': {'country_code': 'KW'},
- 'KYRGYZSTAN': {'country_code': 'KG'},
- 'LAO': {'country_code': 'LA'},
- 'LATVIA': {'country_code': 'LV'},
- 'LEBANON': {'country_code': 'LB'},
- 'LESOTHO': {'country_code': 'LS'},
- 'LIBERIA': {'country_code': 'LR'},
- 'LIBYA': {'country_code': 'LY'},
- 'LIECHTENSTEIN': {'country_code': 'LI'},
- 'LITHUANIA': {'country_code': 'LT'},
- 'LUXEMBOURG': {'country_code': 'LU'},
- 'MACAO': {'country_code': 'MO'},
- 'MACEDONIA': {'country_code': 'MK'},
- 'MADAGASCAR': {'country_code': 'MG'},
- 'MALAWI': {'country_code': 'MW'},
- 'MALAYSIA': {'country_code': 'MY'},
- 'MALDIVES': {'country_code': 'MV'},
- 'MALI': {'country_code': 'ML'},
- 'MALTA': {'country_code': 'MT'},
- 'MARSHALL_ISLANDS': {'country_code': 'MH'},
- 'MARTINIQUE': {'country_code': 'MQ'},
- 'MAURITANIA': {'country_code': 'MR'},
- 'MAURITIUS': {'country_code': 'MU'},
- 'MAYOTTE': {'country_code': 'YT'},
- 'MEXICO': {'country_code': 'MX'},
- 'MICRONESIA': {'country_code': 'FM'},
- 'MOLDOVA': {'country_code': 'MD'},
- 'MONACO': {'country_code': 'MC'},
- 'MONGOLIA': {'country_code': 'MN'},
- 'MONTENEGRO': {'country_code': 'ME'},
- 'MONTSERRAT': {'country_code': 'MS'},
- 'MOROCCO': {'country_code': 'MA'},
- 'MOZAMBIQUE': {'country_code': 'MZ'},
- 'MYANMAR': {'country_code': 'MM'},
- 'NAMIBIA': {'country_code': 'NA'},
- 'NAURU': {'country_code': 'NR'},
- 'NEPAL': {'country_code': 'NP'},
- 'NETHERLANDS': {'country_code': 'NL'},
- 'NEW_CALEDONIA': {'country_code': 'NC'},
- 'NEW_ZEALAND': {'country_code': 'NZ'},
- 'NICARAGUA': {'country_code': 'NI'},
- 'NIGER': {'country_code': 'NE'},
- 'NIGERIA': {'country_code': 'NG'},
- 'NIUE': {'country_code': 'NU'},
- 'NORFOLK_ISLAND': {'country_code': 'NF'},
- 'NORTHERN_MARIANA_ISLANDS': {'country_code': 'MP'},
- 'NORWAY': {'country_code': 'NO'},
- 'OMAN': {'country_code': 'OM'},
- 'PAKISTAN': {'country_code': 'PK'},
- 'PALAU': {'country_code': 'PW'},
- 'PALESTINE': {'country_code': 'PS'},
- 'PANAMA': {'country_code': 'PA'},
- 'PAPUA_NEW_GUINEA': {'country_code': 'PG'},
- 'PARAGUAY': {'country_code': 'PY'},
- 'PERU': {'country_code': 'PE'},
- 'PHILIPPINES': {'country_code': 'PH'},
- 'PITCAIRN': {'country_code': 'PN'},
- 'POLAND': {'country_code': 'PL'},
- 'PORTUGAL': {'country_code': 'PT'},
- 'PUERTO_RICO': {'country_code': 'PR'},
- 'QATAR': {'country_code': 'QA'},
- 'RÉUNION': {'country_code': 'RE'},
- 'ROMANIA': {'country_code': 'RO'},
- 'RUSSIAN_FEDERATION': {'country_code': 'RU'},
- 'RWANDA': {'country_code': 'RW'},
- 'SAINT_BARTHELEMY': {'country_code': 'BL'},
- 'SAINT_KITTS_AND_NEVIS': {'country_code': 'KN'},
- 'SAINT_LUCIA': {'country_code': 'LC'},
- 'SAINT_MARTIN': {'country_code': 'MF'},
- 'SAINT_PIERRE_AND_MIQUELON': {'country_code': 'PM'},
- 'SAINT_VINCENT_AND_THE_GRENADINES': {'country_code': 'VC'},
- 'SAMOA': {'country_code': 'WS'},
- 'SAN_MARINO': {'country_code': 'SM'},
- 'SAO_TOME_AND_PRINCIPE': {'country_code': 'ST'},
- 'SAUDI_ARABIA': {'country_code': 'SA'},
- 'SENEGAL': {'country_code': 'SN'},
- 'SERBIA': {'country_code': 'RS'},
- 'SEYCHELLES': {'country_code': 'SC'},
- 'SIERRA_LEONE': {'country_code': 'SL'},
- 'SINGAPORE': {'country_code': 'SG'},
- 'SINT_MAARTEN': {'country_code': 'SX'},
- 'SLOVAKIA': {'country_code': 'SK'},
- 'SLOVENIA': {'country_code': 'SI'},
- 'SOLOMON_ISLANDS': {'country_code': 'SB'},
- 'SOMALIA': {'country_code': 'SO'},
- 'SOUTH_AFRICA': {'country_code': 'ZA'},
- 'SOUTH_GEORGIA': {'country_code': 'GS'},
- 'SOUTH_SUDAN': {'country_code': 'SS'},
- 'SPAIN': {'country_code': 'ES'},
- 'SRI_LANKA': {'country_code': 'LK'},
- 'SUDAN': {'country_code': 'SD'},
- 'SURINAME': {'country_code': 'SR'},
- 'SVALBARD_AND_JAN_MAYEN': {'country_code': 'SJ'},
- 'SWAZILAND': {'country_code': 'SZ'},
- 'SWEDEN': {'country_code': 'SE'},
- 'SWITZERLAND': {'country_code': 'CH'},
- 'SYRIAN_ARAB_REPUBLIC': {'country_code': 'SY'},
- 'TAIWAN': {'country_code': 'TW'},
- 'TAJIKISTAN': {'country_code': 'TJ'},
- 'TANZANIA': {'country_code': 'TZ'},
- 'THAILAND': {'country_code': 'TH'},
- 'TIMOR-LESTE': {'country_code': 'TL'},
- 'TOGO': {'country_code': 'TG'},
- 'TOKELAU': {'country_code': 'TK'},
- 'TONGA': {'country_code': 'TO'},
- 'TRINIDAD_AND_TOBAGO': {'country_code': 'TT'},
- 'TUNISIA': {'country_code': 'TN'},
- 'TURKEY': {'country_code': 'TR'},
- 'TURKMENISTAN': {'country_code': 'TM'},
- 'TURKS_AND_CAICOS_ISLANDS': {'country_code': 'TC'},
- 'TUVALU': {'country_code': 'TV'},
- 'UGANDA': {'country_code': 'UG'},
- 'UKRAINE': {'country_code': 'UA'},
- 'UNITED_ARAB_EMIRATES': {'country_code': 'AE'},
- 'UNITED_KINGDOM': {'country_code': 'GB'},
- 'UNITED_STATES': {'country_code': 'US'},
- 'UNITED_STATES_MINOR_OUTLYING_ISLANDS': {'country_code': 'UM'},
- 'URUGUAY': {'country_code': 'UY'},
- 'UZBEKISTAN': {'country_code': 'UZ'},
- 'VANUATU': {'country_code': 'VU'},
- 'VENEZUELA': {'country_code': 'VE'},
- 'VIETNAM': {'country_code': 'VN'},
- 'VIRGIN_ISLANDS_BRITISH': {'country_code': 'VG'},
- 'VIRGIN_ISLANDS_US': {'country_code': 'VI'},
- 'WALLIS_AND_FUTUNA': {'country_code': 'WF'},
- 'WESTERN_SAHARA': {'country_code': 'EH'},
- 'YEMEN': {'country_code': 'YE'},
- 'ZAMBIA': {'country_code': 'ZM'},
- 'ZIMBABWE': {'country_code': 'ZW'},
- 'NON_COUNTRY': {'country_code': 'XX'}
+ 'AFGHANISTAN': {
+ 'country_code': 'AF'
+ },
+ 'ALAND_ISLANDS': {
+ 'country_code': 'AX'
+ },
+ 'ALBANIA': {
+ 'country_code': 'AL'
+ },
+ 'ALGERIA': {
+ 'country_code': 'DZ'
+ },
+ 'AMERICAN_SAMOA': {
+ 'country_code': 'AS'
+ },
+ 'ANDORRA': {
+ 'country_code': 'AD'
+ },
+ 'ANGOLA': {
+ 'country_code': 'AO'
+ },
+ 'ANGUILLA': {
+ 'country_code': 'AI'
+ },
+ 'ANTARCTICA': {
+ 'country_code': 'AQ'
+ },
+ 'ANTIGUA_AND_BARBUDA': {
+ 'country_code': 'AG'
+ },
+ 'ARGENTINA': {
+ 'country_code': 'AR'
+ },
+ 'ARMENIA': {
+ 'country_code': 'AM'
+ },
+ 'ARUBA': {
+ 'country_code': 'AW'
+ },
+ 'AUSTRALIA': {
+ 'country_code': 'AU'
+ },
+ 'AUSTRIA': {
+ 'country_code': 'AT'
+ },
+ 'AZERBAIJAN': {
+ 'country_code': 'AZ'
+ },
+ 'BAHAMAS': {
+ 'country_code': 'BS'
+ },
+ 'BAHRAIN': {
+ 'country_code': 'BH'
+ },
+ 'BANGLADESH': {
+ 'country_code': 'BD'
+ },
+ 'BARBADOS': {
+ 'country_code': 'BB'
+ },
+ 'BELARUS': {
+ 'country_code': 'BY'
+ },
+ 'BELGIUM': {
+ 'country_code': 'BE'
+ },
+ 'BELIZE': {
+ 'country_code': 'BZ'
+ },
+ 'BENIN': {
+ 'country_code': 'BJ'
+ },
+ 'BERMUDA': {
+ 'country_code': 'BM'
+ },
+ 'BHUTAN': {
+ 'country_code': 'BT'
+ },
+ 'BOLIVIA': {
+ 'country_code': 'BO'
+ },
+ 'BONAIRE': {
+ 'country_code': 'BQ'
+ },
+ 'BOSNIA_AND_HERZEGOVINA': {
+ 'country_code': 'BA'
+ },
+ 'BOTSWANA': {
+ 'country_code': 'BW'
+ },
+ 'BOUVET_ISLAND': {
+ 'country_code': 'BV'
+ },
+ 'BRAZIL': {
+ 'country_code': 'BR'
+ },
+ 'BRITISH_INDIAN_OCEAN_TERRITORY': {
+ 'country_code': 'IO'
+ },
+ 'BRUNEI_DARUSSALAM': {
+ 'country_code': 'BN'
+ },
+ 'BULGARIA': {
+ 'country_code': 'BG'
+ },
+ 'BURKINA_FASO': {
+ 'country_code': 'BF'
+ },
+ 'BURUNDI': {
+ 'country_code': 'BI'
+ },
+ 'CAMBODIA': {
+ 'country_code': 'KH'
+ },
+ 'CAMEROON': {
+ 'country_code': 'CM'
+ },
+ 'CANADA': {
+ 'country_code': 'CA'
+ },
+ 'CAPE_VERDE': {
+ 'country_code': 'CV'
+ },
+ 'CAYMAN_ISLANDS': {
+ 'country_code': 'KY'
+ },
+ 'CENTRAL_AFRICAN_REPUBLIC': {
+ 'country_code': 'CF'
+ },
+ 'CHAD': {
+ 'country_code': 'TD'
+ },
+ 'CHILE': {
+ 'country_code': 'CL'
+ },
+ 'CHINA': {
+ 'country_code': 'CN'
+ },
+ 'CHRISTMAS_ISLAND': {
+ 'country_code': 'CX'
+ },
+ 'COCOS_ISLANDS': {
+ 'country_code': 'CC'
+ },
+ 'COLOMBIA': {
+ 'country_code': 'CO'
+ },
+ 'COMOROS': {
+ 'country_code': 'KM'
+ },
+ 'CONGO': {
+ 'country_code': 'CG'
+ },
+ 'DEMOCRATIC_REPUBLIC_CONGO': {
+ 'country_code': 'CD'
+ },
+ 'COOK_ISLANDS': {
+ 'country_code': 'CK'
+ },
+ 'COSTA_RICA': {
+ 'country_code': 'CR'
+ },
+ 'COTE_D_IVOIRE': {
+ 'country_code': 'CI'
+ },
+ 'CROATIA': {
+ 'country_code': 'HR'
+ },
+ 'CUBA': {
+ 'country_code': 'CU'
+ },
+ 'CURACAO': {
+ 'country_code': 'CW'
+ },
+ 'CYPRUS': {
+ 'country_code': 'CY'
+ },
+ 'CZECH_REPUBLIC': {
+ 'country_code': 'CZ'
+ },
+ 'DENMARK': {
+ 'country_code': 'DK'
+ },
+ 'DJIBOUTI': {
+ 'country_code': 'DJ'
+ },
+ 'DOMINICA': {
+ 'country_code': 'DM'
+ },
+ 'DOMINICAN_REPUBLIC': {
+ 'country_code': 'DO'
+ },
+ 'ECUADOR': {
+ 'country_code': 'EC'
+ },
+ 'EGYPT': {
+ 'country_code': 'EG'
+ },
+ 'EL_SALVADOR': {
+ 'country_code': 'SV'
+ },
+ 'EQUATORIAL_GUINEA': {
+ 'country_code': 'GQ'
+ },
+ 'ERITREA': {
+ 'country_code': 'ER'
+ },
+ 'ESTONIA': {
+ 'country_code': 'EE'
+ },
+ 'ETHIOPIA': {
+ 'country_code': 'ET'
+ },
+ 'FALKLAND_ISLANDS_(MALVINAS)': {
+ 'country_code': 'FK'
+ },
+ 'FAROE_ISLANDS': {
+ 'country_code': 'FO'
+ },
+ 'FIJI': {
+ 'country_code': 'FJ'
+ },
+ 'FINLAND': {
+ 'country_code': 'FI'
+ },
+ 'FRANCE': {
+ 'country_code': 'FR'
+ },
+ 'FRENCH_GUIANA': {
+ 'country_code': 'GF'
+ },
+ 'FRENCH_POLYNESIA': {
+ 'country_code': 'PF'
+ },
+ 'FRENCH_SOUTHERN_TERRITORIES': {
+ 'country_code': 'TF'
+ },
+ 'GABON': {
+ 'country_code': 'GA'
+ },
+ 'GAMBIA': {
+ 'country_code': 'GM'
+ },
+ 'GEORGIA': {
+ 'country_code': 'GE'
+ },
+ 'GERMANY': {
+ 'country_code': 'DE'
+ },
+ 'GHANA': {
+ 'country_code': 'GH'
+ },
+ 'GIBRALTAR': {
+ 'country_code': 'GI'
+ },
+ 'GREECE': {
+ 'country_code': 'GR'
+ },
+ 'GREENLAND': {
+ 'country_code': 'GL'
+ },
+ 'GRENADA': {
+ 'country_code': 'GD'
+ },
+ 'GUADELOUPE': {
+ 'country_code': 'GP'
+ },
+ 'GUAM': {
+ 'country_code': 'GU'
+ },
+ 'GUATEMALA': {
+ 'country_code': 'GT'
+ },
+ 'GUERNSEY': {
+ 'country_code': 'GG'
+ },
+ 'GUINEA': {
+ 'country_code': 'GN'
+ },
+ 'GUINEA-BISSAU': {
+ 'country_code': 'GW'
+ },
+ 'GUYANA': {
+ 'country_code': 'GY'
+ },
+ 'HAITI': {
+ 'country_code': 'HT'
+ },
+ 'HEARD_ISLAND_AND_MCDONALD_ISLANDS': {
+ 'country_code': 'HM'
+ },
+ 'VATICAN_CITY_STATE': {
+ 'country_code': 'VA'
+ },
+ 'HONDURAS': {
+ 'country_code': 'HN'
+ },
+ 'HONG_KONG': {
+ 'country_code': 'HK'
+ },
+ 'HUNGARY': {
+ 'country_code': 'HU'
+ },
+ 'ICELAND': {
+ 'country_code': 'IS'
+ },
+ 'INDIA': {
+ 'country_code': 'IN'
+ },
+ 'INDONESIA': {
+ 'country_code': 'ID'
+ },
+ 'IRAN': {
+ 'country_code': 'IR'
+ },
+ 'IRAQ': {
+ 'country_code': 'IQ'
+ },
+ 'IRELAND': {
+ 'country_code': 'IE'
+ },
+ 'ISLE_OF_MAN': {
+ 'country_code': 'IM'
+ },
+ 'ISRAEL': {
+ 'country_code': 'IL'
+ },
+ 'ITALY': {
+ 'country_code': 'IT'
+ },
+ 'JAMAICA': {
+ 'country_code': 'JM'
+ },
+ 'JAPAN': {
+ 'country_code': 'JP'
+ },
+ 'JERSEY': {
+ 'country_code': 'JE'
+ },
+ 'JORDAN': {
+ 'country_code': 'JO'
+ },
+ 'KAZAKHSTAN': {
+ 'country_code': 'KZ'
+ },
+ 'KENYA': {
+ 'country_code': 'KE'
+ },
+ 'KIRIBATI': {
+ 'country_code': 'KI'
+ },
+ 'DEMOCRATIC_PEOPLE_S_REPUBLIC_OF_KOREA': {
+ 'country_code': 'KP'
+ },
+ 'REPUBLIC_OF_KOREA': {
+ 'country_code': 'KR'
+ },
+ 'KUWAIT': {
+ 'country_code': 'KW'
+ },
+ 'KYRGYZSTAN': {
+ 'country_code': 'KG'
+ },
+ 'LAO': {
+ 'country_code': 'LA'
+ },
+ 'LATVIA': {
+ 'country_code': 'LV'
+ },
+ 'LEBANON': {
+ 'country_code': 'LB'
+ },
+ 'LESOTHO': {
+ 'country_code': 'LS'
+ },
+ 'LIBERIA': {
+ 'country_code': 'LR'
+ },
+ 'LIBYA': {
+ 'country_code': 'LY'
+ },
+ 'LIECHTENSTEIN': {
+ 'country_code': 'LI'
+ },
+ 'LITHUANIA': {
+ 'country_code': 'LT'
+ },
+ 'LUXEMBOURG': {
+ 'country_code': 'LU'
+ },
+ 'MACAO': {
+ 'country_code': 'MO'
+ },
+ 'MACEDONIA': {
+ 'country_code': 'MK'
+ },
+ 'MADAGASCAR': {
+ 'country_code': 'MG'
+ },
+ 'MALAWI': {
+ 'country_code': 'MW'
+ },
+ 'MALAYSIA': {
+ 'country_code': 'MY'
+ },
+ 'MALDIVES': {
+ 'country_code': 'MV'
+ },
+ 'MALI': {
+ 'country_code': 'ML'
+ },
+ 'MALTA': {
+ 'country_code': 'MT'
+ },
+ 'MARSHALL_ISLANDS': {
+ 'country_code': 'MH'
+ },
+ 'MARTINIQUE': {
+ 'country_code': 'MQ'
+ },
+ 'MAURITANIA': {
+ 'country_code': 'MR'
+ },
+ 'MAURITIUS': {
+ 'country_code': 'MU'
+ },
+ 'MAYOTTE': {
+ 'country_code': 'YT'
+ },
+ 'MEXICO': {
+ 'country_code': 'MX'
+ },
+ 'MICRONESIA': {
+ 'country_code': 'FM'
+ },
+ 'MOLDOVA': {
+ 'country_code': 'MD'
+ },
+ 'MONACO': {
+ 'country_code': 'MC'
+ },
+ 'MONGOLIA': {
+ 'country_code': 'MN'
+ },
+ 'MONTENEGRO': {
+ 'country_code': 'ME'
+ },
+ 'MONTSERRAT': {
+ 'country_code': 'MS'
+ },
+ 'MOROCCO': {
+ 'country_code': 'MA'
+ },
+ 'MOZAMBIQUE': {
+ 'country_code': 'MZ'
+ },
+ 'MYANMAR': {
+ 'country_code': 'MM'
+ },
+ 'NAMIBIA': {
+ 'country_code': 'NA'
+ },
+ 'NAURU': {
+ 'country_code': 'NR'
+ },
+ 'NEPAL': {
+ 'country_code': 'NP'
+ },
+ 'NETHERLANDS': {
+ 'country_code': 'NL'
+ },
+ 'NEW_CALEDONIA': {
+ 'country_code': 'NC'
+ },
+ 'NEW_ZEALAND': {
+ 'country_code': 'NZ'
+ },
+ 'NICARAGUA': {
+ 'country_code': 'NI'
+ },
+ 'NIGER': {
+ 'country_code': 'NE'
+ },
+ 'NIGERIA': {
+ 'country_code': 'NG'
+ },
+ 'NIUE': {
+ 'country_code': 'NU'
+ },
+ 'NORFOLK_ISLAND': {
+ 'country_code': 'NF'
+ },
+ 'NORTHERN_MARIANA_ISLANDS': {
+ 'country_code': 'MP'
+ },
+ 'NORWAY': {
+ 'country_code': 'NO'
+ },
+ 'OMAN': {
+ 'country_code': 'OM'
+ },
+ 'PAKISTAN': {
+ 'country_code': 'PK'
+ },
+ 'PALAU': {
+ 'country_code': 'PW'
+ },
+ 'PALESTINE': {
+ 'country_code': 'PS'
+ },
+ 'PANAMA': {
+ 'country_code': 'PA'
+ },
+ 'PAPUA_NEW_GUINEA': {
+ 'country_code': 'PG'
+ },
+ 'PARAGUAY': {
+ 'country_code': 'PY'
+ },
+ 'PERU': {
+ 'country_code': 'PE'
+ },
+ 'PHILIPPINES': {
+ 'country_code': 'PH'
+ },
+ 'PITCAIRN': {
+ 'country_code': 'PN'
+ },
+ 'POLAND': {
+ 'country_code': 'PL'
+ },
+ 'PORTUGAL': {
+ 'country_code': 'PT'
+ },
+ 'PUERTO_RICO': {
+ 'country_code': 'PR'
+ },
+ 'QATAR': {
+ 'country_code': 'QA'
+ },
+ 'RÉUNION': {
+ 'country_code': 'RE'
+ },
+ 'ROMANIA': {
+ 'country_code': 'RO'
+ },
+ 'RUSSIAN_FEDERATION': {
+ 'country_code': 'RU'
+ },
+ 'RWANDA': {
+ 'country_code': 'RW'
+ },
+ 'SAINT_BARTHELEMY': {
+ 'country_code': 'BL'
+ },
+ 'SAINT_KITTS_AND_NEVIS': {
+ 'country_code': 'KN'
+ },
+ 'SAINT_LUCIA': {
+ 'country_code': 'LC'
+ },
+ 'SAINT_MARTIN': {
+ 'country_code': 'MF'
+ },
+ 'SAINT_PIERRE_AND_MIQUELON': {
+ 'country_code': 'PM'
+ },
+ 'SAINT_VINCENT_AND_THE_GRENADINES': {
+ 'country_code': 'VC'
+ },
+ 'SAMOA': {
+ 'country_code': 'WS'
+ },
+ 'SAN_MARINO': {
+ 'country_code': 'SM'
+ },
+ 'SAO_TOME_AND_PRINCIPE': {
+ 'country_code': 'ST'
+ },
+ 'SAUDI_ARABIA': {
+ 'country_code': 'SA'
+ },
+ 'SENEGAL': {
+ 'country_code': 'SN'
+ },
+ 'SERBIA': {
+ 'country_code': 'RS'
+ },
+ 'SEYCHELLES': {
+ 'country_code': 'SC'
+ },
+ 'SIERRA_LEONE': {
+ 'country_code': 'SL'
+ },
+ 'SINGAPORE': {
+ 'country_code': 'SG'
+ },
+ 'SINT_MAARTEN': {
+ 'country_code': 'SX'
+ },
+ 'SLOVAKIA': {
+ 'country_code': 'SK'
+ },
+ 'SLOVENIA': {
+ 'country_code': 'SI'
+ },
+ 'SOLOMON_ISLANDS': {
+ 'country_code': 'SB'
+ },
+ 'SOMALIA': {
+ 'country_code': 'SO'
+ },
+ 'SOUTH_AFRICA': {
+ 'country_code': 'ZA'
+ },
+ 'SOUTH_GEORGIA': {
+ 'country_code': 'GS'
+ },
+ 'SOUTH_SUDAN': {
+ 'country_code': 'SS'
+ },
+ 'SPAIN': {
+ 'country_code': 'ES'
+ },
+ 'SRI_LANKA': {
+ 'country_code': 'LK'
+ },
+ 'SUDAN': {
+ 'country_code': 'SD'
+ },
+ 'SURINAME': {
+ 'country_code': 'SR'
+ },
+ 'SVALBARD_AND_JAN_MAYEN': {
+ 'country_code': 'SJ'
+ },
+ 'SWAZILAND': {
+ 'country_code': 'SZ'
+ },
+ 'SWEDEN': {
+ 'country_code': 'SE'
+ },
+ 'SWITZERLAND': {
+ 'country_code': 'CH'
+ },
+ 'SYRIAN_ARAB_REPUBLIC': {
+ 'country_code': 'SY'
+ },
+ 'TAIWAN': {
+ 'country_code': 'TW'
+ },
+ 'TAJIKISTAN': {
+ 'country_code': 'TJ'
+ },
+ 'TANZANIA': {
+ 'country_code': 'TZ'
+ },
+ 'THAILAND': {
+ 'country_code': 'TH'
+ },
+ 'TIMOR-LESTE': {
+ 'country_code': 'TL'
+ },
+ 'TOGO': {
+ 'country_code': 'TG'
+ },
+ 'TOKELAU': {
+ 'country_code': 'TK'
+ },
+ 'TONGA': {
+ 'country_code': 'TO'
+ },
+ 'TRINIDAD_AND_TOBAGO': {
+ 'country_code': 'TT'
+ },
+ 'TUNISIA': {
+ 'country_code': 'TN'
+ },
+ 'TURKEY': {
+ 'country_code': 'TR'
+ },
+ 'TURKMENISTAN': {
+ 'country_code': 'TM'
+ },
+ 'TURKS_AND_CAICOS_ISLANDS': {
+ 'country_code': 'TC'
+ },
+ 'TUVALU': {
+ 'country_code': 'TV'
+ },
+ 'UGANDA': {
+ 'country_code': 'UG'
+ },
+ 'UKRAINE': {
+ 'country_code': 'UA'
+ },
+ 'UNITED_ARAB_EMIRATES': {
+ 'country_code': 'AE'
+ },
+ 'UNITED_KINGDOM': {
+ 'country_code': 'GB'
+ },
+ 'UNITED_STATES': {
+ 'country_code': 'US'
+ },
+ 'UNITED_STATES_MINOR_OUTLYING_ISLANDS': {
+ 'country_code': 'UM'
+ },
+ 'URUGUAY': {
+ 'country_code': 'UY'
+ },
+ 'UZBEKISTAN': {
+ 'country_code': 'UZ'
+ },
+ 'VANUATU': {
+ 'country_code': 'VU'
+ },
+ 'VENEZUELA': {
+ 'country_code': 'VE'
+ },
+ 'VIETNAM': {
+ 'country_code': 'VN'
+ },
+ 'VIRGIN_ISLANDS_BRITISH': {
+ 'country_code': 'VG'
+ },
+ 'VIRGIN_ISLANDS_US': {
+ 'country_code': 'VI'
+ },
+ 'WALLIS_AND_FUTUNA': {
+ 'country_code': 'WF'
+ },
+ 'WESTERN_SAHARA': {
+ 'country_code': 'EH'
+ },
+ 'YEMEN': {
+ 'country_code': 'YE'
+ },
+ 'ZAMBIA': {
+ 'country_code': 'ZM'
+ },
+ 'ZIMBABWE': {
+ 'country_code': 'ZW'
+ },
+ 'NON_COUNTRY': {
+ 'country_code': 'XX'
+ }
}
diff --git a/acts/framework/acts/controllers/ap_lib/third_party_ap_profiles/actiontec.py b/acts/framework/acts/controllers/ap_lib/third_party_ap_profiles/actiontec.py
index e2452d63d1..1e576ebded 100644
--- a/acts/framework/acts/controllers/ap_lib/third_party_ap_profiles/actiontec.py
+++ b/acts/framework/acts/controllers/ap_lib/third_party_ap_profiles/actiontec.py
@@ -98,7 +98,6 @@ def actiontec_pk5000(iface_wlan_2g=None,
def actiontec_mi424wr(iface_wlan_2g=None,
- iface_wlan_5g=None,
channel=None,
security=None,
ssid=None):
@@ -106,7 +105,6 @@ def actiontec_mi424wr(iface_wlan_2g=None,
"""A simulated implementation of an Actiontec MI424WR AP.
Args:
iface_wlan_2g: The 2.4Ghz interface of the test AP.
- iface_wlan_5g: The 5Ghz interface of the test AP.
channel: What channel to use (2.4Ghz or 5Ghz).
security: A security profile.
ssid: The network name.
@@ -114,37 +112,34 @@ def actiontec_mi424wr(iface_wlan_2g=None,
A hostapd config.
Differences from real MI424WR:
- ERP Information:
+ HT Capabilities:
MI424WR:
- Use Protection: Set
+ HT Rx STBC: Support for 1, 2, and 3
Simulated:
- Use Protection: Not Set
- HT Capabilities:
- MI424WR: [TX-STBC][DSSS_CCK-40][RX-STBC123]
- Simulated: [TX-STBC][DSSS_CCK-40][RX-STBC1]
+ HT Rx STBC: Support for 1
HT Information:
MI424WR:
RIFS: Premitted
- Reserved (Subset 2): 0x1
Simulated:
RIFS: Prohibited
- Reserved (Subset 2): 0x0
"""
- if not iface_wlan_2g or not iface_wlan_5g:
- raise ValueError('WLAN interface for 2G and/or 5G is missing.')
-
- if (iface_wlan_2g not in hostapd_constants.INTERFACE_2G_LIST
- or iface_wlan_5g not in hostapd_constants.INTERFACE_5G_LIST):
+ if channel > 11:
+ raise ValueError('The Actiontec MI424WR does not support 5Ghz. '
+ 'Invalid channel (%s)' % channel)
+ if (iface_wlan_2g not in hostapd_constants.INTERFACE_2G_LIST):
raise ValueError('Invalid interface name was passed.')
- rates = {'supported_rates': '10 20 55 110 60 90 120 180 240 360 480 540'}
-
- if channel <= 11:
- interface = iface_wlan_2g
- rates['basic_rates'] = '10 20 55 110'
- else:
- interface = iface_wlan_5g
- rates['basic_rates'] = '60 120 240'
+ if security:
+ if security.security_mode is hostapd_constants.WPA2:
+ if not security.wpa2_cipher == 'CCMP':
+ raise ValueError('The mock Actiontec MI424WR only supports a '
+ 'WPA2 unicast and multicast cipher of CCMP.'
+ 'Invalid cipher mode (%s)' %
+ security.security.wpa2_cipher)
+ else:
+ raise ValueError('The mock Actiontec MI424WR only supports WPA2. '
+ 'Invalid security mode (%s)' %
+ security.security_mode)
n_capabilities = [
hostapd_constants.N_CAPABILITY_TX_STBC,
@@ -152,6 +147,11 @@ def actiontec_mi424wr(iface_wlan_2g=None,
hostapd_constants.N_CAPABILITY_RX_STBC1
]
+ rates = {
+ 'basic_rates': '10 20 55 110',
+ 'supported_rates': '10 20 55 110 60 90 120 180 240 360 480 540'
+ }
+
# Proprietary Atheros Communication: Adv Capability IE
# Proprietary Atheros Communication: Unknown IE
# Country Info: US Only IE
@@ -161,26 +161,15 @@ def actiontec_mi424wr(iface_wlan_2g=None,
'dd0a00037f04010000000000'
'0706555320010b1b'
}
- additional_params = _merge_dicts(rates, vendor_elements)
- if security:
- if security.security_mode is hostapd_constants.WPA2:
- if not security.wpa2_cipher == 'CCMP':
- raise ValueError('The mock Actiontec MI424WR only supports a '
- 'WPA2 unicast and multicast cipher of CCMP.'
- 'Invalid cipher mode (%s)' %
- security.security.wpa2_cipher)
- else:
- raise ValueError('The mock Actiontec MI424WR only supports WPA2. '
- 'Invalid security mode (%s)' %
- security.security_mode)
+ additional_params = _merge_dicts(rates, vendor_elements)
config = hostapd_config.HostapdConfig(
ssid=ssid,
channel=channel,
hidden=False,
security=security,
- interface=interface,
+ interface=iface_wlan_2g,
mode=hostapd_constants.MODE_11N_MIXED,
force_wmm=True,
beacon_interval=100,
diff --git a/acts/framework/acts/controllers/ap_lib/third_party_ap_profiles/asus.py b/acts/framework/acts/controllers/ap_lib/third_party_ap_profiles/asus.py
new file mode 100644
index 0000000000..8ab68c5563
--- /dev/null
+++ b/acts/framework/acts/controllers/ap_lib/third_party_ap_profiles/asus.py
@@ -0,0 +1,395 @@
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from acts.controllers.ap_lib import hostapd_config
+from acts.controllers.ap_lib import hostapd_constants
+
+
+def _merge_dicts(*dict_args):
+ result = {}
+ for dictionary in dict_args:
+ result.update(dictionary)
+ return result
+
+
+def asus_rtac66u(iface_wlan_2g=None,
+ iface_wlan_5g=None,
+ channel=None,
+ security=None,
+ ssid=None):
+ # TODO(b/143104825): Permit RIFS once it is supported
+ """A simulated implementation of an Asus RTAC66U AP.
+ Args:
+ iface_wlan_2g: The 2.4Ghz interface of the test AP.
+ iface_wlan_5g: The 5Ghz interface of the test AP.
+ channel: What channel to use.
+ security: A security profile. Must be none or WPA2 as this is what is
+ supported by the RTAC66U.
+ ssid: Network name
+ Returns:
+ A hostapd config
+ Differences from real RTAC66U:
+ 2.4 GHz:
+ Rates:
+ RTAC66U:
+ Supported: 1, 2, 5.5, 11, 18, 24, 36, 54
+ Extended: 6, 9, 12, 48
+ Simulated:
+ Supported: 1, 2, 5.5, 11, 6, 9, 12, 18
+ Extended: 24, 36, 48, 54
+ HT Capab:
+ Info
+ RTAC66U: Green Field supported
+ Simulated: Green Field not supported by driver
+ 5GHz:
+ VHT Capab:
+ RTAC66U:
+ SU Beamformer Supported,
+ SU Beamformee Supported,
+ Beamformee STS Capability: 3,
+ Number of Sounding Dimensions: 3,
+ VHT Link Adaptation: Both
+ Simulated:
+ Above are not supported by driver
+ VHT Operation Info:
+ RTAC66U: Basic MCS Map (0x0000)
+ Simulated: Basic MCS Map (0xfffc)
+ VHT Tx Power Envelope:
+ RTAC66U: Local Max Tx Pwr Constraint: 1.0 dBm
+ Simulated: Local Max Tx Pwr Constraint: 23.0 dBm
+ Both:
+ HT Capab:
+ A-MPDU
+ RTAC66U: MPDU Density 4
+ Simulated: MPDU Density 8
+ HT Info:
+ RTAC66U: RIFS Permitted
+ Simulated: RIFS Prohibited
+ """
+ if not iface_wlan_2g or not iface_wlan_5g:
+ raise ValueError('Wlan interface for 2G and/or 5G is missing.')
+ if (iface_wlan_2g not in hostapd_constants.INTERFACE_2G_LIST
+ or iface_wlan_5g not in hostapd_constants.INTERFACE_5G_LIST):
+ raise ValueError('Invalid interface name was passed.')
+ if security:
+ if security.security_mode is hostapd_constants.WPA2:
+ if not security.wpa2_cipher == 'CCMP':
+ raise ValueError('The mock ASUS RT-AC66U only supports a WPA2 '
+ 'unicast and multicast cipher of CCMP. '
+ 'Invalid cipher mode (%s)' %
+ security.security.wpa2_cipher)
+ else:
+ raise ValueError(
+ 'The Asus RT-AC66U only supports WPA2 or open. Invalid '
+ 'security mode (%s)' % security.security_mode)
+
+ # Common Parameters
+ rates = {'supported_rates': '10 20 55 110 60 90 120 180 240 360 480 540'}
+ n_capabilities = [
+ hostapd_constants.N_CAPABILITY_LDPC,
+ hostapd_constants.N_CAPABILITY_TX_STBC,
+ hostapd_constants.N_CAPABILITY_RX_STBC1,
+ hostapd_constants.N_CAPABILITY_MAX_AMSDU_7935,
+ hostapd_constants.N_CAPABILITY_DSSS_CCK_40,
+ hostapd_constants.N_CAPABILITY_SGI20
+ ]
+ # WPS IE
+ # Broadcom IE
+ vendor_elements = {
+ 'vendor_elements':
+ 'dd310050f204104a00011010440001021047001093689729d373c26cb1563c6c570f33'
+ 'd7103c0001031049000600372a000120'
+ 'dd090010180200001c0000'
+ }
+
+ # 2.4GHz
+ if channel <= 11:
+ interface = iface_wlan_2g
+ rates['basic_rates'] = '10 20 55 110'
+ mode = hostapd_constants.MODE_11N_MIXED
+ ac_capabilities = None
+ vht_channel_width = None
+ vht_center_channel = None
+
+ # 5GHz
+ else:
+ interface = iface_wlan_5g
+ rates['basic_rates'] = '60 120 240'
+ mode = hostapd_constants.MODE_11AC_MIXED
+ ac_capabilities = [
+ hostapd_constants.AC_CAPABILITY_RXLDPC,
+ hostapd_constants.AC_CAPABILITY_SHORT_GI_80,
+ hostapd_constants.AC_CAPABILITY_TX_STBC_2BY1,
+ hostapd_constants.AC_CAPABILITY_RX_STBC_1,
+ hostapd_constants.AC_CAPABILITY_MAX_MPDU_11454,
+ hostapd_constants.AC_CAPABILITY_MAX_A_MPDU_LEN_EXP7
+ ]
+ vht_channel_width = 40
+ vht_center_channel = 36
+
+ additional_params = _merge_dicts(rates, vendor_elements,
+ hostapd_constants.UAPSD_ENABLED)
+
+ config = hostapd_config.HostapdConfig(
+ ssid=ssid,
+ channel=channel,
+ hidden=False,
+ security=security,
+ interface=interface,
+ mode=mode,
+ force_wmm=True,
+ beacon_interval=100,
+ dtim_period=3,
+ short_preamble=False,
+ n_capabilities=n_capabilities,
+ ac_capabilities=ac_capabilities,
+ vht_channel_width=vht_channel_width,
+ vht_center_channel=vht_center_channel,
+ additional_parameters=additional_params)
+
+ return config
+
+
+def asus_rtac86u(iface_wlan_2g=None,
+ iface_wlan_5g=None,
+ channel=None,
+ security=None,
+ ssid=None):
+ """A simulated implementation of an Asus RTAC86U AP.
+ Args:
+ iface_wlan_2g: The 2.4Ghz interface of the test AP.
+ iface_wlan_5g: The 5Ghz interface of the test AP.
+ channel: What channel to use.
+ security: A security profile. Must be none or WPA2 as this is what is
+ supported by the RTAC86U.
+ ssid: Network name
+ Returns:
+ A hostapd config
+ Differences from real RTAC86U:
+ 2.4GHz:
+ Rates:
+ RTAC86U:
+ Supported: 1, 2, 5.5, 11, 18, 24, 36, 54
+ Extended: 6, 9, 12, 48
+ Simulated:
+ Supported: 1, 2, 5.5, 11, 6, 9, 12, 18
+ Extended: 24, 36, 48, 54
+ 5GHz:
+ Country Code:
+ Simulated: Has two country code IEs, one that matches
+ the actual, and another explicit IE that was required for
+ hostapd's 802.11d to work.
+ Both (w/ WPA2):
+ RSN Capabilities:
+ RTA86U: 0x000c (RSN PTKSA Replay Counter Capab: 16)
+ Simulated: 0x0000
+ """
+ if not iface_wlan_2g or not iface_wlan_5g:
+ raise ValueError('Wlan interface for 2G and/or 5G is missing.')
+ if (iface_wlan_2g not in hostapd_constants.INTERFACE_2G_LIST
+ or iface_wlan_5g not in hostapd_constants.INTERFACE_5G_LIST):
+ raise ValueError('Invalid interface name was passed.')
+ if security:
+ if security.security_mode is hostapd_constants.WPA2:
+ if not security.wpa2_cipher == 'CCMP':
+ raise ValueError('The mock ASUS RTAC86U only supports a WPA2 '
+ 'unicast and multicast cipher of CCMP. '
+ 'Invalid cipher mode (%s)' %
+ security.security.wpa2_cipher)
+ else:
+ raise ValueError(
+ 'The Asus RTAC86U only supports WPA2 or open. Invalid '
+ 'security mode (%s)' % security.security_mode)
+
+ # Common Parameters
+ rates = {'supported_rates': '10 20 55 110 60 90 120 180 240 360 480 540'}
+ qbss = {'bss_load_update_period': 50, 'chan_util_avg_period': 600}
+
+ # 2.4GHz
+ if channel <= 11:
+ interface = iface_wlan_2g
+ mode = hostapd_constants.MODE_11G
+ rates['basic_rates'] = '10 20 55 110'
+ spectrum_mgmt = False
+ # Measurement Pilot Transmission IE
+ vendor_elements = {'vendor_elements': '42020000'}
+
+ # 5GHz
+ else:
+ interface = iface_wlan_5g
+ mode = hostapd_constants.MODE_11A
+ rates['basic_rates'] = '60 120 240'
+ spectrum_mgmt = True,
+ # Country Information IE (w/ individual channel info)
+ # TPC Report Transmit Power IE
+ # Measurement Pilot Transmission IE
+ vendor_elements = {
+ 'vendor_elements':
+ '074255532024011e28011e2c011e30011e34011e38011e3c011e40011e64011e'
+ '68011e6c011e70011e74011e84011e88011e8c011e95011e99011e9d011ea1011e'
+ 'a5011e'
+ '23021300'
+ '42020000'
+ }
+
+ additional_params = _merge_dicts(rates, qbss, vendor_elements)
+
+ config = hostapd_config.HostapdConfig(
+ ssid=ssid,
+ channel=channel,
+ hidden=False,
+ security=security,
+ interface=interface,
+ mode=mode,
+ force_wmm=False,
+ beacon_interval=100,
+ dtim_period=3,
+ short_preamble=False,
+ spectrum_mgmt_required=spectrum_mgmt,
+ additional_parameters=additional_params)
+ return config
+
+
+def asus_rtac5300(iface_wlan_2g=None,
+ iface_wlan_5g=None,
+ channel=None,
+ security=None,
+ ssid=None):
+ # TODO(b/143104825): Permit RIFS once it is supported
+ """A simulated implementation of an Asus RTAC5300 AP.
+ Args:
+ iface_wlan_2g: The 2.4Ghz interface of the test AP.
+ iface_wlan_5g: The 5Ghz interface of the test AP.
+ channel: What channel to use.
+ security: A security profile. Must be none or WPA2 as this is what is
+ supported by the RTAC5300.
+ ssid: Network name
+ Returns:
+ A hostapd config
+ Differences from real RTAC5300:
+ 2.4GHz:
+ Rates:
+ RTAC86U:
+ Supported: 1, 2, 5.5, 11, 18, 24, 36, 54
+ Extended: 6, 9, 12, 48
+ Simulated:
+ Supported: 1, 2, 5.5, 11, 6, 9, 12, 18
+ Extended: 24, 36, 48, 54
+ 5GHz:
+ VHT Capab:
+ RTAC5300:
+ SU Beamformer Supported,
+ SU Beamformee Supported,
+ Beamformee STS Capability: 4,
+ Number of Sounding Dimensions: 4,
+ MU Beamformer Supported,
+ VHT Link Adaptation: Both
+ Simulated:
+ Above are not supported by driver
+ VHT Operation Info:
+ RTAC5300: Basic MCS Map (0x0000)
+ Simulated: Basic MCS Map (0xfffc)
+ VHT Tx Power Envelope:
+ RTAC5300: Local Max Tx Pwr Constraint: 1.0 dBm
+ Simulated: Local Max Tx Pwr Constraint: 23.0 dBm
+ Both:
+ HT Capab:
+ A-MPDU
+ RTAC5300: MPDU Density 4
+ Simulated: MPDU Density 8
+ HT Info:
+ RTAC5300: RIFS Permitted
+ Simulated: RIFS Prohibited
+ """
+ if not iface_wlan_2g or not iface_wlan_5g:
+ raise ValueError('Wlan interface for 2G and/or 5G is missing.')
+ if (iface_wlan_2g not in hostapd_constants.INTERFACE_2G_LIST
+ or iface_wlan_5g not in hostapd_constants.INTERFACE_5G_LIST):
+ raise ValueError('Invalid interface name was passed.')
+ if security:
+ if security.security_mode is hostapd_constants.WPA2:
+ if not security.wpa2_cipher == 'CCMP':
+ raise ValueError('The mock ASUS RTAC5300 only supports a WPA2 '
+ 'unicast and multicast cipher of CCMP. '
+ 'Invalid cipher mode (%s)' %
+ security.security.wpa2_cipher)
+ else:
+ raise ValueError(
+ 'The Asus RTAC5300 only supports WPA2 or open. Invalid '
+ 'security mode (%s)' % security.security_mode)
+
+ # Common Parameters
+ rates = {'supported_rates': '10 20 55 110 60 90 120 180 240 360 480 540'}
+ qbss = {'bss_load_update_period': 50, 'chan_util_avg_period': 600}
+ n_capabilities = [
+ hostapd_constants.N_CAPABILITY_LDPC,
+ hostapd_constants.N_CAPABILITY_TX_STBC,
+ hostapd_constants.N_CAPABILITY_RX_STBC1,
+ hostapd_constants.N_CAPABILITY_SGI20
+ ]
+ # Broadcom IE
+ vendor_elements = {'vendor_elements': 'dd090010180200009c0000'}
+
+ # 2.4GHz
+ if channel <= 11:
+ interface = iface_wlan_2g
+ rates['basic_rates'] = '10 20 55 110'
+ mode = hostapd_constants.MODE_11N_MIXED
+ # AsusTek IE
+ # Epigram 2.4GHz IE
+ vendor_elements['vendor_elements'] += 'dd25f832e4010101020100031411b5' \
+ '2fd437509c30b3d7f5cf5754fb125aed3b8507045aed3b85' \
+ 'dd1e00904c0418bf0cb2798b0faaff0000aaff0000c0050001000000c3020002'
+ ac_capabilities = None
+ vht_channel_width = None
+ vht_center_channel = None
+
+ # 5GHz
+ else:
+ interface = iface_wlan_5g
+ rates['basic_rates'] = '60 120 240'
+ mode = hostapd_constants.MODE_11AC_MIXED
+ # Epigram 5GHz IE
+ vendor_elements['vendor_elements'] += 'dd0500904c0410'
+ ac_capabilities = [
+ hostapd_constants.AC_CAPABILITY_RXLDPC,
+ hostapd_constants.AC_CAPABILITY_SHORT_GI_80,
+ hostapd_constants.AC_CAPABILITY_TX_STBC_2BY1,
+ hostapd_constants.AC_CAPABILITY_RX_STBC_1,
+ hostapd_constants.AC_CAPABILITY_MAX_MPDU_11454,
+ hostapd_constants.AC_CAPABILITY_MAX_A_MPDU_LEN_EXP7
+ ]
+ vht_channel_width = 40
+ vht_center_channel = 36
+
+ additional_params = _merge_dicts(rates, qbss, vendor_elements,
+ hostapd_constants.UAPSD_ENABLED)
+
+ config = hostapd_config.HostapdConfig(
+ ssid=ssid,
+ channel=channel,
+ hidden=False,
+ security=security,
+ interface=interface,
+ mode=mode,
+ force_wmm=True,
+ beacon_interval=100,
+ dtim_period=3,
+ short_preamble=False,
+ n_capabilities=n_capabilities,
+ ac_capabilities=ac_capabilities,
+ vht_channel_width=vht_channel_width,
+ vht_center_channel=vht_center_channel,
+ additional_parameters=additional_params)
+ return config
diff --git a/acts/framework/acts/controllers/ap_lib/third_party_ap_profiles/belkin.py b/acts/framework/acts/controllers/ap_lib/third_party_ap_profiles/belkin.py
new file mode 100644
index 0000000000..00e80290b5
--- /dev/null
+++ b/acts/framework/acts/controllers/ap_lib/third_party_ap_profiles/belkin.py
@@ -0,0 +1,108 @@
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from acts.controllers.ap_lib import hostapd_config
+from acts.controllers.ap_lib import hostapd_constants
+
+
+def _merge_dicts(*dict_args):
+ result = {}
+ for dictionary in dict_args:
+ result.update(dictionary)
+ return result
+
+
+def belkin_f9k1001v5(iface_wlan_2g=None,
+ channel=None,
+ security=None,
+ ssid=None):
+ # TODO(b/143104825): Permit RIFS once it is supported
+ """A simulated implementation of what a Belkin F9K1001v5 AP
+ Args:
+ iface_wlan_2g: The 2.4Ghz interface of the test AP.
+ channel: What channel to use.
+ security: A security profile (None or WPA2).
+ ssid: The network name.
+ Returns:
+ A hostapd config.
+ Differences from real F9K1001v5:
+ Rates:
+ F9K1001v5:
+ Supported: 1, 2, 5.5, 11, 18, 24, 36, 54
+ Extended: 6, 9, 12, 48
+ Simulated:
+ Supported: 1, 2, 5.5, 11, 6, 9, 12, 18
+ Extended: 24, 36, 48, 54
+ HT Info:
+ F9K1001v5:
+ RIFS: Permitted
+ Simulated:
+ RIFS: Prohibited
+ """
+ if channel > 11:
+ raise ValueError('The Belkin F9k1001v5 does not support 5Ghz. '
+ 'Invalid channel (%s)' % channel)
+ if (iface_wlan_2g not in hostapd_constants.INTERFACE_2G_LIST):
+ raise ValueError('Invalid interface name was passed.')
+
+ if security:
+ if security.security_mode is hostapd_constants.WPA2:
+ if not security.wpa2_cipher == 'CCMP':
+ raise ValueError('The mock Belkin F9k1001v5 only supports a '
+ 'WPA2 unicast and multicast cipher of CCMP.'
+ 'Invalid cipher mode (%s)' %
+ security.security.wpa2_cipher)
+ else:
+ raise ValueError('The mock Belkin F9k1001v5 only supports WPA2. '
+ 'Invalid security mode (%s)' %
+ security.security_mode)
+
+ n_capabilities = [
+ hostapd_constants.N_CAPABILITY_SGI20,
+ hostapd_constants.N_CAPABILITY_SGI40,
+ hostapd_constants.N_CAPABILITY_TX_STBC,
+ hostapd_constants.N_CAPABILITY_MAX_AMSDU_7935,
+ hostapd_constants.N_CAPABILITY_DSSS_CCK_40
+ ]
+
+ rates = {
+ 'basic_rates': '10 20 55 110',
+ 'supported_rates': '10 20 55 110 60 90 120 180 240 360 480 540'
+ }
+
+ # Broadcom IE
+ # WPS IE
+ vendor_elements = {
+ 'vendor_elements':
+ 'dd090010180200100c0000'
+ 'dd180050f204104a00011010440001021049000600372a000120'
+ }
+
+ additional_params = _merge_dicts(rates, vendor_elements)
+
+ config = hostapd_config.HostapdConfig(
+ ssid=ssid,
+ channel=channel,
+ hidden=False,
+ security=security,
+ interface=iface_wlan_2g,
+ mode=hostapd_constants.MODE_11N_MIXED,
+ force_wmm=True,
+ beacon_interval=100,
+ dtim_period=3,
+ short_preamble=False,
+ n_capabilities=n_capabilities,
+ additional_parameters=additional_params)
+
+ return config
diff --git a/acts/framework/acts/controllers/attenuator_lib/minicircuits/telnet.py b/acts/framework/acts/controllers/attenuator_lib/minicircuits/telnet.py
index 96c890bd5f..be415d0ef3 100644
--- a/acts/framework/acts/controllers/attenuator_lib/minicircuits/telnet.py
+++ b/acts/framework/acts/controllers/attenuator_lib/minicircuits/telnet.py
@@ -37,11 +37,11 @@ class AttenuatorInstrument(attenuator.AttenuatorInstrument):
the functionality of AttenuatorInstrument is contingent upon a telnet
connection being established.
"""
-
def __init__(self, num_atten=0):
super(AttenuatorInstrument, self).__init__(num_atten)
- self._tnhelper = _tnhelper._TNHelper(
- tx_cmd_separator='\r\n', rx_cmd_separator='\r\n', prompt='')
+ self._tnhelper = _tnhelper._TNHelper(tx_cmd_separator='\r\n',
+ rx_cmd_separator='\r\n',
+ prompt='')
def __del__(self):
if self.is_open():
@@ -134,6 +134,9 @@ class AttenuatorInstrument(attenuator.AttenuatorInstrument):
raise IndexError('Attenuator index out of range!', self.num_atten,
idx)
- atten_val_str = self._tnhelper.cmd('CHAN:%s:ATT?' % (idx + 1))
+ if self.num_atten == 1:
+ atten_val_str = self._tnhelper.cmd(':ATT?')
+ else:
+ atten_val_str = self._tnhelper.cmd('CHAN:%s:ATT?' % (idx + 1))
atten_val = float(atten_val_str)
return atten_val
diff --git a/acts/framework/acts/controllers/cellular_simulator.py b/acts/framework/acts/controllers/cellular_simulator.py
index a140c0f202..35f0885c46 100644
--- a/acts/framework/acts/controllers/cellular_simulator.py
+++ b/acts/framework/acts/controllers/cellular_simulator.py
@@ -133,6 +133,15 @@ class AbstractCellularSimulator:
if config.tbs_pattern_on is not None:
self.set_tbs_pattern_on(bts_index, config.tbs_pattern_on)
+ def set_lte_rrc_state_change_timer(self, enabled, time=10):
+ """ Configures the LTE RRC state change timer.
+
+ Args:
+ enabled: a boolean indicating if the timer should be on or off.
+ time: time in seconds for the timer to expire
+ """
+ raise NotImplementedError()
+
def set_band(self, bts_index, band):
""" Sets the band for the indicated base station.
diff --git a/acts/framework/acts/controllers/fuchsia_device.py b/acts/framework/acts/controllers/fuchsia_device.py
index 350cfe4ee0..4e9b2dc9bd 100644
--- a/acts/framework/acts/controllers/fuchsia_device.py
+++ b/acts/framework/acts/controllers/fuchsia_device.py
@@ -535,7 +535,7 @@ class FuchsiaDevice:
self.log.info(unable_to_connect_msg)
raise e
finally:
- if action == 'stop':
+ if action == 'stop' and process_name == 'sl4f':
self._persistent_ssh_conn.close()
self._persistent_ssh_conn = None
diff --git a/acts/framework/acts/controllers/fuchsia_lib/utils_lib.py b/acts/framework/acts/controllers/fuchsia_lib/utils_lib.py
index 5df5b40127..f0b8dda4fb 100644
--- a/acts/framework/acts/controllers/fuchsia_lib/utils_lib.py
+++ b/acts/framework/acts/controllers/fuchsia_lib/utils_lib.py
@@ -30,21 +30,24 @@ def get_private_key(ip_address, ssh_config):
Returns:
The ssh private key
"""
+ exceptions = []
try:
logging.debug('Trying to load SSH key type: ed25519')
return paramiko.ed25519key.Ed25519Key(
filename=get_ssh_key_for_host(ip_address, ssh_config))
- except paramiko.SSHException:
+ except paramiko.SSHException as e:
+ exceptions.append(e)
logging.debug('Failed loading SSH key type: ed25519')
try:
logging.debug('Trying to load SSH key type: rsa')
return paramiko.RSAKey.from_private_key_file(
filename=get_ssh_key_for_host(ip_address, ssh_config))
- except paramiko.SSHException:
+ except paramiko.SSHException as e:
+ exceptions.append(e)
logging.debug('Failed loading SSH key type: rsa')
- raise paramiko.SSHException('No valid ssh key type found')
+ raise Exception('No valid ssh key type found', exceptions)
def create_ssh_connection(ip_address,
diff --git a/acts/framework/acts/controllers/iperf_server.py b/acts/framework/acts/controllers/iperf_server.py
index bedc05a748..039f143470 100755
--- a/acts/framework/acts/controllers/iperf_server.py
+++ b/acts/framework/acts/controllers/iperf_server.py
@@ -81,20 +81,25 @@ class IPerfResult(object):
will be loaded and this funtion is not intended to be used with files
containing multiple iperf client runs.
"""
- try:
- with open(result_path, 'r') as f:
- iperf_output = f.readlines()
- if '}\n' in iperf_output:
- iperf_output = iperf_output[:iperf_output.index('}\n') + 1]
- iperf_string = ''.join(iperf_output)
- iperf_string = iperf_string.replace('nan', '0')
- self.result = json.loads(iperf_string)
- except ValueError:
- with open(result_path, 'r') as f:
- # Possibly a result from interrupted iperf run, skip first line
- # and try again.
- lines = f.readlines()[1:]
- self.result = json.loads(''.join(lines))
+ # if result_path isn't a path, treat it as JSON
+ if not os.path.exists(result_path):
+ self.result = json.loads(result_path)
+ else:
+ try:
+ with open(result_path, 'r') as f:
+ iperf_output = f.readlines()
+ if '}\n' in iperf_output:
+ iperf_output = iperf_output[:iperf_output.index('}\n')
+ + 1]
+ iperf_string = ''.join(iperf_output)
+ iperf_string = iperf_string.replace('nan', '0')
+ self.result = json.loads(iperf_string)
+ except ValueError:
+ with open(result_path, 'r') as f:
+ # Possibly a result from interrupted iperf run,
+ # skip first line and try again.
+ lines = f.readlines()[1:]
+ self.result = json.loads(''.join(lines))
def _has_data(self):
"""Checks if the iperf result has valid throughput data.
@@ -197,7 +202,7 @@ class IPerfResult(object):
instantaneous_rates = self.instantaneous_rates[iperf_ignored_interval:
-1]
avg_rate = math.fsum(instantaneous_rates) / len(instantaneous_rates)
- sqd_deviations = [(rate - avg_rate)**2 for rate in instantaneous_rates]
+ sqd_deviations = [(rate - avg_rate) ** 2 for rate in instantaneous_rates]
std_dev = math.sqrt(
math.fsum(sqd_deviations) / (len(sqd_deviations) - 1))
return std_dev
diff --git a/acts/framework/acts/controllers/monsoon.py b/acts/framework/acts/controllers/monsoon.py
index d5b24a2703..9488837dbb 100644
--- a/acts/framework/acts/controllers/monsoon.py
+++ b/acts/framework/acts/controllers/monsoon.py
@@ -13,1006 +13,28 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-"""Interface for a USB-connected Monsoon power meter
-(http://msoon.com/LabEquipment/PowerMonitor/).
-Based on the original py2 script of kens@google.com
-"""
-import fcntl
-import logging
-import os
-import select
-import struct
-import sys
-import time
-import collections
+from acts.controllers.monsoon_lib.api.hvpm.monsoon import Monsoon as HvpmMonsoon
+from acts.controllers.monsoon_lib.api.lvpm_stock.monsoon import Monsoon as LvpmStockMonsoon
-# http://pyserial.sourceforge.net/
-# On ubuntu, apt-get install python3-pyserial
-import serial
-
-import acts.signals
-
-from acts import utils
-from acts.controllers import android_device
-
-ACTS_CONTROLLER_CONFIG_NAME = "Monsoon"
-ACTS_CONTROLLER_REFERENCE_NAME = "monsoons"
+ACTS_CONTROLLER_CONFIG_NAME = 'Monsoon'
+ACTS_CONTROLLER_REFERENCE_NAME = 'monsoons'
def create(configs):
objs = []
- for c in configs:
- objs.append(Monsoon(serial=int(c)))
- return objs
-
-
-def destroy(objs):
- for obj in objs:
- fcntl.flock(obj.mon._tempfile, fcntl.LOCK_UN)
- obj.mon._tempfile.close()
-
-
-class MonsoonError(acts.signals.ControllerError):
- """Raised for exceptions encountered in monsoon lib."""
-
-
-class MonsoonProxy(object):
- """Class that directly talks to monsoon over serial.
-
- Provides a simple class to use the power meter, e.g.
- mon = monsoon.Monsoon()
- mon.SetVoltage(3.7)
- mon.StartDataCollection()
- mydata = []
- while len(mydata) < 1000:
- mydata.extend(mon.CollectData())
- mon.StopDataCollection()
-
- See http://wiki/Main/MonsoonProtocol for information on the protocol.
- """
-
- def __init__(self, device=None, serialno=None, wait=1):
- """Establish a connection to a Monsoon.
-
- By default, opens the first available port, waiting if none are ready.
- A particular port can be specified with "device", or a particular
- Monsoon can be specified with "serialno" (using the number printed on
- its back). With wait=0, IOError is thrown if a device is not
- immediately available.
- """
- self._coarse_ref = self._fine_ref = self._coarse_zero = 0
- self._fine_zero = self._coarse_scale = self._fine_scale = 0
- self._last_seq = 0
- self.start_voltage = 0
- self.serial = serialno
-
- if device:
- self.ser = serial.Serial(device, timeout=1)
- return
- # Try all devices connected through USB virtual serial ports until we
- # find one we can use.
- while True:
- for dev in os.listdir("/dev"):
- prefix = "ttyACM"
- # Prefix is different on Mac OS X.
- if sys.platform == "darwin":
- prefix = "tty.usbmodem"
- if not dev.startswith(prefix):
- continue
- tmpname = "/tmp/monsoon.%s.%s" % (os.uname()[0], dev)
- self._tempfile = open(tmpname, "w")
- try:
- os.chmod(tmpname, 0o666)
- except OSError as e:
- pass
-
- try: # use a lockfile to ensure exclusive access
- fcntl.flock(self._tempfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
- except IOError as e:
- logging.error("device %s is in use", dev)
- continue
-
- try: # try to open the device
- self.ser = serial.Serial("/dev/%s" % dev, timeout=1)
- self.StopDataCollection() # just in case
- self._FlushInput() # discard stale input
- status = self.GetStatus()
- except Exception as e:
- logging.exception("Error opening device %s: %s", dev, e)
- continue
-
- if not status:
- logging.error("no response from device %s", dev)
- elif serialno and status["serialNumber"] != serialno:
- logging.error("Another device serial #%d seen on %s",
- status["serialNumber"], dev)
- else:
- self.start_voltage = status["voltage1"]
- return
-
- self._tempfile = None
- if not wait: raise IOError("No device found")
- logging.info("Waiting for device...")
- time.sleep(1)
-
- def GetStatus(self):
- """Requests and waits for status.
-
- Returns:
- status dictionary.
- """
- # status packet format
- STATUS_FORMAT = ">BBBhhhHhhhHBBBxBbHBHHHHBbbHHBBBbbbbbbbbbBH"
- STATUS_FIELDS = [
- "packetType",
- "firmwareVersion",
- "protocolVersion",
- "mainFineCurrent",
- "usbFineCurrent",
- "auxFineCurrent",
- "voltage1",
- "mainCoarseCurrent",
- "usbCoarseCurrent",
- "auxCoarseCurrent",
- "voltage2",
- "outputVoltageSetting",
- "temperature",
- "status",
- "leds",
- "mainFineResistor",
- "serialNumber",
- "sampleRate",
- "dacCalLow",
- "dacCalHigh",
- "powerUpCurrentLimit",
- "runTimeCurrentLimit",
- "powerUpTime",
- "usbFineResistor",
- "auxFineResistor",
- "initialUsbVoltage",
- "initialAuxVoltage",
- "hardwareRevision",
- "temperatureLimit",
- "usbPassthroughMode",
- "mainCoarseResistor",
- "usbCoarseResistor",
- "auxCoarseResistor",
- "defMainFineResistor",
- "defUsbFineResistor",
- "defAuxFineResistor",
- "defMainCoarseResistor",
- "defUsbCoarseResistor",
- "defAuxCoarseResistor",
- "eventCode",
- "eventData",
- ]
-
- self._SendStruct("BBB", 0x01, 0x00, 0x00)
- while 1: # Keep reading, discarding non-status packets
- read_bytes = self._ReadPacket()
- if not read_bytes:
- raise MonsoonError("Failed to read Monsoon status")
- calsize = struct.calcsize(STATUS_FORMAT)
- if len(read_bytes) != calsize or read_bytes[0] != 0x10:
- raise MonsoonError(
- "Wanted status, dropped type=0x%02x, len=%d",
- read_bytes[0], len(read_bytes))
- status = dict(
- zip(STATUS_FIELDS, struct.unpack(STATUS_FORMAT, read_bytes)))
- p_type = status["packetType"]
- if p_type != 0x10:
- raise MonsoonError("Package type %s is not 0x10." % p_type)
- for k in status.keys():
- if k.endswith("VoltageSetting"):
- status[k] = 2.0 + status[k] * 0.01
- elif k.endswith("FineCurrent"):
- pass # needs calibration data
- elif k.endswith("CoarseCurrent"):
- pass # needs calibration data
- elif k.startswith("voltage") or k.endswith("Voltage"):
- status[k] = status[k] * 0.000125
- elif k.endswith("Resistor"):
- status[k] = 0.05 + status[k] * 0.0001
- if k.startswith("aux") or k.startswith("defAux"):
- status[k] += 0.05
- elif k.endswith("CurrentLimit"):
- status[k] = 8 * (1023 - status[k]) / 1023.0
- return status
-
- def RampVoltage(self, start, end):
- v = start
- if v < 3.0: v = 3.0 # protocol doesn't support lower than this
- while (v < end):
- self.SetVoltage(v)
- v += .1
- time.sleep(.1)
- self.SetVoltage(end)
-
- def SetVoltage(self, v):
- """Set the output voltage, 0 to disable.
- """
- if v == 0:
- self._SendStruct("BBB", 0x01, 0x01, 0x00)
- else:
- self._SendStruct("BBB", 0x01, 0x01, int((v - 2.0) * 100))
-
- def GetVoltage(self):
- """Get the output voltage.
-
- Returns:
- Current Output Voltage (in unit of v).
- """
- try:
- return self.GetStatus()["outputVoltageSetting"]
- # Catch potential errors such as struct.error, TypeError and other
- # unknown errors which would bring down the whole test
- except Exception as e:
- raise MonsoonError("Error getting Monsoon voltage")
-
- def SetMaxCurrent(self, i):
- """Set the max output current.
- """
- if i < 0 or i > 8:
- raise MonsoonError(("Target max current %sA, is out of acceptable "
- "range [0, 8].") % i)
- val = 1023 - int((i / 8) * 1023)
- self._SendStruct("BBB", 0x01, 0x0a, val & 0xff)
- self._SendStruct("BBB", 0x01, 0x0b, val >> 8)
-
- def SetMaxPowerUpCurrent(self, i):
- """Set the max power up current.
- """
- if i < 0 or i > 8:
- raise MonsoonError(("Target max current %sA, is out of acceptable "
- "range [0, 8].") % i)
- val = 1023 - int((i / 8) * 1023)
- self._SendStruct("BBB", 0x01, 0x08, val & 0xff)
- self._SendStruct("BBB", 0x01, 0x09, val >> 8)
-
- def SetUsbPassthrough(self, val):
- """Set the USB passthrough mode: 0 = off, 1 = on, 2 = auto.
- """
- self._SendStruct("BBB", 0x01, 0x10, val)
-
- def GetUsbPassthrough(self):
- """Get the USB passthrough mode: 0 = off, 1 = on, 2 = auto.
-
- Returns:
- Current USB passthrough mode.
- """
- try:
- return self.GetStatus()["usbPassthroughMode"]
- # Catch potential errors such as struct.error, TypeError and other
- # unknown errors which would bring down the whole test
- except Exception as e:
- raise MonsoonError("Error reading Monsoon USB passthrough status")
-
- def StartDataCollection(self):
- """Tell the device to start collecting and sending measurement data.
- """
- self._SendStruct("BBB", 0x01, 0x1b, 0x01) # Mystery command
- self._SendStruct("BBBBBBB", 0x02, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8)
-
- def StopDataCollection(self):
- """Tell the device to stop collecting measurement data.
- """
- self._SendStruct("BB", 0x03, 0x00) # stop
-
- def CollectData(self):
- """Return some current samples. Call StartDataCollection() first.
- """
- while 1: # loop until we get data or a timeout
- _bytes = self._ReadPacket()
- if not _bytes:
- raise MonsoonError("Data collection failed due to empty data")
- if len(_bytes) < 4 + 8 + 1 or _bytes[0] < 0x20 or _bytes[0] > 0x2F:
- logging.warning("Wanted data, dropped type=0x%02x, len=%d",
- _bytes[0], len(_bytes))
- continue
-
- seq, _type, x, y = struct.unpack("BBBB", _bytes[:4])
- data = [
- struct.unpack(">hhhh", _bytes[x:x + 8])
- for x in range(4,
- len(_bytes) - 8, 8)
- ]
-
- if self._last_seq and seq & 0xF != (self._last_seq + 1) & 0xF:
- logging.warning("Data sequence skipped, lost packet?")
- self._last_seq = seq
-
- if _type == 0:
- if not self._coarse_scale or not self._fine_scale:
- logging.warning(
- "Waiting for calibration, dropped data packet.")
- continue
- out = []
- for main, usb, aux, voltage in data:
- if main & 1:
- coarse = ((main & ~1) - self._coarse_zero)
- out.append(coarse * self._coarse_scale)
- else:
- out.append((main - self._fine_zero) * self._fine_scale)
- return out
- elif _type == 1:
- self._fine_zero = data[0][0]
- self._coarse_zero = data[1][0]
- elif _type == 2:
- self._fine_ref = data[0][0]
- self._coarse_ref = data[1][0]
- else:
- logging.warning("Discarding data packet type=0x%02x", _type)
- continue
-
- # See http://wiki/Main/MonsoonProtocol for details on these values.
- if self._coarse_ref != self._coarse_zero:
- self._coarse_scale = 2.88 / (
- self._coarse_ref - self._coarse_zero)
- if self._fine_ref != self._fine_zero:
- self._fine_scale = 0.0332 / (self._fine_ref - self._fine_zero)
-
- def _SendStruct(self, fmt, *args):
- """Pack a struct (without length or checksum) and send it.
- """
- # Flush out the input buffer before sending data
- self._FlushInput()
- data = struct.pack(fmt, *args)
- data_len = len(data) + 1
- checksum = (data_len + sum(bytearray(data))) % 256
- out = struct.pack("B", data_len) + data + struct.pack("B", checksum)
- self.ser.write(out)
-
- def _ReadPacket(self):
- """Read a single data record as a string (without length or checksum).
- """
- len_char = self.ser.read(1)
- if not len_char:
- raise MonsoonError("Reading from serial port timed out")
-
- data_len = ord(len_char)
- if not data_len:
- return ""
- result = self.ser.read(int(data_len))
- result = bytearray(result)
- if len(result) != data_len:
- raise MonsoonError(
- "Length mismatch, expected %d bytes, got %d bytes.", data_len,
- len(result))
- body = result[:-1]
- checksum = (sum(struct.unpack("B" * len(body), body)) + data_len) % 256
- if result[-1] != checksum:
- raise MonsoonError(
- "Invalid checksum from serial port! Expected %s, got %s",
- hex(checksum), hex(result[-1]))
- return result[:-1]
-
- def _FlushInput(self):
- """ Flush all read data until no more available. """
- self.ser.reset_input_buffer()
- flushed = 0
- while True:
- ready_r, ready_w, ready_x = select.select([self.ser], [],
- [self.ser], 0)
- if len(ready_x) > 0:
- raise MonsoonError("Exception from serial port.")
- elif len(ready_r) > 0:
- flushed += 1
- self.ser.read(1) # This may cause underlying buffering.
- self.ser.reset_input_buffer(
- ) # Flush the underlying buffer too.
- else:
- break
- # if flushed > 0:
- # logging.info("dropped >%d bytes" % flushed)
-
-
-class MonsoonData(object):
- """A class for reporting power measurement data from monsoon.
-
- Data means the measured current value in Amps.
- """
- # Number of digits for long rounding.
- lr = 8
- # Number of digits for short rounding
- sr = 6
- # Delimiter for writing multiple MonsoonData objects to text file.
- delimiter = "\n\n==========\n\n"
-
- def __init__(self, data_points, timestamps, hz, voltage, offset=0):
- """Instantiates a MonsoonData object.
-
- Args:
- data_points: A list of current values in Amp (float).
- timestamps: A list of epoch timestamps (int).
- hz: The hertz at which the data points are measured.
- voltage: The voltage at which the data points are measured.
- offset: The number of initial data points to discard
- in calculations.
- """
- self._data_points = data_points
- self._timestamps = timestamps
- self.offset = offset
- num_of_data_pt = len(self._data_points)
- if self.offset >= num_of_data_pt:
- raise MonsoonError(
- ("Offset number (%d) must be smaller than the "
- "number of data points (%d).") % (offset, num_of_data_pt))
- self.data_points = self._data_points[self.offset:]
- self.timestamps = self._timestamps[self.offset:]
- self.hz = hz
- self.voltage = voltage
- self.tag = None
- self._validate_data()
-
- @property
- def average_current(self):
- """Average current in the unit of mA.
- """
- len_data_pt = len(self.data_points)
- if len_data_pt == 0:
- return 0
- cur = sum(self.data_points) * 1000 / len_data_pt
- return round(cur, self.sr)
-
- @property
- def total_charge(self):
- """Total charged used in the unit of mAh.
- """
- charge = (sum(self.data_points) / self.hz) * 1000 / 3600
- return round(charge, self.sr)
-
- @property
- def total_power(self):
- """Total power used.
- """
- power = self.average_current * self.voltage
- return round(power, self.sr)
-
- @staticmethod
- def from_string(data_str):
- """Creates a MonsoonData object from a string representation generated
- by __str__.
-
- Args:
- str: The string representation of a MonsoonData.
-
- Returns:
- A MonsoonData object.
- """
- lines = data_str.strip().split('\n')
- err_msg = ("Invalid input string format. Is this string generated by "
- "MonsoonData class?")
- conditions = [
- len(lines) <= 4, "Average Current:" not in lines[1],
- "Voltage: " not in lines[2], "Total Power: " not in lines[3],
- "samples taken at " not in lines[4],
- lines[5] != "Time" + ' ' * 7 + "Amp"
- ]
- if any(conditions):
- raise MonsoonError(err_msg)
- """Example string from Monsoon output file, first line is empty.
- Line1:
- Line2: test_2g_screenoff_dtimx2_marlin_OPD1.170706.006
- Line3: Average Current: 51.87984mA.
- Line4: Voltage: 4.2V.
- Line5: Total Power: 217.895328mW.
- Line6: 150000 samples taken at 500Hz, with an offset of 0 samples.
- """
- hz_str = lines[4].split()[4]
- hz = int(hz_str[:-3])
- voltage_str = lines[2].split()[1]
- voltage = float(voltage_str[:-2])
- lines = lines[6:]
- t = []
- v = []
- for l in lines:
- try:
- timestamp, value = l.split(' ')
- t.append(int(timestamp))
- v.append(float(value))
- except ValueError:
- raise MonsoonError(err_msg)
- return MonsoonData(v, t, hz, voltage)
-
- @staticmethod
- def save_to_text_file(monsoon_data, file_path):
- """Save multiple MonsoonData objects to a text file.
-
- Args:
- monsoon_data: A list of MonsoonData objects to write to a text
- file.
- file_path: The full path of the file to save to, including the file
- name.
- """
- if not monsoon_data:
- raise MonsoonError("Attempting to write empty Monsoon data to "
- "file, abort")
- utils.create_dir(os.path.dirname(file_path))
- with open(file_path, 'a') as f:
- for md in monsoon_data:
- f.write(str(md))
- f.write(MonsoonData.delimiter)
-
- @staticmethod
- def from_text_file(file_path):
- """Load MonsoonData objects from a text file generated by
- MonsoonData.save_to_text_file.
-
- Args:
- file_path: The full path of the file load from, including the file
- name.
-
- Returns:
- A list of MonsoonData objects.
- """
- results = []
- with open(file_path, 'r') as f:
- data_strs = f.read().split(MonsoonData.delimiter)
- data_strs = data_strs[:-1]
- for data_str in data_strs:
- results.append(MonsoonData.from_string(data_str))
- return results
-
- def _validate_data(self):
- """Verifies that the data points contained in the class are valid.
- """
- msg = "Error! Expected {} timestamps, found {}.".format(
- len(self._data_points), len(self._timestamps))
- if len(self._data_points) != len(self._timestamps):
- raise MonsoonError(msg)
-
- def update_offset(self, new_offset):
- """Updates how many data points to skip in caculations.
-
- Always use this function to update offset instead of directly setting
- self.offset.
-
- Args:
- new_offset: The new offset.
- """
- self.offset = new_offset
- self.data_points = self._data_points[self.offset:]
- self.timestamps = self._timestamps[self.offset:]
-
- def get_data_with_timestamps(self):
- """Returns the data points with timestamps.
-
- Returns:
- A list of tuples in the format of (timestamp, data)
- """
- result = []
- for t, d in zip(self.timestamps, self.data_points):
- result.append(t, round(d, self.lr))
- return result
-
- def get_average_record(self, n):
- """Returns a list of average current numbers, each representing the
- average over the last n data points.
-
- Args:
- n: Number of data points to average over.
-
- Returns:
- A list of average current values.
- """
- history_deque = collections.deque()
- averages = []
- for d in self.data_points:
- history_deque.appendleft(d)
- if len(history_deque) > n:
- history_deque.pop()
- avg = sum(history_deque) / len(history_deque)
- averages.append(round(avg, self.lr))
- return averages
-
- def _header(self):
- strs = [""]
- if self.tag:
- strs.append(self.tag)
+ for serial in configs:
+ serial_number = int(serial)
+ if serial_number < 20000:
+ # This code assumes the LVPM has not been updated to have a
+ # non-stock firmware. If someone has updated the firmware,
+ # power measurement will fail.
+ objs.append(LvpmStockMonsoon(serial=serial_number))
else:
- strs.append("Monsoon Measurement Data")
- strs.append("Average Current: {}mA.".format(self.average_current))
- strs.append("Voltage: {}V.".format(self.voltage))
- strs.append("Total Power: {}mW.".format(self.total_power))
- strs.append(
- ("{} samples taken at {}Hz, with an offset of {} samples.").format(
- len(self._data_points), self.hz, self.offset))
- return "\n".join(strs)
-
- def __len__(self):
- return len(self.data_points)
-
- def __str__(self):
- strs = []
- strs.append(self._header())
- strs.append("Time" + ' ' * 7 + "Amp")
- for t, d in zip(self.timestamps, self.data_points):
- strs.append("{} {}".format(t, round(d, self.sr)))
- return "\n".join(strs)
-
- def __repr__(self):
- return self._header()
-
-
-class Monsoon(object):
- """The wrapper class for test scripts to interact with monsoon.
- """
-
- def __init__(self, *args, **kwargs):
- serial = kwargs["serial"]
- device = None
- self.log = logging.getLogger()
- if "device" in kwargs:
- device = kwargs["device"]
- self.mon = MonsoonProxy(serialno=serial, device=device)
- self.dev = self.mon.ser.name
- self.serial = serial
- self.dut = None
-
- def attach_device(self, dut):
- """Attach the controller object for the Device Under Test (DUT)
- physically attached to the Monsoon box.
-
- Args:
- dut: A controller object representing the device being powered by
- this Monsoon box.
- """
- self.dut = dut
-
- def set_voltage(self, volt, ramp=False):
- """Sets the output voltage of monsoon.
-
- Args:
- volt: Voltage to set the output to.
- ramp: If true, the output voltage will be increased gradually to
- prevent tripping Monsoon overvoltage.
- """
- if ramp:
- self.mon.RampVoltage(mon.start_voltage, volt)
- else:
- self.mon.SetVoltage(volt)
-
- def set_max_current(self, cur):
- """Sets monsoon's max output current.
-
- Args:
- cur: The max current in A.
- """
- self.mon.SetMaxCurrent(cur)
-
- def set_max_init_current(self, cur):
- """Sets the max power-up/inital current.
-
- Args:
- cur: The max initial current allowed in mA.
- """
- self.mon.SetMaxPowerUpCurrent(cur)
-
- @property
- def status(self):
- """Gets the status params of monsoon.
-
- Returns:
- A dictionary where each key-value pair represents a monsoon status
- param.
- """
- return self.mon.GetStatus()
-
- def take_samples(self, sample_hz, sample_num, sample_offset=0, live=False):
- """Take samples of the current value supplied by monsoon.
-
- This is the actual measurement for power consumption. This function
- blocks until the number of samples requested has been fulfilled.
-
- Args:
- hz: Number of points to take for every second.
- sample_num: Number of samples to take.
- offset: The number of initial data points to discard in MonsoonData
- calculations. sample_num is extended by offset to compensate.
- live: Print each sample in console as measurement goes on.
-
- Returns:
- A MonsoonData object representing the data obtained in this
- sampling. None if sampling is unsuccessful.
- """
- sys.stdout.flush()
- voltage = self.mon.GetVoltage()
- self.log.info("Taking samples at %dhz for %ds, voltage %.2fv.",
- sample_hz, (sample_num / sample_hz), voltage)
- sample_num += sample_offset
- # Make sure state is normal
- self.mon.StopDataCollection()
- status = self.mon.GetStatus()
- native_hz = status["sampleRate"] * 1000
-
- # Collect and average samples as specified
- self.mon.StartDataCollection()
-
- # In case sample_hz doesn't divide native_hz exactly, use this
- # invariant: 'offset' = (consumed samples) * sample_hz -
- # (emitted samples) * native_hz
- # This is the error accumulator in a variation of Bresenham's
- # algorithm.
- emitted = offset = 0
- collected = []
- # past n samples for rolling average
- history_deque = collections.deque()
- current_values = []
- timestamps = []
-
- try:
- last_flush = time.time()
- while emitted < sample_num or sample_num == -1:
- # The number of raw samples to consume before emitting the next
- # output
- need = int((native_hz - offset + sample_hz - 1) / sample_hz)
- if need > len(collected): # still need more input samples
- samples = self.mon.CollectData()
- if not samples:
- break
- collected.extend(samples)
- else:
- # Have enough data, generate output samples.
- # Adjust for consuming 'need' input samples.
- offset += need * sample_hz
- # maybe multiple, if sample_hz > native_hz
- while offset >= native_hz:
- # TODO(angli): Optimize "collected" operations.
- this_sample = sum(collected[:need]) / need
- this_time = int(time.time())
- timestamps.append(this_time)
- if live:
- self.log.info("%s %s", this_time, this_sample)
- current_values.append(this_sample)
- sys.stdout.flush()
- offset -= native_hz
- emitted += 1 # adjust for emitting 1 output sample
- collected = collected[need:]
- now = time.time()
- if now - last_flush >= 0.99: # flush every second
- sys.stdout.flush()
- last_flush = now
- except Exception as e:
- pass
- self.mon.StopDataCollection()
- try:
- return MonsoonData(
- current_values,
- timestamps,
- sample_hz,
- voltage,
- offset=sample_offset)
- except:
- return None
-
- @utils.timeout(60)
- def usb(self, state):
- """Sets the monsoon's USB passthrough mode. This is specific to the
- USB port in front of the monsoon box which connects to the powered
- device, NOT the USB that is used to talk to the monsoon itself.
-
- "Off" means USB always off.
- "On" means USB always on.
- "Auto" means USB is automatically turned off when sampling is going on,
- and turned back on when sampling finishes.
-
- Args:
- stats: The state to set the USB passthrough to.
-
- Returns:
- True if the state is legal and set. False otherwise.
- """
- state_lookup = {"off": 0, "on": 1, "auto": 2}
- state = state.lower()
- if state in state_lookup:
- current_state = self.mon.GetUsbPassthrough()
- while (current_state != state_lookup[state]):
- self.mon.SetUsbPassthrough(state_lookup[state])
- time.sleep(1)
- current_state = self.mon.GetUsbPassthrough()
- return True
- return False
-
- def _check_dut(self):
- """Verifies there is a DUT attached to the monsoon.
-
- This should be called in the functions that operate the DUT.
- """
- if not self.dut:
- raise MonsoonError("Need to attach the device before using it.")
-
- @utils.timeout(15)
- def _wait_for_device(self, ad):
- while ad.serial not in android_device.list_adb_devices():
- pass
- ad.adb.wait_for_device()
-
- def execute_sequence_and_measure(self,
- step_funcs,
- hz,
- duration,
- offset_sec=20,
- *args,
- **kwargs):
- """@Deprecated.
- Executes a sequence of steps and take samples in-between.
-
- For each step function, the following steps are followed:
- 1. The function is executed to put the android device in a state.
- 2. If the function returns False, skip to next step function.
- 3. If the function returns True, sl4a session is disconnected.
- 4. Monsoon takes samples.
- 5. Sl4a is reconnected.
-
- Because it takes some time for the device to calm down after the usb
- connection is cut, an offset is set for each measurement. The default
- is 20s.
-
- Args:
- hz: Number of samples to take per second.
- durations: Number(s) of minutes to take samples for in each step.
- If this is an integer, all the steps will sample for the same
- amount of time. If this is an iterable of the same length as
- step_funcs, then each number represents the number of minutes
- to take samples for after each step function.
- e.g. If durations[0] is 10, we'll sample for 10 minutes after
- step_funcs[0] is executed.
- step_funcs: A list of funtions, whose first param is an android
- device object. If a step function returns True, samples are
- taken after this step, otherwise we move on to the next step
- function.
- ad: The android device object connected to this monsoon.
- offset_sec: The number of seconds of initial data to discard.
- *args, **kwargs: Extra args to be passed into each step functions.
-
- Returns:
- The MonsoonData objects from samplings.
- """
- self._check_dut()
- sample_nums = []
- try:
- if len(duration) != len(step_funcs):
- raise MonsoonError(("The number of durations need to be the "
- "same as the number of step functions."))
- for d in duration:
- sample_nums.append(d * 60 * hz)
- except TypeError:
- num = duration * 60 * hz
- sample_nums = [num] * len(step_funcs)
- results = []
- oset = offset_sec * hz
- for func, num in zip(step_funcs, sample_nums):
- try:
- self.usb("auto")
- step_name = func.__name__
- self.log.info("Executing step function %s.", step_name)
- take_sample = func(ad, *args, **kwargs)
- if not take_sample:
- self.log.info("Skip taking samples for %s", step_name)
- continue
- time.sleep(1)
- self.dut.stop_services()
- time.sleep(1)
- self.log.info("Taking samples for %s.", step_name)
- data = self.take_samples(hz, num, sample_offset=oset)
- if not data:
- raise MonsoonError("Sampling for %s failed." % step_name)
- self.log.info("Sample summary: %s", repr(data))
- data.tag = step_name
- results.append(data)
- except Exception:
- self.log.exception("Exception happened during step %s, abort!"
- % func.__name__)
- return results
- finally:
- self.mon.StopDataCollection()
- self.usb("on")
- self._wait_for_device(self.dut)
- # Wait for device to come back online.
- time.sleep(10)
- self.dut.start_services()
- # Release wake lock to put device into sleep.
- self.dut.droid.goToSleepNow()
- return results
-
- def disconnect_dut(self):
- """Disconnect DUT from monsoon.
-
- Stop the sl4a service on the DUT and disconnect USB connection
- raises:
- MonsoonError: monsoon erro trying to disconnect usb
- """
- try:
- self.dut.stop_services()
- time.sleep(1)
- self.usb("off")
- except Exception as e:
- raise MonsoonError(
- "Error happended trying to disconnect DUT from Monsoon")
-
- def monsoon_usb_auto(self):
- """Set monsoon USB to auto to ready the device for power measurement.
-
- Stop the sl4a service on the DUT and disconnect USB connection
- raises:
- MonsoonError: monsoon erro trying to set usbpassthrough to auto
- """
- try:
- self.dut.stop_services()
- time.sleep(1)
- self.usb("auto")
- except Exception as e:
- raise MonsoonError(
- "Error happended trying to set Monsoon usbpassthrough to auto")
-
- def reconnect_dut(self):
- """Reconnect DUT to monsoon and start sl4a services.
-
- raises:
- MonsoonError: monsoon erro trying to reconnect usb
- Turn usbpassthrough on and start the sl4a services.
- """
- self.log.info("Reconnecting dut.")
- try:
- # If wait for device failed, reset monsoon and try it again, if
- # this still fails, then raise
- try:
- self._wait_for_device(self.dut)
- except acts.utils.TimeoutError:
- self.log.info('Retry-reset monsoon and connect again')
- self.usb('off')
- time.sleep(1)
- self.usb('on')
- self._wait_for_device(self.dut)
- # Wait for device to come back online.
- time.sleep(2)
- self.dut.start_services()
- # Release wake lock to put device into sleep.
- self.dut.droid.goToSleepNow()
- self.log.info("Dut reconnected.")
- except Exception as e:
- raise MonsoonError("Error happened trying to reconnect DUT")
-
- def measure_power(self, hz, duration, tag, offset=30):
- """Measure power consumption of the attached device.
-
- Because it takes some time for the device to calm down after the usb
- connection is cut, an offset is set for each measurement. The default
- is 30s. The total time taken to measure will be (duration + offset).
-
- Args:
- hz: Number of samples to take per second.
- duration: Number of seconds to take samples for in each step.
- offset: The number of seconds of initial data to discard.
- tag: A string that's the name of the collected data group.
-
- Returns:
- A MonsoonData object with the measured power data.
- """
- num = duration * hz
- oset = offset * hz
- data = None
- try:
- data = self.take_samples(hz, num, sample_offset=oset)
- if not data:
- raise MonsoonError(
- ("No data was collected in measurement %s.") % tag)
- data.tag = tag
- self.log.info("Measurement summary: %s", repr(data))
- finally:
- self.mon.StopDataCollection()
- return data
+ objs.append(HvpmMonsoon(serial=serial_number))
+ return objs
- def reconnect_monsoon(self):
- """Reconnect Monsoon to serial port.
- """
- logging.info("Close serial connection")
- self.mon.ser.close()
- logging.info("Reset serial port")
- time.sleep(5)
- logging.info("Open serial connection")
- self.mon.ser.open()
- self.mon.ser.reset_input_buffer()
- self.mon.ser.reset_output_buffer()
+def destroy(monsoons):
+ for monsoon in monsoons:
+ monsoon.release_monsoon_connection()
diff --git a/acts/framework/acts/controllers/monsoon_lib/api/common.py b/acts/framework/acts/controllers/monsoon_lib/api/common.py
index fffed4cbd3..f932535467 100644
--- a/acts/framework/acts/controllers/monsoon_lib/api/common.py
+++ b/acts/framework/acts/controllers/monsoon_lib/api/common.py
@@ -40,7 +40,37 @@ PASSTHROUGH_STATES = {
}
-class MonsoonData(object):
+class MonsoonDataRecord(object):
+ """A data class for Monsoon data points."""
+ def __init__(self, time, current):
+ """Creates a new MonsoonDataRecord.
+
+ Args:
+ time: the string '{time}s', where time is measured in seconds since
+ the beginning of the data collection.
+ current: The current in Amperes as a string.
+ """
+ self._time = float(time[:-1])
+ self._current = float(current)
+
+ @property
+ def time(self):
+ """The time the record was fetched."""
+ return self._time
+
+ @property
+ def current(self):
+ """The amount of current in Amperes measured for the given record."""
+ return self._current
+
+ @classmethod
+ def create_from_record_line(cls, line):
+ """Creates a data record from the line passed in from the output file.
+ """
+ return cls(*line.split(' '))
+
+
+class MonsoonResult(object):
"""An object that contains aggregated data collected during sampling.
Attributes:
@@ -53,19 +83,48 @@ class MonsoonData(object):
# The number of decimal places to round a value to.
ROUND_TO = 6
- def __init__(self, num_samples, sum_currents, hz, voltage, tag=None):
+ def __init__(self, num_samples, sum_currents, hz, voltage, datafile_path):
+ """Creates a new MonsoonResult.
+
+ Args:
+ num_samples: the number of samples collected.
+ sum_currents: the total summation of every current measurement.
+ hz: the number of samples per second.
+ voltage: the voltage used during the test.
+ datafile_path: the path to the monsoon data file.
+ """
self._num_samples = num_samples
self._sum_currents = sum_currents
self._hz = hz
self._voltage = voltage
- self.tag = tag
+ self.tag = datafile_path
+
+ def get_data_points(self):
+ """Returns an iterator of MonsoonDataRecords."""
+ class MonsoonDataIterator:
+ def __init__(self, file):
+ self.file = file
+
+ def __iter__(self):
+ with open(self.file, 'r') as f:
+ for line in f:
+ # Remove the newline character.
+ line.strip()
+ yield MonsoonDataRecord.create_from_record_line(line)
+
+ return MonsoonDataIterator(self.tag)
+
+ @property
+ def num_samples(self):
+ """The number of samples recorded during the test."""
+ return self._num_samples
@property
def average_current(self):
"""Average current in mA."""
- if self._num_samples == 0:
+ if self.num_samples == 0:
return 0
- return round(self._sum_currents * 1000 / self._num_samples,
+ return round(self._sum_currents * 1000 / self.num_samples,
self.ROUND_TO)
@property
@@ -79,8 +138,14 @@ class MonsoonData(object):
"""Total power used."""
return round(self.average_current * self._voltage, self.ROUND_TO)
+ @property
+ def voltage(self):
+ """The voltage during the measurement (in Volts)."""
+ return self._voltage
+
def __str__(self):
return ('avg current: %s\n'
'total charge: %s\n'
- 'total power: %s' % (self.average_current, self.total_charge,
- self.total_power))
+ 'total power: %s\n'
+ 'total samples: %s' % (self.average_current, self.total_charge,
+ self.total_power, self._num_samples))
diff --git a/acts/framework/acts/controllers/monsoon_lib/api/hvpm/monsoon.py b/acts/framework/acts/controllers/monsoon_lib/api/hvpm/monsoon.py
index 6dc72c8f91..f1b03c9114 100644
--- a/acts/framework/acts/controllers/monsoon_lib/api/hvpm/monsoon.py
+++ b/acts/framework/acts/controllers/monsoon_lib/api/hvpm/monsoon.py
@@ -20,7 +20,7 @@ import time
from Monsoon import HVPM
from Monsoon import Operations as op
-from acts.controllers.monsoon_lib.api.common import MonsoonData
+from acts.controllers.monsoon_lib.api.common import MonsoonResult
from acts.controllers.monsoon_lib.api.monsoon import BaseMonsoon
from acts.controllers.monsoon_lib.sampling.engine.assembly_line import AssemblyLineBuilder
from acts.controllers.monsoon_lib.sampling.engine.assembly_line import ThreadAssemblyLine
@@ -137,8 +137,9 @@ class Monsoon(BaseMonsoon):
manager.shutdown()
self._mon.setup_usb(self.serial)
- monsoon_data = MonsoonData(aggregator.num_samples,
- aggregator.sum_currents, hz, voltage)
+ monsoon_data = MonsoonResult(aggregator.num_samples,
+ aggregator.sum_currents, hz, voltage,
+ output_path)
self._log.info('Measurement summary:\n%s', str(monsoon_data))
return monsoon_data
diff --git a/acts/framework/acts/controllers/monsoon_lib/api/lvpm_stock/monsoon.py b/acts/framework/acts/controllers/monsoon_lib/api/lvpm_stock/monsoon.py
index b54b6794fc..e8d116d83f 100644
--- a/acts/framework/acts/controllers/monsoon_lib/api/lvpm_stock/monsoon.py
+++ b/acts/framework/acts/controllers/monsoon_lib/api/lvpm_stock/monsoon.py
@@ -16,7 +16,7 @@
import multiprocessing
import time
-from acts.controllers.monsoon_lib.api.common import MonsoonData
+from acts.controllers.monsoon_lib.api.common import MonsoonResult
from acts.controllers.monsoon_lib.api.lvpm_stock.monsoon_proxy import MonsoonProxy
from acts.controllers.monsoon_lib.api.monsoon import BaseMonsoon
from acts.controllers.monsoon_lib.sampling.engine.assembly_line import AssemblyLineBuilder
@@ -119,8 +119,9 @@ class Monsoon(BaseMonsoon):
manager.shutdown()
- monsoon_data = MonsoonData(aggregator.num_samples,
- aggregator.sum_currents, hz, voltage)
+ monsoon_data = MonsoonResult(aggregator.num_samples,
+ aggregator.sum_currents, hz, voltage,
+ output_path)
self._log.info('Measurement summary:\n%s', str(monsoon_data))
return monsoon_data
diff --git a/acts/framework/acts/controllers/monsoon_lib/api/monsoon.py b/acts/framework/acts/controllers/monsoon_lib/api/monsoon.py
index 4916da2f15..a55fc8a0e0 100644
--- a/acts/framework/acts/controllers/monsoon_lib/api/monsoon.py
+++ b/acts/framework/acts/controllers/monsoon_lib/api/monsoon.py
@@ -160,6 +160,8 @@ class BaseMonsoon(object):
"""Deprecated. Use the connection callbacks instead."""
def on_reconnect():
+ # Make sure the device is connected and available for commands.
+ android_device.wait_for_boot_completion()
android_device.start_services()
# Release wake lock to put device into sleep.
android_device.droid.goToSleepNow()
@@ -180,15 +182,6 @@ class BaseMonsoon(object):
"""Sets the callback to be called when Monsoon reconnects USB."""
self.on_reconnect = callback
- def monsoon_usb_auto(self):
- """Deprecated. Use usb('auto') or usb(PassthroughStates.AUTO) instead.
- """
- self.usb(PassthroughStates.AUTO)
-
- def reconnect_dut(self):
- """Deprecated. Use usb('on') or usb(PassthroughStates.ON) instead."""
- self.usb(PassthroughStates.ON)
-
def take_samples(self, assembly_line):
"""Runs the sampling procedure based on the given assembly line."""
# Sampling is always done in a separate process. Release the Monsoon
diff --git a/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500.py b/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500.py
index e717e8c5e5..0163d16517 100644
--- a/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500.py
+++ b/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500.py
@@ -73,7 +73,6 @@ class TransmissionModes(Enum):
TM2 = 'TM2'
TM3 = 'TM3'
TM4 = 'TM4'
- TM6 = 'TM6'
TM7 = 'TM7'
TM8 = 'TM8'
TM9 = 'TM9'
@@ -122,6 +121,13 @@ class MimoModes(Enum):
MIMO4x4 = 'FOUR'
+class MimoScenario(Enum):
+ """Supportted mimo scenarios"""
+ SCEN1x1 = 'SCELl:FLEXible SUA1,RF1C,RX1,RF1C,TX1'
+ SCEN2x2 = 'TRO:FLEXible SUA1,RF1C,RX1,RF1C,TX1,RF3C,TX2'
+ SCEN4x4 = 'FRO FLEXible SUA1,RF1C,RX1,RF1C,TX1,RF3C,TX2,RF2C,TX3,RF4C,TX4'
+
+
class RrcState(Enum):
"""States to enable/disable rrc."""
RRC_ON = 'ON'
@@ -195,11 +201,14 @@ class Cmw500(abstract_inst.SocketInstrument):
status = self._recv()
return status
- def set_mimo(self):
- """Sets the scenario for the test."""
- # TODO:(ganeshganesh) Create a common function to set mimo modes.
- self.send_and_recv('ROUTe:LTE:SIGN:SCENario:SCELl:FLEXible SUW1,RF1C,'
- 'RX1,RF1C,TX1')
+ def configure_mimo_settings(self, mimo):
+ """Sets the mimo scenario for the test.
+
+ Args:
+ mimo: mimo scenario to set.
+ """
+ cmd = 'ROUTe:LTE:SIGN:SCENario:{}'.format(mimo.value)
+ self.send_and_recv(cmd)
def wait_for_lte_state_change(self, timeout=20):
"""Waits until the state of LTE changes.
@@ -244,7 +253,7 @@ class Cmw500(abstract_inst.SocketInstrument):
else:
raise CmwError('Failure in setting up/detaching connection')
- def wait_for_connected_state(self, timeout=120):
+ def wait_for_attached_state(self, timeout=120):
"""Attach the controller with device.
Args:
@@ -263,10 +272,22 @@ class Cmw500(abstract_inst.SocketInstrument):
else:
raise CmwError('Device could not be attached')
- conn_state = self.send_and_recv('SENSe:LTE:SIGN:RRCState?')
+ def wait_for_connected_state(self, timeout=120):
+ """Checks if controller connected with device.
+
+ Args:
+ timeout: timeout for phone to be in connnected state.
- if conn_state == LTE_CONN_RESP:
- self._logger.debug('Call box connected with device')
+ Raises:
+ CmwError on time out.
+ """
+ end_time = time.time() + timeout
+ while time.time() <= end_time:
+ conn_state = self.send_and_recv('SENSe:LTE:SIGN:RRCState?')
+
+ if conn_state == LTE_CONN_RESP:
+ self._logger.debug('Call box connected with device')
+ break
else:
raise CmwError('Call box could not be connected with device')
@@ -280,7 +301,7 @@ class Cmw500(abstract_inst.SocketInstrument):
return self.send_and_recv('*IDN?')
def disconnect(self):
- """Detach controller from device and switch to local mode."""
+ """Disconnect controller from device and switch to local mode."""
self.switch_lte_signalling(LteState.LTE_OFF)
self.close_remote_mode()
self._close_socket()
@@ -289,6 +310,10 @@ class Cmw500(abstract_inst.SocketInstrument):
"""Exits remote mode to local mode."""
self.send_and_recv('&GTL')
+ def detach(self):
+ """Detach callbox and controller."""
+ self.send_and_recv('CALL:LTE:SIGN:PSWitched:ACTion DETach')
+
@property
def rrc_connection(self):
"""Gets the RRC connection state."""
@@ -570,19 +595,18 @@ class BaseStation(object):
self._bts, mode.value)
self._cmw.send_and_recv(cmd)
- # TODO: (@sairamganesh) find a common way to set parameters for rmc and
- # user defined channels(udch) scheduling.
@property
- def rmc_rb_configuration_dl(self):
+ def rb_configuration_dl(self):
"""Gets rmc's rb configuration for down link. This function returns
Number of Resource blocks, Resource block position and Modulation type.
"""
- cmd = 'CONFigure:LTE:SIGN:CONNection:{}:RMC:DL?'.format(self._bts)
+ cmd = 'CONFigure:LTE:SIGN:CONNection:{}:{}:DL?'.format(
+ self._bts, self.scheduling_mode)
return self._cmw.send_and_recv(cmd)
- @rmc_rb_configuration_dl.setter
- def rmc_rb_configuration_dl(self, rb_config):
- """Sets the rb configuration for down link with scheduling type RMC.
+ @rb_configuration_dl.setter
+ def rb_configuration_dl(self, rb_config):
+ """Sets the rb configuration for down link for scheduling type.
Args:
rb_config: Tuple containing Number of resource blocks, resource
@@ -591,100 +615,59 @@ class BaseStation(object):
Raises:
ValueError: If tuple unpacking fails.
"""
- rb, rb_pos, modulation = rb_config
+ if self.scheduling_mode == 'RMC':
+ rb, rb_pos, modulation = rb_config
- cmd = ('CONFigure:LTE:SIGN:CONNection:{}:RMC:DL {},{},'
- '{}'.format(self._bts, rb, rb_pos, modulation))
- self._cmw.send_and_recv(cmd)
+ cmd = ('CONFigure:LTE:SIGN:CONNection:{}:RMC:DL {},{},'
+ '{}'.format(self._bts, rb, rb_pos, modulation))
+ self._cmw.send_and_recv(cmd)
- @property
- def rmc_rb_configuration_ul(self):
- """Gets rmc's rb configuration for up link. This function returns
- Number of Resource blocks, Resource block position and Modulation type.
- """
- cmd = 'CONFigure:LTE:SIGN:CONNection:{}:RMC:UL?'.format(self._bts)
- return self._cmw.send_and_recv(cmd)
+ elif self.scheduling_mode == 'UDCH':
+ rb, start_rb, modulation, tbs = rb_config
- @rmc_rb_configuration_ul.setter
- def rmc_rb_configuration_ul(self, rb_config):
- """Sets the rb configuration for up link with scheduling type RMC.
+ if not 0 <= rb <= 26:
+ raise ValueError('rb should be between 0 and 26 inclusive.')
- Args:
- rb_config: Tuple containing Number of resource blocks, resource
- block position and modulation type.
-
- Raises:
- ValueError: If tuple unpacking fails.
- """
- rb, rb_pos, modulation = rb_config
-
- cmd = ('CONFigure:LTE:SIGN:CONNection:{}:RMC:UL {},{},'
- '{}'.format(self._bts, rb, rb_pos, modulation))
- self._cmw.send_and_recv(cmd)
+ cmd = ('CONFigure:LTE:SIGN:CONNection:{}:UDCHannels:DL {},{},'
+ '{},{}'.format(self._bts, rb, start_rb, modulation, tbs))
+ self._cmw.send_and_recv(cmd)
@property
- def udch_rb_configuration_dl(self):
- """Gets udch's rb configuration for down link. This function returns
- Number of Resource blocks, Resource block position, Modulation type and
- tbs.
+ def rb_configuration_ul(self):
+ """Gets rb configuration for up link. This function returns
+ Number of Resource blocks, Resource block position and Modulation type.
"""
- cmd = 'CONFigure:LTE:SIGN:CONNection:{}:UDCH:DL?'.format(self._bts)
+ cmd = 'CONFigure:LTE:SIGN:CONNection:{}:{}:UL?'.format(
+ self._bts, self.scheduling_mode)
return self._cmw.send_and_recv(cmd)
- @udch_rb_configuration_dl.setter
- def udch_rb_configuration_dl(self, rb_config):
- """Sets the rb configuration for down link with scheduling type RMC.
+ @rb_configuration_ul.setter
+ def rb_configuration_ul(self, rb_config):
+ """Sets the rb configuration for down link for scheduling mode.
Args:
rb_config: Tuple containing Number of resource blocks, resource
- block position, modulation type and tbs value.
+ block position and modulation type.
Raises:
ValueError: If tuple unpacking fails.
"""
- rb, start_rb, md_type, tbs = rb_config
-
- if rb not in range(0, 51):
- raise ValueError('rb should be between 0 and 50 inclusive.')
-
- if not isinstance(md_type, ModulationType):
- raise ValueError('md_type should be the instance of ModulationType.')
-
- cmd = ('CONFigure:LTE:SIGN:CONNection:{}:udch:DL {},{},'
- '{},{}'.format(self._bts, rb, start_rb, md_type, tbs))
- self._cmw.send_and_recv(cmd)
-
- @property
- def udch_rb_configuration_ul(self):
- """Gets udch's rb configuration for up link. This function returns
- Number of Resource blocks, Resource block position, Modulation type
- and tbs.
- """
- cmd = 'CONFigure:LTE:SIGN:CONNection:{}:UDCH:UL?'.format(self._bts)
- return self._cmw.send_and_recv(cmd)
-
- @udch_rb_configuration_ul.setter
- def udch_rb_configuration_ul(self, rb_config):
- """Sets the rb configuration for up link with scheduling type RMC.
+ if self.scheduling_mode == 'RMC':
+ rb, rb_pos, modulation = rb_config
- Args:
- rb_config: Tuple containing Number of resource blocks, resource
- block position, modulation type and tbs value.
-
- Raises:
- ValueError: If tuple unpacking fails/Specified out of range.
- """
- rb, start_rb, md_type, tbs = rb_config
+ cmd = ('CONFigure:LTE:SIGN:CONNection:{}:RMC:UL {},{},'
+ '{}'.format(self._bts, rb, rb_pos, modulation))
+ self._cmw.send_and_recv(cmd)
- if rb not in range(0, 51):
- raise ValueError('rb should be between 0 and 50 inclusive.')
+ elif self.scheduling_mode == 'UDCH':
+ rb, start_rb, modulation, tbs = rb_config
- if not isinstance(md_type, ModulationType):
- raise ValueError('md_type should be the instance of ModulationType.')
+ if not 0 <= rb <= 26:
+ raise ValueError('rb should be between 0 and 26 inclusive.')
- cmd = ('CONFigure:LTE:SIGN:CONNection:{}:udch:UL {},{},'
- '{},{}'.format(self._bts, rb, start_rb, md_type, tbs))
- self._cmw.send_and_recv(cmd)
+ cmd = ('CONFigure:LTE:SIGN:CONNection:{}:UDCHannels:UL {},{},'
+ '{},{}'.format(self._bts, rb, start_rb, modulation, tbs))
+ self._cmw.send_and_recv(cmd)
@property
def rb_position_dl(self):
@@ -767,6 +750,8 @@ class BaseStation(object):
Args:
num_antenna: Count of number of dl antennas to use.
"""
+ if not isinstance(num_antenna, MimoModes):
+ raise ValueError('num_antenna should be an instance of MimoModes.')
cmd = 'CONFigure:LTE:SIGN:CONNection:{}:NENBantennas {}'.format(
self._bts, num_antenna)
self._cmw.send_and_recv(cmd)
diff --git a/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500_cellular_simulator.py b/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500_cellular_simulator.py
index 6c76e62f7a..64d127b808 100644
--- a/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500_cellular_simulator.py
+++ b/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500_cellular_simulator.py
@@ -13,11 +13,34 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+import time
+
from acts.controllers.rohdeschwarz_lib import cmw500
from acts.controllers import cellular_simulator as cc
-
-
-class CMW500CellularSimulator:
+from acts.test_utils.power.tel_simulations import LteSimulation
+
+CMW_TM_MAPPING = {
+ LteSimulation.TransmissionMode.TM1: cmw500.TransmissionModes.TM1,
+ LteSimulation.TransmissionMode.TM2: cmw500.TransmissionModes.TM2,
+ LteSimulation.TransmissionMode.TM3: cmw500.TransmissionModes.TM3,
+ LteSimulation.TransmissionMode.TM4: cmw500.TransmissionModes.TM4,
+ LteSimulation.TransmissionMode.TM7: cmw500.TransmissionModes.TM7,
+ LteSimulation.TransmissionMode.TM8: cmw500.TransmissionModes.TM8,
+ LteSimulation.TransmissionMode.TM9: cmw500.TransmissionModes.TM9
+}
+
+CMW_SCH_MAPPING = {
+ LteSimulation.SchedulingMode.STATIC: cmw500.SchedulingMode.USERDEFINEDCH
+}
+
+CMW_MIMO_MAPPING = {
+ LteSimulation.MimoMode.MIMO_1x1: cmw500.MimoModes.MIMO1x1,
+ LteSimulation.MimoMode.MIMO_2x2: cmw500.MimoModes.MIMO2x2,
+ LteSimulation.MimoMode.MIMO_4x4: cmw500.MimoModes.MIMO4x4
+}
+
+
+class CMW500CellularSimulator(cc.AbstractCellularSimulator):
""" A cellular simulator for telephony simulations based on the CMW 500
controller. """
@@ -40,24 +63,43 @@ class CMW500CellularSimulator:
ip_address: the ip address of the CMW500
port: the port number for the CMW500 controller
"""
+ super().__init__()
+
try:
self.cmw = cmw500.Cmw500(ip_address, port)
except cmw500.CmwError:
raise cc.CellularSimulatorError('Could not connect to CMW500.')
+ self.bts = None
+ self.dl_modulation = None
+ self.ul_modulation = None
+
def destroy(self):
""" Sends finalization commands to the cellular equipment and closes
the connection. """
- raise NotImplementedError()
+ self.cmw.disconnect()
def setup_lte_scenario(self):
""" Configures the equipment for an LTE simulation. """
- raise NotImplementedError()
+ self.bts = [self.cmw.get_base_station()]
+ self.cmw.switch_lte_signalling(cmw500.LteState.LTE_ON)
def setup_lte_ca_scenario(self):
""" Configures the equipment for an LTE with CA simulation. """
raise NotImplementedError()
+ def set_lte_rrc_state_change_timer(self, enabled, time=10):
+ """ Configures the LTE RRC state change timer.
+
+ Args:
+ enabled: a boolean indicating if the timer should be on or off.
+ time: time in seconds for the timer to expire
+ """
+ # Setting this method to pass instead of raising an exception as it
+ # it is required by LTE sims.
+ # TODO (b/141838145): Implement RRC status change timer for CMW500.
+ pass
+
def set_band(self, bts_index, band):
""" Sets the band for the indicated base station.
@@ -65,7 +107,25 @@ class CMW500CellularSimulator:
bts_index: the base station number
band: the new band
"""
- raise NotImplementedError()
+ bts = self.bts[bts_index]
+ bts.duplex_mode = self.get_duplex_mode(band)
+ band = 'OB' + band
+ bts.band = band
+ self.log.debug('Band set to {}'.format(band))
+
+ def get_duplex_mode(self, band):
+ """ Determines if the band uses FDD or TDD duplex mode
+
+ Args:
+ band: a band number
+
+ Returns:
+ an variable of class DuplexMode indicating if band is FDD or TDD
+ """
+ if 33 <= int(band) <= 46:
+ return cmw500.DuplexMode.TDD
+ else:
+ return cmw500.DuplexMode.FDD
def set_input_power(self, bts_index, input_power):
""" Sets the input power for the indicated base station.
@@ -83,7 +143,8 @@ class CMW500CellularSimulator:
bts_index: the base station number
output_power: the new output power
"""
- raise NotImplementedError()
+ bts = self.bts[bts_index]
+ bts.downlink_power_level = output_power
def set_tdd_config(self, bts_index, tdd_config):
""" Sets the tdd configuration number for the indicated base station.
@@ -92,7 +153,7 @@ class CMW500CellularSimulator:
bts_index: the base station number
tdd_config: the new tdd configuration number
"""
- raise NotImplementedError()
+ self.bts[bts_index].uldl_configuration = tdd_config
def set_bandwidth(self, bts_index, bandwidth):
""" Sets the bandwidth for the indicated base station.
@@ -101,7 +162,23 @@ class CMW500CellularSimulator:
bts_index: the base station number
bandwidth: the new bandwidth
"""
- raise NotImplementedError()
+ bts = self.bts[bts_index]
+
+ if bandwidth == 20:
+ bts.bandwidth = cmw500.LteBandwidth.BANDWIDTH_20MHz
+ elif bandwidth == 15:
+ bts.bandwidth = cmw500.LteBandwidth.BANDWIDTH_15MHz
+ elif bandwidth == 10:
+ bts.bandwidth = cmw500.LteBandwidth.BANDWIDTH_10MHz
+ elif bandwidth == 5:
+ bts.bandwidth = cmw500.LteBandwidth.BANDWIDTH_5MHz
+ elif bandwidth == 3:
+ bts.bandwidth = cmw500.LteBandwidth.BANDWIDTH_3MHz
+ elif bandwidth == 1.4:
+ bts.bandwidth = cmw500.LteBandwidth.BANDWIDTH_1MHz
+ else:
+ msg = 'Bandwidth {} MHz is not valid for LTE'.format(bandwidth)
+ raise ValueError(msg)
def set_downlink_channel_number(self, bts_index, channel_number):
""" Sets the downlink channel number for the indicated base station.
@@ -110,7 +187,9 @@ class CMW500CellularSimulator:
bts_index: the base station number
channel_number: the new channel number
"""
- raise NotImplementedError()
+ bts = self.bts[bts_index]
+ bts.dl_channel = channel_number
+ self.log.debug('Downlink Channel set to {}'.format(bts.dl_channel))
def set_mimo_mode(self, bts_index, mimo_mode):
""" Sets the mimo mode for the indicated base station.
@@ -119,30 +198,103 @@ class CMW500CellularSimulator:
bts_index: the base station number
mimo_mode: the new mimo mode
"""
- raise NotImplementedError()
-
- def set_transmission_mode(self, bts_index, transmission_mode):
+ bts = self.bts[bts_index]
+ mimo_mode = CMW_MIMO_MAPPING[mimo_mode]
+ if mimo_mode == cmw500.MimoModes.MIMO1x1:
+ self.cmw.configure_mimo_settings(cmw500.MimoScenario.SCEN1x1)
+ bts.dl_antenna = cmw500.MimoModes.MIMO1x1
+
+ elif mimo_mode == cmw500.MimoModes.MIMO2x2:
+ self.cmw.configure_mimo_settings(cmw500.MimoScenario.SCEN2x2)
+ bts.dl_antenna = cmw500.MimoModes.MIMO2x2
+
+ elif mimo_mode == cmw500.MimoModes.MIMO4x4:
+ self.cmw.configure_mimo_settings(cmw500.MimoScenario.SCEN4x4)
+ bts.dl_antenna = cmw500.MimoModes.MIMO4x4
+ else:
+ RuntimeError('The requested MIMO mode is not supported.')
+
+ def set_transmission_mode(self, bts_index, tmode):
""" Sets the transmission mode for the indicated base station.
Args:
bts_index: the base station number
- transmission_mode: the new transmission mode
+ tmode: the new transmission mode
"""
- raise NotImplementedError()
-
- def set_scheduling_mode(self, bts_index, scheduling_mode, mcs_dl, mcs_ul,
- nrb_dl, nrb_ul):
+ bts = self.bts[bts_index]
+
+ tmode = CMW_TM_MAPPING[tmode]
+ if (tmode in [
+ cmw500.TransmissionModes.TM1,
+ cmw500.TransmissionModes.TM7
+ ] and bts.dl_antenna != cmw500.MimoModes.MIMO1x1):
+ bts.transmode = tmode
+ elif (tmode in cmw500.TransmissionModes.__members__ and
+ bts.dl_antenna != cmw500.MimoModes.MIMO2x2):
+ bts.transmode = tmode
+ elif (tmode in [
+ cmw500.TransmissionModes.TM2,
+ cmw500.TransmissionModes.TM3,
+ cmw500.TransmissionModes.TM4,
+ cmw500.TransmissionModes.TM6,
+ cmw500.TransmissionModes.TM9
+ ] and bts.dl_antenna == cmw500.MimoModes.MIMO4x4):
+ bts.transmode = tmode
+
+ else:
+ raise ValueError('Transmission modes should support the current '
+ 'mimo mode')
+
+ def set_scheduling_mode(self, bts_index, scheduling, mcs_dl=None,
+ mcs_ul=None, nrb_dl=None, nrb_ul=None):
""" Sets the scheduling mode for the indicated base station.
Args:
- bts_index: the base station number
- scheduling_mode: the new scheduling mode
- mcs_dl: Downlink MCS (only for STATIC scheduling)
- mcs_ul: Uplink MCS (only for STATIC scheduling)
- nrb_dl: Number of RBs for downlink (only for STATIC scheduling)
- nrb_ul: Number of RBs for uplink (only for STATIC scheduling)
+ bts_index: the base station number.
+ scheduling: the new scheduling mode.
+ mcs_dl: Downlink MCS.
+ mcs_ul: Uplink MCS.
+ nrb_dl: Number of RBs for downlink.
+ nrb_ul: Number of RBs for uplink.
"""
- raise NotImplementedError()
+ bts = self.bts[bts_index]
+ bts.scheduling_mode = CMW_SCH_MAPPING[scheduling]
+
+ if not self.ul_modulation and self.dl_modulation:
+ raise ValueError('Modulation should be set prior to scheduling '
+ 'call')
+
+ if scheduling == cmw500.SchedulingMode.RMC:
+
+ if not nrb_ul and nrb_dl:
+ raise ValueError('nrb_ul and nrb dl should not be none')
+
+ bts.rb_configuration_ul = (nrb_ul, self.ul_modulation, 'KEEP')
+ self.log.info('ul rb configurations set to {}'.format(
+ bts.rb_configuration_ul))
+
+ time.sleep(1)
+
+ self.log.debug('Setting rb configurations for down link')
+ bts.rb_configuration_dl = (nrb_dl, self.dl_modulation, 'KEEP')
+ self.log.info('dl rb configurations set to {}'.format(
+ bts.rb_configuration_ul))
+
+ elif scheduling == cmw500.SchedulingMode.USERDEFINEDCH:
+
+ if not all([nrb_ul, nrb_dl, mcs_dl, mcs_ul]):
+ raise ValueError('All parameters are mandatory.')
+
+ bts.rb_configuration_ul = (nrb_ul, 0, self.ul_modulation,
+ mcs_ul)
+ self.log.info('ul rb configurations set to {}'.format(
+ bts.rb_configuration_ul))
+
+ time.sleep(1)
+
+ bts.rb_configuration_dl = (nrb_dl, 0, self.dl_modulation, mcs_dl)
+ self.log.info('dl rb configurations set to {}'.format(
+ bts.rb_configuration_dl))
def set_enabled_for_ca(self, bts_index, enabled):
""" Enables or disables the base station during carrier aggregation.
@@ -160,7 +312,12 @@ class CMW500CellularSimulator:
bts_index: the base station number
modulation: the new DL modulation
"""
- raise NotImplementedError()
+
+ # This function is only used to store the values of modulation to
+ # be inline with abstract class signature.
+ self.dl_modulation = modulation
+ self.log.warning('Modulation config stored but not applied until '
+ 'set_scheduling_mode called.')
def set_ul_modulation(self, bts_index, modulation):
""" Sets the UL modulation for the indicated base station.
@@ -169,7 +326,11 @@ class CMW500CellularSimulator:
bts_index: the base station number
modulation: the new UL modulation
"""
- raise NotImplementedError()
+ # This function is only used to store the values of modulation to
+ # be inline with abstract class signature.
+ self.ul_modulation = modulation
+ self.log.warning('Modulation config stored but not applied until '
+ 'set_scheduling_mode called.')
def set_tbs_pattern_on(self, bts_index, tbs_pattern_on):
""" Enables or disables TBS pattern in the indicated base station.
@@ -178,7 +339,8 @@ class CMW500CellularSimulator:
bts_index: the base station number
tbs_pattern_on: the new TBS pattern setting
"""
- raise NotImplementedError()
+ # TODO (b/143918664): CMW500 doesn't have an equivalent setting.
+ pass
def lte_attach_secondary_carriers(self):
""" Activates the secondary carriers for CA. Requires the DUT to be
@@ -192,7 +354,7 @@ class CMW500CellularSimulator:
timeout: after this amount of time the method will raise a
CellularSimulatorError exception. Default is 120 seconds.
"""
- raise NotImplementedError()
+ self.cmw.wait_for_attached_state(timeout=timeout)
def wait_until_communication_state(self, timeout=120):
""" Waits until the DUT is in Communication state.
@@ -201,7 +363,7 @@ class CMW500CellularSimulator:
timeout: after this amount of time the method will raise a
CellularSimulatorError exception. Default is 120 seconds.
"""
- raise NotImplementedError()
+ self.cmw.wait_for_connected_state(timeout=timeout)
def wait_until_idle_state(self, timeout=120):
""" Waits until the DUT is in Idle state.
@@ -214,7 +376,7 @@ class CMW500CellularSimulator:
def detach(self):
""" Turns off all the base stations so the DUT loose connection."""
- raise NotImplementedError()
+ self.cmw.detach()
def stop(self):
""" Stops current simulation. After calling this method, the simulator
diff --git a/acts/framework/acts/test_utils/abstract_devices/bluetooth_handsfree_abstract_device.py b/acts/framework/acts/test_utils/abstract_devices/bluetooth_handsfree_abstract_device.py
index bf4d549c5a..bb63bd9934 100644
--- a/acts/framework/acts/test_utils/abstract_devices/bluetooth_handsfree_abstract_device.py
+++ b/acts/framework/acts/test_utils/abstract_devices/bluetooth_handsfree_abstract_device.py
@@ -14,8 +14,11 @@
# License for the specific language governing permissions and limitations under
# the License.
import inspect
-
+import time
+from acts import asserts
from acts.controllers.buds_lib.dev_utils import apollo_sink_events
+from acts.test_utils.bt.bt_constants import bt_default_timeout
+
def validate_controller(controller, abstract_device_class):
@@ -29,8 +32,8 @@ def validate_controller(controller, abstract_device_class):
NotImplementedError: if controller is missing one or more methods.
"""
ctlr_methods = inspect.getmembers(controller, predicate=callable)
- reqd_methods = inspect.getmembers(abstract_device_class,
- predicate=inspect.ismethod)
+ reqd_methods = inspect.getmembers(
+ abstract_device_class, predicate=inspect.ismethod)
expected_func_names = {method[0] for method in reqd_methods}
controller_func_names = {method[0] for method in ctlr_methods}
@@ -47,8 +50,7 @@ def validate_controller(controller, abstract_device_class):
if inspect.signature(controller_func) != required_signature:
raise NotImplementedError(
'Method {} must have the signature {}{}.'.format(
- controller_func.__qualname__,
- controller_func.__name__,
+ controller_func.__qualname__, controller_func.__name__,
required_signature))
@@ -58,6 +60,7 @@ class BluetoothHandsfreeAbstractDevice:
Desired controller classes should have a corresponding Bluetooth handsfree
abstract device class defined in this module.
"""
+
@property
def mac_address(self):
raise NotImplementedError
@@ -115,7 +118,8 @@ class PixelBudsBluetoothHandsfreeAbstractDevice(
return self.pixel_buds_controller.bluetooth_address
def accept_call(self):
- return self.pixel_buds_controller.cmd(self.format_cmd('EventUsrAnswer'))
+ return self.pixel_buds_controller.cmd(
+ self.format_cmd('EventUsrAnswer'))
def end_call(self):
return self.pixel_buds_controller.cmd(
@@ -147,7 +151,8 @@ class PixelBudsBluetoothHandsfreeAbstractDevice(
self.format_cmd('EventUsrAvrcpSkipBackward'))
def reject_call(self):
- return self.pixel_buds_controller.cmd(self.format_cmd('EventUsrReject'))
+ return self.pixel_buds_controller.cmd(
+ self.format_cmd('EventUsrReject'))
def volume_down(self):
return self.pixel_buds_controller.volume('Down')
@@ -158,7 +163,6 @@ class PixelBudsBluetoothHandsfreeAbstractDevice(
class EarstudioReceiverBluetoothHandsfreeAbstractDevice(
BluetoothHandsfreeAbstractDevice):
-
def __init__(self, earstudio_controller):
self.earstudio_controller = earstudio_controller
@@ -205,7 +209,6 @@ class EarstudioReceiverBluetoothHandsfreeAbstractDevice(
class JaybirdX3EarbudsBluetoothHandsfreeAbstractDevice(
BluetoothHandsfreeAbstractDevice):
-
def __init__(self, jaybird_controller):
self.jaybird_controller = jaybird_controller
@@ -252,13 +255,23 @@ class JaybirdX3EarbudsBluetoothHandsfreeAbstractDevice(
class AndroidHeadsetBluetoothHandsfreeAbstractDevice(
BluetoothHandsfreeAbstractDevice):
-
def __init__(self, ad_controller):
self.ad_controller = ad_controller
@property
def mac_address(self):
- return self.ad_controller.droid.bluetoothGetLocalAddress()
+ """Getting device mac with more stability ensurance.
+
+ Sometime, getting mac address is flaky that it returns None. Adding a
+ loop to add more ensurance of getting correct mac address.
+ """
+ device_mac = None
+ start_time = time.time()
+ end_time = start_time + bt_default_timeout
+ while not device_mac and time.time() < end_time:
+ device_mac = self.ad_controller.droid.bluetoothGetLocalAddress()
+ asserts.assert_true(device_mac, 'Can not get the MAC address')
+ return device_mac
def accept_call(self):
return self.ad_controller.droid.telecomAcceptRingingCall(None)
@@ -271,8 +284,7 @@ class AndroidHeadsetBluetoothHandsfreeAbstractDevice(
return self.ad_controller.droid.bluetoothMakeDiscoverable()
def next_track(self):
- return (self.ad_controller.droid.
- bluetoothMediaPassthrough("skipNext"))
+ return (self.ad_controller.droid.bluetoothMediaPassthrough("skipNext"))
def pause(self):
return self.ad_controller.droid.bluetoothMediaPassthrough("pause")
@@ -287,13 +299,15 @@ class AndroidHeadsetBluetoothHandsfreeAbstractDevice(
return self.ad_controller.droid.bluetoothToggleState(True)
def previous_track(self):
- return (self.ad_controller.droid.
- bluetoothMediaPassthrough("skipPrev"))
+ return (self.ad_controller.droid.bluetoothMediaPassthrough("skipPrev"))
def reject_call(self):
return self.ad_controller.droid.telecomCallDisconnect(
self.ad_controller.droid.telecomCallGetCallIds()[0])
+ def reset(self):
+ return self.ad_controller.droid.bluetoothFactoryReset()
+
def volume_down(self):
target_step = self.ad_controller.droid.getMediaVolume() - 1
target_step = max(target_step, 0)
diff --git a/acts/framework/acts/test_utils/bt/BtEnum.py b/acts/framework/acts/test_utils/bt/BtEnum.py
index a2010d0074..b9fe6e23e1 100644
--- a/acts/framework/acts/test_utils/bt/BtEnum.py
+++ b/acts/framework/acts/test_utils/bt/BtEnum.py
@@ -103,3 +103,11 @@ class BluetoothPriorityLevel(Enum):
PRIORITY_ON = 100
PRIORITY_OFF = 0
PRIORITY_UNDEFINED = -1
+
+class BluetoothA2dpCodecType(Enum):
+ SBC = 0
+ AAC = 1
+ APTX = 2
+ APTX_HD = 3
+ LDAC = 4
+ MAX = 5
diff --git a/acts/framework/acts/test_utils/bt/bt_power_test_utils.py b/acts/framework/acts/test_utils/bt/bt_power_test_utils.py
index c9c60723e9..628c3375d1 100644
--- a/acts/framework/acts/test_utils/bt/bt_power_test_utils.py
+++ b/acts/framework/acts/test_utils/bt/bt_power_test_utils.py
@@ -14,93 +14,144 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import logging
import time
+import acts.test_utils.bt.BleEnum as bleenum
+import acts.test_utils.instrumentation.instrumentation_command_builder as icb
-from acts.test_utils.wifi import wifi_power_test_utils as wputils
-from acts.test_utils.bt.bt_test_utils import enable_bluetooth
-from acts.test_utils.bt.bt_test_utils import disable_bluetooth
-
-BT_BASE_UUID = '00000000-0000-1000-8000-00805F9B34FB'
-BT_CLASSICAL_DATA = [1, 2, 3]
BLE_LOCATION_SCAN_ENABLE = 'settings put global ble_scan_always_enabled 1'
BLE_LOCATION_SCAN_DISABLE = 'settings put global ble_scan_always_enabled 0'
-START_PMC_CMD = 'am start -n com.android.pmc/com.android.pmc.PMCMainActivity'
-PMC_VERBOSE_CMD = 'setprop log.tag.PMC VERBOSE'
-PMC_BASE_SCAN = 'am broadcast -a com.android.pmc.BLESCAN --es ScanMode '
+SCREEN_WAIT_TIME = 1
-def phone_setup_for_BT(dut, bt_on, ble_on, screen_status):
- """Sets the phone and Bluetooth in the desired state
+class MediaControl(object):
+ """Media control using adb shell for power testing.
- Args:
- dut: object of the android device under test
- bt_on: Enable/Disable BT
- ble_on: Enable/Disable BLE
- screen_status: screen ON or OFF
+ Object to control media play status using adb.
"""
- # Initialize the dut to rock-bottom state
- wputils.dut_rockbottom(dut)
- time.sleep(2)
-
- # Check if we are enabling a background scan
- # TODO: Turn OFF cellular wihtout having to turn ON airplane mode
- if bt_on == 'OFF' and ble_on == 'ON':
- dut.adb.shell(BLE_LOCATION_SCAN_ENABLE)
- dut.droid.connectivityToggleAirplaneMode(False)
- time.sleep(2)
-
- # Turn ON/OFF BT
- if bt_on == 'ON':
- enable_bluetooth(dut.droid, dut.ed)
- dut.log.info('BT is ON')
- else:
- disable_bluetooth(dut.droid)
- dut.droid.bluetoothDisableBLE()
- dut.log.info('BT is OFF')
- time.sleep(2)
-
- # Turn ON/OFF BLE
- if ble_on == 'ON':
- dut.droid.bluetoothEnableBLE()
- dut.log.info('BLE is ON')
- else:
- dut.droid.bluetoothDisableBLE()
- dut.log.info('BLE is OFF')
- time.sleep(2)
-
- # Set the desired screen status
- if screen_status == 'OFF':
- dut.droid.goToSleepNow()
- dut.log.info('Screen is OFF')
- time.sleep(2)
-
-
-def start_pmc_ble_scan(dut,
- scan_mode,
- offset_start,
- scan_time,
- idle_time=None,
- num_reps=1):
- """Starts a generic BLE scan via the PMC app
+
+ def __init__(self, android_device, music_file):
+ """Initialize the media_control class.
+
+ Args:
+ android_dut: android_device object
+ music_file: location of the music file
+ """
+ self.android_device = android_device
+ self.music_file = music_file
+
+ def player_on_foreground(self):
+ """Turn on screen and make sure media play is on foreground
+
+ All media control keycode only works when screen is on and media player
+ is on the foreground. Turn off screen first and turn it on to make sure
+ all operation is based on the same screen status. Otherwise, 'MENU' key
+ would block command to be sent.
+ """
+ self.android_device.droid.goToSleepNow()
+ time.sleep(SCREEN_WAIT_TIME)
+ self.android_device.droid.wakeUpNow()
+ time.sleep(SCREEN_WAIT_TIME)
+ self.android_device.send_keycode('MENU')
+ time.sleep(SCREEN_WAIT_TIME)
+
+ def play(self):
+ """Start playing music.
+
+ """
+ self.player_on_foreground()
+ PLAY = 'am start -a android.intent.action.VIEW -d file://{} -t audio/wav'.format(
+ self.music_file)
+ self.android_device.adb.shell(PLAY)
+
+ def pause(self):
+ """Pause music.
+
+ """
+ self.player_on_foreground()
+ self.android_device.send_keycode('MEDIA_PAUSE')
+
+ def resume(self):
+ """Pause music.
+
+ """
+ self.player_on_foreground()
+ self.android_device.send_keycode('MEDIA_PLAY')
+
+ def stop(self):
+ """Stop music and close media play.
+
+ """
+ self.player_on_foreground()
+ self.android_device.send_keycode('MEDIA_STOP')
+
+
+def start_apk_ble_adv(dut, adv_mode, adv_power_level, adv_duration):
+ """Trigger BLE advertisement from power-test.apk.
Args:
- dut: object of the android device under test
- scan mode: desired BLE scan type
- offset_start: Time delay in seconds before scan starts
- scan_time: active scan time
- idle_time: iddle time (i.e., no scans occuring)
- num_reps: Number of repetions of the ative+idle scan sequence
+ dut: Android device under test, type AndroidDevice obj
+ adv_mode: The BLE advertisement mode.
+ {0: 'LowPower', 1: 'Balanced', 2: 'LowLatency'}
+ adv_power_leve: The BLE advertisement TX power level.
+ {0: 'UltraLowTXPower', 1: 'LowTXPower', 2: 'MediumTXPower,
+ 3: HighTXPower}
+ adv_duration: duration of advertisement in seconds, type int
"""
- scan_dur = scan_time
- if not idle_time:
- idle_time = 0.2 * scan_time
- scan_dur = 0.8 * scan_time
-
- first_part_msg = '%s%s --es StartTime %d --es ScanTime %d' % (
- PMC_BASE_SCAN, scan_mode, offset_start, scan_dur)
- msg = '%s --es NoScanTime %d --es Repetitions %d' % (first_part_msg,
- idle_time, num_reps)
+ adv_duration = str(adv_duration) + 's'
+ builder = icb.InstrumentationTestCommandBuilder.default()
+ builder.add_test_class(
+ "com.google.android.device.power.tests.ble.BleAdvertise")
+ builder.set_manifest_package("com.google.android.device.power")
+ builder.set_runner("androidx.test.runner.AndroidJUnitRunner")
+ builder.add_key_value_param("cool-off-duration", "0s")
+ builder.add_key_value_param("idle-duration", "0s")
+ builder.add_key_value_param(
+ "com.android.test.power.receiver.ADVERTISE_MODE", adv_mode)
+ builder.add_key_value_param("com.android.test.power.receiver.POWER_LEVEL",
+ adv_power_level)
+ builder.add_key_value_param(
+ "com.android.test.power.receiver.ADVERTISING_DURATION", adv_duration)
+
+ adv_command = builder.build() + ' &'
+ logging.info('Start BLE {} at {} for {} seconds'.format(
+ bleenum.AdvertiseSettingsAdvertiseMode(adv_mode).name,
+ bleenum.AdvertiseSettingsAdvertiseTxPower(adv_power_level).name,
+ adv_duration))
+ dut.adb.shell_nb(adv_command)
+
+
+def start_apk_ble_scan(dut, scan_mode, scan_duration):
+ """Build the command to trigger BLE scan from power-test.apk.
- dut.log.info('Sent BLE scan broadcast message: %s', msg)
- dut.adb.shell(msg)
+ Args:
+ dut: Android device under test, type AndroidDevice obj
+ scan_mode: The BLE scan mode.
+ {0: 'LowPower', 1: 'Balanced', 2: 'LowLatency', -1: 'Opportunistic'}
+ scan_duration: duration of scan in seconds, type int
+ Returns:
+ adv_command: the command for BLE scan
+ """
+ scan_duration = str(scan_duration) + 's'
+ builder = icb.InstrumentationTestCommandBuilder.default()
+ builder.add_test_class("com.google.android.device.power.tests.ble.BleScan")
+ builder.set_manifest_package("com.google.android.device.power")
+ builder.set_runner("androidx.test.runner.AndroidJUnitRunner")
+ builder.add_key_value_param("cool-off-duration", "0s")
+ builder.add_key_value_param("idle-duration", "0s")
+ builder.add_key_value_param("com.android.test.power.receiver.SCAN_MODE",
+ scan_mode)
+ builder.add_key_value_param("com.android.test.power.receiver.MATCH_MODE",
+ 2)
+ builder.add_key_value_param(
+ "com.android.test.power.receiver.SCAN_DURATION", scan_duration)
+ builder.add_key_value_param(
+ "com.android.test.power.receiver.CALLBACK_TYPE", 1)
+ builder.add_key_value_param("com.android.test.power.receiver.FILTER",
+ 'true')
+
+ scan_command = builder.build() + ' &'
+ logging.info('Start BLE {} scans for {} seconds'.format(
+ bleenum.ScanSettingsScanMode(scan_mode).name, scan_duration))
+ dut.adb.shell_nb(scan_command)
diff --git a/acts/framework/acts/test_utils/bt/bt_test_utils.py b/acts/framework/acts/test_utils/bt/bt_test_utils.py
index 57992eb19f..da4a88089f 100644
--- a/acts/framework/acts/test_utils/bt/bt_test_utils.py
+++ b/acts/framework/acts/test_utils/bt/bt_test_utils.py
@@ -79,7 +79,8 @@ def _add_android_device_to_dictionary(android_device, profile_list,
profile_list: The list of profiles the Android device supports.
"""
for profile in profile_list:
- if profile in selector_dict and android_device not in selector_dict[profile]:
+ if profile in selector_dict and android_device not in selector_dict[
+ profile]:
selector_dict[profile].append(android_device)
else:
selector_dict[profile] = [android_device]
@@ -164,7 +165,7 @@ def cleanup_scanners_and_advertisers(scn_android_device, scn_callback_list,
except Exception as err:
adv_android_device.log.debug(
"Failed to stop LE advertisement... reseting Bluetooth. Error {}".
- format(err))
+ format(err))
reset_bluetooth([adv_android_device])
@@ -197,7 +198,9 @@ def clear_bonded_devices(ad):
return True
-def connect_phone_to_headset(android, headset, timeout=bt_default_timeout,
+def connect_phone_to_headset(android,
+ headset,
+ timeout=bt_default_timeout,
connection_check_period=10):
"""Connects android phone to bluetooth headset.
Headset object must have methods power_on and enter_pairing_mode,
@@ -214,7 +217,8 @@ def connect_phone_to_headset(android, headset, timeout=bt_default_timeout,
connected (bool): True if devices are paired and connected by end of
method. False otherwise.
"""
- connected = is_a2dp_src_device_connected(android, headset.mac_address)
+ headset_mac_address = headset.mac_address
+ connected = is_a2dp_src_device_connected(android, headset_mac_address)
log.info('Devices connected before pair attempt: %s' % connected)
if not connected:
# Turn on headset and initiate pairing mode.
@@ -224,15 +228,18 @@ def connect_phone_to_headset(android, headset, timeout=bt_default_timeout,
# If already connected, skip pair and connect attempt.
while not connected and (time.time() - start_time < timeout):
bonded_info = android.droid.bluetoothGetBondedDevices()
- if headset.mac_address not in [info["address"] for info in bonded_info]:
+ if headset.mac_address not in [
+ info["address"] for info in bonded_info
+ ]:
# Use SL4A to pair and connect with headset.
- android.droid.bluetoothDiscoverAndBond(headset.mac_address)
+ headset.enter_pairing_mode()
+ android.droid.bluetoothDiscoverAndBond(headset_mac_address)
else: # Device is bonded but not connected
- android.droid.bluetoothConnectBonded(headset.mac_address)
+ android.droid.bluetoothConnectBonded(headset_mac_address)
log.info('Waiting for connection...')
time.sleep(connection_check_period)
# Check for connection.
- connected = is_a2dp_src_device_connected(android, headset.mac_address)
+ connected = is_a2dp_src_device_connected(android, headset_mac_address)
log.info('Devices connected after pair attempt: %s' % connected)
return connected
@@ -393,12 +400,13 @@ def determine_max_advertisements(android_device):
advertise_callback = android_device.droid.bleGenBleAdvertiseCallback()
advertise_callback_list.append(advertise_callback)
- android_device.droid.bleStartBleAdvertising(
- advertise_callback, advertise_data, advertise_settings)
+ android_device.droid.bleStartBleAdvertising(advertise_callback,
+ advertise_data,
+ advertise_settings)
regex = "(" + adv_succ.format(
advertise_callback) + "|" + adv_fail.format(
- advertise_callback) + ")"
+ advertise_callback) + ")"
# wait for either success or failure event
evt = android_device.ed.pop_events(regex, bt_default_timeout,
small_timeout)
@@ -584,10 +592,9 @@ def generate_ble_scan_objects(droid):
return filter_list, scan_settings, scan_callback
-def generate_id_by_size(
- size,
- chars=(
- string.ascii_lowercase + string.ascii_uppercase + string.digits)):
+def generate_id_by_size(size,
+ chars=(string.ascii_lowercase +
+ string.ascii_uppercase + string.digits)):
"""Generate random ascii characters of input size and input char types
Args:
@@ -649,6 +656,122 @@ def get_bluetooth_crash_count(android_device):
return int(re.search("crashed(.*\d)", out).group(1))
+def get_bt_metric(ad_list, duration=1, tag="bt_metric", processed=True):
+ """ Function to get the bt metric from logcat.
+
+ Captures logcat for the specified duration and returns the bqr results.
+ Takes list of android objects as input. If a single android object is given,
+ converts it into a list.
+
+ Args:
+ ad_list: list of android_device objects
+ duration: time duration (seconds) for which the logcat is parsed.
+ tag: tag to be appended to the logcat dump.
+ processed: flag to process bqr output.
+
+ Returns:
+ metrics_dict: dict of metrics for each android device.
+ """
+
+ # Defining bqr quantitites and their regex to extract
+ regex_dict = {"pwlv": "PwLv:\s(\S+)", "rssi": "RSSI:\s[-](\d+)"}
+ metrics_dict = {"rssi": {}, "pwlv": {}}
+
+ # Converting a single android device object to list
+ if not isinstance(ad_list, list):
+ ad_list = [ad_list]
+
+ #Time sync with the test machine
+ for ad in ad_list:
+ ad.droid.setTime(int(round(time.time() * 1000)))
+ time.sleep(0.5)
+
+ begin_time = utils.get_current_epoch_time()
+ time.sleep(duration)
+ end_time = utils.get_current_epoch_time()
+
+ for ad in ad_list:
+ bt_rssi_log = ad.cat_adb_log(tag, begin_time, end_time)
+ bqr_tag = "Monitoring , Handle:"
+
+ # Extracting supporting bqr quantities
+ for metric, regex in regex_dict.items():
+ bqr_metric = []
+ file_bt_log = open(bt_rssi_log, "r")
+ for line in file_bt_log:
+ if bqr_tag in line:
+ if re.findall(regex, line):
+ m = re.findall(regex, line)[0].strip(",")
+ bqr_metric.append(m)
+ metrics_dict[metric][ad.serial] = bqr_metric
+
+ # Formatting the raw data
+ metrics_dict["rssi"][ad.serial] = [
+ (-1) * int(x) for x in metrics_dict["rssi"][ad.serial]
+ ]
+ metrics_dict["pwlv"][ad.serial] = [
+ int(x, 16) for x in metrics_dict["pwlv"][ad.serial]
+ ]
+
+ # Processing formatted data if processing is required
+ if processed:
+ # Computes the average RSSI
+ metrics_dict["rssi"][ad.serial] = round(
+ sum(metrics_dict["rssi"][ad.serial]) /
+ len(metrics_dict["rssi"][ad.serial]), 2)
+ # Returns last noted value for power level
+ metrics_dict["pwlv"][ad.serial] = metrics_dict["pwlv"][
+ ad.serial][-1]
+
+ return metrics_dict
+
+
+def get_bt_rssi(ad, duration=1, processed=True):
+ """Function to get average bt rssi from logcat.
+
+ This function returns the average RSSI for the given duration. RSSI values are
+ extracted from BQR.
+
+ Args:
+ ad: (list of) android_device object.
+ duration: time duration(seconds) for which logcat is parsed.
+
+ Returns:
+ avg_rssi: average RSSI on each android device for the given duration.
+ """
+ function_tag = "get_bt_rssi"
+ bqr_results = get_bt_metric(ad,
+ duration,
+ tag=function_tag,
+ processed=processed)
+ return bqr_results["rssi"]
+
+
+def enable_bqr(ad_list, bqr_interval=10, bqr_event_mask=15,):
+ """Sets up BQR reporting.
+
+ Sets up BQR to report BT metrics at the requested frequency and toggles
+ airplane mode for the bqr settings to take effect.
+
+ Args:
+ ad_list: an android_device or list of android devices.
+ """
+ # Converting a single android device object to list
+ if not isinstance(ad_list, list):
+ ad_list = [ad_list]
+
+ for ad in ad_list:
+ #Setting BQR parameters
+ ad.adb.shell("setprop persist.bluetooth.bqr.event_mask {}".format(
+ bqr_event_mask))
+ ad.adb.shell("setprop persist.bluetooth.bqr.min_interval_ms {}".format(
+ bqr_interval))
+
+ ## Toggle airplane mode
+ ad.droid.connectivityToggleAirplaneMode(True)
+ ad.droid.connectivityToggleAirplaneMode(False)
+
+
def get_device_selector_dictionary(android_device_list):
"""Create a dictionary of Bluetooth features vs Android devices.
@@ -732,8 +855,8 @@ def get_mac_address_of_generic_advertisement(scan_ad, adv_ad):
adv_ad.droid.bleStartBleAdvertising(advertise_callback, advertise_data,
advertise_settings)
try:
- adv_ad.ed.pop_event(
- adv_succ.format(advertise_callback), bt_default_timeout)
+ adv_ad.ed.pop_event(adv_succ.format(advertise_callback),
+ bt_default_timeout)
except Empty as err:
raise BtTestUtilsError(
"Advertiser did not start successfully {}".format(err))
@@ -972,8 +1095,8 @@ def orchestrate_bluetooth_socket_connection(
client_ad.droid.bluetoothStartPairingHelper()
server_ad.droid.bluetoothSocketConnBeginAcceptThreadUuid(
- (bluetooth_socket_conn_test_uuid
- if uuid is None else uuid), accept_timeout_ms)
+ (bluetooth_socket_conn_test_uuid if uuid is None else uuid),
+ accept_timeout_ms)
client_ad.droid.bluetoothSocketConnBeginConnectThreadUuid(
server_ad.droid.bluetoothGetLocalAddress(),
(bluetooth_socket_conn_test_uuid if uuid is None else uuid))
@@ -1037,12 +1160,14 @@ def pair_pri_to_sec(pri_ad, sec_ad, attempts=2, auto_confirm=True):
# Wait 2 seconds before unbound
time.sleep(2)
if not clear_bonded_devices(pri_ad):
- log.error("Failed to clear bond for primary device at attempt {}"
- .format(str(curr_attempts)))
+ log.error(
+ "Failed to clear bond for primary device at attempt {}".format(
+ str(curr_attempts)))
return False
if not clear_bonded_devices(sec_ad):
- log.error("Failed to clear bond for secondary device at attempt {}"
- .format(str(curr_attempts)))
+ log.error(
+ "Failed to clear bond for secondary device at attempt {}".
+ format(str(curr_attempts)))
return False
# Wait 2 seconds after unbound
time.sleep(2)
@@ -1140,8 +1265,8 @@ def scan_and_verify_n_advertisements(scn_ad, max_advertisements):
while (start_time + bt_default_timeout) > time.time():
event = None
try:
- event = scn_ad.ed.pop_event(
- scan_result.format(scan_callback), bt_default_timeout)
+ event = scn_ad.ed.pop_event(scan_result.format(scan_callback),
+ bt_default_timeout)
except Empty as error:
raise BtTestUtilsError(
"Failed to find scan event: {}".format(error))
@@ -1155,13 +1280,12 @@ def scan_and_verify_n_advertisements(scn_ad, max_advertisements):
return test_result
-def set_bluetooth_codec(
- android_device,
- codec_type,
- sample_rate,
- bits_per_sample,
- channel_mode,
- codec_specific_1=0):
+def set_bluetooth_codec(android_device,
+ codec_type,
+ sample_rate,
+ bits_per_sample,
+ channel_mode,
+ codec_specific_1=0):
"""Sets the A2DP codec configuration on the AndroidDevice.
Args:
@@ -1180,31 +1304,26 @@ def set_bluetooth_codec(
bool: True if the codec config was successfully changed to the desired
values. Else False.
"""
- message = (
- "Set Android Device A2DP Bluetooth codec configuration:\n"
- "\tCodec: {codec_type}\n"
- "\tSample Rate: {sample_rate}\n"
- "\tBits per Sample: {bits_per_sample}\n"
- "\tChannel Mode: {channel_mode}".format(
- codec_type=codec_type,
- sample_rate=sample_rate,
- bits_per_sample=bits_per_sample,
- channel_mode=channel_mode
- )
- )
+ message = ("Set Android Device A2DP Bluetooth codec configuration:\n"
+ "\tCodec: {codec_type}\n"
+ "\tSample Rate: {sample_rate}\n"
+ "\tBits per Sample: {bits_per_sample}\n"
+ "\tChannel Mode: {channel_mode}".format(
+ codec_type=codec_type,
+ sample_rate=sample_rate,
+ bits_per_sample=bits_per_sample,
+ channel_mode=channel_mode))
android_device.log.info(message)
# Send SL4A command
droid, ed = android_device.droid, android_device.ed
if not droid.bluetoothA2dpSetCodecConfigPreference(
- codec_types[codec_type],
- sample_rates[str(sample_rate)],
- bits_per_samples[str(bits_per_sample)],
- channel_modes[channel_mode],
- codec_specific_1
- ):
- android_device.log.warning("SL4A command returned False. Codec was not "
- "changed.")
+ codec_types[codec_type], sample_rates[str(sample_rate)],
+ bits_per_samples[str(bits_per_sample)],
+ channel_modes[channel_mode], codec_specific_1):
+ android_device.log.warning(
+ "SL4A command returned False. Codec was not "
+ "changed.")
else:
try:
ed.pop_event(bluetooth_a2dp_codec_config_changed,
@@ -1222,14 +1341,10 @@ def set_bluetooth_codec(
android_device.log.warning("Could not verify codec config change "
"through ADB.")
elif split_out[1].strip().upper() != codec_type:
- android_device.log.error(
- "Codec config was not changed.\n"
- "\tExpected codec: {exp}\n"
- "\tActual codec: {act}".format(
- exp=codec_type,
- act=split_out[1].strip()
- )
- )
+ android_device.log.error("Codec config was not changed.\n"
+ "\tExpected codec: {exp}\n"
+ "\tActual codec: {act}".format(
+ exp=codec_type, act=split_out[1].strip()))
return False
android_device.log.info("Bluetooth codec successfully changed.")
return True
@@ -1335,8 +1450,8 @@ def setup_multiple_devices_for_bt_test(android_devices):
threads = []
try:
for a in android_devices:
- thread = threading.Thread(
- target=factory_reset_bluetooth, args=([[a]]))
+ thread = threading.Thread(target=factory_reset_bluetooth,
+ args=([[a]]))
threads.append(thread)
thread.start()
for t in threads:
@@ -1390,8 +1505,8 @@ def setup_n_advertisements(adv_ad, num_advertisements):
adv_ad.droid.bleStartBleAdvertising(advertise_callback, advertise_data,
advertise_settings)
try:
- adv_ad.ed.pop_event(
- adv_succ.format(advertise_callback), bt_default_timeout)
+ adv_ad.ed.pop_event(adv_succ.format(advertise_callback),
+ bt_default_timeout)
adv_ad.log.info("Advertisement {} started.".format(i + 1))
except Empty as error:
adv_ad.log.error("Advertisement {} failed to start.".format(i + 1))
@@ -1542,8 +1657,9 @@ def _wait_for_passkey_match(pri_ad, sec_ad):
timeout=bt_default_timeout)
sec_variant = sec_pairing_req["data"]["PairingVariant"]
sec_pin = sec_pairing_req["data"]["Pin"]
- sec_ad.log.info("Secondary device received Pin: {}, Variant: {}"
- .format(sec_pin, sec_variant))
+ sec_ad.log.info(
+ "Secondary device received Pin: {}, Variant: {}".format(
+ sec_pin, sec_variant))
except Empty as err:
log.error("Wait for pin error: {}".format(err))
log.error("Pairing request state, Primary: {}, Secondary: {}".format(
diff --git a/acts/framework/acts/test_utils/bt/pts/fuchsia_pts_ics_lib.py b/acts/framework/acts/test_utils/bt/pts/fuchsia_pts_ics_lib.py
index 5ea32c1e68..f2f9b2c295 100644
--- a/acts/framework/acts/test_utils/bt/pts/fuchsia_pts_ics_lib.py
+++ b/acts/framework/acts/test_utils/bt/pts/fuchsia_pts_ics_lib.py
@@ -108,6 +108,56 @@ A2DP_ICS = {
b'TSPC_A2DP_7a_3': b'FALSE',
b'TSPC_A2DP_7b_1': b'FALSE',
b'TSPC_A2DP_7b_2': b'FALSE',
+
+ # Not available in Launch Studio Yet
+ b'TSPC_A2DP_10_1': b'FALSE',
+ b'TSPC_A2DP_10_2': b'FALSE',
+ b'TSPC_A2DP_10_3': b'FALSE',
+ b'TSPC_A2DP_10_4': b'FALSE',
+ b'TSPC_A2DP_10_5': b'FALSE',
+ b'TSPC_A2DP_10_6': b'FALSE',
+ b'TSPC_A2DP_11_1': b'FALSE',
+ b'TSPC_A2DP_11_2': b'FALSE',
+ b'TSPC_A2DP_11_3': b'FALSE',
+ b'TSPC_A2DP_11_4': b'FALSE',
+ b'TSPC_A2DP_11_5': b'FALSE',
+ b'TSPC_A2DP_11_6': b'FALSE',
+ b'TSPC_A2DP_12_2': b'FALSE',
+ b'TSPC_A2DP_12_3': b'FALSE',
+ b'TSPC_A2DP_12_3': b'FALSE',
+ b'TSPC_A2DP_12_4': b'FALSE',
+ b'TSPC_A2DP_13_1': b'FALSE',
+ b'TSPC_A2DP_13_2': b'FALSE',
+ b'TSPC_A2DP_13_3': b'FALSE',
+ b'TSPC_A2DP_13_4': b'FALSE',
+ b'TSPC_A2DP_14_1': b'FALSE',
+ b'TSPC_A2DP_14_2': b'FALSE',
+ b'TSPC_A2DP_14_3': b'FALSE',
+ b'TSPC_A2DP_14_4': b'FALSE',
+ b'TSPC_A2DP_14_5': b'FALSE',
+ b'TSPC_A2DP_15_1': b'FALSE',
+ b'TSPC_A2DP_15_2': b'FALSE',
+ b'TSPC_A2DP_15_3': b'FALSE',
+ b'TSPC_A2DP_15_4': b'FALSE',
+ b'TSPC_A2DP_15_5': b'FALSE',
+ b'TSPC_A2DP_15_6': b'FALSE',
+ b'TSPC_A2DP_3_2a': b'FALSE',
+ b'TSPC_A2DP_3_2b': b'FALSE',
+ b'TSPC_A2DP_3_2c': b'FALSE',
+ b'TSPC_A2DP_3_2d': b'FALSE',
+ b'TSPC_A2DP_3_2e': b'FALSE',
+ b'TSPC_A2DP_3_2f': b'FALSE',
+ b'TSPC_A2DP_5_2a': b'FALSE',
+ b'TSPC_A2DP_5_2b': b'FALSE',
+ b'TSPC_A2DP_5_2c': b'FALSE',
+ b'TSPC_A2DP_8_2': b'FALSE',
+ b'TSPC_A2DP_8_3': b'FALSE',
+ b'TSPC_A2DP_8_4': b'FALSE',
+ b'TSPC_A2DP_9_1': b'FALSE',
+ b'TSPC_A2DP_9_2': b'FALSE',
+ b'TSPC_A2DP_9_3': b'FALSE',
+ b'TSPC_A2DP_9_4': b'FALSE',
+
}
diff --git a/acts/framework/acts/test_utils/bt/pts/pts_base_class.py b/acts/framework/acts/test_utils/bt/pts/pts_base_class.py
index 568c092736..b2c48d66e1 100644
--- a/acts/framework/acts/test_utils/bt/pts/pts_base_class.py
+++ b/acts/framework/acts/test_utils/bt/pts/pts_base_class.py
@@ -121,8 +121,6 @@ class PtsBaseClass(BaseTestClass):
# TODO: Implement MMIs as necessary
}
}
-
- self.pts.setup_pts()
self.pts.bind_to(self.process_next_action)
def teardown_class(self):
diff --git a/acts/framework/acts/test_utils/gnss/gnss_test_utils.py b/acts/framework/acts/test_utils/gnss/gnss_test_utils.py
index 2b4e6f3a72..641c8e5ad8 100644
--- a/acts/framework/acts/test_utils/gnss/gnss_test_utils.py
+++ b/acts/framework/acts/test_utils/gnss/gnss_test_utils.py
@@ -84,9 +84,6 @@ def reboot(ad):
if not int(ad.adb.shell("settings get global mobile_data")) == 1:
set_mobile_data(ad, True)
utils.sync_device_time(ad)
- if ad.model == "sailfish" or ad.model == "marlin":
- remount_device(ad)
- ad.adb.shell("echo at@test=8 >> /dev/at_mdm0")
def enable_gnss_verbose_logging(ad):
"""Enable GNSS VERBOSE Logging and persistent logcat.
@@ -99,6 +96,7 @@ def enable_gnss_verbose_logging(ad):
ad.adb.shell("echo DEBUG_LEVEL = 5 >> /vendor/etc/gps.conf")
ad.adb.shell("echo %r >> /data/local.prop" % LOCAL_PROP_FILE_CONTENTS)
ad.adb.shell("chmod 644 /data/local.prop")
+ ad.adb.shell("setprop persist.logd.logpersistd.size 20000")
ad.adb.shell("setprop persist.logd.size 16777216")
ad.adb.shell("setprop persist.vendor.radio.adb_log_on 1")
ad.adb.shell("setprop persist.logd.logpersistd logcatd")
@@ -504,22 +502,29 @@ def clear_aiding_data_by_gtw_gpstool(ad):
ad.adb.shell("am start -S -n com.android.gpstool/.GPSTool --es mode clear")
time.sleep(10)
-def start_gnss_by_gtw_gpstool(ad, state, type="gnss"):
+
+def start_gnss_by_gtw_gpstool(ad, state, type="gnss", bgdisplay=False):
"""Start or stop GNSS on GTW_GPSTool.
Args:
ad: An AndroidDevice object.
state: True to start GNSS. False to Stop GNSS.
type: Different API for location fix. Use gnss/flp/nmea
+ bgdisplay: true to run GTW when Display off.
+ false to not run GTW when Display off.
"""
- if state:
+ if state and not bgdisplay:
ad.adb.shell("am start -S -n com.android.gpstool/.GPSTool "
"--es mode gps --es type %s" % type)
+ elif state and bgdisplay:
+ ad.adb.shell("am start -S -n com.android.gpstool/.GPSTool --es mode"
+ " gps --es type {} --ez BG {}".format(type, bgdisplay))
if not state:
ad.log.info("Stop %s on GTW_GPSTool." % type)
ad.adb.shell("am broadcast -a com.android.gpstool.stop_gps_action")
time.sleep(3)
+
def process_gnss_by_gtw_gpstool(ad, criteria, type="gnss"):
"""Launch GTW GPSTool and Clear all GNSS aiding data
Start GNSS tracking on GTW_GPSTool.
@@ -807,7 +812,10 @@ def ttff_property_key_and_value(ad, ttff_data, ttff_mode):
pe_list = [float(ttff_data[key].ttff_pe) for key in ttff_data.keys()]
cn_list = [float(ttff_data[key].ttff_cn) for key in ttff_data.keys()]
timeoutcount = sec_list.count(0.0)
- avgttff = sum(sec_list)/(len(sec_list) - timeoutcount)
+ if len(sec_list) == timeoutcount:
+ avgttff = 9527
+ else:
+ avgttff = sum(sec_list)/(len(sec_list) - timeoutcount)
if timeoutcount != 0:
maxttff = 9527
else:
@@ -1021,6 +1029,7 @@ def get_baseband_and_gms_version(ad, extra_msg=""):
ad: An AndroidDevice object.
"""
try:
+ build_version = ad.adb.getprop("ro.build.id")
baseband_version = ad.adb.getprop("gsm.version.baseband")
gms_version = ad.adb.shell(
"dumpsys package com.google.android.gms | grep versionName"
@@ -1028,6 +1037,7 @@ def get_baseband_and_gms_version(ad, extra_msg=""):
mpss_version = ad.adb.shell("cat /sys/devices/soc0/images | grep MPSS "
"| cut -d ':' -f 3")
if not extra_msg:
+ ad.log.info("TestResult Build_Version %s" % build_version)
ad.log.info("TestResult Baseband_Version %s" % baseband_version)
ad.log.info(
"TestResult GMS_Version %s" % gms_version.replace(" ", ""))
diff --git a/acts/framework/acts/test_utils/gnss/gnss_testlog_utils.py b/acts/framework/acts/test_utils/gnss/gnss_testlog_utils.py
new file mode 100644
index 0000000000..6bb18dfc1a
--- /dev/null
+++ b/acts/framework/acts/test_utils/gnss/gnss_testlog_utils.py
@@ -0,0 +1,306 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+'''Python Module for GNSS test log utilities.'''
+
+import re as regex
+import datetime
+import functools as fts
+import numpy as npy
+import pandas as pds
+from acts import logger
+
+# GPS API Log Reading Config
+CONFIG_GPSAPILOG = {
+ 'phone_time':
+ r'(?P<date>\d+\/\d+\/\d+)\s+(?P<time>\d+:\d+:\d+)\s+'
+ r'Read:\s+(?P<logsize>\d+)\s+bytes',
+ 'SpaceVehicle':
+ r'Fix:\s+(?P<Fix>\w+)\s+Type:\s+(?P<Type>\w+)\s+'
+ r'SV:\s+(?P<SV>\d+)\s+C\/No:\s+(?P<CNo>\d+\.\d+)\s+'
+ r'Elevation:\s+(?P<Elevation>\d+\.\d+)\s+'
+ r'Azimuth:\s+(?P<Azimuth>\d+\.\d+)\s+'
+ r'Signal:\s+(?P<Signal>\w+)\s+'
+ r'Frequency:\s+(?P<Frequency>\d+\.\d+)\s+'
+ r'EPH:\s+(?P<EPH>\w+)\s+ALM:\s+(?P<ALM>\w+)',
+ 'HistoryAvgTop4CNo':
+ r'History\s+Avg\s+Top4\s+:\s+(?P<HistoryAvgTop4CNo>\d+\.\d+)',
+ 'CurrentAvgTop4CNo':
+ r'Current\s+Avg\s+Top4\s+:\s+(?P<CurrentAvgTop4CNo>\d+\.\d+)',
+ 'HistoryAvgCNo':
+ r'History\s+Avg\s+:\s+(?P<HistoryAvgCNo>\d+\.\d+)',
+ 'CurrentAvgCNo':
+ r'Current\s+Avg\s+:\s+(?P<CurrentAvgCNo>\d+\.\d+)',
+ 'L5inFix':
+ r'L5\s+used\s+in\s+fix:\s+(?P<L5inFix>\w+)',
+ 'L5EngagingRate':
+ r'L5\s+engaging\s+rate:\s+(?P<L5EngagingRate>\d+.\d+)%',
+ 'Provider':
+ r'Provider:\s+(?P<Provider>\w+)',
+ 'Latitude':
+ r'Latitude:\s+(?P<Latitude>-?\d+.\d+)',
+ 'Longitude':
+ r'Longitude:\s+(?P<Longitude>-?\d+.\d+)',
+ 'Altitude':
+ r'Altitude:\s+(?P<Altitude>-?\d+.\d+)',
+ 'GNSSTime':
+ r'Time:\s+(?P<Date>\d+\/\d+\/\d+)\s+'
+ r'(?P<Time>\d+:\d+:\d+)',
+ 'Speed':
+ r'Speed:\s+(?P<Speed>\d+.\d+)',
+ 'Bearing':
+ r'Bearing:\s+(?P<Bearing>\d+.\d+)',
+}
+
+# Space Vehicle Statistics Dataframe List
+LIST_SVSTAT = [
+ 'HistoryAvgTop4CNo', 'CurrentAvgTop4CNo', 'HistoryAvgCNo', 'CurrentAvgCNo',
+ 'L5inFix', 'L5EngagingRate'
+]
+
+# Location Fix Info Dataframe List
+LIST_LOCINFO = [
+ 'Provider', 'Latitude', 'Longitude', 'Altitude', 'GNSSTime', 'Speed',
+ 'Bearing'
+]
+
+LOGPARSE_UTIL_LOGGER = logger.create_logger()
+
+
+def parse_log_to_df(filename, configs, index_rownum=True):
+ r"""Parse log to a dictionary of Pandas dataframes.
+
+ Args:
+ filename: log file name.
+ Type String.
+ configs: configs dictionary of parsed Pandas dataframes.
+ Type dictionary.
+ dict key, the parsed pattern name, such as 'Speed',
+ dict value, regex of the config pattern,
+ Type Raw String.
+ index_rownum: index row number from raw data.
+ Type Boolean.
+ Default, True.
+
+ Returns:
+ parsed_data: dictionary of parsed data.
+ Type dictionary.
+ dict key, the parsed pattern name, such as 'Speed',
+ dict value, the corresponding parsed dataframe.
+
+ Examples:
+ configs = {
+ 'GNSSTime':
+ r'Time:\s+(?P<Date>\d+\/\d+\/\d+)\s+
+ r(?P<Time>\d+:\d+:\d+)')},
+ 'Speed': r'Speed:\s+(?P<Speed>\d+.\d+)',
+ }
+ """
+ # Init a local config dictionary to hold compiled regex and match dict.
+ configs_local = {}
+ # Construct parsed data dictionary
+ parsed_data = {}
+
+ # Loop the config dictionary to compile regex and init data list
+ for key, regex_string in configs.items():
+ configs_local[key] = {
+ 'cregex': regex.compile(regex_string),
+ 'datalist': [],
+ }
+
+ # Open the file, loop and parse
+ with open(filename, 'r') as fid:
+
+ for idx_line, current_line in enumerate(fid):
+ for _, config in configs_local.items():
+ matched_log_object = config['cregex'].search(current_line)
+
+ if matched_log_object:
+ matched_data = matched_log_object.groupdict()
+ matched_data['rownumber'] = idx_line + 1
+ config['datalist'].append(matched_data)
+
+ # Loop to generate parsed data from configs list
+ for key, config in configs_local.items():
+ parsed_data[key] = pds.DataFrame(config['datalist'])
+ if index_rownum and not parsed_data[key].empty:
+ parsed_data[key].set_index('rownumber', inplace=True)
+ elif parsed_data[key].empty:
+ LOGPARSE_UTIL_LOGGER.warning(
+ 'The parsed dataframe of "%s" is empty.', key)
+
+ # Return parsed data list
+ return parsed_data
+
+
+def parse_gpsapilog_to_df(filename):
+ """Parse GPS API log to Pandas dataframes.
+
+ Args:
+ filename: full log file name.
+ Type, String.
+
+ Returns:
+ timestamp_df: Timestamp Data Frame.
+ Type, Pandas DataFrame.
+ sv_info_df: GNSS SV info Data Frame.
+ Type, Pandas DataFrame.
+ loc_info_df: Location Information Data Frame.
+ Type, Pandas DataFrame.
+ include Provider, Latitude, Longitude, Altitude, GNSSTime, Speed, Bearing
+ """
+
+ def get_phone_time(target_df_row, timestamp_df):
+ """subfunction to get the phone_time."""
+
+ try:
+ row_num = timestamp_df[
+ timestamp_df.index < target_df_row.name].iloc[-1].name
+ phone_time = timestamp_df.loc[row_num]['phone_time']
+ except IndexError:
+ row_num = npy.NaN
+ phone_time = npy.NaN
+
+ return phone_time, row_num
+
+ # Get parsed dataframe list
+ parsed_data = parse_log_to_df(
+ filename=filename,
+ configs=CONFIG_GPSAPILOG,
+ )
+
+ # get DUT Timestamp
+ timestamp_df = parsed_data['phone_time']
+ timestamp_df['phone_time'] = timestamp_df.apply(
+ lambda row: datetime.datetime.strptime(row.date + '-' + row.time,
+ '%Y/%m/%d-%H:%M:%S'),
+ axis=1)
+
+ # Add phone_time from timestamp_df dataframe by row number
+ for key in parsed_data:
+ if key != 'phone_time':
+ current_df = parsed_data[key]
+ time_n_row_num = current_df.apply(get_phone_time,
+ axis=1,
+ timestamp_df=timestamp_df)
+ current_df[['phone_time', 'time_row_num'
+ ]] = pds.DataFrame(time_n_row_num.apply(pds.Series))
+
+ # Get space vehicle info dataframe
+ sv_info_df = parsed_data['SpaceVehicle']
+
+ # Get space vehicle statistics dataframe
+ # First merge all dataframe from LIST_SVSTAT[1:],
+ # Drop duplicated 'phone_time', based on time_row_num
+ sv_stat_df = fts.reduce(
+ lambda item1, item2: pds.merge(item1, item2, on='time_row_num'), [
+ parsed_data[key].drop(['phone_time'], axis=1)
+ for key in LIST_SVSTAT[1:]
+ ])
+ # Then merge with LIST_SVSTAT[0]
+ sv_stat_df = pds.merge(sv_stat_df,
+ parsed_data[LIST_SVSTAT[0]],
+ on='time_row_num')
+
+ # Get location fix information dataframe
+ # First merge all dataframe from LIST_LOCINFO[1:],
+ # Drop duplicated 'phone_time', based on time_row_num
+ loc_info_df = fts.reduce(
+ lambda item1, item2: pds.merge(item1, item2, on='time_row_num'), [
+ parsed_data[key].drop(['phone_time'], axis=1)
+ for key in LIST_LOCINFO[1:]
+ ])
+ # Then merge with LIST_LOCINFO[8]
+ loc_info_df = pds.merge(loc_info_df,
+ parsed_data[LIST_LOCINFO[0]],
+ on='time_row_num')
+ # Convert GNSS Time
+ loc_info_df['gnsstime'] = loc_info_df.apply(
+ lambda row: datetime.datetime.strptime(row.Date + '-' + row.Time,
+ '%Y/%m/%d-%H:%M:%S'),
+ axis=1)
+
+ return timestamp_df, sv_info_df, sv_stat_df, loc_info_df
+
+
+def parse_gpsapilog_to_df_v2(filename):
+ """Parse GPS API log to Pandas dataframes, by using merge_asof.
+
+ Args:
+ filename: full log file name.
+ Type, String.
+
+ Returns:
+ timestamp_df: Timestamp Data Frame.
+ Type, Pandas DataFrame.
+ sv_info_df: GNSS SV info Data Frame.
+ Type, Pandas DataFrame.
+ loc_info_df: Location Information Data Frame.
+ Type, Pandas DataFrame.
+ include Provider, Latitude, Longitude, Altitude, GNSSTime, Speed, Bearing
+ """
+ # Get parsed dataframe list
+ parsed_data = parse_log_to_df(
+ filename=filename,
+ configs=CONFIG_GPSAPILOG,
+ )
+
+ # get DUT Timestamp
+ timestamp_df = parsed_data['phone_time']
+ timestamp_df['phone_time'] = timestamp_df.apply(
+ lambda row: datetime.datetime.strptime(row.date + '-' + row.time,
+ '%Y/%m/%d-%H:%M:%S'),
+ axis=1)
+ # drop logsize, date, time
+ parsed_data['phone_time'] = timestamp_df.drop(['logsize', 'date', 'time'],
+ axis=1)
+
+ # Add phone_time from timestamp dataframe by row number
+ for key in parsed_data:
+ if key != 'phone_time':
+ parsed_data[key] = pds.merge_asof(parsed_data[key],
+ parsed_data['phone_time'],
+ left_index=True,
+ right_index=True)
+
+ # Get space vehicle info dataframe
+ sv_info_df = parsed_data['SpaceVehicle']
+
+ # Get space vehicle statistics dataframe
+ # First merge all dataframe from LIST_SVSTAT[1:],
+ sv_stat_df = fts.reduce(
+ lambda item1, item2: pds.merge(item1, item2, on='phone_time'),
+ [parsed_data[key] for key in LIST_SVSTAT[1:]])
+ # Then merge with LIST_SVSTAT[0]
+ sv_stat_df = pds.merge(sv_stat_df,
+ parsed_data[LIST_SVSTAT[0]],
+ on='phone_time')
+
+ # Get location fix information dataframe
+ # First merge all dataframe from LIST_LOCINFO[1:],
+ loc_info_df = fts.reduce(
+ lambda item1, item2: pds.merge(item1, item2, on='phone_time'),
+ [parsed_data[key] for key in LIST_LOCINFO[1:]])
+ # Then merge with LIST_LOCINFO[8]
+ loc_info_df = pds.merge(loc_info_df,
+ parsed_data[LIST_LOCINFO[0]],
+ on='phone_time')
+ # Convert GNSS Time
+ loc_info_df['gnsstime'] = loc_info_df.apply(
+ lambda row: datetime.datetime.strptime(row.Date + '-' + row.Time,
+ '%Y/%m/%d-%H:%M:%S'),
+ axis=1)
+
+ return timestamp_df, sv_info_df, sv_stat_df, loc_info_df
diff --git a/acts/framework/acts/test_utils/power/PowerBTBaseTest.py b/acts/framework/acts/test_utils/power/PowerBTBaseTest.py
index 5dfda12c7a..e0fd37da2e 100644
--- a/acts/framework/acts/test_utils/power/PowerBTBaseTest.py
+++ b/acts/framework/acts/test_utils/power/PowerBTBaseTest.py
@@ -14,18 +14,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import time
+import os
+import acts.test_utils.bt.bt_power_test_utils as btputils
+import acts.test_utils.bt.bt_test_utils as btutils
import acts.test_utils.power.PowerBaseTest as PBT
-from acts.test_utils.bt.bt_test_utils import enable_bluetooth
-from acts.test_utils.bt.bt_test_utils import disable_bluetooth
+from acts.test_utils.abstract_devices.bluetooth_handsfree_abstract_device import BluetoothHandsfreeAbstractDeviceFactory as bt_factory
-BT_BASE_UUID = '00000000-0000-1000-8000-00805F9B34FB'
-BT_CLASSICAL_DATA = [1, 2, 3]
-BLE_LOCATION_SCAN_ENABLE = 'settings put global ble_scan_always_enabled 1'
-BLE_LOCATION_SCAN_DISABLE = 'settings put global ble_scan_always_enabled 0'
-START_PMC_CMD = 'am start -n com.android.pmc/com.android.pmc.PMCMainActivity'
-PMC_VERBOSE_CMD = 'setprop log.tag.PMC VERBOSE'
-PMC_BASE_SCAN = 'am broadcast -a com.android.pmc.BLESCAN --es ScanMode '
+BLE_LOCATION_SCAN_DISABLE = 'settings put secure location_mode 0'
+PHONE_MUSIC_FILE_DIRECTORY = '/sdcard/Music'
+INIT_ATTEN = [30]
class PowerBTBaseTest(PBT.PowerBaseTest):
@@ -34,16 +31,43 @@ class PowerBTBaseTest(PBT.PowerBaseTest):
Inherited from the PowerBaseTest class
"""
+ def setup_class(self):
+
+ super().setup_class()
+ # Get music file and push it to the phone
+ music_files = self.user_params.get('music_files', [])
+ if music_files:
+ music_src = music_files[0]
+ music_dest = PHONE_MUSIC_FILE_DIRECTORY
+ success = self.dut.push_system_file(music_src, music_dest)
+ if success:
+ self.music_file = os.path.join(PHONE_MUSIC_FILE_DIRECTORY,
+ os.path.basename(music_src))
+ # Initialize media_control class
+ self.media = btputils.MediaControl(self.dut, self.music_file)
+ # Set Attenuator to the initial attenuation
+ if hasattr(self, 'attenuators'):
+ self.set_attenuation(INIT_ATTEN)
+ # Create the BTOE(Bluetooth-Other-End) device object
+ bt_devices = self.user_params.get('bt_devices', [])
+ if bt_devices:
+ attr, idx = bt_devices.split(':')
+ self.bt_device_controller = getattr(self, attr)[int(idx)]
+ self.bt_device = bt_factory().generate(self.bt_device_controller)
+ else:
+ self.log.error('No BT devices config is provided!')
+ # Turn off screen as all tests will be screen off
+ self.dut.droid.goToSleepNow()
+
def setup_test(self):
super().setup_test()
+ self.unpack_userparams(volume=0.9)
# Reset BT to factory defaults
self.dut.droid.bluetoothFactoryReset()
- time.sleep(2)
- # Start PMC app.
- self.log.info('Start PMC app...')
- self.dut.adb.shell(START_PMC_CMD)
- self.dut.adb.shell(PMC_VERBOSE_CMD)
+ self.bt_device.reset()
+ self.bt_device.power_on()
+ btutils.enable_bluetooth(self.dut.droid, self.dut.ed)
def teardown_test(self):
"""Tear down necessary objects after test case is finished.
@@ -54,6 +78,11 @@ class PowerBTBaseTest(PBT.PowerBaseTest):
super().teardown_test()
self.dut.droid.bluetoothFactoryReset()
self.dut.adb.shell(BLE_LOCATION_SCAN_DISABLE)
+ if hasattr(self, 'media'):
+ self.media.stop()
+ self.bt_device.reset()
+ self.bt_device.power_off()
+ btutils.disable_bluetooth(self.dut.droid)
def teardown_class(self):
"""Clean up the test class after tests finish running
@@ -61,75 +90,7 @@ class PowerBTBaseTest(PBT.PowerBaseTest):
"""
super().teardown_class()
self.dut.droid.bluetoothFactoryReset()
-
- def phone_setup_for_BT(self, bt_on, ble_on, screen_status):
- """Sets the phone and Bluetooth in the desired state
-
- Args:
- bt_on: Enable/Disable BT
- ble_on: Enable/Disable BLE
- screen_status: screen ON or OFF
- """
-
- # Check if we are enabling a background scan
- # TODO: Turn OFF cellular wihtout having to turn ON airplane mode
- if bt_on == 'OFF' and ble_on == 'ON':
- self.dut.adb.shell(BLE_LOCATION_SCAN_ENABLE)
- self.dut.droid.connectivityToggleAirplaneMode(False)
- time.sleep(2)
-
- # Turn ON/OFF BT
- if bt_on == 'ON':
- enable_bluetooth(self.dut.droid, self.dut.ed)
- self.dut.log.info('BT is ON')
- else:
- disable_bluetooth(self.dut.droid)
- self.dut.droid.bluetoothDisableBLE()
- self.dut.log.info('BT is OFF')
- time.sleep(2)
-
- # Turn ON/OFF BLE
- if ble_on == 'ON':
- self.dut.droid.bluetoothEnableBLE()
- self.dut.log.info('BLE is ON')
- else:
- self.dut.droid.bluetoothDisableBLE()
- self.dut.log.info('BLE is OFF')
- time.sleep(2)
-
- # Set the desired screen status
- if screen_status == 'OFF':
- self.dut.droid.goToSleepNow()
- self.dut.log.info('Screen is OFF')
- time.sleep(2)
-
- def start_pmc_ble_scan(self,
- scan_mode,
- offset_start,
- scan_time,
- idle_time=None,
- num_reps=1):
- """Starts a generic BLE scan via the PMC app
-
- Args:
- dut: object of the android device under test
- scan mode: desired BLE scan type
- offset_start: Time delay in seconds before scan starts
- scan_time: active scan time
- idle_time: iddle time (i.e., no scans occuring)
- num_reps: Number of repetions of the ative+idle scan sequence
- """
- scan_dur = scan_time
- if not idle_time:
- idle_time = 0.2 * scan_time
- scan_dur = 0.8 * scan_time
-
- first_part_msg = '%s%s --es StartTime %d --es ScanTime %d' % (
- PMC_BASE_SCAN, scan_mode, offset_start, scan_dur)
-
- msg = '%s --es NoScanTime %d --es Repetitions %d' % (first_part_msg,
- idle_time,
- num_reps)
-
- self.dut.log.info('Sent BLE scan broadcast message: %s', msg)
- self.dut.adb.shell(msg)
+ self.dut.adb.shell(BLE_LOCATION_SCAN_DISABLE)
+ self.bt_device.reset()
+ self.bt_device.power_off()
+ btutils.disable_bluetooth(self.dut.droid)
diff --git a/acts/framework/acts/test_utils/power/PowerBaseTest.py b/acts/framework/acts/test_utils/power/PowerBaseTest.py
index 92fc603209..2334702828 100644
--- a/acts/framework/acts/test_utils/power/PowerBaseTest.py
+++ b/acts/framework/acts/test_utils/power/PowerBaseTest.py
@@ -17,39 +17,42 @@ import json
import logging
import math
import os
+import re
import time
+
import acts.controllers.iperf_server as ipf
from acts import asserts
from acts import base_test
from acts import utils
-from acts.controllers import monsoon
+from acts.controllers.monsoon_lib.api.common import MonsoonError
+from acts.controllers.monsoon_lib.api.common import PassthroughStates
from acts.metrics.loggers.blackbox import BlackboxMetricLogger
from acts.test_utils.power.loggers.power_metric_logger import PowerMetricLogger
-from acts.test_utils.wifi import wifi_test_utils as wutils
from acts.test_utils.wifi import wifi_power_test_utils as wputils
+from acts.test_utils.wifi import wifi_test_utils as wutils
RESET_BATTERY_STATS = 'dumpsys batterystats --reset'
IPERF_TIMEOUT = 180
-THRESHOLD_TOLERANCE = 0.2
+THRESHOLD_TOLERANCE_DEFAULT = 0.2
GET_FROM_PHONE = 'get_from_dut'
GET_FROM_AP = 'get_from_ap'
-PHONE_BATTERY_VOLTAGE = 4.2
+PHONE_BATTERY_VOLTAGE_DEFAULT = 4.2
MONSOON_MAX_CURRENT = 8.0
MONSOON_RETRY_INTERVAL = 300
+DEFAULT_MONSOON_FREQUENCY = 500
MEASUREMENT_RETRY_COUNT = 3
RECOVER_MONSOON_RETRY_COUNT = 3
MIN_PERCENT_SAMPLE = 95
ENABLED_MODULATED_DTIM = 'gEnableModulatedDTIM='
MAX_MODULATED_DTIM = 'gMaxLIModulatedDTIM='
TEMP_FILE = '/sdcard/Download/tmp.log'
-IPERF_DURATION = 'iperf_duration'
-INITIAL_ATTEN = [0, 0, 90, 90]
class ObjNew():
"""Create a random obj with unknown attributes and value.
"""
+
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
@@ -68,6 +71,7 @@ class PowerBaseTest(base_test.BaseTestClass):
"""Base class for all wireless power related tests.
"""
+
def __init__(self, controllers):
base_test.BaseTestClass.__init__(self, controllers)
@@ -83,17 +87,38 @@ class PowerBaseTest(base_test.BaseTestClass):
self.log = logging.getLogger()
self.tests = self._get_all_test_names()
+ # Obtain test parameters from user_params
+ TEST_PARAMS = self.TAG + '_params'
+ self.test_params = self.user_params.get(TEST_PARAMS, {})
+ if not self.test_params:
+ self.log.warning(TEST_PARAMS + ' was not found in the user '
+ 'parameters defined in the config file.')
+
+ # Override user_param values with test parameters
+ self.user_params.update(self.test_params)
+
+ # Unpack user_params with default values. All the usages of user_params
+ # as self attributes need to be included either as a required parameter
+ # or as a parameter with a default value.
+ req_params = ['custom_files', 'mon_duration']
+ self.unpack_userparams(req_params,
+ mon_freq=DEFAULT_MONSOON_FREQUENCY,
+ mon_offset=0,
+ bug_report=False,
+ extra_wait=None,
+ iperf_duration=None,
+ pass_fail_tolerance=THRESHOLD_TOLERANCE_DEFAULT,
+ mon_voltage=PHONE_BATTERY_VOLTAGE_DEFAULT)
+
# Setup the must have controllers, phone and monsoon
self.dut = self.android_devices[0]
self.mon_data_path = os.path.join(self.log_path, 'Monsoon')
+ os.makedirs(self.mon_data_path, exist_ok=True)
self.mon = self.monsoons[0]
self.mon.set_max_current(8.0)
- self.mon.set_voltage(PHONE_BATTERY_VOLTAGE)
+ self.mon.set_voltage(self.mon_voltage)
self.mon.attach_device(self.dut)
- # Unpack the test/device specific parameters
- req_params = ['custom_files']
- self.unpack_userparams(req_params)
# Unpack the custom files based on the test configs
for file in self.custom_files:
if 'pass_fail_threshold_' + self.dut.model in file:
@@ -104,7 +129,7 @@ class PowerBaseTest(base_test.BaseTestClass):
self.network_file = file
elif 'rockbottom_' + self.dut.model in file:
self.rockbottom_script = file
- #Abort the class if threshold and rockbottom file is missing
+ # Abort the class if threshold and rockbottom file is missing
asserts.abort_class_if(
not self.threshold_file,
'Required test pass/fail threshold file is missing')
@@ -112,19 +137,14 @@ class PowerBaseTest(base_test.BaseTestClass):
not self.rockbottom_script,
'Required rockbottom setting script is missing')
- # Unpack test specific configs
- TEST_PARAMS = self.TAG + '_params'
- self.test_params = self.user_params.get(TEST_PARAMS, {})
- self.unpack_testparams(self.test_params)
if hasattr(self, 'attenuators'):
self.num_atten = self.attenuators[0].instrument.num_atten
self.atten_level = self.unpack_custom_file(self.attenuation_file)
- self.set_attenuation(INITIAL_ATTEN)
self.threshold = self.unpack_custom_file(self.threshold_file)
self.mon_info = self.create_monsoon_info()
# Sync device time, timezone and country code
- utils.require_sl4a((self.dut, ))
+ utils.require_sl4a((self.dut,))
utils.sync_device_time(self.dut)
self.dut.droid.wifiSetCountryCode('US')
@@ -148,7 +168,7 @@ class PowerBaseTest(base_test.BaseTestClass):
wutils.wifi_toggle_state(self.dut, False)
# Wait for extra time if needed for the first test
- if hasattr(self, 'extra_wait'):
+ if self.extra_wait:
self.more_wait_first_test()
def teardown_test(self):
@@ -194,20 +214,13 @@ class PowerBaseTest(base_test.BaseTestClass):
# Restart SL4A
self.dut.start_services()
- def unpack_testparams(self, bulk_params):
- """Unpack all the test specific parameters.
-
- Args:
- bulk_params: dict with all test specific params in the config file
- """
- for key in bulk_params.keys():
- setattr(self, key, bulk_params[key])
-
def unpack_custom_file(self, file, test_specific=True):
"""Unpack the pass_fail_thresholds from a common file.
Args:
file: the common file containing pass fail threshold.
+ test_specific: if True, returns the JSON element within the file
+ that starts with the test class name.
"""
with open(file, 'r') as f:
params = json.load(f)
@@ -255,21 +268,23 @@ class PowerBaseTest(base_test.BaseTestClass):
"""The actual test flow and result processing and validate.
"""
- self.collect_power_data()
- self.pass_fail_check()
+ result = self.collect_power_data()
+ self.pass_fail_check(result.average_current)
def collect_power_data(self):
"""Measure power, plot and take log if needed.
+ Returns:
+ A MonsoonResult object.
"""
- tag = ''
# Collecting current measurement data and plot
- self.file_path, self.test_result = self.monsoon_data_collect_save()
- self.power_result.metric_value = (self.test_result *
- PHONE_BATTERY_VOLTAGE)
- wputils.monsoon_data_plot(self.mon_info, self.file_path, tag=tag)
+ result = self.monsoon_data_collect_save()
+ self.power_result.metric_value = (result.average_current *
+ self.mon_voltage)
+ wputils.monsoon_data_plot(self.mon_info, result)
+ return result
- def pass_fail_check(self):
+ def pass_fail_check(self, average_current=None):
"""Check the test result and decide if it passed or failed.
The threshold is provided in the config file. In this class, result is
@@ -282,18 +297,18 @@ class PowerBaseTest(base_test.BaseTestClass):
return
current_threshold = self.threshold[self.test_name]
- if self.test_result:
+ if average_current:
asserts.assert_true(
- abs(self.test_result - current_threshold) / current_threshold <
- THRESHOLD_TOLERANCE,
+ abs(average_current - current_threshold) / current_threshold <
+ self.pass_fail_tolerance,
'Measured average current in [{}]: {:.2f}mA, which is '
'out of the acceptable range {:.2f}±{:.2f}mA'.format(
- self.test_name, self.test_result, current_threshold,
+ self.test_name, average_current, current_threshold,
self.pass_fail_tolerance * current_threshold))
asserts.explicit_pass(
'Measurement finished for [{}]: {:.2f}mA, which is '
'within the acceptable range {:.2f}±{:.2f}'.format(
- self.test_name, self.test_result, current_threshold,
+ self.test_name, average_current, current_threshold,
self.pass_fail_tolerance * current_threshold))
else:
asserts.fail(
@@ -305,7 +320,7 @@ class PowerBaseTest(base_test.BaseTestClass):
Returns:
mon_info: Dictionary with the monsoon packet config
"""
- if hasattr(self, IPERF_DURATION):
+ if self.iperf_duration:
self.mon_duration = self.iperf_duration - 10
mon_info = ObjNew(dut=self.mon,
freq=self.mon_freq,
@@ -330,8 +345,12 @@ class PowerBaseTest(base_test.BaseTestClass):
logging.info('Monsoon recovered from unexpected error')
time.sleep(2)
return True
- except monsoon.MonsoonError:
- logging.info(self.mon.mon.ser.in_waiting)
+ except MonsoonError:
+ try:
+ self.log.info(self.mon_info.dut._mon.ser.in_waiting)
+ except AttributeError:
+ # This attribute does not exist for HVPMs.
+ pass
logging.warning('Unable to recover monsoon from unexpected error')
return False
@@ -342,94 +361,70 @@ class PowerBaseTest(base_test.BaseTestClass):
log file. Take bug report if requested.
Returns:
- data_path: the absolute path to the log file of monsoon current
- measurement
- avg_current: the average current of the test
+ A MonsoonResult object containing information about the gathered
+ data.
"""
tag = '{}_{}_{}'.format(self.test_name, self.dut.model,
self.dut.build_info['build_id'])
+
data_path = os.path.join(self.mon_info.data_path, '{}.txt'.format(tag))
- total_expected_samples = self.mon_info.freq * (self.mon_info.duration +
- self.mon_info.offset)
- min_required_samples = total_expected_samples * MIN_PERCENT_SAMPLE / 100
- # Retry counter for monsoon data aquisition
- retry_measure = 1
- # Indicator that need to re-collect data
- need_collect_data = 1
- result = None
- while retry_measure <= MEASUREMENT_RETRY_COUNT:
- try:
- # If need to retake data
- if need_collect_data == 1:
- #Resets the battery status right before the test started
- self.dut.adb.shell(RESET_BATTERY_STATS)
- self.log.info(
- 'Starting power measurement with monsoon box, try #{}'.
- format(retry_measure))
- #Start the power measurement using monsoon
- self.mon_info.dut.monsoon_usb_auto()
- result = self.mon_info.dut.measure_power(
- self.mon_info.freq,
- self.mon_info.duration,
- tag=tag,
- offset=self.mon_info.offset)
- self.mon_info.dut.reconnect_dut()
- # Reconnect to dut
- else:
- self.mon_info.dut.reconnect_dut()
- # Reconnect and return measurement results if no error happens
- avg_current = result.average_current
- monsoon.MonsoonData.save_to_text_file([result], data_path)
- self.log.info('Power measurement done within {} try'.format(
+
+ # If the specified Monsoon data file already exists (e.g., multiple
+ # measurements in a single test), write the results to a new file with
+ # the postfix "_#".
+ if os.path.exists(data_path):
+ highest_value = 1
+ for filename in os.listdir(os.path.dirname(data_path)):
+ match = re.match(r'{}_(\d+).txt'.format(tag), filename)
+ if match:
+ highest_value = int(match.group(1))
+
+ data_path = os.path.join(self.mon_info.data_path,
+ '%s_%s.txt' % (tag, highest_value + 1))
+
+ total_expected_samples = self.mon_info.freq * self.mon_info.duration
+ min_required_samples = (total_expected_samples
+ * MIN_PERCENT_SAMPLE / 100)
+ for retry_measure in range(1, MEASUREMENT_RETRY_COUNT + 1):
+ # Resets the battery status right before the test starts.
+ self.dut.adb.shell(RESET_BATTERY_STATS)
+ self.log.info(
+ 'Starting power measurement, attempt #{}.'.format(
retry_measure))
- return data_path, avg_current
- # Catch monsoon errors during measurement
- except monsoon.MonsoonError:
- self.log.info(self.mon_info.dut.mon.ser.in_waiting)
- # Break early if it's one count away from limit
- if retry_measure == MEASUREMENT_RETRY_COUNT:
- self.log.error(
- 'Test failed after maximum measurement retry')
- break
-
- self.log.warning('Monsoon error happened, now try to recover')
- # Retry loop to recover monsoon from error
- retry_monsoon = 1
- while retry_monsoon <= RECOVER_MONSOON_RETRY_COUNT:
- mon_status = self.monsoon_recover()
- if mon_status:
- break
- else:
- retry_monsoon += 1
- self.log.warning(
- 'Wait for {} second then try again'.format(
- MONSOON_RETRY_INTERVAL))
- time.sleep(MONSOON_RETRY_INTERVAL)
-
- # Break the loop to end test if failed to recover monsoon
- if not mon_status:
- self.log.error(
- 'Tried our best, still failed to recover monsoon')
- break
- else:
- # If there is no data, or captured samples are less than min
- # required, re-take
- if not result:
- self.log.warning('No data taken, need to remeasure')
- elif len(result._data_points) <= min_required_samples:
- self.log.warning(
- 'More than {} percent of samples are missing due to monsoon error. Need to remeasure'
- .format(100 - MIN_PERCENT_SAMPLE))
- else:
- need_collect_data = 0
- self.log.warning(
- 'Data collected is valid, try reconnect to DUT to finish test'
- )
- retry_measure += 1
-
- if retry_measure > MEASUREMENT_RETRY_COUNT:
- self.log.error('Test failed after maximum measurement retry')
+ # Start the power measurement using monsoon.
+ self.mon_info.dut.usb(PassthroughStates.AUTO)
+ result = self.mon_info.dut.measure_power(
+ self.mon_info.duration,
+ measure_after_seconds=self.mon_info.offset,
+ hz=self.mon_info.freq,
+ output_path=data_path)
+ self.mon_info.dut.usb(PassthroughStates.ON)
+
+ self.log.debug(result)
+ self.log.debug('Samples Gathered: %s. Max Samples: %s '
+ 'Min Samples Required: %s.' %
+ (result.num_samples, total_expected_samples,
+ min_required_samples))
+
+ if result.num_samples <= min_required_samples:
+ retry_measure += 1
+ self.log.warning(
+ 'More than {} percent of samples are missing due to '
+ 'dropped packets. Need to remeasure.'.format(
+ 100 - MIN_PERCENT_SAMPLE))
+ continue
+
+ self.log.info('Measurement successful after {} attempt(s).'.format(
+ retry_measure))
+ return result
+ else:
+ try:
+ self.log.info(self.mon_info.dut._mon.ser.in_waiting)
+ except AttributeError:
+ # This attribute does not exist for HVPMs.
+ pass
+ self.log.error('Unable to gather enough samples to run validation.')
def process_iperf_results(self):
"""Get the iperf results and process.
@@ -454,7 +449,7 @@ class PowerBaseTest(base_test.BaseTestClass):
throughput = (math.fsum(
iperf_result.instantaneous_rates[self.start_meas_time:-1]
) / len(iperf_result.instantaneous_rates[self.start_meas_time:-1])
- ) * 8 * (1.024**2)
+ ) * 8 * (1.024 ** 2)
self.log.info('The average throughput is {}'.format(throughput))
except ValueError:
diff --git a/acts/framework/acts/test_utils/power/PowerCellularLabBaseTest.py b/acts/framework/acts/test_utils/power/PowerCellularLabBaseTest.py
index d7bc63cfdc..d3dae158e8 100644
--- a/acts/framework/acts/test_utils/power/PowerCellularLabBaseTest.py
+++ b/acts/framework/acts/test_utils/power/PowerCellularLabBaseTest.py
@@ -76,6 +76,12 @@ class PowerCellularLabBaseTest(PBT.PowerBaseTest):
super().setup_class()
+ # Unpack test parameters used in this class
+ self.unpack_userparams(md8475_version=None,
+ md8475a_ip_address=None,
+ cmw500_ip=None,
+ cmw500_port=None)
+
# Load calibration tables
filename_calibration_table = (
self.FILENAME_CALIBRATION_TABLE_UNFORMATTED.format(
@@ -114,12 +120,12 @@ class PowerCellularLabBaseTest(PBT.PowerBaseTest):
False if a connection with the callbox could not be started
"""
- if hasattr(self, 'md8475_version'):
+ if self.md8475_version:
self.log.info('Selecting Anrtisu MD8475 callbox.')
# Verify the callbox IP address has been indicated in the configs
- if not hasattr(self, 'md8475_version'):
+ if not self.md8475a_ip_address:
raise RuntimeError(
'md8475a_ip_address was not included in the test '
'configuration.')
@@ -132,7 +138,7 @@ class PowerCellularLabBaseTest(PBT.PowerBaseTest):
else:
raise ValueError('Invalid MD8475 version.')
- elif hasattr(self, 'cmw500_ip') or hasattr(self, 'cmw500_port'):
+ elif self.cmw500_ip or self.cmw500_port:
for key in ['cmw500_ip', 'cmw500_port']:
if not hasattr(self, key):
diff --git a/acts/framework/acts/test_utils/power/PowerGnssBaseTest.py b/acts/framework/acts/test_utils/power/PowerGnssBaseTest.py
index 14c95975c9..5f38aec086 100644
--- a/acts/framework/acts/test_utils/power/PowerGnssBaseTest.py
+++ b/acts/framework/acts/test_utils/power/PowerGnssBaseTest.py
@@ -17,6 +17,8 @@
import logging
import time
+import os
+
import acts.test_utils.power.PowerBaseTest as PBT
from acts import base_test
@@ -38,40 +40,50 @@ class PowerGnssBaseTest(PBT.PowerBaseTest):
"""
def collect_power_data(self):
- """Measure power and plot.
-
- """
- tag = '_Power'
- super().collect_power_data()
- self.monsoon_data_plot_power(self.mon_info, self.file_path, tag=tag)
+ """Measure power and plot."""
+ result = super().collect_power_data()
+ self.monsoon_data_plot_power(self.mon_info, result, tag='_Power')
+ return result
- def monsoon_data_plot_power(self, mon_info, file_path, tag=""):
+ def monsoon_data_plot_power(self, mon_info, monsoon_results, tag=''):
"""Plot the monsoon power data using bokeh interactive plotting tool.
Args:
- mon_info: Dictionary with the monsoon packet config
- file_path: the path to the monsoon log file with current data
+ mon_info: Dictionary with the monsoon packet config.
+ monsoon_results: a MonsoonResult or list of MonsoonResult objects to
+ to plot.
+ tag: an extra tag to append to the resulting filename.
"""
- self.log.info("Plot the power measurement data")
- # Get results as monsoon data object from the input file
- results = monsoon.MonsoonData.from_text_file(file_path)
- # Decouple current and timestamp data from the monsoon object
- current_data = []
- timestamps = []
- voltage = results[0].voltage
- for result in results:
- current_data.extend(result.data_points)
- timestamps.extend(result.timestamps)
- period = 1 / mon_info.freq
- time_relative = [x * period for x in range(len(current_data))]
- # Calculate the average current for the test
-
- current_data = [x * 1000 for x in current_data]
- power_data = [x * voltage for x in current_data]
- avg_current = sum(current_data) / len(current_data)
- color = ['navy'] * len(current_data)
+ if not isinstance(monsoon_results, list):
+ monsoon_results = [monsoon_results]
+ logging.info('Plotting the power measurement data.')
+
+ voltage = monsoon_results[0].voltage
+
+ total_current = 0
+ total_samples = 0
+ for result in monsoon_results:
+ total_current += result.average_current * result.num_samples
+ total_samples += result.num_samples
+ avg_current = total_current / total_samples
+
+ time_relative = [
+ data_point.time
+ for monsoon_result in monsoon_results
+ for data_point in monsoon_result.get_data_points()
+ ]
+
+ power_data = [
+ data_point.current * voltage
+ for monsoon_result in monsoon_results
+ for data_point in monsoon_result.get_data_points()
+ ]
+
+ total_data_points = sum(
+ result.num_samples for result in monsoon_results)
+ color = ['navy'] * total_data_points
# Preparing the data and source link for bokehn java callback
source = ColumnDataSource(
@@ -94,18 +106,20 @@ class PowerGnssBaseTest(PBT.PowerBaseTest):
dt = DataTable(
source=s2, columns=columns, width=1300, height=60, editable=True)
- plot_title = file_path[file_path.rfind('/') + 1:-4] + tag
- output_file("%s/%s.html" % (mon_info.data_path, plot_title))
- TOOLS = 'box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save'
+ plot_title = (os.path.basename(
+ os.path.splitext(monsoon_results[0].tag)[0])
+ + tag)
+ output_file(os.path.join(mon_info.data_path, plot_title + '.html'))
+ tools = 'box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save'
# Create a new plot with the datatable above
plot = figure(
plot_width=1300,
plot_height=700,
title=plot_title,
- tools=TOOLS,
- output_backend="webgl")
- plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="width"))
- plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="height"))
+ tools=tools,
+ output_backend='webgl')
+ plot.add_tools(bokeh_tools.WheelZoomTool(dimensions='width'))
+ plot.add_tools(bokeh_tools.WheelZoomTool(dimensions='height'))
plot.line('x0', 'y0', source=source, line_width=2)
plot.circle('x0', 'y0', source=source, size=0.5, fill_color='color')
plot.xaxis.axis_label = 'Time (s)'
@@ -119,8 +133,7 @@ class PowerGnssBaseTest(PBT.PowerBaseTest):
code=customjsscript)
# Layout the plot and the datatable bar
- l = layout([[dt], [plot]])
- save(l)
+ save(layout([[dt], [plot]]))
def disconnect_usb(self, ad, sleeptime):
"""Disconnect usb while device is on sleep and
diff --git a/acts/framework/acts/test_utils/power/PowerWiFiBaseTest.py b/acts/framework/acts/test_utils/power/PowerWiFiBaseTest.py
index e357ae953b..937656e352 100644
--- a/acts/framework/acts/test_utils/power/PowerWiFiBaseTest.py
+++ b/acts/framework/acts/test_utils/power/PowerWiFiBaseTest.py
@@ -19,6 +19,7 @@ from acts.test_utils.wifi import wifi_test_utils as wutils
from acts.test_utils.wifi import wifi_power_test_utils as wputils
IPERF_DURATION = 'iperf_duration'
+INITIAL_ATTEN = [0, 0, 90, 90]
class PowerWiFiBaseTest(PBT.PowerBaseTest):
@@ -35,6 +36,8 @@ class PowerWiFiBaseTest(PBT.PowerBaseTest):
self.access_point_main = self.access_points[0]
if len(self.access_points) > 1:
self.access_point_aux = self.access_points[1]
+ if hasattr(self, 'attenuators'):
+ self.set_attenuation(INITIAL_ATTEN)
if hasattr(self, 'network_file'):
self.networks = self.unpack_custom_file(self.network_file, False)
self.main_network = self.networks['main_network']
@@ -43,7 +46,7 @@ class PowerWiFiBaseTest(PBT.PowerBaseTest):
self.pkt_sender = self.packet_senders[0]
if hasattr(self, 'iperf_servers'):
self.iperf_server = self.iperf_servers[0]
- if hasattr(self, 'iperf_duration'):
+ if self.iperf_duration:
self.mon_duration = self.iperf_duration - 10
self.create_monsoon_info()
@@ -112,10 +115,11 @@ class PowerWiFiBaseTest(PBT.PowerBaseTest):
If IPERF is run, need to pull iperf results and attach it to the plot.
"""
- super().collect_power_data()
+ result = super().collect_power_data()
tag = ''
- if hasattr(self, IPERF_DURATION):
+ if self.iperf_duration:
throughput = self.process_iperf_results()
tag = '_RSSI_{0:d}dBm_Throughput_{1:.2f}Mbps'.format(
self.RSSI, throughput)
- wputils.monsoon_data_plot(self.mon_info, self.file_path, tag=tag)
+ wputils.monsoon_data_plot(self.mon_info, result, tag=tag)
+ return result
diff --git a/acts/framework/acts/test_utils/power/tel_simulations/LteSimulation.py b/acts/framework/acts/test_utils/power/tel_simulations/LteSimulation.py
index c0cf063107..8637b2dcee 100644
--- a/acts/framework/acts/test_utils/power/tel_simulations/LteSimulation.py
+++ b/acts/framework/acts/test_utils/power/tel_simulations/LteSimulation.py
@@ -17,7 +17,6 @@
import math
from enum import Enum
-from acts.controllers.anritsu_lib import md8475_cellular_simulator as anritsusim
from acts.test_utils.power.tel_simulations.BaseSimulation import BaseSimulation
from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_ONLY
@@ -429,11 +428,6 @@ class LteSimulation(BaseSimulation):
super().__init__(simulator, log, dut, test_config, calibration_table)
- # The LTE simulation relies on the cellular simulator to be a MD8475
- if not isinstance(self.simulator, anritsusim.MD8475CellularSimulator):
- raise ValueError('The LTE simulation relies on the simulator to '
- 'be an Anritsu MD8475 A/B instrument.')
-
if not dut.droid.telephonySetPreferredNetworkTypesForSubscription(
NETWORK_MODE_LTE_ONLY,
dut.droid.subscriptionGetDefaultSubId()):
@@ -651,12 +645,10 @@ class LteSimulation(BaseSimulation):
self.log.info(
"The test name does not include the '{}' parameter. Disabled "
"by default.".format(self.PARAM_RRC_STATUS_CHANGE_TIMER))
- self.simulator.anritsu.set_lte_rrc_status_change(False)
+ self.simulator.set_lte_rrc_state_change_timer(False)
else:
- self.rrc_sc_timer = int(values[1])
- self.simulator.anritsu.set_lte_rrc_status_change(True)
- self.simulator.anritsu.set_lte_rrc_status_change_timer(
- self.rrc_sc_timer)
+ timer = int(values[1])
+ self.simulator.anritsu.set_lte_rrc_status_change(True, timer)
# Get uplink power
diff --git a/acts/framework/acts/test_utils/tel/tel_test_utils.py b/acts/framework/acts/test_utils/tel/tel_test_utils.py
index 0607fc1112..323e1bc1e0 100644
--- a/acts/framework/acts/test_utils/tel/tel_test_utils.py
+++ b/acts/framework/acts/test_utils/tel/tel_test_utils.py
@@ -24,6 +24,7 @@ import re
import os
import urllib.parse
import time
+import acts.controllers.iperf_server as ipf
from acts import signals
from acts import utils
@@ -551,9 +552,14 @@ def toggle_airplane_mode_by_adb(log, ad, new_state=None):
elif new_state is None:
new_state = not cur_state
ad.log.info("Change airplane mode from %s to %s", cur_state, new_state)
- ad.adb.shell("settings put global airplane_mode_on %s" % int(new_state))
- ad.adb.shell("am broadcast -a android.intent.action.AIRPLANE_MODE")
- return True
+ try:
+ ad.adb.shell("settings put global airplane_mode_on %s" % int(new_state))
+ ad.adb.shell("am broadcast -a android.intent.action.AIRPLANE_MODE")
+ except Exception as e:
+ ad.log.error(e)
+ return False
+ changed_state = bool(int(ad.adb.shell("settings get global airplane_mode_on")))
+ return changed_state == new_state
def toggle_airplane_mode(log, ad, new_state=None, strict_checking=True):
@@ -796,6 +802,15 @@ def get_service_state_by_adb(log, ad):
ad.log.info("mVoiceRegState is %s %s", result.group(1),
result.group(2))
return result.group(2)
+ else:
+ if getattr(ad, "sdm_log", False):
+ #look for all occurrence in string
+ result2 = re.findall(r"mVoiceRegState=(\S+)\((\S+)\)", output)
+ for voice_state in result2:
+ if voice_state[0] == 0:
+ ad.log.info("mVoiceRegState is 0 %s", voice_state[1])
+ return voice_state[1]
+ return result2[1][1]
else:
result = re.search(r"mServiceState=(\S+)", output)
if result:
@@ -2800,38 +2815,33 @@ def verify_internet_connection(log, ad, retries=3, expected_state=True):
return False
-def iperf_test_by_adb(log,
- ad,
- iperf_server,
- port_num=None,
- reverse=False,
- timeout=180,
- limit_rate=None,
- omit=10,
- ipv6=False,
- rate_dict=None,
- blocking=True,
- log_file_path=None):
- """Iperf test by adb.
+def iperf_test_with_options(log,
+ ad,
+ iperf_server,
+ iperf_option,
+ timeout=180,
+ rate_dict=None,
+ blocking=True,
+ log_file_path=None):
+ """Iperf adb run helper.
Args:
log: log object
ad: Android Device Object.
- iperf_Server: The iperf host url".
- port_num: TCP/UDP server port
+ iperf_server: The iperf host url".
+ iperf_option: The options to pass to iperf client
timeout: timeout for file download to complete.
- limit_rate: iperf bandwidth option. None by default
- omit: the omit option provided in iperf command.
+ rate_dict: dictionary that can be passed in to save data
+ blocking: run iperf in blocking mode if True
+ log_file_path: location to save logs
+ Returns:
+ True if IPerf runs without throwing an exception
"""
- iperf_option = "-t %s -O %s -J" % (timeout, omit)
- if limit_rate: iperf_option += " -b %s" % limit_rate
- if port_num: iperf_option += " -p %s" % port_num
- if ipv6: iperf_option += " -6"
- if reverse: iperf_option += " -R"
try:
if log_file_path:
ad.adb.shell("rm %s" % log_file_path, ignore_status=True)
ad.log.info("Running adb iperf test with server %s", iperf_server)
+ ad.log.info("IPerf options are %s", iperf_option)
if not blocking:
ad.run_iperf_client_nb(
iperf_server,
@@ -2841,21 +2851,133 @@ def iperf_test_by_adb(log,
return True
result, data = ad.run_iperf_client(
iperf_server, iperf_option, timeout=timeout + 60)
- ad.log.info("Iperf test result with server %s is %s", iperf_server,
+ ad.log.info("IPerf test result with server %s is %s", iperf_server,
result)
if result:
- data_json = json.loads(''.join(data))
- tx_rate = data_json['end']['sum_sent']['bits_per_second']
- rx_rate = data_json['end']['sum_received']['bits_per_second']
- ad.log.info(
- 'iPerf3 upload speed is %sbps, download speed is %sbps',
- tx_rate, rx_rate)
+ iperf_str = ''.join(data)
+ iperf_result = ipf.IPerfResult(iperf_str)
+ if "-u" in iperf_option:
+ udp_rate = iperf_result.avg_rate
+ if udp_rate is None:
+ ad.log.warning(
+ "UDP rate is none, IPerf server returned error: %s",
+ iperf_result.error)
+ ad.log.info("IPerf3 udp speed is %sbps", udp_rate)
+ else:
+ tx_rate = iperf_result.avg_send_rate
+ rx_rate = iperf_result.avg_receive_rate
+ if (tx_rate or rx_rate) is None:
+ ad.log.warning(
+ "A TCP rate is none, IPerf server returned error: %s",
+ iperf_result.error)
+ ad.log.info(
+ "IPerf3 upload speed is %sbps, download speed is %sbps",
+ tx_rate, rx_rate)
if rate_dict is not None:
rate_dict["Uplink"] = tx_rate
rate_dict["Downlink"] = rx_rate
return result
- except Exception as e:
+ except AdbError as e:
ad.log.warning("Fail to run iperf test with exception %s", e)
+ raise
+
+
+def iperf_udp_test_by_adb(log,
+ ad,
+ iperf_server,
+ port_num=None,
+ reverse=False,
+ timeout=180,
+ limit_rate=None,
+ omit=10,
+ ipv6=False,
+ rate_dict=None,
+ blocking=True,
+ log_file_path=None):
+ """Iperf test by adb using UDP.
+
+ Args:
+ log: log object
+ ad: Android Device Object.
+ iperf_Server: The iperf host url".
+ port_num: TCP/UDP server port
+ reverse: whether to test download instead of upload
+ timeout: timeout for file download to complete.
+ limit_rate: iperf bandwidth option. None by default
+ omit: the omit option provided in iperf command.
+ ipv6: whether to run the test as ipv6
+ rate_dict: dictionary that can be passed in to save data
+ blocking: run iperf in blocking mode if True
+ log_file_path: location to save logs
+ """
+ iperf_option = "-u -i 1 -t %s -O %s -J" % (timeout, omit)
+ if limit_rate:
+ iperf_option += " -b %s" % limit_rate
+ if port_num:
+ iperf_option += " -p %s" % port_num
+ if ipv6:
+ iperf_option += " -6"
+ if reverse:
+ iperf_option += " -R"
+ try:
+ return iperf_test_with_options(log,
+ ad,
+ iperf_server,
+ iperf_option,
+ timeout,
+ rate_dict,
+ blocking,
+ log_file_path)
+ except AdbError:
+ return False
+
+def iperf_test_by_adb(log,
+ ad,
+ iperf_server,
+ port_num=None,
+ reverse=False,
+ timeout=180,
+ limit_rate=None,
+ omit=10,
+ ipv6=False,
+ rate_dict=None,
+ blocking=True,
+ log_file_path=None):
+ """Iperf test by adb using TCP.
+
+ Args:
+ log: log object
+ ad: Android Device Object.
+ iperf_server: The iperf host url".
+ port_num: TCP/UDP server port
+ reverse: whether to test download instead of upload
+ timeout: timeout for file download to complete.
+ limit_rate: iperf bandwidth option. None by default
+ omit: the omit option provided in iperf command.
+ ipv6: whether to run the test as ipv6
+ rate_dict: dictionary that can be passed in to save data
+ blocking: run iperf in blocking mode if True
+ log_file_path: location to save logs
+ """
+ iperf_option = "-t %s -O %s -J" % (timeout, omit)
+ if limit_rate:
+ iperf_option += " -b %s" % limit_rate
+ if port_num:
+ iperf_option += " -p %s" % port_num
+ if ipv6:
+ iperf_option += " -6"
+ if reverse:
+ iperf_option += " -R"
+ try:
+ return iperf_test_with_options(log,
+ ad,
+ iperf_server,
+ iperf_option,
+ timeout,
+ rate_dict,
+ blocking,
+ log_file_path)
+ except AdbError:
return False
diff --git a/acts/framework/acts/test_utils/wifi/ota_chamber.py b/acts/framework/acts/test_utils/wifi/ota_chamber.py
index 26abbe5f8b..53494dad52 100644
--- a/acts/framework/acts/test_utils/wifi/ota_chamber.py
+++ b/acts/framework/acts/test_utils/wifi/ota_chamber.py
@@ -14,6 +14,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import contextlib
+import io
import time
from acts import logger
from acts import utils
@@ -48,7 +50,6 @@ class OtaChamber(object):
Base class provides functions whose implementation is shared by all
chambers.
"""
-
def reset_chamber(self):
"""Resets the chamber to its zero/home state."""
raise NotImplementedError
@@ -80,7 +81,6 @@ class OtaChamber(object):
class MockChamber(OtaChamber):
"""Class that implements mock chamber for test development and debug."""
-
def __init__(self, config):
self.config = config.copy()
self.device_id = self.config['device_id']
@@ -117,7 +117,6 @@ class MockChamber(OtaChamber):
class OctoboxChamber(OtaChamber):
"""Class that implements Octobox chamber."""
-
def __init__(self, config):
self.config = config.copy()
self.device_id = self.config['device_id']
@@ -130,8 +129,9 @@ class OctoboxChamber(OtaChamber):
def set_orientation(self, orientation):
self.log.info('Setting orientation to {} degrees.'.format(orientation))
- utils.exe_cmd('sudo {} -d {} -p {}'.format(
- self.TURNTABLE_FILE_PATH, self.device_id, orientation))
+ utils.exe_cmd('sudo {} -d {} -p {}'.format(self.TURNTABLE_FILE_PATH,
+ self.device_id,
+ orientation))
def reset_chamber(self):
self.log.info('Resetting chamber to home state')
@@ -155,7 +155,6 @@ class ChamberAutoConnect(object):
class BluetestChamber(OtaChamber):
"""Class that implements Octobox chamber."""
-
def __init__(self, config):
import flow
self.config = config.copy()
@@ -165,6 +164,16 @@ class BluetestChamber(OtaChamber):
self.stirrer_ids = [0, 1, 2]
self.current_mode = None
+ # Capture print output decorator
+ @staticmethod
+ def _capture_output(func, *args, **kwargs):
+ """Creates a decorator to capture stdout from bluetest module"""
+ f = io.StringIO()
+ with contextlib.redirect_stdout(f):
+ func(*args, **kwargs)
+ output = f.getvalue()
+ return output
+
def _connect(self):
self.chamber.connect(self.config['ip_address'],
self.config['username'], self.config['password'])
@@ -172,12 +181,15 @@ class BluetestChamber(OtaChamber):
def _init_manual_mode(self):
self.current_mode = 'manual'
for stirrer_id in self.stirrer_ids:
- self.chamber.chamber_stirring_manual_init(stirrer_id)
+ out = self._capture_output(
+ self.chamber.chamber_stirring_manual_init, stirrer_id)
+ if "failed" in out:
+ self.log.warning("Initialization error: {}".format(out))
time.sleep(CHAMBER_SLEEP)
def _init_continuous_mode(self):
self.current_mode = 'continuous'
- self.chamber.chamber_stirring_continous_init()
+ self.chamber.chamber_stirring_continuous_init()
def _init_stepped_mode(self, steps):
self.current_mode = 'stepped'
@@ -188,7 +200,17 @@ class BluetestChamber(OtaChamber):
if self.current_mode != 'manual':
self._init_manual_mode()
self.log.info('Setting stirrer {} to {}.'.format(stirrer_id, position))
- self.chamber.chamber_stirring_manual_set_pos(stirrer_id, position)
+ out = self._capture_output(
+ self.chamber.chamber_stirring_manual_set_pos, stirrer_id, position)
+ if "failed" in out:
+ self.log.warning("Bluetest error: {}".format(out))
+ self.log.warning("Set position failed. Retrying.")
+ self.current_mode = None
+ self.set_stirrer_pos(stirrer_id, position)
+ else:
+ self._capture_output(self.chamber.chamber_stirring_manual_wait,
+ CHAMBER_SLEEP)
+ self.log.warning('Stirrer {} at {}.'.format(stirrer_id, position))
def set_orientation(self, orientation):
self.set_stirrer_pos(2, orientation * 100 / 360)
@@ -196,10 +218,10 @@ class BluetestChamber(OtaChamber):
def start_continuous_stirrers(self):
if self.current_mode != 'continuous':
self._init_continuous_mode()
- self.chamber.chamber_stirring_continous_start()
+ self.chamber.chamber_stirring_continuous_start()
def stop_continuous_stirrers(self):
- self.chamber.chamber_stirring_continous_stop()
+ self.chamber.chamber_stirring_continuous_stop()
def step_stirrers(self, steps):
if self.current_mode != 'stepped':
diff --git a/acts/framework/acts/test_utils/wifi/ota_sniffer.py b/acts/framework/acts/test_utils/wifi/ota_sniffer.py
index 264fd36819..884f8f12ca 100644
--- a/acts/framework/acts/test_utils/wifi/ota_sniffer.py
+++ b/acts/framework/acts/test_utils/wifi/ota_sniffer.py
@@ -1,23 +1,49 @@
-from acts import logger
-from acts.controllers.utils_lib import ssh
+#!/usr/bin/env python3
+#
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
import csv
-import io
import os
+from acts import context
+from acts import logger
+from acts import utils
+from acts.controllers.utils_lib import ssh
-def create(configs, logging_dir):
- """Factory method for sniffer
+def create(configs):
+ """Factory method for sniffer.
Args:
- configs: list of dicts with sniffer settings, settings must contain the following :
- ssh_settings (ssh credentials required to log into the sniffer)
+ configs: list of dicts with sniffer settings.
+ Settings must contain the following : ssh_settings, type, OS, interface.
+
+ Returns:
+ objs: list of sniffer class objects.
"""
objs = []
for config in configs:
try:
if config["type"] == "tshark":
- objs.append(TsharkSniffer(config, logging_dir))
+ if config["os"] == "unix":
+ objs.append(TsharkSnifferOnUnix(config))
+ elif config["os"] == "linux":
+ objs.append(TsharkSnifferOnLinux(config))
+ else:
+ raise RuntimeError("Wrong sniffer config")
+
elif config["type"] == "mock":
- objs.append(MockSniffer(config, logging_dir))
+ objs.append(MockSniffer(config))
except KeyError:
raise KeyError("Invalid sniffer configurations")
return objs
@@ -28,343 +54,431 @@ def destroy(objs):
class OtaSnifferBase(object):
- """Base class provides functions whose implementation in shared by all sniffers"""
+ """Base class defining common sniffers functions."""
_log_file_counter = 0
- def start_capture(self):
- """Starts the sniffer Capture"""
- raise NotImplementedError
+ @property
+ def started(self):
+ raise NotImplementedError('started must be specified.')
- def stop_capture(self):
- """Stops the sniffer Capture"""
- raise NotImplementedError
+ def start_capture(self, network, duration=30):
+ """Starts the sniffer Capture.
+
+ Args:
+ network: dict containing network information such as SSID, etc.
+ duration: duration of sniffer capture in seconds.
+ """
+ raise NotImplementedError('start_capture must be specified.')
+
+ def stop_capture(self, tag=""):
+ """Stops the sniffer Capture.
+
+ Args:
+ tag: string to tag sniffer capture file name with.
+ """
+ raise NotImplementedError('stop_capture must be specified.')
def _get_remote_dump_path(self):
+ """Returns name of the sniffer dump file."""
return "sniffer_dump.csv"
def _get_full_file_path(self, tag=None):
"""Returns the full file path for the sniffer capture dump file.
- Returns the full file path (on test machine) for the sniffer capture dump file
+ Returns the full file path (on test machine) for the sniffer capture
+ dump file.
Args:
tag: The tag appended to the sniffer capture dump file .
"""
- out_dir = os.path.join(self.log_dir, "sniffer_files")
- if not os.path.exists(out_dir):
- os.mkdir(out_dir)
tags = [tag, "count", OtaSnifferBase._log_file_counter]
out_file_name = 'Sniffer_Capture_%s.csv' % ('_'.join(
[str(x) for x in tags if x != '' and x is not None]))
OtaSnifferBase._log_file_counter += 1
- file_path = os.path.join(out_dir, out_file_name)
+ file_path = os.path.join(self.log_path, out_file_name)
return file_path
+ @property
+ def log_path(self):
+ current_context = context.get_current_context()
+ full_out_dir = os.path.join(current_context.get_full_output_path(),
+ 'sniffer_captures')
-class MockSniffer(OtaSnifferBase):
- """Class that implements mock sniffer for test development and debug"""
+ # Ensure the directory exists.
+ utils.create_dir(full_out_dir)
- def __init__(self, config, logging_dir):
- self.log = logger.create_tagged_trace_logger("Mock Sniffer")
- self.log_dir = logging_dir
+ return full_out_dir
- def _ssh_into_sniffer(self):
- """logs into the sniffer machine"""
- self.log.info("Logging into the sniffer machine")
- def _connect_to_network(self):
- """ Connects to a given network
+class MockSniffer(OtaSnifferBase):
+ """Class that implements mock sniffer for test development and debug."""
+ def __init__(self, config):
+ self.log = logger.create_tagged_trace_logger("Mock Sniffer")
+
+ def start_capture(self, network, duration=30):
+ """Starts sniffer capture on the specified machine.
Args:
- network: dictionary of network credentials; SSID and password
+ network: dict of network credentials.
+ duration: duration of the sniff.
"""
- self.log.info("Connecting to wireless network ")
-
- def _run_sniffer(self):
- """Starts the sniffer"""
- self.log.info("Starting Sniffer")
- self.log.info(
- "Executing sniffer command on the sniffer machine")
-
- def _stop_sniffer(self):
- """ Stops the sniffer"""
- self.log.info("Stopping Sniffer")
-
- def start_capture(self):
- """Starts sniffer capture on the specified machine"""
-
- self._ssh_into_sniffer()
- self._connect_to_network()
- self._run_sniffer()
+ self.log.info("Starting sniffer.")
def stop_capture(self):
- """Stops the sniffer
+ """Stops the sniffer.
Returns:
- The name of the processed sniffer dump from the terminated sniffer process
+ log_file: name of processed sniffer.
"""
- self._stop_sniffer()
- log_file = self._get_full_file_path("Mock")
+ self.log.info("Stopping sniffer.")
+ log_file = self._get_full_file_path()
+ with open(log_file, 'w') as file:
+ file.write('this is a sniffer dump.')
return log_file
-class TsharkSniffer(OtaSnifferBase):
- """Class that implements Tshark based Sniffer """
-
- def __init__(self, config, logging_dir):
+class TsharkSnifferBase(OtaSnifferBase):
+ """Class that implements Tshark based sniffer controller. """
+
+ TYPE_SUBTYPE_DICT = {
+ "0": "Association Requests",
+ "1": "Association Responses",
+ "2": "Reassociation Requests",
+ "3": "Resssociation Responses",
+ "4": "Probe Requests",
+ "5": "Probe Responses",
+ "8": "Beacon",
+ "9": "ATIM",
+ "10": "Disassociations",
+ "11": "Authentications",
+ "12": "Deauthentications",
+ "13": "Actions",
+ "24": "Block ACK Requests",
+ "25": "Block ACKs",
+ "26": "PS-Polls",
+ "27": "RTS",
+ "28": "CTS",
+ "29": "ACK",
+ "30": "CF-Ends",
+ "31": "CF-Ends/CF-Acks",
+ "32": "Data",
+ "33": "Data+CF-Ack",
+ "34": "Data+CF-Poll",
+ "35": "Data+CF-Ack+CF-Poll",
+ "36": "Null",
+ "37": "CF-Ack",
+ "38": "CF-Poll",
+ "39": "CF-Ack+CF-Poll",
+ "40": "QoS Data",
+ "41": "QoS Data+CF-Ack",
+ "42": "QoS Data+CF-Poll",
+ "43": "QoS Data+CF-Ack+CF-Poll",
+ "44": "QoS Null",
+ "46": "QoS CF-Poll (Null)",
+ "47": "QoS CF-Ack+CF-Poll (Null)"
+ }
+
+ TSHARK_COLUMNS = [
+ "frame_number", "frame_time_relative", "mactime", "frame_len", "rssi",
+ "channel", "ta", "ra", "bssid", "type", "subtype", "duration", "seq",
+ "retry", "pwrmgmt", "moredata", "ds", "phy", "radio_datarate",
+ "vht_datarate", "radiotap_mcs_index", "vht_mcs", "wlan_data_rate",
+ "11n_mcs_index", "11ac_mcs", "11n_bw", "11ac_bw", "vht_nss", "mcs_gi",
+ "vht_gi", "vht_coding", "ba_bm", "fc_status", "bf_report"
+ ]
+
+ TSHARK_OUTPUT_COLUMNS = [
+ "frame_number", "frame_time_relative", "mactime", "ta", "ra", "bssid",
+ "rssi", "channel", "frame_len", "Info", "radio_datarate",
+ "radiotap_mcs_index", "pwrmgmt", "phy", "vht_nss", "vht_mcs",
+ "vht_datarate", "11ac_mcs", "11ac_bw", "vht_gi", "vht_coding",
+ "wlan_data_rate", "11n_mcs_index", "11n_bw", "mcs_gi", "type",
+ "subtype", "duration", "seq", "retry", "moredata", "ds", "ba_bm",
+ "fc_status", "bf_report"
+ ]
+
+ TSHARK_FIELDS_LIST = [
+ 'frame.number', 'frame.time_relative', 'radiotap.mactime', 'frame.len',
+ 'radiotap.dbm_antsignal', 'wlan_radio.channel', 'wlan.ta', 'wlan.ra',
+ 'wlan.bssid', 'wlan.fc.type', 'wlan.fc.type_subtype', 'wlan.duration',
+ 'wlan.seq', 'wlan.fc.retry', 'wlan.fc.pwrmgt', 'wlan.fc.moredata',
+ 'wlan.fc.ds', 'wlan_radio.phy', 'radiotap.datarate',
+ 'radiotap.vht.datarate.0', 'radiotap.mcs.index', 'radiotap.vht.mcs.0',
+ 'wlan_radio.data_rate', 'wlan_radio.11n.mcs_index',
+ 'wlan_radio.11ac.mcs', 'wlan_radio.11n.bandwidth',
+ 'wlan_radio.11ac.bandwidth', 'radiotap.vht.nss.0', 'radiotap.mcs.gi',
+ 'radiotap.vht.gi', 'radiotap.vht.coding.0', 'wlan.ba.bm',
+ 'wlan.fcs.status', 'wlan.vht.compressed_beamforming_report.snr'
+ ]
+
+ def __init__(self, config):
self.sniffer_proc_pid = None
self.log = logger.create_tagged_trace_logger("Tshark Sniffer")
self.ssh_config = config["ssh_config"]
- self.params = config["sniffer_params"]
- self.log_dir = logging_dir
- self.type_subtype_dict = {
- "0": "Association Requests",
- "1": "Association Responses",
- "2": "Reassociation Requests",
- "3": "Resssociation Responses",
- "4": "Probe Requests",
- "5": "Probe Responses",
- "8": "Beacon",
- "9": "ATIM",
- "10": "Disassociations",
- "11": "Authentications",
- "12": "Deauthentications",
- "13": "Actions",
- "24": "Block ACK Requests",
- "25": "Block ACKs",
- "26": "PS-Polls",
- "27": "RTS",
- "28": "CTS",
- "29": "ACK",
- "30": "CF-Ends",
- "31": "CF-Ends/CF-Acks",
- "32": "Data",
- "33": "Data+CF-Ack",
- "34": "Data+CF-Poll",
- "35": "Data+CF-Ack+CF-Poll",
- "36": "Null",
- "37": "CF-Ack",
- "38": "CF-Poll",
- "39": "CF-Ack+CF-Poll",
- "40": "QoS Data",
- "41": "QoS Data+CF-Ack",
- "42": "QoS Data+CF-Poll",
- "43": "QoS Data+CF-Ack+CF-Poll",
- "44": "QoS Null",
- "46": "QoS CF-Poll (Null)",
- "47": "QoS CF-Ack+CF-Poll (Null)"
- }
-
- self.tshark_columns = [
- "frame_number", "frame_time_relative", "mactime", "frame_len",
- "rssi", "channel", "ta", "ra", "bssid", "type", "subtype",
- "duration", "seq", "retry", "pwrmgmt", "moredata", "ds", "phy",
- "radio_datarate", "vht_datarate", "radiotap_mcs_index", "vht_mcs", "wlan_data_rate",
- "11n_mcs_index", "11ac_mcs", "11n_bw", "11ac_bw", "vht_nss", "mcs_gi",
- "vht_gi", "vht_coding", "ba_bm", "fc_status",
- "bf_report"
- ]
-
-
- self._tshark_output_columns = [
- "frame_number",
- "frame_time_relative",
- "mactime",
- "ta",
- "ra",
- "bssid",
- "rssi",
- "channel",
- "frame_len",
- "Info",
- "radio_datarate",
- "radiotap_mcs_index",
- "pwrmgmt",
- "phy",
- "vht_nss",
- "vht_mcs",
- "vht_datarate",
- "11ac_mcs",
- "11ac_bw",
- "vht_gi",
- "vht_coding",
- "wlan_data_rate",
- "11n_mcs_index",
- "11n_bw",
- "mcs_gi",
- "type",
- "subtype",
- "duration",
- "seq",
- "retry",
- "moredata",
- "ds",
- "ba_bm",
- "fc_status",
- "bf_report"
- ]
-
-
- self.tshark_fields = '-T fields -e frame.number -e frame.time_relative -e radiotap.mactime -e frame.len '+\
- '-e radiotap.dbm_antsignal -e wlan_radio.channel '+\
- '-e wlan.ta -e wlan.ra -e wlan.bssid '+\
- '-e wlan.fc.type -e wlan.fc.type_subtype -e wlan.duration -e wlan.seq -e wlan.fc.retry -e wlan.fc.pwrmgt -e wlan.fc.moredata -e wlan.fc.ds '+\
- '-e wlan_radio.phy '+\
- '-e radiotap.datarate -e radiotap.vht.datarate.0 '+\
- '-e radiotap.mcs.index -e radiotap.vht.mcs.0 '+\
- '-e wlan_radio.data_rate -e wlan_radio.11n.mcs_index -e wlan_radio.11ac.mcs '+\
- '-e wlan_radio.11n.bandwidth -e wlan_radio.11ac.bandwidth '+\
- '-e radiotap.vht.nss.0 -e radiotap.mcs.gi -e radiotap.vht.gi -e radiotap.vht.coding.0 '+\
- '-e wlan.ba.bm -e wlan.fcs.status -e wlan.vht.compressed_beamforming_report.snr '+ \
- '-y IEEE802_11_RADIO -E separator="^" '
+ self.sniffer_os = config["os"]
+ self.sniffer_interface = config["interface"]
+
+ #Logging into sniffer
+ self.log.info("Logging into sniffer.")
+ self._sniffer_server = ssh.connection.SshConnection(
+ ssh.settings.from_config(self.ssh_config))
+
+ self.tshark_fields = self._generate_tshark_fields(
+ self.TSHARK_FIELDS_LIST)
+
+ self.tshark_path = self._sniffer_server.run("which tshark").stdout
@property
def _started(self):
return self.sniffer_proc_pid is not None
- def _ssh_into_sniffer(self):
- """logs into the sniffer machine"""
- self.log.info("Logging into Sniffer")
- self._sniffer_server = ssh.connection.SshConnection(
- ssh.settings.from_config(self.ssh_config))
+ def _scan_for_networks(self):
+ """Scans for wireless networks on the sniffer."""
+ raise NotImplementedError
+
+ def _init_network_association(self, ssid, password):
+ """Associates the sniffer to the network to sniff.
+
+ Args:
+ ssid: SSID of the wireless network to connect to.
+ password: password of the wireless network to connect to.
+ """
+ raise NotImplementedError
+
+ def _get_tshark_command(self, duration):
+ """Frames the appropriate tshark command.
+
+ Args:
+ duration: duration to sniff for.
+
+ Returns:
+ tshark_command : appropriate tshark command.
+ """
+
+ tshark_command = "{} -l -i {} -I -t u -a duration:{}".format(
+ self.tshark_path, self.sniffer_interface, int(duration))
+
+ return tshark_command
+
+ def _generate_tshark_fields(self, fields):
+ """Generates tshark fields to be appended to the tshark command.
+
+ Args:
+ fields: list of tshark fields to be appended to the tshark command.
+
+ Returns:
+ tshark_fields: string of tshark fields to be appended to the tshark command.
+ """
+
+ tshark_fields = '-T fields -y IEEE802_11_RADIO -E separator="^"'
+ for field in fields:
+ tshark_fields = tshark_fields + " -e {}".format(field)
+ return tshark_fields
def _connect_to_network(self, network):
- """ Connects to a given network
- Connects to a wireless network using networksetup utility
+ """ Connects to a wireless network using networksetup utility.
Args:
- network: dictionary of network credentials; SSID and password
+ network: dictionary of network credentials; SSID and password.
"""
+
self.log.info("Connecting to network {}".format(network["SSID"]))
- #Scan to see if the requested SSID is available
- scan_result = self._sniffer_server.run("/usr/local/bin/airport -s")
+ # Scan to see if the requested SSID is available
+ scan_result = self._scan_for_networks()
- if network["SSID"] not in scan_result.stdout:
+ if network["SSID"] not in scan_result:
self.log.error("{} not found in scan".format(network["SSID"]))
if "password" not in network.keys():
network["password"] = ""
- if set(["SSID", "password"]).issubset(network.keys()):
- pass
- else:
- raise KeyError("Incorrect Network Config")
-
- connect_command = "networksetup -setairportnetwork en0 {} {}".format(
- network["SSID"], network["password"])
- self._sniffer_server.run(connect_command)
+ self._init_network_association(network["SSID"], network["password"])
def _run_tshark(self, sniffer_command):
- """Starts the sniffer"""
+ """Starts the sniffer.
- self.log.info("Starting Sniffer")
+ Args:
+ sniffer_command: sniffer command to execute.
+ """
+
+ self.log.info("Starting sniffer.")
sniffer_job = self._sniffer_server.run_async(sniffer_command)
self.sniffer_proc_pid = sniffer_job.stdout
def _stop_tshark(self):
- """ Stops the sniffer"""
+ """ Stops the sniffer."""
- self.log.info("Stopping Sniffer")
+ self.log.info("Stopping sniffer")
# while loop to kill the sniffer process
+ kill_line_logged = False
+
while True:
try:
- #Returns error if process was killed already
- self._sniffer_server.run("kill -15 {}".format(
- str(self.sniffer_proc_pid)))
- except:
- pass
- try:
- #Returns 1 if process was killed
+ # Returns 1 if process was killed
self._sniffer_server.run(
- "/bin/ps aux| grep {} | grep -v grep".format(
+ "ps aux| grep {} | grep -v grep".format(
self.sniffer_proc_pid))
except:
break
+ try:
+ # Returns error if process was killed already
+ if not kill_line_logged:
+ self.log.info('Killing tshark process.')
+ kill_line_logged = True
+ self._sniffer_server.run("kill -15 {}".format(
+ str(self.sniffer_proc_pid)))
+ except:
+ # Except is hit when tshark is already dead but we will break
+ # out of the loop when confirming process is dead using ps aux
+ pass
+
+ def _process_tshark_dump(self, temp_dump_file, tag):
+ """ Process tshark dump for better readability.
- def _process_tshark_dump(self, dump, sniffer_tag):
- """ Process tshark dump for better readability
Processes tshark dump for better readability and saves it to a file.
- Adds an info column at the end of each row.
- Format of the info columns: subtype of the frame, sequence no and retry status
+ Adds an info column at the end of each row. Format of the info columns:
+ subtype of the frame, sequence no and retry status.
Args:
- dump : string of sniffer capture output
- sniffer_tag : tag to be appended to the dump file
-
+ temp_dump_file : string of sniffer capture output.
+ tag : tag to be appended to the dump file.
Returns:
- log_file : name of the file where the processed dump is stored
+ log_file : name of the file where the processed dump is stored.
"""
- dump = io.StringIO(dump)
- log_file = self._get_full_file_path(sniffer_tag)
- with open(log_file, "w") as output_csv:
- reader = csv.DictReader(
- dump, fieldnames=self.tshark_columns, delimiter="^")
- writer = csv.DictWriter(
- output_csv, fieldnames=self._tshark_output_columns, delimiter="\t")
+ log_file = self._get_full_file_path(tag)
+ with open(temp_dump_file, "r") as input_csv, open(log_file,
+ "w") as output_csv:
+ reader = csv.DictReader(input_csv,
+ fieldnames=self.TSHARK_COLUMNS,
+ delimiter="^")
+ writer = csv.DictWriter(output_csv,
+ fieldnames=self.TSHARK_OUTPUT_COLUMNS,
+ delimiter="\t")
writer.writeheader()
for row in reader:
- if row["subtype"] in self.type_subtype_dict.keys():
+ if row["subtype"] in self.TYPE_SUBTYPE_DICT.keys():
row["Info"] = "{sub} S={seq} retry={retry_status}".format(
- sub=self.type_subtype_dict[row["subtype"]],
+ sub=self.TYPE_SUBTYPE_DICT[row["subtype"]],
seq=row["seq"],
retry_status=row["retry"])
else:
- row["Info"] = "{sub} S={seq} retry={retry_status}\n".format(
- sub=row["subtype"],
- seq=row["seq"],
- retry_status=row["retry"])
+ row["Info"] = "{} S={} retry={}\n".format(
+ row["subtype"], row["seq"], row["retry"])
writer.writerow(row)
return log_file
def start_capture(self, network, duration=30):
- """Starts sniffer capture on the specified machine"""
+ """Starts sniffer capture on the specified machine.
+
+ Args:
+ network: dict describing network to sniff on.
+ duration: duration of sniff.
+ """
# Checking for existing sniffer processes
if self._started:
self.log.info("Sniffer already running")
return
- self.tshark_command = "/usr/local/bin/tshark -l -I -t u -a duration:{}".format(
- duration)
-
- # Logging into the sniffer
- self._ssh_into_sniffer()
-
- #Connecting to network
+ # Connecting to network
self._connect_to_network(network)
+ tshark_command = self._get_tshark_command(duration)
+
sniffer_command = "{tshark} {fields} > {log_file}".format(
- tshark=self.tshark_command,
+ tshark=tshark_command,
fields=self.tshark_fields,
log_file=self._get_remote_dump_path())
- #Starting sniffer capture by executing tshark command
+ # Starting sniffer capture by executing tshark command
self._run_tshark(sniffer_command)
- def stop_capture(self, sniffer_tag=""):
- """Stops the sniffer
+ def stop_capture(self, tag=""):
+ """Stops the sniffer.
+ Args:
+ tag: tag to be appended to the sniffer output file.
Returns:
- The name of the processed sniffer dump from the terminated sniffer process
+ log_file: path to sniffer dump.
"""
- #Checking if there is an ongoing sniffer capture
+ # Checking if there is an ongoing sniffer capture
if not self._started:
self.log.error("No sniffer process running")
return
# Killing sniffer process
self._stop_tshark()
- sniffer_dump = self._sniffer_server.run('cat {}'.format(
- self._get_remote_dump_path()))
-
- #Processing writing capture output to file
- log_file = self._process_tshark_dump(sniffer_dump.stdout, sniffer_tag)
+ # Processing writing capture output to file
+ temp_dump_path = os.path.join(self.log_path, "sniffer_temp_dump.csv")
+ self._sniffer_server.pull_file(temp_dump_path,
+ self._get_remote_dump_path())
+ log_file = self._process_tshark_dump(temp_dump_path, tag)
self.sniffer_proc_pid = None
-
+ utils.exe_cmd("rm -f {}".format(temp_dump_path))
return log_file
+
+
+class TsharkSnifferOnUnix(TsharkSnifferBase):
+ """Class that implements Tshark based sniffer controller on Unix systems."""
+ def _scan_for_networks(self):
+ """Scans the wireless networks on the sniffer.
+
+ Returns:
+ scan_results : output of the scan command.
+ """
+
+ scan_command = "/usr/local/bin/airport -s"
+ scan_result = self._sniffer_server.run(scan_command).stdout
+
+ return scan_result
+
+ def _init_network_association(self, ssid, password):
+ """Associates the sniffer to the network to sniff.
+
+ Associates the sniffer to wireless network to sniff using networksetup utility.
+
+ Args:
+ ssid: SSID of the wireless network to connect to.
+ password: password of the wireless network to connect to.
+ """
+
+ connect_command = "networksetup -setairportnetwork en0 {} {}".format(
+ ssid, password)
+ self._sniffer_server.run(connect_command)
+
+
+class TsharkSnifferOnLinux(TsharkSnifferBase):
+ """Class that implements Tshark based sniffer controller on Linux systems."""
+ def _scan_for_networks(self):
+ """Scans the wireless networks on the sniffer.
+
+ Returns:
+ scan_results : output of the scan command.
+ """
+
+ scan_command = "nmcli device wifi rescan; nmcli device wifi list"
+ scan_result = self._sniffer_server.run(scan_command).stdout
+
+ return scan_result
+
+ def _init_network_association(self, ssid, password):
+ """Associates the sniffer to the network to sniff.
+
+ Associates the sniffer to wireless network to sniff using nmcli utility.
+
+ Args:
+ ssid: SSID of the wireless network to connect to.
+ password: password of the wireless network to connect to.
+ """
+ if password != "":
+ connect_command = "sudo nmcli device wifi connect {} password {}".format(
+ ssid, password)
+ else:
+ connect_command = "sudo nmcli device wifi connect {}".format(ssid)
+ self._sniffer_server.run(connect_command)
diff --git a/acts/framework/acts/test_utils/wifi/wifi_constants.py b/acts/framework/acts/test_utils/wifi/wifi_constants.py
index 82da5208a8..21f13d2fc1 100644
--- a/acts/framework/acts/test_utils/wifi/wifi_constants.py
+++ b/acts/framework/acts/test_utils/wifi/wifi_constants.py
@@ -32,6 +32,24 @@ WIFI_NETWORK_SUGGESTION_POST_CONNECTION = "WifiNetworkSuggestionPostConnection"
CONNECT_BY_CONFIG_SUCCESS = 'WifiManagerConnectByConfigOnSuccess'
CONNECT_BY_NETID_SUCCESS = 'WifiManagerConnectByNetIdOnSuccess'
+# Softap related constants
+SOFTAP_CALLBACK_EVENT = "WifiManagerSoftApCallback-"
+# Callback Event for softap state change
+# WifiManagerSoftApCallback-[callbackId]-OnStateChanged
+SOFTAP_STATE_CHANGED = "-OnStateChanged"
+# Cllback Event for client number change:
+# WifiManagerSoftApCallback-[callbackId]-OnNumClientsChanged
+SOFTAP_NUMBER_CLIENTS_CHANGED = "-OnNumClientsChanged"
+SOFTAP_NUMBER_CLIENTS_CALLBACK_KEY = "NumClients"
+SOFTAP_STATE_CHANGE_CALLBACK_KEY = "State"
+WIFI_AP_DISABLING_STATE = 10
+WIFI_AP_DISABLED_STATE = 11
+WIFI_AP_ENABLING_STATE = 12
+WIFI_AP_ENABLED_STATE = 13
+WIFI_AP_FAILED_STATE = 14
+DEFAULT_SOFTAP_TIMEOUT_S = 600 # 10 minutes
+
+
# AP related constants
AP_MAIN = "main_AP"
AP_AUX = "aux_AP"
diff --git a/acts/framework/acts/test_utils/wifi/wifi_power_test_utils.py b/acts/framework/acts/test_utils/wifi/wifi_power_test_utils.py
index 34a820b934..ac18a8f3e8 100644
--- a/acts/framework/acts/test_utils/wifi/wifi_power_test_utils.py
+++ b/acts/framework/acts/test_utils/wifi/wifi_power_test_utils.py
@@ -16,8 +16,8 @@
import logging
import time
+import os
from acts import utils
-from acts.controllers import monsoon
from acts.libs.proc import job
from acts.controllers.ap_lib import bridge_interface as bi
from acts.test_utils.wifi import wifi_test_utils as wutils
@@ -39,7 +39,7 @@ ENABLED_MODULATED_DTIM = 'gEnableModulatedDTIM='
MAX_MODULATED_DTIM = 'gMaxLIModulatedDTIM='
-def monsoon_data_plot(mon_info, file_path, tag=""):
+def monsoon_data_plot(mon_info, monsoon_results, tag=''):
"""Plot the monsoon current data using bokeh interactive plotting tool.
Plotting power measurement data with bokeh to generate interactive plots.
@@ -50,9 +50,10 @@ def monsoon_data_plot(mon_info, file_path, tag=""):
Args:
mon_info: obj with information of monsoon measurement, including
- monsoon device object, measurement frequency, duration and
- offset etc.
- file_path: the path to the monsoon log file with current data
+ monsoon device object, measurement frequency, duration, etc.
+ monsoon_results: a MonsoonResult or list of MonsoonResult objects to
+ to plot.
+ tag: an extra tag to append to the resulting filename.
Returns:
plot: the plotting object of bokeh, optional, will be needed if multiple
@@ -60,25 +61,35 @@ def monsoon_data_plot(mon_info, file_path, tag=""):
dt: the datatable object of bokeh, optional, will be needed if multiple
datatables will be combined to one html file.
"""
+ if not isinstance(monsoon_results, list):
+ monsoon_results = [monsoon_results]
+ logging.info('Plotting the power measurement data.')
+
+ voltage = monsoon_results[0].voltage
+
+ total_current = 0
+ total_samples = 0
+ for result in monsoon_results:
+ total_current += result.average_current * result.num_samples
+ total_samples += result.num_samples
+ avg_current = total_current / total_samples
+
+ time_relative = [
+ data_point.time
+ for monsoon_result in monsoon_results
+ for data_point in monsoon_result.get_data_points()
+ ]
- log = logging.getLogger()
- log.info("Plot the power measurement data")
- #Get results as monsoon data object from the input file
- results = monsoon.MonsoonData.from_text_file(file_path)
- #Decouple current and timestamp data from the monsoon object
- current_data = []
- timestamps = []
- voltage = results[0].voltage
- [current_data.extend(x.data_points) for x in results]
- [timestamps.extend(x.timestamps) for x in results]
- period = 1 / float(mon_info.freq)
- time_relative = [x * period for x in range(len(current_data))]
- #Calculate the average current for the test
- current_data = [x * 1000 for x in current_data]
- avg_current = sum(current_data) / len(current_data)
- color = ['navy'] * len(current_data)
-
- #Preparing the data and source link for bokehn java callback
+ current_data = [
+ data_point.current * 1000
+ for monsoon_result in monsoon_results
+ for data_point in monsoon_result.get_data_points()
+ ]
+
+ total_data_points = sum(result.num_samples for result in monsoon_results)
+ color = ['navy'] * total_data_points
+
+ # Preparing the data and source link for bokehn java callback
source = ColumnDataSource(
data=dict(x0=time_relative, y0=current_data, color=color))
s2 = ColumnDataSource(
@@ -88,7 +99,7 @@ def monsoon_data_plot(mon_info, file_path, tag=""):
x0=[round(avg_current * voltage, 2)],
z1=[round(avg_current * voltage * mon_info.duration, 2)],
z2=[round(avg_current * mon_info.duration, 2)]))
- #Setting up data table for the output
+ # Setting up data table for the output
columns = [
TableColumn(field='z0', title='Total Duration (s)'),
TableColumn(field='y0', title='Average Current (mA)'),
@@ -99,26 +110,29 @@ def monsoon_data_plot(mon_info, file_path, tag=""):
dt = DataTable(
source=s2, columns=columns, width=1300, height=60, editable=True)
- plot_title = file_path[file_path.rfind('/') + 1:-4] + tag
- output_file("%s/%s.html" % (mon_info.data_path, plot_title))
- TOOLS = ('box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save')
+ plot_title = (os.path.basename(os.path.splitext(monsoon_results[0].tag)[0])
+ + tag)
+ output_file(os.path.join(mon_info.data_path, plot_title + '.html'))
+ tools = 'box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save'
# Create a new plot with the datatable above
plot = figure(
- plot_width=1300, plot_height=700, title=plot_title, tools=TOOLS)
- plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="width"))
- plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="height"))
+ plot_width=1300,
+ plot_height=700,
+ title=plot_title,
+ tools=tools,
+ output_backend='webgl')
+ plot.add_tools(bokeh_tools.WheelZoomTool(dimensions='width'))
+ plot.add_tools(bokeh_tools.WheelZoomTool(dimensions='height'))
plot.line('x0', 'y0', source=source, line_width=2)
plot.circle('x0', 'y0', source=source, size=0.5, fill_color='color')
plot.xaxis.axis_label = 'Time (s)'
plot.yaxis.axis_label = 'Current (mA)'
plot.title.text_font_size = {'value': '15pt'}
- #Callback Java scripting
+ # Callback JavaScript
source.selected.js_on_change(
"indices",
- CustomJS(
- args=dict(source=source, mytable=dt),
- code="""
+ CustomJS(args=dict(source=source, mytable=dt), code="""
var inds = cb_obj.indices;
var d1 = source.data;
var d2 = mytable.source.data;
@@ -154,10 +168,9 @@ def monsoon_data_plot(mon_info, file_path, tag=""):
mytable.change.emit();
"""))
- #Layout the plot and the datatable bar
- l = layout([[dt], [plot]])
- save(l)
- return [plot, dt]
+ # Layout the plot and the datatable bar
+ save(layout([[dt], [plot]]))
+ return plot, dt
def change_dtim(ad, gEnableModulatedDTIM, gMaxLIModulatedDTIM=10):
@@ -287,12 +300,12 @@ def bokeh_plot(data_sets,
Returns:
plot: bokeh plot figure object
"""
- TOOLS = ('box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save')
+ tools = 'box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save'
plot = figure(
plot_width=1300,
plot_height=700,
title=fig_property['title'],
- tools=TOOLS,
+ tools=tools,
output_backend="webgl")
plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="width"))
plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="height"))
@@ -324,7 +337,7 @@ def bokeh_plot(data_sets,
legend=str(legend),
fill_color=color)
- #Plot properties
+ # Plot properties
plot.xaxis.axis_label = fig_property['x_label']
plot.yaxis.axis_label = fig_property['y_label']
plot.legend.location = "top_right"
diff --git a/acts/framework/acts/test_utils/wifi/wifi_test_utils.py b/acts/framework/acts/test_utils/wifi/wifi_test_utils.py
index ccb6107f25..37426c682b 100755
--- a/acts/framework/acts/test_utils/wifi/wifi_test_utils.py
+++ b/acts/framework/acts/test_utils/wifi/wifi_test_utils.py
@@ -2012,7 +2012,6 @@ def create_softap_config():
}
return config
-
def start_softap_and_verify(ad, band):
"""Bring-up softap and verify AP mode and in scan results.
@@ -2032,6 +2031,53 @@ def start_softap_and_verify(ad, band):
config[WifiEnums.SSID_KEY])
return config
+def wait_for_expected_number_of_softap_clients(ad, callbackId,
+ expected_num_of_softap_clients):
+ """Wait for the number of softap clients to be updated as expected.
+ Args:
+ callbackId: Id of the callback associated with registering.
+ expected_num_of_softap_clients: expected number of softap clients.
+ """
+ eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
+ callbackId) + wifi_constants.SOFTAP_NUMBER_CLIENTS_CHANGED
+ asserts.assert_equal(ad.ed.pop_event(eventStr,
+ SHORT_TIMEOUT)['data'][wifi_constants.
+ SOFTAP_NUMBER_CLIENTS_CALLBACK_KEY],
+ expected_num_of_softap_clients,
+ "Number of softap clients doesn't match with expected number")
+
+def wait_for_expected_softap_state(ad, callbackId, expected_softap_state):
+ """Wait for the expected softap state change.
+ Args:
+ callbackId: Id of the callback associated with registering.
+ expected_softap_state: The expected softap state.
+ """
+ eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
+ callbackId) + wifi_constants.SOFTAP_STATE_CHANGED
+ asserts.assert_equal(ad.ed.pop_event(eventStr,
+ SHORT_TIMEOUT)['data'][wifi_constants.
+ SOFTAP_STATE_CHANGE_CALLBACK_KEY],
+ expected_softap_state,
+ "Softap state doesn't match with expected state")
+
+def get_current_number_of_softap_clients(ad, callbackId):
+ """pop up all of softap client updated event from queue.
+ Args:
+ callbackId: Id of the callback associated with registering.
+
+ Returns:
+ If exist aleast callback, returns last updated number_of_softap_clients.
+ Returns None when no any match callback event in queue.
+ """
+ eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
+ callbackId) + wifi_constants.SOFTAP_NUMBER_CLIENTS_CHANGED
+ events = ad.ed.pop_all(eventStr)
+ for event in events:
+ num_of_clients = event['data'][wifi_constants.
+ SOFTAP_NUMBER_CLIENTS_CALLBACK_KEY]
+ if len(events) == 0:
+ return None
+ return num_of_clients
def get_ssrdumps(ad, test_name=""):
"""Pulls dumps in the ssrdump dir
@@ -2048,7 +2094,6 @@ def get_ssrdumps(ad, test_name=""):
ad.pull_files(logs, log_path)
ad.adb.shell("find /data/vendor/ssrdump/ -type f -delete")
-
def start_pcap(pcap, wifi_band, test_name):
"""Start packet capture in monitor mode.
diff --git a/acts/framework/setup.py b/acts/framework/setup.py
index b5e4467bb6..32b8a93cad 100755
--- a/acts/framework/setup.py
+++ b/acts/framework/setup.py
@@ -39,6 +39,7 @@ install_requires = [
'xlsxwriter',
'mobly',
'grpcio',
+ 'Monsoon',
# paramiko-ng is needed vs paramiko as currently paramiko does not support
# ed25519 ssh keys, which is what Fuchsia uses.
'paramiko-ng',
diff --git a/acts/framework/tests/acts_import_unit_test.py b/acts/framework/tests/acts_import_unit_test.py
index db9c5e74d6..38dc3bf103 100755
--- a/acts/framework/tests/acts_import_unit_test.py
+++ b/acts/framework/tests/acts_import_unit_test.py
@@ -48,6 +48,12 @@ else:
PY_FILE_REGEX = re.compile('.+\.py$')
BLACKLIST = [
+ # TODO(markdr): Remove these after BT team evaluates these tests.
+ 'acts/test_utils/bt/PowerBaseTest.py',
+ 'tests/google/ble/power/GattPowerTest.py',
+ 'tests/google/bt/power/A2dpPowerTest.py',
+ 'tests/google/ble/power/BleScanPowerTest.py',
+
'acts/controllers/rohdeschwarz_lib/contest.py',
'acts/controllers/native.py',
'acts/controllers/native_android_device.py',
@@ -86,6 +92,7 @@ BLACKLIST = [
'tests/google/fuchsia/bt/FuchsiaCmdLineTest.py',
'tests/google/fuchsia/bt/gatt/GattServerSetupTest.py',
'tests/google/fuchsia/wlan/RebootStressTest.py',
+ 'acts/test_utils/gnss/gnss_testlog_utils.py',
]
BLACKLIST_DIRECTORIES = [
diff --git a/acts/framework/tests/controllers/monsoon_lib/api/monsoon_test.py b/acts/framework/tests/controllers/monsoon_lib/api/monsoon_test.py
index ec274b956e..9d628959d9 100755
--- a/acts/framework/tests/controllers/monsoon_lib/api/monsoon_test.py
+++ b/acts/framework/tests/controllers/monsoon_lib/api/monsoon_test.py
@@ -209,15 +209,6 @@ class BaseMonsoonTest(unittest.TestCase):
'usbPassthroughMode should not be called when the '
'state does not change.')
- def test_monsoon_usb_auto_sets_usb_state_to_auto(self):
- monsoon = MonsoonImpl()
-
- monsoon.monsoon_usb_auto()
-
- self.assertEqual(monsoon.status.usbPassthroughMode,
- PassthroughStates.AUTO,
- 'monsoon_usb_auto() did not disconnect USB.')
-
def take_samples_always_reestablishes_the_monsoon_connection(self):
monsoon = MonsoonImpl()
assembly_line = mock.Mock()
diff --git a/acts/tests/google/bt/pts/A2dpPtsTest.py b/acts/tests/google/bt/pts/A2dpPtsTest.py
index 25ed3ce21b..2c2ffeefa4 100644
--- a/acts/tests/google/bt/pts/A2dpPtsTest.py
+++ b/acts/tests/google/bt/pts/A2dpPtsTest.py
@@ -29,11 +29,10 @@ class A2dpPtsTest(PtsBaseClass):
pts_action_mapping = None
def setup_class(self):
- super(A2dpPtsTest, self).setup_class()
+ super().setup_class()
self.dut.initialize_bluetooth_controller()
# self.dut.set_bluetooth_local_name(self.dut_bluetooth_local_name)
local_dut_mac_address = self.dut.get_local_bluetooth_address()
- self.pts.set_profile_under_test("A2DP")
ics = None
ixit = None
@@ -56,7 +55,11 @@ class A2dpPtsTest(PtsBaseClass):
ics = f_ics_lib.A2DP_ICS
ixit = fuchsia_ixit
+ ### PTS SETUP: Required after ICS, IXIT, and profile is setup ###
+ self.pts.set_profile_under_test("A2DP")
self.pts.set_ics_and_ixit(ics, ixit)
+ self.pts.setup_pts()
+ ### End PTS Setup ###
self.dut.unbond_all_known_devices()
self.dut.start_pairing_helper()
diff --git a/acts/tests/google/bt/pts/GattPtsTest.py b/acts/tests/google/bt/pts/GattPtsTest.py
index 7883f3727c..4166f8e9b6 100644
--- a/acts/tests/google/bt/pts/GattPtsTest.py
+++ b/acts/tests/google/bt/pts/GattPtsTest.py
@@ -33,12 +33,11 @@ class GattPtsTest(PtsBaseClass):
pts_action_mapping = None
def setup_class(self):
- super(GattPtsTest, self).setup_class()
+ super().setup_class()
self.dut_bluetooth_local_name = "fs_test"
self.dut.initialize_bluetooth_controller()
self.dut.set_bluetooth_local_name(self.dut_bluetooth_local_name)
local_dut_mac_address = self.dut.get_local_bluetooth_address()
- self.pts.set_profile_under_test("GATT")
ics = None
ixit = None
@@ -72,7 +71,11 @@ class GattPtsTest(PtsBaseClass):
"Unable to run PTS tests on unsupported hardare {}.".format(
type(self.dut)))
+ ### PTS SETUP: Required after ICS, IXIT, and profile is setup ###
+ self.pts.set_profile_under_test("GATT")
self.pts.set_ics_and_ixit(ics, ixit)
+ self.pts.setup_pts()
+ ### End PTS Setup ###
self.dut.unbond_all_known_devices()
self.dut.start_pairing_helper()
diff --git a/acts/tests/google/bt/pts/SdpPtsTest.py b/acts/tests/google/bt/pts/SdpPtsTest.py
index ac35cd413c..1dd5d66a24 100644
--- a/acts/tests/google/bt/pts/SdpPtsTest.py
+++ b/acts/tests/google/bt/pts/SdpPtsTest.py
@@ -99,10 +99,10 @@ PROFILE_ID = int(sig_uuid_constants['AudioSource'], 16)
class SdpPtsTest(PtsBaseClass):
def setup_class(self):
+ super().setup_class()
self.dut.initialize_bluetooth_controller()
# self.dut.set_bluetooth_local_name(self.dut_bluetooth_local_name)
local_dut_mac_address = self.dut.get_local_bluetooth_address()
- self.pts.set_profile_under_test("SDP")
ics = None
ixit = None
@@ -125,8 +125,12 @@ class SdpPtsTest(PtsBaseClass):
ics = f_ics_lib.SDP_ICS
ixit = fuchsia_ixit
+ ### PTS SETUP: Required after ICS, IXIT, and profile is setup ###
+ self.pts.set_profile_under_test("SDP")
self.pts.set_ics_and_ixit(ics, ixit)
- super(SdpPtsTest, self).setup_class()
+ self.pts.setup_pts()
+ ### End PTS Setup ###
+
self.dut.unbond_all_known_devices()
self.dut.set_discoverable(True)
diff --git a/acts/tests/google/coex/hotspot_tests/HotspotWiFiChannelTest.py b/acts/tests/google/coex/hotspot_tests/HotspotWiFiChannelTest.py
index b44f784337..c7f0c32922 100644
--- a/acts/tests/google/coex/hotspot_tests/HotspotWiFiChannelTest.py
+++ b/acts/tests/google/coex/hotspot_tests/HotspotWiFiChannelTest.py
@@ -173,8 +173,11 @@ class HotspotWiFiChannelTest(acts.base_test.BaseTestClass):
toggle_airplane_mode(self.log, self.pri_ad, False)
self.log.info('Waiting for device to attach.')
- self.cmw.wait_for_connected_state()
+ self.cmw.wait_for_attached_state()
self.log.info('Device attached with callbox.')
+ self.log.debug('Waiting for connected state.')
+ self.cmw.wait_for_connected_state()
+ self.log.info('Device connected with callbox')
def initiate_wifi_tethering_and_connect(self, wifi_band=None):
"""Initiates wifi tethering and connects wifi.
diff --git a/acts/tests/google/net/DhcpServerTest.py b/acts/tests/google/net/DhcpServerTest.py
index c473b25375..6fb6ed4d03 100644
--- a/acts/tests/google/net/DhcpServerTest.py
+++ b/acts/tests/google/net/DhcpServerTest.py
@@ -1,5 +1,6 @@
from acts import asserts
from acts import base_test
+from acts import signals
from acts.controllers import android_device
from acts.test_decorators import test_tracker_info
@@ -19,6 +20,9 @@ NETADDR_PREFIX = '192.168.42.'
OTHER_NETADDR_PREFIX = '192.168.43.'
NETADDR_BROADCAST = '255.255.255.255'
SUBNET_BROADCAST = NETADDR_PREFIX + '255'
+USB_CHARGE_MODE = 'svc usb setFunctions'
+USB_TETHERING_MODE = 'svc usb setFunctions rndis'
+DEVICE_IP_ADDRESS = 'ip address'
OFFER = 2
@@ -27,14 +31,6 @@ ACK = 5
NAK = 6
-pmc_base_cmd = (
- "am broadcast -a com.android.pmc.action.AUTOPOWER --es PowerAction ")
-start_pmc_cmd = (
- "am start -S -n com.android.pmc/com.android.pmc.PMCMainActivity")
-pmc_start_usb_tethering_cmd = "%sStartUSBTethering" % pmc_base_cmd
-pmc_stop_usb_tethering_cmd = "%sStopUSBTethering" % pmc_base_cmd
-
-
class DhcpServerTest(base_test.BaseTestClass):
def setup_class(self):
self.dut = self.android_devices[0]
@@ -47,8 +43,6 @@ class DhcpServerTest(base_test.BaseTestClass):
# Allow using non-67 server ports as long as client uses 68
bind_layers(UDP, BOOTP, dport=CLIENT_PORT)
- self.dut.adb.shell(start_pmc_cmd)
- self.dut.adb.shell("setprop log.tag.PMC VERBOSE")
iflist_before = get_if_list()
self._start_usb_tethering(self.dut)
self.iface = self._wait_for_new_iface(iflist_before)
@@ -98,8 +92,11 @@ class DhcpServerTest(base_test.BaseTestClass):
"""
self.log.info("Starting USB Tethering")
dut.stop_services()
- dut.adb.shell(pmc_start_usb_tethering_cmd)
- self._wait_for_device(self.dut)
+ dut.adb.shell(USB_TETHERING_MODE, ignore_status=True)
+ dut.adb.wait_for_device()
+ dut.start_services()
+ if 'rndis' not in dut.adb.shell(DEVICE_IP_ADDRESS):
+ raise signals.TestFailure('Unable to enable USB tethering.')
self.USB_TETHERED = True
def _stop_usb_tethering(self, dut):
@@ -109,8 +106,9 @@ class DhcpServerTest(base_test.BaseTestClass):
1. dut - ad object
"""
self.log.info("Stopping USB Tethering")
- dut.adb.shell(pmc_stop_usb_tethering_cmd)
- self._wait_for_device(self.dut)
+ dut.stop_services()
+ dut.adb.shell(USB_CHARGE_MODE)
+ dut.adb.wait_for_device()
dut.start_services()
self.USB_TETHERED = False
diff --git a/acts/tests/google/net/DnsOverTlsTest.py b/acts/tests/google/net/DnsOverTlsTest.py
index e8a883d635..84646d7f9e 100644
--- a/acts/tests/google/net/DnsOverTlsTest.py
+++ b/acts/tests/google/net/DnsOverTlsTest.py
@@ -95,7 +95,7 @@ class DnsOverTlsTest(base_test.BaseTestClass):
"""
try:
packets = rdpcap(pcap_file)
- except Scapy_Excaption:
+ except Scapy_Exception:
asserts.fail("Not a valid pcap file")
for pkt in packets:
summary = "%s" % pkt.summary()
diff --git a/acts/tests/google/power/PowerBaselineTest.py b/acts/tests/google/power/PowerBaselineTest.py
index b75514423d..d8ef277b2e 100644
--- a/acts/tests/google/power/PowerBaselineTest.py
+++ b/acts/tests/google/power/PowerBaselineTest.py
@@ -31,7 +31,7 @@ class PowerBaselineTest(PowerBaseTest):
self.dut.droid.goToSleepNow()
# Measure power
- self.collect_power_data()
+ result = self.collect_power_data()
# Check if power measurement is below the required value
- self.pass_fail_check()
+ self.pass_fail_check(result.average_current)
diff --git a/acts/tests/google/power/bt/PowerBLEadvertiseTest.py b/acts/tests/google/power/bt/PowerBLEadvertiseTest.py
new file mode 100644
index 0000000000..c12d2293e1
--- /dev/null
+++ b/acts/tests/google/power/bt/PowerBLEadvertiseTest.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2018 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import time
+import acts.test_utils.bt.BleEnum as bleenum
+import acts.test_utils.bt.bt_power_test_utils as btputils
+import acts.test_utils.power.PowerBTBaseTest as PBtBT
+
+BLE_LOCATION_SCAN_ENABLE = 'settings put secure location_mode 3'
+EXTRA_ADV_TIME = 10
+
+
+class PowerBLEadvertiseTest(PBtBT.PowerBTBaseTest):
+ def __init__(self, configs):
+ super().__init__(configs)
+ req_params = ['adv_modes', 'adv_power_levels', 'adv_duration']
+ self.unpack_userparams(req_params)
+ # Loop all advertise modes and power levels
+ for adv_mode in self.adv_modes:
+ for adv_power_level in self.adv_power_levels:
+ self.generate_test_case(adv_mode, adv_power_level,
+ self.adv_duration)
+
+ def setup_class(self):
+
+ super().setup_class()
+ self.dut.adb.shell(BLE_LOCATION_SCAN_ENABLE)
+ # Make sure during power measurement, advertisement is always on
+ self.mon_info.duration = (
+ self.adv_duration - self.mon_offset - EXTRA_ADV_TIME)
+
+ def generate_test_case(self, adv_mode, adv_power_level, adv_duration):
+ def test_case_fn():
+
+ self.measure_ble_advertise_power(adv_mode, adv_power_level,
+ adv_duration)
+
+ adv_mode_str = bleenum.AdvertiseSettingsAdvertiseMode(adv_mode).name
+ adv_txpl_str = bleenum.AdvertiseSettingsAdvertiseTxPower(
+ adv_power_level).name.strip('ADVERTISE').strip('_')
+ test_case_name = ('test_BLE_{}_{}'.format(adv_mode_str, adv_txpl_str))
+ setattr(self, test_case_name, test_case_fn)
+
+ def measure_ble_advertise_power(self, adv_mode, adv_power_level,
+ adv_duration):
+
+ btputils.start_apk_ble_adv(self.dut, adv_mode, adv_power_level,
+ adv_duration)
+ time.sleep(EXTRA_ADV_TIME)
+ self.measure_power_and_validate()
diff --git a/acts/tests/google/power/bt/PowerBLEscanTest.py b/acts/tests/google/power/bt/PowerBLEscanTest.py
new file mode 100644
index 0000000000..8ed77b5a94
--- /dev/null
+++ b/acts/tests/google/power/bt/PowerBLEscanTest.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2018 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import time
+import acts.test_utils.bt.BleEnum as bleenum
+import acts.test_utils.bt.bt_power_test_utils as btputils
+import acts.test_utils.power.PowerBTBaseTest as PBtBT
+
+BLE_LOCATION_SCAN_ENABLE = 'settings put secure location_mode 3'
+EXTRA_SCAN_TIME = 10
+
+
+class PowerBLEscanTest(PBtBT.PowerBTBaseTest):
+ def __init__(self, configs):
+ super().__init__(configs)
+ req_params = ['scan_modes', 'scan_duration']
+ self.unpack_userparams(req_params)
+
+ for scan_mode in self.scan_modes:
+ self.generate_test_case_no_devices_around(scan_mode,
+ self.scan_duration)
+
+ def setup_class(self):
+
+ super().setup_class()
+ self.dut.adb.shell(BLE_LOCATION_SCAN_ENABLE)
+ # Make sure during power measurement, scan is always on
+ self.mon_info.duration = (
+ self.scan_duration - self.mon_offset - EXTRA_SCAN_TIME)
+
+ def generate_test_case_no_devices_around(self, scan_mode, scan_duration):
+ def test_case_fn():
+
+ self.measure_ble_scan_power(scan_mode, scan_duration)
+
+ test_case_name = ('test_BLE_{}_no_advertisers'.format(
+ bleenum.ScanSettingsScanMode(scan_mode).name))
+ setattr(self, test_case_name, test_case_fn)
+
+ def measure_ble_scan_power(self, scan_mode, scan_duration):
+
+ btputils.start_apk_ble_scan(self.dut, scan_mode, scan_duration)
+ time.sleep(EXTRA_SCAN_TIME)
+ self.measure_power_and_validate()
diff --git a/acts/tests/google/power/bt/PowerBTa2dpTest.py b/acts/tests/google/power/bt/PowerBTa2dpTest.py
new file mode 100644
index 0000000000..8122fc0748
--- /dev/null
+++ b/acts/tests/google/power/bt/PowerBTa2dpTest.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2018 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import time
+import acts.test_utils.bt.bt_test_utils as btutils
+import acts.test_utils.power.PowerBTBaseTest as PBtBT
+from acts import asserts
+from acts.test_utils.bt import BtEnum
+
+EXTRA_PLAY_TIME = 10
+
+
+class PowerBTa2dpTest(PBtBT.PowerBTBaseTest):
+ def __init__(self, configs):
+ super().__init__(configs)
+ req_params = ['codecs', 'tx_power_levels', 'atten_pl_settings']
+ self.unpack_userparams(req_params)
+ # Loop all codecs and tx power levels
+ for codec_config in self.codecs:
+ for tpl in self.tx_power_levels:
+ self.generate_test_case(codec_config, tpl)
+
+ def setup_test(self):
+ super().setup_test()
+ btutils.connect_phone_to_headset(self.dut, self.bt_device, 60)
+ vol = self.dut.droid.getMaxMediaVolume() * self.volume
+ self.dut.droid.setMediaVolume(0)
+ time.sleep(1)
+ self.dut.droid.setMediaVolume(int(vol))
+
+
+ def generate_test_case(self, codec_config, tpl):
+ def test_case_fn():
+ self.measure_a2dp_power(codec_config, tpl)
+
+ test_case_name = ('test_BTa2dp_{}_codec_at_PL{}'.format(
+ codec_config['codec_type'], tpl))
+ setattr(self, test_case_name, test_case_fn)
+
+ def measure_a2dp_power(self, codec_config, tpl):
+
+ current_codec = self.dut.droid.bluetoothA2dpGetCurrentCodecConfig()
+ current_codec_type = BtEnum.BluetoothA2dpCodecType(
+ current_codec['codecType']).name
+ if current_codec_type != codec_config['codec_type']:
+ codec_set = btutils.set_bluetooth_codec(self.dut, **codec_config)
+ asserts.assert_true(codec_set, 'Codec configuration failed.')
+ else:
+ self.log.info('Current Codec is {}, no need to change'.format(
+ current_codec_type))
+
+ # Set attenuation so BT tx at desired power level
+ tpl = 'PL' + str(tpl)
+ self.set_attenuation(self.atten_pl_settings[tpl])
+ self.log.info('Setting Attenuator to {} dB'.format(self.atten_pl_settings[tpl]))
+
+ self.media.play()
+ self.log.info('Running A2DP with codec {} at {}'.format(
+ codec_config['codec_type'], tpl))
+ self.dut.droid.goToSleepNow()
+ time.sleep(EXTRA_PLAY_TIME)
+ self.measure_power_and_validate() \ No newline at end of file
diff --git a/acts/tests/google/power/bt/PowerBTbaselineTest.py b/acts/tests/google/power/bt/PowerBTbaselineTest.py
deleted file mode 100644
index f4050c8de5..0000000000
--- a/acts/tests/google/power/bt/PowerBTbaselineTest.py
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/usr/bin/env python3.4
-#
-# Copyright 2018 - The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from acts.test_decorators import test_tracker_info
-import acts.test_utils.power.PowerBTBaseTest as PBtBT
-
-
-class PowerBTbaselineTest(PBtBT.PowerBTBaseTest):
- def bt_baseline_test_func(self):
- """Base function for BT baseline measurement.
-
- Steps:
- 1. Sets the phone in airplane mode, disables gestures and location
- 2. Turns ON/OFF BT, BLE and screen according to test conditions
- 3. Measures the power consumption
- 4. Asserts pass/fail criteria based on measured power
- """
-
- # Decode the test params from test name
- attrs = ['screen_status', 'bt_status', 'ble_status', 'scan_status']
- indices = [2, 4, 6, 7]
- self.decode_test_configs(attrs, indices)
- # Setup the phoen at desired state
- self.phone_setup_for_BT(self.test_configs.bt_status,
- self.test_configs.ble_status,
- self.test_configs.screen_status)
- if self.test_configs.scan_status == 'connectable':
- self.dut.droid.bluetoothMakeConnectable()
- elif self.test_configs.scan_status == 'discoverable':
- self.dut.droid.bluetoothMakeDiscoverable(
- self.mon_info.duration + self.mon_info.offset)
- self.measure_power_and_validate()
-
- # Test cases- Baseline
- @test_tracker_info(uuid='3f8ac0cb-f20d-4569-a58e-6009c89ea049')
- def test_screen_OFF_bt_ON_ble_ON_connectable(self):
- self.bt_baseline_test_func()
-
- @test_tracker_info(uuid='d54a992e-37ed-460a-ada7-2c51941557fd')
- def test_screen_OFF_bt_ON_ble_ON_discoverable(self):
- self.bt_baseline_test_func()
-
- @test_tracker_info(uuid='8f4c36b5-b18e-4aa5-9fe5-aafb729c1034')
- def test_screen_ON_bt_ON_ble_ON_connectable(self):
- self.bt_baseline_test_func()
-
- @test_tracker_info(uuid='7128356f-67d8-46b3-9d6b-1a4c9a7a1745')
- def test_screen_ON_bt_ON_ble_ON_discoverable(self):
- self.bt_baseline_test_func()
diff --git a/acts/tests/google/power/bt/PowerBTcalibrationTest.py b/acts/tests/google/power/bt/PowerBTcalibrationTest.py
new file mode 100644
index 0000000000..dffcc67231
--- /dev/null
+++ b/acts/tests/google/power/bt/PowerBTcalibrationTest.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2018 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import csv
+import os
+import time
+import acts.test_utils.bt.bt_test_utils as btutils
+import acts.test_utils.power.PowerBTBaseTest as PBtBT
+from acts import utils
+
+EXTRA_PLAY_TIME = 10
+
+
+class PowerBTcalibrationTest(PBtBT.PowerBTBaseTest):
+ def setup_test(self):
+
+ super().setup_test()
+ self.attenuator = self.attenuators[0]
+ btutils.enable_bqr(self.dut)
+ btutils.enable_bluetooth(self.dut.droid, self.dut.ed)
+ btutils.connect_phone_to_headset(self.dut, self.bt_device, 60)
+ vol = self.dut.droid.getMaxMediaVolume() * self.volume
+ self.dut.droid.setMediaVolume(int(vol))
+
+ self.cal_data_path = os.path.join(self.log_path, 'Calibration')
+ self.log_file = os.path.join(self.cal_data_path, 'Cal_data.csv')
+ utils.create_dir(os.path.dirname(self.log_file))
+
+
+ def test_calibrate(self):
+ """Run calibration to get attenuation value at each power level
+
+ """
+
+ self.cal_matrix = []
+ self.media.play()
+ time.sleep(EXTRA_PLAY_TIME)
+
+ # Loop through attenuation in 1 dB step until reaching at PL10
+ for i in range(int(self.attenuator.get_max_atten())):
+
+ self.attenuator.set_atten(i)
+ bt_metrics_dict = btutils.get_bt_metric(self.dut)
+ pwl = int(bt_metrics_dict['pwlv'][self.dut.serial])
+ self.cal_matrix.append([i, pwl])
+ if pwl == 10:
+ break
+
+ # Write cal results to csv
+ with open(self.log_file, 'w', newline='') as f:
+ writer = csv.writer(f)
+ writer.writerows(self.cal_matrix)
diff --git a/acts/tests/google/power/bt/PowerBTidleTest.py b/acts/tests/google/power/bt/PowerBTidleTest.py
new file mode 100644
index 0000000000..bab79d0836
--- /dev/null
+++ b/acts/tests/google/power/bt/PowerBTidleTest.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2018 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import time
+import acts.test_utils.power.PowerBTBaseTest as PBtBT
+import acts.test_utils.bt.bt_test_utils as btutils
+
+SCREEN_OFF_WAIT_TIME = 2
+
+
+class PowerBTidleTest(PBtBT.PowerBTBaseTest):
+ def setup_class(self):
+
+ super().setup_class()
+ btutils.enable_bluetooth(self.dut.droid, self.dut.ed)
+
+ # Test cases- Baseline
+ def test_bt_on_unconnected_connectable(self):
+ """BT turned on connectable mode.
+
+ Page scan only.
+ """
+ self.dut.droid.bluetoothMakeConnectable()
+ self.dut.droid.goToSleepNow()
+ time.sleep(SCREEN_OFF_WAIT_TIME)
+ self.measure_power_and_validate()
+
+ def test_bt_on_unconnected_discoverable(self):
+ """BT turned on discoverable mode.
+
+ Page and inquiry scan.
+ """
+ self.dut.droid.bluetoothMakeConnectable()
+ self.dut.droid.bluetoothMakeDiscoverable()
+ self.dut.droid.goToSleepNow()
+ time.sleep(SCREEN_OFF_WAIT_TIME)
+ self.measure_power_and_validate()
+
+ def test_bt_connected_idle(self):
+ """BT idle after connecting to headset.
+
+ """
+ btutils.connect_phone_to_headset(self.dut, self.bt_device, 60)
+ self.dut.droid.goToSleepNow()
+ time.sleep(SCREEN_OFF_WAIT_TIME)
+ self.measure_power_and_validate()
diff --git a/acts/tests/google/power/bt/PowerBTscanTest.py b/acts/tests/google/power/bt/PowerBTscanTest.py
deleted file mode 100644
index 0ef622c648..0000000000
--- a/acts/tests/google/power/bt/PowerBTscanTest.py
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/usr/bin/env python3.4
-#
-# Copyright 2018 - The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import acts.test_utils.power.PowerBTBaseTest as PBtBT
-from acts.test_decorators import test_tracker_info
-
-
-class PowerBTscanTest(PBtBT.PowerBTBaseTest):
- def ble_scan_base_func(self):
- """Base function to start a generic BLE scan and measures the power
-
- Steps:
- 1. Sets the phone in airplane mode, disables gestures and location
- 2. Turns ON/OFF BT, BLE and screen according to test conditions
- 3. Sends the adb shell command to PMC to start scan
- 4. Measures the power consumption
- 5. Asserts pass/fail criteria based on measured power
- """
- # Decode the test params from test name
- attrs = ['screen_status', 'bt_status', 'ble_status', 'scan_mode']
- indices = [2, 4, 6, -1]
- self.decode_test_configs(attrs, indices)
- if self.test_configs.scan_mode == 'lowpower':
- scan_mode = 'low_power'
- elif self.test_configs.scan_mode == 'lowlatency':
- scan_mode = 'low_latency'
- else:
- scan_mode = self.test_configs.scan_mode
- self.phone_setup_for_BT(self.test_configs.bt_status,
- self.test_configs.ble_status,
- self.test_configs.screen_status)
- self.start_pmc_ble_scan(scan_mode, self.mon_info.offset,
- self.mon_info.duration)
- self.measure_power_and_validate()
-
- # Test Cases: BLE Scans + Filtered scans
- @test_tracker_info(uuid='e9a36161-1d0c-4b9a-8bd8-80fef8cdfe28')
- def test_screen_ON_bt_ON_ble_ON_default_scan_balanced(self):
- self.ble_scan_base_func()
-
- @test_tracker_info(uuid='5fa61bf4-5f04-40bf-af52-6644b534d02e')
- def test_screen_OFF_bt_ON_ble_ON_filter_scan_opportunistic(self):
- self.ble_scan_base_func()
-
- @test_tracker_info(uuid='512b6cde-be83-43b0-b799-761380ba69ff')
- def test_screen_OFF_bt_ON_ble_ON_filter_scan_lowpower(self):
- self.ble_scan_base_func()
-
- @test_tracker_info(uuid='3a526838-ae7b-4cdb-bc29-89a5503d2306')
- def test_screen_OFF_bt_ON_ble_ON_filter_scan_balanced(self):
- self.ble_scan_base_func()
-
- @test_tracker_info(uuid='03a57cfd-4269-4a09-8544-84f878d2e801')
- def test_screen_OFF_bt_ON_ble_ON_filter_scan_lowlatency(self):
- self.ble_scan_base_func()
-
- # Test Cases: Background scans
- @test_tracker_info(uuid='20145317-e362-4bfd-9860-4ceddf764784')
- def test_screen_ON_bt_OFF_ble_ON_background_scan_lowlatency(self):
- self.ble_scan_base_func()
-
- @test_tracker_info(uuid='00a53dc3-2c33-43c4-b356-dba93249b823')
- def test_screen_ON_bt_OFF_ble_ON_background_scan_lowpower(self):
- self.ble_scan_base_func()
-
- @test_tracker_info(uuid='b7185d64-631f-4b18-8d0b-4e14b80db375')
- def test_screen_OFF_bt_OFF_ble_ON_background_scan_lowlatency(self):
- self.ble_scan_base_func()
-
- @test_tracker_info(uuid='93eb05da-a577-409c-8208-6af1899a10c2')
- def test_screen_OFF_bt_OFF_ble_ON_background_scan_lowpower(self):
- self.ble_scan_base_func()
diff --git a/acts/tests/google/power/gnss/PowerGnssDpoSimTest.py b/acts/tests/google/power/gnss/PowerGnssDpoSimTest.py
index 09b47a62aa..9c2ce9a2ef 100644
--- a/acts/tests/google/power/gnss/PowerGnssDpoSimTest.py
+++ b/acts/tests/google/power/gnss/PowerGnssDpoSimTest.py
@@ -16,10 +16,12 @@
import acts.test_utils.power.PowerGnssBaseTest as GBT
from acts.test_utils.gnss import dut_log_test_utils as diaglog
+from acts.test_utils.gnss import gnss_test_utils as gutil
import time
import os
from acts import utils
-MDLOG_RUNNING_TIME = 120
+MDLOG_RUNNING_TIME = 300
+DUT_ACTION_WAIT_TIME = 2
class PowerGnssDpoSimTest(GBT.PowerGnssBaseTest):
"""Power baseline tests for rockbottom state.
@@ -33,27 +35,35 @@ class PowerGnssDpoSimTest(GBT.PowerGnssBaseTest):
Decode the test config from the test name, set device to desired state.
Measure power and plot results.
"""
- self.collect_power_data()
- self.pass_fail_check()
+ result = self.collect_power_data()
+ self.pass_fail_check(result.average_current)
# Test cases
def test_gnss_dpoOFF_measurement(self):
utils.set_location_service(self.dut, True)
+ time.sleep(DUT_ACTION_WAIT_TIME)
+ gutil.start_gnss_by_gtw_gpstool(self.dut, state=True, type="gnss", bgdisplay=True)
self.dut.send_keycode("SLEEP")
+ time.sleep(DUT_ACTION_WAIT_TIME)
self.measure_gnsspower_test_func()
diaglog.start_diagmdlog_background(self.dut, maskfile=self.maskfile)
self.disconnect_usb(self.dut, MDLOG_RUNNING_TIME)
qxdm_log_path = os.path.join(self.log_path, 'QXDM')
diaglog.stop_background_diagmdlog(self.dut, qxdm_log_path)
+ gutil.start_gnss_by_gtw_gpstool(self.dut, state=False)
def test_gnss_dpoON_measurement(self):
utils.set_location_service(self.dut, True)
+ time.sleep(DUT_ACTION_WAIT_TIME)
+ gutil.start_gnss_by_gtw_gpstool(self.dut, state=True, type="gnss", bgdisplay=True)
self.dut.send_keycode("SLEEP")
+ time.sleep(DUT_ACTION_WAIT_TIME)
self.measure_gnsspower_test_func()
diaglog.start_diagmdlog_background(self.dut, maskfile=self.maskfile)
self.disconnect_usb(self.dut, MDLOG_RUNNING_TIME)
qxdm_log_path = os.path.join(self.log_path, 'QXDM')
diaglog.stop_background_diagmdlog(self.dut, qxdm_log_path)
+ gutil.start_gnss_by_gtw_gpstool(self.dut, state=False)
def test_gnss_rockbottom(self):
self.dut.send_keycode("SLEEP")
diff --git a/acts/tests/google/power/tel/lab/PowerTelHotspotTest.py b/acts/tests/google/power/tel/lab/PowerTelHotspotTest.py
index 66aa45815c..e5aabb76f9 100644
--- a/acts/tests/google/power/tel/lab/PowerTelHotspotTest.py
+++ b/acts/tests/google/power/tel/lab/PowerTelHotspotTest.py
@@ -119,7 +119,7 @@ class PowerTelHotspotTest(PowerTelTrafficTest):
iperf_helpers = self.start_tel_traffic(self.android_devices[1])
# Measure power
- self.collect_power_data()
+ result = self.collect_power_data()
# Wait for iPerf to finish
time.sleep(self.IPERF_MARGIN + 2)
@@ -129,7 +129,7 @@ class PowerTelHotspotTest(PowerTelTrafficTest):
iperf_helpers)
# Checks if power is below the required threshold.
- self.pass_fail_check()
+ self.pass_fail_check(result.average_current)
def setup_test(self):
""" Executed before every test case.
diff --git a/acts/tests/google/power/tel/lab/PowerTelIdleTest.py b/acts/tests/google/power/tel/lab/PowerTelIdleTest.py
index 934fe20dc0..f1197988c6 100644
--- a/acts/tests/google/power/tel/lab/PowerTelIdleTest.py
+++ b/acts/tests/google/power/tel/lab/PowerTelIdleTest.py
@@ -33,7 +33,7 @@ class PowerTelIdleTest(PWCEL.PowerCellularLabBaseTest):
self.cellular_simulator.wait_until_idle_state(idle_wait_time)
# Measure power
- self.collect_power_data()
+ result = self.collect_power_data()
# Check if power measurement is below the required value
- self.pass_fail_check()
+ self.pass_fail_check(result.average_current)
diff --git a/acts/tests/google/power/tel/lab/PowerTelTrafficTest.py b/acts/tests/google/power/tel/lab/PowerTelTrafficTest.py
index 97094e3017..b373fdb3d0 100644
--- a/acts/tests/google/power/tel/lab/PowerTelTrafficTest.py
+++ b/acts/tests/google/power/tel/lab/PowerTelTrafficTest.py
@@ -55,11 +55,6 @@ class PowerTelTrafficTest(PWCEL.PowerCellularLabBaseTest):
super().__init__(controllers)
- # Verify that at least one PacketSender controller has been initialized
- if not hasattr(self, 'packet_senders'):
- raise RuntimeError('At least one packet sender controller needs '
- 'to be defined in the test config files.')
-
# These variables are passed to iPerf when starting data
# traffic with the -b parameter to limit throughput on
# the application layer.
@@ -76,6 +71,14 @@ class PowerTelTrafficTest(PWCEL.PowerCellularLabBaseTest):
self.ul_tput_logger = BlackboxMetricLogger.for_test_case(
metric_name='avg_ul_tput')
+ def setup_class(self):
+ super().setup_class()
+
+ # Verify that at least one PacketSender controller has been initialized
+ if not hasattr(self, 'packet_senders'):
+ raise RuntimeError('At least one packet sender controller needs '
+ 'to be defined in the test config files.')
+
def setup_test(self):
""" Executed before every test case.
@@ -155,7 +158,7 @@ class PowerTelTrafficTest(PWCEL.PowerCellularLabBaseTest):
iperf_helpers = self.start_tel_traffic(self.dut)
# Measure power
- self.collect_power_data()
+ result = self.collect_power_data()
# Wait for iPerf to finish
time.sleep(self.IPERF_MARGIN + 2)
@@ -164,9 +167,9 @@ class PowerTelTrafficTest(PWCEL.PowerCellularLabBaseTest):
self.iperf_results = self.get_iperf_results(self.dut, iperf_helpers)
# Check if power measurement is below the required value
- self.pass_fail_check()
+ self.pass_fail_check(result.average_current)
- return self.test_result, self.iperf_results
+ return result.average_current, self.iperf_results
def get_iperf_results(self, device, iperf_helpers):
""" Pulls iperf results from the device.
@@ -193,7 +196,7 @@ class PowerTelTrafficTest(PWCEL.PowerCellularLabBaseTest):
return throughput
- def pass_fail_check(self):
+ def pass_fail_check(self, average_current=None):
""" Checks power consumption and throughput.
Uses the base class method to check power consumption. Also, compares
@@ -227,7 +230,7 @@ class PowerTelTrafficTest(PWCEL.PowerCellularLabBaseTest):
direction, round(throughput, 3), round(expected_t, 3),
round(throughput / expected_t, 3)))
- super().pass_fail_check()
+ super().pass_fail_check(average_current)
def start_tel_traffic(self, client_host):
""" Starts iPerf in the indicated device and initiates traffic.
@@ -241,7 +244,6 @@ class PowerTelTrafficTest(PWCEL.PowerCellularLabBaseTest):
Returns:
A list of iperf helpers.
"""
-
# The iPerf server is hosted in this computer
self.iperf_server_address = scapy.get_if_addr(
self.packet_senders[0].interface)
diff --git a/acts/tests/google/power/tel/lab/PowerTelVoiceCallTest.py b/acts/tests/google/power/tel/lab/PowerTelVoiceCallTest.py
index f0d0eda6ce..93b69c6e2b 100644
--- a/acts/tests/google/power/tel/lab/PowerTelVoiceCallTest.py
+++ b/acts/tests/google/power/tel/lab/PowerTelVoiceCallTest.py
@@ -70,10 +70,10 @@ class PowerTelVoiceCallTest(PWCEL.PowerCellularLabBaseTest):
self.dut.droid.goToSleepNow()
# Measure power
- self.collect_power_data()
+ result = self.collect_power_data()
# End the call
hangup_call(self.log, self.dut)
# Check if power measurement is within the required values
- self.pass_fail_check()
+ self.pass_fail_check(result.average_current)
diff --git a/acts/tests/google/power/wifi/PowerWiFiHotspotTest.py b/acts/tests/google/power/wifi/PowerWiFiHotspotTest.py
index f554d12bde..57e926376a 100644
--- a/acts/tests/google/power/wifi/PowerWiFiHotspotTest.py
+++ b/acts/tests/google/power/wifi/PowerWiFiHotspotTest.py
@@ -158,7 +158,7 @@ class PowerWiFiHotspotTest(PWBT.PowerWiFiBaseTest):
time.sleep(2)
# Measure power
- self.collect_power_data()
+ result = self.collect_power_data()
if traffic:
# Wait for iperf to finish
@@ -168,7 +168,7 @@ class PowerWiFiHotspotTest(PWBT.PowerWiFiBaseTest):
self.client_iperf_helper.process_iperf_results(
self.dut, self.log, self.iperf_servers, self.test_name)
- self.pass_fail_check()
+ self.pass_fail_check(result.average_current)
def power_idle_tethering_test(self):
""" Start power test when Hotspot is idle
diff --git a/acts/tests/google/power/wifi/PowerWiFimulticastTest.py b/acts/tests/google/power/wifi/PowerWiFimulticastTest.py
index 5a6108cdfb..8832469980 100644
--- a/acts/tests/google/power/wifi/PowerWiFimulticastTest.py
+++ b/acts/tests/google/power/wifi/PowerWiFimulticastTest.py
@@ -27,6 +27,22 @@ DNS_SHORT_LIFETIME = 3
class PowerWiFimulticastTest(PWBT.PowerWiFiBaseTest):
+ def setup_class(self):
+ super().setup_class()
+ self.unpack_userparams(sub_mask="255.255.255.0",
+ mac_dst="get_from_dut",
+ mac_src="get_local",
+ ipv4_dst="get_from_dut",
+ ipv4_src="get_local",
+ ipv6_dst="get_from_dut",
+ ipv6_src="get_local",
+ ipv6_src_type="LINK_LOCAL",
+ ipv4_gwt="192.168.1.1",
+ mac_dst_fake="40:90:28:EF:4B:20",
+ ipv4_dst_fake="192.168.1.60",
+ ipv6_dst_fake="fe80::300f:40ee:ee0a:5000",
+ interval=1)
+
def set_connection(self):
"""Setup connection between AP and client.
@@ -38,8 +54,9 @@ class PowerWiFimulticastTest(PWBT.PowerWiFiBaseTest):
indices = [2, 4]
self.decode_test_configs(attrs, indices)
# Change DTIMx1 on the phone to receive all Multicast packets
- rebooted = wputils.change_dtim(
- self.dut, gEnableModulatedDTIM=1, gMaxLIModulatedDTIM=10)
+ rebooted = wputils.change_dtim(self.dut,
+ gEnableModulatedDTIM=1,
+ gMaxLIModulatedDTIM=10)
self.dut.log.info('DTIM value of the phone is now DTIMx1')
if rebooted:
self.dut_rockbottom()
@@ -87,8 +104,8 @@ class PowerWiFimulticastTest(PWBT.PowerWiFiBaseTest):
self.set_connection()
self.pkt_gen_config = wputils.create_pkt_config(self)
pkt_gen = pkt_utils.ArpGenerator(**self.pkt_gen_config)
- packet = pkt_gen.generate(
- ip_dst='0.0.0.0', eth_dst=self.pkt_gen_config['dst_mac'])
+ packet = pkt_gen.generate(ip_dst='0.0.0.0',
+ eth_dst=self.pkt_gen_config['dst_mac'])
self.sendPacketAndMeasure(packet)
@test_tracker_info(uuid='5dcb16f1-725c-45de-8103-340104d60a22')
@@ -96,8 +113,8 @@ class PowerWiFimulticastTest(PWBT.PowerWiFiBaseTest):
self.set_connection()
self.pkt_gen_config = wputils.create_pkt_config(self)
pkt_gen = pkt_utils.ArpGenerator(**self.pkt_gen_config)
- packet = pkt_gen.generate(
- ip_dst=self.ipv4_dst_fake, eth_dst=self.pkt_gen_config['dst_mac'])
+ packet = pkt_gen.generate(ip_dst=self.ipv4_dst_fake,
+ eth_dst=self.pkt_gen_config['dst_mac'])
self.sendPacketAndMeasure(packet)
@test_tracker_info(uuid='5ec4800f-a82e-4462-8b65-4fcd0b1940a2')
@@ -105,12 +122,11 @@ class PowerWiFimulticastTest(PWBT.PowerWiFiBaseTest):
self.set_connection()
self.pkt_gen_config = wputils.create_pkt_config(self)
pkt_gen = pkt_utils.ArpGenerator(**self.pkt_gen_config)
- packet = pkt_gen.generate(
- op='is-at',
- ip_src='0.0.0.0',
- ip_dst=self.ipv4_dst_fake,
- hwdst=self.mac_dst_fake,
- eth_dst=self.pkt_gen_config['dst_mac'])
+ packet = pkt_gen.generate(op='is-at',
+ ip_src='0.0.0.0',
+ ip_dst=self.ipv4_dst_fake,
+ hwdst=self.mac_dst_fake,
+ eth_dst=self.pkt_gen_config['dst_mac'])
self.sendPacketAndMeasure(packet)
@test_tracker_info(uuid='6c5c0e9e-7a00-43d0-a6e8-355141467703')
@@ -118,11 +134,10 @@ class PowerWiFimulticastTest(PWBT.PowerWiFiBaseTest):
self.set_connection()
self.pkt_gen_config = wputils.create_pkt_config(self)
pkt_gen = pkt_utils.ArpGenerator(**self.pkt_gen_config)
- packet = pkt_gen.generate(
- op='is-at',
- ip_dst=self.ipv4_dst_fake,
- hwdst=self.mac_dst_fake,
- eth_dst=self.pkt_gen_config['dst_mac'])
+ packet = pkt_gen.generate(op='is-at',
+ ip_dst=self.ipv4_dst_fake,
+ hwdst=self.mac_dst_fake,
+ eth_dst=self.pkt_gen_config['dst_mac'])
self.sendPacketAndMeasure(packet)
@test_tracker_info(uuid='8e534d3b-5a25-429a-a1bb-8119d7d28b5a')
@@ -178,8 +193,9 @@ class PowerWiFimulticastTest(PWBT.PowerWiFiBaseTest):
self.set_connection()
self.pkt_gen_config = wputils.create_pkt_config(self)
pkt_gen = pkt_utils.RaGenerator(**self.pkt_gen_config)
- packet = pkt_gen.generate(
- RA_LONG_LIFETIME, enableDNS=True, dns_lifetime=DNS_SHORT_LIFETIME)
+ packet = pkt_gen.generate(RA_LONG_LIFETIME,
+ enableDNS=True,
+ dns_lifetime=DNS_SHORT_LIFETIME)
self.sendPacketAndMeasure(packet)
@test_tracker_info(uuid='84d2f1ff-bd4f-46c6-9b06-826d9b14909c')
@@ -187,8 +203,9 @@ class PowerWiFimulticastTest(PWBT.PowerWiFiBaseTest):
self.set_connection()
self.pkt_gen_config = wputils.create_pkt_config(self)
pkt_gen = pkt_utils.RaGenerator(**self.pkt_gen_config)
- packet = pkt_gen.generate(
- RA_LONG_LIFETIME, enableDNS=True, dns_lifetime=DNS_LONG_LIFETIME)
+ packet = pkt_gen.generate(RA_LONG_LIFETIME,
+ enableDNS=True,
+ dns_lifetime=DNS_LONG_LIFETIME)
self.sendPacketAndMeasure(packet)
@test_tracker_info(uuid='4a17e74f-3e7f-4e90-ac9e-884a7c13cede')
@@ -333,8 +350,9 @@ class PowerWiFimulticastTest(PWBT.PowerWiFiBaseTest):
self.set_connection()
self.pkt_gen_config = wputils.create_pkt_config(self)
pkt_gen = pkt_utils.RaGenerator(**self.pkt_gen_config)
- packet = pkt_gen.generate(
- RA_LONG_LIFETIME, enableDNS=True, dns_lifetime=DNS_SHORT_LIFETIME)
+ packet = pkt_gen.generate(RA_LONG_LIFETIME,
+ enableDNS=True,
+ dns_lifetime=DNS_SHORT_LIFETIME)
self.sendPacketAndMeasure(packet)
@test_tracker_info(uuid='62b99cd7-75bf-45be-b93f-bb037a13b3e2')
@@ -342,8 +360,9 @@ class PowerWiFimulticastTest(PWBT.PowerWiFiBaseTest):
self.set_connection()
self.pkt_gen_config = wputils.create_pkt_config(self)
pkt_gen = pkt_utils.RaGenerator(**self.pkt_gen_config)
- packet = pkt_gen.generate(
- RA_LONG_LIFETIME, enableDNS=True, dns_lifetime=DNS_LONG_LIFETIME)
+ packet = pkt_gen.generate(RA_LONG_LIFETIME,
+ enableDNS=True,
+ dns_lifetime=DNS_LONG_LIFETIME)
self.sendPacketAndMeasure(packet)
@test_tracker_info(uuid='4088af4c-a64b-4fc1-848c-688936cc6c12')
diff --git a/acts/tests/google/power/wifi/PowerWiFiroamingTest.py b/acts/tests/google/power/wifi/PowerWiFiroamingTest.py
index 98409895f4..66110a1f34 100644
--- a/acts/tests/google/power/wifi/PowerWiFiroamingTest.py
+++ b/acts/tests/google/power/wifi/PowerWiFiroamingTest.py
@@ -81,22 +81,30 @@ class PowerWiFiroamingTest(PWBT.PowerWiFiBaseTest):
time.sleep(5)
# Toggle between two networks
begin_time = utils.get_current_epoch_time()
+ results = []
for i in range(self.toggle_times):
self.dut.log.info('Connecting to %s' % network_main[wc.SSID])
self.dut.droid.wifiConnect(network_main)
- file_path, avg_current = self.monsoon_data_collect_save()
+ results.append(self.monsoon_data_collect_save())
self.dut.log.info('Connecting to %s' % network_aux[wc.SSID])
self.dut.droid.wifiConnect(network_aux)
- file_path, avg_current = self.monsoon_data_collect_save()
- [plot, dt] = wputils.monsoon_data_plot(self.mon_info, file_path)
- self.test_result = dt.source.data['y0'][0]
- self.power_result.metric_value = (
- self.test_result * PHONE_BATTERY_VOLTAGE)
+ results.append(self.monsoon_data_collect_save())
+ wputils.monsoon_data_plot(self.mon_info, results)
+
+ total_current = 0
+ total_samples = 0
+ for result in results:
+ total_current += result.average_current * result.num_samples
+ total_samples += result.num_samples
+ average_current = total_current / total_samples
+
+ self.power_result.metric_value = [result.total_power for result in
+ results]
# Take Bugreport
if self.bug_report:
self.dut.take_bug_report(self.test_name, begin_time)
# Path fail check
- self.pass_fail_check()
+ self.pass_fail_check(average_current)
@test_tracker_info(uuid='e5ff95c0-b17e-425c-a903-821ba555a9b9')
def test_screenon_toggle_between_AP(self):
@@ -119,22 +127,30 @@ class PowerWiFiroamingTest(PWBT.PowerWiFiBaseTest):
time.sleep(5)
# Toggle between two networks
begin_time = utils.get_current_epoch_time()
+ results = []
for i in range(self.toggle_times):
self.dut.log.info('Connecting to %s' % network_main[wc.SSID])
self.dut.droid.wifiConnect(network_main)
- file_path, avg_current = self.monsoon_data_collect_save()
+ results.append(self.monsoon_data_collect_save())
self.dut.log.info('Connecting to %s' % network_aux[wc.SSID])
self.dut.droid.wifiConnect(network_aux)
- file_path, avg_current = self.monsoon_data_collect_save()
- [plot, dt] = wputils.monsoon_data_plot(self.mon_info, file_path)
- self.test_result = dt.source.data['y0'][0]
- self.power_result.metric_value = (
- self.test_result * PHONE_BATTERY_VOLTAGE)
+ results.append(self.monsoon_data_collect_save())
+ wputils.monsoon_data_plot(self.mon_info, results)
+
+ total_current = 0
+ total_samples = 0
+ for result in results:
+ total_current += result.average_current * result.num_samples
+ total_samples += result.num_samples
+ average_current = total_current / total_samples
+
+ self.power_result.metric_value = [result.total_power for result in
+ results]
# Take Bugreport
if self.bug_report:
self.dut.take_bug_report(self.test_name, begin_time)
# Path fail check
- self.pass_fail_check()
+ self.pass_fail_check(average_current)
@test_tracker_info(uuid='a16ae337-326f-4d09-990f-42232c3c0dc4')
def test_screenoff_wifi_wedge(self):
diff --git a/acts/tests/google/tel/live/TelLiveDataTest.py b/acts/tests/google/tel/live/TelLiveDataTest.py
index 3d967cce40..e1a691069c 100644
--- a/acts/tests/google/tel/live/TelLiveDataTest.py
+++ b/acts/tests/google/tel/live/TelLiveDataTest.py
@@ -3232,14 +3232,8 @@ class TelLiveDataTest(TelephonyBaseTest):
for i in range(1, total_iteration + 1):
msg = "Airplane mode test Iteration: <%s> / <%s>" % (i, total_iteration)
self.log.info(msg)
- if not toggle_airplane_mode(ad.log, ad, True):
- ad.log.error("Toggle APM on failed")
- fail_count["apm_on"] += 1
- ad.log.error(">----Iteration : %d/%d failed.----<",
- i, total_iteration)
- if not toggle_airplane_mode(ad.log, ad, False):
- ad.log.error("Toggle APM off failed")
- fail_count["apm_off"] += 1
+ if not airplane_mode_test(self.log, ad):
+ fail_count["apm_run"] += 1
ad.log.error(">----Iteration : %d/%d failed.----<",
i, total_iteration)
ad.log.info(">----Iteration : %d/%d succeeded.----<",
@@ -3249,11 +3243,12 @@ class TelLiveDataTest(TelephonyBaseTest):
for failure, count in fail_count.items():
if count:
ad.log.error("%s: %s %s failures in %s iterations",
- self.test_name, count, failure,
- total_iteration)
+ self.test_name, count, failure,
+ total_iteration)
test_result = False
return test_result
+
@test_tracker_info(uuid="3a82728f-18b5-4a35-9eab-4e6cf55271d9")
@TelephonyBaseTest.tel_test_wrap
def test_apm_toggle_stress(self):
diff --git a/acts/tests/google/tel/live/TelLiveStressDataTest.py b/acts/tests/google/tel/live/TelLiveStressDataTest.py
new file mode 100644
index 0000000000..b8012aeca2
--- /dev/null
+++ b/acts/tests/google/tel/live/TelLiveStressDataTest.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019 - Google
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+ Test Script for Telephony Stress data Test
+"""
+from acts.test_decorators import test_tracker_info
+from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts.test_utils.tel.tel_test_utils import iperf_test_by_adb
+from acts.test_utils.tel.tel_test_utils import iperf_udp_test_by_adb
+
+
+class TelLiveStressDataTest(TelephonyBaseTest):
+ def setup_class(self):
+ super().setup_class()
+ self.ad = self.android_devices[0]
+ self.iperf_server_address = self.user_params.get("iperf_server",
+ '0.0.0.0')
+ self.iperf_srv_tcp_port = self.user_params.get("iperf_server_tcp_port",
+ 0)
+ self.iperf_srv_udp_port = self.user_params.get("iperf_server_udp_port",
+ 0)
+ self.test_duration = self.user_params.get("data_stress_duration", 60)
+
+ return True
+
+ @test_tracker_info(uuid="190fdeb1-541e-455f-9f37-762a8e55c07f")
+ @TelephonyBaseTest.tel_test_wrap
+ def test_tcp_upload_stress(self):
+ return iperf_test_by_adb(self.log,
+ self.ad,
+ self.iperf_server_address,
+ self.iperf_srv_tcp_port,
+ False,
+ self.test_duration)
+
+ @test_tracker_info(uuid="af9805f8-6ed5-4e05-823e-d88dcef45637")
+ @TelephonyBaseTest.tel_test_wrap
+ def test_tcp_download_stress(self):
+ return iperf_test_by_adb(self.log,
+ self.ad,
+ self.iperf_server_address,
+ self.iperf_srv_tcp_port,
+ True,
+ self.test_duration)
+
+ @test_tracker_info(uuid="55bf5e09-dc7b-40bc-843f-31fed076ffe4")
+ @TelephonyBaseTest.tel_test_wrap
+ def test_udp_upload_stress(self):
+ return iperf_udp_test_by_adb(self.log,
+ self.ad,
+ self.iperf_server_address,
+ self.iperf_srv_udp_port,
+ False,
+ self.test_duration)
+
+ @test_tracker_info(uuid="02ae88b2-d597-45df-ab5a-d701d1125a0f")
+ @TelephonyBaseTest.tel_test_wrap
+ def test_udp_download_stress(self):
+ return iperf_udp_test_by_adb(self.log,
+ self.ad,
+ self.iperf_server_address,
+ self.iperf_srv_udp_port,
+ True,
+ self.test_duration)
diff --git a/acts/tests/google/tel/live/TelLiveStressTest.py b/acts/tests/google/tel/live/TelLiveStressTest.py
index f70fc0880e..ec364ced23 100644
--- a/acts/tests/google/tel/live/TelLiveStressTest.py
+++ b/acts/tests/google/tel/live/TelLiveStressTest.py
@@ -289,8 +289,8 @@ class TelLiveStressTest(TelephonyBaseTest):
0: sms_send_receive_verify,
1: mms_send_receive_verify
}
-
- self.dut.log.info("Network in RAT %s", self._get_network_rat(slot_id))
+ rat = self._get_network_rat(slot_id)
+ self.dut.log.info("Network in RAT %s", rat)
if self.dut_incall and not is_rat_svd_capable(rat.upper()):
self.dut.log.info("In call data not supported, test SMS only")
selection = 0
@@ -344,7 +344,8 @@ class TelLiveStressTest(TelephonyBaseTest):
self.log.error("%s fails", log_msg)
self.result_info["%s Failure" % message_type] += 1
else:
- self.dut.log.info("Network in RAT %s", self._get_network_rat(slot_id))
+ rat = self._get_network_rat(slot_id)
+ self.dut.log.info("Network in RAT %s", rat)
if self.dut_incall and not is_rat_svd_capable(rat.upper()):
self.dut.log.info(
"In call data not supported, MMS failure expected")
@@ -784,8 +785,9 @@ class TelLiveStressTest(TelephonyBaseTest):
file_name = file_names[selection]
self.result_info["Internet Connection Check Total"] += 1
+ rat = self._get_network_rat(slot_id)
if not self.internet_connection_check_method(self.log, self.dut):
- self.dut.log.info("Network in RAT %s", self._get_network_rat(slot_id))
+ self.dut.log.info("Network in RAT %s", rat)
if self.dut_incall and not is_rat_svd_capable(rat.upper()):
self.result_info[
"Expected Incall Internet Connection Check Failure"] += 1
diff --git a/acts/tests/google/tel/live/TelLiveVoiceTest.py b/acts/tests/google/tel/live/TelLiveVoiceTest.py
index fc6aabad2d..5114daa41c 100644
--- a/acts/tests/google/tel/live/TelLiveVoiceTest.py
+++ b/acts/tests/google/tel/live/TelLiveVoiceTest.py
@@ -56,6 +56,7 @@ from acts.test_utils.tel.tel_test_utils import get_mobile_data_usage
from acts.test_utils.tel.tel_test_utils import hangup_call
from acts.test_utils.tel.tel_test_utils import initiate_call
from acts.test_utils.tel.tel_test_utils import is_phone_in_call_active
+from acts.test_utils.tel.tel_test_utils import is_phone_in_call
from acts.test_utils.tel.tel_test_utils import multithread_func
from acts.test_utils.tel.tel_test_utils import num_active_calls
from acts.test_utils.tel.tel_test_utils import remove_mobile_data_usage_limit
@@ -68,6 +69,8 @@ from acts.test_utils.tel.tel_test_utils import wait_for_ringing_call
from acts.test_utils.tel.tel_test_utils import wait_for_state
from acts.test_utils.tel.tel_test_utils import start_youtube_video
from acts.test_utils.tel.tel_test_utils import set_wifi_to_default
+from acts.test_utils.tel.tel_test_utils import STORY_LINE
+from acts.test_utils.tel.tel_test_utils import wait_for_in_call_active
from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_1x
from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
@@ -113,6 +116,45 @@ class TelLiveVoiceTest(TelephonyBaseTest):
""" Tests Begin """
@TelephonyBaseTest.tel_test_wrap
+ @test_tracker_info(uuid="c5009f8c-eb1d-4cd9-85ce-604298bbeb3e")
+ def test_call_to_answering_machine(self):
+ """ Voice call to an answering machine.
+
+ 1. Make Sure PhoneA attached to voice network.
+ 2. Call from PhoneA to Storyline
+ 3. Verify call is in ACTIVE state
+ 4. Hangup Call from PhoneA
+
+ Raises:
+ TestFailure if not success.
+ """
+ ad = self.android_devices[0]
+
+ if not phone_setup_voice_general(ad.log, ad):
+ ad.log.error("Phone Failed to Set Up Properly for Voice.")
+ return False
+ for iteration in range(3):
+ result = True
+ ad.log.info("Attempt %d", iteration + 1)
+ if not initiate_call(ad.log, ad, STORY_LINE) and \
+ wait_for_in_call_active(ad, 60, 3):
+ ad.log.error("Call Failed to Initiate")
+ result = False
+ time.sleep(WAIT_TIME_IN_CALL)
+ if not is_phone_in_call(ad.log, ad):
+ ad.log.error("Call Dropped")
+ result = False
+ if not hangup_call(ad.log, ad):
+ ad.log.error("Call Failed to Hangup")
+ result = False
+ if result:
+ ad.log.info("Call test PASS in iteration %d", iteration + 1)
+ return True
+ ad.log.info("Call test FAIL in all 3 iterations")
+ return False
+
+
+ @TelephonyBaseTest.tel_test_wrap
@test_tracker_info(uuid="fca3f9e1-447a-416f-9a9c-50b7161981bf")
def test_call_mo_voice_general(self):
""" General voice to voice call.
diff --git a/acts/tests/google/wifi/WifiChaosTest.py b/acts/tests/google/wifi/WifiChaosTest.py
index 0fa77b3ae4..e0a4668013 100755
--- a/acts/tests/google/wifi/WifiChaosTest.py
+++ b/acts/tests/google/wifi/WifiChaosTest.py
@@ -199,8 +199,12 @@ class WifiChaosTest(WifiBaseTest):
sec: Time in seconds to run teh ping traffic.
"""
+ self.log.info("Finding Gateway...")
+ route_response = self.dut.adb.shell("ip route get 8.8.8.8")
+ gateway_ip = re.search('via (.*) dev', str(route_response)).group(1)
+ self.log.info("Gateway IP = %s" % gateway_ip)
self.log.info("Running ping for %d seconds" % sec)
- result = self.dut.adb.shell("ping -w %d %s" % (sec, PING_ADDR),
+ result = self.dut.adb.shell("ping -w %d %s" % (sec, gateway_ip),
timeout=sec + 1)
self.log.debug("Ping Result = %s" % result)
if "100% packet loss" in result:
diff --git a/acts/tests/google/wifi/WifiPingTest.py b/acts/tests/google/wifi/WifiPingTest.py
index 8e9bcee24c..1bce90392b 100644
--- a/acts/tests/google/wifi/WifiPingTest.py
+++ b/acts/tests/google/wifi/WifiPingTest.py
@@ -27,6 +27,7 @@ from acts import utils
from acts.controllers.utils_lib import ssh
from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
from acts.test_utils.wifi import ota_chamber
+from acts.test_utils.wifi import ota_sniffer
from acts.test_utils.wifi import wifi_performance_test_utils as wputils
from acts.test_utils.wifi import wifi_retail_ap as retail_ap
from acts.test_utils.wifi import wifi_test_utils as wutils
@@ -77,13 +78,15 @@ class WifiPingTest(base_test.BaseTestClass):
'ping_test_params', 'testbed_params', 'main_network',
'RetailAccessPoints', 'RemoteServer'
]
- opt_params = ['golden_files_list']
+ opt_params = ['golden_files_list', 'OTASniffer']
self.unpack_userparams(req_params, opt_params)
self.testclass_params = self.ping_test_params
self.num_atten = self.attenuators[0].instrument.num_atten
self.ping_server = ssh.connection.SshConnection(
ssh.settings.from_config(self.RemoteServer[0]['ssh_config']))
self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
+ if hasattr(self, 'OTASniffer'):
+ self.sniffer = ota_sniffer.create(self.OTASniffer)[0]
self.log.info('Access Point Configuration: {}'.format(
self.access_point.ap_settings))
self.log_path = os.path.join(logging.log_path, 'results')
@@ -325,6 +328,12 @@ class WifiPingTest(base_test.BaseTestClass):
test_result['rssi_results'] = []
test_result['ping_results'] = []
test_result['llstats'] = []
+ # Setup sniffer
+ if self.testbed_params['sniffer_enable']:
+ self.sniffer.start_capture(
+ testcase_params['test_network'],
+ testcase_params['ping_duration'] *
+ len(testcase_params['atten_range']) + self.TEST_TIMEOUT)
# Run ping and sweep attenuation as needed
zero_counter = 0
for atten in testcase_params['atten_range']:
@@ -372,6 +381,8 @@ class WifiPingTest(base_test.BaseTestClass):
test_result['ping_results'].append(
self.DISCONNECTED_PING_RESULT)
break
+ if self.testbed_params['sniffer_enable']:
+ self.sniffer.stop_capture()
return test_result
def setup_ap(self, testcase_params):
@@ -411,23 +422,22 @@ class WifiPingTest(base_test.BaseTestClass):
asserts.skip('Battery level too low. Skipping test.')
# Turn screen off to preserve battery
self.dut.go_to_sleep()
- band = self.access_point.band_lookup_by_channel(
- testcase_params['channel'])
current_network = self.dut.droid.wifiGetConnectionInfo()
try:
connected = wutils.validate_connection(self.dut) is not None
except:
connected = False
- if connected and current_network['SSID'] == self.main_network[band][
- 'SSID']:
+ if connected and current_network['SSID'] == testcase_params[
+ 'test_network']['SSID']:
self.log.info('Already connected to desired network')
else:
wutils.reset_wifi(self.dut)
self.dut.droid.wifiSetCountryCode(
self.testclass_params['country_code'])
- self.main_network[band]['channel'] = testcase_params['channel']
+ testcase_params['test_network']['channel'] = testcase_params[
+ 'channel']
wutils.wifi_connect(self.dut,
- self.main_network[band],
+ testcase_params['test_network'],
num_of_tries=5,
check_connectivity=False)
self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
@@ -460,6 +470,9 @@ class WifiPingTest(base_test.BaseTestClass):
return self.testclass_params['range_atten_start']
def compile_test_params(self, testcase_params):
+ band = self.access_point.band_lookup_by_channel(
+ testcase_params['channel'])
+ testcase_params['test_network'] = self.main_network[band]
if testcase_params['test_type'] == 'test_ping_range':
testcase_params.update(
ping_interval=self.testclass_params['range_ping_interval'],
diff --git a/acts/tests/google/wifi/WifiRvrTest.py b/acts/tests/google/wifi/WifiRvrTest.py
index cd2ee766c9..289c5e4b27 100644
--- a/acts/tests/google/wifi/WifiRvrTest.py
+++ b/acts/tests/google/wifi/WifiRvrTest.py
@@ -27,6 +27,7 @@ from acts.controllers import iperf_server as ipf
from acts.controllers.utils_lib import ssh
from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
from acts.test_utils.wifi import ota_chamber
+from acts.test_utils.wifi import ota_sniffer
from acts.test_utils.wifi import wifi_performance_test_utils as wputils
from acts.test_utils.wifi import wifi_retail_ap as retail_ap
from acts.test_utils.wifi import wifi_test_utils as wutils
@@ -65,7 +66,7 @@ class WifiRvrTest(base_test.BaseTestClass):
'RetailAccessPoints', 'rvr_test_params', 'testbed_params',
'RemoteServer'
]
- opt_params = ['main_network', 'golden_files_list']
+ opt_params = ['main_network', 'golden_files_list', 'OTASniffer']
self.unpack_userparams(req_params, opt_params)
self.testclass_params = self.rvr_test_params
self.num_atten = self.attenuators[0].instrument.num_atten
@@ -74,6 +75,8 @@ class WifiRvrTest(base_test.BaseTestClass):
ssh.settings.from_config(self.RemoteServer[0]['ssh_config']))
self.iperf_client = self.iperf_clients[0]
self.access_point = retail_ap.create(self.RetailAccessPoints)[0]
+ if hasattr(self, 'OTASniffer'):
+ self.sniffer = ota_sniffer.create(self.OTASniffer)[0]
self.log.info('Access Point Configuration: {}'.format(
self.access_point.ap_settings))
self.log_path = os.path.join(logging.log_path, 'results')
@@ -311,6 +314,7 @@ class WifiRvrTest(base_test.BaseTestClass):
rvr_result['testcase_params']['mode']]['high']
for tput in rvr_result['throughput_receive']
]
+ rvr_result['metrics']['high_tput_range'] = -1
for idx in range(len(tput_below_limit)):
if all(tput_below_limit[idx:]):
if idx == 0:
@@ -367,6 +371,11 @@ class WifiRvrTest(base_test.BaseTestClass):
attenuator.set_atten(atten, strict=False)
# Refresh link layer stats
llstats_obj.update_stats()
+ # Setup sniffer
+ if self.testbed_params['sniffer_enable']:
+ self.sniffer.start_capture(
+ network=testcase_params['test_network'],
+ duration=self.testclass_params['iperf_duration'] / 5)
# Start iperf session
self.iperf_server.start(tag=str(atten))
rssi_future = wputils.get_connected_rssi_nb(
@@ -383,6 +392,9 @@ class WifiRvrTest(base_test.BaseTestClass):
'chain_1_rssi': rssi_result['chain_1_rssi']['mean']
}
rssi.append(current_rssi)
+ # Stop sniffer
+ if self.testbed_params['sniffer_enable']:
+ self.sniffer.stop_capture(tag=str(atten))
# Parse and log result
if testcase_params['use_client_output']:
iperf_file = client_output_path
@@ -407,7 +419,9 @@ class WifiRvrTest(base_test.BaseTestClass):
atten, curr_throughput, current_rssi['signal_poll_rssi'],
current_rssi['chain_0_rssi'],
current_rssi['chain_1_rssi']))
- if curr_throughput == 0 and current_rssi['signal_poll_rssi'] < -80:
+ if curr_throughput == 0 and (
+ current_rssi['signal_poll_rssi'] < -80
+ or numpy.isnan(current_rssi['signal_poll_rssi'])):
zero_counter = zero_counter + 1
else:
zero_counter = 0
@@ -472,17 +486,17 @@ class WifiRvrTest(base_test.BaseTestClass):
asserts.skip('Overheating or Battery level low. Skipping test.')
# Turn screen off to preserve battery
self.dut.go_to_sleep()
- band = self.access_point.band_lookup_by_channel(
- testcase_params['channel'])
- if wputils.validate_network(self.dut, self.main_network[band]['SSID']):
+ if wputils.validate_network(self.dut,
+ testcase_params['test_network']['SSID']):
self.log.info('Already connected to desired network')
else:
wutils.reset_wifi(self.dut)
self.dut.droid.wifiSetCountryCode(
self.testclass_params['country_code'])
- self.main_network[band]['channel'] = testcase_params['channel']
+ testcase_params['test_network']['channel'] = testcase_params[
+ 'channel']
wutils.wifi_connect(self.dut,
- self.main_network[band],
+ testcase_params['test_network'],
num_of_tries=5,
check_connectivity=True)
self.dut_ip = self.dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
@@ -522,6 +536,9 @@ class WifiRvrTest(base_test.BaseTestClass):
x * self.testclass_params['atten_step']
for x in range(0, num_atten_steps)
]
+ band = self.access_point.band_lookup_by_channel(
+ testcase_params['channel'])
+ testcase_params['test_network'] = self.main_network[band]
if (testcase_params['traffic_direction'] == 'DL'
and not isinstance(self.iperf_server, ipf.IPerfServerOverAdb)
) or (testcase_params['traffic_direction'] == 'UL'
diff --git a/acts/tests/google/wifi/WifiSensitivityTest.py b/acts/tests/google/wifi/WifiSensitivityTest.py
index 19476843cd..a982cc6972 100644
--- a/acts/tests/google/wifi/WifiSensitivityTest.py
+++ b/acts/tests/google/wifi/WifiSensitivityTest.py
@@ -194,7 +194,7 @@ class WifiSensitivityTest(WifiRvrTest, WifiPingTest):
Args:
result: dict containing attenuation, throughput and other meta
- data
+ data
"""
try:
golden_path = next(file_name
@@ -207,7 +207,7 @@ class WifiSensitivityTest(WifiRvrTest, WifiPingTest):
except:
golden_sensitivity = float('nan')
- result_string = ('Througput = {}%, Sensitivity = {}.'
+ result_string = ('Throughput = {}%, Sensitivity = {}.'
'Target Sensitivity = {}'.format(
result['peak_throughput_pct'],
result['sensitivity'], golden_sensitivity))
diff --git a/acts/tests/google/wifi/WifiSoftApTest.py b/acts/tests/google/wifi/WifiSoftApTest.py
index cfc62d9077..9298e63ae4 100644
--- a/acts/tests/google/wifi/WifiSoftApTest.py
+++ b/acts/tests/google/wifi/WifiSoftApTest.py
@@ -29,6 +29,7 @@ from acts.test_utils.tel import tel_test_utils as tel_utils
from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_AUTO
+from acts.test_utils.wifi import wifi_constants
from acts.test_utils.wifi import wifi_test_utils as wutils
from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
@@ -486,6 +487,139 @@ class WifiSoftApTest(WifiBaseTest):
"No extra android devices. Skip test")
self.validate_full_tether_startup(WIFI_CONFIG_APBAND_5G, test_clients=True)
+ @test_tracker_info(uuid="b991129e-030a-4998-9b08-0687270bec24")
+ def test_number_of_softap_clients(self):
+ """Test for number of softap clients to be updated correctly
+
+ 1. Turn of hotspot
+ 2. Register softap callback
+ 3. Let client connect to the hotspot
+ 4. Register second softap callback
+ 5. Force client connect/disconnect to hotspot
+ 6. Unregister second softap callback
+ 7. Force second client connect to hotspot (if supported)
+ 8. Turn off hotspot
+ 9. Verify second softap callback doesn't respond after unresister
+ """
+ config = wutils.start_softap_and_verify(self, WIFI_CONFIG_APBAND_AUTO)
+ # Register callback after softap enabled to avoid unnecessary callback
+ # impact the test
+ callbackId = self.dut.droid.registerSoftApCallback()
+ # Verify clients will update immediately after register callback
+ wutils.wait_for_expected_number_of_softap_clients(
+ self.dut, callbackId, 0)
+ wutils.wait_for_expected_softap_state(self.dut, callbackId,
+ wifi_constants.WIFI_AP_ENABLED_STATE)
+
+ # Force DUTs connect to Network
+ wutils.wifi_connect(self.dut_client, config,
+ check_connectivity=False)
+ wutils.wait_for_expected_number_of_softap_clients(
+ self.dut, callbackId, 1)
+
+ # Register another callback to verify multi callback clients case
+ callbackId_2 = self.dut.droid.registerSoftApCallback()
+ # Verify clients will update immediately after register callback
+ wutils.wait_for_expected_number_of_softap_clients(
+ self.dut, callbackId_2, 1)
+ wutils.wait_for_expected_softap_state(self.dut, callbackId_2,
+ wifi_constants.WIFI_AP_ENABLED_STATE)
+
+ # Client Off/On Wifi to verify number of softap clients will be updated
+ wutils.toggle_wifi_and_wait_for_reconnection(self.dut_client, config)
+
+ wutils.wait_for_expected_number_of_softap_clients(self.dut,
+ callbackId, 0)
+ wutils.wait_for_expected_number_of_softap_clients(self.dut,
+ callbackId_2, 0)
+ wutils.wait_for_expected_number_of_softap_clients(self.dut,
+ callbackId, 1)
+ wutils.wait_for_expected_number_of_softap_clients(self.dut,
+ callbackId_2, 1)
+
+ # Unregister callbackId_2 to verify multi callback clients case
+ self.dut.droid.unregisterSoftApCallback(callbackId_2)
+
+ if len(self.android_devices) > 2:
+ wutils.wifi_connect(self.android_devices[2], config,
+ check_connectivity=False)
+ wutils.wait_for_expected_number_of_softap_clients(
+ self.dut, callbackId, 2)
+
+ # Turn off softap when clients connected
+ wutils.stop_wifi_tethering(self.dut)
+ wutils.wait_for_disconnect(self.dut_client)
+ if len(self.android_devices) > 2:
+ wutils.wait_for_disconnect(self.android_devices[2])
+
+ # Verify client number change back to 0 after softap stop if client
+ # doesn't disconnect before softap stop
+ wutils.wait_for_expected_softap_state(self.dut, callbackId,
+ wifi_constants.WIFI_AP_DISABLING_STATE)
+ wutils.wait_for_expected_softap_state(self.dut, callbackId,
+ wifi_constants.WIFI_AP_DISABLED_STATE)
+ wutils.wait_for_expected_number_of_softap_clients(
+ self.dut, callbackId, 0)
+ # Unregister callback
+ self.dut.droid.unregisterSoftApCallback(callbackId)
+
+ # Check no any callbackId_2 event after unregister
+ asserts.assert_equal(
+ wutils.get_current_number_of_softap_clients(
+ self.dut, callbackId_2), None)
+
+ @test_tracker_info(uuid="35bc4ba1-bade-42ee-a563-0c73afb2402a")
+ def test_softap_auto_shut_off(self):
+ """Test for softap auto shut off
+
+ 1. Turn of hotspot
+ 2. Register softap callback
+ 3. Let client connect to the hotspot
+ 4. Start wait [wifi_constants.DEFAULT_SOFTAP_TIMEOUT_S] seconds
+ 5. Check hotspot doesn't shut off
+ 6. Let client disconnect to the hotspot
+ 7. Start wait [wifi_constants.DEFAULT_SOFTAP_TIMEOUT_S] seconds
+ 8. Check hotspot auto shut off
+ """
+ config = wutils.start_softap_and_verify(self, WIFI_CONFIG_APBAND_AUTO)
+ # Register callback after softap enabled to avoid unnecessary callback
+ # impact the test
+ callbackId = self.dut.droid.registerSoftApCallback()
+ # Verify clients will update immediately after register callback
+ wutils.wait_for_expected_number_of_softap_clients(self.dut,
+ callbackId, 0)
+ wutils.wait_for_expected_softap_state(self.dut, callbackId,
+ wifi_constants.WIFI_AP_ENABLED_STATE)
+
+ # Force DUTs connect to Network
+ wutils.wifi_connect(self.dut_client, config, check_connectivity=False)
+ wutils.wait_for_expected_number_of_softap_clients(
+ self.dut, callbackId, 1)
+
+ self.dut.log.info("Start waiting %s seconds with 1 clients ",
+ wifi_constants.DEFAULT_SOFTAP_TIMEOUT_S*1.1)
+ time.sleep(wifi_constants.DEFAULT_SOFTAP_TIMEOUT_S*1.1)
+
+ # When client connected, softap should keep as enabled
+ asserts.assert_true(self.dut.droid.wifiIsApEnabled(),
+ "SoftAp is not reported as running")
+
+ wutils.wifi_toggle_state(self.dut_client, False)
+ wutils.wait_for_expected_number_of_softap_clients(self.dut,
+ callbackId, 0)
+ self.dut.log.info("Start waiting %s seconds with 0 client",
+ wifi_constants.DEFAULT_SOFTAP_TIMEOUT_S*1.1)
+ time.sleep(wifi_constants.DEFAULT_SOFTAP_TIMEOUT_S*1.1)
+ # Softap should stop since no client connected
+ # doesn't disconnect before softap stop
+ wutils.wait_for_expected_softap_state(self.dut, callbackId,
+ wifi_constants.WIFI_AP_DISABLING_STATE)
+ wutils.wait_for_expected_softap_state(self.dut, callbackId,
+ wifi_constants.WIFI_AP_DISABLED_STATE)
+ asserts.assert_false(self.dut.droid.wifiIsApEnabled(),
+ "SoftAp is not reported as running")
+ self.dut.droid.unregisterSoftApCallback(callbackId)
+
""" Tests End """
diff --git a/acts/tests/google/wifi/WifiStressTest.py b/acts/tests/google/wifi/WifiStressTest.py
index 40a31083d0..0f9032aaf8 100644
--- a/acts/tests/google/wifi/WifiStressTest.py
+++ b/acts/tests/google/wifi/WifiStressTest.py
@@ -198,8 +198,8 @@ class WifiStressTest(WifiBaseTest):
else:
# force start a single scan so we don't have to wait for the scheduled scan.
wutils.start_wifi_connection_scan_and_return_status(self.dut)
- self.log.info("Wait 20s for network selection.")
- time.sleep(20)
+ self.log.info("Wait 60s for network selection.")
+ time.sleep(60)
try:
self.log.info("Connected to %s network after network selection"
% self.dut.droid.wifiGetConnectionInfo())