Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4efda32
Updated BLE domain to allow better control on advertising data and ad…
virtualabs Sep 25, 2025
b00233f
Added BLE Advertiser class.
virtualabs Sep 26, 2025
c507c96
Modified call to create_adv_mode to convert advertising data and scan…
virtualabs Sep 26, 2025
cf30f05
Added extra advertising-related parameters to the PeriphMode message …
virtualabs Sep 26, 2025
af32cd1
Added features to ChannelMap class to ease filtering and get number o…
virtualabs Sep 26, 2025
f64a29f
Added support for advanced advertising parameters in PeriphMode messa…
virtualabs Sep 26, 2025
ccc2b8a
Fixed a typo in HCI virtual device.
virtualabs Sep 26, 2025
d583aff
Improved the Advertiser connector and BLE peripheral mode related met…
virtualabs Sep 26, 2025
8eedf4d
Improve mock message dispatch by allowing a single method to be calle…
virtualabs Sep 26, 2025
6462567
Modified the mock route decorator to allow multiple message classes t…
virtualabs Sep 26, 2025
c6d7ca0
Update HCI virtual device to handle new peripheral mode message.
virtualabs Sep 26, 2025
f6a6054
Updated the BLE peripheral mock device to handle the new PeriphMode m…
virtualabs Sep 26, 2025
af5d6d1
Updated BLE's Peripheral connector to use the extended advertising pa…
virtualabs Sep 26, 2025
542ec95
Removed a duplicated `enable_peripheral_mode()` method that messed up…
virtualabs Sep 26, 2025
590996a
Updated PeriphMode message.
virtualabs Sep 27, 2025
c4fc677
Added more tests for Peripheral connector.
virtualabs Sep 27, 2025
d996dd0
Updated hub to support BLE advertising parameters update for both Per…
virtualabs Sep 27, 2025
818d25c
Updated the Advertiser connector and added an example.
virtualabs Sep 27, 2025
d96256b
Added unit/functional tests for BLE Advertiser connector
virtualabs Sep 27, 2025
ccde85a
Improved Peripheral and Advertiser to use a tuple for the advertising…
virtualabs Sep 29, 2025
21d4fbf
Updated default BLE connector to allow advertising and scan response …
virtualabs Sep 29, 2025
13e357e
Update unit tests related to BLE advertising data due to a change of …
virtualabs Sep 29, 2025
de53a5e
Updated the BLE advertiser example to reflect the recent changes made…
virtualabs Sep 29, 2025
1a94ad6
Updated BLE getting started documentation to include the new Advertis…
virtualabs Sep 29, 2025
87d28b5
Fixed a typo.
virtualabs Sep 29, 2025
123d46c
Added BLE advertiser class documentation
virtualabs Sep 29, 2025
b02a4cd
Updated documentation to reflect recent modifications.
virtualabs Sep 29, 2025
8554d2a
Fixed a bug in the new PeriphMode message that caused tests to fail w…
virtualabs Sep 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions doc/source/ble/advertiser.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
Advertiser role
===============

Bluetooth Low Energy advertising only feature is provided by a dedicated connector implementing the
*Broadcaster* role as defined in the Bluetooth specification, :class:`whad.ble.connector.Advertiser`.
This class drives a BLE-enable WHAD device to send advertisements with no stack bound.

Creating a Bluetooth Low Energy *advertiser*
--------------------------------------------

The :class:`~whad.ble.connector.Advertiser` class implements the Bluetooth Low Energy *Broadcaster*
role that is used to only *broadcast* advertisements without accepting any connection. This role
allows to send specific advertisements with specific type, on one or more advertising channels at
a given pace. These parameters can be specified in this class constructor and updated later.

Advertising parameters
~~~~~~~~~~~~~~~~~~~~~~

Advertisements can be of different types, each defining a specific behavior and giving any device
that receives them information about the advertised device. They are also used in the *Bluetooth Mesh*
protocol to convey data as a primary transport channel.

The first parameter ``adv_type`` required when creating an advertisement is the *advertisement type*. This parameter
defines the purpose of the advertisement and can be one of the values defined in the :class:`~whad.hub.ble.AdvType`
class:

