Making our own ESP32S3 BLE receive JPEG image Firmware

Since long I’ve been interested in using another means to receive binary data that are more low-consumption oriented than WiFi.
And there is a protocol for that, which should be the standard, when sending and receiving BLE files. It’s called L2CAP: Logical Link Control and Adaptation Protocol:

L2CAP permits higher-level protocols (such as GATT and SM) and applications to transmit and receive upper layer data packets

Texas Instruments BLE stack

What is the L2CAP status in ESP32 framework?
There is code and documentation to implement L2CAP in Classic Bluetooth:
This examples, Server and Client, can be built and flashed in ESP32. It allows to send and receive data using L2CAP. But you cannot just connect to the ESP32 and send a file.
Need to research more, but it seems something more needs to be implemented to act as a “Receive file” BT- server.
Note that this examples are not buildable in ESP32S3. Newer models of ESP32 like S3 have BLE on board, but not Bluetooth-classic.
I asked Espressif both per Twitter and also per email to see if there is any timeline to add L2CAP support in BLE. To the date of writing this line I still didn’t got any reply.

Will L2CAP be supported also in BLE, an hence, in newer MCU’s like ESP32S3 ?

UPDATE: When I wrote this article I was being focused in only one of the BLE stack: Bluedroid. But I researched more and discovered there is also NimBLE which has an existing L2CAP example that I still didn’t tried. Maybe is worth some tests and also NimBLE seems to be the recommended way if you just need BLE (Less code, less RAM usage)

So how do developers send data or images using BLE using Espressif MCU’s ?

Basically everyone implements their own protocol and App to send their data.
Since I didn’t want to be less of a fool than the others, I decided to implement my own example, that I will detail here below. If there is any interest, then I will add it some repository, so someone can benefit from it.
My example grown based on the GATT server. So it’s important to read and understand correctly the GATT server walk-through.

To start with first we kickstart a BLE service_id and publish it’s characteristics. The char_id’s are the “abilities” this services is offering. Like for example Receive file, Notifications etc.
So we start by publishing a service and one characteristic on our ESP32BLE Server instance.
In the Client side, using mainly Javascript and Chrome, we will connect to this BLE Service and grab it’s charasteristic, to be able to WRITE to it.
Note that both parts of this project are fully open-source

And then we need to add some additional hooks, since in my case I would like to know how long is the file we are going to receive (The old know content-length HTTP header, that we don’t have in BLE, so we will invent it)
And I also would like to receive an “end of file” signal or EOF, that will come after the byte-stream ends, and will be also the Firmware signal to say: Hey the JPG just finished to arrive, just decompress the receive buffer, and send it to the display.

This is what I though to be appropriate to implement those

  • 0x01 -> uint32 content length so the server will receive a 5 bytes message
    0x01 byte1 byte2 byte3 byte4 (MSB on the left) which can be decoded in the Firmware like: uint32_t received_length = param->write.value[1] + (param->write.value[2] << 8) +(param->write.value[3] << 16) + (param->write.value[4] << 24);
  • Byte stream. This is not prepended by any special command. Is just a continuous byte streams in chunks of about 448 bytes from the beginning till the end of our file
  • 0x09 end of file signal. Only 1 byte. Just decompress the JPEG using JPEGDEC from Larry bank, at the same time this will be our validation that it’s a valid JPEG.
    Render it to the epaper display.
First idea on how to implement this
BLE receive at work

As a resume this is doable. Is not a perfect way to receive a binary. It’s just not great to use Chrome since you force your users to use this browser which also needs a special “Bluetooth permission”. Probably would be better suited to make an Android app that is far away from the scope of this experiment.
And it’s just a very RAW sending that does not implement any way to recover lost packages. Walk away with your receiving client and your JPEG will never arrive in one piece.

Sending this using Chrome browser

The way we are currently sending this, is to open and read the JPG with any server backend language, we use PHP in

$jpg = file_get_contents($jpgUrl);

// Convert that bytes to HEXA
$hexStr = bin2hex($jpg);
// str_split — Convert a string to an array
foreach (str_split($hexStr,2) as $byte) {
            if ($count%16 === 0) {
          // Add newline every 16 bytes so is easy to compare
                $image_array[] = $byte."\n";
            } else {
                $image_array[] = $byte;
// Output image_array in your textarea

// Like this JS reads and converts to byte again
// A bit awkward but is easy to compare if the same bytes arrive
// to the ESP32 end. One wrong byte and JPG won't decompress

Probably there is an easier and cleaner way to do it. My way to show this is just a developer proof-of-concept example, but of course you could do it nicer. Or just pack all this in an Android App, which will be much more friendlier to use, just needs to open an image from your phone and resize it to your display width / height so you avoid sending a huge JPEG.
Other than this notes and some failed sendings, since Chrome BLE is also not enabled by default and considered to be experimental beta, this is a valid example and it’s working 99% of the time to receive images. In my Linux I often had to restart bluetooth since I’m getting some GATT is busy error messages in Chrome, that I don’t know where they come from and how to get rid of them.

If you feel that this can be helpful for you in an independent repository just let me know and I will tidy it up a bit.

Create a website or blog at

%d bloggers like this: