Skip to Content
This is the Beta version of our new Learning Paths approach. Please email feedback.

Bluetooth Low Energy (BLE)

bluetooth_low_energy.ino
/* Project: Smart Home Component: Arduino Ek R4 Built-in BLE Sketch Description: This sketch uses the built-in BLE capability to enable the board to act as a BLE Peripheral and exchange data over a BLE connection with a BLE Central Device which in this case will be a smart phone running the Ventor IoT App. Author: STEMVentor Educonsulting This code is copyrighted. Please do not reuse or share for any purpose other than for learning with subscribed STEMVentor programs. This code is for educational purposes only and is not for production use. */ // DATA FORMATS: // The mobile app will be the final consumer of sensor data and the source for control signals. // It will receive and send data either from an MQTT message broker topic or over Bluetooth (BLE). // // BLE: // BLE uses the concept of services and characteristics to exchange data between // the app (the central in BLE terms) and the board (the peripheral in BLE terms). // Each sensor or device has a UUID which repesents the characteristics // and they are grouped into a service. // BLE service and characteristic UUIDs must be generated in the app and added/updated // in the board_parameters.h file. // Data is sent as the characteristic value. The characteristic can be created of the // required data type. // This is a combination of the example code for Notify and Write. // Notify is to send data from the BLE board (this code) to the app. // Write is to receive data from the app to the BLE board (this code). // Video: https://www.youtube.com/watch?v=oCMOYS71NIU // Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp // Ported to Arduino ESP32 by Evandro Copercini // updated by chegewara // Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleWrite.cpp // Ported to Arduino ESP32 by Evandro Copercini // Create a BLE server that, once we receive a connection, will send periodic notifications. // The service advertises itself as: 4fafc201-1fb5-459e-8fcc-c5c9c331914b // And has a characteristic of: beb5483e-36e1-4688-b7f5-ea07361b26a8 // The design of creating the BLE server is: // 1. Create a BLE Server // 2. Create a BLE Service // 3. Create a BLE Characteristic on the Service // 4. Create a BLE Descriptor on the characteristic // 5. Start the service. // 6. Start advertising. // A connect handler associated with the server starts a background task that performs notification // every couple of seconds. // See the following for generating UUIDs: // https://www.uuidgenerator.net/ // LIBRARIES #include <ArduinoBLE.h> // Internal file with BLE Service and Characteristic UUIDs. // Use "" when including library files in same directory as against <> for library files in global directory. // PIN DEFINTIONS // GLOBAL CONSTANTS // GLOBAL VARIABLES // Service objects BLEService sensor_data_service(SENSOR_DATA_SERVICE_UUID); BLEService control_signal_service(CONTROL_SIGNAL_SERVICE_UUID); // Advertising parameters should have a global scope. Do NOT define them in 'setup' or in 'loop' const uint8_t manufactData[4] = {0x01, 0x02, 0x03, 0x04}; const uint8_t serviceData[3] = {0x00, 0x01, 0x02}; // INITIALIZE OBJECTS // Sensor Characteristic objects BLEIntCharacteristic temperature_characteristic(TEMPERATURE_CHARACTERISTIC_UUID, BLERead | BLEWrite | BLENotify); BLEIntCharacteristic humidity_characteristic(HUMIDITY_CHARACTERISTIC_UUID, BLERead | BLEWrite | BLENotify); BLEIntCharacteristic gas_characteristic(GAS_CHARACTERISTIC_UUID, BLERead | BLEWrite | BLENotify); BLEIntCharacteristic soil_moisture_characteristic(SOIL_MOISTURE_CHARACTERISTIC_UUID, BLERead | BLEWrite | BLENotify); BLEIntCharacteristic ambient_light_characteristic(AMBIENT_LIGHT_CHARACTERISTIC_UUID, BLERead | BLEWrite | BLENotify); BLEIntCharacteristic distance_characteristic(DISTANCE_CHARACTERISTIC_UUID, BLERead | BLEWrite | BLENotify); BLEIntCharacteristic motion_characteristic(MOTION_CHARACTERISTIC_UUID, BLERead | BLEWrite | BLENotify); // Control Characteristic objects // BLE has a write size limit of 32 bytes. So we have to send values and hard-code interpretation. BLEIntCharacteristic light_switch_characteristic(LIGHT_SWITCH_CHARACTERISTIC_UUID, BLERead | BLEWrite | BLENotify); BLEIntCharacteristic pump_switch_characteristic(PUMP_SWITCH_CHARACTERISTIC_UUID, BLERead | BLEWrite | BLENotify); BLEIntCharacteristic servo_movement_characteristic(SERVO_MOVEMENT_CHARACTERISTIC_UUID, BLERead | BLEWrite | BLENotify); // This is a common function that will process values from MQTT and BLE. // Due to the way BLE writes values we need to compare characteristics twice but // at least the actions are all in one place. void processControlSignal(String characteristic_uuid, uint8_t value){ if(characteristic_uuid == LIGHT_SWITCH_CHARACTERISTIC_UUID){ // Turn the light connected to relay 1 on or off. if(value == 0){ switchRelayOff(LIGHT_RELAY); }else{ // any non-zero value to turn on switchRelayOn(LIGHT_RELAY); } }else if(characteristic_uuid == PUMP_SWITCH_CHARACTERISTIC_UUID){ // Turn the pump connected to relay 2 on or off. if(value == 0){ switchRelayOff(PUMP_RELAY); }else{ // any non-zero value to turn on switchRelayOn(PUMP_RELAY); } }else if(characteristic_uuid == SERVO_MOVEMENT_CHARACTERISTIC_UUID){ // Move the servo by the degrees value received. moveServoByDegrees(SERVO_PIN, value); } } // CALLBACK FUNCTIONS // Callback for characteristic written event which will be triggered when a value is sent from the app. // The characteristic object is passed in the second parameter from which you can get the UUID. // The action will have to be decided based on the characteritic received. void readCharacteristicWritten(BLEDevice central, BLECharacteristic characteristic) { if(OPERATING_MODE == 'D'){ Serial.print("Characteristic: "); Serial.println(characteristic.uuid()); } // We will have a common function to process control signals that can be used for // signals received via BLE or MQTT since we are keeping a common format. if(characteristic.uuid() == LIGHT_SWITCH_CHARACTERISTIC_UUID){ if(OPERATING_MODE == 'D'){ Serial.print(light_switch_characteristic.value()); } processControlSignal(characteristic.uuid(), light_switch_characteristic.value()); }else if(characteristic.uuid() == PUMP_SWITCH_CHARACTERISTIC_UUID){ if(OPERATING_MODE == 'D'){ Serial.print(pump_switch_characteristic.value()); } processControlSignal(characteristic.uuid(), pump_switch_characteristic.value()); }else if(characteristic.uuid() == SERVO_MOVEMENT_CHARACTERISTIC_UUID){ if(OPERATING_MODE == 'D'){ Serial.print(servo_movement_characteristic.value()); } processControlSignal(characteristic.uuid(), servo_movement_characteristic.value()); } // if(characteristic.uuid() == LIGHT_SWITCH_CHARACTERISTIC_UUID){ // if(OPERATING_MODE == 'D'){ // Serial.print("Light Switch Value: "); // Serial.println(light_switch_characteristic.value()); // } // // Turn the light connected to relay 1 on or off. // if(light_switch_characteristic.value() == 0){ // switchRelayOff(LIGHT_RELAY); // }else{ // any non-zero value to turn on // switchRelayOn(LIGHT_RELAY); // } // }else if(characteristic.uuid() == PUMP_SWITCH_CHARACTERISTIC_UUID){ // if(OPERATING_MODE == 'D'){ // Serial.print("Pump Switch Value: "); // Serial.println(pump_switch_characteristic.value()); // } // // Turn the pump connected to relay 2 on or off. // if(pump_switch_characteristic.value() == 0){ // switchRelayOff(PUMP_RELAY); // }else{ // any non-zero value to turn on // switchRelayOn(PUMP_RELAY); // } // }else if(characteristic.uuid() == SERVO_MOVEMENT_CHARACTERISTIC_UUID){ // if(OPERATING_MODE == 'D'){ // Serial.print("Servo Movement Value: "); // Serial.println(servo_movement_characteristic.value()); // } // // Move the servo by the degrees value received. // moveServoByDegrees(SERVO_PIN, servo_movement_characteristic.value()); // } } // LOCAL FUNCTIONS // Setup as Peripheral void BLEPeripheralSetup() { if (!BLE.begin()) { if(OPERATING_MODE == 'D'){ Serial.println("Failed to initialize BLE!"); } is_ble_connected = false; displayNotConnectedOnLEDMatrix(); while (1); } // Connected, add services and characteristics. is_ble_connected = true; // Add Sensor characteristics to Sensor Data service. // One service only supports 7 characteristics. sensor_data_service.addCharacteristic(temperature_characteristic); sensor_data_service.addCharacteristic(humidity_characteristic); sensor_data_service.addCharacteristic(gas_characteristic); sensor_data_service.addCharacteristic(soil_moisture_characteristic); sensor_data_service.addCharacteristic(ambient_light_characteristic); sensor_data_service.addCharacteristic(distance_characteristic); sensor_data_service.addCharacteristic(motion_characteristic); // And add service to the BLE object. BLE.addService(sensor_data_service); // Assign event handler for Control Signal characteristics. light_switch_characteristic.setEventHandler(BLEWritten, readCharacteristicWritten); // Set initial characteristic value. light_switch_characteristic.setValue(0); pump_switch_characteristic.setEventHandler(BLEWritten, readCharacteristicWritten); // Set initial characteristic value. pump_switch_characteristic.setValue(0); servo_movement_characteristic.setEventHandler(BLEWritten, readCharacteristicWritten); // Set initial characteristic value. servo_movement_characteristic.setValue(0); // Add Controller characteristics to Control Signal service. control_signal_service.addCharacteristic(light_switch_characteristic); control_signal_service.addCharacteristic(pump_switch_characteristic); control_signal_service.addCharacteristic(servo_movement_characteristic); // And add service to the BLE object. BLE.addService(control_signal_service); // Build scan response data packet BLEAdvertisingData scanData; // Set parameters for scan response packet scanData.setLocalName("Arduino Ek R4"); // Copy set parameters in the actual scan response packet BLE.setScanResponseData(scanData); // THIS IS NOT WELL DOCUMENTED AND ALSO NOT REQUIRED FOR NON-PRODUCTION DEVICES // Build advertising data packet BLEAdvertisingData advData; // Set parameters for advertising packet advData.setManufacturerData(0x004C, manufactData, sizeof(manufactData)); advData.setAdvertisedService(sensor_data_service); advData.setAdvertisedServiceData(0xfff0, serviceData, sizeof(serviceData)); // Copy set parameters in the actual advertising packet BLE.setAdvertisingData(advData); BLE.advertise(); if(OPERATING_MODE == 'D'){ Serial.println("Advertising ..."); } // Set global status. is_ble_connected = true; displayBLEOnLEDMatrix(); } // Call this in the main loop. void BLEPoll(){ BLE.poll(); } // Call this from anywhere (sensor reading sketches mainly) you want to send data for a characteristic. void writeBLECharacteristicValue(String characteristic_uuid, int value){ if(characteristic_uuid == TEMPERATURE_CHARACTERISTIC_UUID){ Serial.println("Writing to temperature characteristic..."); temperature_characteristic.writeValue(value); } else if(characteristic_uuid == HUMIDITY_CHARACTERISTIC_UUID){ Serial.println("Writing to humidity characteristic..."); humidity_characteristic.writeValue(value); } else if(characteristic_uuid == GAS_CHARACTERISTIC_UUID){ Serial.println("Writing to gas characteristic..."); gas_characteristic.writeValue(value); } else if(characteristic_uuid == SOIL_MOISTURE_CHARACTERISTIC_UUID){ Serial.println("Writing to soil moisture characteristic..."); soil_moisture_characteristic.writeValue(value); } else if(characteristic_uuid == AMBIENT_LIGHT_CHARACTERISTIC_UUID){ Serial.println("Writing to ambient light characteristic..."); ambient_light_characteristic.writeValue(value); } else if(characteristic_uuid == DISTANCE_CHARACTERISTIC_UUID){ Serial.println("Writing to distance characteristic..."); distance_characteristic.writeValue(value); } else if(characteristic_uuid == MOTION_CHARACTERISTIC_UUID){ Serial.println("Writing to motion characteristic..."); motion_characteristic.writeValue(value); } } // THE CODE BELOW IS TO SET UP A CENTRAL DEVICE // WE WILL NOT BE NEEDING THIS FOR NOW SINCE THE BOARD IS A PERIPHERAL // AND THE IOT APP IS THE CENTRAL DEVICE. // void BLECentralSetup() { // // begin initialization // if (!BLE.begin()) { // Serial.println("starting Bluetooth® Low Energy module failed!"); // while (1); // } // Serial.println("Bluetooth® Low Energy Central - Peripheral Explorer"); // // start scanning for peripherals // BLE.scan(); // } // void scanForPeripherals() { // // check if a peripheral has been discovered // BLEDevice peripheral = BLE.available(); // if (peripheral) { // // discovered a peripheral, print out address, local name, and advertised service // Serial.print("Found "); // Serial.print(peripheral.address()); // Serial.print(" '"); // Serial.print(peripheral.localName()); // Serial.print("' "); // Serial.print(peripheral.advertisedServiceUuid()); // Serial.println(); // // check for peripheral's name // if (peripheral.localName() == "<PERIPHERAL_NAME>") { // // stop scanning // BLE.stopScan(); // explorerPeripheral(peripheral); // // peripheral disconnected, we are done // while (1) { // // do nothing // } // } // } // } // void explorerPeripheral(BLEDevice peripheral) { // // connect to the peripheral // Serial.println("Connecting ..."); // if (peripheral.connect()) { // Serial.println("Connected"); // } else { // Serial.println("Failed to connect!"); // return; // } // // discover peripheral attributes // Serial.println("Discovering attributes ..."); // if (peripheral.discoverAttributes()) { // Serial.println("Attributes discovered"); // } else { // Serial.println("Attribute discovery failed!"); // peripheral.disconnect(); // return; // } // // read and print device name of peripheral // Serial.println(); // Serial.print("Device name: "); // Serial.println(peripheral.deviceName()); // Serial.print("Appearance: 0x"); // Serial.println(peripheral.appearance(), HEX); // Serial.println(); // // loop the services of the peripheral and explore each // for (int i = 0; i < peripheral.serviceCount(); i++) { // BLEService service = peripheral.service(i); // exploreService(service); // } // Serial.println(); // // we are done exploring, disconnect // Serial.println("Disconnecting ..."); // peripheral.disconnect(); // Serial.println("Disconnected"); // } // void exploreService(BLEService service) { // // print the UUID of the service // Serial.print("Service "); // Serial.println(service.uuid()); // // loop the characteristics of the service and explore each // for (int i = 0; i < service.characteristicCount(); i++) { // BLECharacteristic characteristic = service.characteristic(i); // exploreCharacteristic(characteristic); // } // } // void exploreCharacteristic(BLECharacteristic characteristic) { // // print the UUID and properties of the characteristic // Serial.print("\tCharacteristic "); // Serial.print(characteristic.uuid()); // Serial.print(", properties 0x"); // Serial.print(characteristic.properties(), HEX); // // check if the characteristic is readable // if (characteristic.canRead()) { // // read the characteristic value // characteristic.read(); // if (characteristic.valueLength() > 0) { // // print out the value of the characteristic // Serial.print(", value 0x"); // printData(characteristic.value(), characteristic.valueLength()); // } // } // Serial.println(); // // loop the descriptors of the characteristic and explore each // for (int i = 0; i < characteristic.descriptorCount(); i++) { // BLEDescriptor descriptor = characteristic.descriptor(i); // exploreDescriptor(descriptor); // } // } // void exploreDescriptor(BLEDescriptor descriptor) { // // print the UUID of the descriptor // Serial.print("\t\tDescriptor "); // Serial.print(descriptor.uuid()); // // read the descriptor value // descriptor.read(); // // print out the value of the descriptor // Serial.print(", value 0x"); // printData(descriptor.value(), descriptor.valueLength()); // Serial.println(); // } // void printData(const unsigned char data[], int length) { // for (int i = 0; i < length; i++) { // unsigned char b = data[i]; // if (b < 16) { // Serial.print("0"); // } // Serial.print(b, HEX); // } // }