ESP32 BLE Example


Introduction

Call To Action

This tutorial is part of a bigger course called "Intelligent Product Design", which I am building out at https://backpack.danaukes.com/course/intelligent_product_design/. Please pardon the dust as I build it out.

If you just want the tutorial, you're in the right spot. But, if you need a little more, such as live lectures, a discussion board, or a community of other students, consider enrolling once the course opens. For now, please feel free to explore https://backpack.danaukes.com.

Prep

Create new project folders

mkdir -p project01-ble/esp/lib project01-ble/pc

download micropython lib and extract to the lib directory:

curl -L -o micropython-lib.zip https://github.com/micropython/micropython-lib/archive/refs/heads/master.zip
unzip micropython-lib.zip  "micropython-lib-master/micropython/bluetooth/aioble/aioble/*" -d "project01-ble/esp/lib/aioble"
rm micropython-lib.zip
invoke-webrequest "https://github.com/micropython/micropython-lib/archive/refs/heads/master.zip" -outfile micropython-lib.zip
expand-archive micropython-lib.zip
mv micropython-lib/micropython-lib-master/micropython/bluetooth/aioble/aioble project01-ble/lib
rm -r  micropython-lib micropython-lib.zip

Micropython Code

create project01-ble/esp/main.py and paste in the following:

from micropython import const

import asyncio
import aioble
import bluetooth
import ubinascii

UUID_SERVICE = bluetooth.UUID(0x1234)
UUID_CHARACTERISTIC = bluetooth.UUID(0x2345)
ADVERTISING_INTERVAL = 250000
NAME = 'ESP32'

service = aioble.Service(UUID_SERVICE)
characteristic = aioble.Characteristic(service, UUID_CHARACTERISTIC, read=True, notify=True)
aioble.register_services(service)

ble = bluetooth.BLE()
dummy, mac = ble.config('mac')
print("MAC:",':'.join(['{:02x}'.format(x) for x in mac]))

async def write_characteristic():
    while True:
        characteristic.write(b'hello', send_update=True)
        await asyncio.sleep_ms(1000)

async def advertise():
    while True:
        async with await aioble.advertise(ADVERTISING_INTERVAL,name=NAME,services=[UUID_SERVICE],) as connection:
            print("Connection from", connection.device)
            await connection.disconnected(timeout_ms=None)

async def main():
    write_task = asyncio.create_task(write_characteristic())
    advertise_task = asyncio.create_task(advertise())
    await asyncio.gather(write_task, advertise_task)


asyncio.run(main())

create a new pymakr project in the esp folder open the terminal, make sure the ESP32 is stopped (ctrl+c) use the pymakr interface to upload all project files to the ESP32 run the esp32 by either pressing the "en" button on the device or by typing ctrl+d to soft boot it from the terminal note the MAC address printed in the terminal once the code starts

PC Code

create project01-ble/pc/main.py and paste in the following making sure to replace the "" with the mac address from the ESP32 above

import asyncio
from bleak import BleakClient, uuids
import bleak

connected = False

MAC = "<ENTER MAC HERE>"
UUID_SERVICE = uuids.normalize_uuid_16(0x1234)
UUID_CHARACTERISTIC = uuids.normalize_uuid_16(0x2345)

def callback(sender, data):
    print(f"Callback: {sender}: {data.decode()}")

def scan_callback(device, advertising_data):
    print("device: ", device, ", advertising_data: ", advertising_data)

stop_event = asyncio.Event()

async def scanner_loop():
    async with bleak.BleakScanner(scan_callback,[UUID_SERVICE],scanning_mode="active",) as scanner:
        await stop_event.wait()
    while True:
        await asyncio.sleep(2)
        print("scanner sleeping")

async def connection_main_loop():
    global connected

    while True:
        client = None

        try:

            client = BleakClient(MAC)
            await client.connect()
            connected = True
            print("Connected")
            services = client.services
            print("Services: ", services)
            await client.start_notify(UUID_CHARACTERISTIC, callback)

            while True:
                await asyncio.sleep(1)
                print("sleeping")
                if not client.is_connected:
                    break

        except asyncio.CancelledError:
            raise
        except Exception as e:
            print(f"Connection failed", e)
        finally:
            connected = False
            if client:
                try:
                    if client.is_connected:
                        await asyncio.wait_for(client.disconnect(), timeout=2.0)
                except:
                    pass

            print("Reconnecting in 3 Seconds...")
            await asyncio.sleep(3)


async def general_loop():
    await asyncio.sleep(10)
    stop_event.set()

async def main():
    try:
        scan_loop = asyncio.create_task(scanner_loop())
        gen_loop = asyncio.create_task(general_loop())
        connection_loop = asyncio.create_task(connection_main_loop())
        await asyncio.gather(scan_loop, gen_loop, connection_loop)
    except asyncio.CancelledError as e:
        print(e)
    except KeyboardInterrupt:
        print(e)
    except Exception as e:
        print(e)
    finally:
        connected = False


if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        pass

External Resources

About

I am an engineer and educator, having spent ten years as a professor. My goal is to help you build your knowledge of design and technology, get your hardware working, and propel your startup or small business. Get in touch!

Read More