• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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()