8.3.3.1. BLE Cellbotics server

This is based on Neil Kolban’s example for IDF. It was ported to the Arduino ESP32 by Evandro Copercini, with updates by chegewara, then extended by Bryan A. Jones.

 

8.3.3.1.1. Includes

#include <string>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLECharacteristic.h>
 
 

8.3.3.1.2. Macros

8.3.3.1.2.1. UUIDs

See here for generating UUIDs.

The UUID for the CellBot service.

#define SERVICE_UUID ("6c533793-9bd6-47d6-8d3b-c10a704b6b97")

Characteristic for resetHardware.

#define RESET_HARDWARE_CHARACTERISTIC_UUID ("60cb180e-838d-4f65-aff4-20b609b453f3")

Characteristic for pinMode.

#define PIN_MODE_CHARACTERISTIC_UUID ("6ea6d9b6-7b7e-451c-ab45-221298e43562")

Characteristic for digitalWrite

#define DIGITAL_WRITE_CHARACTERISTIC_UUID ("d3423cf6-6da7-4dd8-a5ba-3c980c74bd6d")

Characteristic for digitalRead

#define DIGITAL_READ_CHARACTERISTIC_UUID ("c370bc79-11c1-4530-9f69-ab9d961aa497")

Characteristic for ledcSetup

#define LEDC_SETUP_CHARACTERISTIC_UUID ("6be57cea-3c46-4687-972b-03429d2acf9b")

Characteristic for ledcAttachPin

#define LEDC_ATTACH_PIN_CHARACTERISTIC_UUID ("2cd63861-078f-436f-9ed9-79e57ec8b638")

Characteristic for ledcDetachPin

#define LEDC_DETACH_PIN_CHARACTERISTIC_UUID ("b9b0cabe-25d8-4965-9259-7d3b6330e940")

Characteristic for ledcWrite

#define LEDC_WRITE_CHARACTERISTIC_UUID ("40698030-a343-448f-a9ea-54b39b03bf81")
 

Define this to return a textual description of each function executed to the Bluetooth client, and also print these descriptions to the serial port.

#define VERBOSE_RETURN
 

8.3.3.1.2.1.1. Hardware

The only blue LED on the ESP32.

#define LED1 (2)

The only pushbutton on the ESP32.

#define PB1 (0)
 
 

Configure a pushbutton and LED to enable BLE pairing.

void configPairing() {

Configure the on-board pushbutton as the pairing enable button.

    pinMode(LED1, OUTPUT);

Configure the on-board LED controlled by the ESP32 to flash quickly while pairing.

    pinMode(PB1, INPUT);
}
 

8.3.3.1.3. Variables

BLEServer *pServer = NULL;
 
 

8.3.3.1.4. Reset

Reset the device to a power-on like state in terms of the I/Os affected by the characteristics above. Also used as an e-stop when Bluetooth disconnects.

void reset_hardware() {

#ifdef VERBOSE_RETURN
    Serial.println("reset_hardware()");
#endif
 

TODO.

}
 
 

8.3.3.1.5. Characteristic callbacks

Provide return values and checks used by all the following characteristics.

class InvokeArduinoCallback: public BLECharacteristicCallbacks {
    public:

A buffer for messages; a C string.

    char buf[100];

A string holding read data to return to the client.

    std::string ret;

The value read from a characteristic.

    std::string value;

    InvokeArduinoCallback() : ret(100, 0) {
        ret.assign("Not yet invoked.");
    };
 

On a read, return buf.

    void onRead(BLECharacteristic* pCharacteristic) {
        pCharacteristic->setValue(ret);
    };
 

Get a write value and check its length.

    bool checkLength(size_t sz_expected_length, BLECharacteristic* pCharacteristic) {
        value = pCharacteristic->getValue();
        if (value.length() != sz_expected_length) {
            snprintf(buf, sizeof(buf), "Error: message length was %u, but expected %u.\n", value.length(), sz_expected_length);
            ret.assign(buf);
            return false;
        }
 

The default response is an empty string.

        ret.clear();
        return true;
    };
};
 
 

Any write to this characteristic invokes reset_hardware.

class ResetHardwareCallback: public InvokeArduinoCallback {
    void onWrite(BLECharacteristic* pCharacteristic) {
            reset_hardware();
#ifdef VERBOSE_RETURN
            ret.assign("reset_hardware()");
#endif
    }
};
 
 

A write to this characteristic invokes pinMode; results are reported by a read of this characteristic.

class PinModeCallback: public InvokeArduinoCallback {
    void onWrite(BLECharacteristic* pCharacteristic) {
        ///                 value[0]    value[1]
        /// void pinMode(uint8_t pin, uint8_t mode)
        uint8_t u8_pin;
        uint8_t u8_mode;

        if (checkLength(2, pCharacteristic)) {
            u8_pin = static_cast<uint8_t>(value[0]);
            u8_mode = static_cast<uint8_t>(value[1]);
            pinMode(u8_pin, u8_mode);
#ifdef VERBOSE_RETURN
            snprintf(buf, sizeof(buf), "pinMode(%u, %u)", u8_pin, u8_mode);
            Serial.println(buf);
            ret.assign(buf);
#endif
        }
    }
};
 
 

