1# Lint as: python2, python3 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 6import dbus 7import dbus.types 8import logging 9 10from autotest_lib.client.cros.cellular.pseudomodem import modem 11from autotest_lib.client.cros.cellular.pseudomodem import pm_constants 12from autotest_lib.client.cros.cellular.pseudomodem import pm_errors 13from autotest_lib.client.cros.cellular.pseudomodem import utils 14 15from autotest_lib.client.cros.cellular import mm1_constants 16 17def SubscriptionStateToPco(state): 18 """ 19 Takes an old SubscriptionState enum and returns a Pco that will be 20 interpreted as that subscription state. 21 22 @param state: see mm1_constants.MM_MODEM_3GPP_SUBSCRIPTION_STATE_* 23 """ 24 25 pco_data = '\x27\x08\x00\xFF\x00\x04\x13\x01\x84' 26 if state == mm1_constants.MM_MODEM_3GPP_SUBSCRIPTION_STATE_UNKNOWN: 27 pco_data += '\xFF' 28 elif state == mm1_constants.MM_MODEM_3GPP_SUBSCRIPTION_STATE_UNPROVISIONED: 29 pco_data += '\x05' 30 elif state == mm1_constants.MM_MODEM_3GPP_SUBSCRIPTION_STATE_PROVISIONED: 31 pco_data += '\x00' 32 elif state == mm1_constants.MM_MODEM_3GPP_SUBSCRIPTION_STATE_OUT_OF_DATA: 33 pco_data += '\x03' 34 35 return dbus.types.Struct( 36 [dbus.types.UInt32(1), 37 dbus.types.Boolean(True), 38 dbus.types.ByteArray(pco_data)], 39 signature='ubay') 40 41 42class Modem3gpp(modem.Modem): 43 """ 44 Pseudomodem implementation of the 45 org.freedesktop.ModemManager1.Modem.Modem3gpp and 46 org.freedesktop.ModemManager1.Modem.Simple interfaces. This class provides 47 access to specific actions that may be performed in modems with 3GPP 48 capabilities. 49 50 """ 51 52 IMEI = '00112342342123' 53 54 class GsmNetwork(object): 55 """ 56 GsmNetwork stores the properties of a 3GPP network that can be 57 discovered during a network scan. 58 59 """ 60 def __init__(self, 61 operator_long, 62 operator_short, 63 operator_code, 64 status, 65 access_technology): 66 self.status = status 67 self.operator_long = operator_long 68 self.operator_short = operator_short 69 self.operator_code = operator_code 70 self.access_technology = access_technology 71 72 73 def ToScanDictionary(self): 74 """ 75 @returns: Dictionary containing operator data as defined by 76 org.freedesktop.ModemManager1.Modem.Modem3gpp.Scan. 77 78 """ 79 return { 80 'status': dbus.types.UInt32(self.status), 81 'operator-long': self.operator_long, 82 'operator-short': self.operator_short, 83 'operator-code': self.operator_code, 84 'access-technology': dbus.types.UInt32(self.access_technology), 85 } 86 87 88 def __init__(self, 89 state_machine_factory=None, 90 bus=None, 91 device='pseudomodem0', 92 index=0, 93 roaming_networks=None, 94 config=None): 95 modem.Modem.__init__(self, 96 state_machine_factory, 97 bus=bus, 98 device=device, 99 roaming_networks=roaming_networks, 100 config=config) 101 102 self._scanned_networks = {} 103 self._cached_pco = dbus.types.Array([], "(ubay)") 104 105 106 def _InitializeProperties(self): 107 ip = modem.Modem._InitializeProperties(self) 108 props = ip[mm1_constants.I_MODEM] 109 props3gpp = self._GetDefault3GPPProperties() 110 if props3gpp: 111 ip[mm1_constants.I_MODEM_3GPP] = props3gpp 112 props['SupportedCapabilities'] = [ 113 dbus.types.UInt32(mm1_constants.MM_MODEM_CAPABILITY_GSM_UMTS), 114 dbus.types.UInt32(mm1_constants.MM_MODEM_CAPABILITY_LTE), 115 dbus.types.UInt32( 116 mm1_constants.MM_MODEM_CAPABILITY_GSM_UMTS | 117 mm1_constants.MM_MODEM_CAPABILITY_LTE) 118 ] 119 props['CurrentCapabilities'] = dbus.types.UInt32( 120 mm1_constants.MM_MODEM_CAPABILITY_GSM_UMTS | 121 mm1_constants.MM_MODEM_CAPABILITY_LTE) 122 props['MaxBearers'] = dbus.types.UInt32(3) 123 props['MaxActiveBearers'] = dbus.types.UInt32(2) 124 props['EquipmentIdentifier'] = self.IMEI 125 props['AccessTechnologies'] = dbus.types.UInt32(( 126 mm1_constants.MM_MODEM_ACCESS_TECHNOLOGY_GSM | 127 mm1_constants.MM_MODEM_ACCESS_TECHNOLOGY_UMTS)) 128 props['SupportedModes'] = [ 129 dbus.types.Struct( 130 [dbus.types.UInt32(mm1_constants.MM_MODEM_MODE_3G | 131 mm1_constants.MM_MODEM_MODE_4G), 132 dbus.types.UInt32(mm1_constants.MM_MODEM_MODE_4G)], 133 signature='uu') 134 ] 135 props['CurrentModes'] = props['SupportedModes'][0] 136 props['SupportedBands'] = [ 137 dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_EGSM), 138 dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_DCS), 139 dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_PCS), 140 dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_G850), 141 dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_U2100), 142 dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_U1800), 143 dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_U17IV), 144 dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_U800), 145 dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_U850) 146 ] 147 props['CurrentBands'] = [ 148 dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_EGSM), 149 dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_DCS), 150 dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_PCS), 151 dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_G850), 152 dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_U2100), 153 dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_U800), 154 dbus.types.UInt32(mm1_constants.MM_MODEM_BAND_U850) 155 ] 156 return ip 157 158 159 def _GetDefault3GPPProperties(self): 160 if not self.sim or self.sim.locked: 161 return None 162 return { 163 'Imei' : self.IMEI, 164 'RegistrationState' : ( 165 dbus.types.UInt32( 166 mm1_constants.MM_MODEM_3GPP_REGISTRATION_STATE_IDLE)), 167 'OperatorCode' : '', 168 'OperatorName' : '', 169 'EnabledFacilityLocks' : ( 170 dbus.types.UInt32(self.sim.enabled_locks)), 171 'Pco': dbus.types.Array([], "(ubay)"), 172 } 173 174 175 def SyncScan(self): 176 """ The synchronous implementation of |Scan| for this class. """ 177 state = self.Get(mm1_constants.I_MODEM, 'State') 178 if state < mm1_constants.MM_MODEM_STATE_ENABLED: 179 raise pm_errors.MMCoreError( 180 pm_errors.MMCoreError.WRONG_STATE, 181 'Modem not enabled, cannot scan for networks.') 182 183 sim_path = self.Get(mm1_constants.I_MODEM, 'Sim') 184 if not self.sim: 185 assert sim_path == mm1_constants.ROOT_PATH 186 raise pm_errors.MMMobileEquipmentError( 187 pm_errors.MMMobileEquipmentError.SIM_NOT_INSERTED, 188 'Cannot scan for networks because no SIM is inserted.') 189 assert sim_path != mm1_constants.ROOT_PATH 190 191 # TODO(armansito): check here for SIM lock? 192 193 scanned = [network.ToScanDictionary() 194 for network in self.roaming_networks] 195 196 # get home network 197 sim_props = self.sim.GetAll(mm1_constants.I_SIM) 198 scanned.append({ 199 'status': dbus.types.UInt32( 200 mm1_constants.MM_MODEM_3GPP_NETWORK_AVAILABILITY_AVAILABLE), 201 'operator-long': sim_props['OperatorName'], 202 'operator-short': sim_props['OperatorName'], 203 'operator-code': sim_props['OperatorIdentifier'], 204 'access-technology': dbus.types.UInt32(self.sim.access_technology) 205 }) 206 207 self._scanned_networks = ( 208 {network['operator-code']: network for network in scanned}) 209 return scanned 210 211 212 def AssignPco(self, pco): 213 """ 214 Stores the given value so that it is shown as the value of Pco when 215 the modem is in a registered state. 216 217 Always prefer this method over calling "Set" directly if the PCO value 218 should be cached. 219 220 Note: See testing.Testing.UpdatePco, which allows calling this method 221 over D-Bus. 222 223 @param pco_value: D-Bus struct containing the PCO value to remember. 224 225 """ 226 self._cached_pco = pco 227 self.UpdatePco() 228 229 230 def UpdatePco(self): 231 """ 232 Updates the current PCO value based on the registration state. 233 234 """ 235 if not mm1_constants.I_MODEM_3GPP in self._properties: 236 return 237 state = self.Get(mm1_constants.I_MODEM_3GPP, 'RegistrationState') 238 if (state == mm1_constants.MM_MODEM_3GPP_REGISTRATION_STATE_HOME or 239 state == mm1_constants.MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING): 240 new_pco_value = self._cached_pco 241 else: 242 new_pco_value = dbus.types.Array([], "(ubay)") 243 self.Set(mm1_constants.I_MODEM_3GPP, 'Pco', new_pco_value) 244 245 246 def AssignSubscriptionState(self, 247 registered_subscription_state): 248 """ 249 Caches the given subscription states and updates the actual 250 |SubscriptionState| property depending on the |RegistrationState|. 251 252 @param unregistered_subscription_state: This subscription state is 253 returned when the modem is not registered on a network. 254 @param registered_subscription_state: This subscription state is 255 returned when the modem is registered on a network. 256 257 """ 258 new_pco = SubscriptionStateToPco(registered_subscription_state) 259 self.AssignPco([new_pco]) 260 self.UpdatePco() 261 262 263 def UpdateLockStatus(self): 264 """ 265 Overloads superclass implementation. Also updates 266 'EnabledFacilityLocks' if 3GPP properties are exposed. 267 268 """ 269 modem.Modem.UpdateLockStatus(self) 270 if mm1_constants.I_MODEM_3GPP in self._properties: 271 self.SetUInt32(mm1_constants.I_MODEM_3GPP, 272 'EnabledFacilityLocks', 273 self.sim.enabled_locks) 274 275 276 def SetSIM(self, sim): 277 """ 278 Overrides modem.Modem.SetSIM. Once the SIM has been assigned, attempts 279 to expose 3GPP properties if SIM readable. 280 281 @param sim: An instance of sim.SIM 282 Emits: 283 PropertiesChanged 284 285 """ 286 modem.Modem.SetSIM(self, sim) 287 self.Expose3GPPProperties() 288 289 290 def Expose3GPPProperties(self): 291 """ 292 A call to this method will attempt to expose 3GPP properties if there 293 is a current SIM and is unlocked. 294 295 """ 296 props = self._GetDefault3GPPProperties() 297 if props: 298 self.SetAll(mm1_constants.I_MODEM_3GPP, props) 299 300 301 def SetRegistrationState(self, state): 302 """ 303 Sets the 'RegistrationState' property. 304 305 @param state: An MMModem3gppRegistrationState value. 306 Emits: 307 PropertiesChanged 308 309 """ 310 self.SetUInt32(mm1_constants.I_MODEM_3GPP, 'RegistrationState', state) 311 self.UpdatePco() 312 313 314 @property 315 def scanned_networks(self): 316 """ 317 @returns: Dictionary containing the result of the most recent network 318 scan, where the keys are the operator code. 319 320 """ 321 return self._scanned_networks 322 323 324 @utils.log_dbus_method(return_cb_arg='return_cb', raise_cb_arg='raise_cb') 325 @dbus.service.method(mm1_constants.I_MODEM_3GPP, in_signature='s', 326 async_callbacks=('return_cb', 'raise_cb')) 327 def Register(self, operator_id, return_cb=None, raise_cb=None): 328 """ 329 Request registration with a given modem network. 330 331 @param operator_id: The operator ID to register. An empty string can be 332 used to register to the home network. 333 @param return_cb: Async success callback. 334 @param raise_cb: Async error callback. 335 336 """ 337 logging.info('Modem3gpp.Register: %s', operator_id) 338 339 # Check if we're already registered with the given network. 340 if (self.Get(mm1_constants.I_MODEM_3GPP, 'OperatorCode') == 341 operator_id or 342 ((not operator_id and self.Get(mm1_constants.I_MODEM, 'State') >= 343 mm1_constants.MM_MODEM_STATE_REGISTERED))): 344 message = 'Already registered.' 345 logging.info(message) 346 raise pm_errors.MMCoreError(pm_errors.MMCoreError.FAILED, message) 347 348 if (self.Get(mm1_constants.I_MODEM, 'State') < 349 mm1_constants.MM_MODEM_STATE_ENABLED): 350 message = 'Cannot register the modem if not enabled.' 351 logging.info(message) 352 raise pm_errors.MMCoreError(pm_errors.MMCoreError.FAILED, message) 353 354 self.CancelAllStateMachines() 355 356 def _Reregister(): 357 if (self.Get(mm1_constants.I_MODEM, 'State') == 358 mm1_constants.MM_MODEM_STATE_REGISTERED): 359 self.UnregisterWithNetwork() 360 self.RegisterWithNetwork(operator_id, return_cb, raise_cb) 361 362 if (self.Get(mm1_constants.I_MODEM, 'State') == 363 mm1_constants.MM_MODEM_STATE_CONNECTED): 364 self.Disconnect(mm1_constants.ROOT_PATH, _Reregister, raise_cb) 365 else: 366 _Reregister() 367 368 369 def SetRegistered(self, operator_code, operator_name): 370 """ 371 Sets the modem to be registered with the give network. Sets the Modem 372 and Modem3gpp registration states. 373 374 @param operator_code: The operator code that should be displayed by 375 the modem. 376 @param operator_name: The operator name that should be displayed by 377 the modem. 378 379 """ 380 if operator_code: 381 assert self.sim 382 assert (self.Get(mm1_constants.I_MODEM, 'Sim') != 383 mm1_constants.ROOT_PATH) 384 if (operator_code == 385 self.sim.Get(mm1_constants.I_SIM, 'OperatorIdentifier')): 386 state = mm1_constants.MM_MODEM_3GPP_REGISTRATION_STATE_HOME 387 else: 388 state = mm1_constants.MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING 389 else: 390 state = mm1_constants.MM_MODEM_3GPP_REGISTRATION_STATE_HOME 391 392 logging.info('Modem3gpp.Register: Setting registration state to %s.', 393 mm1_constants.RegistrationStateToString(state)) 394 self.SetRegistrationState(state) 395 logging.info('Modem3gpp.Register: Setting state to REGISTERED.') 396 self.ChangeState(mm1_constants.MM_MODEM_STATE_REGISTERED, 397 mm1_constants.MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED) 398 self.Set(mm1_constants.I_MODEM_3GPP, 'OperatorCode', operator_code) 399 self.Set(mm1_constants.I_MODEM_3GPP, 'OperatorName', operator_name) 400 401 402 @utils.log_dbus_method(return_cb_arg='return_cb', raise_cb_arg='raise_cb') 403 @dbus.service.method(mm1_constants.I_MODEM_3GPP, out_signature='aa{sv}', 404 async_callbacks=('return_cb', 'raise_cb')) 405 def Scan(self, return_cb, raise_cb): 406 """ 407 Scan for available networks. 408 409 @param return_cb: This function is called with the result. 410 @param raise_cb: This function may be called with error. 411 @returns: An array of dictionaries with each array element describing a 412 mobile network found in the scan. See the ModemManager reference 413 manual for the list of keys that may be included in the returned 414 dictionary. 415 416 """ 417 scan_result = self.SyncScan() 418 return_cb(scan_result) 419 420 421 def RegisterWithNetwork( 422 self, operator_id="", return_cb=None, raise_cb=None): 423 """ 424 Overridden from superclass. 425 426 @param operator_id: See superclass documentation. 427 @param return_cb: See superclass documentation. 428 @param raise_cb: See superclass documentation. 429 430 """ 431 machine = self._state_machine_factory.CreateMachine( 432 pm_constants.STATE_MACHINE_REGISTER, 433 self, 434 operator_id, 435 return_cb, 436 raise_cb) 437 machine.Start() 438 439 440 def UnregisterWithNetwork(self): 441 """ 442 Overridden from superclass. 443 444 """ 445 logging.info('Modem3gpp.UnregisterWithHomeNetwork') 446 logging.info('Setting registration state to IDLE.') 447 self.SetRegistrationState( 448 mm1_constants.MM_MODEM_3GPP_REGISTRATION_STATE_IDLE) 449 logging.info('Setting state to ENABLED.') 450 self.ChangeState(mm1_constants.MM_MODEM_STATE_ENABLED, 451 mm1_constants.MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED) 452 self.Set(mm1_constants.I_MODEM_3GPP, 'OperatorName', '') 453 self.Set(mm1_constants.I_MODEM_3GPP, 'OperatorCode', '') 454 455 456 # Inherited from modem_simple.ModemSimple. 457 @utils.log_dbus_method(return_cb_arg='return_cb', raise_cb_arg='raise_cb') 458 def Connect(self, properties, return_cb, raise_cb): 459 """ 460 Overriden from superclass. 461 462 @param properties 463 @param return_cb 464 @param raise_cb 465 466 """ 467 logging.info('Connect') 468 machine = self._state_machine_factory.CreateMachine( 469 pm_constants.STATE_MACHINE_CONNECT, 470 self, 471 properties, 472 return_cb, 473 raise_cb) 474 machine.Start() 475 476 477 # Inherited from modem_simple.ModemSimple. 478 @utils.log_dbus_method(return_cb_arg='return_cb', raise_cb_arg='raise_cb') 479 def Disconnect(self, bearer_path, return_cb, raise_cb, *return_cb_args): 480 """ 481 Overriden from superclass. 482 483 @param bearer_path 484 @param return_cb 485 @param raise_cb 486 @param return_cb_args 487 488 """ 489 logging.info('Disconnect: %s', bearer_path) 490 machine = self._state_machine_factory.CreateMachine( 491 pm_constants.STATE_MACHINE_DISCONNECT, 492 self, 493 bearer_path, 494 return_cb, 495 raise_cb, 496 return_cb_args) 497 machine.Start() 498 499 500 # Inherited from modem_simple.ModemSimple. 501 @utils.log_dbus_method() 502 def GetStatus(self): 503 """ 504 Overriden from superclass. 505 506 """ 507 modem_props = self.GetAll(mm1_constants.I_MODEM) 508 m3gpp_props = self.GetAll(mm1_constants.I_MODEM_3GPP) 509 retval = {} 510 retval['state'] = modem_props['State'] 511 if retval['state'] >= mm1_constants.MM_MODEM_STATE_REGISTERED: 512 retval['signal-quality'] = modem_props['SignalQuality'][0] 513 retval['bands'] = modem_props['CurrentBands'] 514 retval['access-technology'] = self.sim.access_technology 515 retval['m3gpp-registration-state'] = \ 516 m3gpp_props['RegistrationState'] 517 retval['m3gpp-operator-code'] = m3gpp_props['OperatorCode'] 518 retval['m3gpp-operator-name'] = m3gpp_props['OperatorName'] 519 return retval 520 # TODO(armansito): implement 521 # org.freedesktop.ModemManager1.Modem.Modem3gpp.Ussd, if needed 522 # (in a separate class?) 523