Central role

Bluetooth Low Energy central role is used to connect to a BLE peripheral and interact with it. WHAD provides a specific connector, Central, that implements this role.

It is possible to register a callback function to be called whenever an asynchronous event is received by this connector using its Central.add_event_handler() method. This callback function will be called every time a central-related event is received.

A CentralConnected event is sent when the current Central connector has successfully connected to a device and a CentralDisconnected event is sent when the remote device has disconnected.

It also provides a specific wrapper for connected devices in order to mask the underlying GATT stack and allow easy access to device services and charactersitics, PeripheralDevice.

Important

The mechanism used to handle asynchronous events like connection and disconnection is still a work in progress. It has been introduced in a recent update to better handle connection and disconnection events and is not yet intended to be used by anything other WHAD’s internal code.

We have a full rework of WHAD’s internals planned, including BLE Central and Peripheral classes, that will definitely bring some changes to the way connectors work. We will do our best not to break the current implementation.

Interacting with a remote Peripheral device

The Central connector can initiate a connection to a remote device and implements a GATT client to interact with it once a connection successfully established. Remote GATT services and characteristics can be discovered through the corresponding GATT procedure, characteristics value can be read and written and it is also possible to subscribe for notifications or indications in order to be notified when the remote GATT server updates a characteristic value.

Initiating a connection

This connector provides the connect() method that initiates a connection to a specific device. A connection to a remote BLE device that advertises itself with a public BD address can be initiated as shown below:

from whad.device import Device
from whad.ble import Central

# We assign a BLE central role to our HCI adapter
central = Central(Device.create("hci0"))

# And we initiate a connection to our public BLE device
target = central.connect("00:11:22:33:44:55")

Calling connect() will set the hardware in central mode, and it will listen for an advertisement from the specified device, including its address type (in this case, a public address). If the specified device cannot be found, a PeripheralNotFound exception is raised. The timeout used during this connection initiation can be specified through the timeout parameter supported by the connect() method:

from whad.device import Device
from whad.ble import Central

# We assign a BLE central role to our HCI adapter
central = Central(Device.create("hci0"))

# And we initiate a connection to our public BLE device
# with a timeout of 5 seconds
target = central.connect("00:11:22:33:44:55", timeout=5.0)

Note

The default timeout for connection initiation is 30 seconds.

When connecting to a device with a random address, the random parameter must be specified to tell the Central connector to look for a device with a random address:

from whad.device import Device
from whad.ble import Central

# We assign a BLE central role to our HCI adapter
central = Central(Device.create("hci0"))

# And we initiate a connection to our device that uses a random
# address
target = central.connect("00:11:22:33:44:55", random=True)

If a connection is successfully established, connect() returns an instance of PeripheralDevice that can be used to interact with the remote GATT server.

In some very specific cases, we may want to set the hop interval value used when initiating a connection in to optimize speed. The connect() method accepts a hop_interval parameter that will be used as the hop_interval value when initiating the connection:

from whad.device import Device
from whad.ble import Central

# We assign a BLE central role to our HCI adapter
central = Central(Device.create("hci0"))

# And we initiate a connection to our device that uses a random
# address
target = central.connect("00:11:22:33:44:55", random=True, hop_interval=6)

Important

Hop interval value must be comprised between 6 and 3200, as specified in the Bluetooth Specification in Vol 6, part B, section 4.5.1.

Enumerating remote services and characteristics

Once connected to a remote device that implements a peripheral role, it is possible to discover the exposed services, characteristics and descriptors by starting a GATT discovery procedure:

# Discover remote services and characteristics
target.discover()

Once this procedure is complete, the corresponding PeripheralDevice instance is populated with the discovered services and characteristics. Discovered services can then be listed with a call to services(), that will yield each service as an instance of PeripheralService:

# List discovered services
for service in target.services()
    print(f"- {service.name} (handle: {service.handle})")

Enumerating each service’s characteristics is then trivial with the help of characteristics():

# Loop over discovered services
for service in target.services()
    print(f"- {service.name} (handle: {service.handle})")

    # List discovered characteristics for the current service
    for char in service.characteristics()
        print(f"  * {char.name} (handle: {char.handle})")

Eventually, it is possible to list each characteristic descriptor in a similar fashion through a call to each characteristic’s descriptors() method:

# Enumerate services
for service in target.services()
    print(f"- {service.name} (handle: {service.handle})")

    # Show characteristics belonging to this service
    for char in service.characteristics()
        print(f"  * {char.name} (handle: {char.handle})")

        # Loop over the current characteristic's descriptors
        for desc in char.descriptors():
            print(f"   desc: f{desc.name}")

Getting a service object from its UUID

Once services and characteristics discovered, retrieving an instance of PeripheralService from a known service’s UUID is achieved by calling service():

# Retrieve an object representing the remote service
service = target.service('1800')

Attention

service() method has been introduced in version 1.2.12 to provide a simple and easy way to access a device’s service, as a replacement of the get_service() method that is now deprecated.

Starting from version 1.2.12, it is also possible to check if a service is present in the discovered attributes with Python’s in operator:

# Check our device does expose a primary service with UUID 0x1800
if UUID('1800') in target:
    print("Primary service 0x1800 is available.")

The returned PeripheralService object represents the remote service exposed by the connected GATT server, and is populated with all the previously discovered characteristics.

Getting a characteristic object from its UUID

The easiest way to interact with a remote GATT server, once its services and characteristics discovered, is to get an instance of PeripheralCharacteristic from the connected peripheral. The PeripheralCharacteristic class exposes some methods to initiate different GATT operations on characteristics, like reading or writing its value.

First, we need to retrieve a characteristic based on its UUID. Let’s say we want to read the remote device’s name through the DeviceName characteristic exposed by the Generic Access service. This characteristic is a standard one, defined in the specification by the 0x2A00 16-bit UUID, and is part of the standard Generic Access service identified with the 0x1800 16-bit UUID:

from whad.device import Device
from whad.ble import Central, UUID
from whad.ble.exceptions import PeripheralNotFound

# We assign a BLE central role to our HCI adapter
central = Central(Device.create("hci0"))

try:
    # Connect to remote device and discover services and characteristics
    target = central.connect("00:11:22:33:44:55", random=True)
    target.discover()

    # Retrieve the Generic Access service
    generic_access = target.service('1800')
    if generic_access:
        device_name = generic_access.char('2a00')
        if device_name:
            print(f"Device name: {device_name.value.decode('utf-8')}")
        else:
            print("Cannot find a device name characteristic (0x2A00).")
    else:
        print("Cannot find a Generic Access service (0x1800)")

# Device not found ?
except PeripheralNotFound:
    print("Device not found.")

In the above example, we first search for the Generic Access service by calling service() with the corresponding service UUID, then check such a service has been found and eventually call char() with the characteristic’s UUID we are looking for to retrieve an object representing this characteristic. If found, the value of this characteristic is read and displayed, if an error occurred while searching for it then an error message is displayed.

