317 lines
11 KiB
Python
317 lines
11 KiB
Python
# Copyright (c) 2014 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.
|
|
|
|
"""
|
|
This module provides bindings for ModemManager1.
|
|
|
|
"""
|
|
|
|
import dbus
|
|
import dbus.mainloop.glib
|
|
|
|
from autotest_lib.client.bin import utils
|
|
from autotest_lib.client.cros.cellular import mm1_constants
|
|
|
|
|
|
def _is_unknown_dbus_binding_exception(e):
|
|
return (isinstance(e, dbus.exceptions.DBusException) and
|
|
e.get_dbus_name() in [mm1_constants.DBUS_SERVICE_UNKNOWN,
|
|
mm1_constants.DBUS_UNKNOWN_METHOD,
|
|
mm1_constants.DBUS_UNKNOWN_OBJECT,
|
|
mm1_constants.DBUS_UNKNOWN_INTERFACE])
|
|
|
|
|
|
class ModemManager1ProxyError(Exception):
|
|
"""Exceptions raised by ModemManager1ProxyError and it's children."""
|
|
pass
|
|
|
|
|
|
class ModemManager1Proxy(object):
|
|
"""A wrapper around a DBus proxy for ModemManager1."""
|
|
|
|
# Amount of time to wait between attempts to connect to ModemManager1.
|
|
CONNECT_WAIT_INTERVAL_SECONDS = 0.2
|
|
|
|
@classmethod
|
|
def get_proxy(cls, bus=None, timeout_seconds=10):
|
|
"""Connect to ModemManager1 over DBus, retrying if necessary.
|
|
|
|
After connecting to ModemManager1, this method will verify that
|
|
ModemManager1 is answering RPCs.
|
|
|
|
@param bus: D-Bus bus to use, or specify None and this object will
|
|
create a mainloop and bus.
|
|
@param timeout_seconds: float number of seconds to try connecting
|
|
A value <= 0 will cause the method to return immediately,
|
|
without trying to connect.
|
|
@return a ModemManager1Proxy instance if we connected, or None
|
|
otherwise.
|
|
@raise ModemManager1ProxyError if it fails to connect to
|
|
ModemManager1.
|
|
|
|
"""
|
|
def _connect_to_mm1(bus):
|
|
try:
|
|
# We create instance of class on which this classmethod was
|
|
# called. This way, calling
|
|
# SubclassOfModemManager1Proxy.get_proxy() will get a proxy of
|
|
# the right type.
|
|
return cls(bus=bus)
|
|
except dbus.exceptions.DBusException as e:
|
|
if _is_unknown_dbus_binding_exception(e):
|
|
return None
|
|
raise ModemManager1ProxyError(
|
|
'Error connecting to ModemManager1. DBus error: |%s|',
|
|
repr(e))
|
|
|
|
utils.poll_for_condition(
|
|
lambda: _connect_to_mm1(bus) is not None,
|
|
exception=ModemManager1ProxyError(
|
|
'Timed out connecting to ModemManager1'),
|
|
timeout=timeout_seconds,
|
|
sleep_interval=ModemManager1Proxy.CONNECT_WAIT_INTERVAL_SECONDS)
|
|
connection = _connect_to_mm1(bus)
|
|
|
|
# Check to make sure ModemManager1 is responding to DBus requests by
|
|
# setting the logging to debug.
|
|
connection.manager.SetLogging('DEBUG', timeout=timeout_seconds)
|
|
|
|
return connection
|
|
|
|
|
|
def __init__(self, bus=None):
|
|
if bus is None:
|
|
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
|
bus = dbus.SystemBus()
|
|
self._bus = bus
|
|
self._manager = dbus.Interface(
|
|
self._bus.get_object(mm1_constants.I_MODEM_MANAGER,
|
|
mm1_constants.MM1),
|
|
mm1_constants.I_MODEM_MANAGER)
|
|
|
|
|
|
@property
|
|
def manager(self):
|
|
"""@return the DBus ModemManager1 Manager object."""
|
|
return self._manager
|
|
|
|
|
|
def get_modem(self):
|
|
"""
|
|
Return the one and only modem object.
|
|
|
|
This method distinguishes between no modem and more than one modem.
|
|
In the former, this could happen if the modem has not yet surfaced and
|
|
is not really considered an error. The caller can wait for the modem
|
|
by repeatedly calling this method. In the latter, it is a clear error
|
|
condition and an exception will be raised.
|
|
|
|
Every call to |get_modem| obtains a fresh DBus proxy for the modem. So,
|
|
if the modem DBus object has changed between two calls to this method,
|
|
the proxy returned will be for the currently exported modem.
|
|
|
|
@return a ModemProxy object. Return None if no modem is found.
|
|
@raise ModemManager1ProxyError unless exactly one modem is found.
|
|
|
|
"""
|
|
try:
|
|
object_manager = dbus.Interface(
|
|
self._bus.get_object(mm1_constants.I_MODEM_MANAGER,
|
|
mm1_constants.MM1),
|
|
mm1_constants.I_OBJECT_MANAGER)
|
|
modems = object_manager.GetManagedObjects()
|
|
except dbus.exceptions.DBusException as e:
|
|
raise ModemManager1ProxyError(
|
|
'Failed to list the available modems. DBus error: |%s|',
|
|
repr(e))
|
|
|
|
if not modems:
|
|
return None
|
|
elif len(modems) > 1:
|
|
raise ModemManager1ProxyError(
|
|
'Expected one modem object, found %d', len(modems))
|
|
|
|
modem_proxy = ModemProxy(self._bus, modems.keys()[0])
|
|
# Check that this object is valid
|
|
try:
|
|
modem_proxy.modem.GetAll(mm1_constants.I_MODEM,
|
|
dbus_interface=mm1_constants.I_PROPERTIES)
|
|
return modem_proxy
|
|
except dbus.exceptions.DBusException as e:
|
|
if _is_unknown_dbus_binding_exception(e):
|
|
return None
|
|
raise ModemManager1ProxyError(
|
|
'Failed to obtain dbus object for the modem. DBus error: '
|
|
'|%s|', repr(e))
|
|
|
|
|
|
def wait_for_modem(self, timeout_seconds):
|
|
"""
|
|
Wait for the modem to appear.
|
|
|
|
@param timeout_seconds: Number of seconds to wait for modem to appear.
|
|
@return a ModemProxy object.
|
|
@raise ModemManager1ProxyError if no modem is found within the timeout
|
|
or if more than one modem is found. NOTE: This method does not
|
|
wait for a second modem. The exception is raised if there is
|
|
more than one modem at the time of polling.
|
|
|
|
"""
|
|
return utils.poll_for_condition(
|
|
self.get_modem,
|
|
exception=ModemManager1ProxyError('No modem found'),
|
|
timeout=timeout_seconds)
|
|
|
|
|
|
class ModemProxy(object):
|
|
"""A wrapper around a DBus proxy for ModemManager1 modem object."""
|
|
|
|
# Amount of time to wait for a state transition.
|
|
STATE_TRANSITION_WAIT_SECONDS = 10
|
|
|
|
def __init__(self, bus, path):
|
|
self._bus = bus
|
|
self._modem = self._bus.get_object(mm1_constants.I_MODEM_MANAGER, path)
|
|
|
|
|
|
@property
|
|
def modem(self):
|
|
"""@return the DBus modem object."""
|
|
return self._modem
|
|
|
|
|
|
@property
|
|
def iface_modem(self):
|
|
"""@return org.freedesktop.ModemManager1.Modem DBus interface."""
|
|
return dbus.Interface(self._modem, mm1_constants.I_MODEM)
|
|
|
|
|
|
@property
|
|
def iface_simple_modem(self):
|
|
"""@return org.freedesktop.ModemManager1.Simple DBus interface."""
|
|
return dbus.Interface(self._modem, mm1_constants.I_MODEM_SIMPLE)
|
|
|
|
|
|
@property
|
|
def iface_gsm_modem(self):
|
|
"""@return org.freedesktop.ModemManager1.Modem3gpp DBus interface."""
|
|
return dbus.Interface(self._modem, mm1_constants.I_MODEM_3GPP)
|
|
|
|
|
|
@property
|
|
def iface_cdma_modem(self):
|
|
"""@return org.freedesktop.ModemManager1.ModemCdma DBus interface."""
|
|
return dbus.Interface(self._modem, mm1_constants.I_MODEM_CDMA)
|
|
|
|
|
|
@property
|
|
def iface_properties(self):
|
|
"""@return org.freedesktop.DBus.Properties DBus interface."""
|
|
return dbus.Interface(self._modem, dbus.PROPERTIES_IFACE)
|
|
|
|
|
|
def properties(self, iface):
|
|
"""Return the properties associated with the specified interface.
|
|
|
|
@param iface: Name of interface to retrieve the properties from.
|
|
@return array of properties.
|
|
|
|
"""
|
|
return self.iface_properties.GetAll(iface)
|
|
|
|
|
|
def get_sim(self):
|
|
"""
|
|
Return the SIM proxy object associated with this modem.
|
|
|
|
@return SimProxy object or None if no SIM exists.
|
|
|
|
"""
|
|
sim_path = self.properties(mm1_constants.I_MODEM).get('Sim')
|
|
if not sim_path:
|
|
return None
|
|
sim_proxy = SimProxy(self._bus, sim_path)
|
|
# Check that this object is valid
|
|
try:
|
|
sim_proxy.properties(mm1_constants.I_SIM)
|
|
return sim_proxy
|
|
except dbus.exceptions.DBusException as e:
|
|
if _is_unknown_dbus_binding_exception(e):
|
|
return None
|
|
raise ModemManager1ProxyError(
|
|
'Failed to obtain dbus object for the SIM. DBus error: '
|
|
'|%s|', repr(e))
|
|
|
|
|
|
def wait_for_states(self, states,
|
|
timeout_seconds=STATE_TRANSITION_WAIT_SECONDS):
|
|
"""
|
|
Wait for the modem to transition to a state in |states|.
|
|
|
|
This method does not support transitory states (eg. enabling,
|
|
disabling, connecting, disconnecting, etc).
|
|
|
|
@param states: List of states the modem can transition to.
|
|
@param timeout_seconds: Max number of seconds to wait.
|
|
@raise ModemManager1ProxyError if the modem does not transition to
|
|
one of the accepted states.
|
|
|
|
"""
|
|
for state in states:
|
|
if state in [mm1_constants.MM_MODEM_STATE_INITIALIZING,
|
|
mm1_constants.MM_MODEM_STATE_DISABLING,
|
|
mm1_constants.MM_MODEM_STATE_ENABLING,
|
|
mm1_constants.MM_MODEM_STATE_SEARCHING,
|
|
mm1_constants.MM_MODEM_STATE_DISCONNECTING,
|
|
mm1_constants.MM_MODEM_STATE_CONNECTING]:
|
|
raise ModemManager1ProxyError(
|
|
'wait_for_states() does not support transitory states.')
|
|
|
|
utils.poll_for_condition(
|
|
lambda: self.properties(mm1_constants.I_MODEM)[
|
|
mm1_constants.MM_MODEM_PROPERTY_NAME_STATE] in states,
|
|
exception=ModemManager1ProxyError(
|
|
'Timed out waiting for modem to enter one of these '
|
|
'states: %s, current state=%s',
|
|
states,
|
|
self.properties(mm1_constants.I_MODEM)[
|
|
mm1_constants.MM_MODEM_PROPERTY_NAME_STATE]),
|
|
timeout=timeout_seconds)
|
|
|
|
|
|
class SimProxy(object):
|
|
"""A wrapper around a DBus proxy for ModemManager1 SIM object."""
|
|
|
|
def __init__(self, bus, path):
|
|
self._bus = bus
|
|
self._sim = self._bus.get_object(mm1_constants.I_MODEM_MANAGER, path)
|
|
|
|
|
|
@property
|
|
def sim(self):
|
|
"""@return the DBus SIM object."""
|
|
return self._sim
|
|
|
|
|
|
@property
|
|
def iface_properties(self):
|
|
"""@return org.freedesktop.DBus.Properties DBus interface."""
|
|
return dbus.Interface(self._sim, dbus.PROPERTIES_IFACE)
|
|
|
|
|
|
@property
|
|
def iface_sim(self):
|
|
"""@return org.freedesktop.ModemManager1.Sim DBus interface."""
|
|
return dbus.Interface(self._sim, mm1_constants.I_SIM)
|
|
|
|
|
|
def properties(self, iface=mm1_constants.I_SIM):
|
|
"""Return the properties associated with the specified interface.
|
|
|
|
@param iface: Name of interface to retrieve the properties from.
|
|
@return array of properties.
|
|
|
|
"""
|
|
return self.iface_properties.GetAll(iface)
|