A write to this characteristic invokes digitalWrite; results are reported by a read of this characteristic.

class DigitalWriteCallback: public InvokeArduinoCallback {
    void onWrite(BLECharacteristic* pCharacteristic) {
        ///                     value[0]     value[1]
        /// void digitalWrite(uint8_t pin, uint8_t val);
        uint8_t u8_pin;
        uint8_t u8_val;

        if (checkLength(2, pCharacteristic)) {
            u8_pin = static_cast<uint8_t>(value[0]);
            u8_val = static_cast<uint8_t>(value[1]);
            digitalWrite(u8_pin, u8_val);
#ifdef VERBOSE_RETURN
            snprintf(buf, sizeof(buf), "digitalWrite(%u, %u)", u8_pin, u8_val);
            Serial.println(buf);
            ret.assign(buf);
#endif
        }
    };
};
 
 

A write to this characteristic invokes digitalWrite; results are reported by a read of this characteristic.

class DigitalReadCallback: public InvokeArduinoCallback {
    void onWrite(BLECharacteristic* pCharacteristic) {
        /// ret[0]               value[0]
        /// int    digitalRead(uint8_t pin);
        uint8_t u8_pin;

        if (checkLength(1, pCharacteristic)) {
            u8_pin = static_cast<uint8_t>(value[0]);

Although digitialRead returns an int, store it in a char, since we assume it’s a one-bit value.

            ret.resize(1);
            ret[0] = static_cast<char>(digitalRead(u8_pin));
#ifdef VERBOSE_RETURN
            snprintf(buf, sizeof(buf), "%u = digitalRead(%u)", ret[0], u8_pin);
            Serial.println(buf);
            ret.replace(1, 99, buf);
#endif
        }
    };
};
 
 

Same as above, for ledcSetup.

class LedcSetupCallback: public InvokeArduinoCallback {
    void onWrite(BLECharacteristic* pCharacteristic) {

This wraps:

        /// ret[0:7]            value[0]      value[9:1]         value[10]
        /// double ledcSetup(uint8_t channel, double freq, uint8_t resolution_bits)
        double d_ret;
        uint8_t u8_channel;
        double d_freq;
        uint8_t u8_resolution_bits;

        if (checkLength(11, pCharacteristic)) {

Extract function parameters.

            u8_channel = static_cast<uint8_t>(value[0]);
            memcpy(&d_freq, value.data() + 1, 8);
            u8_resolution_bits = static_cast<uint8_t>(value[10]);
 

Call the function.

            d_ret = ledcSetup(u8_channel, d_freq, u8_resolution_bits);
            ret.resize(8);
            ret.assign(reinterpret_cast<char*>(&d_ret), 8);
#ifdef VERBOSE_RETURN
            snprintf(buf, sizeof(buf), "%lf = ledcSetup(%u, %lf, %u)", d_ret, u8_channel, d_freq, u8_resolution_bits);
            Serial.println(buf);
            ret.replace(8, 92, buf);
#endif
        }
    };
};
 
 

Same as above, for ledcAttachPin.

class ledcAttachPinCallback: public InvokeArduinoCallback {
    void onWrite(BLECharacteristic* pCharacteristic) {
        ///                      value[0]     value[1]
        /// void ledcAttachPin(uint8_t pin, uint8_t channel)
        uint8_t u8_pin;
        uint8_t u8_channel;

        if (checkLength(2, pCharacteristic)) {
            u8_pin = static_cast<uint8_t>(value[0]);
            u8_channel = static_cast<uint8_t>(value[1]);
            ledcAttachPin(u8_pin, u8_channel);
#ifdef VERBOSE_RETURN
            snprintf(buf, sizeof(buf), "ledcAttachPin(%u, %u)", u8_pin, u8_channel);
            Serial.println(buf);
            ret.assign(buf);
#endif
        }
    };
};
 
 

Same as above, for ledcAttachPin.

class ledcDetachPinCallback: public InvokeArduinoCallback {
    void onWrite(BLECharacteristic* pCharacteristic) {
        ///                      value[0]
        /// void ledcDetachPin(uint8_t pin)
        uint8_t u8_pin;

        if (checkLength(2, pCharacteristic)) {
            u8_pin = static_cast<uint8_t>(value[0]);
            ledcDetachPin(u8_pin);
#ifdef VERBOSE_RETURN
            snprintf(buf, sizeof(buf), "ledcDetachPin(%u)", u8_pin);
            Serial.println(buf);
            ret.assign(buf);
#endif
        }
    };
};
 
 

Same as above, for ledcWrite.

