How to create a custom BLE sensor?

Hello,
I am planning to install sensors where I dont have energy supply. A BLE sensor which could communicate with esp32_ble_tracker would be nice for this cases with a power socket is not available.
I am in love with Esphome, so I have almost all my sensor integrated with it. But I can´t found any component to do that.

I saw BLE beacon component, but is not possible to change the parameters to send.

Another approach would be to create a BLE payload with a BLE integrated component as xiaomi hum/temp sensor format and send it with custom values. But I dont find any documentation about to do that apart of https://www.electronics-lab.com/project/using-the-ble-functionality-of-the-esp32/.

So any advice, documentation or example project would be perfect.
Best regards

do you mean there is a component which can send sensor reading through BT ? because I dont find it

No I mean the ESPHome sensor integrations are specific to each individual device. The documents I linked to are how to get started developing your own.

1 Like

I was trying to create a custom BLE sensor but it is too complicated for me.
So I am trying to emulate a xiaomi lywsd02 sensor based in this library.


I think that ESP BLE caracterization could be something like this.

lywsd02 data:
UUID_SERVICE = 0x2902 or “00002902-0000-1000-8000-00805f9b34fb”
this descriptor has the following properties

<Descriptor xsi:noNamespaceSchemaLocation="http://schemas.bluetooth.org/Documents/descriptor.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" type="org.bluetooth.descriptor.gatt.client_characteristic_configuration" uuid="2902" name="Client Characteristic Configuration">
    <InformativeText>
        <Abstract>
        The Client Characteristic Configuration descriptor defines how the characteristic may be configured by a specific client.            
		</Abstract>
        <InformativeDisclaimer/>
        <Summary>
        This descriptor shall be persistent across connections for bonded devices.
        The Client Characteristic Configuration descriptor is unique for each client. A client may read and write this descriptor to determine and set the configuration for that client.
        Authentication and authorization may be required by the server to write this descriptor.
        The default value for the Client Characteristic Configuration descriptor is 0x00. Upon connection of non-binded clients, this descriptor is set to the default value.
		</Summary>
	</InformativeText>
    <Value>
        <Field name="Properties">
            <Requirement>Mandatory</Requirement>
            <Format>16bit</Format>
            <Minimum>0</Minimum>
            <Maximum>3</Maximum>
            <BitField>
                <Bit index="0" size="1">
                    <Enumerations>
                        <Enumeration key="0" value="Notifications disabled" />
                        <Enumeration key="1" value="Notifications enabled" />
					</Enumerations>
                </Bit>
                <Bit index="1" size="1">
                    <Enumerations>
                        <Enumeration key="0" value="Indications disabled" />
                        <Enumeration key="1" value="Indications enabled" />
					</Enumerations>
                </Bit>
                <ReservedForFutureUse index="2" size="1" />
                <ReservedForFutureUse index="3" size="1" />
                <ReservedForFutureUse index="4" size="1" />
                <ReservedForFutureUse index="5" size="1" />
                <ReservedForFutureUse index="6" size="1" />
                <ReservedForFutureUse index="7" size="1" />
                <ReservedForFutureUse index="8" size="1" />
                <ReservedForFutureUse index="9" size="1" />
                <ReservedForFutureUse index="10" size="1" />
                <ReservedForFutureUse index="11" size="1" />
                <ReservedForFutureUse index="12" size="1" />
                <ReservedForFutureUse index="13" size="1" />
                <ReservedForFutureUse index="14" size="1" />
                <ReservedForFutureUse index="15" size="1" />
            </BitField>
        </Field>
    </Value>
</Descriptor>

UUID that the sensor uses extracted from python library:

#define SERVICE_UUID        [UUID GENERATOR](https://www.uuidgenerator.net/)
#define UUID_DATA "EBE0CCC1-7A0A-4B0C-8A1A-6FF2997DA3A6"
#define UUID_UNITS = "EBE0CCBE-7A0A-4B0C-8A1A-6FF2997DA3A6"
#define UUID_HISTORY = "EBE0CCBC-7A0A-4B0C-8A1A-6FF2997DA3A6" 
#define UUID_TIME = "EBE0CCB7-7A0A-4B0C-8A1A-6FF2997DA3A6"
#define UUID_DATA = "EBE0CCC1-7A0A-4B0C-8A1A-6FF2997DA3A6"   
#define UUID_BATTERY = "EBE0CCC4-7A0A-4B0C-8A1A-6FF2997DA3A6"

ESP code would be something like this:

I added sniffed payloadthat maybe esphome could understand from here.

#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>


