BLE API

New in version 1.2.0: BLE API added

BLE Limitations

This module uses the RF24 class to make the nRF24L01 imitate a Bluetooth-Low-Emissions (BLE) beacon. A BLE beacon can send data (referred to as advertisements) to any BLE compatible device (ie smart devices with Bluetooth 4.0 or later) that is listening.

Original research was done by Dmitry Grinberg and his write-up (including C source code) can be found here As this technique can prove invaluable in certain project designs, the code here has been adapted to work with CircuitPython.

Important

Because the nRF24L01 wasn’t designed for BLE advertising, it has some limitations that helps to be aware of.

  1. The maximum payload length is shortened to 18 bytes (when not broadcasting a device name nor the nRF24L01 show_pa_level). This is calculated as:

    32 (nRF24L01 maximum) - 6 (MAC address) - 5 (required flags) - 3 (CRC checksum) = 18

    Use the helper function len_available() to determine if your payload can be transmit.

  2. the channels that BLE use are limited to the following three: 2.402 GHz, 2.426 GHz, and 2.480 GHz. We have provided a tuple of these specific channels for convenience (See BLE_FREQ and hop_channel()).

  3. crc is disabled in the nRF24L01 firmware because BLE specifications require 3 bytes (crc24_ble()), and the nRF24L01 firmware can only handle a maximum of 2. Thus, we have appended the required 3 bytes of CRC24 into the payload.

  4. address_length of BLE packet only uses 4 bytes, so we have set that accordingly.

  5. The auto_ack (automatic acknowledgment) feature of the nRF24L01 is useless when transmitting to BLE devices, thus it is disabled as well as automatic re-transmit (arc) and custom ACK payloads (ack) features which both depend on the automatic acknowledgments feature.

  6. The dynamic_payloads feature of the nRF24L01 isn’t compatible with BLE specifications. Thus, we have disabled it.

  7. BLE specifications only allow using 1 Mbps RF data_rate, so that too has been hard coded.

  8. Only the “on data sent” (irq_ds) & “on data ready” (irq_dr) events will have an effect on the interrupt (IRQ) pin. The “on data fail” (irq_df) is never triggered because auto_ack attribute is disabled.

fake_ble module helpers

circuitpython_nrf24l01.fake_ble.swap_bits(original: int) int[source]

This function reverses the bit order for a single byte.

Returns:

An int containing the byte whose bits are reversed compared to the value passed to the original parameter.

Parameters:
original : int

This is truncated to a single unsigned byte, meaning this parameter’s value can only range from 0 to 255.

circuitpython_nrf24l01.fake_ble.reverse_bits(original: bytes | bytearray) bytearray[source]

This function reverses the bit order for an entire buffer protocol object.

Returns:

A bytearray whose byte order remains the same, but each byte’s bit order is reversed.

Parameters:
original : bytearray,bytes

The original buffer whose bits are to be reversed.

circuitpython_nrf24l01.fake_ble.chunk(buf: bytes | bytearray, data_type: int = 22) bytearray[source]

This function is used to pack data values into a block of data that make up part of the BLE payload per Bluetooth Core Specifications.

Parameters:
buf: bytes | bytearray

The actual data contained in the block.

data_type: int = 22

The type of data contained in the chunk. This is a predefined number according to BLE specifications. The default value 0x16 describes all service data. 0xFF describes manufacturer information. Any other values are not applicable to BLE advertisements.

Important

This function is called internally by advertise(). To pack multiple data values into a single payload, use this function for each data value and pass a list or tuple of the returned results to advertise() (see example code in documentation about advertise() for more detail). Remember that broadcasting multiple data values may require the name be set to None and/or the show_pa_level be set to False for reasons about the payload size with BLE Limitations.

circuitpython_nrf24l01.fake_ble.crc24_ble(data: bytes | bytearray, deg_poly: int = 1627, init_val: int = 5592405) bytearray[source]

This function calculates a checksum of various sized buffers.

This is exposed for convenience and should not be used for other buffer protocols that require big endian CRC24 format.

