1#!/usr/bin/python
2# Copyright (c) 2013 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
6import os
7
8from autotest_lib.client.cros.cellular import cellular
9import dbus
10
11MODEM_TIMEOUT=60
12
13class Modem(object):
14    """An object which talks to a ModemManager modem."""
15    MODEM_INTERFACE = 'org.freedesktop.ModemManager.Modem'
16    SIMPLE_MODEM_INTERFACE = 'org.freedesktop.ModemManager.Modem.Simple'
17    CDMA_MODEM_INTERFACE = 'org.freedesktop.ModemManager.Modem.Cdma'
18    GSM_MODEM_INTERFACE = 'org.freedesktop.ModemManager.Modem.Gsm'
19    GOBI_MODEM_INTERFACE = 'org.chromium.ModemManager.Modem.Gobi'
20    GSM_CARD_INTERFACE = 'org.freedesktop.ModemManager.Modem.Gsm.Card'
21    GSM_SMS_INTERFACE = 'org.freedesktop.ModemManager.Modem.Gsm.SMS'
22    GSM_NETWORK_INTERFACE = 'org.freedesktop.ModemManager.Modem.Gsm.Network'
23    PROPERTIES_INTERFACE = 'org.freedesktop.DBus.Properties'
24
25    GSM_MODEM = 1
26    CDMA_MODEM = 2
27
28    NETWORK_PREFERENCE_AUTOMATIC = 0
29    NETWORK_PREFERENCE_CDMA_2000 = 1
30    NETWORK_PREFERENCE_EVDO_1X = 2
31    NETWORK_PREFERENCE_GSM = 3
32    NETWORK_PREFERENCE_WCDMA = 4
33
34    # MM_MODEM_GSM_ACCESS_TECH (not exported)
35    # From /usr/include/mm/mm-modem.h
36    _MM_MODEM_GSM_ACCESS_TECH_UNKNOWN = 0
37    _MM_MODEM_GSM_ACCESS_TECH_GSM = 1
38    _MM_MODEM_GSM_ACCESS_TECH_GSM_COMPACT = 2
39    _MM_MODEM_GSM_ACCESS_TECH_GPRS = 3
40    _MM_MODEM_GSM_ACCESS_TECH_EDGE = 4
41    _MM_MODEM_GSM_ACCESS_TECH_UMTS = 5
42    _MM_MODEM_GSM_ACCESS_TECH_HSDPA = 6
43    _MM_MODEM_GSM_ACCESS_TECH_HSUPA = 7
44    _MM_MODEM_GSM_ACCESS_TECH_HSPA = 8
45
46    # MM_MODEM_STATE (not exported)
47    # From /usr/include/mm/mm-modem.h
48    _MM_MODEM_STATE_UNKNOWN = 0
49    _MM_MODEM_STATE_DISABLED = 10
50    _MM_MODEM_STATE_DISABLING = 20
51    _MM_MODEM_STATE_ENABLING = 30
52    _MM_MODEM_STATE_ENABLED = 40
53    _MM_MODEM_STATE_SEARCHING = 50
54    _MM_MODEM_STATE_REGISTERED = 60
55    _MM_MODEM_STATE_DISCONNECTING = 70
56    _MM_MODEM_STATE_CONNECTING = 80
57    _MM_MODEM_STATE_CONNECTED = 90
58
59    # Mapping of modem technologies to cellular technologies
60    _ACCESS_TECH_TO_TECHNOLOGY = {
61        _MM_MODEM_GSM_ACCESS_TECH_GSM: cellular.Technology.WCDMA,
62        _MM_MODEM_GSM_ACCESS_TECH_GSM_COMPACT: cellular.Technology.WCDMA,
63        _MM_MODEM_GSM_ACCESS_TECH_GPRS: cellular.Technology.GPRS,
64        _MM_MODEM_GSM_ACCESS_TECH_EDGE: cellular.Technology.EGPRS,
65        _MM_MODEM_GSM_ACCESS_TECH_UMTS: cellular.Technology.WCDMA,
66        _MM_MODEM_GSM_ACCESS_TECH_HSDPA: cellular.Technology.HSDPA,
67        _MM_MODEM_GSM_ACCESS_TECH_HSUPA: cellular.Technology.HSUPA,
68        _MM_MODEM_GSM_ACCESS_TECH_HSPA: cellular.Technology.HSDUPA,
69    }
70
71    def __init__(self, manager, path):
72        self.manager = manager
73        self.bus = manager.bus
74        self.service = manager.service
75        self.path = path
76
77    def Modem(self):
78        obj = self.bus.get_object(self.service, self.path)
79        return dbus.Interface(obj, Modem.MODEM_INTERFACE)
80
81    def SimpleModem(self):
82        obj = self.bus.get_object(self.service, self.path)
83        return dbus.Interface(obj, Modem.SIMPLE_MODEM_INTERFACE)
84
85    def CdmaModem(self):
86        obj = self.bus.get_object(self.service, self.path)
87        return dbus.Interface(obj, Modem.CDMA_MODEM_INTERFACE)
88
89    def GobiModem(self):
90        obj = self.bus.get_object(self.service, self.path)
91        return dbus.Interface(obj, Modem.GOBI_MODEM_INTERFACE)
92
93    def GsmModem(self):
94        obj = self.bus.get_object(self.service, self.path)
95        return dbus.Interface(obj, Modem.GSM_MODEM_INTERFACE)
96
97    def GsmCard(self):
98        obj = self.bus.get_object(self.service, self.path)
99        return dbus.Interface(obj, Modem.GSM_CARD_INTERFACE)
100
101    def GsmSms(self):
102        obj = self.bus.get_object(self.service, self.path)
103        return dbus.Interface(obj, Modem.GSM_SMS_INTERFACE)
104
105    def GsmNetwork(self):
106        obj = self.bus.get_object(self.service, self.path)
107        return dbus.Interface(obj, Modem.GSM_NETWORK_INTERFACE)
108
109    def GetAll(self, iface):
110        obj = self.bus.get_object(self.service, self.path)
111        obj_iface = dbus.Interface(obj, Modem.PROPERTIES_INTERFACE)
112        return obj_iface.GetAll(iface)
113
114    def _GetModemInterfaces(self):
115        return [
116            Modem.MODEM_INTERFACE,
117            Modem.SIMPLE_MODEM_INTERFACE,
118            Modem.CDMA_MODEM_INTERFACE,
119            Modem.GSM_MODEM_INTERFACE,
120            Modem.GSM_NETWORK_INTERFACE,
121            Modem.GOBI_MODEM_INTERFACE]
122
123
124    @staticmethod
125    def _CopyPropertiesCheckUnique(src, dest):
126        """Copies properties from |src| to |dest| and makes sure there are no
127           duplicate properties that have different values."""
128        for key, value in src.iteritems():
129            if key in dest and value != dest[key]:
130                raise KeyError('Duplicate property %s, different values '
131                               '("%s", "%s")' % (key, value, dest[key]))
132            dest[key] = value
133
134    def GetModemProperties(self):
135        """Returns all DBus Properties of all the modem interfaces."""
136        props = dict()
137        for iface in self._GetModemInterfaces():
138            try:
139                iface_props = self.GetAll(iface)
140            except dbus.exceptions.DBusException:
141                continue
142            if iface_props:
143                self._CopyPropertiesCheckUnique(iface_props, props)
144
145        status = self.SimpleModem().GetStatus()
146        if 'meid' in status:
147            props['Meid'] = status['meid']
148        if 'imei' in status:
149            props['Imei'] = status['imei']
150        if 'imsi' in status:
151            props['Imsi'] = status['imsi']
152        if 'esn' in status:
153            props['Esn'] = status['esn']
154
155        # Operator information is not exposed through the properties interface.
156        # Try to get it directly. This may fail on a disabled modem.
157        try:
158            network = self.GsmNetwork()
159            _, operator_code, operator_name = network.GetRegistrationInfo()
160            if operator_code:
161                props['OperatorCode'] = operator_code
162            if operator_name:
163                props['OperatorName'] = operator_name
164        except dbus.DBusException:
165            pass
166
167        return props
168
169    def GetAccessTechnology(self):
170        """Returns the modem access technology."""
171        props = self.GetModemProperties()
172        tech = props.get('AccessTechnology')
173        return Modem._ACCESS_TECH_TO_TECHNOLOGY[tech]
174
175    def GetCurrentTechnologyFamily(self):
176        """Returns the modem technology family."""
177        try:
178            self.GetAll(Modem.GSM_CARD_INTERFACE)
179            return cellular.TechnologyFamily.UMTS
180        except dbus.exceptions.DBusException:
181            return cellular.TechnologyFamily.CDMA
182
183    def GetVersion(self):
184        """Returns the modem version information."""
185        return self.Modem().GetInfo()[2]
186
187    def _GetRegistrationState(self):
188        try:
189            network = self.GsmNetwork()
190            (status, unused_code, unused_name) = network.GetRegistrationInfo()
191            # TODO(jglasgow): HOME - 1, ROAMING - 5
192            return status == 1 or status == 5
193        except dbus.exceptions.DBusException:
194            pass
195
196        cdma_modem = self.CdmaModem()
197        try:
198            cdma, evdo = cdma_modem.GetRegistrationState()
199            return cdma > 0 or evdo > 0
200        except dbus.exceptions.DBusException:
201            pass
202
203        return False
204
205    def ModemIsRegistered(self):
206        """Ensure that modem is registered on the network."""
207        return self._GetRegistrationState()
208
209    def ModemIsRegisteredUsing(self, technology):
210        """Ensure that modem is registered on the network with a technology."""
211        if not self.ModemIsRegistered():
212            return False
213
214        reported_tech = self.GetAccessTechnology()
215
216        # TODO(jglasgow): Remove this mapping.  Basestation and
217        # reported technology should be identical.
218        BASESTATION_TO_REPORTED_TECHNOLOGY = {
219            cellular.Technology.GPRS: cellular.Technology.GPRS,
220            cellular.Technology.EGPRS: cellular.Technology.EGPRS,
221            cellular.Technology.WCDMA: cellular.Technology.HSDUPA,
222            cellular.Technology.HSDPA: cellular.Technology.HSDUPA,
223            cellular.Technology.HSUPA: cellular.Technology.HSDUPA,
224            cellular.Technology.HSDUPA: cellular.Technology.HSDUPA,
225            cellular.Technology.HSPA_PLUS: cellular.Technology.HSPA_PLUS
226        }
227
228        return BASESTATION_TO_REPORTED_TECHNOLOGY[technology] == reported_tech
229
230    def IsConnectingOrDisconnecting(self):
231        props = self.GetAll(Modem.MODEM_INTERFACE)
232        return props['State'] in [
233            Modem._MM_MODEM_STATE_CONNECTING,
234            Modem._MM_MODEM_STATE_DISCONNECTING
235        ]
236
237    def IsEnabled(self):
238        props = self.GetAll(Modem.MODEM_INTERFACE)
239        return props['Enabled']
240
241    def IsDisabled(self):
242        return not self.IsEnabled()
243
244    def Enable(self, enable, **kwargs):
245        self.Modem().Enable(enable, timeout=MODEM_TIMEOUT, **kwargs)
246
247    def Connect(self, props):
248        self.SimpleModem().Connect(props, timeout=MODEM_TIMEOUT)
249
250    def Disconnect(self):
251        self.Modem().Disconnect(timeout=MODEM_TIMEOUT)
252
253
254class ModemManager(object):
255    """An object which talks to a ModemManager service."""
256    INTERFACE = 'org.freedesktop.ModemManager'
257
258    def __init__(self, provider=None):
259        self.bus = dbus.SystemBus()
260        self.provider = provider or os.getenv('MMPROVIDER') or 'org.chromium'
261        self.service = '%s.ModemManager' % self.provider
262        self.path = '/%s/ModemManager' % (self.provider.replace('.', '/'))
263        self.manager = dbus.Interface(
264            self.bus.get_object(self.service, self.path),
265            ModemManager.INTERFACE)
266
267    def EnumerateDevices(self):
268        return self.manager.EnumerateDevices()
269
270    def GetModem(self, path):
271        return Modem(self, path)
272
273    def SetDebugLogging(self):
274        self.manager.SetLogging('debug')
275