/** PIXLED Bluetooth Demo
*
* Demonstrates a custom bluetooth application with a WS2811 LED strip
*
* Date : 2024-12-07
* Author: Marc-Antoine Doyon @madoyon
*
* Based on the NimBLE_Server Demo written by H2zero
*
*/
#include <NimBLEDevice.h>
#include <FastLED.h>
// Generated service and characteristic UUID
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
// How many leds in your strip?
#define NUM_LEDS 100
// Data GPIO on the ESP32
#define DATA_PIN 2
static NimBLEServer* pServer;
uint8_t dataToSend[6] = {0, 213,0,49,127,1};
uint8_t* dataReceived;
enum {
ISINITIALIZED,
COLOR_HEX_FF0000,
COLOR_HEX_00FF00,
COLOR_HEX_0000FF,
BRIGHTNESS,
STATE
};
// Defaults values
uint8_t brightness = 127; // 50% brightness
uint8_t colorHex[3] = {213,0,49}; //Red ish default color
uint8_t isInitialized = 0;
uint8_t state = 0;
// Define the array of leds
CRGB leds[NUM_LEDS];
/** None of these are required as they will be handled by the library with defaults. **
** Remove as you see fit for your needs */
class ServerCallbacks: public NimBLEServerCallbacks {
void onConnect(NimBLEServer* pServer) {
Serial.println("Client connected");
Serial.println("Multi-connect support: start advertising");
NimBLEDevice::startAdvertising();
};
/** Alternative onConnect() method to extract details of the connection.
* See: src/ble_gap.h for the details of the ble_gap_conn_desc struct.
*/
void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) {
Serial.print("Client address: ");
Serial.println(NimBLEAddress(desc->peer_ota_addr).toString().c_str());
/** We can use the connection handle here to ask for different connection parameters.
* Args: connection handle, min connection interval, max connection interval
* latency, supervision timeout.
* Units; Min/Max Intervals: 1.25 millisecond increments.
* Latency: number of intervals allowed to skip.
* Timeout: 10 millisecond increments, try for 5x interval time for best results.
*/
pServer->updateConnParams(desc->conn_handle, 24, 48, 0, 60);
};
void onDisconnect(NimBLEServer* pServer) {
Serial.println("Client disconnected - start advertising");
NimBLEDevice::startAdvertising();
dataToSend[ISINITIALIZED] = 0;
// pBeefCharacteristic->setValue(dataToSend);
};
void onMTUChange(uint16_t MTU, ble_gap_conn_desc* desc) {
Serial.printf("MTU updated: %u for connection ID: %u\n", MTU, desc->conn_handle);
};
/********************* Security handled here **********************
****** Note: these are the same return values as defaults ********/
uint32_t onPassKeyRequest(){
Serial.println("Server Passkey Request");
/** This should return a random 6 digit number for security
* or make your own static passkey as done here.
*/
return 123456;
};
bool onConfirmPIN(uint32_t pass_key){
Serial.print("The passkey YES/NO number: ");Serial.println(pass_key);
/** Return false if passkeys don't match. */
return true;
};
void onAuthenticationComplete(ble_gap_conn_desc* desc){
/** Check that encryption was successful, if not we disconnect the client */
if(!desc->sec_state.encrypted) {
NimBLEDevice::getServer()->disconnect(desc->conn_handle);
Serial.println("Encrypt connection failed - disconnecting client");
return;
}
Serial.println("Starting BLE work!");
};
};
/** Handler class for characteristic actions */
class CharacteristicCallbacks: public NimBLECharacteristicCallbacks {
void onRead(NimBLECharacteristic* pCharacteristic){
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(": onRead(), value: ");
std::string value = pCharacteristic->getValue();
uint8_t intValue = 0;
Serial.println(value.c_str());
};
void onWrite(NimBLECharacteristic* pCharacteristic) {
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(": onWrite(), value: ");
std::string value = pCharacteristic->getValue();
uint8_t intValue = 0;
Serial.println(value.c_str());
for(uint8_t i = 0; i < value.length(); i++)
{
intValue = static_cast<uint8_t>(value[i]);
Serial.println(intValue);
}
// Extract the data from the bluetooth values
brightness = value[BRIGHTNESS];
state = value[STATE];
colorHex[0] = value[COLOR_HEX_FF0000];
colorHex[1] = value[COLOR_HEX_00FF00];
colorHex[2] = value[COLOR_HEX_0000FF];
isInitialized = value[ISINITIALIZED];
};
/** Called before notification or indication is sent,
* the value can be changed here before sending if desired.
*/
void onNotify(NimBLECharacteristic* pCharacteristic) {
Serial.println("Sending notification to clients");
};
/** The status returned in status is defined in NimBLECharacteristic.h.
* The value returned in code is the NimBLE host return code.
*/
void onStatus(NimBLECharacteristic* pCharacteristic, Status status, int code) {
String str = ("Notification/Indication status code: ");
str += status;
str += ", return code: ";
str += code;
str += ", ";
str += NimBLEUtils::returnCodeToString(code);
Serial.println(str);
};
void onSubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc, uint16_t subValue) {
String str = "Client ID: ";
str += desc->conn_handle;
str += " Address: ";
str += std::string(NimBLEAddress(desc->peer_ota_addr)).c_str();
if(subValue == 0) {
str += " Unsubscribed to ";
}else if(subValue == 1) {
str += " Subscribed to notfications for ";
} else if(subValue == 2) {
str += " Subscribed to indications for ";
} else if(subValue == 3) {
str += " Subscribed to notifications and indications for ";
}
str += std::string(pCharacteristic->getUUID()).c_str();
Serial.println(str);
};
};
/** Handler class for descriptor actions */
class DescriptorCallbacks : public NimBLEDescriptorCallbacks {
void onWrite(NimBLEDescriptor* pDescriptor) {
std::string dscVal = pDescriptor->getValue();
Serial.print("Descriptor witten value:");
Serial.println(dscVal.c_str());
Serial.println(dscVal[0]);
};
void onRead(NimBLEDescriptor* pDescriptor) {
Serial.print(pDescriptor->getUUID().toString().c_str());
Serial.print(pDescriptor->getUUID().toString()[0]);
Serial.println(" Descriptor read");
};
};
/** Define callback instances globally to use for multiple Charateristics \ Descriptors */
static DescriptorCallbacks dscCallbacks;
static CharacteristicCallbacks chrCallbacks;
//Synchronisation variables
unsigned long previousLEDTask = 0;
unsigned long previousBluetoothTask = 0;
void taskLED();
void taskBluetooth();
void setup() {
Serial.begin(9600);
Serial.println("Starting NimBLE Server");
/** sets device name */
NimBLEDevice::init("NimBLE-Arduino");
/** Optional: set the transmit power, default is 3db */
#ifdef ESP_PLATFORM
NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */
#else
NimBLEDevice::setPower(9); /** +9db */
#endif
/** Set the IO capabilities of the device, each option will trigger a different pairing method.
* BLE_HS_IO_DISPLAY_ONLY - Passkey pairing
* BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing
* BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing
*/
//NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY); // use passkey
//NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO); //use numeric comparison
/** 2 different ways to set security - both calls achieve the same result.
* no bonding, no man in the middle protection, secure connections.
*
* These are the default values, only shown here for demonstration.
*/
//NimBLEDevice::setSecurityAuth(false, false, true);
NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/ BLE_SM_PAIR_AUTHREQ_SC);
pServer = NimBLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
NimBLEService* pDeadService = pServer->createService(SERVICE_UUID);
NimBLECharacteristic* pBeefCharacteristic = pDeadService->createCharacteristic(
CHARACTERISTIC_UUID,
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE |
NIMBLE_PROPERTY::NOTIFY |
NIMBLE_PROPERTY::INDICATE
/** Require a secure connection for read and write access */
// NIMBLE_PROPERTY::READ_ENC | // only allow reading if paired / encrypted
// NIMBLE_PROPERTY::WRITE_ENC // only allow writing if paired / encrypted
);
pBeefCharacteristic->setValue(dataToSend);
pBeefCharacteristic->setCallbacks(&chrCallbacks);
/** Start the services when finished creating all Characteristics and Descriptors */
pDeadService->start();
NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
/** Add the services to the advertisment data **/
pAdvertising->addServiceUUID(pDeadService->getUUID());
/** If your device is battery powered you may consider setting scan response
* to false as it will extend battery life at the expense of less data sent.
*/
pAdvertising->setScanResponse(true);
pAdvertising->start();
Serial.println("Advertising Started");
// Uncomment/edit one of the following lines for your leds arrangement.
// ## Clockless types ##
// FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS); // GRB ordering is assumed
// FastLED.addLeds<SM16703, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<TM1829, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<TM1812, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<TM1809, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<TM1804, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<TM1803, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<UCS1903, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<UCS1903B, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<UCS1904, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<UCS2903, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<WS2812, DATA_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// FastLED.addLeds<WS2852, DATA_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// FastLED.addLeds<GS1903, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<SK6812, DATA_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// FastLED.addLeds<SK6822, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<APA106, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<PL9823, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<SK6822, DATA_PIN, RGB>(leds, NUM_LEDS);
FastLED.addLeds<WS2811, DATA_PIN, GRB>(leds, NUM_LEDS);
// FastLED.addLeds<WS2813, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<APA104, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<WS2811_400, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<GE8822, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<GW6205, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<GW6205_400, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<LPD1886, DATA_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<LPD1886_8BIT, DATA_PIN, RGB>(leds, NUM_LEDS);
// ## Clocked (SPI) types ##
// FastLED.addLeds<LPD6803, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// FastLED.addLeds<LPD8806, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// FastLED.addLeds<WS2801, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<WS2803, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<SM16716, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS);
// FastLED.addLeds<P9813, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // BGR ordering is typical
// FastLED.addLeds<DOTSTAR, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // BGR ordering is typical
// FastLED.addLeds<APA102, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // BGR ordering is typical
// FastLED.addLeds<SK9822, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // BGR ordering is typical
}
void loop() {
/** Do your thing here, this just spams notifications to all connected clients */
if(millis() - previousBluetoothTask >= 1000)
{
taskBluetooth();
previousBluetoothTask = millis();
}
if(millis() - previousLEDTask >= 100)
{
taskLED();
previousLEDTask = millis();
}
}
void taskBluetooth()
{
if(pServer->getConnectedCount()) {
NimBLEService* pSvc = pServer->getServiceByUUID(SERVICE_UUID);
if(pSvc) {
NimBLECharacteristic* pChr = pSvc->getCharacteristic(CHARACTERISTIC_UUID);
if(pChr) {
pChr->notify(true);
// Prepare the initialization values if the connection is lost with the device
if(dataToSend[ISINITIALIZED] == 0) {
dataToSend[BRIGHTNESS] = brightness;
dataToSend[COLOR_HEX_0000FF] = colorHex[2];
dataToSend[COLOR_HEX_00FF00] = colorHex[1];
dataToSend[COLOR_HEX_FF0000] = colorHex[0];
dataToSend[STATE] = state;
pChr->setValue(dataToSend);
}
}
}
}
}
void taskLED()
{
// Update brightness
FastLED.setBrightness(brightness);
// Update color
CRGB color = CRGB(colorHex[0], colorHex[1], colorHex[2]);
// Update the state
if(state == 0) color = CRGB::Black;
// Set the color to the all LEDs
fill_solid(leds, NUM_LEDS, color);
FastLED.show();
}
Comments
Please log in or sign up to comment.