Parameters:
data: bytes | bytearray

The buffer of data to be uncorrupted.

deg_poly: int = 1627

A preset “degree polynomial” in which each bit represents a degree who’s coefficient is 1. BLE specifications require 0x00065b (default value).

init_val: int = 5592405

This will be the initial value that the checksum will use while shifting in the buffer data. BLE specifications require 0x555555 (default value).

Returns:

A 24-bit bytearray representing the checksum of the data (in proper little endian).

circuitpython_nrf24l01.fake_ble.whitener(buf: bytes | bytearray, coef: int) bytearray[source]

Whiten and de-whiten data according to the given coefficient.

This is a helper function to FakeBLE.whiten(). It has been broken out of the FakeBLE class to allow whitening and dewhitening a BLE payload without the hardcoded coefficient.

Parameters:
buf: bytes | bytearray

The BLE payloads data. This data should include the CRC24 checksum.

coef: int

The whitening coefficient used to avoid repeating binary patterns. This is the index of BLE_FREQ tuple for nRF24L01 channel that the payload transits (plus 37).

coef = None  # placeholder for the coefficient
rx_channel = nrf.channel
for index, chl in enumerate(BLE_FREQ):
    if chl == rx_channel:
        coef = index + 37
        break

Note

If currently used nRF24L01 channel is different from the channel in which the payload was received, then set this parameter accordingly.

circuitpython_nrf24l01.fake_ble.BLE_FREQ = (2, 26, 80)

The BLE channel number is different from the nRF channel number.

This tuple contains the relative predefined channels used:

nRF24L01 channel

BLE channel

2

37

26

38

80

39

QueueElement class

New in version 2.1.0: This class was added when implementing BLE signal sniffing.

class circuitpython_nrf24l01.fake_ble.QueueElement(buffer: bytearray)[source]

A data structure used for storing received & decoded BLE payloads in the rx_queue.

Parameters:
buffer : bytes,bytearray

the validated BLE payload (not including the CRC checksum). The buffer passed here is decoded into this class’s properties.

mac : bytes | bytearray

The transmitting BLE device’s MAC address as a bytes object.

name : str | bytes | None

The transmitting BLE device’s name. This will be a str, bytes object (if a UnicodeError was caught), or None (if not included in the received payload).

pa_level : int | None

The transmitting device’s PA Level (if included in the received payload) as an int.

Note

This value does not represent the received signal strength. The nRF24L01 will receive anything over a -64 dbm threshold.

data : list[bytearray | ServiceData]

A list of the transmitting device’s data structures (if any). If an element in this list is not an instance (or descendant) of the ServiceData class, then it is likely a custom, user-defined, or unsupported specification - in which case it will be a bytearray object.

FakeBLE class

class circuitpython_nrf24l01.fake_ble.FakeBLE(spi: SPI, csn: DigitalInOut, ce_pin: DigitalInOut, spi_frequency: int = 10000000)[source]

Bases: RF24

A class to implement BLE advertisements using the nRF24L01.

Per the limitations of this technique, only some of underlying RF24 functionality is available for configuration when implementing BLE transmissions. See the Unavailable RF24 functionality for more details.

See Also

For all parameters’ descriptions, see the RF24 class’ constructor documentation.

property FakeBLE.mac : bytes | bytearray

This attribute returns a 6-byte buffer that is used as the arbitrary mac address of the BLE device being emulated.

You can set this attribute using a 6-byte int or bytearray. If this is set to None, then a random 6-byte address is generated.

property FakeBLE.name : bytes | None

The broadcasted BLE name of the nRF24L01.

This is not required. In fact, setting this attribute will subtract from the available payload length (in bytes). Set this attribute to None to disable advertising the device name.

Valid inputs are str, bytes, bytearray, or None. A str will be converted to a bytes object automatically.

Note

This information occupies (in the TX FIFO) an extra 2 bytes plus the length of the name set by this attribute.

Changed in version 2.2.0: This attribute can also be set with a str, but it must be UTF-8 compatible.

property FakeBLE.show_pa_level : bool

