Bluetooth Low Energy Device Model

WHAD uses a specific device model to create BLE peripherals. This device model is implemented in whad.ble.profile.GenericProfile and allows dynamic modification of services and characteristics but also provides a convenient way to define a device services and characteristics.

Creating a device model of a BLE peripheral

Here is an example of a BLE peripheral implemented with WHAD:

from whad.ble import UUID
from whad.ble.profile import GenericProfile, PrimaryService, \
    Characteristic

class MyPeripheral(GenericProfile):

    generic_access = PrimaryService(
        uuid=UUID(0x1800),

        device_name = Characteristic(
            uuid=UUID(0x2A00),
            permissions=['read', 'notify'],
            notify=True,
            value=b'My device name'
        )
    )

whad.ble.profile.GenericProfile performs an introspection on its properties to find every instance of whad.ble.profile.PrimaryService, finds every instance of whad.ble.profile.Characteristic declared into each service and populates its attribute database based on the discovered information.

But this mechanism also allows dynamic modification of any characteristic, for instance the device name characteristic:

periph_inst = MyPeripheral()
periph_inst.generic_access.device_name = b'Another name'

Of course, this can also be done when the peripheral is running and will cause the BLE stack to send notifications or indications based on the characteristics properties.

Hooking GATT events on characteristics

WHAD BLE device model provides a set of method decorators that must be used to attach a method to a specific event and a specific characteristic:

  • whad.ble.profile.read declares a characteristic read event handler

  • whad.ble.profile.write declares a characteristic before-write event handler

  • whad.ble.profile.written declares a characteristic after-write event handler

  • whad.ble.profile.subscribed declares a characteristic subscribe event handler

  • whad.ble.profile.unsubscribed declares a characteristic unsubscribe event handler

A characteristic event handler may raise one of the following exception to cause the GATT stack to react accordingly:

  • whad.ble.exceptions.HookReturnValue: force a characteristic value to be returned to a GATT client on a read event

  • whad.ble.exceptions.HookReturnGattError: generates a GATT error that will be sent back to the connected GATT client

  • whad.ble.exceptions.HookReturnNotFound: tells a GATT client the characteristic does not exist

  • whad.ble.exceptions.HookReturnAccesDenied: tells a GATT client that authentication is required to access this characteristic

If no exception is raised in the event handler, the GATT operation continues as expected. As an example, here follows a peripheral model declaration that uses a characteristic event handler:

class MyPeripheral(GattProfile):

    generic_access = PrimaryService(
        uuid=UUID(0x1800),

        device_name = Characteristic(
            uuid=UUID(0x2A00),
            permissions=['read', 'write', 'notify'],
            notify=True,
            value=b'My device name'
        )
    )

    @read(generic_access.device_name)
    def on_device_name_read(self, offset, mtu):
        """Return the content of the device name characteristic prefixed with 'FOO'
        """
        raise HookReturnValue(b'FOO'+ self.generic_access.device_name.value)

    @written(generic_access.device_name)
    def on_device_name_changed(self, value, without_response):
        """Called every time the device name characteristic has been changed by client.
        """
        print('Device name has been changed to: %s' % value)

GATT Generic Profile

class whad.ble.profile.GenericProfile(start_handle=0, from_json=None)[source]

Generic Profile

__init__(start_handle=0, from_json=None)[source]

Parse the device model, instanciate all the services, characteristics and descriptors, compute all handle values and registers everything inside this instance for further use.

Parameters:
  • start_handle (int) – Start handle value to use (default: 0)

  • from_json (str) – JSON data describing a GATT profile

add_service(service, handles_only=False)[source]

Add a service to the current device

Parameters:
  • service (whad.ble.profile.service.Service) – Service to add to the device

  • handles_only (bool) – Add only service handles if set to True

attr_by_type_uuid(uuid, start=1, end=65535) Iterator[Attribute][source]

Enumerate attributes that have a specific type UUID.

Parameters:
  • uuid (whad.ble.profile.attribute.UUID) – Type UUID

  • start (int) – Start handle

  • end (int) – End handle

export_json()[source]

Export profile as JSON data, including services, characteristics and descriptors definition.

Returns:

JSON data corresponding to this profile

Return type:

str

find_characteristic_by_value_handle(value_handle) Characteristic[source]

Find characteristic object by its value handle.

Parameters:

value_handle (int) – Characteristic value handle

Returns:

