1#!/usr/bin/python
2# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Implement a modem proxy to talk to a ModemManager1 modem."""
6
7from autotest_lib.client.common_lib import error
8from autotest_lib.client.cros.cellular import cellular
9from autotest_lib.client.cros.cellular import mm1
10from autotest_lib.client.cros.cellular import mm1_constants
11import dbus
12import cellular_logging
13
14log = cellular_logging.SetupCellularLogging('modem1')
15
16MODEM_TIMEOUT = 60
17
18
19class Modem(object):
20    """An object which talks to a ModemManager1 modem."""
21    # MM_MODEM_GSM_ACCESS_TECH (not exported)
22    # From /usr/include/mm/mm-modem.h
23    _MM_MODEM_GSM_ACCESS_TECH_UNKNOWN = 0
24    _MM_MODEM_GSM_ACCESS_TECH_GSM = 1 << 1
25    _MM_MODEM_GSM_ACCESS_TECH_GSM_COMPACT = 1 << 2
26    _MM_MODEM_GSM_ACCESS_TECH_GPRS = 1 << 3
27    _MM_MODEM_GSM_ACCESS_TECH_EDGE = 1 << 4
28    _MM_MODEM_GSM_ACCESS_TECH_UMTS = 1 << 5
29    _MM_MODEM_GSM_ACCESS_TECH_HSDPA = 1 << 6
30    _MM_MODEM_GSM_ACCESS_TECH_HSUPA = 1 << 7
31    _MM_MODEM_GSM_ACCESS_TECH_HSPA = 1 << 8
32
33    # Mapping of modem technologies to cellular technologies
34    _ACCESS_TECH_TO_TECHNOLOGY = {
35        _MM_MODEM_GSM_ACCESS_TECH_GSM: cellular.Technology.WCDMA,
36        _MM_MODEM_GSM_ACCESS_TECH_GSM_COMPACT: cellular.Technology.WCDMA,
37        _MM_MODEM_GSM_ACCESS_TECH_GPRS: cellular.Technology.GPRS,
38        _MM_MODEM_GSM_ACCESS_TECH_EDGE: cellular.Technology.EGPRS,
39        _MM_MODEM_GSM_ACCESS_TECH_UMTS: cellular.Technology.WCDMA,
40        _MM_MODEM_GSM_ACCESS_TECH_HSDPA: cellular.Technology.HSDPA,
41        _MM_MODEM_GSM_ACCESS_TECH_HSUPA: cellular.Technology.HSUPA,
42        _MM_MODEM_GSM_ACCESS_TECH_HSPA: cellular.Technology.HSDUPA,
43    }
44
45    def __init__(self, manager, path):
46        self.manager = manager
47        self.bus = manager.bus
48        self.service = manager.service
49        self.path = path
50
51    def Modem(self):
52        obj = self.bus.get_object(self.service, self.path)
53        return dbus.Interface(obj, mm1.MODEM_INTERFACE)
54
55    def SimpleModem(self):
56        obj = self.bus.get_object(self.service, self.path)
57        return dbus.Interface(obj, mm1.MODEM_SIMPLE_INTERFACE)
58
59    def GsmModem(self):
60        obj = self.bus.get_object(self.service, self.path)
61        return dbus.Interface(obj, mm1.MODEM_MODEM3GPP_INTERFACE)
62
63    def CdmaModem(self):
64        obj = self.bus.get_object(self.service, self.path)
65        return dbus.Interface(obj, mm1.MODEM_MODEMCDMA_INTERFACE)
66
67    def Sim(self):
68        obj = self.bus.get_object(self.service, self.path)
69        return dbus.Interface(obj, mm1.SIM_INTERFACE)
70
71    def PropertiesInterface(self):
72        obj = self.bus.get_object(self.service, self.path)
73        return dbus.Interface(obj, dbus.PROPERTIES_IFACE)
74
75    def GetAll(self, iface):
76        obj_iface = self.PropertiesInterface()
77        return obj_iface.GetAll(iface)
78
79    def _GetModemInterfaces(self):
80        return [
81            mm1.MODEM_INTERFACE,
82            mm1.MODEM_SIMPLE_INTERFACE,
83            mm1.MODEM_MODEM3GPP_INTERFACE,
84            mm1.MODEM_MODEMCDMA_INTERFACE
85            ]
86
87    @staticmethod
88    def _CopyPropertiesCheckUnique(src, dest):
89        """Copies properties from |src| to |dest| and makes sure there are no
90           duplicate properties that have different values."""
91        for key, value in src.iteritems():
92            if key in dest and value != dest[key]:
93                raise KeyError('Duplicate property %s, different values '
94                               '("%s", "%s")' % (key, value, dest[key]))
95            dest[key] = value
96
97    def GetModemProperties(self):
98        """Returns all DBus Properties of all the modem interfaces."""
99        props = dict()
100        for iface in self._GetModemInterfaces():
101            try:
102                iface_props = self.GetAll(iface)
103            except dbus.exceptions.DBusException:
104                continue
105            if iface_props:
106                self._CopyPropertiesCheckUnique(iface_props, props)
107
108        try:
109            sim_obj = self.bus.get_object(self.service, props['Sim'])
110            sim_props_iface = dbus.Interface(sim_obj, dbus.PROPERTIES_IFACE)
111            sim_props = sim_props_iface.GetAll(mm1.SIM_INTERFACE)
112
113            # SIM cards may store an empty operator name or store a value
114            # different from the one obtained OTA. Rename the 'OperatorName'
115            # property obtained from the SIM card to 'SimOperatorName' in
116            # order to avoid a potential conflict with the 'OperatorName'
117            # property obtained from the Modem3gpp interface.
118            if 'OperatorName' in sim_props:
119                sim_props['SimOperatorName'] = sim_props.pop('OperatorName')
120
121            self._CopyPropertiesCheckUnique(sim_props, props)
122        except dbus.exceptions.DBusException:
123            pass
124
125        return props
126
127    def GetAccessTechnology(self):
128        """Returns the modem access technology."""
129        props = self.GetModemProperties()
130        tech = props['AccessTechnologies']
131        return Modem._ACCESS_TECH_TO_TECHNOLOGY[tech]
132
133    def GetCurrentTechnologyFamily(self):
134        """Returns the modem technology family."""
135        props = self.GetAll(mm1.MODEM_INTERFACE)
136        capabilities = props.get('SupportedCapabilities')
137        if self._IsCDMAModem(capabilities):
138            return cellular.TechnologyFamily.CDMA
139        if self._Is3GPPModem(capabilities):
140            return cellular.TechnologyFamily.UMTS
141        raise error.TestError('Invalid modem type')
142
143    def GetVersion(self):
144        """Returns the modem version information."""
145        return self.GetModemProperties()['Revision']
146
147    def _IsCDMAModem(self, capabilities):
148        return mm1_constants.MM_MODEM_CAPABILITY_CDMA_EVDO in capabilities
149
150    def _Is3GPPModem(self, capabilities):
151        for capability in capabilities:
152            if (capability &
153                    (mm1_constants.MM_MODEM_CAPABILITY_LTE |
154                     mm1_constants.MM_MODEM_CAPABILITY_LTE_ADVANCED |
155                     mm1_constants.MM_MODEM_CAPABILITY_GSM_UMTS)):
156                return True
157        return False
158
159    def _CDMAModemIsRegistered(self):
160        modem_status = self.SimpleModem().GetStatus()
161        cdma1x_state = modem_status.get(
162                'cdma-cdma1x-registration-state',
163                mm1_constants.MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
164        evdo_state = modem_status.get(
165                'cdma-evdo-registration-state',
166                mm1_constants.MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
167        return (cdma1x_state !=
168                mm1_constants.MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN or
169                evdo_state !=
170                mm1_constants.MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
171
172    def _3GPPModemIsRegistered(self):
173        modem_status = self.SimpleModem().GetStatus()
174        state = modem_status.get('m3gpp-registration-state')
175        return (state == mm1_constants.MM_MODEM_3GPP_REGISTRATION_STATE_HOME or
176                state == mm1_constants.MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING)
177
178    def ModemIsRegistered(self):
179        """Ensure that modem is registered on the network."""
180        props = self.GetAll(mm1.MODEM_INTERFACE)
181        capabilities = props.get('SupportedCapabilities')
182        if self._IsCDMAModem(capabilities):
183            return self._CDMAModemIsRegistered()
184        elif self._Is3GPPModem(capabilities):
185            return self._3GPPModemIsRegistered()
186        else:
187            raise error.TestError('Invalid modem type')
188
189    def ModemIsRegisteredUsing(self, technology):
190        """Ensure that modem is registered on the network with a technology."""
191        if not self.ModemIsRegistered():
192            return False
193
194        reported_tech = self.GetAccessTechnology()
195
196        # TODO(jglasgow): Remove this mapping.  Basestation and
197        # reported technology should be identical.
198        BASESTATION_TO_REPORTED_TECHNOLOGY = {
199            cellular.Technology.GPRS: cellular.Technology.GPRS,
200            cellular.Technology.EGPRS: cellular.Technology.GPRS,
201            cellular.Technology.WCDMA: cellular.Technology.HSDUPA,
202            cellular.Technology.HSDPA: cellular.Technology.HSDUPA,
203            cellular.Technology.HSUPA: cellular.Technology.HSDUPA,
204            cellular.Technology.HSDUPA: cellular.Technology.HSDUPA,
205            cellular.Technology.HSPA_PLUS: cellular.Technology.HSPA_PLUS
206        }
207
208        return BASESTATION_TO_REPORTED_TECHNOLOGY[technology] == reported_tech
209
210    def IsConnectingOrDisconnecting(self):
211        props = self.GetAll(mm1.MODEM_INTERFACE)
212        return props['State'] in [
213            mm1.MM_MODEM_STATE_CONNECTING,
214            mm1.MM_MODEM_STATE_DISCONNECTING
215        ]
216
217    def IsEnabled(self):
218        props = self.GetAll(mm1.MODEM_INTERFACE)
219        return props['State'] in [
220            mm1.MM_MODEM_STATE_ENABLED,
221            mm1.MM_MODEM_STATE_SEARCHING,
222            mm1.MM_MODEM_STATE_REGISTERED,
223            mm1.MM_MODEM_STATE_DISCONNECTING,
224            mm1.MM_MODEM_STATE_CONNECTING,
225            mm1.MM_MODEM_STATE_CONNECTED
226        ]
227
228    def IsDisabled(self):
229        props = self.GetAll(mm1.MODEM_INTERFACE)
230        return props['State'] == mm1.MM_MODEM_STATE_DISABLED
231
232    def Enable(self, enable, **kwargs):
233        self.Modem().Enable(enable, timeout=MODEM_TIMEOUT, **kwargs)
234
235    def Connect(self, props):
236        self.SimpleModem().Connect(props, timeout=MODEM_TIMEOUT)
237
238    def Disconnect(self):
239        self.SimpleModem().Disconnect('/', timeout=MODEM_TIMEOUT)
240
241
242class ModemManager(object):
243    """An object which talks to a ModemManager1 service."""
244
245    def __init__(self):
246        self.bus = dbus.SystemBus()
247        self.service = mm1.MODEM_MANAGER_INTERFACE
248        self.path = mm1.OMM
249        self.manager = dbus.Interface(
250            self.bus.get_object(self.service, self.path),
251            mm1.MODEM_MANAGER_INTERFACE)
252        self.objectmanager = dbus.Interface(
253            self.bus.get_object(self.service, self.path), mm1.OFDOM)
254
255    def EnumerateDevices(self):
256        devices = self.objectmanager.GetManagedObjects()
257        return devices.keys()
258
259    def GetModem(self, path):
260        return Modem(self, path)
261
262    def SetDebugLogging(self):
263        self.manager.SetLogging('DEBUG')
264