ESP32 Bluetooth LE (BLE) reconnect client when server appears
Posted: Tue Dec 29, 2020 11:23 am
Hi here...
This is my first post on this board, so i'am a newbee
Story ::
I have created an ESP32 remote-controller which used to send BLE data to an RaspberryPi with enabled BLE. -Working okay within 1-2 meters range.
The Remote-controller is the server part in the chain.
Problem 1. ::
The RaspberryPi sits in my main stereo inside a chassis (picture shows my testrig, not my main stereo) and it keep's loosing connection when remote goes + 1-2meters away or so.
Solution for problem 1. ::
I have another ESP32 connected to my RaspberryPi. With this setup it's working much better and the range is around 10 meters now.
This ESP32 is a Client in my setup.
Problem is NOW ::
When Serverpart (remote-controller) is loosing connection or awakes after sleeping, the Client cannot connect before it's reset or rebooted or like.
I'am sure that it should be possible for the client to autoreconnect when loosing connection, but i can't figure out howto do that.
The server is working as expected and no reboot/reset is neccesary here.
Server (remote-controller) code ::
Client code :: (The one which cannot reconnect to the server by itself)
Really hope someone can help me further from here...
Rgds; Jesper.
This is my first post on this board, so i'am a newbee
Story ::
I have created an ESP32 remote-controller which used to send BLE data to an RaspberryPi with enabled BLE. -Working okay within 1-2 meters range.
The Remote-controller is the server part in the chain.
Problem 1. ::
The RaspberryPi sits in my main stereo inside a chassis (picture shows my testrig, not my main stereo) and it keep's loosing connection when remote goes + 1-2meters away or so.
Solution for problem 1. ::
I have another ESP32 connected to my RaspberryPi. With this setup it's working much better and the range is around 10 meters now.
This ESP32 is a Client in my setup.
Problem is NOW ::
When Serverpart (remote-controller) is loosing connection or awakes after sleeping, the Client cannot connect before it's reset or rebooted or like.
I'am sure that it should be possible for the client to autoreconnect when loosing connection, but i can't figure out howto do that.
The server is working as expected and no reboot/reset is neccesary here.
Server (remote-controller) code ::
Code: Select all
//
// SuperPlayer Bluetooth (BLE) Controller
// Build from different sources (www)
// Jesper Lykke [User :: Lykkedk @ diyaudio.com]
//
// Lib's needed :: (Installed from Arduino IDE)
//
// https://github.com/mickey9801/ButtonFever
// https://github.com/madhephaestus/ESP32Encoder/
//
// - Sleep stuff -
unsigned long startMillis;
unsigned long currentMillis;
// period ::
//1 Hour 3,600,000 ms
//2 Hours 7,200,000 ms
//3 Hours 10,800,000 ms
//4 Hours 14,400,000 ms
const unsigned long period = 3600000; //the value is a number of milliseconds
//const unsigned long period = 20000; //the value is a number of milliseconds
unsigned long BLE_startMillis;
unsigned long BLE_currentMillis;
const unsigned long BLE_period = 3000; //Adjust a bit to fit the wakeup/vs. when first BLE send is fired
// - Encoder stuff -
#include <ESP32Encoder.h>
#include <BfButton.h>
// - BLE stuff -
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
// - Battery level -
//#define VBATPIN A13
#define VBATPIN 35
float SaveBle = 0;
// - Sleep stuff -
#define BUTTON_PIN_BITMASK 0x200000000 // 2^33 in hex
RTC_DATA_ATTR int bootCount = 0;
// - Encoder -
const unsigned int btnPin = 25;
BfButton btn(BfButton::STANDALONE_DIGITAL, btnPin, false, HIGH); // I added a pull-down myself
ESP32Encoder encoder;
// Encoder values for Volume control
int32_t Volume = 0;
int32_t OLD_Volume = 0;
uint32_t BLE_encoder = 0;
uint32_t OLD_BLE_encoder = 0;
// - BLE stuff -
BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
};
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
}
};
// - Handle Sleep -
void print_wakeup_reason(){
esp_sleep_wakeup_cause_t wakeup_reason;
wakeup_reason = esp_sleep_get_wakeup_cause();
switch(wakeup_reason)
{
case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
}
}
// - Handle BLE -
void Ble() { // Start void BLE
// Only fire BLE stuff, after a short time when awake from deep sleep
// BLE_period is that time configured first in this code
BLE_currentMillis = millis();
if (BLE_currentMillis - BLE_startMillis >= BLE_period) { // Start BLE first fire
if (BLE_encoder!=OLD_BLE_encoder) {
Serial.print("BLE_encoder value : ");
Serial.println(BLE_encoder);
startMillis = millis(); // Reset initial gotosleep start time
if (deviceConnected) {
pCharacteristic->setValue(BLE_encoder);
pCharacteristic->notify();
delay(500); // Critical time... to short will send to many packet's, too long will make it seem slow
}
OLD_BLE_encoder = BLE_encoder;
}
// disconnecting
if (!deviceConnected && oldDeviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("start advertising");
Serial.println("DisConnecting");
oldDeviceConnected = deviceConnected;
}
// connecting
if (deviceConnected && !oldDeviceConnected) {
Serial.println("Connecting");
// do stuff here on connecting
oldDeviceConnected = deviceConnected;
}
} // End BLE first fire
} // End void BLE
// - Handle Encoder Button -
void pressHandler (BfButton *btn, BfButton::press_pattern_t pattern) {
Serial.print(btn->getID());
switch (pattern) {
case BfButton::SINGLE_PRESS: // Does not work very well!
Serial.println(" pressed.");
BLE_encoder = 75000; // Not used
break;
case BfButton::DOUBLE_PRESS: // Works fine
Serial.println(" double pressed."); // Mute/unmute
BLE_encoder = 50000;
break;
case BfButton::LONG_PRESS: // Works fine
Serial.println(" long pressed.");
BLE_encoder = 25000; // Play/pause/battery level
delay(50);
ReadPower(); // Read battery voltage & save the volume value
Ble(); // Send the value
delay(50);
LoadBle(); // Restore volume value
break;
}
}
// - Handle Encoder data -
void ReadEncoder () {
// Loop and read the count
Volume = encoder.getCount();
if (Volume!=OLD_Volume) {
OLD_Volume = Volume;
}
BLE_encoder = Volume;
}
void ReadPower () {
float measuredvbat = analogRead(VBATPIN);
measuredvbat = measuredvbat/4095;
measuredvbat = measuredvbat*2*3.3*1.1;
measuredvbat = measuredvbat*1000;
Serial.print("DC Voltage :: "); Serial.println(measuredvbat);
SaveBle = BLE_encoder;
BLE_encoder = measuredvbat;
}
void LoadBle () {
BLE_encoder = SaveBle;
}
// - SETUP -
void setup(){
Serial.begin(115200);
startMillis = millis(); // Initial gotosleep start time
BLE_startMillis = millis(); // First BLE fire start counter
//Increment boot number and print it every reboot - Sleep
++bootCount;
Serial.println("Boot number: " + String(bootCount));
esp_sleep_enable_ext0_wakeup(GPIO_NUM_25,0); // 1 = High, 0 = Low - Wakeup when encoder is clicked (Long pressed)
// - BLE stuff -
// Create the BLE Device
BLEDevice::init("SPR"); // SuperPlayer Remote (Server part of chain)
// - TX Power -
esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, ESP_PWR_LVL_P9); // ESP_PWR_LVL_N12 = 0 Corresponding to -12dbm
// ESP_PWR_LVL_N9 = 1 Corresponding to -9dbm
// ESP_PWR_LVL_N6 = 2 Corresponding to -6dbm
// ESP_PWR_LVL_N3 = 3 Corresponding to -3dbm
// ESP_PWR_LVL_N0 = 4 Corresponding to 0dbm
// ESP_PWR_LVL_P3 = 5 Corresponding to +3dbm <--- Default
// ESP_PWR_LVL_P6 = 6 Corresponding to +6dbm
// ESP_PWR_LVL_P9 = 7 Corresponding to +9dbm
// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_NOTIFY |
BLECharacteristic::PROPERTY_INDICATE
);
// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
// Create a BLE Descriptor
pCharacteristic->addDescriptor(new BLE2902());
// Start the service
pService->start();
// Start advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(false);
//pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
BLEDevice::startAdvertising();
Serial.println("Waiting a client connection to notify...");
// - Button stuff -
btn.onPress(pressHandler)
.onDoublePress(pressHandler) // default timeout
.onPressFor(pressHandler, 1000); // custom timeout for 1 second
// - Encoder stuff -
// Enable the weak pull down resistors
//ESP32Encoder::useInternalWeakPullResistors=DOWN;
// Enable the weak pull up resistors
ESP32Encoder::useInternalWeakPullResistors=UP;
//CLK (A pin) - to any microcontroler intput pin with interrupt -> in this example pin 32
//DT (B pin) - to any microcontroler intput pin with interrupt -> in this example pin 21
//SW (button pin) - to any microcontroler intput pin -> in this example pin 25
//VCC - to microcontroler VCC (then set ROTARY_ENCODER_VCC_PIN -1) or in this example pin 25
//GND - to microcontroler GND
encoder.attachHalfQuad(32, 21);
// set starting count value after attaching
encoder.setCount(100000);
//encoder.setCount(250);
}
// - Loop section -
void loop(){
ReadEncoder();
btn.read();
Ble();
currentMillis = millis(); // Grab the current time
if (currentMillis - startMillis >= period) { // Check if period before gotosleep is gone (period = time before we gotosleep)
Serial.println("Going to sleep now ZZzzz...");
esp_deep_sleep_start();
}
}
Code: Select all
#include <cstdlib>
#include <string>
#include "BLEDevice.h"
int32_t Volume = 100000; // Variable to store Volume from SuperPlayer Remote
// (100000 = Where encoder on SuperPlayer Remote starts)
int32_t OLD_Volume = 100010; // Variable to store Volume value to compare
static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b"); // The remote service of SuperPlayer Remote [Server part]
static BLEUUID charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8"); // The characteristic of SuperPlayer Remote [Server part]
static BLEAddress *pServerAddress;
static boolean doConnect = false;
static boolean connected = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
// this gets called when we receive a message
// Note that the server has to call notify to trigger this
// This function just prints the received message
static void notifyCallback(
BLERemoteCharacteristic* pBLERemoteCharacteristic,
uint8_t* pData,
size_t length,
bool isNotify)
{
Volume = (*(uint32_t *)pData); // BLE Volume read from SuperPlayer_Remote via Bluetooth LE (BLE)
}
bool connectToServer(BLEAddress pAddress)
{
Serial.print("Creating connection to ");
Serial.println(pAddress.toString().c_str());
BLEClient* pClient = BLEDevice::createClient();
Serial.println(" - Created client");
// Connect to the remove BLE Server.
pClient->connect(pAddress);
Serial.println(" - Connected to server");
// Obtain a reference to the service we are after in the remote BLE server.
BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
if (pRemoteService == nullptr) {
Serial.print("Failed to find our service UUID: ");
Serial.println(serviceUUID.toString().c_str());
return false;
}
Serial.println(" - Found our service");
// Obtain a reference to the characteristic in the service of the remote BLE server.
pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
if (pRemoteCharacteristic == nullptr) {
Serial.print("Failed to find our characteristic UUID: ");
Serial.println(charUUID.toString().c_str());
return false;
}
Serial.println(" - Found our characteristic");
// Bind the notifyCallback method to the characteristic
pRemoteCharacteristic->registerForNotify(notifyCallback);
}
/**
* Scan for BLE servers and find the first one that advertises the service we are looking for.
*/
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
/**
* Called for each advertising BLE server.
*/
void onResult(BLEAdvertisedDevice advertisedDevice) {
// We have found a device, let us now see if it contains the service we are looking for.
if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) {
Serial.println(advertisedDevice.toString().c_str());
Serial.print("Found our device! :: ");
advertisedDevice.getScan()->stop();
pServerAddress = new BLEAddress(advertisedDevice.getAddress());
doConnect = true;
} // Found our server
} // onResult
}; // MyAdvertisedDeviceCallbacks
void send_volume() {
if (Volume!=OLD_Volume) {
//Serial.print("Sending volume on serial ---> ");
Serial.println(Volume);
}
OLD_Volume = Volume;
}
void setup() {
Serial.begin(115200);
Serial.println("SETUP init ...Starting Arduino BLE Client application...");
BLEDevice::init("");
// Retrieve a Scanner and set the callback we want to use to be informed when we
// have detected a new device. Specify that we want active scanning and start the
// scan to run for 30 seconds.
BLEScan* pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true);
pBLEScan->start(5);
} // End of setup.
// This is the Arduino main loop function.
void loop() {
// If the flag "doConnect" is true then we have scanned for and found the desired
// BLE Server with which we wish to connect. Now we connect to it. Once we are
// connected we set the connected flag to be true.
if (doConnect == true) {
Serial.println("DEBUG 1");
if (connectToServer(*pServerAddress)) {
Serial.println("We are now connected to SuperPlayer Remote...");
connected = true;
}
else {
Serial.println("We have failed to connect to the server; there is nothing more we will do.");
}
doConnect = false;
}
send_volume();
delay(250);
}
Rgds; Jesper.