Corresponding characteristic object or None if not found.

Return type:

whad.ble.profile.characteristic.Characteristic

find_characteristic_end_handle(handle) int[source]

Find characteristic end handle based on its handle.

Parameters:

handle (int) – Characteristic handle

Return type:

int

Returns:

Characteristic value handle

Raises:

whad.ble.exceptions.InvalidHandleValueException

find_hook(service, characteristic, operation) callable[source]

Find a registered hook for a specific service, characteristic and operation.

Parameters:
  • service (whad.ble.profile.service.Service) – Service object

  • characteristic (whad.ble.profile.characteristic.Characteristic) – Characteristic object

  • operation (str) – GATT operation

Returns:

Hook callback

Return type:

callable

find_object_by_handle(handle) Attribute[source]

Find an object by its handle value

Parameters:

handle (int) – Object handle

Returns:

Object if handle is valid, or raise an IndexError exception otherwise

Return type:

whad.ble.profile.attribute.Attribute

Raises:

IndexError

find_objects_by_range(start, end) List[Attribute][source]

Find attributes with handles belonging in the [start, end+1] interval.

Parameters:
  • start (int) – Start handle value

  • end (int) – End handle value

Returns:

List of objects with handles between start and end values

Return type:

list

find_service_by_characteristic_handle(handle) Service[source]

Find a service object given a characteristic handle that belongs to this service.

Parameters:

handle (int) – Characteristic handle belonging to the searched service

Return type:

whad.ble.profile.service.Service

Returns:

Service object containing the specified characteristic

Raises:

whad.ble.exceptions.InvalidHandleValueException

get_characteristic_by_UUID(charac_uuid: UUID)[source]

Get characteristic by its UUID.

Parameters:

charac_uuid (whad.ble.profile.attribute.UUID) – Characteristic UUID to look for

Returns:

Characteristic if found, None otherwise

Return type:

whad.ble.profile.characteristic.Characteristic

get_service_by_UUID(service_uuid: UUID)[source]

Get a service by its UUID.

Parameters:

service_uuid (whad.ble.profile.attribute.UUID) – Service UUID to look for

Returns:

Service if found, None otherwise

Return type:

whad.ble.profile.service.Service

on_characteristic_read(service, characteristic, offset=0, length=0)[source]

Characteristic read hook.

This hook is called whenever a characteristic is about to be read by a GATT client. If this method returns a byte array, this byte array will be sent back to the GATT client. If this method returns None, then the read operation will return an error (not allowed to read characteristic value).

Parameters:
  • service (whad.ble.profile.service.Service) – Service owning the characteristic

  • characteristic (whad.ble.profile.characteristic.Characteristic) – Characteristic object

  • offset (int) – Read offset (default: 0)

  • length (int) – Max read length

Returns:

Value to return to the GATT client

Return type:

bytes

on_characteristic_subscribed(service, characteristic, notification=False, indication=False)[source]

Characteristic subscribed hook

This hook is called whenever a characteristic has been subscribed to.

Parameters:
  • service (whad.ble.profile.service.Service) – Service owning the characteristic

  • characteristic (whad.ble.profile.characteristic.Characteristic) – Characteristic object

  • notification (bool) – Set to True if subscribed to notification

  • indication (bool) – Set to True if subscribed to notification

on_characteristic_unsubscribed(service, characteristic)[source]

Characteristic unsubscribed hook

This hook is called whenever a characteristic has been unsubscribed.

Parameters:
  • service (whad.ble.profile.service.Service) – Service owning the characteristic

  • characteristic (whad.ble.profile.characteristic.Characteristic) – Characteristic object

on_characteristic_write(service, characteristic, offset=0, value=b'', without_response=False)[source]

Characteristic write hook

This hook is called whenever a charactertistic is about to be written by a GATT client.

Parameters:
  • service (whad.ble.profile.service.Service) – Service owning the characteristic

  • characteristic (whad.ble.profile.characteristic.Characteristic) – Characteristic object

  • offset (int) – Read offset (default: 0)

  • value (bytes) – Value about to be written into the characteristic

  • without_response (bool) – Set to True if no response is required

on_characteristic_written(service, characteristic, offset=0, value=b'', without_response=False)[source]

Characteristic written hook

This hook is called whenever a charactertistic has been written by a GATT client.

