ESP32 BLE-based LED strip

Introduction

Provide smart lighting to a home using an RGB LED strip and an ESP32. The ESP32 will expose a BLE server that can receive commands from any BLE client. To prove functionality I implemented some simple features such as selecting from a number of preset colors and adjusting brightness.

Hardware

  • ESP32-WROOM-32: 1 x ESP32 Sparrow development board
  • NeoPixel LED Strip: 1 x 5m NeoPixel LED strip, 300 LED's.
  • Breadboard: 1x Breadboard for securing connections.
  • Connecting Cables
  • USB-A to micro USB cable

Diagram

Actual Hardware

The NeoPixel LED strip need +5V and GND to power the LED's and the addresing is done using a single data pin, which I have connected to GPIO 17.

Software

  • Arduino IDE for development
  • Adafruit_NeoPixel library for easy LED strip manipulation
  • ArduinoBLE library for Bluetooth service.

Code Snippets

Setting up BLE service with 2 Characteristics, one for setting the color and one for setting the brightness. The color receives a string with the desired color (“red”, “blue”, “yellow”, “green”, “purple”, “orange”, “cyan”) and the brightness is a single 8-bit value where 0 is off and 255 is the highest possible brightness.

// BLE server callback
class MyServerCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
    deviceConnected = true;
  };
 
  void onDisconnect(BLEServer* pServer) {
    deviceConnected = false;
  }
};
 
/* BLE Characteristics */
BLECharacteristic *colorCharacteristic;
class ColorCallback: public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *colorCharacteristic) {
    receivedColor = colorCharacteristic->getValue();
    if (receivedColor.length() > 0) {
      Serial.print("Received Color: ");
      Serial.println(receivedColor.c_str());
    }
  }
};
 
BLECharacteristic *brightCharacteristic;
class BrightnessCallback: public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *brightCharacteristic) {
    receivedBrightness = brightCharacteristic->getValue().c_str()[0];
    Serial.print("Received Brightness: ");
    Serial.println(receivedBrightness, DEC);
  }
};
 
[ ... ]
 
// Initialize BLE
BLEDevice::init("ESP32_LED_Strip");
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
 
BLEService *bmeService = pServer->createService(SERVICE_UUID);
 
// Create colorCharacteristic
colorCharacteristic = bmeService->createCharacteristic(
                    COLOR_UUID,
                    BLECharacteristic::PROPERTY_READ |
                    BLECharacteristic::PROPERTY_WRITE
                );
colorCharacteristic->setCallbacks(new ColorCallback());
colorCharacteristic->setValue("Hello, World!");
 
// Create colorCharacteristic
brightCharacteristic = bmeService->createCharacteristic(
                    BRIGHTNESS_UUID,
                    BLECharacteristic::PROPERTY_READ |
                    BLECharacteristic::PROPERTY_WRITE
                );
brightCharacteristic->setCallbacks(new BrightnessCallback());
int defaultBrightness = 255;
brightCharacteristic->setValue(defaultBrightness);
 
// Start bme service.
bmeService->start();
 
// Start Advertising
pServer->getAdvertising()->start();
Serial.println("Waiting a client connection to notify...");

Setting up the LedStrip and control logic is straight-forward. We have global variables holding the current state of the strip that get update on BLECallback triggers. We also have a chgStrip boolean that prevents unneccesary updating of the LedStrip if no changes are currently performed.

// Initialize the NeoPixel library
strip.begin();
// Set all pixels to 'off'
strip.show();
 
// Set some default strip values;
actualBrightness = 128;
actualColor = strip.Color(0, 75, 0); // moderately bright green
chgStrip = true;
 
[ ... ]
 
// Check if a device was connected
if (deviceConnected) {
  if (!receivedColor.isEmpty()) {
    actualColor = getColor(receivedColor);
    receivedColor = "";
    chgStrip = true;
  }
 
  if (receivedBrightness != 0) {
    actualBrightness = receivedBrightness;
    receivedBrightness = 0;
    chgStrip = true;
  }
}
 
// Update Strip.
if (chgStrip) {
  strip.clear();
 
  for(int i=0; i<NUM_PIXELS; i++) { // For each pixel...
    strip.setPixelColor(i, actualColor);
  }
 
  strip.setBrightness(actualBrightness);
  strip.show();
 
  // Do not change strip next cycle
  chgStrip = false;
}

Challenges

Not having proper soldering tools leads to bad electrical contacts and makes it a nightmare to debug any issues. Thankfully, jamming a wire through the ESP32 DevBoard pins worked brilliantly, just don't ask me to move it anywhere.

Initially I wanted to use both BLE and a webpage hosted on the ESP32 itself to allow control of the LED strip from multiple sources but due to memory constraints on the ESP32 I had to choose only one (91% memory usage with the BLE implementation alone).

I find it very annoying how BLE handles characteristics, only having a predefined set of BLEUUID's available to pick and choose from. For example, the was no readily available UUID for brightness or color, just a generic “LED Array” or “Led Strip”.

I choose BLE due to the easy implementaion as baking-in a HTML page in the Arduino IDE seems janky to me. The right way to approach would be by installing a lightweight RTOS such as NuttX or freeRTOS and ditching the BLE for a more customizable webpage, as in our case battery and power constraints are irrelevant.

References

iothings/proiecte/2025sric/ledstrip.txt · Last modified: 2025/05/29 00:48 by andrei.besliu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0