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