From 01a54cdc6374dd7ba282052b9718895603dd7ba8 Mon Sep 17 00:00:00 2001 From: Max Richter Date: Sat, 30 Dec 2023 11:18:29 +0100 Subject: [PATCH] feat: add ble server-client code --- ble_client/ble_client.ino | 173 ++++++++++++++++++++++++++++++++++++++ ble_server/ble_server.ino | 138 ++++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 ble_client/ble_client.ino create mode 100644 ble_server/ble_server.ino diff --git a/ble_client/ble_client.ino b/ble_client/ble_client.ino new file mode 100644 index 0000000..68b4cb4 --- /dev/null +++ b/ble_client/ble_client.ino @@ -0,0 +1,173 @@ +/********* + Rui Santos + Complete instructions at https://RandomNerdTutorials.com/esp32-ble-server-client/ + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +*********/ + +#include "BLEDevice.h" + +//Default Temperature is in Celsius +//Comment the next line for Temperature in Fahrenheit +#define temperatureCelsius + +//BLE Server name (the other ESP32 name running the server sketch) +#define bleServerName "ESP32" +#define bleServerAddress "64:e8:33:da:a2:b6" + +/* UUID's of the service, characteristic that we want to read*/ +// BLE Service +static BLEUUID bmeServiceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); + +//Temperature Celsius Characteristic +static BLEUUID temperatureCharacteristicUUID("cba1d466-344c-4be3-ab3f-189f80dd7518"); + +//Flags stating if should begin connecting and if the connection is up +static boolean doConnect = false; +static boolean connected = false; + +//Address of the peripheral device. Address will be found during scanning... +static BLEAddress *pServerAddress; + +//Characteristicd that we want to read +static BLERemoteCharacteristic* temperatureCharacteristic; + +//Activate notify +const uint8_t notificationOn[] = {0x1, 0x0}; +const uint8_t notificationOff[] = {0x0, 0x0}; + +//Variables to store temperature and humidity +char* temperatureChar; + +//Flags to check whether new temperature and humidity readings are available +boolean newTemperature = false; + +//Connect to the BLE Server that has the name, Service, and Characteristics +bool connectToServer(BLEAddress pAddress) { + BLEClient* pClient = BLEDevice::createClient(); + + // 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(bmeServiceUUID); + if (pRemoteService == nullptr) { + Serial.print("Failed to find our service UUID: "); + Serial.println(bmeServiceUUID.toString().c_str()); + return (false); + } + + // Obtain a reference to the characteristics in the service of the remote BLE server. + temperatureCharacteristic = pRemoteService->getCharacteristic(temperatureCharacteristicUUID); + + if (temperatureCharacteristic == nullptr) { + Serial.print("Failed to find our characteristic UUID"); + return false; + } + Serial.println(" - Found our characteristics"); + + //Assign callback functions for the Characteristics + temperatureCharacteristic->registerForNotify(temperatureNotifyCallback); + return true; +} + +//Callback function that gets called, when another device's advertisement has been received +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + void onResult(BLEAdvertisedDevice advertisedDevice) { + Serial.print("Found Device: "); + Serial.println(advertisedDevice.getAddress().toString().c_str()); + if (advertisedDevice.getAddress().toString() == bleServerAddress) { //Check if the name of the advertiser matches + advertisedDevice.getScan()->stop(); //Scan can be stopped, we found what we are looking for + pServerAddress = new BLEAddress(advertisedDevice.getAddress()); //Address of advertiser is the one we need + doConnect = true; //Set indicator, stating that we are ready to connect + Serial.println("Device found. Connecting!"); + } + } +}; + +int last_update = 0; + +void deserialize_int16_t(uint8_t* buffer, int16_t* values, size_t count) { + for (size_t i = 0; i < count; ++i) { + values[i] = (int16_t)((buffer[i * 2 + 1] << 8) | buffer[i * 2]); + } +} + +//When the BLE Server sends a new temperature reading with the notify property +static void temperatureNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, + uint8_t* pData, size_t length, bool isNotify) { + // Check if the length of the received data is as expected + if (length % 2 != 0) { + // Handle error: The length should be even for 2-byte integers + Serial.println("Error: Invalid data length"); + return; + } + + // Assuming you know the number of int16_t values in the received data + size_t count = length / 2; + int16_t receivedValues[count]; + + // Deserialize the received data + deserialize_int16_t(pData, receivedValues, count); + + // Process the received values as needed + for (size_t i = 0; i < count; ++i) { + Serial.print(receivedValues[i]); + Serial.print(" "); + } + Serial.println(); +} + +void setup() { + + //Start serial communication + Serial.begin(115200); + Serial.println("Starting Arduino BLE Client application..."); + + //Init BLE device + 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(30); +} + +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) { + if (connectToServer(*pServerAddress)) { + Serial.println("We are now connected to the BLE Server."); + //Activate the Notify property of each Characteristic + temperatureCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true); + connected = true; + } else { + Serial.println("We have failed to connect to the server; Restart your device to scan for nearby BLE server again."); + } + doConnect = false; + } + //if new temperature readings are available, print in the OLED + if (newTemperature){ + newTemperature = false; + } + + // Check for a reset command from serial + if (Serial.available() > 0) { + char command = Serial.read(); + + // Check if the received command is 'R' + if (command == 'R' || command == 'r') { + Serial.println("Resetting..."); + delay(1000); // Optional delay for visibility in the Serial Monitor + ESP.restart(); // This function resets the ESP32 + } + } + + delay(1000); // Delay a second between loops. +} diff --git a/ble_server/ble_server.ino b/ble_server/ble_server.ino new file mode 100644 index 0000000..8a7cb91 --- /dev/null +++ b/ble_server/ble_server.ino @@ -0,0 +1,138 @@ +#include +#include +#include +#include +#include +#include "MPU6050.h" + +//BLE server name +#define bleServerName "ESP32" + +int16_t ax, ay, az; +int16_t gx, gy, gz; + +bool deviceConnected = false; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +#define SERVICE_UUID "91bad492-b950-4226-aa2b-4ede9fa42f59" + +MPU6050 accelgyro; +BLEServer *pServer; +BLECharacteristic bmeTemperatureCelsiusCharacteristics("cba1d466-344c-4be3-ab3f-189f80dd7518", BLECharacteristic::PROPERTY_NOTIFY); +BLEDescriptor bmeTemperatureCelsiusDescriptor(BLEUUID((uint16_t)0x2902)); + +//Setup callbacks onConnect and onDisconnect +class MyServerCallbacks: public BLEServerCallbacks { + void onConnect(BLEServer* pServer) { + deviceConnected = true; + Serial.println("Device Connected"); + }; + void onDisconnect(BLEServer* pServer) { + deviceConnected = false; + Serial.println("Device Disconnected"); + } +}; + + +void setup() { + // Start serial communication + Serial.begin(115200); + + // setup software i2c + Wire.begin(); + + // initialize device + Serial.println("Initializing I2C devices..."); + accelgyro.initialize(); + + // verify connection + Serial.println("Testing device connections..."); + Serial.println(accelgyro.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed"); + + // Create the BLE Device + BLEDevice::init(bleServerName); + + // Create the BLE Server + pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + // Create the BLE Service + BLEService *bmeService = pServer->createService(SERVICE_UUID); + + // Create BLE Characteristics and Create a BLE Descriptor + bmeService->addCharacteristic(&bmeTemperatureCelsiusCharacteristics); + bmeTemperatureCelsiusDescriptor.setValue("BME temperature Celsius"); + bmeTemperatureCelsiusCharacteristics.addDescriptor(&bmeTemperatureCelsiusDescriptor); + + // Start the service + bmeService->start(); + + // Start advertising + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); + pServer->getAdvertising()->start(); + Serial.println("Waiting a client connection to notify..."); +} + +int idx = 0; + +void serialize_int16_t(uint8_t* buffer, int16_t* values, size_t count) { + for (size_t i = 0; i < count; ++i) { + buffer[i * 2] = (uint8_t)(values[i] & 0xFF); + buffer[i * 2 + 1] = (uint8_t)((values[i] >> 8) & 0xFF); + } +} + + +void loop() { + + idx++; + + if (deviceConnected) { + + // read raw accel/gyro measurements from device + accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); + + int16_t values[] = {ax, ay, az, gx, gy, gz}; + size_t count = sizeof(values) / sizeof(values[0]); + + // Serialize + uint8_t serializedBuffer[count * 2]; + serialize_int16_t(serializedBuffer, values, count); + + // Set characteristic value and notify connected client + bmeTemperatureCelsiusCharacteristics.setValue(serializedBuffer, count * 2); + bmeTemperatureCelsiusCharacteristics.notify(); + if (idx % 20 == 0 && false) { + // display tab-separated accel/gyro x/y/z values + Serial.print("a/g:\t"); + Serial.print(ax); + Serial.print("\t"); + Serial.print(ay); + Serial.print("\t"); + Serial.print(az); + Serial.print("\t"); + Serial.print(gx); + Serial.print("\t"); + Serial.print(gy); + Serial.print("\t"); + Serial.println(gz); + } + + } + + // Check for a reset command from serial + if (Serial.available() > 0) { + char command = Serial.read(); + + // Check if the received command is 'R' + if (command == 'R' || command == 'r') { + Serial.println("Resetting..."); + delay(1000); // Optional delay for visibility in the Serial Monitor + ESP.restart(); // This function resets the ESP32 + } + } + + delay(20); +}