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 Python.


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_length 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_width 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 (get_arc()) and custom ACK payloads (ack_payloads) 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” & “on data ready” events will have an effect on the interrupt (IRQ) pin. The “on data fail” is never triggered because auto-ack feature is disabled. Keep this in mind when using mask_irq().

fake_ble module helpers

pyrf24.fake_ble.address_repr(buf, reverse: bool = True, delimit: str = '') str

Convert a buffer into a hexlified string.

This method is primarily used to display how the address is used by the radio.

>>> from pyrf24.fake_ble import address_repr
>>> address_repr(b"1Node")
buf : bytes,bytearray

The buffer of bytes to convert into a hexlified string.

reverse : bool

A bool to control the resulting endianess. True outputs the result as big endian. False outputs the result as little endian. This parameter defaults to True since bytearray and bytes objects are stored in big endian but written in little endian.

delimit : str

A chr or str to use as a delimiter between bytes. Defaults to an empty string.


A string of hexadecimal characters in big endian form of the specified buf parameter.

pyrf24.fake_ble.swap_bits(original: int) int

This function reverses the bit order for a single byte.


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

original : int

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

pyrf24.fake_ble.reverse_bits(original: bytes | bytearray) bytearray

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


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

original : bytearray,bytes

The original buffer whose bits are to be reversed.

pyrf24.fake_ble.crc24_ble(data: bytes | bytearray, deg_poly: int = 1627, init_val: int = 5592405) bytearray

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.

data : bytearray,bytes

The buffer of data to be uncorrupted.

deg_poly : int

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

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


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

pyrf24.fake_ble.chunk(buf: bytes | bytearray, data_type: int = 22) bytearray

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.

buf : bytearray,bytes

The actual data contained in the block.

data_type : int

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.


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.

pyrf24.fake_ble.whitener(buf: bytes | bytearray, coefficient: int) bytearray

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.

buf : bytes,bytearray

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

coefficient : int

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

rx_channel =
coef = (
    index + 37 for index, chl in enumerate(BLE_FREQ) if chl == rx_channel


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

pyrf24.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







QueueElement class

class pyrf24.fake_ble.QueueElement(buffer: bytes | bytearray)

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

buffer : bytes,bytearray

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

data : list[bytearray | bytes | BatteryServiceData | TemperatureServiceData | UrlServiceData]

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.

mac : bytes

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

name : bytes | str | 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.


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

FakeBLE class

class pyrf24.fake_ble.FakeBLE(radio: 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 Restricted RF24 functionality for more details.

radio : RF24

The RF24 object that will be used to access the radio’s hardware.

See Also

See the RF24 class’ constructor documentation.

advertise(buf: bytes | bytearray = b'', data_type: int = 255)

This blocking function is used to broadcast a payload.


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

buf : bytearray

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

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.


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) = b"some value 1" = b"some value 2"

# make a tuple of the buffers
buffers = (

# let `ble` be the instantiated object of the FakeBLE class
available() bool

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.


Use read() to fetch the decoded data.

  • True if payload was received and validated

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

begin(ce_pin: int | None = None, csn_pin: int | None = None) bool

Initialize the radio using BLE specifications.

Internally, this function also calls begin(), so there’s no need to initialized the radio’s hardware before calling this function.

If dynamically specifying the radio’s GPIO pins, then 2 positional arguments are supported.

ce_pin : int

The pin number connected to the radio’s CE pin.

csn_pin : int

The pin number connected to the radio’s CSN pin.


Trigger an automatic change of BLE compliant channels.

len_available(hypothetical=b'') int

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.

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.


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

property mac

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 name

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.


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

read() QueueElement | None

Get the First Out element from the queue.


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 and decode it.


This attribute is exposed for debugging purposes.

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.


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

property 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.


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.

whiten(data) bytearray

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

This is done according to BLE specifications.

data : bytearray,bytes

The packet to whiten.


A bytearray of the data with the whitening algorithm applied.


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


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.

Restricted RF24 functionality

The following RF24 functionality should not be used when FakeBLE objects are instantiated with an RF24 object:

Abstract Parent

class pyrf24.fake_ble.ServiceData(uuid)

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

uuid : int

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

__len__() int

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

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.

property buffer : bytes

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

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

This attribute is a bytearray or bytes object.

property uuid : bytes

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

Service data UUID numbers

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

pyrf24.fake_ble.TEMPERATURE_UUID = 0x1809

The Temperature Service UUID number

pyrf24.fake_ble.BATTERY_UUID = 0x180F

The Battery Service UUID number

pyrf24.fake_ble.EDDYSTONE_UUID = 0xFEAA

The Eddystone Service UUID number

Derivative Children

class pyrf24.fake_ble.TemperatureServiceData

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 pyrf24.fake_ble.BatteryServiceData

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 pyrf24.fake_ble.UrlServiceData

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 data : str

This attribute is a str of URL data.

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.