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
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    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
276class 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
554class 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
592class 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
634def main():
635    usage = """
636Run pseudo_modem to simulate a GSM modem using the modemmanager-next
637DBUS 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
643To use on a test image you use test_that to run
644network_3GModemControl which will cause pseudo_modem.py to be
645installed in /usr/local/autotests/cros/cellular.  Then stop
646modemmanager and start the pseudo modem with the commands:
647
648  stop modemmanager
649  /usr/local/autotest/cros/cellular/pseudo_modem.py
650
651When done, use Control-C to stop the process and restart modem manager:
652  start modemmanager
653
654Additional help documentation is available by invoking pseudo_modem.py
655--help.
656
657SMS testing can be accomnplished by supplying the -s flag to simulate
658the receipt of a number of SMS messages.  The message text can be
659specified with the --text option on the command line, or read from a
660file by using the --file option.  If the messages are located in a
661file, then each line corresponds to a single SMS message.
662
663Chrome should display the SMS messages as soon as a user logs in to
664the Chromebook, or if the user is already logged in, then shortly
665after 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
731if __name__ == '__main__':
732    main()
733