The idea for this project came as I wanted to add some backlight to my computer and desk so that it would be more cozy especially during the night. I wanted to be able to have more mods and have a finer control on the LEDs, so I chose a NeoPixels LED Strip, where I can control each LED of the Strip individually.
For the communication protocol between the ESP32 controller and my phone, I decided to use the Bluetooth LowEnergy protocol. I will use my phone as a remote control to send commands to the ESP32 controller and control the LED strip.
I used an OLED display as a way to better visualize the current status of the system, like if there is a device connected, if the lights are turned on or of, and what mode is currently selected.
// BLE settings #define SERVICE_UUID "LED12345-1234-1234-1234-123456789LED" #define CHARACTERISTIC_UUID "87654321-4321-4321-4321-LED987654321" BLECharacteristic *pCharacteristic; bool deviceConnected = false; std::string receivedValue; // BLE server callback class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true; }; void onDisconnect(BLEServer* pServer) { deviceConnected = false; } }; // BLE characteristic callback class MyCallbacks: public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pCharacteristic) { receivedValue = pCharacteristic->getValue(); if (receivedValue.length() > 0) { Serial.print("Received Value: "); Serial.println(receivedValue.c_str()); } } }; void setup() { Serial.begin(115200); // Initialize the NeoPixel library strip.begin(); // Set all pixels to 'off' strip.show(); // Initialize BLE BLEDevice::init("ESP32_LED_Control"); BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); BLEService *pService = pServer->createService(SERVICE_UUID); pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE ); pCharacteristic->setCallbacks(new MyCallbacks()); pCharacteristic->setValue("Hello, World!"); pService->start(); BLEAdvertising *pAdvertising = pServer->getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->setScanResponse(true); pAdvertising->setMinPreferred(0x06); pAdvertising->setMinPreferred(0x12); BLEDevice::startAdvertising(); Serial.println("Waiting for a client connection to notify..."); }
void loop() { // Check if a device was connected if (deviceConnected) { if (!receivedValue.empty()) { handleCommand(receivedValue); receivedValue = ""; } } } // Function that handles the commands got from the Phone via BLE void handleCommand(const std::string& command) { if (command == "off") { lightsOn = false; strip.clear(); strip.show(); } else { lightsOn = true; mode = command; } }
void loop() { // Check if the lights are turned on or off if (lightsOn) { unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= interval) { previousMillis = currentMillis; updateLEDs(); } } } // Function that handles the commands got from the Phone via BLE void handleCommand(const std::string& command) { if (command == "off") { lightsOn = false; strip.clear(); strip.show(); } else { lightsOn = true; mode = command; if (mode == "red") { setAllPixels(strip.Color(255, 0, 0)); } else if (mode == "blue") { setAllPixels(strip.Color(0, 0, 255)); } else if (mode == "yellow") { setAllPixels(strip.Color(255, 255, 0)); } else if (mode == "green") { setAllPixels(strip.Color(0, 255, 0)); } else if (mode == "purple") { setAllPixels(strip.Color(128, 0, 128)); } else if (mode == "orange") { setAllPixels(strip.Color(255, 100, 0)); // Adjusted towards red } else if (mode == "cyan") { setAllPixels(strip.Color(0, 255, 255)); } else if (command.find("-") != std::string::npos) { mode = "split"; std::string color1 = command.substr(0, command.find('-')); std::string color2 = command.substr(command.find('-') + 1); setSplitPixels(getColor(color1), getColor(color2)); } strip.show(); } } // Set all the pixels to one color void setAllPixels(uint32_t color) { for (int i = 0; i < NUM_LEDS; i++) { strip.setPixelColor(i, color); } } // Set half the pixels to one color and the otehr half to the other void setSplitPixels(uint32_t color1, uint32_t color2) { for (int i = 0; i < NUM_LEDS / 2; i++) { strip.setPixelColor(i, color1); } for (int i = NUM_LEDS / 2; i < NUM_LEDS; i++) { strip.setPixelColor(i, color2); } strip.show(); } uint32_t getColor(const std::string& color) { if (color == "red") return strip.Color(255, 0, 0); if (color == "blue") return strip.Color(0, 0, 255); if (color == "yellow") return strip.Color(255, 255, 0); if (color == "green") return strip.Color(0, 255, 0); if (color == "purple") return strip.Color(128, 0, 128); if (color == "orange") return strip.Color(242, 133, 0); if (color == "cyan") return strip.Color(0, 255, 255); return strip.Color(0, 0, 0); // Default to black if color not recognized } // Run the selected mode void updateLEDs() { if (mode == "cycle") { cycleColors(); } else if (mode == "rainbow") { rainbowCycle(); } else if (mode == "snake") { snakeMode(); } else if (mode == "random_blink") { randomBlink(); } } // Function to cycle through colors void cycleColors() { static int currentColor = 0; uint32_t colors[] = { strip.Color(255, 0, 0), // Red strip.Color(0, 0, 255), // Blue strip.Color(255, 255, 0), // Yellow strip.Color(0, 255, 0), // Green strip.Color(128, 0, 128), // Purple strip.Color(242, 132, 0), // Orange strip.Color(0, 255, 255) // Cyan }; setAllPixels(colors[currentColor]); currentColor = (currentColor + 1) % 7; strip.show(); } // Function that lights the LED strip in an evolving rainbow void rainbowCycle() { static uint16_t j = 0; for (int i = 0; i < strip.numPixels(); i++) { strip.setPixelColor(i, Wheel((i + j) & 255)); } strip.show(); j++; if (j >= 256) j = 0; } // Auxilary function that helps the rainbow function uint32_t Wheel(byte WheelPos) { WheelPos = 255 - WheelPos; if (WheelPos < 85) { return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3); } if (WheelPos < 170) { WheelPos -= 85; return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3); } WheelPos -= 170; return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0); } // Build a snake that slitters through the light strip void snakeMode() { static int head = 0; strip.clear(); for (int i = 0; i < 10; i++) { int pos = (head - i + NUM_LEDS) % NUM_LEDS; strip.setPixelColor(pos, strip.Color(0, 255 - i * 25, 0)); // Body is darker green } strip.show(); head = (head + 1) % NUM_LEDS; } // Function that makes the light strip to blink random LEDs with random colors void randomBlink() { strip.clear(); for (int i = 0; i < NUM_LEDS; i++) { if (random(10) < 2) { strip.setPixelColor(i, strip.Color(random(256), random(256), random(256))); } } strip.show(); }
// OLED display settings #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 32 #define OLED_RESET -1 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { // Initialize the OLED display // Address 0x3C for 128x32 OLED display if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F("SSD1306 allocation failed")); for(;;); } display.display(); delay(2000); // Pause for 2 seconds display.clearDisplay(); } void loop() { // Update the display with the current status updateDisplay(); } // Update the OLED display with the current status void updateDisplay() { display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); // Show if any device is connected display.setCursor(0, 0); display.print("Device: "); display.print(deviceConnected ? "Connected" : "Disconnected"); // Show if the lights are on or off display.setCursor(0, 10); display.print("Lights: "); display.print(lightsOn ? "On" : "Off"); // Show the current lighting mode display.setCursor(0, 20); display.print("Mode: "); display.print(mode.c_str()); display.display(); }
Initially, I wanted to use both WiFi and BLE to control the LED Strip, but I had to limit myself to just one of them, so I chose BLE. The issue was that the WiFi library was just too big in size, taking over 59% of the text memory on my development board, so I dropped it.
By dropping the WiFi features, I had to drop the automatic time triggering as I could not set the time. To set the time to the Local Time, i would have to use a NTP server, but without the WiFi library, I could not connect to one. One other option was to pass the time as a parameter when the code is flashed on the board, but this is not optimal. Other option is to pass it as a command through my phone, but this just destroys the purpose of it being automatic.
Lastly, I found to my surprise that the ESP32 development board I have bought has not a LTR308 module on it, so I was unable to set an automatic detection of light in the room and trigger the light when needed.
First of all, buy a better development board, one that has at leas a LTR308 module on it. After that, I will implement the automatic light detection function and have the lights start automatically when it becomes dark in the room. I also have to actually fix the connection that I screwed up and had to glue together. Lastly, I have to find a way to mount this thing.