- ``ADV_IND``: connectable and scannable undirected advertising
- ``ADV_DIRECT_IND``: connectable directed advertising
- ``ADV_SCAN_IND``: scannable undirected advertising
- ``ADV_NONCONN_IND``: non connectable undirected advertising

.. important::

Other values from :class:`~whad.hub.ble.AdvType` will cause the advertiser to use connectable and
scannable undirected advertisements.

Configuring the advertising channels used by the advertiser is possible through the ``channels`` parameter.
This parameter accepts a list of channels amongst the ones used for primary advertising: ``37``, ``38`` and ``39``.
At least one channel must be specified, and any channel that does not belong to these advertising channels
will be ignored.

Advertising interval can be specified through the ``interval`` parameter, a tuple of two integers specifying
the minimum and maximum values of the advertising interval to use (e.g. ``(<min value>, <max value>)``, with the following constraints:

- the minimum interval value must be greater than 0x20 and lower than the maximum interval value
- the maximum interval value must not exceed 0x4000 and be greater than minimum value

:class:`~whad.ble.connector.Advertiser` will raise a :class:`ValueError` exception whenever an invalid
parameter is passed to its constructor.

Here is a valid example of the creation of an advertiser sending non-connectable undirected
advertisements on channel 37 only, with an advertising interval comprised between 0x20 and 0x4000:

.. code-block:: python

from whad.device import Device
from whad.ble import Advertiser, AdvType
from whad.ble.profile import AdvDataFieldList, AdvDataFlagsField

adv = Advertiser(
Device.create("hci0"),
adv_data = AdvDataFieldList(AdvDataFlagsField()),
scan_data = None,
adv_type = AdvType.ADV_NONCONN_IND,
channels = [37],
interval = (0x20, 0x4000)
)
adv.start()

# Wait for user input
input("Press enter to stop advertising.")

If the advertising interval must be updated, the connector must be stopped before and restarted once the
parameters updated:

.. code-block:: python

adv.stop()
adv.interval = (0x1000, 0x2000)
adv.start()


Advertising data
~~~~~~~~~~~~~~~~

Advertising data is specified through the ``adv_data`` parameter of the constructor, and scan response data
through the ``scan_data`` parameter. Both parameters accept either instances of :class:`~whad.ble.profile.AdvDataFieldList`
or directly a ``bytes`` object. In the first case, the advertiser will convert the provided advertising records
into a byte sequence while in the other it will directly use the provided byte sequence without checking its
completeness.


.. code-block:: python

adv.adv_data = AdvDataFieldList(AdvDataFlagsField(), AdvDataCompleteLocalName(b"Foobar"))

.. note::

Advertising and scan response data can be updated at any time, no matter the advertiser status.

Bluetooth Low Energy Advertiser connector
-----------------------------------------

.. autoclass:: whad.ble.connector.Advertiser
:members:

4 changes: 2 additions & 2 deletions doc/source/ble/peripheral.rst
Original file line number Diff line number Diff line change
Expand Up @@ -264,10 +264,10 @@ is *mandatory*:

def on_characteristic_read(self, service: Service, characteristic: Characteristic, offset: int = 0, length: int = 0):
"""Hook for GATT read operation"""

# Call parent method
super().on_characteristic_read(service, characteristic, offset=offset, length=length)

# Continue with custom processing (Forcing characteristic value)
raise HookReturnValue(b"Oops")

Expand Down
2 changes: 1 addition & 1 deletion doc/source/ble/scanner.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Device Scanning
Device scanning
===============

Bluetooth Low Energy scanning is provided by a dedicated connector, :class:`whad.ble.connector.Scanner`,
Expand Down
119 changes: 98 additions & 21 deletions doc/source/ble/started.rst
Original file line number Diff line number Diff line change
@@ -1,18 +1,87 @@
Getting started
===============

WHAD provides a set of classes and features related to Bluetooth Low Energy allowing
to advertise, scan, connect, interact and even emulate BLE devices. This section
introduces these different classes with minimal examples to get you started.

Send advertisements with no connection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Use the :class:`~whad.ble.connector.advertiser.Advertiser` class to instantiate
a BLE advertiser and send advertisements on Bluetooth Low Energy's advertising
channels (*i.e. channels 37, 38, 39*).

.. code-block:: python

from whad.device import Device
from whad.ble import Advertiser, AdvDataFieldList, AdvFlagsField, AdvCompleteLocalName
from whad.hub.ble import AdvType

advert = Advertiser(
# Use device 'hci0' (Bluetooth HCI adapter)
Device.create("hci0"),

# Set advertising data
AdvDataFieldList(
# Add a default flags AD record
AdvFlagsField(),
# Add a complete local name AD record
AdvCompleteLocalName(b"AdvertiserDemo")
),

# No scan response data
None,

# Use a non-connectable undirected advertisement type
AdvType.ADV_NONCONN_IND,

# Advertise on all channels
channels=[37,38,39]
)

# Start advertising
advert.start()

# Wait for a keypress to stop
input('Press a key to stop advertising device ...')
advert.stop()
advert.close()

This example advertises a device named `AdvertiserDemo` that is announced as non-connectable.
Nordic's *NRF Connect* application can be used to check the advertised device exposes the
specified advertisement record and is non-connectable, as shown below.


.. image:: /images/ble/advertiser_nrfconnect.png
:alt: NRF Connect application showing a non-connectable device named "AdvertiserDemo"
:width: 400px
:align: center


The device's advertising data and scan response data can be updated at any moment by accessing
the instance's ``adv_data`` and ``scan_data`` properties:

.. code-block:: python

advert.adv_data = AdvDataFieldList(AdvFlagsField(), AdvCompleteLocalName(b"ChangedName"))

Advertisement core parameters like the advertisement type used, channel map or even
advertising interval can be updated by using their associated properties but only when the
advertiser is stopped.

Scan available devices
~~~~~~~~~~~~~~~~~~~~~~

Use the :class:`whad.ble.connector.scanner.Scanner` class to instantiate
Use the :class:`~whad.ble.connector.scanner.Scanner` class to instantiate
a BLE device scanner and detect all the available devices.

.. code-block:: python

from whad import UartDevice
from whad.device import Device
from whad.ble import Scanner

scanner = Scanner(UartDevice('/dev/ttyUSB0'))
scanner = Scanner(Device.create("hci0"))
scanner.start()
for rssi, advertisement in scanner.discover_devices():
advertisement.show()
Expand All @@ -21,21 +90,21 @@ a BLE device scanner and detect all the available devices.
Initiate a connection to a BLE device
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Use the :class:`whad.ble.connector.central.Central` class to create a
Use the :class:`~whad.ble.connector.central.Central` class to create a
BLE central device and initiate a connection to a BLE peripheral device.

.. code-block:: python

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

# Create a central device
central = Central(UartDevice('/dev/ttyUSB0'))
central = Central(Device.create("hci0"))

# Connect to our target device
target = central.connect('0C:B8:15:C4:88:8E')

The `connect()` method returns a :class:`whad.ble.profile.device.PeripheralDevice` object
The `connect()` method returns a :class:`~whad.ble.profile.device.PeripheralDevice` object
that represents the remote device.

Enumerate services and characteristics
Expand All @@ -52,7 +121,7 @@ and display them.
# Display target profile
print(target)

The :class:`whad.ble.profile.device.PeripheralDevice` also provides some methods
The :class:`~whad.ble.profile.device.PeripheralDevice` also provides some methods
to iterate over services and characteristics:

.. code-block:: python
Expand Down Expand Up @@ -124,7 +193,7 @@ the device services and characteristics:

.. code-block:: python

from whad import UartDevice
from whad.device import Device
from whad.ble import Peripheral
from whad.ble.profile import GattProfile
from whad.ble.profile.advdata import AdvCompleteLocalName, AdvDataFieldList, AdvFlagsField
Expand Down Expand Up @@ -158,7 +227,7 @@ using this profile:
my_profile = MyPeripheral()

# Create a periphal device based on this profile
periph = Peripheral(UartDevice('/dev/ttyUSB0', 115200), profile=my_profile)
periph = Peripheral(Device.create("hci0"), profile=my_profile)

# Enable peripheral mode with advertisement data:
# * default flags (general discovery mode, connectable, BR/EDR not supported)
Expand All @@ -175,29 +244,29 @@ It is also possible to trigger specific actions when a characteristic is read or
through the dedicated callbacks provided by :class:`whad.ble.profile.GenericProfile`.

Advanced features
-----------------
~~~~~~~~~~~~~~~~~

Sending and receiving PDU
~~~~~~~~~~~~~~~~~~~~~~~~~
Sending and receiving PDUs
--------------------------

It is sometimes useful to send a PDU to a device as well as processing any
incoming PDU without having to use a protocol stack. The BLE :py:class:`whad.ble.connector.Peripheral`
and :py:class:`whad.ble.connector.Central` connector provides a nifty way to do it:
incoming PDU without having to use a protocol stack. The BLE :py:class:`~whad.ble.connector.Peripheral`
and :py:class:`~whad.ble.connector.Central` connector provides a nifty way to do it:

.. code:: python

from whad.ble import Central
from whad.device import WhadDevice
from whad.device import Device
from scapy.layers.bluetooth4LE import *

# Connect to target
print('Connecting to remote device ...')
central = Central(WhadDevice.create('uart0'))
central = Central(Device.create("uart0"))
device = central.connect('00:11:22:33:44:55', random=False)

# Make sure connection has succeeded
if device is not None:

# Enable synchronous mode: we must process any incoming BLE packet.
central.enable_synchronous(True)

Expand All @@ -221,16 +290,17 @@ and :py:class:`whad.ble.connector.Central` connector provides a nifty way to do
The above example connects to a target device, sends an `LL_VERSION_IND`
PDU and waits for an `LL_VERSION_IND` PDU from the remote device.

Normally, when a :class:`whad.device.connector.WhadDeviceConnector`
Normally, when a :class:`~whad.device.connector.WhadDeviceConnector`
(or any of its inherited classes) is used it may rely on a protocol stack to process
outgoing and ingoing PDUs. By doing so, there is no way to get access to the received
PDUs and avoid them to be forwarded to the connector's protocol stack.

However, all connectors expose a method called :meth:`whad.device.connector.WhadDeviceConnector.enable_synchronous`
However, all connectors expose a method called :meth:`~whad.device.connector.WhadDeviceConnector.enable_synchronous`
that can enable or disable this automatic processing of PDUs. By default,
PDUs are passed to the underlying protocol stack but we can force the connector
to keep them in a queue and to wait for us to retrieve them:


.. code:: python

# Disable automatic PDU processing
Expand All @@ -241,4 +311,11 @@ the connector in a dedicated queue and can be retrieved using
:py:meth:`whad.device.connector.WhadDeviceConnector.wait_packet`.
This method requires the connector to be in synchronous mode and will return
a PDU from the connector's queue, or `None` if the queue is empty once the
specified timeout period expired.
specified timeout period expired.

.. warning::

Sending BLE control PDUs can only be performed with WHAD devices supporting *raw PDUs*, like
a nRF52840 dongle running *ButteRFly*. Bluetooth USB dongles exposing an HCI interface cannot
send or receive raw PDUs.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/source/images/ble/advertiser_nrfconnect.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Welcome to WHAD's documentation!

ble/started
ble/scanner
ble/advertiser
ble/central
ble/peripheral
ble/profile
Expand Down
40 changes: 40 additions & 0 deletions examples/ble/ble_advertiser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import sys
import logging
from whad.hub.ble import AdvType
from whad.ble.connector.advertiser import Advertiser
from whad.ble.profile.advdata import AdvCompleteLocalName, AdvDataFieldList, AdvFlagsField
from whad.device import WhadDevice

if len(sys.argv) >= 2:
# Create the connector & the profile
advertiser = Advertiser(
WhadDevice.create(sys.argv[1]),
AdvDataFieldList(
AdvCompleteLocalName(b'Advertising demo'),
AdvFlagsField()
),
None,
adv_type=AdvType.ADV_NONCONN_IND,
interval=(0x20, 0x40)
)
advertiser.start()
print("Device is now advertising ...")

# Wait for a key press
try:
print("Press enter to update device name.")
input()
advertiser.update(
adv_data=AdvDataFieldList(
AdvCompleteLocalName(b'Updated'),
AdvFlagsField()
),
)
print("Device name has been changed to 'Updated'.\nPress enter to stop advertising.")
input()
advertiser.close()
except KeyboardInterrupt:
advertiser.close()

else:
print("Usage:", sys.argv[0], "<interface>")
Loading