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