blob: ae1808578488d0811d17258ba2aa4d26a0b6d963 [file] [log] [blame]
# Copyright (c) 2013 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.
import base64
import json
from autotest_lib.client.cros import constants
from autotest_lib.server import autotest
class BluetoothDevice(object):
"""BluetoothDevice is a thin layer of logic over a remote DUT.
The Autotest host object representing the remote DUT, passed to this
class on initialization, can be accessed from its host property.
"""
XMLRPC_BRINGUP_TIMEOUT_SECONDS = 60
XMLRPC_LOG_PATH = '/var/log/bluetooth_xmlrpc_device.log'
def __init__(self, device_host):
"""Construct a BluetoothDevice.
@param device_host: host object representing a remote host.
"""
self.host = device_host
# Make sure the client library is on the device so that the proxy code
# is there when we try to call it.
client_at = autotest.Autotest(self.host)
client_at.install()
# Start up the XML-RPC proxy on the client.
self._proxy = self.host.rpc_server_tracker.xmlrpc_connect(
constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_COMMAND,
constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_PORT,
command_name=
constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_CLEANUP_PATTERN,
ready_test_name=
constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_READY_METHOD,
timeout_seconds=self.XMLRPC_BRINGUP_TIMEOUT_SECONDS,
logfile=self.XMLRPC_LOG_PATH)
# Get some static information about the bluetooth adapter.
properties = self.get_adapter_properties()
self.bluez_version = properties.get('Name')
self.address = properties.get('Address')
self.bluetooth_class = properties.get('Class')
self.UUIDs = properties.get('UUIDs')
def start_bluetoothd(self):
"""start bluetoothd.
@returns: True if bluetoothd is started correctly.
False otherwise.
"""
return self._proxy.start_bluetoothd()
def stop_bluetoothd(self):
"""stop bluetoothd.
@returns: True if bluetoothd is stopped correctly.
False otherwise.
"""
return self._proxy.stop_bluetoothd()
def is_bluetoothd_running(self):
"""Is bluetoothd running?
@returns: True if bluetoothd is running
"""
return self._proxy.is_bluetoothd_running()
def reset_on(self):
"""Reset the adapter and settings and power up the adapter.
@return True on success, False otherwise.
"""
return self._proxy.reset_on()
def reset_off(self):
"""Reset the adapter and settings, leave the adapter powered off.
@return True on success, False otherwise.
"""
return self._proxy.reset_off()
def has_adapter(self):
"""@return True if an adapter is present, False if not."""
return self._proxy.has_adapter()
def set_powered(self, powered):
"""Set the adapter power state.
@param powered: adapter power state to set (True or False).
@return True on success, False otherwise.
"""
return self._proxy.set_powered(powered)
def is_powered_on(self):
"""Is the adapter powered on?
@returns: True if the adapter is powered on
"""
properties = self.get_adapter_properties()
return bool(properties.get(u'Powered'))
def get_hci(self):
"""Get hci of the adapter; normally, it is 'hci0'.
@returns: the hci name of the adapter.
"""
dev_info = self.get_dev_info()
hci = (dev_info[1] if isinstance(dev_info, list) and
len(dev_info) > 1 else None)
return hci
def get_address(self):
"""Get the bluetooth address of the adapter.
An example of the bluetooth address of the adapter: '6C:29:95:1A:D4:6F'
@returns: the bluetooth address of the adapter.
"""
return self.address
def get_bluez_version(self):
"""Get bluez version.
An exmaple of bluez version: 'BlueZ 5.39'
@returns: the bluez version
"""
return self.bluez_version
def get_bluetooth_class(self):
"""Get the bluetooth class of the adapter.
An example of the bluetooth class of a chromebook: 4718852
@returns: the bluetooth class.
"""
return self.bluetooth_class
def get_UUIDs(self):
"""Get the UUIDs.
An example of UUIDs:
[u'00001112-0000-1000-8000-00805f9b34fb',
u'00001801-0000-1000-8000-00805f9b34fb',
u'0000110a-0000-1000-8000-00805f9b34fb',
u'0000111f-0000-1000-8000-00805f9b34fb',
u'00001200-0000-1000-8000-00805f9b34fb',
u'00001800-0000-1000-8000-00805f9b34fb']
@returns: the list of the UUIDs.
"""
return self.UUIDs
def set_discoverable(self, discoverable):
"""Set the adapter discoverable state.
@param discoverable: adapter discoverable state to set (True or False).
@return True on success, False otherwise.
"""
return self._proxy.set_discoverable(discoverable)
def is_discoverable(self):
"""Is the adapter in the discoverable state?
@return True if discoverable. False otherwise.
"""
properties = self.get_adapter_properties()
return properties.get('Discoverable') == 1
def set_pairable(self, pairable):
"""Set the adapter pairable state.
@param pairable: adapter pairable state to set (True or False).
@return True on success, False otherwise.
"""
return self._proxy.set_pairable(pairable)
def is_pairable(self):
"""Is the adapter in the pairable state?
@return True if pairable. False otherwise.
"""
properties = self.get_adapter_properties()
return properties.get('Pairable') == 1
def get_adapter_properties(self):
"""Read the adapter properties from the Bluetooth Daemon.
An example of the adapter properties looks like
{u'Name': u'BlueZ 5.35',
u'Alias': u'Chromebook',
u'Modalias': u'bluetooth:v00E0p2436d0400',
u'Powered': 1,
u'DiscoverableTimeout': 180,
u'PairableTimeout': 0,
u'Discoverable': 0,
u'Address': u'6C:29:95:1A:D4:6F',
u'Discovering': 0,
u'Pairable': 1,
u'Class': 4718852,
u'UUIDs': [u'00001112-0000-1000-8000-00805f9b34fb',
u'00001801-0000-1000-8000-00805f9b34fb',
u'0000110a-0000-1000-8000-00805f9b34fb',
u'0000111f-0000-1000-8000-00805f9b34fb',
u'00001200-0000-1000-8000-00805f9b34fb',
u'00001800-0000-1000-8000-00805f9b34fb']}
@return the properties as a dictionary on success,
the value False otherwise.
"""
return json.loads(self._proxy.get_adapter_properties())
def read_version(self):
"""Read the version of the management interface from the Kernel.
@return the version as a tuple of:
( version, revision )
"""
return json.loads(self._proxy.read_version())
def read_supported_commands(self):
"""Read the set of supported commands from the Kernel.
@return set of supported commands as arrays in a tuple of:
( commands, events )
"""
return json.loads(self._proxy.read_supported_commands())
def read_index_list(self):
"""Read the list of currently known controllers from the Kernel.
@return array of controller indexes.
"""
return json.loads(self._proxy.read_index_list())
def read_info(self):
"""Read the adapter information from the Kernel.
An example of the adapter information looks like
[u'6C:29:95:1A:D4:6F', 6, 2, 65535, 2769, 4718852, u'Chromebook', u'']
@return the information as a tuple of:
( address, bluetooth_version, manufacturer_id,
supported_settings, current_settings, class_of_device,
name, short_name )
"""
return json.loads(self._proxy.read_info())
def add_device(self, address, address_type, action):
"""Add a device to the Kernel action list.
@param address: Address of the device to add.
@param address_type: Type of device in @address.
@param action: Action to take.
@return tuple of ( address, address_type ) on success,
None on failure.
"""
return json.loads(self._proxy.add_device(address, address_type, action))
def remove_device(self, address, address_type):
"""Remove a device from the Kernel action list.
@param address: Address of the device to remove.
@param address_type: Type of device in @address.
@return tuple of ( address, address_type ) on success,
None on failure.
"""
return json.loads(self._proxy.remove_device(address, address_type))
def get_devices(self):
"""Read information about remote devices known to the adapter.
An example of the device information of RN-42 looks like
[{u'Name': u'RNBT-A96F',
u'Alias': u'RNBT-A96F',
u'Adapter': u'/org/bluez/hci0',
u'LegacyPairing': 0,
u'Paired': 1,
u'Connected': 0,
u'UUIDs': [u'00001124-0000-1000-8000-00805f9b34fb'],
u'Address': u'00:06:66:75:A9:6F',
u'Icon': u'input-mouse',
u'Class': 1408,
u'Trusted': 1,
u'Blocked': 0}]
@return the properties of each device as an array of
dictionaries on success, the value False otherwise.
"""
return json.loads(self._proxy.get_devices())
def get_device_properties(self, address):
"""Read information about remote devices known to the adapter.
An example of the device information of RN-42 looks like
@param address: Address of the device to pair.
@param pin: The pin code of the device to pair.
@param timeout: The timeout in seconds for pairing.
@returns: a dictionary of device properties of the device on success;
an empty dictionary otherwise.
"""
return json.loads(self._proxy.get_device_by_address(address))
for device in self.get_devices():
if device.get['Address'] == address:
return device
return dict()
def start_discovery(self):
"""Start discovery of remote devices.
Obtain the discovered device information using get_devices(), called
stop_discovery() when done.
@return True on success, False otherwise.
"""
return self._proxy.start_discovery()
def stop_discovery(self):
"""Stop discovery of remote devices.
@return True on success, False otherwise.
"""
return self._proxy.stop_discovery()
def is_discovering(self):
"""Is it discovering?
@return True if it is discovering. False otherwise.
"""
return self.get_adapter_properties().get('Discovering') == 1
def get_dev_info(self):
"""Read raw HCI device information.
An example of the device information looks like:
[0, u'hci0', u'6C:29:95:1A:D4:6F', 13, 0, 1, 581900950526, 52472, 7,
32768, 1021, 5, 96, 6, 0, 0, 151, 151, 0, 0, 0, 0, 1968, 12507]
@return tuple of (index, name, address, flags, device_type, bus_type,
features, pkt_type, link_policy, link_mode,
acl_mtu, acl_pkts, sco_mtu, sco_pkts,
err_rx, err_tx, cmd_tx, evt_rx, acl_tx, acl_rx,
sco_tx, sco_rx, byte_rx, byte_tx) on success,
None on failure.
"""
return json.loads(self._proxy.get_dev_info())
def register_profile(self, path, uuid, options):
"""Register new profile (service).
@param path: Path to the profile object.
@param uuid: Service Class ID of the service as string.
@param options: Dictionary of options for the new service, compliant
with BlueZ D-Bus Profile API standard.
@return True on success, False otherwise.
"""
return self._proxy.register_profile(path, uuid, options)
def has_device(self, address):
"""Checks if the device with a given address exists.
@param address: Address of the device.
@returns: True if there is a device with that address.
False otherwise.
"""
return self._proxy.has_device(address)
def device_is_paired(self, address):
"""Checks if a device is paired.
@param address: address of the device.
@returns: True if device is paired. False otherwise.
"""
return self._proxy.device_is_paired(address)
def device_services_resolved(self, address):
"""Checks if services are resolved for a device.
@param address: address of the device.
@returns: True if services are resolved. False otherwise.
"""
return self._proxy.device_services_resolved(address)
def set_trusted(self, address, trusted=True):
"""Set the device trusted.
@param address: The bluetooth address of the device.
@param trusted: True or False indicating whether to set trusted or not.
@returns: True if successful. False otherwise.
"""
return self._proxy.set_trusted(address, trusted)
def pair_legacy_device(self, address, pin, trusted, timeout):
"""Pairs a device with a given pin code.
Registers an agent who handles pin code request and
pairs a device with known pin code.
@param address: Address of the device to pair.
@param pin: The pin code of the device to pair.
@param trusted: indicating whether to set the device trusted.
@param timeout: The timeout in seconds for pairing.
@returns: True on success. False otherwise.
"""
return self._proxy.pair_legacy_device(address, pin, trusted, timeout)
def remove_device_object(self, address):
"""Removes a device object and the pairing information.
Calls RemoveDevice method to remove remote device
object and the pairing information.
@param address: address of the device to unpair.
@returns: True on success. False otherwise.
"""
return self._proxy.remove_device_object(address)
def connect_device(self, address):
"""Connects a device.
Connects a device if it is not connected.
@param address: Address of the device to connect.
@returns: True on success. False otherwise.
"""
return self._proxy.connect_device(address)
def device_is_connected(self, address):
"""Checks if a device is connected.
@param address: Address of the device to check if it is connected.
@returns: True if device is connected. False otherwise.
"""
return self._proxy.device_is_connected(address)
def disconnect_device(self, address):
"""Disconnects a device.
Disconnects a device if it is connected.
@param address: Address of the device to disconnect.
@returns: True on success. False otherwise.
"""
return self._proxy.disconnect_device(address)
def btmon_start(self):
"""Start btmon monitoring."""
self._proxy.btmon_start()
def btmon_stop(self):
"""Stop btmon monitoring."""
self._proxy.btmon_stop()
def btmon_get(self, search_str='', start_str=''):
"""Get btmon output contents.
@param search_str: only lines with search_str would be kept.
@param start_str: all lines before the occurrence of start_str would be
filtered.
@returns: the recorded btmon output.
"""
return self._proxy.btmon_get(search_str, start_str)
def btmon_find(self, pattern_str):
"""Find if a pattern string exists in btmon output.
@param pattern_str: the pattern string to find.
@returns: True on success. False otherwise.
"""
return self._proxy.btmon_find(pattern_str)
def register_advertisement(self, advertisement_data):
"""Register an advertisement.
Note that rpc supports only conformable types. Hence, a
dict about the advertisement is passed as a parameter such
that the advertisement object could be contructed on the host.
@param advertisement_data: a dict of the advertisement for
the adapter to register.
@returns: True on success. False otherwise.
"""
return self._proxy.register_advertisement(advertisement_data)
def unregister_advertisement(self, advertisement_data):
"""Unregister an advertisement.
@param advertisement_data: a dict of the advertisement to unregister.
@returns: True on success. False otherwise.
"""
return self._proxy.unregister_advertisement(advertisement_data)
def set_advertising_intervals(self, min_adv_interval_ms,
max_adv_interval_ms):
"""Set advertising intervals.
@param min_adv_interval_ms: the min advertising interval in ms.
@param max_adv_interval_ms: the max advertising interval in ms.
@returns: True on success. False otherwise.
"""
return self._proxy.set_advertising_intervals(min_adv_interval_ms,
max_adv_interval_ms)
def reset_advertising(self):
"""Reset advertising.
This includes unregister all advertisements, reset advertising
intervals, and disable advertising.
@returns: True on success. False otherwise.
"""
return self._proxy.reset_advertising()
def read_characteristic(self, uuid, address):
"""Reads the value of a gatt characteristic.
Reads the current value of a gatt characteristic.
@param uuid: The uuid of the characteristic to read, as a string.
@param address: The MAC address of the remote device.
@returns: A byte array containing the value of the if the uuid/address
was found in the object tree.
None if the uuid/address was not found in the object tree, or
if a DBus exception was raised by the read operation.
"""
value = self._proxy.read_characteristic(uuid, address)
if value is None:
return None
return bytearray(base64.standard_b64decode(value))
def write_characteristic(self, uuid, address, bytes_to_write):
"""Performs a write operation on a gatt characteristic.
Writes to a GATT characteristic on a remote device.
@param uuid: The uuid of the characteristic to write to, as a string.
@param address: The MAC address of the remote device, as a string.
@param bytes_to_write: A byte array containing the data to write.
@returns: True if the write operation does not raise an exception.
None if the uuid/address was not found in the object tree, or
if a DBus exception was raised by the write operation.
"""
return self._proxy.write_characteristic(
uuid, address, base64.standard_b64encode(bytes_to_write))
def is_characteristic_path_resolved(self, uuid, address):
"""Checks whether a characteristic is in the object tree.
Checks whether a characteristic is curently found in the object tree.
@param uuid: The uuid of the characteristic to search for.
@param address: The MAC address of the device on which to search for
the characteristic.
@returns: True if the characteristic is found, False otherwise.
"""
return self._proxy.is_characteristic_path_resolved(uuid, address)
def copy_logs(self, destination):
"""Copy the logs generated by this device to a given location.
@param destination: destination directory for the logs.
"""
self.host.collect_logs(self.XMLRPC_LOG_PATH, destination)
def close(self, close_host=True):
"""Tear down state associated with the client.
@param close_host: If True, shut down the xml rpc server by closing the
underlying host object (which also shuts down all other xml rpc
servers running on the DUT). Otherwise, only shut down the
bluetooth device xml rpc server, which can be desirable if the host
object and/or other xml rpc servers need to be used afterwards.
"""
# Turn off the discoverable flag since it may affect future tests.
self._proxy.set_discoverable(False)
# Leave the adapter powered off, but don't do a full reset.
self._proxy.set_powered(False)
# This kills the RPC server.
if close_host:
self.host.close()
else:
self.host.rpc_server_tracker.disconnect(
constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_PORT)