OK as suggested/requested I’ve included my code so far below. Please note it is mainly just test/experiment code allowing me to try to understand how it all works. Setup involves 2 ESP32s, a regular one and a C3. The regular one runs with both WiFi and BLE on and talks to HA through ESPHome and the C3 just has BLE.
In the example, the server has 1 Service and 1 Characteristic - I’ve not bothered with Descriptors. It does 2 things, 1) starts Advertising and 2) waits for a command from the Client using on_write.
When the Client connects, it uses the on_connect to send a simple “ESP32C3 Hello” back to the Client using notify. Note it waits 4s for the connection to stabilise - I’m not too sure about this, but it is needed and anything less than 4s (ish) doesn’t work.
Note as far as I understand the Server starts advertising when successfully configured and no client is connected and stops when a client is connected, you cannot control this in code.
The Client is setup (see later) to use a HA switch to send a command “on” or “off” to the Server and the Server will see this using the on_write. It then simply switches its onboard LED on or off and sends back (using notify) an acknowledge command.
The Client is waiting for Advertisements using BLE Tracker and when it sees one it connects using the on_ble_advertise (obviously you would want to do more to check things in here, but it’s just for testing). Apparently, MAC addresses need to be static so I can’t find a way to look for any Server and although I can get its MAC address I don’t see a way of then using it in communications. Note also that it resets a timer, explained later.
It then uses the text sensor on_notify to get the data and at the moment just send it to the serial interface. It then disconnects. Note it resets the timer and also starts the scanning. It also starts scanning again as it was stopped when connected in the on_ble_advertise - note I found I had to stop scanning or it wouldn’t connect.
I had a problem with the approach hanging if the Server was reset because the Client sometimes got caught thinking the Server was connected and never started scanning again, hence the interval and the timer. This just checks every so often to see if we think we are connected but haven’t had any data for a while. Of course, this would need to be properly setup for a particular situation, but just here for testing.
The Client also has a switch which is use to send “on” "off to the Server to turn on or off the Server LED,
Code below and I hope it is useful to someone. Please no comments about my coding, al lof this is just to learn and I’m sure I’ve got things wrong.
Server Code
substitutions:
serviceUUID: '2a24b789-7aab-4535-af3e-ee76a35cc42d'
characteristicID_float: 'cad48e28-7fbe-41cf-bae9-d77a6c233423'
characteristicID_uint16: 'ea6b6666-1171-4c8a-bc42-bc97bfcda1b0'
characteristicID_string: '43267d33-f52f-4364-854b-a5cf614e3552'
esphome:
name: sm-ble-1
friendly_name: SM BLE 1
on_boot:
priority: -100
then:
- repeat:
count: 5
then:
- output.turn_on: led
- delay: 100ms
- output.turn_off: led
- delay: 100ms
esp32:
board: esp32-c3-devkitm-1
framework:
type: arduino
logger:
level: INFO
output:
- platform: gpio
pin: GPIO8
id: led
inverted: true
esp32_ble_server:
id: ble_server
manufacturer: "Dave"
manufacturer_data: [0xFF, 0xFF, 0xF1, 0x00]
services:
uuid: ${serviceUUID}
advertise: True
characteristics:
- id: tc3
uuid: ${characteristicID_string}
notify: True
read: True
write: True
value:
data: "Waiting..."
type: string
on_write:
then:
- lambda: |-
// x is a std::vector<uint8_t>, convert it to a std::sring and the for the ESP32 use .c_str() to get it in the right format
std::string cmd(x.begin(), x.end());
ESP_LOGI("ESP32C3", "Command received: %s from client %d", cmd.c_str(), id);
auto send_ack = [&]() {
std::string ack = "ack";
id(tc3).set_value(ack);
id(tc3).notify();
ESP_LOGI("ESP32C3", "Telling client %d we've accepted the command", id);
};
if (cmd == "on")
{
ESP_LOGI("ESP32C3", "Turning ON");
id(led).turn_on(); // Turns ON the LED (pulls GPIO8 LOW due to inverted: true)
send_ack;
}
else if (cmd == "off")
{
ESP_LOGI("ESP32C3", "Turning OFF");
id(led).turn_off(); // Turns ON the LED (pulls GPIO8 LOW due to inverted: true)
send_ack;
}
on_connect:
then:
- lambda: |-
ESP_LOGI("ESP32C3", "Client connected, waiting before notify...");
- delay: 4s
- lambda: |-
ESP_LOGI("ESP32C3", "Client connected, sending notify");
std::string string_value = "ESP32C3 Hello";
id(tc3).set_value(string_value);
id(tc3).notify();
on_disconnect:
- lambda: |-
ESP_LOGI("ESP32C3", "Client disconnected");
Client code
substitutions:
serviceUUID: '2a24b789-7aab-4535-af3e-ee76a35cc42d'
characteristicID_float: 'cad48e28-7fbe-41cf-bae9-d77a6c233423'
characteristicID_uint16: 'ea6b6666-1171-4c8a-bc42-bc97bfcda1b0'
characteristicID_string: '43267d33-f52f-4364-854b-a5cf614e3552'
esphome:
name: bleclient
friendly_name: BLEClient
esp32:
board: esp32dev
framework:
type: arduino
logger:
level: INFO
api:
encryption:
key: "w3xsT7cJ7WjWuOy5FnfpRPcXGPvdXbwoE3sJLk5Dpvk="
ota:
- platform: esphome
password: "24ea9cfd4f62a00fdedd53397c962c62"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "Bletest Fallback Hotspot"
password: "pJjhkhCKXNf9"
esp32_ble_tracker:
scan_parameters:
interval: 320ms
window: 30ms
active: true
on_ble_advertise:
- mac_address: 98:88:E0:CB:11:E2
then:
- esp32_ble_tracker.stop_scan: {}
- lambda: |-
ESP_LOGI("Client", "MAC: %s, RSSI: %d", x.address_str().c_str(), x.get_rssi());
for (auto uuid : x.get_service_uuids()) {
ESP_LOGI("Client", "Advertised UUID: %s", uuid.to_string().c_str());
}
id(last_data_time) = millis();
id(myESP32_C3_Super_Mini).connect();
ble_client:
- mac_address: 98:88:E0:CB:11:E2
id: myESP32_C3_Super_Mini
auto_connect: false
switch:
- platform: template
name: "Send on/off to ESP32-C3-Super-Mini"
id: esp32_c3_super_mini_switch
optimistic: true
turn_on_action:
- ble_client.ble_write:
id: myESP32_C3_Super_Mini
service_uuid: ${serviceUUID}
characteristic_uuid: ${characteristicID_string}
value: !lambda |-
std::string my_string = "on";
ESP_LOGI("Client", "Sending: %s", my_string.c_str());
return std::vector<uint8_t>(my_string.begin(), my_string.end());
turn_off_action:
- ble_client.ble_write:
id: myESP32_C3_Super_Mini
service_uuid: ${serviceUUID}
characteristic_uuid: ${characteristicID_string}
value: !lambda |-
std::string my_string = "off";
ESP_LOGI("Client", "Sending: %s", my_string.c_str());
return std::vector<uint8_t>(my_string.begin(), my_string.end());
text_sensor:
- platform: ble_client
ble_client_id: myESP32_C3_Super_Mini
name: "myESP32 C3 Super Mini Command Acknowledge"
service_uuid: ${serviceUUID}
characteristic_uuid: ${characteristicID_string}
id: myESP32_C3_Super_Mini_command_acknowleledge
notify: true
update_interval: never
on_notify:
then:
- lambda: |-
id(last_data_time) = millis();
std::string output = "Notify received, " + std::to_string(x.size()) + " bytes:";
for (size_t i = 0; i < x.size(); i++) {
char buf[6];
snprintf(buf, sizeof(buf), " 0x%02X", (uint8_t)x[i]);
output += buf;
}
ESP_LOGI("Client", "%s", output.c_str());
std::string s(x.begin(), x.end());
ESP_LOGI("Client", "String: %s", s.c_str());
id(myESP32_C3_Super_Mini).disconnect();
- esp32_ble_tracker.start_scan: {}
interval:
- interval: 15s # Check every 15 seconds
then:
- if:
condition:
lambda: |-
// If we think we're connected but haven't received data recently
return id(myESP32_C3_Super_Mini).connected() &&
id(last_data_time) > 0 &&
(millis() - id(last_data_time)) > 20000; // 20 seconds without data
then:
lambda: |-
ESP_LOGI("Client","Stale connection detected, forcing disconnect");
id(myESP32_C3_Super_Mini).disconnect();
#- logger.log: "Stale connection detected, forcing disconnect"
#- ble_client.disconnect: myESP32_C3_Super_Mini
- if:
condition:
lambda: 'return !id(myESP32_C3_Super_Mini).connected();'
then:
- lambda: |-
ESP_LOGI("Client","Not connected, start scan");
- esp32_ble_tracker.start_scan: {}
globals:
- id: last_data_time
type: unsigned long
initial_value: '0'