422 lines
14 KiB
Python
422 lines
14 KiB
Python
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""
|
|
Python implementation of the standard interfaces:
|
|
- org.freedesktop.DBus.Properties
|
|
- org.freedesktop.DBus.Introspectable (TODO(armansito): May not be necessary)
|
|
- org.freedesktop.DBus.ObjectManager
|
|
|
|
"""
|
|
|
|
import dbus
|
|
import dbus.service
|
|
import dbus.types
|
|
import logging
|
|
|
|
import pm_errors
|
|
import utils
|
|
|
|
from autotest_lib.client.cros.cellular import mm1_constants
|
|
|
|
class MMPropertyError(pm_errors.MMError):
|
|
"""
|
|
MMPropertyError is raised by DBusProperties methods
|
|
to indicate that a value for the given interface or
|
|
property could not be found.
|
|
|
|
"""
|
|
|
|
UNKNOWN_PROPERTY = 0
|
|
UNKNOWN_INTERFACE = 1
|
|
|
|
def __init__(self, errno, *args, **kwargs):
|
|
super(MMPropertyError, self).__init__(errno, args, kwargs)
|
|
|
|
|
|
def _Setup(self):
|
|
self._error_name_base = mm1_constants.I_MODEM_MANAGER
|
|
self._error_name_map = {
|
|
self.UNKNOWN_PROPERTY : '.UnknownProperty',
|
|
self.UNKNOWN_INTERFACE : '.UnknownInterface'
|
|
}
|
|
|
|
|
|
class DBusProperties(dbus.service.Object):
|
|
"""
|
|
== org.freedesktop.DBus.Properties ==
|
|
|
|
This serves as the abstract base class for all objects that expose
|
|
properties. Each instance holds a mapping from DBus interface names to
|
|
property-value mappings, which are provided by the subclasses.
|
|
|
|
"""
|
|
|
|
def __init__(self, path, bus=None, config=None):
|
|
"""
|
|
@param bus: The pydbus bus object.
|
|
@param path: The DBus object path of this object.
|
|
@param config: This is an optional dictionary that can be used to
|
|
initialize the property dictionary with values other than the
|
|
ones provided by |_InitializeProperties|. The dictionary has to
|
|
contain a mapping from DBus interfaces to property-value pairs,
|
|
and all contained keys must have been initialized during
|
|
|_InitializeProperties|, i.e. if config contains any keys that
|
|
have not been already set in the internal property dictionary,
|
|
an error will be raised (see DBusProperties.Set).
|
|
|
|
"""
|
|
if not path:
|
|
raise TypeError(('A value for "path" has to be provided that is '
|
|
'not "None".'))
|
|
if bus:
|
|
dbus.service.Object.__init__(self, bus, path)
|
|
else:
|
|
dbus.service.Object.__init__(self, None, None)
|
|
self.path = path
|
|
self.bus = bus
|
|
self._properties = self._InitializeProperties()
|
|
|
|
if config:
|
|
for key, props in config:
|
|
for prop, val in props:
|
|
self.Set(key, prop, val)
|
|
|
|
|
|
@property
|
|
def properties(self):
|
|
"""
|
|
@returns: The property dictionary.
|
|
|
|
"""
|
|
return self._properties
|
|
|
|
|
|
def SetBus(self, bus):
|
|
"""
|
|
Sets the pydbus bus object that this instance of DBusProperties should
|
|
be exposed on. Call this method only if |bus| is not already set.
|
|
|
|
@param bus: The pydbus bus object to assign.
|
|
|
|
"""
|
|
self.bus = bus
|
|
self.add_to_connection(bus, self.path)
|
|
|
|
|
|
def SetPath(self, path):
|
|
"""
|
|
Exposes this object on a new DBus path. This method fails with an
|
|
Exception by default, since exposing an object on multiple paths is
|
|
disallowed by default.
|
|
|
|
Subclasses can change this behavior by setting the
|
|
SUPPORTS_MULTIPLE_OBJECT_PATHS class variable to True.
|
|
|
|
@param path: The new path to assign to this object.
|
|
|
|
"""
|
|
self.path = path
|
|
self.add_to_connection(self.bus, path)
|
|
|
|
|
|
def SetUInt32(self, interface_name, property_name, value):
|
|
"""
|
|
Sets the given uint32 value matching the given property and interface.
|
|
Wraps the given value inside a dbus.types.UInt32.
|
|
|
|
@param interface_name: The DBus interface name.
|
|
@param property_name: The property name.
|
|
@param value: Value to set.
|
|
@raises: MMPropertyError, if the given |interface_name| or
|
|
|property_name| is not exposed by this object.
|
|
Emits:
|
|
PropertiesChanged
|
|
|
|
"""
|
|
self.Set(interface_name, property_name, dbus.types.UInt32(value))
|
|
|
|
|
|
def SetInt32(self, interface_name, property_name, value):
|
|
"""
|
|
Sets the given int32 value matching the given property and interface.
|
|
Wraps the given value inside a dbus.types.Int32.
|
|
|
|
@param interface_name: The DBus interface name.
|
|
@param property_name: The property name.
|
|
@param value: Value to set.
|
|
@raises: MMPropertyError, if the given |interface_name| or
|
|
|property_name| is not exposed by this object.
|
|
Emits:
|
|
PropertiesChanged
|
|
|
|
"""
|
|
self.Set(interface_name, property_name, dbus.types.Int32(value))
|
|
|
|
|
|
@utils.log_dbus_method()
|
|
@dbus.service.method(mm1_constants.I_PROPERTIES, in_signature='ss',
|
|
out_signature='v')
|
|
def Get(self, interface_name, property_name):
|
|
"""
|
|
Returns the value matching the given property and interface.
|
|
|
|
@param interface_name: The DBus interface name.
|
|
@param property_name: The property name.
|
|
@returns: The value matching the given property and interface.
|
|
@raises: MMPropertyError, if the given |interface_name| or
|
|
|property_name| is not exposed by this object.
|
|
|
|
"""
|
|
logging.info(
|
|
'%s: Get(%s, %s)',
|
|
self.path,
|
|
interface_name,
|
|
property_name)
|
|
val = self.GetAll(interface_name).get(property_name, None)
|
|
if val is None:
|
|
message = ("Property '%s' not implemented for interface '%s'." %
|
|
(property_name, interface_name))
|
|
logging.info(message)
|
|
raise MMPropertyError(
|
|
MMPropertyError.UNKNOWN_PROPERTY, message)
|
|
return val
|
|
|
|
|
|
@utils.log_dbus_method()
|
|
@dbus.service.method(mm1_constants.I_PROPERTIES, in_signature='ssv')
|
|
def Set(self, interface_name, property_name, value):
|
|
"""
|
|
Sets the value matching the given property and interface.
|
|
|
|
@param interface_name: The DBus interface name.
|
|
@param property_name: The property name.
|
|
@param value: The value to set.
|
|
@raises: MMPropertyError, if the given |interface_name| or
|
|
|property_name| is not exposed by this object.
|
|
Emits:
|
|
PropertiesChanged
|
|
|
|
"""
|
|
logging.info(
|
|
'%s: Set(%s, %s)',
|
|
self.path,
|
|
interface_name,
|
|
property_name)
|
|
props = self.GetAll(interface_name)
|
|
if property_name not in props:
|
|
raise MMPropertyError(
|
|
MMPropertyError.UNKNOWN_PROPERTY,
|
|
("Property '%s' not implemented for "
|
|
"interface '%s'.") %
|
|
(property_name, interface_name))
|
|
if props[property_name] == value:
|
|
logging.info("Property '%s' already has value '%s'. Ignoring.",
|
|
property_name,
|
|
value)
|
|
return
|
|
props[property_name] = value
|
|
changed = { property_name : value }
|
|
inv = self._InvalidatedPropertiesForChangedValues(changed)
|
|
self.PropertiesChanged(interface_name, changed, inv)
|
|
|
|
|
|
@utils.log_dbus_method()
|
|
@dbus.service.method(mm1_constants.I_PROPERTIES,
|
|
in_signature='s', out_signature='a{sv}')
|
|
def GetAll(self, interface_name):
|
|
"""
|
|
Returns all property-value pairs that match the given interface.
|
|
|
|
@param interface_name: The DBus interface name.
|
|
@returns: A dictionary, containing the properties of the given DBus
|
|
interface and their values.
|
|
@raises: MMPropertyError, if the given |interface_name| or
|
|
|property_name| is not exposed by this object.
|
|
|
|
"""
|
|
logging.info(
|
|
'%s: GetAll(%s)',
|
|
self.path,
|
|
interface_name)
|
|
props = self._properties.get(interface_name, None)
|
|
if props is None:
|
|
raise MMPropertyError(
|
|
MMPropertyError.UNKNOWN_INTERFACE,
|
|
"Object does not implement interface '%s'." %
|
|
interface_name)
|
|
return props
|
|
|
|
|
|
@dbus.service.signal(mm1_constants.I_PROPERTIES, signature='sa{sv}as')
|
|
def PropertiesChanged(
|
|
self,
|
|
interface_name,
|
|
changed_properties,
|
|
invalidated_properties):
|
|
"""
|
|
This signal is emitted by Set, when the value of a property is changed.
|
|
|
|
@param interface_name: The interface the changed properties belong to.
|
|
@param changed_properties: Dictionary containing the changed properties
|
|
and their new values.
|
|
@param invalidated_properties: List of properties that were invalidated
|
|
when properties changed.
|
|
|
|
"""
|
|
logging.info(('Properties Changed on interface: %s Changed Properties:'
|
|
' %s InvalidatedProperties: %s.', interface_name,
|
|
str(changed_properties), str(invalidated_properties)))
|
|
|
|
|
|
def SetAll(self, interface, properties):
|
|
"""
|
|
Sets the entire property dictionary for the given interface.
|
|
|
|
@param interface: String specifying the DBus interface.
|
|
@param properties: Dictionary containing the properties to set.
|
|
Emits:
|
|
PropertiesChanged
|
|
|
|
"""
|
|
old_props = self._properties.get(interface, None)
|
|
if old_props:
|
|
invalidated = old_props.keys()
|
|
else:
|
|
invalidated = []
|
|
self._properties[interface] = properties
|
|
self.PropertiesChanged(interface, properties, invalidated)
|
|
|
|
|
|
def GetInterfacesAndProperties(self):
|
|
"""
|
|
Returns all DBus properties of this object.
|
|
|
|
@returns: The complete property dictionary. The returned dict is a tree,
|
|
where the keys are DBus interfaces and the values are
|
|
dictionaries that map properties to values.
|
|
"""
|
|
return self._properties
|
|
|
|
|
|
def _InvalidatedPropertiesForChangedValues(self, changed):
|
|
"""
|
|
Called by Set, returns the list of property names that should become
|
|
invalidated given the properties and their new values contained in
|
|
changed. Subclasses can override this method; the default implementation
|
|
returns an empty list.
|
|
|
|
"""
|
|
return []
|
|
|
|
|
|
def _InitializeProperties(self):
|
|
"""
|
|
Called at instantiation. Subclasses have to override this method and
|
|
return a dictionary containing mappings from implemented interfaces to
|
|
dictionaries of property-value mappings.
|
|
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
|
|
class DBusObjectManager(dbus.service.Object):
|
|
"""
|
|
== org.freedesktop.DBus.ObjectManager ==
|
|
|
|
This interface, included in rev. 0.17 of the DBus specification, allows a
|
|
generic way to control the addition and removal of Modem objects, as well
|
|
as the addition and removal of interfaces in the given objects.
|
|
|
|
"""
|
|
|
|
def __init__(self, bus, path):
|
|
dbus.service.Object.__init__(self, bus, path)
|
|
self.devices = []
|
|
self.bus = bus
|
|
self.path = path
|
|
|
|
|
|
def Add(self, device):
|
|
"""
|
|
Adds a device to the list of devices that are managed by this modem
|
|
manager.
|
|
|
|
@param device: Device to add.
|
|
Emits:
|
|
InterfacesAdded
|
|
|
|
"""
|
|
self.devices.append(device)
|
|
device.manager = self
|
|
self.InterfacesAdded(device.path, device.GetInterfacesAndProperties())
|
|
|
|
|
|
def Remove(self, device):
|
|
"""
|
|
Removes a device from the list of devices that are managed by this
|
|
modem manager.
|
|
|
|
@param device: Device to remove.
|
|
Emits:
|
|
InterfacesRemoved
|
|
|
|
"""
|
|
if device in self.devices:
|
|
self.devices.remove(device)
|
|
interfaces = device.GetInterfacesAndProperties().keys()
|
|
self.InterfacesRemoved(device.path, interfaces)
|
|
device.remove_from_connection()
|
|
|
|
|
|
@utils.log_dbus_method()
|
|
@dbus.service.method(mm1_constants.I_OBJECT_MANAGER,
|
|
out_signature='a{oa{sa{sv}}}')
|
|
def GetManagedObjects(self):
|
|
"""
|
|
@returns: A dictionary containing all objects and their properties. The
|
|
keys to the dictionary are object paths which are mapped to
|
|
dictionaries containing mappings from DBus interface names to
|
|
property-value pairs.
|
|
|
|
"""
|
|
results = {}
|
|
for device in self.devices:
|
|
results[dbus.types.ObjectPath(device.path)] = (
|
|
device.GetInterfacesAndProperties())
|
|
logging.info('%s: GetManagedObjects: %s', self.path,
|
|
', '.join(results.keys()))
|
|
return results
|
|
|
|
|
|
@dbus.service.signal(mm1_constants.I_OBJECT_MANAGER,
|
|
signature='oa{sa{sv}}')
|
|
def InterfacesAdded(self, object_path, interfaces_and_properties):
|
|
"""
|
|
The InterfacesAdded signal is emitted when either a new object is added
|
|
or when an existing object gains one or more interfaces.
|
|
|
|
@param object_path: Path of the added object.
|
|
@param interfaces_and_properties: The complete property dictionary that
|
|
belongs to the recently added object.
|
|
|
|
"""
|
|
logging.info((self.path + ': InterfacesAdded(' + object_path +
|
|
', ' + str(interfaces_and_properties)) + ')')
|
|
|
|
|
|
@dbus.service.signal(mm1_constants.I_OBJECT_MANAGER, signature='oas')
|
|
def InterfacesRemoved(self, object_path, interfaces):
|
|
"""
|
|
The InterfacesRemoved signal is emitted whenever an object is removed
|
|
or it loses one or more interfaces.
|
|
|
|
@param object_path: Path of the remove object.
|
|
@param interfaces_and_properties: The complete property dictionary that
|
|
belongs to the recently removed object.
|
|
|
|
"""
|
|
logging.info((self.path + ': InterfacesRemoved(' + object_path +
|
|
', ' + str(interfaces) + ')'))
|