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