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 ofwhad.ble.profile.device.PeripheralDevicein return.- 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:
- export_profile()[source]
Export GATT profile of the existing connection.
- Returns:
Profile as a JSON string
- Return type:
str
- is_connected() bool[source]
Determine if the central device is connected to a peripheral.
- Returns:
Trueif 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_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.
- 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
- property stack
Return the current stack instance
- property target_peer: BDAddress
Remote peer BD address.
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.
- 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:
uuid (
str) – Characteristic’s UUIDservice (
whad.ble.profile.attribute.UUID,str, optional) – Service’s UUID
- Returns:
First matching characteristic, None if not found
- Return type:
- Raise:
InvalidUUIDException
- property conn_handle: int
Current connection handle.
- 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:
- 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:
- 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:
service_uuid (
whad.ble.profile.attribute.UUID) – Service UUIDcharac_uuid (
whad.ble.profile.attribute.UUID) – Characteristic UUID
- Returns:
PeripheralCharacteristic object on success, None if not found.
- Return type:
- 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:
Deprecated since version 1.3.0: The new
service()method shall be used to retrieve aPeripheralServiceobject representing a service identified by a given UUID.
- on_disconnect(conn_handle)[source]
Disconnection callback
- Parameters:
conn_handle (int) – Connection handle
- 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:
- 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
- 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:
- 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:
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