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"""An implementation of the ModemManager1 DBUS interface. 6 7This modem mimics a GSM (eventually LTE & CDMA) modem and allows a 8user to test shill and UI behaviors when a supported SIM is inserted 9into the device. Invoked with the proper flags it can test that SMS 10messages are deliver to the UI. 11 12This program creates a virtual network interface to simulate the 13network interface of a modem. It depends on modemmanager-next to 14set the dbus permissions properly. 15 16TODO: 17 * Use more appropriate values for many of the properties 18 * Support all ModemManager1 interfaces 19 * implement LTE modems 20 * implement CDMA modems 21""" 22 23from __future__ import absolute_import 24from __future__ import division 25from __future__ import print_function 26 27from optparse import OptionParser 28import logging 29import os 30import signal 31import string 32import subprocess 33import sys 34import time 35 36import dbus 37from dbus.exceptions import DBusException 38import dbus.mainloop.glib 39import dbus.service 40from dbus.types import Int32 41from dbus.types import ObjectPath 42from dbus.types import Struct 43from dbus.types import UInt32 44import glib 45# AU tests use ToT client code, but ToT -3 client version. 46try: 47 from gi.repository import GObject 48except ImportError: 49 import gobject as GObject 50import mm1 51from six.moves import range 52 53 54# Miscellaneous delays to simulate a modem 55DEFAULT_CONNECT_DELAY_MS = 1500 56 57DEFAULT_CARRIER = 'att' 58 59 60class DBusObjectWithProperties(dbus.service.Object): 61 """Implements the org.freedesktop.DBus.Properties interface. 62 63 Implements the org.freedesktop.DBus.Properties interface, specifically 64 the Get and GetAll methods. Class which inherit from this class must 65 implement the InterfacesAndProperties function which will return a 66 dictionary of all interfaces and the properties defined on those interfaces. 67 """ 68 69 def __init__(self, bus, path): 70 dbus.service.Object.__init__(self, bus, path) 71 72 @dbus.service.method(dbus.PROPERTIES_IFACE, 73 in_signature='ss', out_signature='v') 74 def Get(self, interface, property_name, *args, **kwargs): 75 """Returns: The value of property_name on interface.""" 76 logging.info('%s: Get %s, %s', self.path, interface, property_name) 77 interfaces = self.InterfacesAndProperties() 78 properties = interfaces.get(interface, None) 79 if property_name in properties: 80 return properties[property_name] 81 raise dbus.exceptions.DBusException( 82 mm1.MODEM_MANAGER_INTERFACE + '.UnknownProperty', 83 'Property %s not defined for interface %s' % 84 (property_name, interface)) 85 86 @dbus.service.method(dbus.PROPERTIES_IFACE, 87 in_signature='s', out_signature='a{sv}') 88 def GetAll(self, interface, *args, **kwargs): 89 """Returns: A dictionary. The properties on interface.""" 90 logging.info('%s: GetAll %s', self.path, interface) 91 interfaces = self.InterfacesAndProperties() 92 properties = interfaces.get(interface, None) 93 if properties is not None: 94 return properties 95 raise dbus.exceptions.DBusException( 96 mm1.MODEM_MANAGER_INTERFACE + '.UnknownInterface', 97 'Object does not implement the %s interface' % interface) 98 99 def InterfacesAndProperties(self): 100 """Subclasses must implement this function. 101 102 Returns: 103 A dictionary of interfaces where the values are dictionaries 104 of dbus properties. 105 """ 106 pass 107 108 109class SIM(DBusObjectWithProperties): 110 """SIM Object. 111 112 Mock SIM Card and the typical information it might contain. 113 SIM cards of different carriers can be created by providing 114 the MCC, MNC, operator name, imsi, and msin. SIM objects are 115 passed to the Modem during Modem initialization. 116 """ 117 118 DEFAULT_MCC = '310' 119 DEFAULT_MNC = '090' 120 DEFAULT_OPERATOR = 'AT&T' 121 DEFAULT_MSIN = '1234567890' 122 DEFAULT_IMSI = '888999111' 123 MCC_LIST = { 124 'us': '310', 125 'de': '262', 126 'es': '214', 127 'fr': '208', 128 'gb': '234', 129 'it': '222', 130 'nl': '204', 131 } 132 CARRIERS = { 133 'att': ('us', '090', 'AT&T'), 134 'tmobile': ('us', '026', 'T-Mobile'), 135 'simyo': ('de', '03', 'simyo'), 136 'movistar': ('es', '07', 'Movistar'), 137 'sfr': ('fr', '10', 'SFR'), 138 'three': ('gb', '20', '3'), 139 'threeita': ('it', '99', '3ITA'), 140 'kpn': ('nl', '08', 'KPN') 141 } 142 143 def __init__(self, 144 manager, 145 mcc_country='us', 146 mnc=DEFAULT_MNC, 147 operator_name=DEFAULT_OPERATOR, 148 msin=DEFAULT_MSIN, 149 imsi=None, 150 mcc=None, 151 name='/Sim/0'): 152 self.manager = manager 153 self.name = name 154 self.path = manager.path + name 155 self.mcc = mcc or SIM.MCC_LIST.get(mcc_country, '000') 156 self.mnc = mnc 157 self.operator_name = operator_name 158 self.msin = msin 159 self.imsi = imsi or (self.mcc + self.mnc + SIM.DEFAULT_IMSI) 160 DBusObjectWithProperties.__init__(self, manager.bus, self.path) 161 162 @staticmethod 163 def FromCarrier(carrier, manager): 164 """Creates a SIM card object for a given carrier.""" 165 args = SIM.CARRIERS.get(carrier, []) 166 return SIM(manager, *args) 167 168 def Properties(self): 169 return { 170 'SimIdentifier': self.msin, 171 'Imsi': self.imsi, 172 'OperatorIdentifier': self.mcc + self.mnc, 173 'OperatorName': self.operator_name 174 } 175 176 def InterfacesAndProperties(self): 177 return {mm1.SIM_INTERFACE: self.Properties()} 178 179class SMS(DBusObjectWithProperties): 180 """SMS Object. 181 182 Mock SMS message. 183 """ 184 185 def __init__(self, manager, name='/SMS/0', text='test', 186 number='123', timestamp='12:00', smsc=''): 187 self.manager = manager 188 self.name = name 189 self.path = manager.path + name 190 self.text = text or 'test sms at %s' % name 191 self.number = number 192 self.timestamp = timestamp 193 self.smsc = smsc 194 DBusObjectWithProperties.__init__(self, manager.bus, self.path) 195 196 def Properties(self): 197 # TODO(jglasgow): State, Validity, Class, Storage are also defined 198 return { 199 'Text': self.text, 200 'Number': self.number, 201 'Timestamp': self.timestamp, 202 'SMSC': self.smsc 203 } 204 205 def InterfacesAndProperties(self): 206 return {mm1.SMS_INTERFACE: self.Properties()} 207 208 209class PseudoNetworkInterface(object): 210 """A Pseudo network interface. 211 212 This uses a pair of network interfaces and dnsmasq to simulate the 213 network device normally associated with a modem. 214 """ 215 216 # Any interface that shill manages will get its own routing 217 # table. Routes added to the main routing table with RTPROT_BOOT (the 218 # default proto value) will be sent to the corresponding interface's 219 # routing table. We want to prevent that in this case, so we use 220 # proto 5, as shill currently ignores proto values greater than 4. 221 ROUTE_PROTO = 'proto 5' 222 223 def __init__(self, interface, base): 224 self.interface = interface 225 self.peer = self.interface + 'p' 226 self.base = base 227 self.lease_file = '/tmp/dnsmasq.%s.leases' % self.interface 228 self.dnsmasq = None 229 230 def __enter__(self): 231 """Make usable with "with" statement.""" 232 self.CreateInterface() 233 return self 234 235 def __exit__(self, exception, value, traceback): 236 """Make usable with "with" statement.""" 237 self.DestroyInterface() 238 return False 239 240 def CreateInterface(self): 241 """Creates a virtual interface. 242 243 Creates the virtual interface self.interface as well as a peer 244 interface. Runs dnsmasq on the peer interface so that a DHCP 245 service can offer ip addresses to the virtual interface. 246 """ 247 os.system('ip link add name %s type veth peer name %s' % ( 248 self.interface, self.peer)) 249 250 os.system('ifconfig %s %s.1/24' % (self.peer, self.base)) 251 os.system('ifconfig %s up' % self.peer) 252 253 os.system('ifconfig %s up' % self.interface) 254 os.system('ip route add 255.255.255.255 dev %s %s' % 255 (self.peer, self.ROUTE_PROTO)) 256 os.close(os.open(self.lease_file, os.O_CREAT | os.O_TRUNC)) 257 self.dnsmasq = subprocess.Popen( 258 ['/usr/local/sbin/dnsmasq', 259 '--pid-file', 260 '-k', 261 '--dhcp-leasefile=%s' % self.lease_file, 262 '--dhcp-range=%s.2,%s.254' % (self.base, self.base), 263 '--port=0', 264 '--interface=%s' % self.peer, 265 '--bind-interfaces' 266 ]) 267 # iptables default policy is to reject packets. Add ACCEPT as the 268 # target for the virtual and peer interfaces. Note that this currently 269 # only accepts v4 traffic. 270 os.system('iptables -I INPUT -i %s -j ACCEPT' % self.peer) 271 os.system('iptables -I INPUT -i %s -j ACCEPT' % self.interface) 272 273 def DestroyInterface(self): 274 """Destroys the virtual interface. 275 276 Stops dnsmasq and cleans up all on disk state. 277 """ 278 if self.dnsmasq: 279 self.dnsmasq.terminate() 280 try: 281 os.system('ip route del 255.255.255.255 %s' % self.ROUTE_PROTO) 282 except: 283 pass 284 try: 285 os.system('ip link del %s' % self.interface) 286 except: 287 pass 288 os.system('iptables -D INPUT -i %s -j ACCEPT' % self.peer) 289 os.system('iptables -D INPUT -i %s -j ACCEPT' % self.interface) 290 if os.path.exists(self.lease_file): 291 os.remove(self.lease_file) 292 293 294class Modem(DBusObjectWithProperties): 295 """A Modem object that implements the ModemManager DBUS API.""" 296 297 def __init__(self, manager, name='/Modem/0', 298 device='pseudomodem0', 299 mdn='0000001234', 300 meid='A100000DCE2CA0', 301 carrier='CrCarrier', 302 esn='EDD1EDD1', 303 sim=None): 304 """Instantiates a Modem with some options. 305 306 Args: 307 manager: a ModemManager object. 308 name: string, a dbus path name. 309 device: string, the network device to use. 310 mdn: string, the mobile directory number. 311 meid: string, the mobile equipment id (CDMA only?). 312 carrier: string, the name of the carrier. 313 esn: string, the electronic serial number. 314 sim: a SIM object. 315 """ 316 self.state = mm1.MM_MODEM_STATE_DISABLED 317 self.manager = manager 318 self.name = name 319 self.path = manager.path + name 320 self.device = device 321 self.mdn = mdn 322 self.meid = meid 323 self.carrier = carrier 324 self.operator_name = carrier 325 self.operator_code = '123' 326 self.esn = esn 327 self.registration_state = mm1.MM_MODEM_3GPP_REGISTRATION_STATE_IDLE 328 self.sim = sim 329 DBusObjectWithProperties.__init__(self, manager.bus, self.path) 330 self.pseudo_interface = PseudoNetworkInterface(self.device, '192.168.7') 331 self.smses = {} 332 333 def __enter__(self): 334 """Make usable with "with" statement.""" 335 self.pseudo_interface.__enter__() 336 # Add the device to the manager only after the pseudo 337 # interface has been created. 338 self.manager.Add(self) 339 return self 340 341 def __exit__(self, exception, value, traceback): 342 """Make usable with "with" statement.""" 343 self.manager.Remove(self) 344 return self.pseudo_interface.__exit__(exception, value, traceback) 345 346 def DiscardModem(self): 347 """Discard this DBUS Object. 348 349 Send a message that a modem has disappeared and deregister from DBUS. 350 """ 351 logging.info('DiscardModem') 352 self.remove_from_connection() 353 self.manager.Remove(self) 354 355 def ModemProperties(self): 356 """Return the properties of the modem object.""" 357 properties = { 358 # 'Sim': type='o' 359 'ModemCapabilities': UInt32(0), 360 'CurrentCapabilities': UInt32(0), 361 'MaxBearers': UInt32(2), 362 'MaxActiveBearers': UInt32(2), 363 'Manufacturer': 'Foo Electronics', 364 'Model': 'Super Foo Modem', 365 'Revision': '1.0', 366 'DeviceIdentifier': '123456789', 367 'Device': self.device, 368 'Driver': 'fake', 369 'Plugin': 'Foo Plugin', 370 'EquipmentIdentifier': self.meid, 371 'UnlockRequired': UInt32(0), 372 #'UnlockRetries' type='a{uu}' 373 mm1.MM_MODEM_PROPERTY_STATE: Int32(self.state), 374 'AccessTechnologies': UInt32(self.state), 375 'SignalQuality': Struct([UInt32(90), True], signature='ub'), 376 'OwnNumbers': ['6175551212'], 377 'SupportedModes': UInt32(0), 378 'AllowedModes': UInt32(0), 379 'PreferredMode': UInt32(0), 380 'SupportedBands': [UInt32(0)], 381 'Bands': [UInt32(0)] 382 } 383 if self.sim: 384 properties['Sim'] = ObjectPath(self.sim.path) 385 return properties 386 387 def InterfacesAndProperties(self): 388 """Return all supported interfaces and their properties.""" 389 return { 390 mm1.MODEM_INTERFACE: self.ModemProperties(), 391 } 392 393 def ChangeState(self, new_state, 394 why=mm1.MM_MODEM_STATE_CHANGE_REASON_UNKNOWN): 395 logging.info('Change state from %s to %s', self.state, new_state) 396 self.StateChanged(Int32(self.state), Int32(new_state), UInt32(why)) 397 self.PropertiesChanged(mm1.MODEM_INTERFACE, 398 {mm1.MM_MODEM_PROPERTY_STATE: Int32(new_state)}, 399 []) 400 self.state = new_state 401 402 @dbus.service.method(mm1.MODEM_INTERFACE, 403 in_signature='b', out_signature='') 404 def Enable(self, on, *args, **kwargs): 405 """Enables the Modem.""" 406 logging.info('Modem: Enable %s', str(on)) 407 if on: 408 if self.state <= mm1.MM_MODEM_STATE_ENABLING: 409 self.ChangeState(mm1.MM_MODEM_STATE_ENABLING) 410 if self.state <= mm1.MM_MODEM_STATE_ENABLED: 411 self.ChangeState(mm1.MM_MODEM_STATE_ENABLED) 412 if self.state <= mm1.MM_MODEM_STATE_SEARCHING: 413 self.ChangeState(mm1.MM_MODEM_STATE_SEARCHING) 414 glib.timeout_add(250, self.OnRegistered) 415 else: 416 if self.state >= mm1.MM_MODEM_STATE_DISABLING: 417 self.ChangeState(mm1.MM_MODEM_STATE_DISABLING) 418 if self.state >= mm1.MM_MODEM_STATE_DISABLED: 419 self.ChangeState(mm1.MM_MODEM_STATE_DISABLED) 420 self.ChangeRegistrationState( 421 mm1.MM_MODEM_3GPP_REGISTRATION_STATE_IDLE) 422 return None 423 424 def ChangeRegistrationState(self, new_state): 425 """Updates the registration state of the modem. 426 427 Updates the registration state of the modem and broadcasts a 428 DBUS signal. 429 430 Args: 431 new_state: the new registation state of the modem. 432 """ 433 if new_state != self.registration_state: 434 self.registration_state = new_state 435 self.PropertiesChanged( 436 mm1.MODEM_MODEM3GPP_INTERFACE, 437 {mm1.MM_MODEM3GPP_PROPERTY_REGISTRATION_STATE: 438 UInt32(new_state)}, 439 []) 440 441 def OnRegistered(self): 442 """Called when the Modem is Registered.""" 443 if (self.state >= mm1.MM_MODEM_STATE_ENABLED and 444 self.state <= mm1.MM_MODEM_STATE_REGISTERED): 445 logging.info('Modem: Marking Registered') 446 self.ChangeRegistrationState( 447 mm1.MM_MODEM_3GPP_REGISTRATION_STATE_HOME) 448 self.ChangeState(mm1.MM_MODEM_STATE_REGISTERED) 449 450 @dbus.service.method(mm1.MODEM_SIMPLE_INTERFACE, in_signature='', 451 out_signature='a{sv}') 452 def GetStatus(self, *args, **kwargs): 453 """Gets the general modem status. 454 455 Returns: 456 A dictionary of properties. 457 """ 458 logging.info('Modem: GetStatus') 459 properties = { 460 'state': UInt32(self.state), 461 'signal-quality': UInt32(99), 462 'bands': self.carrier, 463 'access-technology': UInt32(0), 464 'm3gpp-registration-state': UInt32(self.registration_state), 465 'm3gpp-operator-code': '123', 466 'm3gpp-operator-name': '123', 467 'cdma-cdma1x-registration-state': UInt32(99), 468 'cdma-evdo-registration-state': UInt32(99), 469 'cdma-sid': '123', 470 'cdma-nid': '123', 471 } 472 if self.state >= mm1.MM_MODEM_STATE_ENABLED: 473 properties['carrier'] = 'Test Network' 474 return properties 475 476 @dbus.service.signal(mm1.MODEM_INTERFACE, signature='iiu') 477 def StateChanged(self, old_state, new_state, why): 478 pass 479 480 @dbus.service.method(mm1.MODEM_SIMPLE_INTERFACE, in_signature='a{sv}', 481 out_signature='o', 482 async_callbacks=('return_cb', 'raise_cb')) 483 def Connect(self, unused_props, return_cb, raise_cb, **kwargs): 484 """Connect the modem to the network. 485 486 Args: 487 unused_props: connection properties. See ModemManager documentation. 488 return_cb: function to call to return result asynchronously. 489 raise_cb: function to call to raise an error asynchronously. 490 """ 491 492 def ConnectDone(new, why): 493 logging.info('Modem: ConnectDone %s -> %s because %s', 494 str(self.state), str(new), str(why)) 495 if self.state == mm1.MM_MODEM_STATE_CONNECTING: 496 self.ChangeState(new, why) 497 # TODO(jglasgow): implement a bearer object 498 bearer_path = '/Bearer/0' 499 return_cb(bearer_path) 500 else: 501 raise_cb(mm1.ConnectionUnknownError()) 502 503 logging.info('Modem: Connect') 504 if self.state != mm1.MM_MODEM_STATE_REGISTERED: 505 logging.info( 506 'Modem: Connect fails on unregistered modem. State = %s', 507 self.state) 508 raise mm1.NoNetworkError() 509 delay_ms = kwargs.get('connect_delay_ms', DEFAULT_CONNECT_DELAY_MS) 510 time.sleep(delay_ms / 1000.0) 511 self.ChangeState(mm1.MM_MODEM_STATE_CONNECTING) 512 glib.timeout_add(50, lambda: ConnectDone( 513 mm1.MM_MODEM_STATE_CONNECTED, 514 mm1.MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED)) 515 516 @dbus.service.method(mm1.MODEM_SIMPLE_INTERFACE, in_signature='o', 517 async_callbacks=('return_cb', 'raise_cb')) 518 def Disconnect(self, bearer, return_cb, raise_cb, **kwargs): 519 """Disconnect the modem from the network.""" 520 521 def DisconnectDone(old, new, why): 522 logging.info('Modem: DisconnectDone %s -> %s because %s', 523 str(old), str(new), str(why)) 524 if self.state == mm1.MM_MODEM_STATE_DISCONNECTING: 525 logging.info('Modem: State is DISCONNECTING, changing to %s', 526 str(new)) 527 self.ChangeState(new) 528 return_cb() 529 elif self.state == mm1.MM_MODEM_STATE_DISABLED: 530 logging.info('Modem: State is DISABLED, not changing state') 531 return_cb() 532 else: 533 raise_cb(mm1.ConnectionUnknownError()) 534 535 logging.info('Modem: Disconnect') 536 self.ChangeState(mm1.MM_MODEM_STATE_DISCONNECTING) 537 glib.timeout_add( 538 500, 539 lambda: DisconnectDone( 540 self.state, 541 mm1.MM_MODEM_STATE_REGISTERED, 542 mm1.MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED)) 543 544 @dbus.service.signal(dbus.PROPERTIES_IFACE, signature='sa{sv}as') 545 def PropertiesChanged(self, interface, changed_properties, 546 invalidated_properties): 547 pass 548 549 def AddSMS(self, sms): 550 logging.info('Adding SMS %s to list', sms.path) 551 self.smses[sms.path] = sms 552 self.Added(self.path, True) 553 554 @dbus.service.method(mm1.MODEM_MESSAGING_INTERFACE, in_signature='', 555 out_signature='ao') 556 def List(self, *args, **kwargs): 557 logging.info('Modem.Messaging: List: %s', 558 ', '.join(list(self.smses.keys()))) 559 return list(self.smses.keys()) 560 561 @dbus.service.method(mm1.MODEM_MESSAGING_INTERFACE, in_signature='o', 562 out_signature='') 563 def Delete(self, sms_path, *args, **kwargs): 564 logging.info('Modem.Messaging: Delete %s', sms_path) 565 del self.smses[sms_path] 566 567 @dbus.service.signal(mm1.MODEM_MESSAGING_INTERFACE, signature='ob') 568 def Added(self, sms_path, complete): 569 pass 570 571 572class GSMModem(Modem): 573 """A GSMModem implements the mm1.MODEM_MODEM3GPP_INTERFACE interface.""" 574 575 def __init__(self, manager, imei='00112342342', **kwargs): 576 self.imei = imei 577 Modem.__init__(self, manager, **kwargs) 578 579 @dbus.service.method(mm1.MODEM_MODEM3GPP_INTERFACE, 580 in_signature='s', out_signature='') 581 def Register(self, operator_id, *args, **kwargs): 582 """Register the modem on the network.""" 583 pass 584 585 def Modem3GPPProperties(self): 586 """Return the 3GPP Properties of the modem object.""" 587 return { 588 'Imei': self.imei, 589 mm1.MM_MODEM3GPP_PROPERTY_REGISTRATION_STATE: 590 UInt32(self.registration_state), 591 'OperatorCode': self.operator_code, 592 'OperatorName': self.operator_name, 593 'EnabledFacilityLocks': UInt32(0) 594 } 595 596 def InterfacesAndProperties(self): 597 """Return all supported interfaces and their properties.""" 598 return { 599 mm1.MODEM_INTERFACE: self.ModemProperties(), 600 mm1.MODEM_MODEM3GPP_INTERFACE: self.Modem3GPPProperties() 601 } 602 603 @dbus.service.method(mm1.MODEM_MODEM3GPP_INTERFACE, in_signature='', 604 out_signature='aa{sv}') 605 def Scan(self, *args, **kwargs): 606 """Scan for networks.""" 607 raise mm1.CoreUnsupportedError() 608 609 610class ModemManager(dbus.service.Object): 611 """Implements the org.freedesktop.DBus.ObjectManager interface.""" 612 613 def __init__(self, bus, path): 614 self.devices = [] 615 self.bus = bus 616 self.path = path 617 dbus.service.Object.__init__(self, bus, path) 618 619 def Add(self, device): 620 """Adds a modem device to the list of devices that are managed.""" 621 logging.info('ModemManager: add %s', device.name) 622 self.devices.append(device) 623 interfaces = device.InterfacesAndProperties() 624 logging.info('Add: %s', interfaces) 625 self.InterfacesAdded(device.path, interfaces) 626 627 def Remove(self, device): 628 """Removes a modem device from the list of managed devices.""" 629 logging.info('ModemManager: remove %s', device.name) 630 self.devices.remove(device) 631 interfaces = list(device.InterfacesAndProperties().keys()) 632 self.InterfacesRemoved(device.path, interfaces) 633 634 @dbus.service.method(mm1.OFDOM, out_signature='a{oa{sa{sv}}}') 635 def GetManagedObjects(self): 636 """Returns the list of managed objects and their properties.""" 637 results = {} 638 for device in self.devices: 639 results[device.path] = device.InterfacesAndProperties() 640 logging.info('GetManagedObjects: %s', ', '.join(list(results.keys()))) 641 return results 642 643 @dbus.service.signal(mm1.OFDOM, signature='oa{sa{sv}}') 644 def InterfacesAdded(self, object_path, interfaces_and_properties): 645 pass 646 647 @dbus.service.signal(mm1.OFDOM, signature='oas') 648 def InterfacesRemoved(self, object_path, interfaces): 649 pass 650 651 652def main(): 653 usage = """ 654Run pseudo_modem to simulate a GSM modem using the modemmanager-next 655DBUS interfaces. This can be used for the following: 656 - to simpilify the verification process of UI features that use of 657 overseas SIM cards 658 - to test shill on a virtual machine without a physical modem 659 - to test that Chrome property displays SMS messages 660 661To use on a test image you use test_that to run 662network_3GModemControl which will cause pseudo_modem.py to be 663installed in /usr/local/autotests/cros/cellular. Then stop 664modemmanager and start the pseudo modem with the commands: 665 666 stop modemmanager 667 /usr/local/autotest/cros/cellular/pseudo_modem.py 668 669When done, use Control-C to stop the process and restart modem manager: 670 start modemmanager 671 672Additional help documentation is available by invoking pseudo_modem.py 673--help. 674 675SMS testing can be accomnplished by supplying the -s flag to simulate 676the receipt of a number of SMS messages. The message text can be 677specified with the --text option on the command line, or read from a 678file by using the --file option. If the messages are located in a 679file, then each line corresponds to a single SMS message. 680 681Chrome should display the SMS messages as soon as a user logs in to 682the Chromebook, or if the user is already logged in, then shortly 683after the pseudo modem is recognized by shill. 684""" 685 parser = OptionParser(usage=usage) 686 parser.add_option('-c', '--carrier', dest='carrier_name', 687 metavar='<carrier name>', 688 help='<carrier name> := %s' % ' | '.join( 689 list(SIM.CARRIERS.keys()))) 690 parser.add_option('-s', '--smscount', dest='sms_count', 691 default=0, 692 metavar='<smscount>', 693 help='<smscount> := integer') 694 parser.add_option('-l', '--logfile', dest='logfile', 695 default='', 696 metavar='<filename>', 697 help='<filename> := filename for logging output') 698 parser.add_option('-t', '--text', dest='sms_text', 699 default=None, 700 metavar='<text>', 701 help='<text> := text for sms messages') 702 parser.add_option('-f', '--file', dest='filename', 703 default=None, 704 metavar='<filename>', 705 help='<filename> := file with text for sms messages') 706 707 (options, args) = parser.parse_args() 708 709 kwargs = {} 710 if options.logfile: 711 kwargs['filename'] = options.logfile 712 logging.basicConfig(format='%(asctime)-15s %(message)s', 713 level=logging.DEBUG, 714 **kwargs) 715 716 if not options.carrier_name: 717 options.carrier_name = DEFAULT_CARRIER 718 719 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 720 bus = dbus.SystemBus() 721 name = dbus.service.BusName(mm1.MODEM_MANAGER_INTERFACE, bus) 722 manager = ModemManager(bus, mm1.OMM) 723 sim_card = SIM.FromCarrier(string.lower(options.carrier_name), 724 manager) 725 with GSMModem(manager, sim=sim_card) as modem: 726 if options.filename: 727 f = open(options.filename, 'r') 728 for index, line in enumerate(f.readlines()): 729 line = line.strip() 730 if line: 731 sms = SMS(manager, name='/SMS/%s' % index, text=line) 732 modem.AddSMS(sms) 733 else: 734 for index in range(int(options.sms_count)): 735 sms = SMS(manager, name='/SMS/%s' % index, 736 text=options.sms_text) 737 modem.AddSMS(sms) 738 739 mainloop = GObject.MainLoop() 740 741 def SignalHandler(signum, frame): 742 logging.info('Signal handler called with signal: %s', signum) 743 mainloop.quit() 744 745 signal.signal(signal.SIGTERM, SignalHandler) 746 747 mainloop.run() 748 749if __name__ == '__main__': 750 main() 751