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 7 from autotest_lib.client.common_lib import error 8 from autotest_lib.client.cros.cellular import cellular 9 from autotest_lib.client.cros.cellular import mm1 10 from autotest_lib.client.cros.cellular import mm1_constants 11 import dbus 12 import cellular_logging 13 14 log = cellular_logging.SetupCellularLogging('modem1') 15 16 MODEM_TIMEOUT = 60 17 18 19 class 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 242 class 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