#define SERVICE_UUID       "01e45e2a-dad1-43d0-9e3a-4fb0db7a2efa"
#define UUID_DATA "EBE0CCC1-7A0A-4B0C-8A1A-6FF2997DA3A6"
#define UUID_UNITS  "EBE0CCBE-7A0A-4B0C-8A1A-6FF2997DA3A6"
#define UUID_HISTORY  "EBE0CCBC-7A0A-4B0C-8A1A-6FF2997DA3A6" 
#define UUID_TIME  "EBE0CCB7-7A0A-4B0C-8A1A-6FF2997DA3A6"
#define UUID_DATA  "EBE0CCC1-7A0A-4B0C-8A1A-6FF2997DA3A6"   
#define UUID_BATTERY  "EBE0CCC4-7A0A-4B0C-8A1A-6FF2997DA3A6"
bool _BLEClientConnected = false;

int16_t aValue1=0;
int16_t aValue2=0;

BLEDescriptor data(UUID_DATA);

BLEDescriptor des_data(BLEUUID((uint16_t)0x2902));
BLECharacteristic char_data(BLEUUID(UUID_DATA), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);

BLEDescriptor des_nits(BLEUUID((uint16_t)0x2902));
BLECharacteristic char_units(UUID_DATA, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);

BLEDescriptor des_history(BLEUUID((uint16_t)0x2902));
BLECharacteristic char_history(UUID_HISTORY, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);

BLEDescriptor des_time(BLEUUID((uint16_t)0x2902));
BLECharacteristic char_time(UUID_TIME, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);

BLEDescriptor des_attery(BLEUUID((uint16_t)0x2902));
BLECharacteristic char_battery(UUID_BATTERY, BLECharacteristic::PROPERTY_READ);



// creación de servicio con sus propiedades; propiedades disponibles:
   	// static const uint32_t PROPERTY_READ      = 1<<0;
	// static const uint32_t PROPERTY_WRITE     = 1<<1;
	// static const uint32_t PROPERTY_NOTIFY    = 1<<2;
	// static const uint32_t PROPERTY_BROADCAST = 1<<3;
	// static const uint32_t PROPERTY_INDICATE  = 1<<4;
	// static const uint32_t PROPERTY_WRITE_NR  = 1<<5;

class MyServerCallbacks : public BLEServerCallbacks {
	void onConnect(BLEServer* pServer) {
		_BLEClientConnected = true;
	};

	void onDisconnect(BLEServer* pServer) {
		_BLEClientConnected = false;
	}
};


void InitBLE() {
	BLEDevice::init("Lywsd02");

	// Create the BLE Server
	BLEServer *pServer = BLEDevice::createServer();
	pServer->setCallbacks(new MyServerCallbacks());

	// Create the BLE Service
	BLEService *pService = pServer->createService(BLEUUID((uint16_t)0x03));

	// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
	// Create a BLE Descriptor
	char_data.addDescriptor(new BLE2902());
	char_units.addDescriptor(new BLE2902());
	char_history.addDescriptor(new BLE2902());
	char_time.addDescriptor(new BLE2902());
	char_battery.addDescriptor(new BLE2902());

	pService->addCharacteristic(&char_data);
	pService->addCharacteristic(&char_units);
	pService->addCharacteristic(&char_history);
	pService->addCharacteristic(&char_time);
	pService->addCharacteristic(&char_battery);
	pService->start();

	// Start advertising
	pServer->getAdvertising()->start();
}

void setup() {
Serial.begin(115200);
InitBLE();
char_units.setValue("xff");
char_units.setValue("0x36");
}


void loop() {

    if (_BLEClientConnected) {
        //chamamos o método "read" do sensor para realizar a leitura da temperatura
        //read retornará 1 caso consiga realizar a leitura, ou 0 caso contrário
        aValue1 = map(analogRead(34), 0, 4095, 0, 100);
        if (aValue1 != aValue2)
        {
            Serial.println(aValue1);
			char_data.setValue("04 3E 2B 02 01 03 01 20 AF 16 2D C1 2E 1F 1E FF 06 00 01 09 20 02 BE C7 A9 2E B4 17 E3 07 73 B4 62 81 C9 1F 5B 17 73 F8 3A F3 03 F7 75 DF");
            aValue1 = aValue2;
        }
    }
    delay(1000);
}

This article help me a lot.

I am not sure which is the BLE data with the sensor lectures to test it.
Also if you see some incongruences in the code you can help me to get it work.
At the moment compiles and I am trying differents payload values.

Meanwhile I am also working in simple custom component to send a simple payload to ble_tracker and catch the value.

1 Like

Have you looked at Xiaomi passive BLE Monitor sensor platform? It scans BLE sensors without the need for any extra device in the middle, such as ESPHome.
I was thinking about making something similar that scans for standard BLE GATT characteristics (temperature, heart rate… etc), and puts them straight into Home Assistant. Is that the kind of thing that you’re interested in too?
Did you get any further with your sensor?

2 Likes