If this attribute is True, the payload will automatically include the nRF24L01’s pa_level in the advertisement.

The default value of False will exclude this optional information.

Note

This information occupies (in the TX FIFO) an extra 3 bytes, and is really only useful for some applications to calculate proximity to the nRF24L01 transceiver.

property FakeBLE.channel : int

This int attribute specifies the nRF24L01’s frequency.

The only allowed channels are those contained in the BLE_FREQ tuple.

Changed in version 2.1.0: Invalid input ignored

Prevoiusly, any invalid input value (that is not found in BLE_FREQ) had raised a ValueError exception. This behavior changed to ignoring invalid input values, and the exception is no longer raised.

FakeBLE.hop_channel()[source]

Trigger an automatic change of BLE compliant channels.

FakeBLE.whiten(data: bytes | bytearray) bytearray[source]

Whitening the BLE packet data ensures there’s no long repetition of bits.

This is done according to BLE specifications.

Parameters:
data : bytearray,bytes

The packet to whiten.

Returns:

A bytearray of the data with the whitening algorithm applied.

Note

advertise() and available() uses this function internally to prevent improper usage.

Warning

This function uses the currently set BLE channel as a base case for the whitening coefficient.

Do not call hop_channel() before calling available() because this function needs to know the correct BLE channel to properly de-whiten received payloads.

FakeBLE.len_available(hypothetical: bytes | bytearray = b'') int[source]

This function will calculates how much length (in bytes) is available in the next payload.

This is determined from the current state of name and show_pa_level attributes.

Parameters:
hypothetical : bytearray,bytes

Pass a potential chunk() of data to this parameter to calculate the resulting left over length in bytes. This parameter is optional.

Returns:

An int representing the length of available bytes for a single payload.

Changed in version 2.0.0: The name of this function changed from “available” to “len_available”.

This was done to avoid confusion with circuitpython_nrf24l01.rf24.RF24.available(). This change also allows providing the underlying RF24 class’ available() method in the FakeBLE API.

FakeBLE.advertise(buf: bytes | bytearray = b'', data_type: int = 255)[source]

This blocking function is used to broadcast a payload.

Returns:

Nothing as every transmission will register as a success under the required settings for BLE beacons.

Parameters:
buf: bytes | bytearray = b''

The payload to transmit. This bytearray must have a length greater than 0 and less than 22 bytes Otherwise a ValueError exception is thrown whose prompt will tell you the maximum length allowed under the current configuration. This can also be a list or tuple of payloads (bytearray); in which case, all items in the list/tuple are processed are packed into 1 payload for a single transmissions. See example code below about passing a list or tuple to this parameter.

data_type: int = 255

This is used to describe the buffer data passed to the buf parameter. 0x16 describes all service data. The default value 0xFF describes manufacturer information. This parameter is ignored when a tuple or list is passed to the buf parameter. Any other values are not applicable to BLE advertisements.

Important

If the name and/or TX power level of the emulated BLE device is also to be broadcast, then the name and/or show_pa_level attribute(s) should be set prior to calling advertise().

To pass multiple data values to the buf parameter see the following code as an example:

# let UUIDs be the 16-bit identifier that corresponds to the
# BLE service type. The following values are not compatible with
# BLE advertisements.
UUID_1 = 0x1805
UUID_2 = 0x1806
service1 = ServiceData(UUID_1)
service2 = ServiceData(UUID_2)
service1.data = b"some value 1"
service2.data = b"some value 2"

# make a tuple of the buffers
buffers = (
    chunk(service1.buffer),
    chunk(service2.buffer)
)

# let `ble` be the instantiated object of the FakeBLE class
ble.advertise(buffers)
ble.hop_channel()
FakeBLE.available() bool[source]

A bool describing if there is a payload in the rx_queue.

This method will take the first available data from the radio’s RX FIFO and validate the payload using the 24bit CRC checksum at the end of the payload. If the payload is indeed a valid BLE transmission that fit within the 32 bytes that the nRF24L01 can capture, then this method will decipher the data within the payload and enqueue the resulting QueueElement in the rx_queue.

