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