Parameters:
  • service (whad.ble.profile.service.Service) – Service owning the characteristic

  • characteristic (whad.ble.profile.characteristic.Characteristic) – Characteristic object

  • offset (int) – Read offset (default: 0)

  • value (bytes) – Value about to be written into the characteristic

  • without_response (bool) – Set to True if no response is required

on_connect(conn_handle)[source]

Connection hook.

This hook is only used to notify the connection of a device.

Parameters:

conn_handle (int) – Connection handle

on_disconnect(conn_handle)[source]

Disconnection hook.

This hook is only used to notify the disconnection of a device.

Parameters:

conn_handle (int) – Connection handle

on_indication(service, characteristic, value)[source]

Characteristic indication hook.

This hook is called when a indication is sent to a characteristic.

Parameters:
  • service (whad.ble.profile.service.Service) – Service owning the characteristic

  • characteristic (whad.ble.profile.characteristic.Characteristic) – Characteristic object

  • value (bytes) – Characteristic value

on_notification(service, characteristic, value)[source]

Characteristic notification hook.

This hook is called when a notification is sent to a characteristic.

Parameters:
  • service (whad.ble.profile.service.Service) – Service owning the characteristic

  • characteristic (whad.ble.profile.characteristic.Characteristic) – Characteristic object

  • value (bytes) – Characteristic value

register_attribute(attribute)[source]

Register a GATT attribute

Parameters:

attribute (whad.ble.profile.attribute.Attribute) – Attribute to register

remove_service(service, handles_only=False)[source]

Remove service

Parameters:
  • service (whad.ble.profile.service.Service) – Service object or UUID

  • handles_only (bool) – Remove only handles if set to True

services() Iterator[Service][source]

Enumerate service objects.

This method is a generator and will yield service objects registered into the profile.

update_service(service) bool[source]

Update service in profile.

Keep service in place in the service list, but update all the services declared after this one.

Parameters:

service (whad.ble.profile.service.Service) – Service object to update.

Returns:

True if service has been updated, False otherwise.

Return type:

bool

GATT Primary service

class whad.ble.profile.PrimaryService(uuid=None, start_handle=0, end_handle=0, name=None, **kwargs)[source]
__init__(uuid=None, start_handle=0, end_handle=0, name=None, **kwargs)[source]

Declares a GATT primary service.

Other named arguments are used to add service’s characteristics.

Parameters:
  • uuid (whad.ble.profile.attribute.UUID) – Primary service UUID

  • start_handle (int, optional) – Service start handle

  • end_handle (int, optional) – Service end handle

  • name (str) – Service name

GATT Characteristic

class whad.ble.profile.Characteristic(name=None, uuid=None, value=b'', permissions=None, notify=False, indicate=False, description=None, security=[], **kwargs)[source]

GATT characteristic.

__init__(name=None, uuid=None, value=b'', permissions=None, notify=False, indicate=False, description=None, security=[], **kwargs)[source]

Declares a GATT characteristic.

Other named arguments are used to declare characteristic’s descriptors.

Parameters:
  • name (str) – Characteristic name used in GATT model

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

  • permissions (list) – List of permissions for this characteristic (read, write, notify, indicate)

  • notify (bool) – Enable notifications

  • indicate (bool) – Enable indications

  • description (str) – Textual description for this characteristic

  • security (SecurityAccess) – Indicate the security property associated to this characteristic

add_descriptor(descriptor)[source]

Add descriptor to our descriptor list

Parameters:

descriptor (whad.ble.profile.characteristic.CharacteristicDescriptor) – Descriptor to add to the characteristic’s descriptor list

attach(service)[source]

Attach this characteristic to the corresponding service.

Parameters:

service (:class:̀ whad.ble.profile.service.Service`) – Service

property description: str

Return characteristic textual description, if any

descriptors() Iterator[CharacteristicDescriptor][source]

Enumerate descriptors attached to this characteristic

This method will yield every descriptor attached to the characteristic.

property end_handle: int

Characteristic end handle (including characteristic value and descriptors).

get_required_handles() int[source]

Compute the number of handles this characteristic will consume

Returns:

Number of handles

Return type:

int

property handle: int

Characteristic handle

property must_indicate: bool

Check if indication has to be sent on value change.

property must_notify: bool

Check if notification has to be sent on value change.

property name: str

Name

property permissions: List[str]

Characteristics permissions

property security: SecurityAccess

Returns security access property

property service: Service

Related service.

property value: UUID

Characteristic UUID