1225 lines
44 KiB
Python
1225 lines
44 KiB
Python
# 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 array
|
|
import btsocket
|
|
import fcntl
|
|
import logging
|
|
import socket
|
|
import struct
|
|
|
|
|
|
# Constants from lib/mgmt.h in BlueZ source
|
|
MGMT_INDEX_NONE = 0xFFFF
|
|
|
|
MGMT_HDR_SIZE = 6
|
|
|
|
MGMT_STATUS_SUCCESS = 0x00
|
|
MGMT_STATUS_UNKNOWN_COMMAND = 0x01
|
|
MGMT_STATUS_NOT_CONNECTED = 0x02
|
|
MGMT_STATUS_FAILED = 0x03
|
|
MGMT_STATUS_CONNECT_FAILED = 0x04
|
|
MGMT_STATUS_AUTH_FAILED = 0x05
|
|
MGMT_STATUS_NOT_PAIRED = 0x06
|
|
MGMT_STATUS_NO_RESOURCES = 0x07
|
|
MGMT_STATUS_TIMEOUT = 0x08
|
|
MGMT_STATUS_ALREADY_CONNECTED = 0x09
|
|
MGMT_STATUS_BUSY = 0x0a
|
|
MGMT_STATUS_REJECTED = 0x0b
|
|
MGMT_STATUS_NOT_SUPPORTED = 0x0c
|
|
MGMT_STATUS_INVALID_PARAMS = 0x0d
|
|
MGMT_STATUS_DISCONNECTED = 0x0e
|
|
MGMT_STATUS_NOT_POWERED = 0x0f
|
|
MGMT_STATUS_CANCELLED = 0x10
|
|
MGMT_STATUS_INVALID_INDEX = 0x11
|
|
MGMT_STATUS_RFKILLED = 0x12
|
|
|
|
MGMT_OP_READ_VERSION = 0x0001
|
|
MGMT_OP_READ_COMMANDS = 0x0002
|
|
MGMT_OP_READ_INDEX_LIST = 0x0003
|
|
MGMT_OP_READ_INFO = 0x0004
|
|
MGMT_OP_SET_POWERED = 0x0005
|
|
MGMT_OP_SET_DISCOVERABLE = 0x0006
|
|
MGMT_OP_SET_CONNECTABLE = 0x0007
|
|
MGMT_OP_SET_FAST_CONNECTABLE = 0x0008
|
|
MGMT_OP_SET_PAIRABLE = 0x0009
|
|
MGMT_OP_SET_LINK_SECURITY = 0x000A
|
|
MGMT_OP_SET_SSP = 0x000B
|
|
MGMT_OP_SET_HS = 0x000C
|
|
MGMT_OP_SET_LE = 0x000D
|
|
MGMT_OP_SET_DEV_CLASS = 0x000E
|
|
MGMT_OP_SET_LOCAL_NAME = 0x000F
|
|
MGMT_OP_ADD_UUID = 0x0010
|
|
MGMT_OP_REMOVE_UUID = 0x0011
|
|
MGMT_OP_LOAD_LINK_KEYS = 0x0012
|
|
MGMT_OP_LOAD_LONG_TERM_KEYS = 0x0013
|
|
MGMT_OP_DISCONNECT = 0x0014
|
|
MGMT_OP_GET_CONNECTIONS = 0x0015
|
|
MGMT_OP_PIN_CODE_REPLY = 0x0016
|
|
MGMT_OP_PIN_CODE_NEG_REPLY = 0x0017
|
|
MGMT_OP_SET_IO_CAPABILITY = 0x0018
|
|
MGMT_OP_PAIR_DEVICE = 0x0019
|
|
MGMT_OP_CANCEL_PAIR_DEVICE = 0x001A
|
|
MGMT_OP_UNPAIR_DEVICE = 0x001B
|
|
MGMT_OP_USER_CONFIRM_REPLY = 0x001C
|
|
MGMT_OP_USER_CONFIRM_NEG_REPLY = 0x001D
|
|
MGMT_OP_USER_PASSKEY_REPLY = 0x001E
|
|
MGMT_OP_USER_PASSKEY_NEG_REPLY = 0x001F
|
|
MGMT_OP_READ_LOCAL_OOB_DATA = 0x0020
|
|
MGMT_OP_ADD_REMOTE_OOB_DATA = 0x0021
|
|
MGMT_OP_REMOVE_REMOTE_OOB_DATA = 0x0022
|
|
MGMT_OP_START_DISCOVERY = 0x0023
|
|
MGMT_OP_STOP_DISCOVERY = 0x0024
|
|
MGMT_OP_CONFIRM_NAME = 0x0025
|
|
MGMT_OP_BLOCK_DEVICE = 0x0026
|
|
MGMT_OP_UNBLOCK_DEVICE = 0x0027
|
|
MGMT_OP_SET_DEVICE_ID = 0x0028
|
|
MGMT_OP_SET_ADVERTISING = 0x0029
|
|
MGMT_OP_SET_BREDR = 0x002A
|
|
MGMT_OP_SET_STATIC_ADDRESS = 0x002B
|
|
MGMT_OP_SET_SCAN_PARAMS = 0x002C
|
|
MGMT_OP_SET_SECURE_CONN = 0x002D
|
|
MGMT_OP_SET_DEBUG_KEYS = 0x002E
|
|
MGMT_OP_SET_PRIVACY = 0x002F
|
|
MGMT_OP_LOAD_IRKS = 0x0030
|
|
MGMT_OP_GET_CONN_INFO = 0x0031
|
|
MGMT_OP_GET_CLOCK_INFO = 0x0032
|
|
MGMT_OP_ADD_DEVICE = 0x0033
|
|
MGMT_OP_REMOVE_DEVICE = 0x0034
|
|
MGMT_OP_LOAD_CONN_PARAM = 0x0035
|
|
MGMT_OP_READ_UNCONF_INDEX_LIST = 0x0036
|
|
MGMT_OP_READ_CONFIG_INFO = 0x0037
|
|
MGMT_OP_SET_EXTERNAL_CONFIG = 0x0038
|
|
MGMT_OP_SET_PUBLIC_ADDRESS = 0x0039
|
|
|
|
MGMT_EV_CMD_COMPLETE = 0x0001
|
|
MGMT_EV_CMD_STATUS = 0x0002
|
|
MGMT_EV_CONTROLLER_ERROR = 0x0003
|
|
MGMT_EV_INDEX_ADDED = 0x0004
|
|
MGMT_EV_INDEX_REMOVED = 0x0005
|
|
MGMT_EV_NEW_SETTINGS = 0x0006
|
|
MGMT_EV_CLASS_OF_DEV_CHANGED = 0x0007
|
|
MGMT_EV_LOCAL_NAME_CHANGED = 0x0008
|
|
MGMT_EV_NEW_LINK_KEY = 0x0009
|
|
MGMT_EV_NEW_LONG_TERM_KEY = 0x000A
|
|
MGMT_EV_DEVICE_CONNECTED = 0x000B
|
|
MGMT_EV_DEVICE_DISCONNECTED = 0x000C
|
|
MGMT_EV_CONNECT_FAILED = 0x000D
|
|
MGMT_EV_PIN_CODE_REQUEST = 0x000E
|
|
MGMT_EV_USER_CONFIRM_REQUEST = 0x000F
|
|
MGMT_EV_USER_PASSKEY_REQUEST = 0x0010
|
|
MGMT_EV_AUTH_FAILED = 0x0011
|
|
MGMT_EV_DEVICE_FOUND = 0x0012
|
|
MGMT_EV_DISCOVERING = 0x0013
|
|
MGMT_EV_DEVICE_BLOCKED = 0x0014
|
|
MGMT_EV_DEVICE_UNBLOCKED = 0x0015
|
|
MGMT_EV_DEVICE_UNPAIRED = 0x0016
|
|
MGMT_EV_PASSKEY_NOTIFY = 0x0017
|
|
MGMT_EV_NEW_IRK = 0x0018
|
|
MGMT_EV_NEW_CSRK = 0x0019
|
|
MGMT_EV_DEVICE_ADDED = 0x001a
|
|
MGMT_EV_DEVICE_REMOVED = 0x001b
|
|
MGMT_EV_NEW_CONN_PARAM = 0x001c
|
|
MGMT_EV_UNCONF_INDEX_ADDED = 0x001d
|
|
MGMT_EV_UNCONF_INDEX_REMOVED = 0x001e
|
|
MGMT_EV_NEW_CONFIG_OPTIONS = 0x001f
|
|
|
|
# Settings returned by MGMT_OP_READ_INFO
|
|
MGMT_SETTING_POWERED = 0x00000001
|
|
MGMT_SETTING_CONNECTABLE = 0x00000002
|
|
MGMT_SETTING_FAST_CONNECTABLE = 0x00000004
|
|
MGMT_SETTING_DISCOVERABLE = 0x00000008
|
|
MGMT_SETTING_PAIRABLE = 0x00000010
|
|
MGMT_SETTING_LINK_SECURITY = 0x00000020
|
|
MGMT_SETTING_SSP = 0x00000040
|
|
MGMT_SETTING_BREDR = 0x00000080
|
|
MGMT_SETTING_HS = 0x00000100
|
|
MGMT_SETTING_LE = 0x00000200
|
|
MGMT_SETTING_ADVERTISING = 0x00000400
|
|
MGMT_SETTING_SECURE_CONNECTIONS = 0x00000800
|
|
MGMT_SETTING_DEBUG_KEYS = 0x00001000
|
|
MGMT_SETTING_PRIVACY = 0x00002000
|
|
MGMT_SETTING_CONTROLLER_CONFIG = 0x00004000
|
|
|
|
# Options returned by MGMT_OP_READ_CONFIG_INFO
|
|
MGMT_OPTION_EXTERNAL_CONFIG = 0x00000001
|
|
MGMT_OPTION_PUBLIC_ADDRESS = 0x00000002
|
|
|
|
# Disconnect reason returned in MGMT_EV_DEVICE_DISCONNECTED
|
|
MGMT_DEV_DISCONN_UNKNOWN = 0x00
|
|
MGMT_DEV_DISCONN_TIMEOUT = 0x01
|
|
MGMT_DEV_DISCONN_LOCAL_HOST = 0x02
|
|
MGMT_DEV_DISCONN_REMOTE = 0x03
|
|
|
|
# Flags returned in MGMT_EV_DEVICE_FOUND
|
|
MGMT_DEV_FOUND_CONFIRM_NAME = 0x01
|
|
MGMT_DEV_FOUND_LEGACY_PAIRING = 0x02
|
|
|
|
|
|
# EIR Data field types
|
|
EIR_FLAGS = 0x01
|
|
EIR_UUID16_SOME = 0x02
|
|
EIR_UUID16_ALL = 0x03
|
|
EIR_UUID32_SOME = 0x04
|
|
EIR_UUID32_ALL = 0x05
|
|
EIR_UUID128_SOME = 0x06
|
|
EIR_UUID128_ALL = 0x07
|
|
EIR_NAME_SHORT = 0x08
|
|
EIR_NAME_COMPLETE = 0x09
|
|
EIR_TX_POWER = 0x0A
|
|
EIR_CLASS_OF_DEV = 0x0D
|
|
EIR_SSP_HASH = 0x0E
|
|
EIR_SSP_RANDOMIZER = 0x0F
|
|
EIR_DEVICE_ID = 0x10
|
|
EIR_GAP_APPEARANCE = 0x19
|
|
|
|
|
|
# Derived from lib/hci.h
|
|
HCIGETDEVLIST = 0x800448d2
|
|
HCIGETDEVINFO = 0x800448d3
|
|
|
|
HCI_UP = 1 << 0
|
|
HCI_INIT = 1 << 1
|
|
HCI_RUNNING = 1 << 2
|
|
HCI_PSCAN = 1 << 3
|
|
HCI_ISCAN = 1 << 4
|
|
HCI_AUTH = 1 << 5
|
|
HCI_ENCRYPT = 1 << 6
|
|
HCI_INQUIRY = 1 << 7
|
|
HCI_RAW = 1 << 8
|
|
|
|
|
|
def parse_eir(eirdata):
|
|
"""Parse Bluetooth Extended Inquiry Result (EIR) data structuree.
|
|
|
|
@param eirdata: Encoded eir data structure.
|
|
|
|
@return Dictionary equivalent to the expanded structure keyed by EIR_*
|
|
fields, with any data members parsed to useful formats.
|
|
|
|
"""
|
|
fields = {}
|
|
pos = 0
|
|
while pos < len(eirdata):
|
|
# Byte at the current position is the field length, which should be
|
|
# zero at the end of the structure.
|
|
(field_len,) = struct.unpack('B', buffer(eirdata, pos, 1))
|
|
if field_len == 0:
|
|
break
|
|
# Next byte is the field type, and the rest of the field is the data.
|
|
# Note that the length field doesn't include itself so that's why the
|
|
# offsets and lengths look a little odd.
|
|
(field_type,) = struct.unpack('B', buffer(eirdata, pos + 1, 1))
|
|
data = eirdata[pos+2:pos+field_len+1]
|
|
pos += field_len + 1
|
|
# Parse the individual fields to make the data meaningful.
|
|
if field_type == EIR_NAME_SHORT or field_type == EIR_NAME_COMPLETE:
|
|
data = data.rstrip('\0')
|
|
# Place in the dictionary keyed by type.
|
|
fields[field_type] = data
|
|
|
|
return fields
|
|
|
|
|
|
|
|
class BluetoothSocketError(Exception):
|
|
"""Error raised for general issues with BluetoothSocket."""
|
|
pass
|
|
|
|
class BluetoothInvalidPacketError(Exception):
|
|
"""Error raised when an invalid packet is received from the socket."""
|
|
pass
|
|
|
|
class BluetoothControllerError(Exception):
|
|
"""Error raised when the Controller Error event is received."""
|
|
pass
|
|
|
|
|
|
class BluetoothSocket(btsocket.socket):
|
|
"""Bluetooth Socket.
|
|
|
|
BluetoothSocket wraps the btsocket.socket() class, and thus the system
|
|
socket.socket() class, to implement the necessary send and receive methods
|
|
for the HCI Control and Monitor protocols (aka mgmt_ops) of the
|
|
Linux Kernel.
|
|
|
|
Instantiate either BluetoothControlSocket or BluetoothRawSocket rather
|
|
than this class directly.
|
|
|
|
See bluez/doc/mgmt_api.txt for details.
|
|
|
|
"""
|
|
|
|
def __init__(self):
|
|
super(BluetoothSocket, self).__init__(family=btsocket.AF_BLUETOOTH,
|
|
type=socket.SOCK_RAW,
|
|
proto=btsocket.BTPROTO_HCI)
|
|
self.events = []
|
|
|
|
|
|
def send_command(self, code, index, data=''):
|
|
"""Send a command to the socket.
|
|
|
|
To send a command, wait for the reply event, and parse it use
|
|
send_command_and_wait() instead.
|
|
|
|
@param code: Command Code.
|
|
@param index: Controller index, may be MGMT_INDEX_NONE.
|
|
@param data: Parameters as bytearray or str (optional).
|
|
|
|
"""
|
|
# Send the command to the kernel
|
|
msg = struct.pack('<HHH', code, index, len(data)) + data
|
|
|
|
length = self.send(msg)
|
|
if length != len(msg):
|
|
raise BluetoothSocketError('Short write on socket')
|
|
|
|
|
|
def recv_event(self):
|
|
"""Receive a single event from the socket.
|
|
|
|
The event data is not parsed; in the case of command complete events
|
|
this means it includes both the data for the event and the response
|
|
for the command.
|
|
|
|
Use settimeout() to set whether this method will block if there is no
|
|
data, return immediately or wait for a specific length of time before
|
|
timing out and raising TimeoutError.
|
|
|
|
@return tuple of (event, index, data)
|
|
|
|
"""
|
|
# Read the message from the socket
|
|
hdr = bytearray(MGMT_HDR_SIZE)
|
|
data = bytearray(512)
|
|
try:
|
|
(nbytes, ancdata, msg_flags, address) = self.recvmsg_into(
|
|
(hdr, data))
|
|
except btsocket.timeout as e:
|
|
raise BluetoothSocketError('Error receiving event: %s' % e)
|
|
if nbytes < MGMT_HDR_SIZE:
|
|
raise BluetoothInvalidPacketError('Packet shorter than header')
|
|
|
|
# Parse the header
|
|
(event, index, length) = struct.unpack_from('<HHH', buffer(hdr))
|
|
if nbytes < MGMT_HDR_SIZE + length:
|
|
raise BluetoothInvalidPacketError('Packet shorter than length')
|
|
|
|
return (event, index, data[:length])
|
|
|
|
|
|
def send_command_and_wait(self, cmd_code, cmd_index, cmd_data='',
|
|
expected_length=None):
|
|
"""Send a command to the socket and wait for the reply.
|
|
|
|
Additional events are appended to the events list of the socket object.
|
|
|
|
@param cmd_code: Command Code.
|
|
@param cmd_index: Controller index, may be btsocket.HCI_DEV_NONE.
|
|
@param cmd_data: Parameters as bytearray or str.
|
|
@param expected_length: May be set to verify the length of the data.
|
|
|
|
Use settimeout() to set whether this method will block if there is no
|
|
reply, return immediately or wait for a specific length of time before
|
|
timing out and raising TimeoutError.
|
|
|
|
@return tuple of (status, data)
|
|
|
|
"""
|
|
self.send_command(cmd_code, cmd_index, cmd_data)
|
|
|
|
while True:
|
|
(event, index, data) = self.recv_event()
|
|
|
|
if event == MGMT_EV_CMD_COMPLETE:
|
|
if index != cmd_index:
|
|
raise BluetoothInvalidPacketError(
|
|
('Response for wrong controller index received: ' +
|
|
'0x%04d (expected 0x%04d)' % (index, cmd_index)))
|
|
if len(data) < 3:
|
|
raise BluetoothInvalidPacketError(
|
|
('Incorrect command complete event data length: ' +
|
|
'%d (expected at least 3)' % len(data)))
|
|
|
|
(code, status) = struct.unpack_from('<HB', buffer(data, 0, 3))
|
|
logging.debug('[0x%04x] command 0x%04x complete: 0x%02x',
|
|
index, code, status)
|
|
|
|
if code != cmd_code:
|
|
raise BluetoothInvalidPacketError(
|
|
('Response for wrong command code received: ' +
|
|
'0x%04d (expected 0x%04d)' % (code, cmd_code)))
|
|
|
|
response_length = len(data) - 3
|
|
if (expected_length is not None and
|
|
response_length != expected_length):
|
|
raise BluetoothInvalidPacketError(
|
|
('Incorrect length of data for response: ' +
|
|
'%d (expected %d)' % (response_length,
|
|
expected_length)))
|
|
|
|
return (status, data[3:])
|
|
|
|
elif event == MGMT_EV_CMD_STATUS:
|
|
if index != cmd_index:
|
|
raise BluetoothInvalidPacketError(
|
|
('Response for wrong controller index received: ' +
|
|
'0x%04d (expected 0x%04d)' % (index, cmd_index)))
|
|
if len(data) != 3:
|
|
raise BluetoothInvalidPacketError(
|
|
('Incorrect command status event data length: ' +
|
|
'%d (expected 3)' % len(data)))
|
|
|
|
(code, status) = struct.unpack_from('<HB', buffer(data, 0, 3))
|
|
logging.debug('[0x%04x] command 0x%02x status: 0x%02x',
|
|
index, code, status)
|
|
|
|
if code != cmd_code:
|
|
raise BluetoothInvalidPacketError(
|
|
('Response for wrong command code received: ' +
|
|
'0x%04d (expected 0x%04d)' % (code, cmd_code)))
|
|
|
|
return (status, None)
|
|
|
|
elif event == MGMT_EV_CONTROLLER_ERROR:
|
|
if len(data) != 1:
|
|
raise BluetoothInvalidPacketError(
|
|
('Incorrect controller error event data length: ' +
|
|
'%d (expected 1)' % len(data)))
|
|
|
|
(error_code) = struct.unpack_from('<B', buffer(data, 0, 1))
|
|
|
|
raise BluetoothControllerError('Controller error: %d' %
|
|
error_code)
|
|
|
|
else:
|
|
logging.debug('[0x%04x] event 0x%02x length: %d',
|
|
index, event, len(data))
|
|
self.events.append((event, index, data))
|
|
|
|
|
|
def wait_for_events(self, index, events):
|
|
"""Wait for and return the first of a set of events specified.
|
|
|
|
@param index: Controller index of event, may be btsocket.HCI_DEV_NONE.
|
|
@param events: List of event codes to wait for.
|
|
|
|
Use settimeout() to set whether this method will block if there is no
|
|
event received, return immediately or wait for a specific length of
|
|
time before timing out and raising TimeoutError.
|
|
|
|
@return Tuple of (event, data)
|
|
|
|
"""
|
|
while True:
|
|
for idx, (event, event_index, data) in enumerate(self.events):
|
|
if event_index == index and event in events:
|
|
self.events.pop(idx)
|
|
return (event, data)
|
|
|
|
(event, event_index, data) = self.recv_event()
|
|
if event_index == index and event in events:
|
|
return (event, data)
|
|
elif event == MGMT_EV_CMD_COMPLETE:
|
|
if len(data) < 3:
|
|
raise BluetoothInvalidPacketError(
|
|
('Incorrect command complete event data length: ' +
|
|
'%d (expected at least 3)' % len(data)))
|
|
|
|
(code, status) = struct.unpack_from('<HB', buffer(data, 0, 3))
|
|
logging.debug('[0x%04x] command 0x%04x complete: 0x%02x '
|
|
'(Ignored)', index, code, status)
|
|
|
|
elif event == MGMT_EV_CMD_STATUS:
|
|
if len(data) != 3:
|
|
raise BluetoothInvalidPacketError(
|
|
('Incorrect command status event data length: ' +
|
|
'%d (expected 3)' % len(data)))
|
|
|
|
(code, status) = struct.unpack_from('<HB', buffer(data, 0, 3))
|
|
logging.debug('[0x%04x] command 0x%02x status: 0x%02x '
|
|
'(Ignored)', index, code, status)
|
|
|
|
elif event == MGMT_EV_CONTROLLER_ERROR:
|
|
if len(data) != 1:
|
|
raise BluetoothInvalidPacketError(
|
|
('Incorrect controller error event data length: ' +
|
|
'%d (expected 1)' % len(data)))
|
|
|
|
(error_code) = struct.unpack_from('<B', buffer(data, 0, 1))
|
|
logging.debug('[0x%04x] controller error: %d (Ignored)',
|
|
index, error_code)
|
|
|
|
else:
|
|
self.events.append((event, index, data))
|
|
|
|
|
|
class BluetoothControlSocket(BluetoothSocket):
|
|
"""Bluetooth Control Socket.
|
|
|
|
BluetoothControlSocket provides convenient methods mapping to each mgmt_ops
|
|
command that send an appropriately formatted command and parse the response.
|
|
|
|
"""
|
|
|
|
DEFAULT_TIMEOUT = 15
|
|
|
|
def __init__(self):
|
|
super(BluetoothControlSocket, self).__init__()
|
|
self.bind((btsocket.HCI_DEV_NONE, btsocket.HCI_CHANNEL_CONTROL))
|
|
self.settimeout(self.DEFAULT_TIMEOUT)
|
|
|
|
# Certain features will depend on the management version and revision,
|
|
# so check those now.
|
|
(version, revision) = self.read_version()
|
|
logging.debug('MGMT API %d.%d', version, revision)
|
|
self._kernel_confirms_name = (
|
|
(version > 1) or ((version == 1) and (revision >= 5)))
|
|
|
|
def read_version(self):
|
|
"""Read the version of the management interface.
|
|
|
|
@return tuple (version, revision) on success, None on failure.
|
|
|
|
"""
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_READ_VERSION,
|
|
MGMT_INDEX_NONE,
|
|
expected_length=3)
|
|
if status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(version, revision) = struct.unpack_from('<BH', buffer(data))
|
|
return (version, revision)
|
|
|
|
|
|
def read_supported_commands(self):
|
|
"""Read the supported management commands and events.
|
|
|
|
@return tuple (commands, events) on success, None on failure.
|
|
|
|
"""
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_READ_COMMANDS,
|
|
MGMT_INDEX_NONE)
|
|
if status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
if len(data) < 4:
|
|
raise BluetoothInvalidPacketError(
|
|
('Incorrect length of data for response: ' +
|
|
'%d (expected at least 4)' % len(data)))
|
|
|
|
(ncommands, nevents) = struct.unpack_from('<HH', buffer(data, 0, 4))
|
|
offset = 4
|
|
expected_length = offset + (ncommands * 2) + (nevents * 2)
|
|
if len(data) != expected_length:
|
|
raise BluetoothInvalidPacketError(
|
|
('Incorrect length of data for response: ' +
|
|
'%d (expected %d)' % (len(data), expected_length)))
|
|
|
|
commands = []
|
|
while len(commands) < ncommands:
|
|
commands.extend(struct.unpack_from('<H', buffer(data, offset, 2)))
|
|
offset += 2
|
|
|
|
events = []
|
|
while len(events) < nevents:
|
|
events.extend(struct.unpack_from('<H', buffer(data, offset, 2)))
|
|
offset += 2
|
|
|
|
return (commands, events)
|
|
|
|
|
|
def read_index_list(self):
|
|
"""Read the list of currently known controllers.
|
|
|
|
@return array of controller indexes on success, None on failure.
|
|
|
|
"""
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_READ_INDEX_LIST,
|
|
MGMT_INDEX_NONE)
|
|
if status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
if len(data) < 2:
|
|
raise BluetoothInvalidPacketError(
|
|
('Incorrect length of data for response: ' +
|
|
'%d (expected at least 2)' % len(data)))
|
|
|
|
(nindexes,) = struct.unpack_from('<H', buffer(data, 0, 2))
|
|
offset = 2
|
|
expected_length = offset + (nindexes * 2)
|
|
if len(data) != expected_length:
|
|
raise BluetoothInvalidPacketError(
|
|
('Incorrect length of data for response: ' +
|
|
'%d (expected %d)' % (len(data), expected_length)))
|
|
|
|
indexes = []
|
|
while len(indexes) < nindexes:
|
|
indexes.extend(struct.unpack_from('<H', buffer(data, offset, 2)))
|
|
offset += 2
|
|
|
|
return indexes
|
|
|
|
|
|
def read_info(self, index):
|
|
"""Read the state and basic information of a controller.
|
|
|
|
Address is returned as a string in upper-case hex to match the
|
|
BlueZ property.
|
|
|
|
@param index: Controller index.
|
|
|
|
@return tuple (address, bluetooth_version, manufacturer,
|
|
supported_settings, current_settings,
|
|
class_of_device, name, short_name) on success,
|
|
None on failure.
|
|
|
|
"""
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_READ_INFO,
|
|
index,
|
|
expected_length=280)
|
|
if status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(address, bluetooth_version, manufacturer,
|
|
supported_settings, current_settings,
|
|
class_of_device_lo, class_of_device_mid, class_of_device_hi,
|
|
name, short_name) = struct.unpack_from(
|
|
'<6sBHLL3B249s11s',
|
|
buffer(data))
|
|
|
|
return (
|
|
':'.join('%02X' % x
|
|
for x in reversed(struct.unpack('6B', address))),
|
|
bluetooth_version,
|
|
manufacturer,
|
|
supported_settings,
|
|
current_settings,
|
|
(class_of_device_lo |(class_of_device_mid << 8) |
|
|
(class_of_device_hi << 16)),
|
|
name.rstrip('\0'),
|
|
short_name.rstrip('\0'))
|
|
|
|
|
|
def set_powered(self, index, powered):
|
|
"""Set the powered state of a controller.
|
|
|
|
@param index: Controller index.
|
|
@param powered: Whether controller radio should be powered.
|
|
|
|
@return New controller settings on success, None on failure.
|
|
|
|
"""
|
|
msg_data = struct.pack('<B', bool(powered))
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_SET_POWERED,
|
|
index,
|
|
msg_data,
|
|
expected_length=4)
|
|
if status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(current_settings, ) = struct.unpack_from('<L', buffer(data))
|
|
return current_settings
|
|
|
|
|
|
def set_discoverable(self, index, discoverable, timeout=0):
|
|
"""Set the discoverable state of a controller.
|
|
|
|
@param index: Controller index.
|
|
@param discoverable: Whether controller should be discoverable.
|
|
@param timeout: Timeout in seconds before disabling discovery again,
|
|
ignored when discoverable is False, must not be zero when
|
|
discoverable is True.
|
|
|
|
@return New controller settings on success, 0 if the feature is not
|
|
supported and the parameter was False, None otherwise.
|
|
|
|
"""
|
|
msg_data = struct.pack('<BH', bool(discoverable), timeout)
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_SET_DISCOVERABLE,
|
|
index,
|
|
msg_data,
|
|
expected_length=4)
|
|
if status == MGMT_STATUS_NOT_SUPPORTED and not discoverable:
|
|
return 0
|
|
elif status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(current_settings, ) = struct.unpack_from('<L', buffer(data))
|
|
return current_settings
|
|
|
|
|
|
def set_connectable(self, index, connectable):
|
|
"""Set the connectable state of a controller.
|
|
|
|
@param index: Controller index.
|
|
@param connectable: Whether controller should be connectable.
|
|
|
|
@return New controller settings on success, 0 if the feature is not
|
|
supported and the parameter was False, None otherwise.
|
|
|
|
"""
|
|
msg_data = struct.pack('<B', bool(connectable))
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_SET_CONNECTABLE,
|
|
index,
|
|
msg_data,
|
|
expected_length=4)
|
|
if status == MGMT_STATUS_NOT_SUPPORTED and not connectable:
|
|
return 0
|
|
elif status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(current_settings, ) = struct.unpack_from('<L', buffer(data))
|
|
return current_settings
|
|
|
|
|
|
def set_fast_connectable(self, index, connectable):
|
|
"""Set the fast connectable state of a controller.
|
|
|
|
Fast Connectable is a state where page scan parameters are set to favor
|
|
faster connect times at the expense of higher power consumption.
|
|
|
|
Unlike most other set_* commands, this may only be used when the
|
|
controller is powered.
|
|
|
|
@param index: Controller index.
|
|
@param connectable: Whether controller should be fast connectable.
|
|
|
|
@return New controller settings on success, 0 if the feature is not
|
|
supported and the parameter was False or the controller is
|
|
powered down, None otherwise.
|
|
|
|
"""
|
|
msg_data = struct.pack('<B', bool(connectable))
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_SET_FAST_CONNECTABLE,
|
|
index,
|
|
msg_data)
|
|
if status == MGMT_STATUS_NOT_SUPPORTED and not connectable:
|
|
return 0
|
|
elif status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
# This is documented as returning current settings, but doesn't in
|
|
# our kernel version (probably a bug), so if no data is returned,
|
|
# pretend that was success.
|
|
if len(data) == 0:
|
|
return 0
|
|
elif len(data) != 4:
|
|
raise BluetoothInvalidPacketError(
|
|
('Incorrect length of data for response: ' +
|
|
'%d (expected 4)' % len(data)))
|
|
|
|
(current_settings, ) = struct.unpack_from('<L', buffer(data))
|
|
return current_settings
|
|
|
|
|
|
def set_pairable(self, index, pairable):
|
|
"""Set the pairable state of a controller.
|
|
|
|
@param index: Controller index.
|
|
@param pairable: Whether controller should be pairable.
|
|
|
|
@return New controller settings on success, 0 if the feature is not
|
|
supported and the parameter was False, None otherwise.
|
|
|
|
"""
|
|
msg_data = struct.pack('<B', bool(pairable))
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_SET_PAIRABLE,
|
|
index,
|
|
msg_data,
|
|
expected_length=4)
|
|
if status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(current_settings, ) = struct.unpack_from('<L', buffer(data))
|
|
return current_settings
|
|
|
|
|
|
def set_link_security(self, index, link_security):
|
|
"""Set the link security state of a controller.
|
|
|
|
Toggles the use of link level security (aka Security Mode 3) for a
|
|
controller.
|
|
|
|
@param index: Controller index.
|
|
@param link_security: Whether controller should be link_security.
|
|
|
|
@return New controller settings on success, 0 if the feature is not
|
|
supported and the parameter was False, None otherwise.
|
|
|
|
"""
|
|
msg_data = struct.pack('<B', bool(link_security))
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_SET_LINK_SECURITY,
|
|
index,
|
|
msg_data,
|
|
expected_length=4)
|
|
if status == MGMT_STATUS_NOT_SUPPORTED and not link_security:
|
|
return 0
|
|
elif status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(current_settings, ) = struct.unpack_from('<L', buffer(data))
|
|
return current_settings
|
|
|
|
|
|
def set_ssp(self, index, ssp):
|
|
"""Set the whether a controller supports Simple Secure Pairing.
|
|
|
|
@param index: Controller index.
|
|
@param ssp: Whether controller should support SSP.
|
|
|
|
@return New controller settings on success, 0 if the feature is not
|
|
supported and the parameter was False, None otherwise.
|
|
|
|
"""
|
|
msg_data = struct.pack('<B', bool(ssp))
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_SET_SSP,
|
|
index,
|
|
msg_data,
|
|
expected_length=4)
|
|
if status == MGMT_STATUS_NOT_SUPPORTED and not ssp:
|
|
return 0
|
|
elif status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(current_settings, ) = struct.unpack_from('<L', buffer(data))
|
|
return current_settings
|
|
|
|
|
|
def set_hs(self, index, hs):
|
|
"""Set the whether a controller supports Bluetooth High Speed.
|
|
|
|
@param index: Controller index.
|
|
@param hs: Whether controller should support High Speed.
|
|
|
|
@return New controller settings on success, 0 if the feature is not
|
|
supported and the parameter was False, None otherwise.
|
|
|
|
"""
|
|
msg_data = struct.pack('<B', bool(hs))
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_SET_HS,
|
|
index,
|
|
msg_data,
|
|
expected_length=4)
|
|
if status == MGMT_STATUS_NOT_SUPPORTED and not hs:
|
|
return 0
|
|
elif status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(current_settings, ) = struct.unpack_from('<L', buffer(data))
|
|
return current_settings
|
|
|
|
|
|
def set_le(self, index, le):
|
|
"""Set the whether a controller supports Bluetooth Low Energy.
|
|
|
|
@param index: Controller index.
|
|
@param le: Whether controller should support Low Energy.
|
|
|
|
@return New controller settings on success, 0 if the feature is not
|
|
supported and the parameter was False, None otherwise.
|
|
|
|
"""
|
|
msg_data = struct.pack('<B', bool(le))
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_SET_LE,
|
|
index,
|
|
msg_data,
|
|
expected_length=4)
|
|
if status == MGMT_STATUS_NOT_SUPPORTED and not le:
|
|
return 0
|
|
elif status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(current_settings, ) = struct.unpack_from('<L', buffer(data))
|
|
return current_settings
|
|
|
|
|
|
def set_device_class(self, index, major, minor):
|
|
"""Set the device class of the controller.
|
|
|
|
Consult the Bluetooth Baseband Assigned Numbers specification for valid
|
|
values, in general both values are bit fields defined by that
|
|
specification.
|
|
|
|
If the device class is set while the controller is powered off, 0 will
|
|
be returned, but the new class will be set by the host subsystem after
|
|
the controller is powered on.
|
|
|
|
@param index: Controller index.
|
|
@param major: Major device class.
|
|
@param minor: Minor device class.
|
|
|
|
@return New three-octet device class on success, None on failure.
|
|
|
|
"""
|
|
msg_data = struct.pack('<BB', major, minor)
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_SET_DEV_CLASS,
|
|
index,
|
|
msg_data,
|
|
expected_length=3)
|
|
if status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(class_of_device_lo, class_of_device_mid,
|
|
class_of_device_hi) = struct.unpack_from('<3B', buffer(data))
|
|
return (class_of_device_lo |(class_of_device_mid << 8) |
|
|
(class_of_device_hi << 16))
|
|
|
|
|
|
def set_local_name(self, index, name, short_name):
|
|
"""Set the local name of the controller.
|
|
|
|
@param index: Controller index.
|
|
@param name: Full length name, up to 248 characters.
|
|
@param short_name: Short name, up to 10 characters.
|
|
|
|
@return Tuple of (name, short_name) on success, None on failure.
|
|
|
|
"""
|
|
# Truncate the provided parameters and then zero-pad using struct
|
|
# so we pass a fixed-length null-terminated string to the kernel.
|
|
msg_data = struct.pack('<249s11s', name[:248], short_name[:10])
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_SET_LOCAL_NAME,
|
|
index,
|
|
msg_data,
|
|
expected_length=260)
|
|
if status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(name, short_name) = struct.unpack_from('<249s11s', buffer(data))
|
|
return (name.rstrip('\0'), short_name.rstrip('\0'))
|
|
|
|
|
|
def start_discovery(self, index, address_type):
|
|
"""Start discovering remote devices.
|
|
|
|
Call get_discovered_devices() to retrieve the list of devices found.
|
|
|
|
@param index: Controller index.
|
|
@param address_type: Address types to discover.
|
|
|
|
@return Address types discovery was started for on success,
|
|
None on failure.
|
|
|
|
"""
|
|
msg_data = struct.pack('<B', address_type)
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_START_DISCOVERY,
|
|
index,
|
|
msg_data,
|
|
expected_length=1)
|
|
if status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(address_type,) = struct.unpack_from('<B', buffer(data))
|
|
return address_type
|
|
|
|
|
|
def stop_discovery(self, index, address_type):
|
|
"""Stop discovering remote devices.
|
|
|
|
There is usually no need to call this method explicitly as discovery
|
|
is automatically stopped once it has iterated through the necessary
|
|
channels.
|
|
|
|
@param index: Controller index.
|
|
@param address_type: Address types to stop discovering.
|
|
|
|
@return Address types discovery was stopped for on success,
|
|
None on failure.
|
|
|
|
"""
|
|
msg_data = struct.pack('<B', address_type)
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_STOP_DISCOVERY,
|
|
index,
|
|
msg_data,
|
|
expected_length=1)
|
|
if status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(address_type,) = struct.unpack_from('<B', buffer(data))
|
|
return address_type
|
|
|
|
|
|
def get_discovered_devices(self, index):
|
|
"""Return list of discovered remote devices.
|
|
|
|
This method may be called any time after start_discovery() and will
|
|
wait until the full list of devices has been returned, there is usually
|
|
no need to call stop_discovery() explicitly.
|
|
|
|
Use settimeout() to set whether this method will block if there are no
|
|
events, return immediately or wait for a specific length of time before
|
|
timing out and raising TimeoutError.
|
|
|
|
@param index: Controller index.
|
|
|
|
@return List of devices found as tuples with the format
|
|
(address, address_type, rssi, flags, eirdata)
|
|
|
|
"""
|
|
devices = []
|
|
discovering = True
|
|
while discovering:
|
|
(event, data) = self.wait_for_events(
|
|
index,
|
|
( MGMT_EV_DISCOVERING, MGMT_EV_DEVICE_FOUND ))
|
|
|
|
if event == MGMT_EV_DISCOVERING:
|
|
if len(data) != 2:
|
|
raise BluetoothInvalidPacketError(
|
|
('Incorrect discovering event data length: ' +
|
|
'%d (expected 2)' % len(data)))
|
|
|
|
(address_type,
|
|
discovering) = struct.unpack_from('<BB', buffer(data))
|
|
|
|
elif event == MGMT_EV_DEVICE_FOUND:
|
|
if len(data) < 14:
|
|
raise BluetoothInvalidPacketError(
|
|
('Incorrect device found event data length: ' +
|
|
'%d (expected at least 14)' % len(data)))
|
|
|
|
(address, address_type, rssi,
|
|
flags, eir_len) = struct.unpack_from('<6sBbLH',
|
|
buffer(data, 0, 14))
|
|
|
|
if len(data) != 14 + eir_len:
|
|
raise BluetoothInvalidPacketError(
|
|
('Incorrect device found event data length: ' +
|
|
'%d (expected %d)' % (len(data), 14 + eir_len)))
|
|
|
|
devices.append((
|
|
':'.join('%02X' % x
|
|
for x in reversed(
|
|
struct.unpack('6B', address))),
|
|
address_type,
|
|
rssi,
|
|
flags,
|
|
bytes(data[14:])
|
|
))
|
|
|
|
# The kernel might want us to confirm whether or not we
|
|
# know the name of the device. We don't really care whether
|
|
# or not this works, we just have to do it to get the EIR
|
|
# Request.
|
|
if flags & MGMT_DEV_FOUND_CONFIRM_NAME:
|
|
msg_data = struct.pack('<6sBB',
|
|
address, address_type, False)
|
|
if self._kernel_confirms_name:
|
|
self.send_command_and_wait(
|
|
MGMT_OP_CONFIRM_NAME,
|
|
index,
|
|
msg_data)
|
|
else:
|
|
self.send_command(
|
|
MGMT_OP_CONFIRM_NAME,
|
|
index,
|
|
msg_data)
|
|
|
|
|
|
return devices
|
|
|
|
|
|
def set_advertising(self, index, advertising):
|
|
"""Set the whether a controller is advertising via LE.
|
|
|
|
@param index: Controller index.
|
|
@param advertising: Whether controller should advertise via LE.
|
|
|
|
@return New controller settings on success, 0 if the feature is not
|
|
supported and the parameter was False, None otherwise.
|
|
|
|
"""
|
|
msg_data = struct.pack('<B', bool(advertising))
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_SET_ADVERTISING,
|
|
index,
|
|
msg_data,
|
|
expected_length=4)
|
|
if status == MGMT_STATUS_NOT_SUPPORTED and not advertising:
|
|
return 0
|
|
elif status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(current_settings, ) = struct.unpack_from('<L', buffer(data))
|
|
return current_settings
|
|
|
|
|
|
def set_bredr(self, index, bredr):
|
|
"""Set the whether a controller supports Bluetooth BR/EDR (classic).
|
|
|
|
@param index: Controller index.
|
|
@param bredr: Whether controller should support BR/EDR.
|
|
|
|
@return New controller settings on success, 0 if the feature is not
|
|
supported and the parameter was False, None otherwise.
|
|
|
|
"""
|
|
msg_data = struct.pack('<B', bool(bredr))
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_SET_BREDR,
|
|
index,
|
|
msg_data,
|
|
expected_length=4)
|
|
if status == MGMT_STATUS_NOT_SUPPORTED and not bredr:
|
|
return 0
|
|
elif status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(current_settings, ) = struct.unpack_from('<L', buffer(data))
|
|
return current_settings
|
|
|
|
|
|
def set_secure_connections(self, index, secure_conn):
|
|
"""Set whether a controller supports secure connections.
|
|
|
|
@param index: Controller index.
|
|
@param secure_conn: Whether controller should support secure connections
|
|
|
|
@return New controller settings on success, 0 if the feature is not
|
|
supported and the parameter was False, None otherwise.
|
|
|
|
"""
|
|
msg_data = struct.pack('<B', bool(secure_conn))
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_SET_SECURE_CONN,
|
|
index,
|
|
msg_data,
|
|
expected_length=4)
|
|
if status == MGMT_STATUS_NOT_SUPPORTED and not secure_conn:
|
|
return 0
|
|
elif status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(current_settings, ) = struct.unpack_from('<L', buffer(data))
|
|
return current_settings
|
|
|
|
|
|
def add_device(self, index, address, address_type, action):
|
|
"""Add a device to the action list.
|
|
|
|
@param index: Controller index.
|
|
@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.
|
|
|
|
"""
|
|
msg_data = struct.pack('<6sBB', address, address_type, action)
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_ADD_DEVICE,
|
|
index,
|
|
msg_data,
|
|
expected_length=7)
|
|
if status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(address, address_type,) = struct.unpack_from('<6sB', buffer(data))
|
|
return (address, address_type)
|
|
|
|
|
|
def remove_device(self, index, address, address_type):
|
|
"""Remove a device from the action list.
|
|
|
|
@param index: Controller index.
|
|
@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.
|
|
|
|
"""
|
|
msg_data = struct.pack('<6sB', address, address_type)
|
|
(status, data) = self.send_command_and_wait(
|
|
MGMT_OP_REMOVE_DEVICE,
|
|
index,
|
|
msg_data,
|
|
expected_length=7)
|
|
if status != MGMT_STATUS_SUCCESS:
|
|
return None
|
|
|
|
(address, address_type,) = struct.unpack_from('<6sB', buffer(data))
|
|
return (address, address_type)
|
|
|
|
|
|
class BluetoothRawSocket(BluetoothSocket):
|
|
"""Bluetooth Raw HCI Socket.
|
|
|
|
BluetoothRawSocket is a subclass of BluetoothSocket representing raw access
|
|
to the HCI controller and providing commands corresponding to ioctls that
|
|
can be made on that kind of socket.
|
|
|
|
"""
|
|
|
|
def get_dev_info(self, index):
|
|
"""Read HCI device information.
|
|
|
|
This method uses the same underlying ioctl as the hciconfig tool.
|
|
|
|
Address is returned as a string in upper-case hex to match the
|
|
BlueZ property.
|
|
|
|
@param index: Device index.
|
|
|
|
@return tuple (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.
|
|
|
|
"""
|
|
buf = array.array('B', [0] * 96)
|
|
fcntl.ioctl(self.fileno(), HCIGETDEVINFO, buf, 1)
|
|
|
|
( dev_id, name, address, flags, dev_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 ) = struct.unpack_from(
|
|
'@H8s6sIBQIIIHHHHIIIIIIIIII', buf)
|
|
|
|
return (
|
|
dev_id,
|
|
name.rstrip('\0'),
|
|
':'.join('%02X' % x
|
|
for x in reversed(struct.unpack('6B', address))),
|
|
flags,
|
|
(dev_type & 0x30) >> 4,
|
|
dev_type & 0x0f,
|
|
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)
|