A characteristic can also be retrieved directly from a connected device with a call to :py:meth:~whad.ble.profile.device.PeripheralDevice.char`, automatically searching a characteristic from its UUID and its parent service UUID:

from whad.device import Device
from whad.ble import Central, UUID
from whad.ble.exceptions import PeripheralNotFound

# We assign a BLE central role to our HCI adapter
central = Central(Device.create("hci0"))

try:
    # Connect to remote device and discover services and characteristics
    target = central.connect("00:11:22:33:44:55", random=True)
    target.discover()

    # Retrieve the DeviceName characteristic object
    device_name = target.char('2a00', '1800')
    if device_name:
        print(f"Device name: {device_name.value.decode('utf-8')}")
    else:
        print("Cannot read device name (characteristic not found).")

# Device not found ?
except PeripheralNotFound:
    print("Device not found.")

Providing the service and characteristic UUIDs is the cleanest way to get a characteristic object, but PeripheralDevice.char() can also be called with only a characteristic’s UUID:

# Retrieve the DeviceName characteristic object
device_name = target.char('2A00')
if device_name:
    print(device_name.name)

This method returns the first characteristic that matches the provided UUID, or None if no matching characteristic has been found.

Attention

char() method has been introduced in version 1.2.12 to provide a simple and easy way to access a device’s service, as a replacement of the get_characteristic() method that is now deprecated.

Reading a characteristic value

Once a characteristic object retrieved, its value can be read by simply accessing its value property:

# Reading the remote device name
device_name = target.char('2A00')
if device_name:
    print(device_name.value)

When this attribute is accessed, a GATT read operation is performed on the corresponding characteristic’s value handle and the response returned by the remote GATT server is returned as the characteristic’s value. Each access to this attribute will perform a GATT read operation.

For characteristics containing long values, i.e values that are longer than the ATT_MTU value used by the GATT server, the read operation performed when accessing this attribute follows the Bluetooth specification and will read the characteristic’s value content piece by piece, and eventually return the whole value as a singe byte array, in a transparent manner.

Important

Calling readable() before reading a characteristic to check if it is supposed to be read is a good idea as WHAD’s BLE stack and GATT client implementation are flexible by design. A GATT read operation can be initiated against a characteristic advertised as non-readable and will lead to an exception being generated if the GATT server denies access.

This flexibility also offers security researchers a way to test remote GATT servers implementation and possibly find inconsistencies between the discovered characteristics and the operations they support, like a non-readable characteristic that still can be read with a GATT read operation because of a missing check in the GATT server implementation ;)

Writing into a characteristic value

Writing to a characteristic’s value is quite as simple as reading it, we just set the characteristic’s value attribute and it starts a GATT write operation:

# Writing the remote device name
# (don't do that, seriously, it's just an example)
device_name = target.char('2A00')
device_name.value = b"pwnd"

Setting a characteristic’s value will always trigger a GATT write operation, not a GATT write command operation (a generic write operation causes the GATT server to reply with the provided content to acknowledge a successful write, while a GATT write command is just received by the GATT server but never acknowledged).

GATT write command operation can still be performed through a call to the write() method provided by the PeripheralCharacteristic class and setting its without_response parameter to True:

# Writing the remote device name through a write command operation
device_name = target.char('2A00')
device_name.write(b"pwnd", without_response=True)

Note

Characteristics with long values are automatically written using GATT prepared write requests.

Important

Calling writeable() before writing into a characteristic’s value to check it accepts write requests is always a good idea.

WHAD’s BLE stack and GATT client are flexible by design and do not ensure a characteristic can be written before starting a GATT write operation and it could lead to an exception raised because the remote GATT server returned an error.

Checking support for notifications or indications

Some characteristics exposed by a GATT server support notifications or indications, depending on the presence of a ClientCharacteristicConfiguration descriptor (defined with type UUID 2902). Notifications and indications are one of the key features of GATT to allow a GATT client to be notified when a characteristic’s value has changed. Both notifications and indications are sent by the remote GATT server, but the latter requires a confirmation message sent by the GATT client (see Vol 3. Part G, section 4.10 of the Bluetooth specification).

Both notifications and indications contain the characteristic’s value (up to ATT_MTU - 3 bytes).

The PeripheralCharacteristic class provides two methods to respectively check if a characteristic supports notifications or indications: can_notify() and can_indicate().

These methods only check if a characteristic has been declared with the correct properties required to support notifications or indications, not if the corresponding characteristics effectively own a ClientCharacteristicConfiguration descriptor in their definition (required to subscribe for notifications or indications).

Subscribing for notifications

A GATT server can send notifications or indications to GATT clients that have subscribed for them by modifying the characteristic’s associated ClientCharacteristicConfiguration descriptor. This operation is implemented in the subscribe() method, and subscribing for notifications is pretty straightforward:

def notification_callback(characteristic, value: bytes, indication=False):
    """Process notifications sent by the GATT server

    :param  characteristic: Characteristic concerned by this notification
    :type   characteristic: whad.ble.profile.device.PeripheralCharacteristic
    :param  value: Characteristic's new value
    :type   value: bytes
    :param  indication: `True` if callback has been called from an indication
    :type   indication: bool
    """
    print(f"Characteristic {characteristic.name} value has been changed to {value.hex()}")

# Subscribe for notification if characteristic supports it
# and sets a callback
device_name = target.char('2A00')
if device_name.can_notify():
    if device_name.subscribe(notification=True, callback=notification_callback):
        print(f"Succesfully subscribed for notifications for characteristic {device_name.uuid}")
    else:
        print(f"An error occurred while subscribing for notifications.")

The provided callback function will be called each time a notification is received, with the new characteristic’s value. For notifications, its indication argument is expected to be False.

Subscribing for indications

Subscribing for indications is very similar:

def indication_callback(characteristic, value: bytes, indication=False):
    """Process notifications sent by the GATT server

    :param  characteristic: Characteristic concerned by this notification
    :type   characteristic: whad.ble.profile.device.PeripheralCharacteristic
    :param  value: Characteristic's new value
    :type   value: bytes
    :param  indication: `True` if callback has been called from an indication
    :type   indication: bool
    """
    assert indication
    print(f"Characteristic {characteristic.name} value has been changed to {value.hex()}")

# Subscribe for indication if characteristic supports it
# and sets a callback
device_name = target.char('2A00')
if device_name.can_indicate():
    if device_name.subscribe(indication=True, callback=notification_callback):
        print(f"Succesfully subscribed for indications for characteristic {device_name.uuid}")
    else:
        print(f"An error occurred while subscribing for indications.")

The provided callback function will be called each time a notification is received, with the new characteristic’s value. If subscribed for indications, its indication argument is expected to be True.

Unsubscribing from notifications or indications

Unsubscribing from a characteristic is the same for notifications and indications, as the corresponding ClientCharacteristicConfiguration descriptor’s value is set to its default value (0x0000):

# Unsubscribe from notifications or indications
if device_name.unsubscribe():
    print(f"Successfully unsubscribe from characteristic {device_name.uuid}")

Terminating the current connection

A connection to a remote peripheral can be terminated by calling its disconnect() method:

# Terminate connection
target.disconnect()

Handling GATT exceptions

When performing various GATT operations against a GATT server, some errors might be sent to the GATT client because of an invalid value used or simply because a GATT operation requires authentication or authorization.

Each time such an error is encountered by WHAD’s GATT client, an exception is raised and must be caught and properly handled to avoid a brutal disconnection due to an unhandled exception.

All GATT-related exceptions inherit from the AttError class and can therefore be caught and processed quite easily. The following example code shows how to handle GATT write errors:

from whad.device import Device
from whad.ble import Central
from whad.ble.exceptions import PeripheralNotFound
from whad.ble import UUID
from whad.ble.stack.att.exceptions import WriteNotPermittedError

# We assign a BLE central role to our HCI adapter
central = Central(Device.create("hci0"))

# Target not connected
target = None

try:
    # Connect to remote device and discover services and characteristics
    target = central.connect("00:11:22:33:44:55", random=True)
    target.discover()

    # Writing into the remote device name (could fail)
    try:
        device_name = target.char('2A00')
        device_name.value = b"p0wn3d"
    except WriteNotPermittedError:
        print("Device name characteristic cannot be written.")

    # Closing connection
    target.disconnect()

# Handle connection error
except PeripheralNotFound:
    print("Target device not found.")

# Handle any other ATT errors
except AttError as att_err:
    print("An unsupported ATT error has been raised:")
    print(att_err)

# Handle CTL-C
except KeyboardInterrupt:
    try:
        if target is not None:
            target.disconnect()
    except AttError:
        print("An error occurred while disconnecting from target.")

Querying and interacting with standard services

The Bluetooth specification defines a set of standard services designed to be used by devices providing one or more standardized features. Services like the Battery service, the Device Information service or the Heart Rate service define each one or more characteristics and how data is exchanged between them and a GATT client. Each service exposes one or more information that can be queried, usually specifically stored inside its characteristic’s values using a specific encoding.

WHAD offers an easy way to query standard services through a specific asbtraction, allowing direct access to the stored information without dealing with the way it is encoded or knowing the expected service’s and characteristics’ UUIDs. The current stable version supports the following services:

  • Device Information service

  • Battery service

  • Heart Rate service

In case of a connection to a GATT server from a central device, a specific service can be queried through the query() method. An additional method has() is available to check if a device exposes the expected primary service and mandatory characteristics, based on their respective UUIDs. It is then quite easy, once a connection to a GATT server established, to get an instance of the BatteryService service class, for instance, and interact in a transparent way. Each time this service’s characteristic’s value is read, a read operation is performed and the returned data is parsed and converted in a value in a convenient format.

The example below shows how this feature should be used to read the battery level of a device:

from whad.device import Device
from whad.ble import Central, UUID, BatteryService
from whad.ble.exceptions import PeripheralNotFound

# We assign a BLE central role to our HCI adapter
central = Central(Device.create("hci0"))

# Target not connected
target = None

try:
    # Connect to remote device and discover services and characteristics
    target = central.connect("00:11:22:33:44:55", random=True)
    target.discover()

    # Check the device exposes a Battery service, queries it and read
    # the battery's level as a percentage
    if target.has(BatteryService):
        battery = target.query(BatteryService)
        print(f"Battery level: {battery.percentage}%")
    else:
        print("Battery service is not supported by this device.")

    # Closing connection
    target.disconnect()

# Handle connection error
except PeripheralNotFound:
    print("Target device not found.")

Note

Custom services can also be defined using the same mechanisms, and used in profile definition as well as in GATT clients. More information about how services are defined in Defining and using a standard service.

Attention

WHAD provides a very limited set of services for now, but we expect to implement more of them in future versions.

Central connector and events

class whad.ble.connector.Central(device, existing_connection=None, from_json=None, stack=<class 'whad.ble.stack.BleStack'>, client=<class 'whad.ble.stack.gatt.GattClient'>, security_database=None)[source]

This connector provides a BLE Central role.

To initiate a connection to a device, just call Central.connect() with the target BD address and it should return an instance of whad.ble.profile.device.PeripheralDevice in return.

add_event_handler(handler, event_type=None)[source]

Add an event handler for a specific event.

clear_event_handlers()[source]

Remove all event handlers

property conn_handle: int

Connection handle.

connect(bd_address, random=False, timeout=30, access_address=None, channel_map=None, crc_init=None, hop_interval=None, hop_increment=None) PeripheralDevice[source]

Connect to a target device

Parameters:
  • bd_address (str) – Bluetooth device address (in format ‘xx:xx:xx:xx:xx:xx’)

  • timeout (float) – Connection timeout

  • access_address (int) – Access address to use (optional)

  • channel_map (int) – Channel map to use (optional)

  • crc_init (int) – CRC Initialization value to use (optional)

  • hop_interval (int) – Hop interval to use (optional)

  • hop_increment (int) – Hop increment to use (optional)

Returns:

An instance of PeripheralDevice on success, None on failure.

Return type:

whad.ble.profile.device.PeripheralDevice

export_profile()[source]

Export GATT profile of the existing connection.

Returns:

Profile as a JSON string

Return type:

str

get_mtu() int | None[source]

Retrieve the connection MTU.

is_connected() bool[source]

Determine if the central device is connected to a peripheral.

Returns:

True if central is connected to a peripheral device, False otherwise.

Return type:

bool

property local_peer: BDAddress

Local peer BD address.

notify(event: Event)[source]

Send event to handlers.

This method is called from methods associated with incoming events and is called from the Central’s connector IoThread

Parameters:

event (CentralEvent) – Central event to notify

on_central_event(event: Event)[source]

Central event handler

Parameters:

event (CentralEvent) – Central event to handle

on_connected(connection_data)[source]

Callback method to handle connection event.

Parameters:

connection_data (dict) – Connection data

on_ctl_pdu(pdu)[source]

This callback method is called whenever a control PDU is received. This PDU is then forwarded to the BLE stack to handle it.

Central devices act as master, so we only forward slave to master messages to the stack.

Parameters:

pdu (scapy.layers.bluetooth4LE.BTLE) – BLE Control PDU

on_data_pdu(pdu)[source]

This callback method is called whenever a data PDU is received. This PDU is then forwarded to the BLE stack to handle it.

Central devices act as master, so we only forward slave to master messages to the stack.

Parameters:

pdu (scapy.layers.bluetooth4LE.BTLE_DATA) – BLE Data PDU

on_disconnected(disconnection_data)[source]

Callback method to handle disconnection event.

Parameters:

disconnection_data (whad.protocol.ble_pb2.Disconnected) – Disconnection data

on_mtu_changed(conn_handle: int, mtu: int)[source]

Notify MTU change to peripheral.

on_new_connection(connection)[source]

On new connection, discover primary services.

Parameters:

connection (whad.protocol.ble_pb2.Connected) – New connection Protobuf message

peripheral() PeripheralDevice | None[source]

Connected BLE peripheral.

remove_event_handler(handler)[source]

Remove an event handler.

property security_database

Central role security database.

send_pdu(pdu, conn_handle=0, direction=1, access_address=2391391958, encrypt=None) bool[source]

Send a PDU to the connected peripheral device or to the central device.

Parameters:
  • pdu (scapy.layers.bluetooth4LE.BTLE) – BLE PDU to send.

  • conn_handle (int) – Connection handle

  • direction (whad.protocol.ble.ble_pb2.BleDirection) – Direction (central to peripheral, peripheral to central)

  • access_address (int) – Access address to use while sending PDU.

  • encrypt (bool) – Enable PDU encryption if set to True.

Returns:

PDU transmission result.

Return type:

bool

set_mtu(mtu: int)[source]

Set connection MTU.

set_profile_json(profile)[source]

Set Central profile as json

property stack

Return the current stack instance

stop()[source]

Stopping Central connector.

property target_peer: BDAddress

Remote peer BD address.

version(synchronous=True)[source]

Query BLE version of remote peer.

class whad.ble.connector.central.CentralConnected(handle: int, local: BDAddress, remote: BDAddress)[source]

Central connection event

property handle: int

Connection handle

property local_peer: BDAddress

Central BD address

property remote_peer: BDAddress

Remote device BD address

class whad.ble.connector.central.CentralDisconnected(handle: int)[source]

Client has disconnected from central

property handle: int

Connection handle

Peripheral device abstraction

This module provides the PeripheralDevice class used to wrap all GATT operations for a given connected device:

  • discovering services, characteristics and descriptors

  • reading a characteristic’s value

  • writing to a characteristic’s value

  • subscribing for notifications and indications

  • exchanging MTU value with the remote peripheral

class whad.ble.profile.device.PeripheralCharacteristic(characteristic, gatt)[source]

Characteristic wrapper for peripheral devices

Instruments gatt to read/write a remote characteristic.

get_descriptor(desc_type: UUID | Type[Descriptor])[source]

Retrieve a specific descriptor from those associated with this characteristic.

read(offset: int = 0, long: bool = False) bytes[source]

Read characteristic value.

Parameters:
  • offset (int) – If specified, start reading at this offset.

  • long – If enabled, perform a long read.

Ttype long:

bool

Returns:

Content of the characterstic’s value

Return type:

bytes

subscribe(notification=False, indication=False, callback=None)[source]

Subscribe for notification/indication.

Parameters:
  • notification (bool) – If set, subscribe for notification

  • indication (bool) – If set, subscribe for indication (cannot be used when notification is set)

  • callback (callable) – Callback function to be called on indication/notification event

Return bool:

True if subscription has successfully been performed, False otherwise.

unsubscribe()[source]

Unsubscribe from this characteristic.

property value: bytes

Characteristic’s value

write(value: bytes, without_response: bool = False) bool[source]

Set characteristic value

If characteristic is only writeable without response, use a write command rather than a write request. Otherwise, use a write request. If a characteristic has both write and write without response properties, without_response must be set to True to use a write command.

Parameters:
  • value (bytes) – Value to write into the characteristic

  • without_response – Send a GATT write command instead of a GATT write if set to True

Returns:

True on successful write, False otherwise

Return type:

bool

class whad.ble.profile.device.PeripheralCharacteristicDescriptor(descriptor, gatt)[source]

Wrapper for a peripheral characteristic descriptor.

property value: bytes

Transparent characteristic read.

Return bytes:

Characteristic value

class whad.ble.profile.device.PeripheralCharacteristicValue(char_value, gatt)[source]

CharacteristicValue wrapper for peripheral devices

Forward all read/write operations to PeripheralCharacteristic wrapper because initially access to characteristic has been implemented there.

Could be interesting in the future to implement all these operations here and to forward from characteristic wrapper to characteristic value wrapper because it makes more sense.

Anyway, this code just works but from an architectural point-of-view is a bit crappy.

property value: bytes

Transparent characteristic read.

Return bytes:

Characteristic value

class whad.ble.profile.device.PeripheralDevice(central, gatt_client, conn_handle, from_json=None)[source]

GATT client wrapper representing a remote device.

This class is used to wrap a device model used in a gatt client in order to provide easy-to-use methods to access its services, characteristics and descriptors.

char(uuid: str | UUID, service: str | UUID | None = None) PeripheralCharacteristic | None[source]

Retrieve a characteristic by its UUID. If more than one characteristic is found, returns the first match.

Search can be narrowed to a specific service if the service parameter is set.

Parameters:
Returns:

First matching characteristic, None if not found

Return type:

PeripheralCharacteristic

Raise:

InvalidUUIDException

property conn_handle: int

Current connection handle.

disconnect()[source]

Terminate the connection to this device

discover(include_values: bool = False)[source]

Discovers services, characteristics and descriptors.

This method must be called before accessing any service or characteristic, as it is required to retrieve the corresponding GATT handles.

discover_primary_services(start: int = 1)[source]

Discovers primary services only, starting from handle start. Discovered services are added to the current GATT profile and can be listed with services().

Parameters:

start (int) – Discover services with handle in range [start, 0xFFFF]

discovery_service_characteristics(service: Service, values: bool = False, start: int = 1) Iterator[Characteristic][source]

Discovers characteristics that belong to a specific service.

Parameters:
  • service (Service) – Service to discover characteristics from

  • values (bool, optional) – When set to True, characteristics’ value will be read and loaded into their corresponding CharacteristicValue’s value

  • start (int, optional) – Handle value used as the starting value for this discovery procedure

Returns:

Iterator over discovered characteristics

Return type:

Iterator[Characteristic]

find_characteristics_by_uuid(uuid: UUID) List[PeripheralCharacteristic][source]

Find characteristic by its UUID

Parameters:

uuid (whad.ble.profile.attribute.UUID) – Characteristic UUID

Returns:

PeripheralCharacteristic: An instance of PeripheralCharacteristic if characteristic has been found, None otherwise.

Return type:

whad.ble.profile.device.PeripheralCharacteristic

find_object_by_handle(handle) Attribute | None[source]

Find an existing object (service, attribute, descriptor) based on its handle, it known from the underlying GenericProfile.

Parameters:

handle (int) – Object handle

Returns:

Characteristic, characteristic value or service

Return type:

whad.ble.profile.device.PeripheralCharacteristic, whad.ble.profile.device.PeripheralCharacteristicValue, whad.ble.profile.device.PeripheralService

find_service_by_uuid(uuid: UUID) Service | None[source]

Find service by its UUID

Parameters:

uuid (whad.ble.profile.attribute.UUID) – Characteristic UUID

Returns:

PeripheralService: An instance of PeripheralService if service has been found, None otherwise.

Return type:

whad.ble.profile.device.PeripheralService

get_characteristic(service_uuid: UUID, charac_uuid: UUID)[source]

Get a PeripheralCharacteristic object representing a characteristic defined by the given service UUID and characteristic UUID.

Parameters:
Returns:

PeripheralCharacteristic object on success, None if not found.

Return type:

whad.ble.profile.device.PeripheralCharacteristic

get_mtu() int[source]

Retrieve the current host’s connection MTU.

Returns:

WHAD device MTU

Return type:

int

get_service(uuid)[source]

Retrieve a PeripheralService object given its UUID.

Parameters:

uuid (whad.ble.profile.attribute.UUID) – Service UUID

Returns:

Corresponding PeripheralService object if found, None otherwise.

Return type:

whad.ble.profile.device.PeripheralService

Deprecated since version 1.3.0: The new service() method shall be used to retrieve a PeripheralService object representing a service identified by a given UUID.

has(interface: Type[PT]) bool[source]

Check if device exposes a specific service interface.

on_disconnect(conn_handle)[source]

Disconnection callback

Parameters:

conn_handle (int) – Connection handle

on_mtu_changed(mtu: int)[source]

MTU change callback

Parameters:

mtu (int) – New MTU value

pairing(pairing=None)[source]

Trigger a pairing according to provided parameters. Default parameters will be used if pairing parameter is None.

query(interface: Type[PT]) PT[source]

Dynamically load a pluggable service into this device definition.

read(handle, offset=None, long=False)[source]

Perform a read operation on an attribute based on its handle.

This method allows to interact with characteristics and descriptors without having performing a GATT services and characteristics discovery. One just need to specify the handle corresponding to a characteristic value or descriptor and our GATT stack will handle it.

Note that there is absolutely no check on corresponding characteristic permissions (meaning you can try to read from a write-only characteristic value) and that this method may raise exceptions due to potential GATT errors the remote device may return.

Parameters:
  • handle (int) – Characteristic or descriptor handle.

  • offset (int, optional) – Offset applied when reading data from characteristic or descriptor (default: 0).

  • long (bool, optional) – use GATT long read procedure if set to True (default: False)

Returns:

Content of the characteristic or descriptor.

Return type:

bytes

service(uuid: str | UUID)[source]

Retrieve a PeripheralService object given its UUID.

Parameters:

uuid (str) – Service UUID

Returns:

Corresponding PeripheralService object if found, None otherwise.

Return type:

whad.ble.profile.device.PeripheralService

Raise:

InvalidUUIDException

services() Iterator[PeripheralService][source]

Iterate over the device’s GATT services.

Returns:

An iterator that can be used to iterate over services.

Return type:

Iterator

set_disconnect_cb(callback)[source]

Set disconnection callback.

Parameters:

callback (callable) – Callback function to call on disconnection.

set_mtu(mtu: int)[source]

Update connection MTU.

Parameters:

mtu (int) – ATT MTU to use for this connection.

Returns:

Remote device MTU.

Return type:

int

start_encryption()[source]

Start encryption procedure for BLE peripheral

write(handle, value)[source]

Perform a write operation on an attribute based on its handle.

This method allows to interact with characteristics and descriptors without having performing a GATT services and characteristics discovery. One just need to specify the handle corresponding to a characteristic value or descriptor and the value to write to it, and our GATT stack will handle it.

Note that there is absolutely no check on corresponding characteristic permissions (meaning you can try to write on a read-only characteristic value) and that this method may raise exceptions due to potential GATT errors the remote device may return.

Parameters:
  • handle (int) – Characteristic or descriptor handle to write.

  • value (bytes) – Bytes to write into this characteristic.

write_command(handle, value)[source]

Perform a write command operation (no write response will be sent) on an attribute based on its handle.

This method allows to interact with characteristics and descriptors without having performing a GATT services and characteristics discovery. One just need to specify the handle corresponding to a characteristic value or descriptor and the value to write to it, and our GATT stack will handle it.

Note that there is absolutely no check on corresponding characteristic permissions (meaning you can try to write on a read-only characteristic value) and that this method may raise exceptions due to potential GATT errors the remote device may return.

Parameters:
  • handle (int) – Characteristic or descriptor handle to write.

  • value (bytes) – Bytes to write into this characteristic.

class whad.ble.profile.device.PeripheralService(service, gatt)[source]

Service wrapper for peripheral devices

char(uuid: str | UUID) PeripheralCharacteristic | None[source]

Look for a specific characteristic belonging to this service, identified by its UUID.

Parameters:

uuid (UUID, str) – Characteristic UUID

Returns:

Found characteristic if any, None otherwise.

Return type:

PeripheralCharacteristic

Raise:

InvalidUUIDException

get_characteristic(uuid: UUID) PeripheralCharacteristic | None[source]

Look for a specific characteristic belonging to this service, identified by its UUID.

Parameters:

uuid (UUID) – Characteristic UUID

Returns:

Found characteristic if any, None otherwise.

Return type:

PeripheralCharacteristic

Deprecated since version 1.3.0: This method has been superseeded by char() starting from version 1.3.0, in a effort to make WHAD’s API simpler and easier to use.

read_characteristic_by_uuid(uuid) bytes | None[source]

Read a characteristic belonging to this service identified by its UUID, using a GATT ReadByType procedure as defined in the specification (Vol 3, Part G, Section 4.8.2).

This method can be called at any time, even if the target device’s attributes have not been discovered yet by calling the discover() method.

Parameters:

uuid (UUID) – Characteristic UUID

Return bytes:

Characteristic value