1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging
6import re
7import time
8import common
9from autotest_lib.client.cros.cellular import cellular_logging
10from autotest_lib.client.cros.cellular import cellular_system_error
11from autotest_lib.client.cros.cellular import air_state_verifier
12from autotest_lib.client.cros.cellular import base_station_interface
13from autotest_lib.client.cros.cellular import cellular
14from autotest_lib.client.bin import utils
15
16POLL_SLEEP = 0.2
17
18log = cellular_logging.SetupCellularLogging('base_station_8960')
19
20class BaseStation8960(base_station_interface.BaseStationInterface):
21    """Wrap an Agilent 8960 Series 10."""
22
23    def __init__(self, scpi_connection, no_initialization=False):
24        """
25        Creates an 8960 call-box object.
26        TODO (byrok): make a factory that returns a call_box, of either
27        a 8960 or a PXT, or a...
28
29        @param scpi_connection:  The scpi port to send commands over
30        @param no_initialization: Don't do anything. Useful for unit testing
31        and debugging when you don't want to run all the usual functions.
32        """
33        self.c = scpi_connection
34        if no_initialization:
35            return
36        self.checker_context = self.c.checker_context
37        with self.checker_context:
38            self._Verify()
39            self._Reset()
40            self.SetPower(cellular.Power.DEFAULT)
41
42    def _Verify(self):
43        idn = self.c.Query('*IDN?')
44        if '8960 Series 10 E5515C' not in idn:
45            raise cellular_system_error.BadState(
46                'Not actually an 8960:  *IDN? says ' + idn)
47
48    def _Reset(self):
49        self.c.Reset()
50        self.Stop()
51        # Perform a partial reset to workaround a problem with the 8960
52        # failing to accept CDMA connections after switching from a
53        # GSM technology.
54        self.c.SendStanza(['SYSTEM:PRESet3'])
55
56    def _IsIdle(self):
57        call_state = self.c.Query('CALL:STATus?')
58        data_state = self.c.Query('CALL:STATus:DATa?')
59        return call_state == 'IDLE' and data_state in ['IDLE', 'OFF']
60
61    def Close(self):
62        self.c.Close()
63
64    def GetAirStateVerifier(self):
65        return air_state_verifier.AirStateVerifierBasestation(self)
66
67    def GetDataCounters(self):
68        output = {}
69        for counter in ['OTATx', 'OTARx', 'IPTX', 'IPRX']:
70            result_text = self.c.Query('CALL:COUNT:DTMonitor:%s:DRATe?' %
71                                       counter)
72            result = [float(x) for x in result_text.rstrip().split(',')]
73            output[counter] = dict(zip(['Mean', 'Current', 'Max', 'Total'],
74                                       result))
75        logging.info('Data counters: %s', output)
76        return output
77
78    def GetRatUeDataStatus(self):
79        """Get the radio-access-technology-specific status of the UE.
80
81        Unlike GetUeDataStatus, below, this returns a status that depends
82        on the RAT being used.
83        """
84        status = self.c.Query('CALL:STATus:DATa?')
85        rat = \
86            ConfigDictionaries.FORMAT_TO_DATA_STATUS_TYPE[self.format][status]
87        return rat
88
89    def GetUeDataStatus(self):
90        """Get the UeGenericDataStatus status of the device."""
91        rat = self.GetRatUeDataStatus()
92        return cellular.RatToGenericDataStatus[rat]
93
94    def ResetDataCounters(self):
95        self.c.SendStanza(['CALL:COUNt:DTMonitor:CLEar'])
96
97    def ClearErrors(self):
98        self.c.RetrieveErrors()
99
100    def LogStats(self):
101        self.c.Query("CALL:HSDPa:SERVice:PSData:HSDSchannel:CONFig?")
102
103        # Category reported by UE
104        self.c.Query("CALL:HSDPa:MS:REPorted:HSDSChannel:CATegory?")
105        # The category in use
106        self.c.Query("CALL:STATUS:MS:HSDSChannel:CATegory?")
107        self.c.Query("CALL:HSDPA:SERV:PSD:CQI?")
108
109    def SetBsIpV4(self, ip1, ip2):
110        self.c.SendStanza([
111            'SYSTem:COMMunicate:LAN:SELF:ADDRess:IP4 "%s"' % ip1,
112            'SYSTem:COMMunicate:LAN:SELF:ADDRess2:IP4 "%s"' % ip2,])
113
114    def SetBsNetmaskV4(self, netmask):
115        self.c.SendStanza([
116            'SYSTem:COMMunicate:LAN:SELF:SMASk:IP4 "%s"' % netmask,])
117
118    def SetPlmn(self, mcc, mnc):
119        # Doing this appears to set the WCDMa versions as well
120        self.c.SendStanza([
121            'CALL:MCCode %s' % mcc,
122            'CALL:MNCode %s' % mnc,])
123
124    def SetPower(self, dbm):
125        if dbm <= cellular.Power.OFF :
126            self.c.SendStanza([
127                'CALL:CELL:POWer:STATe off',])
128        else:
129            self.c.SendStanza([
130                'CALL:CELL:POWer %s' % dbm,])
131
132    def SetTechnology(self, technology):
133        # TODO(rochberg): Check that we're not already in chosen tech for
134        # speed boost
135
136        # Print out a helpful message on a key error.
137        try:
138            self.format = ConfigDictionaries.TECHNOLOGY_TO_FORMAT[technology]
139        except KeyError:
140            raise KeyError('%s not in %s ' %
141                           (technology,
142                            ConfigDictionaries.TECHNOLOGY_TO_FORMAT))
143        self.technology = technology
144
145        self.c.SimpleVerify('SYSTem:APPLication:FORMat', self.format)
146        # Setting the format will start the call box, we need to stop it so we
147        # can configure the new format.
148        self.Stop()
149        self.c.SendStanza(
150            ConfigDictionaries.TECHNOLOGY_TO_CONFIG_STANZA.get(technology, []))
151
152    def SetUeDnsV4(self, dns1, dns2):
153        """Set the DNS values provided to the UE.  Emulator must be stopped."""
154        stanza = ['CALL:MS:DNSServer:PRIMary:IP:ADDRess "%s"' % dns1]
155        if dns2:
156            stanza.append('CALL:MS:DNSServer:SECondary:IP:ADDRess "%s"' % dns2)
157        self.c.SendStanza(stanza)
158
159    def SetUeIpV4(self, ip1, ip2=None):
160        """
161        Set the IP addresses provided to the UE.  Emulator must be stopped.
162        """
163        stanza = ['CALL:MS:IP:ADDRess1 "%s"' % ip1]
164        if ip2:
165            stanza.append('CALL:MS:IP:ADDRess2 "%s"' % ip2)
166        self.c.SendStanza(stanza)
167
168    def Start(self):
169        self.c.SendStanza(['CALL:OPERating:MODE CALL'])
170
171    def Stop(self):
172        self.c.SendStanza(['CALL:OPERating:MODE OFF'])
173        # Make sure the call status goes to idle before continuing.
174        utils.poll_for_condition(
175            self._IsIdle,
176            timeout=cellular.DEFAULT_TIMEOUT,
177            exception=cellular_system_error.BadState(
178                '8960 did not enter IDLE state'))
179
180    def SupportedTechnologies(self):
181        return [
182            cellular.Technology.GPRS,
183            cellular.Technology.EGPRS,
184            cellular.Technology.WCDMA,
185            cellular.Technology.HSDPA,
186            cellular.Technology.HSUPA,
187            cellular.Technology.HSDUPA,
188            cellular.Technology.HSPA_PLUS,
189            cellular.Technology.CDMA_2000,
190            cellular.Technology.EVDO_1X,
191        ]
192
193    def WaitForStatusChange(self,
194                            interested=None,
195                            timeout=cellular.DEFAULT_TIMEOUT):
196        """When UE status changes (to a value in interested), return the value.
197
198        Arguments:
199            interested: if non-None, only transitions to these states will
200              cause a return
201            timeout: in seconds.
202        Returns: state
203        Raises:  cellular_system_error.InstrumentTimeout
204        """
205        start = time.time()
206        while time.time() - start <= timeout:
207            state = self.GetUeDataStatus()
208            if state in interested:
209                return state
210            time.sleep(POLL_SLEEP)
211
212        state = self.GetUeDataStatus()
213        if state in interested:
214            return state
215
216        raise cellular_system_error.InstrumentTimeout(
217            'Timed out waiting for state in %s.  State was %s' %
218                      (interested, state))
219
220def _Parse(command_sequence):
221    """Split and remove comments from a config stanza."""
222    uncommented = [re.sub(r'\s*#.*', '', line)
223                   for line in command_sequence.split('\n')]
224
225    # Return only nonempty lines
226    return [line for line in uncommented if line]
227
228
229class ConfigStanzas(object):
230    # p 22 of http://cp.literature.agilent.com/litweb/pdf/5989-5932EN.pdf
231    WCDMA_MAX = _Parse("""
232# RAB3: 64 Up/384 down
233# http://wireless.agilent.com/rfcomms/refdocs/wcdma/wcdmala_hpib_call_service.html#CACBDEAH
234CALL:UPLink:TXPower:LEVel:MAXimum 24
235CALL:SERVICE:GPRS:RAB GPRSRAB3
236""")
237
238    # p 20 of http://cp.literature.agilent.com/litweb/pdf/5989-5932EN.pdf
239    CDMA_2000_MAX = _Parse("""
240CALL:SCHannel:FORWard:DRATe BPS153600
241CALL:CELL:SOPTion:RCONfig3 SOFS33
242""")
243
244    # p 19 of http://cp.literature.agilent.com/litweb/pdf/5989-5932EN.pdf
245    EVDO_1X_MAX = _Parse("""
246CALL:CELL:CONTrol:CATTribute:ISTate:PCCCycle ATSP
247# Default data application
248CALL:APPLication:SESSion DPAPlication
249# Give DUT 100% of channel
250CALL:CELL:APPLication:ATDPackets 100
251""")
252
253    GPRS_MAX = _Parse("""
254call:bch:scel gprs
255call:pdtch:mslot:config d1u1
256call:cell:tbflow:t3192 ms1500
257""")
258
259    EGPRS_MAX = _Parse("""
260call:bch:scel egprs
261call:pdtch:mslot:config d4u1
262call:cell:tbflow:t3192 ms1500
263""")
264
265    CAT_08 = _Parse("""
266call:pow:stat ON
267call:ms:pow:targ 0
268call:cell:rlc:rees OFF
269call:hsdpa:ms:hsdschannel:cat:control:auto off
270call:hsdpa:ms:hsdschannel:cat:man 8
271call:hsdpa:service:psdata:hsdschannel:config cqiv
272call:hsdpa:service:psdata:cqi 22
273call:serv:gprs:rab PHSP
274call:serv:rbt:rab HSDP12
275call:serv:psd:srb:mapp UEDD
276call:hsup:serv:psd:edpd:ccod:max T2T4
277call:hsup:edch:tti MS10
278call:hsup:serv:psd:ergc:inf:stat Off
279""")
280
281    CAT_10 = _Parse("""
282call:pow:stat ON
283call:ms:pow:targ 0
284call:cell:rlc:rees OFF
285call:hsdpa:ms:hsdschannel:cat:control:auto off
286call:hsdpa:ms:hsdschannel:cat:man 10
287call:serv:gprs:rab PHSP
288call:serv:rbt:rab HSDP12
289call:hsdpa:service:psdata:hsdschannel:config cqiv
290call:hsdpa:service:psdata:cqi 22
291call:serv:psd:srb:mapp UEDD
292call:hsup:serv:psd:edpd:ccod:max T2T4
293call:hsup:edch:tti MS2
294call:hsup:serv:psd:ergc:inf:stat Off
295""")
296
297class ConfigDictionaries(object):
298    TECHNOLOGY_TO_FORMAT_RAW = {
299        cellular.Technology.GPRS: 'GSM/GPRS',
300        cellular.Technology.EGPRS: 'GSM/GPRS',
301
302        cellular.Technology.WCDMA: 'WCDMA',
303        cellular.Technology.HSDPA: 'WCDMA',
304        cellular.Technology.HSUPA: 'WCDMA',
305        cellular.Technology.HSDUPA: 'WCDMA',
306        cellular.Technology.HSPA_PLUS: 'WCDMA',
307
308        cellular.Technology.CDMA_2000: 'IS-2000/IS-95/AMPS',
309
310        cellular.Technology.EVDO_1X: 'IS-856',
311    }
312
313    # Put each value in "" marks to quote it for GPIB
314    TECHNOLOGY_TO_FORMAT = dict([
315        (x, '"%s"' % y) for
316        x, y in TECHNOLOGY_TO_FORMAT_RAW.iteritems()])
317
318    TECHNOLOGY_TO_CONFIG_STANZA = {
319        cellular.Technology.CDMA_2000: ConfigStanzas.CDMA_2000_MAX,
320        cellular.Technology.EVDO_1X: ConfigStanzas.EVDO_1X_MAX,
321        cellular.Technology.GPRS: ConfigStanzas.GPRS_MAX,
322        cellular.Technology.EGPRS: ConfigStanzas.EGPRS_MAX,
323        cellular.Technology.WCDMA: ConfigStanzas.WCDMA_MAX,
324        cellular.Technology.HSDPA: ConfigStanzas.CAT_08,
325        cellular.Technology.HSUPA: ConfigStanzas.CAT_08,
326        cellular.Technology.HSDUPA: ConfigStanzas.CAT_08,
327        cellular.Technology.HSPA_PLUS: ConfigStanzas.CAT_10,
328    }
329
330    # http://wireless.agilent.com/rfcomms/refdocs/
331    #        gsmgprs/prog_synch_callstategprs.html#CHDDFBAJ
332    # NB:  We have elided a few states of the GSM state machine here.
333    CALL_STATUS_DATA_TO_STATUS_GSM_GPRS = {
334        'IDLE': cellular.UeGsmDataStatus.IDLE,
335        'ATTG': cellular.UeGsmDataStatus.ATTACHING,
336        'DET': cellular.UeGsmDataStatus.DETACHING,
337        'ATT': cellular.UeGsmDataStatus.ATTACHED,
338        'STAR': cellular.UeGsmDataStatus.ATTACHING,
339        'END': cellular.UeGsmDataStatus.PDP_DEACTIVATING,
340        'TRAN': cellular.UeGsmDataStatus.PDP_ACTIVE,
341        'PDPAG': cellular.UeGsmDataStatus.PDP_ACTIVATING,
342        'PDP': cellular.UeGsmDataStatus.PDP_ACTIVE,
343        'PDPD': cellular.UeGsmDataStatus.PDP_DEACTIVATING,
344        'DCON': cellular.UeGsmDataStatus.PDP_ACTIVE,
345        'SUSP': cellular.UeGsmDataStatus.IDLE,
346    }
347
348    # http://wireless.agilent.com/rfcomms/refdocs/
349    #        wcdma/wcdma_gen_call_proc_status.html#CJADGAHG
350    CALL_STATUS_DATA_TO_STATUS_WCDMA = {
351        'IDLE': cellular.UeGsmDataStatus.IDLE,
352        'ATTG': cellular.UeGsmDataStatus.ATTACHING,
353        'DET': cellular.UeGsmDataStatus.DETACHING,
354        'OFF': cellular.UeGsmDataStatus.NONE,
355        'PDPAG': cellular.UeGsmDataStatus.PDP_ACTIVATING,
356        'PDP': cellular.UeGsmDataStatus.PDP_ACTIVE,
357        'PDPD': cellular.UeGsmDataStatus.PDP_DEACTIVATING,
358    }
359
360    # http://wireless.agilent.com/rfcomms/refdocs/
361    #        cdma2k/cdma2000_hpib_call_status.html#CJABGBCF
362    CALL_STATUS_DATA_TO_STATUS_CDMA_2000 = {
363        'OFF': cellular.UeC2kDataStatus.OFF,
364        'DORM': cellular.UeC2kDataStatus.DORMANT,
365        'DCON': cellular.UeC2kDataStatus.DATA_CONNECTED,
366    }
367
368    # http://wireless.agilent.com/rfcomms/refdocs/
369    #        1xevdo/1xevdo_hpib_call_status.html#BABCGBCD
370    CALL_STATUS_DATA_TO_STATUS_EVDO = {
371        'CCL': cellular.UeEvdoDataStatus.CONNECTION_CLOSING,
372        'CNEG': cellular.UeEvdoDataStatus.CONNECTION_NEGOTIATE,
373        'CREQ': cellular.UeEvdoDataStatus.CONNECTION_REQUEST,
374        'DCON': cellular.UeEvdoDataStatus.DATA_CONNECTED,
375        'DORM': cellular.UeEvdoDataStatus.DORMANT,
376        'HAND': cellular.UeEvdoDataStatus.HANDOFF,
377        'IDLE': cellular.UeEvdoDataStatus.IDLE,
378        'PAG': cellular.UeEvdoDataStatus.PAGING,
379        'SCL': cellular.UeEvdoDataStatus.SESSION_CLOSING,
380        'SNEG': cellular.UeEvdoDataStatus.SESSION_NEGOTIATE,
381        'SOP': cellular.UeEvdoDataStatus.SESSION_OPEN,
382        'UREQ': cellular.UeEvdoDataStatus.UATI_REQUEST,
383    }
384
385    FORMAT_TO_DATA_STATUS_TYPE = {
386        '"GSM/GPRS"': CALL_STATUS_DATA_TO_STATUS_GSM_GPRS,
387        '"WCDMA"': CALL_STATUS_DATA_TO_STATUS_WCDMA,
388        '"IS-2000/IS-95/AMPS"': CALL_STATUS_DATA_TO_STATUS_CDMA_2000,
389        '"IS-856"': CALL_STATUS_DATA_TO_STATUS_EVDO,
390    }
391