Tip

Use read() to fetch the decoded data.

Returns:
  • True if payload was received and validated

  • False if no payload was received or the received payload could not be deciphered.

Changed in version 2.1.0: This was an added override to validate & decipher received BLE data.

FakeBLE.rx_queue : list[QueueElement]

The internal queue of received BLE payloads’ data.

Each Element in this queue is a QueueElement object whose members are set according to the its internal decoding algorithm. The read() function will remove & return the first element in this queue.

Hint

This attribute is exposed for debugging purposes, but it can also be used by applications.

New in version 2.1.0.

FakeBLE.rx_cache : bytearray

The internal cache used when validating received BLE payloads.

This attribute is only used by available() to cache the data from the top level of the radio’s RX FIFO then validate & decode it.

Hint

This attribute is exposed for debugging purposes.

New in version 2.1.0.

FakeBLE.read() QueueElement | None[source]

Get the First Out element from the queue.

Returns:

Changed in version 2.1.0: This was an added override to fetch deciphered BLE data from the rx_queue.

FakeBLE.interrupt_config(data_recv: bool = True, data_sent: bool = True, data_fail: bool = True)

Sets the configuration of the nRF24L01’s IRQ pin. (write-only)

Warning

The irq_df attribute is not implemented for BLE operations.

Unavailable RF24 functionality

The following RF24 functionality is not available in FakeBLE objects:

Abstract Parent

class circuitpython_nrf24l01.fake_ble.ServiceData(uuid: int)[source]

An abstract helper class to package specific service data using Bluetooth SIG defined 16-bit UUID flags to describe the data type.

Parameters:
uuid: int

The 16-bit UUID “GATT Service assigned number” defined by the Bluetooth SIG to describe the service data. This parameter is required.

property uuid : bytes

This returns the 16-bit Service UUID as a bytearray in little endian. (read-only)

property data : int | float | str | bytes | bytearray

This attribute is a bytearray or bytes object.

property buffer : bytes

Get the representation of the instantiated object as an immutable bytes object (read-only).

__len__() int[source]

For convenience, this class is compatible with python’s builtin len() method. In this context, this will return the length of the object (in bytes) as it would appear in the advertisement payload.

__repr__() str[source]

For convenience, this class is compatible with python’s builtin repr() method. In this context, it will return a string of data with applicable suffixed units.

Service data UUID numbers

These are the 16-bit UUID numbers used by the Derivative Children of the ServiceData class

circuitpython_nrf24l01.fake_ble.TEMPERATURE_UUID = 0x1809

The Temperature Service UUID number

circuitpython_nrf24l01.fake_ble.BATTERY_UUID = 0x180F

The Battery Service UUID number

circuitpython_nrf24l01.fake_ble.EDDYSTONE_UUID = 0xFEAA

The Eddystone Service UUID number

Derivative Children

class circuitpython_nrf24l01.fake_ble.TemperatureServiceData[source]

Bases: ServiceData

This derivative of the ServiceData class can be used to represent temperature data values as a float value.

See Also

Bluetooth Health Thermometer Measurement format as defined in the GATT Specifications Supplement.

property data : float

This attribute is a float value.

class circuitpython_nrf24l01.fake_ble.BatteryServiceData[source]

Bases: ServiceData

This derivative of the ServiceData class can be used to represent battery charge percentage as a 1-byte value.

See Also

The Bluetooth Battery Level format as defined in the GATT Specifications Supplement.

property data : int

The attribute is a 1-byte unsigned int value.

class circuitpython_nrf24l01.fake_ble.UrlServiceData[source]

Bases: ServiceData

This derivative of the ServiceData class can be used to represent URL data as a bytes value.

See Also

Google’s Eddystone-URL specifications.

property pa_level_at_1_meter : int

The TX power level (in dBm) at 1 meter from the nRF24L01. This defaults to -25 (due to testing when broadcasting with 0 dBm) and must be a 1-byte signed int.

property data : str

This attribute is a str of URL data.