class LedcWriteCallback: public InvokeArduinoCallback {
    void onWrite(BLECharacteristic* pCharacteristic) {

This wraps:

        ///                    value[0]      value[4:1]
        /// void ledcWrite(uint8_t channel, uint32_t duty)
        uint8_t u8_channel;
        uint32_t u32_duty;

        if (checkLength(5, pCharacteristic)) {

Extract function parameters.

            u8_channel = static_cast<uint8_t>(value[0]);

Since the data isn’t aligned, use memcpy.

            memcpy(&u32_duty, value.data() + 1, 4);
 

Call the function.

            ledcWrite(u8_channel, u32_duty);
#ifdef VERBOSE_RETURN
            snprintf(buf, sizeof(buf), "ledcWrite(%u, %u)", u8_channel, u32_duty);
            Serial.println(buf);
            ret.assign(buf);
#endif
        }
    };
};
 
 

On a server disconnect, reset and update paired status.

class CellBotServerCallback : public BLEServerCallbacks {
    virtual void onDisconnect(BLEServer* pServer) {
#ifdef VERBOSE_RETURN
        Serial.println("BLE disconnected.");
#endif
        reset_hardware();

Now that the board isn’t connected, allow pairing.

        configPairing();
    };
};
 
 

8.3.3.1.6. Functions

void setup() {
    Serial.begin(115200);
    Serial.println("Starting BLE work!");
 

Define the name visible when pairing this device.

    BLEDevice::init("CellBot");
    pServer = BLEDevice::createServer();
    pServer->setCallbacks(new CellBotServerCallback());
 

Define the primary service for this server.

    BLEService *pService = pServer->createService(SERVICE_UUID);
 

Define characteristics for this server

    //***************************************
    BLECharacteristic *pCharacteristic = pService->createCharacteristic(
        RESET_HARDWARE_CHARACTERISTIC_UUID,
        BLECharacteristic::PROPERTY_READ |
        BLECharacteristic::PROPERTY_WRITE
    );
    pCharacteristic->setCallbacks(new ResetHardwareCallback());

    pCharacteristic = pService->createCharacteristic(
        PIN_MODE_CHARACTERISTIC_UUID,
        BLECharacteristic::PROPERTY_READ |
        BLECharacteristic::PROPERTY_WRITE
    );
    pCharacteristic->setCallbacks(new PinModeCallback());

    pCharacteristic = pService->createCharacteristic(
        DIGITAL_WRITE_CHARACTERISTIC_UUID,
        BLECharacteristic::PROPERTY_READ |
        BLECharacteristic::PROPERTY_WRITE
    );
    pCharacteristic->setCallbacks(new DigitalWriteCallback());

    pCharacteristic = pService->createCharacteristic(
        DIGITAL_READ_CHARACTERISTIC_UUID,
        BLECharacteristic::PROPERTY_READ |
        BLECharacteristic::PROPERTY_WRITE
    );
    pCharacteristic->setCallbacks(new DigitalReadCallback());

    pCharacteristic = pService->createCharacteristic(
        LEDC_SETUP_CHARACTERISTIC_UUID,
        BLECharacteristic::PROPERTY_READ |
        BLECharacteristic::PROPERTY_WRITE
    );
    pCharacteristic->setCallbacks(new LedcSetupCallback());

    pCharacteristic = pService->createCharacteristic(
        LEDC_WRITE_CHARACTERISTIC_UUID,
        BLECharacteristic::PROPERTY_READ |
        BLECharacteristic::PROPERTY_WRITE
    );
    pCharacteristic->setCallbacks(new LedcWriteCallback());

    pCharacteristic = pService->createCharacteristic(
        LEDC_ATTACH_PIN_CHARACTERISTIC_UUID,
        BLECharacteristic::PROPERTY_READ |
        BLECharacteristic::PROPERTY_WRITE
    );
    pCharacteristic->setCallbacks(new ledcAttachPinCallback());

    pCharacteristic = pService->createCharacteristic(
        LEDC_DETACH_PIN_CHARACTERISTIC_UUID,
        BLECharacteristic::PROPERTY_READ |
        BLECharacteristic::PROPERTY_WRITE
    );
    pCharacteristic->setCallbacks(new ledcDetachPinCallback());
 

Complete Bluetooth config.

    pService->start();
    BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
    pAdvertising->addServiceUUID(SERVICE_UUID);
    pAdvertising->setScanResponse(true);

Functions that help with iPhone connections issue. Why is this done twice?

    pAdvertising->setMinPreferred(0x06);
    pAdvertising->setMinPreferred(0x12);
    Serial.println("Characteristic defined! Now you can read it in your phone!");

    configPairing();
}


void loop() {

Time in 0.1 s intervals since the pair pushbutton was pressed.

    static uint u_pairing_time_ds = 0;
 

Look for a paring pushbutton press only if we’re not connected.

    if (!pServer->getConnectedCount()) {
        if (!digitalRead(PB1)) {

Set the timer to 30 s.

            u_pairing_time_ds = 300;
            BLEDevice::getAdvertising()->start();
        }
    }
 

Blink while pairing.

    if (u_pairing_time_ds) {
        --u_pairing_time_ds;

Turn the LED off and pairing off for the last blink.

        if (!u_pairing_time_ds) {
            digitalWrite(LED1, 0);
            BLEDevice::getAdvertising()->stop();
        } else {
            digitalWrite(LED1, !digitalRead(LED1));
        }
    }

    delay(100);
}
You have attempted of activities on this page