This shows you the differences between two versions of the page.
iothings:proiecte:2025sric:ledstrip [2025/05/28 23:39] andrei.besliu [ESP32 Web-based LED strip] |
iothings:proiecte:2025sric:ledstrip [2025/05/29 00:48] (current) andrei.besliu [ESP32 Web-based LED strip] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== ESP32 Web-based LED strip ====== | + | ====== ESP32 BLE-based LED strip ====== |
* Author: Besliu Andrei-Cornel | * Author: Besliu Andrei-Cornel | ||
Line 5: | Line 5: | ||
* Master: SCPD | * Master: SCPD | ||
* Video Link: https://youtu.be/Q6bHqYo0OGY | * Video Link: https://youtu.be/Q6bHqYo0OGY | ||
- | * Source Code: TBD | + | * Source Code: {{:iothings:proiecte:2025sric:besliu_andrei_iot.zip}} |
====== Introduction ====== | ====== Introduction ====== | ||
- | Provide smart lighting to a home using an RGB LED strip and an ESP32. The ESP32 will act as a web server, accessible from devices such as a smartphone or desktop PC. | + | 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. |
- | ====== Context ====== | ||
====== Hardware ====== | ====== 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 ====== | ||
+ | {{ :iothings:proiecte:2025sric:diagram_ledstrip.png }} | ||
+ | |||
+ | ====== Actual Hardware ====== | ||
+ | |||
+ | {{ :iothings:proiecte:2025sric:actualHw_ledStrip.jpeg?500 |}} | ||
+ | |||
+ | 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 ====== | ====== Software ====== | ||
- | === Code Snippets === | + | * Arduino IDE for development |
+ | * Adafruit_NeoPixel library for easy LED strip manipulation | ||
+ | * ArduinoBLE library for Bluetooth service. | ||
- | ==== Firebase ==== | + | ====== 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. | ||
+ | <code C++> | ||
+ | // 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..."); | ||
+ | |||
+ | </code> | ||
+ | |||
+ | 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. | ||
+ | <code c++> | ||
+ | // 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; | ||
+ | } | ||
+ | |||
+ | </code> | ||
====== Challenges ====== | ====== Challenges ====== | ||
- | ====== References ====== | + | 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 ====== | ||
+ | * Adafruit_NeoPixel: https://github.com/adafruit/Adafruit_NeoPixel | ||
+ | * ArduinoBle: https://docs.arduino.cc/libraries/arduinoble/ | ||
+ | * Our OCW labs. | ||
+ | * Some helpful StackOverflow threads. |