1import os 2from typing import Optional, List 3import time 4from acts import context 5from acts import signals 6from acts.controllers.cellular_lib import AndroidCellularDut 7import acts_contrib.test_utils.power.cellular.cellular_power_base_test as PWCEL 8 9# TODO: b/261639867 10class AtUtil(): 11 """Util class for sending at command. 12 13 Attributes: 14 dut: AndroidDevice controller object. 15 """ 16 ADB_CMD_DISABLE_TXAS = 'am instrument -w -e request at+googtxas=2 -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"' 17 18 def __init__(self, dut, log) -> None: 19 self.dut = dut 20 self.log = log 21 22 # TODO: to be remove when b/261639867 complete, 23 # and we are using parent method. 24 def send(self, cmd: str,) -> Optional[str]: 25 res = str(self.dut.adb.shell(cmd)) 26 self.log.info(f'cmd sent: {cmd}') 27 self.log.info(f'response: {res}') 28 if 'SUCCESS' in res: 29 self.log.info('Command executed.') 30 else: 31 self.log.error('Fail to executed command.') 32 return res 33 34 def lock_LTE(self): 35 adb_enable_band_lock_lte = r'am instrument -w -e request at+GOOGSETNV=\"!SAEL3.Manual.Band.Select\ Enb\/\ Dis\",00,\"01\" -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"' 36 adb_set_band_lock_mode_lte = r'am instrument -w -e request at+GOOGSETNV=\"NASU.SIPC.NetworkMode.ManualMode\",0,\"0D\" -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"' 37 adb_set_band_lock_bitmap_0 = r'am instrument -w -e request at+GOOGSETNV=\"!SAEL3.Manual.Enabled.RFBands.BitMap\",0,\"09,00,00,00,00,00,00,00\" -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"' 38 adb_set_band_lock_bitmap_1 = r'am instrument -w -e request at+GOOGSETNV=\"!SAEL3.Manual.Enabled.RFBands.BitMap\",1,\"00,00,00,00,00,00,00,00\" -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"' 39 adb_set_band_lock_bitmap_2 = r'am instrument -w -e request at+GOOGSETNV=\"!SAEL3.Manual.Enabled.RFBands.BitMap\",2,\"00,00,00,00,00,00,00,00\" -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"' 40 adb_set_band_lock_bitmap_3 = r'am instrument -w -e request at+GOOGSETNV=\"!SAEL3.Manual.Enabled.RFBands.BitMap\",3,\"00,00,00,00,00,00,00,00\" -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"' 41 42 # enable lte 43 self.send(adb_enable_band_lock_lte) 44 time.sleep(2) 45 46 # OD is for NR/LTE in 4412 menu 47 self.send(adb_set_band_lock_mode_lte) 48 time.sleep(2) 49 50 # lock to B1 and B4 51 self.send(adb_set_band_lock_bitmap_0) 52 time.sleep(2) 53 self.send(adb_set_band_lock_bitmap_1) 54 time.sleep(2) 55 self.send(adb_set_band_lock_bitmap_2) 56 time.sleep(2) 57 self.send(adb_set_band_lock_bitmap_3) 58 time.sleep(2) 59 60 def clear_lock_band(self): 61 adb_set_band_lock_mode_auto = r'am instrument -w -e request at+GOOGSETNV=\"NASU.SIPC.NetworkMode.ManualMode\",0,\"03\" -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"' 62 adb_disable_band_lock_lte = r'am instrument -w -e request at+GOOGSETNV=\"!SAEL3.Manual.Band.Select\ Enb\/\ Dis\",0,\"00\" -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"' 63 64 # band lock mode auto 65 self.send(adb_set_band_lock_mode_auto) 66 time.sleep(2) 67 68 # disable band lock lte 69 self.send(adb_disable_band_lock_lte) 70 time.sleep(2) 71 72 def disable_txas(self): 73 cmd = self.ADB_CMD_DISABLE_TXAS 74 response = self.send(cmd) 75 return 'OK' in response 76 77class PowerCellularPresetLabBaseTest(PWCEL.PowerCellularLabBaseTest): 78 # Key for ODPM report 79 ODPM_ENERGY_TABLE_NAME = 'PowerStats HAL 2.0 energy meter' 80 ODPM_MODEM_CHANNEL_NAME = '[VSYS_PWR_MODEM]:Modem' 81 82 # Key for custom_property in Sponge 83 CUSTOM_PROP_KEY_BUILD_ID = 'build_id' 84 CUSTOM_PROP_KEY_INCR_BUILD_ID = 'incremental_build_id' 85 CUSTOM_PROP_KEY_BUILD_TYPE = 'build_type' 86 CUSTOM_PROP_KEY_SYSTEM_POWER = 'system_power' 87 CUSTOM_PROP_KEY_MODEM_BASEBAND = 'baseband' 88 CUSTOM_PROP_KEY_MODEM_ODPM_POWER= 'modem_odpm_power' 89 CUSTOM_PROP_KEY_DEVICE_NAME = 'device' 90 CUSTOM_PROP_KEY_DEVICE_BUILD_PHASE = 'device_build_phase' 91 CUSTOM_PROP_KEY_MODEM_KIBBLE_POWER = 'modem_kibble_power' 92 CUSTOM_PROP_KEY_TEST_NAME = 'test_name' 93 CUSTOM_PROP_KEY_MODEM_KIBBLE_WO_PCIE_POWER = 'modem_kibble_power_wo_pcie' 94 CUSTOM_PROP_KEY_MODEM_KIBBLE_PCIE_POWER = 'modem_kibble_pcie_power' 95 CUSTOM_PROP_KEY_RFFE_POWER = 'rffe_power' 96 CUSTOM_PROP_KEY_MMWAVE_POWER = 'mmwave_power' 97 # kibble report 98 KIBBLE_SYSTEM_RECORD_NAME = '- name: default_device.C10_EVT_1_1.Monsoon:mA' 99 MODEM_PCIE_RAIL_NAME_LIST = [ 100 'PP1800_L2C_PCIEG3', 101 'PP1200_L9C_PCIE', 102 'PP0850_L8C_PCIE' 103 ] 104 105 MODEM_RFFE_RAIL_NAME_LIST = [ 106 'PP1200_L31C_RFFE', 107 'VSYS_PWR_RFFE', 108 'PP2800_L33C_RFFE' 109 ] 110 111 MODEM_POWER_RAIL_NAME = 'VSYS_PWR_MODEM' 112 113 MODEM_MMWAVE_RAIL_NAME = 'VSYS_PWR_MMWAVE' 114 115 MONSOON_RAIL_NAME = 'Monsoon' 116 117 # params key 118 MONSOON_VOLTAGE_KEY = 'mon_voltage' 119 120 MDSTEST_APP_APK_NAME = 'mdstest.apk' 121 ADB_CMD_INSTALL = 'install {apk_path}' 122 ADB_CMD_DISABLE_TXAS = 'am instrument -w -e request at+googtxas=2 -e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"' 123 ADB_CMD_SET_NV = ('am instrument -w ' 124 '-e request at+googsetnv=\"{nv_name}\",{nv_index},\"{nv_value}\" ' 125 '-e response wait "com.google.mdstest/com.google.mdstest.instrument.ModemATCommandInstrumentation"') 126 127 def __init__(self, controllers): 128 super().__init__(controllers) 129 self.retryable_exceptions = signals.TestFailure 130 self.power_rails = {} 131 self.pcie_power = 0 132 self.rffe_power = 0 133 self.mmwave_power = 0 134 self.modem_power = 0 135 self.monsoon_power = 0 136 137 def setup_class(self): 138 super().setup_class() 139 140 # preset callbox 141 is_fr2 = 'Fr2' in self.TAG 142 self.cellular_simulator.switch_HCCU_settings(is_fr2=is_fr2) 143 144 self.at_util = AtUtil(self.cellular_dut.ad, self.log) 145 146 # preset UE. 147 self.log.info('Installing mdstest app.') 148 self.install_apk() 149 150 self.log.info('Disable antenna switch.') 151 is_txas_disabled = self.at_util.disable_txas() 152 self.log.info('Disable txas: ' + str(is_txas_disabled)) 153 154 # get sim type 155 self.unpack_userparams(has_3gpp_sim=True) 156 157 def setup_test(self): 158 self.cellular_simulator.set_sim_type(self.has_3gpp_sim) 159 try: 160 if 'LTE' in self.test_name: 161 self.at_util.lock_LTE() 162 super().setup_test() 163 except BrokenPipeError: 164 self.log.info('TA crashed test need retry.') 165 self.need_retry = True 166 self.cellular_simulator.recovery_ta() 167 self.cellular_simulator.socket_connect() 168 raise signals.TestFailure('TA crashed mid test, retry needed.') 169 # except: 170 # # self.log.info('Waiting for device to on.') 171 # # self.dut.adb.wait_for_device() 172 # # self.cellular_dut = AndroidCellularDut.AndroidCellularDut( 173 # # self.android_devices[0], self.log) 174 # # self.dut.root_adb() 175 # # # Restart SL4A 176 # # self.dut.start_services() 177 # # self.need_retry = True 178 # raise signals.TestError('Device reboot mid test, retry needed.') 179 180 def install_apk(self): 181 sleep_time = 3 182 for file in self.custom_files: 183 if self.MDSTEST_APP_APK_NAME in file: 184 if not self.cellular_dut.ad.is_apk_installed("com.google.mdstest"): 185 self.cellular_dut.ad.adb.install("-r -g %s" % file, timeout=300, ignore_status=True) 186 time.sleep(sleep_time) 187 if self.cellular_dut.ad.is_apk_installed("com.google.mdstest"): 188 self.log.info('mdstest installed.') 189 else: 190 self.log.warning('fail to install mdstest.') 191 192 def set_nv(self, nv_name, index, value): 193 cmd = self.ADB_CMD_SET_NV.format( 194 nv_name=nv_name, 195 nv_index=index, 196 nv_value=value 197 ) 198 response = str(self.cellular_dut.ad.adb.shell(cmd)) 199 self.log.info(response) 200 201 def enable_ims_nr(self): 202 # set !NRCAPA.Gen.VoiceOverNr 203 self.set_nv( 204 nv_name = '!NRCAPA.Gen.VoiceOverNr', 205 index = '0', 206 value = '01' 207 ) 208 # set PSS.AIMS.Enable.NRSACONTROL 209 self.set_nv( 210 nv_name = 'PSS.AIMS.Enable.NRSACONTROL', 211 index = '0', 212 value = '00' 213 ) 214 # set DS.PSS.AIMS.Enable.NRSACONTROL 215 self.set_nv( 216 nv_name = 'DS.PSS.AIMS.Enable.NRSACONTROL', 217 index = '0', 218 value = '00' 219 ) 220 if self.cellular_dut.ad.model == 'oriole': 221 # For P21, NR.CONFIG.MODE/DS.NR.CONFIG.MODE 222 self.set_nv( 223 nv_name = 'NR.CONFIG.MODE', 224 index = '0', 225 value = '11' 226 ) 227 # set DS.NR.CONFIG.MODE 228 self.set_nv( 229 nv_name = 'DS.NR.CONFIG.MODE', 230 index = '0', 231 value = '11' 232 ) 233 else: 234 # For P22, NASU.NR.CONFIG.MODE to 11 235 self.set_nv( 236 nv_name = 'NASU.NR.CONFIG.MODE', 237 index = '0', 238 value = '11' 239 ) 240 241 def get_odpm_values(self): 242 """Get power measure from ODPM. 243 244 Parsing energy table in ODPM file 245 and convert to. 246 Returns: 247 odpm_power_results: a dictionary 248 has key as channel name, 249 and value as power measurement of that channel. 250 """ 251 self.log.info('Start calculating power by channel from ODPM report.') 252 odpm_power_results = {} 253 254 # device before P21 don't have ODPM reading 255 if not self.odpm_folder: 256 return odpm_power_results 257 258 # getting ODPM modem power value 259 odpm_file_name = '{}.{}.dumpsys_odpm_{}.txt'.format( 260 self.__class__.__name__, 261 self.current_test_name, 262 'after') 263 odpm_file_path = os.path.join(self.odpm_folder, odpm_file_name) 264 if os.path.exists(odpm_file_path): 265 elapsed_time = None 266 with open(odpm_file_path, 'r') as f: 267 # find energy table in ODPM report 268 for line in f: 269 if self.ODPM_ENERGY_TABLE_NAME in line: 270 break 271 272 # get elapse time 2 adb ODPM cmd (mS) 273 elapsed_time_str = f.readline() 274 elapsed_time = float(elapsed_time_str 275 .split(':')[1] 276 .strip() 277 .split(' ')[0]) 278 self.log.info(elapsed_time_str) 279 280 # skip column name row 281 next(f) 282 283 # get power of different channel from odpm report 284 for line in f: 285 if 'End' in line: 286 break 287 else: 288 # parse columns 289 # example result of line.strip().split() 290 # ['[VSYS_PWR_DISPLAY]:Display', '1039108.42', 'mWs', '(', '344.69)'] 291 channel, _, _, _, delta_str = line.strip().split() 292 delta = float(delta_str[:-2].strip()) 293 294 # calculate OPDM power 295 # delta is a different in cumulative energy 296 # between 2 adb ODPM cmd 297 elapsed_time_s = elapsed_time / 1000 298 power = delta / elapsed_time_s 299 odpm_power_results[channel] = power 300 self.log.info( 301 channel + ' ' + str(power) + ' mW' 302 ) 303 return odpm_power_results 304 305 def _is_any_substring(self, longer_word: str, word_list: List[str]) -> bool: 306 """Check if any word in word list a substring of a longer word.""" 307 return any(w in longer_word for w in word_list) 308 309 def parse_power_rails_csv(self): 310 kibble_dir = os.path.join(self.root_output_path, 'Kibble') 311 kibble_csv_path = None 312 if os.path.exists(kibble_dir): 313 for f in os.listdir(kibble_dir): 314 if self.test_name in f and '.csv' in f: 315 kibble_csv_path = os.path.join(kibble_dir, f) 316 self.log.info('Kibble csv file path: ' + kibble_csv_path) 317 break 318 319 self.log.info('Parsing power rails from csv.') 320 if kibble_csv_path: 321 with open(kibble_csv_path, 'r') as f: 322 for line in f: 323 # railname,val,mA,val,mV,val,mW 324 railname, _, _, _, _, power, _ = line.split(',') 325 # parse pcie power 326 if self._is_any_substring(railname, self.MODEM_PCIE_RAIL_NAME_LIST): 327 self.log.info(railname + ': ' + power) 328 self.pcie_power += float(power) 329 elif self.MODEM_POWER_RAIL_NAME in railname: 330 self.log.info(railname + ': ' + power) 331 self.modem_power = float(power) 332 elif self._is_any_substring(railname, self.MODEM_RFFE_RAIL_NAME_LIST): 333 self.log.info(railname + ': ' + power) 334 self.rffe_power = float(power) 335 elif self.MODEM_MMWAVE_RAIL_NAME in railname: 336 self.log.info(railname + ': ' + power) 337 self.mmwave_power = float(power) 338 elif self.MONSOON_RAIL_NAME == railname: 339 self.log.info(railname + ': ' + power) 340 self.monsoon_power = float(power) 341 if self.modem_power: 342 self.power_results[self.test_name] = self.modem_power 343 344 def sponge_upload(self): 345 """Upload result to sponge as custom field.""" 346 # test name 347 test_name_arr = self.current_test_name.split('_') 348 test_name_for_sponge = ''.join( 349 word[0].upper() + word[1:].lower() 350 for word in test_name_arr 351 if word not in ('preset', 'test') 352 ) 353 354 # build info 355 build_info = self.cellular_dut.ad.build_info 356 build_id = build_info.get('build_id', 'Unknown') 357 incr_build_id = build_info.get('incremental_build_id', 'Unknown') 358 modem_base_band = self.cellular_dut.ad.adb.getprop( 359 'gsm.version.baseband') 360 build_type = build_info.get('build_type', 'Unknown') 361 362 # device info 363 device_info = self.cellular_dut.ad.device_info 364 device_name = device_info.get('model', 'Unknown') 365 device_build_phase = self.cellular_dut.ad.adb.getprop( 366 'ro.boot.hardware.revision' 367 ) 368 369 # power measurement results 370 odpm_power_results = self.get_odpm_values() 371 odpm_power = odpm_power_results.get(self.ODPM_MODEM_CHANNEL_NAME, 0) 372 system_power = 0 373 374 # if kibbles are using, get power from kibble 375 modem_kibble_power_wo_pcie = 0 376 if hasattr(self, 'bitses'): 377 self.parse_power_rails_csv() 378 modem_kibble_power_wo_pcie = self.modem_power - self.pcie_power 379 system_power = self.monsoon_power 380 else: 381 system_power = self.power_results.get(self.test_name, 0) 382 383 self.record_data({ 384 'Test Name': self.test_name, 385 'sponge_properties': { 386 self.CUSTOM_PROP_KEY_SYSTEM_POWER: system_power, 387 self.CUSTOM_PROP_KEY_BUILD_ID: build_id, 388 self.CUSTOM_PROP_KEY_INCR_BUILD_ID: incr_build_id, 389 self.CUSTOM_PROP_KEY_MODEM_BASEBAND: modem_base_band, 390 self.CUSTOM_PROP_KEY_BUILD_TYPE: build_type, 391 self.CUSTOM_PROP_KEY_MODEM_ODPM_POWER: odpm_power, 392 self.CUSTOM_PROP_KEY_DEVICE_NAME: device_name, 393 self.CUSTOM_PROP_KEY_DEVICE_BUILD_PHASE: device_build_phase, 394 self.CUSTOM_PROP_KEY_MODEM_KIBBLE_POWER: self.modem_power, 395 self.CUSTOM_PROP_KEY_TEST_NAME: test_name_for_sponge, 396 self.CUSTOM_PROP_KEY_MODEM_KIBBLE_WO_PCIE_POWER: modem_kibble_power_wo_pcie, 397 self.CUSTOM_PROP_KEY_MODEM_KIBBLE_PCIE_POWER: self.pcie_power, 398 self.CUSTOM_PROP_KEY_RFFE_POWER: self.rffe_power, 399 self.CUSTOM_PROP_KEY_MMWAVE_POWER: self.mmwave_power 400 }, 401 }) 402 403 def teardown_test(self): 404 super().teardown_test() 405 # restore device to ready state for next test 406 self.log.info('Enable mobile data.') 407 self.dut.adb.shell('svc data enable') 408 self.cellular_simulator.detach() 409 self.cellular_dut.toggle_airplane_mode(True) 410 411 # processing result 412 self.sponge_upload() 413 if 'LTE' in self.test_name: 414 self.at_util.clear_lock_band()