Home | History | Annotate | Download | only in pseudomodem
      1 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """
      6 Python implementation of the standard interfaces:
      7   - org.freedesktop.DBus.Properties
      8   - org.freedesktop.DBus.Introspectable (TODO(armansito): May not be necessary)
      9   - org.freedesktop.DBus.ObjectManager
     10 
     11 """
     12 
     13 import dbus
     14 import dbus.service
     15 import dbus.types
     16 import logging
     17 
     18 import pm_errors
     19 import utils
     20 
     21 from autotest_lib.client.cros.cellular import mm1_constants
     22 
     23 class MMPropertyError(pm_errors.MMError):
     24     """
     25     MMPropertyError is raised by DBusProperties methods
     26     to indicate that a value for the given interface or
     27     property could not be found.
     28 
     29     """
     30 
     31     UNKNOWN_PROPERTY = 0
     32     UNKNOWN_INTERFACE = 1
     33 
     34     def __init__(self, errno, *args, **kwargs):
     35         super(MMPropertyError, self).__init__(errno, args, kwargs)
     36 
     37 
     38     def _Setup(self):
     39         self._error_name_base = mm1_constants.I_MODEM_MANAGER
     40         self._error_name_map = {
     41             self.UNKNOWN_PROPERTY : '.UnknownProperty',
     42             self.UNKNOWN_INTERFACE : '.UnknownInterface'
     43         }
     44 
     45 
     46 class DBusProperties(dbus.service.Object):
     47     """
     48     == org.freedesktop.DBus.Properties ==
     49 
     50     This serves as the abstract base class for all objects that expose
     51     properties. Each instance holds a mapping from DBus interface names to
     52     property-value mappings, which are provided by the subclasses.
     53 
     54     """
     55 
     56     def __init__(self, path, bus=None, config=None):
     57         """
     58         @param bus: The pydbus bus object.
     59         @param path: The DBus object path of this object.
     60         @param config: This is an optional dictionary that can be used to
     61                 initialize the property dictionary with values other than the
     62                 ones provided by |_InitializeProperties|. The dictionary has to
     63                 contain a mapping from DBus interfaces to property-value pairs,
     64                 and all contained keys must have been initialized during
     65                 |_InitializeProperties|, i.e. if config contains any keys that
     66                 have not been already set in the internal property dictionary,
     67                 an error will be raised (see DBusProperties.Set).
     68 
     69         """
     70         if not path:
     71             raise TypeError(('A value for "path" has to be provided that is '
     72                 'not "None".'))
     73         if bus:
     74           dbus.service.Object.__init__(self, bus, path)
     75         else:
     76           dbus.service.Object.__init__(self, None, None)
     77         self.path = path
     78         self.bus = bus
     79         self._properties = self._InitializeProperties()
     80 
     81         if config:
     82             for key, props in config:
     83                 for prop, val in props:
     84                     self.Set(key, prop, val)
     85 
     86 
     87     @property
     88     def properties(self):
     89         """
     90         @returns: The property dictionary.
     91 
     92         """
     93         return self._properties
     94 
     95 
     96     def SetBus(self, bus):
     97         """
     98         Sets the pydbus bus object that this instance of DBusProperties should
     99         be exposed on. Call this method only if |bus| is not already set.
    100 
    101         @param bus: The pydbus bus object to assign.
    102 
    103         """
    104         self.bus = bus
    105         self.add_to_connection(bus, self.path)
    106 
    107 
    108     def SetPath(self, path):
    109         """
    110         Exposes this object on a new DBus path. This method fails with an
    111         Exception by default, since exposing an object on multiple paths is
    112         disallowed by default.
    113 
    114         Subclasses can change this behavior by setting the
    115         SUPPORTS_MULTIPLE_OBJECT_PATHS class variable to True.
    116 
    117         @param path: The new path to assign to this object.
    118 
    119         """
    120         self.path = path
    121         self.add_to_connection(self.bus, path)
    122 
    123 
    124     def SetUInt32(self, interface_name, property_name, value):
    125         """
    126         Sets the given uint32 value matching the given property and interface.
    127         Wraps the given value inside a dbus.types.UInt32.
    128 
    129         @param interface_name: The DBus interface name.
    130         @param property_name: The property name.
    131         @param value: Value to set.
    132         @raises: MMPropertyError, if the given |interface_name| or
    133                 |property_name| is not exposed by this object.
    134         Emits:
    135             PropertiesChanged
    136 
    137         """
    138         self.Set(interface_name, property_name, dbus.types.UInt32(value))
    139 
    140 
    141     def SetInt32(self, interface_name, property_name, value):
    142         """
    143         Sets the given int32 value matching the given property and interface.
    144         Wraps the given value inside a dbus.types.Int32.
    145 
    146         @param interface_name: The DBus interface name.
    147         @param property_name: The property name.
    148         @param value: Value to set.
    149         @raises: MMPropertyError, if the given |interface_name| or
    150                 |property_name| is not exposed by this object.
    151         Emits:
    152             PropertiesChanged
    153 
    154         """
    155         self.Set(interface_name, property_name, dbus.types.Int32(value))
    156 
    157 
    158     @utils.log_dbus_method()
    159     @dbus.service.method(mm1_constants.I_PROPERTIES, in_signature='ss',
    160                          out_signature='v')
    161     def Get(self, interface_name, property_name):
    162         """
    163         Returns the value matching the given property and interface.
    164 
    165         @param interface_name: The DBus interface name.
    166         @param property_name: The property name.
    167         @returns: The value matching the given property and interface.
    168         @raises: MMPropertyError, if the given |interface_name| or
    169                 |property_name| is not exposed by this object.
    170 
    171         """
    172         logging.info(
    173             '%s: Get(%s, %s)',
    174             self.path,
    175             interface_name,
    176             property_name)
    177         val = self.GetAll(interface_name).get(property_name, None)
    178         if val is None:
    179             message = ("Property '%s' not implemented for interface '%s'." %
    180                 (property_name, interface_name))
    181             logging.info(message)
    182             raise MMPropertyError(
    183                 MMPropertyError.UNKNOWN_PROPERTY, message)
    184         return val
    185 
    186 
    187     @utils.log_dbus_method()
    188     @dbus.service.method(mm1_constants.I_PROPERTIES, in_signature='ssv')
    189     def Set(self, interface_name, property_name, value):
    190         """
    191         Sets the value matching the given property and interface.
    192 
    193         @param interface_name: The DBus interface name.
    194         @param property_name: The property name.
    195         @param value: The value to set.
    196         @raises: MMPropertyError, if the given |interface_name| or
    197                 |property_name| is not exposed by this object.
    198         Emits:
    199             PropertiesChanged
    200 
    201         """
    202         logging.info(
    203             '%s: Set(%s, %s)',
    204             self.path,
    205             interface_name,
    206             property_name)
    207         props = self.GetAll(interface_name)
    208         if property_name not in props:
    209             raise MMPropertyError(
    210                 MMPropertyError.UNKNOWN_PROPERTY,
    211                 ("Property '%s' not implemented for "
    212                 "interface '%s'.") %
    213                 (property_name, interface_name))
    214         if props[property_name] == value:
    215             logging.info("Property '%s' already has value '%s'. Ignoring.",
    216                          property_name,
    217                          value)
    218             return
    219         props[property_name] = value
    220         changed = { property_name : value }
    221         inv = self._InvalidatedPropertiesForChangedValues(changed)
    222         self.PropertiesChanged(interface_name, changed, inv)
    223 
    224 
    225     @utils.log_dbus_method()
    226     @dbus.service.method(mm1_constants.I_PROPERTIES,
    227                          in_signature='s', out_signature='a{sv}')
    228     def GetAll(self, interface_name):
    229         """
    230         Returns all property-value pairs that match the given interface.
    231 
    232         @param interface_name: The DBus interface name.
    233         @returns: A dictionary, containing the properties of the given DBus
    234                 interface and their values.
    235         @raises: MMPropertyError, if the given |interface_name| or
    236                 |property_name| is not exposed by this object.
    237 
    238         """
    239         logging.info(
    240             '%s: GetAll(%s)',
    241             self.path,
    242             interface_name)
    243         props = self._properties.get(interface_name, None)
    244         if props is None:
    245             raise MMPropertyError(
    246                 MMPropertyError.UNKNOWN_INTERFACE,
    247                 "Object does not implement interface '%s'." %
    248                 interface_name)
    249         return props
    250 
    251 
    252     @dbus.service.signal(mm1_constants.I_PROPERTIES, signature='sa{sv}as')
    253     def PropertiesChanged(
    254             self,
    255             interface_name,
    256             changed_properties,
    257             invalidated_properties):
    258         """
    259         This signal is emitted by Set, when the value of a property is changed.
    260 
    261         @param interface_name: The interface the changed properties belong to.
    262         @param changed_properties: Dictionary containing the changed properties
    263                                    and their new values.
    264         @param invalidated_properties: List of properties that were invalidated
    265                                        when properties changed.
    266 
    267         """
    268         logging.info(('Properties Changed on interface: %s Changed Properties:'
    269             ' %s InvalidatedProperties: %s.', interface_name,
    270             str(changed_properties), str(invalidated_properties)))
    271 
    272 
    273     def SetAll(self, interface, properties):
    274         """
    275         Sets the entire property dictionary for the given interface.
    276 
    277         @param interface: String specifying the DBus interface.
    278         @param properties: Dictionary containing the properties to set.
    279         Emits:
    280             PropertiesChanged
    281 
    282         """
    283         old_props = self._properties.get(interface, None)
    284         if old_props:
    285             invalidated = old_props.keys()
    286         else:
    287             invalidated = []
    288         self._properties[interface] = properties
    289         self.PropertiesChanged(interface, properties, invalidated)
    290 
    291 
    292     def GetInterfacesAndProperties(self):
    293         """
    294         Returns all DBus properties of this object.
    295 
    296         @returns: The complete property dictionary. The returned dict is a tree,
    297                 where the keys are DBus interfaces and the values are
    298                 dictionaries that map properties to values.
    299         """
    300         return self._properties
    301 
    302 
    303     def _InvalidatedPropertiesForChangedValues(self, changed):
    304         """
    305         Called by Set, returns the list of property names that should become
    306         invalidated given the properties and their new values contained in
    307         changed. Subclasses can override this method; the default implementation
    308         returns an empty list.
    309 
    310         """
    311         return []
    312 
    313 
    314     def _InitializeProperties(self):
    315         """
    316         Called at instantiation. Subclasses have to override this method and
    317         return a dictionary containing mappings from implemented interfaces to
    318         dictionaries of property-value mappings.
    319 
    320         """
    321         raise NotImplementedError()
    322 
    323 
    324 class DBusObjectManager(dbus.service.Object):
    325     """
    326     == org.freedesktop.DBus.ObjectManager ==
    327 
    328     This interface, included in rev. 0.17 of the DBus specification, allows a
    329     generic way to control the addition and removal of Modem objects, as well
    330     as the addition and removal of interfaces in the given objects.
    331 
    332     """
    333 
    334     def __init__(self, bus, path):
    335         dbus.service.Object.__init__(self, bus, path)
    336         self.devices = []
    337         self.bus = bus
    338         self.path = path
    339 
    340 
    341     def Add(self, device):
    342         """
    343         Adds a device to the list of devices that are managed by this modem
    344         manager.
    345 
    346         @param device: Device to add.
    347         Emits:
    348             InterfacesAdded
    349 
    350         """
    351         self.devices.append(device)
    352         device.manager = self
    353         self.InterfacesAdded(device.path, device.GetInterfacesAndProperties())
    354 
    355 
    356     def Remove(self, device):
    357         """
    358         Removes a device from the list of devices that are managed by this
    359         modem manager.
    360 
    361         @param device: Device to remove.
    362         Emits:
    363             InterfacesRemoved
    364 
    365         """
    366         if device in self.devices:
    367             self.devices.remove(device)
    368         interfaces = device.GetInterfacesAndProperties().keys()
    369         self.InterfacesRemoved(device.path, interfaces)
    370         device.remove_from_connection()
    371 
    372 
    373     @utils.log_dbus_method()
    374     @dbus.service.method(mm1_constants.I_OBJECT_MANAGER,
    375                          out_signature='a{oa{sa{sv}}}')
    376     def GetManagedObjects(self):
    377         """
    378         @returns: A dictionary containing all objects and their properties. The
    379                 keys to the dictionary are object paths which are mapped to
    380                 dictionaries containing mappings from DBus interface names to
    381                 property-value pairs.
    382 
    383         """
    384         results = {}
    385         for device in self.devices:
    386             results[dbus.types.ObjectPath(device.path)] = (
    387                     device.GetInterfacesAndProperties())
    388         logging.info('%s: GetManagedObjects: %s', self.path,
    389                      ', '.join(results.keys()))
    390         return results
    391 
    392 
    393     @dbus.service.signal(mm1_constants.I_OBJECT_MANAGER,
    394                          signature='oa{sa{sv}}')
    395     def InterfacesAdded(self, object_path, interfaces_and_properties):
    396         """
    397         The InterfacesAdded signal is emitted when either a new object is added
    398         or when an existing object gains one or more interfaces.
    399 
    400         @param object_path: Path of the added object.
    401         @param interfaces_and_properties: The complete property dictionary that
    402                 belongs to the recently added object.
    403 
    404         """
    405         logging.info((self.path + ': InterfacesAdded(' + object_path +
    406                      ', ' + str(interfaces_and_properties)) + ')')
    407 
    408 
    409     @dbus.service.signal(mm1_constants.I_OBJECT_MANAGER, signature='oas')
    410     def InterfacesRemoved(self, object_path, interfaces):
    411         """
    412         The InterfacesRemoved signal is emitted whenever an object is removed
    413         or it loses one or more interfaces.
    414 
    415         @param object_path: Path of the remove object.
    416         @param interfaces_and_properties: The complete property dictionary that
    417                 belongs to the recently removed object.
    418 
    419         """
    420         logging.info((self.path + ': InterfacesRemoved(' + object_path +
    421                      ', ' + str(interfaces) + ')'))
    422